diff --git a/extensions/synthetic/onboard.test.ts b/extensions/synthetic/onboard.test.ts index a2d55a5be2d..056477b7d87 100644 --- a/extensions/synthetic/onboard.test.ts +++ b/extensions/synthetic/onboard.test.ts @@ -1,5 +1,5 @@ import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard"; -import { SYNTHETIC_DEFAULT_MODEL_ID } from "openclaw/plugin-sdk/synthetic"; +import { SYNTHETIC_DEFAULT_MODEL_REF as SYNTHETIC_DEFAULT_MODEL_REF_PUBLIC } from "openclaw/plugin-sdk/synthetic"; import { describe, expect, it } from "vitest"; import { createLegacyProviderConfig } from "../../test/helpers/plugins/onboard-config.js"; import { @@ -16,7 +16,7 @@ describe("synthetic onboard", () => { api: "anthropic-messages", }); expect(resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model)).toBe( - SYNTHETIC_DEFAULT_MODEL_REF, + SYNTHETIC_DEFAULT_MODEL_REF_PUBLIC, ); }); @@ -32,6 +32,6 @@ describe("synthetic onboard", () => { expect(cfg.models?.providers?.synthetic?.apiKey).toBe("old-key"); const ids = cfg.models?.providers?.synthetic?.models.map((m) => m.id); expect(ids).toContain("old-model"); - expect(ids).toContain(SYNTHETIC_DEFAULT_MODEL_ID); + expect(ids).toContain(SYNTHETIC_DEFAULT_MODEL_REF.replace(/^synthetic\//, "")); }); }); diff --git a/src/tasks/task-executor-policy.test.ts b/src/tasks/task-executor-policy.test.ts index 19469002a8b..766dbfc3de6 100644 --- a/src/tasks/task-executor-policy.test.ts +++ b/src/tasks/task-executor-policy.test.ts @@ -14,8 +14,9 @@ function createTask(partial: Partial): TaskRecord { return { taskId: partial.taskId ?? "task-1", runtime: partial.runtime ?? "acp", - ownerKey: partial.ownerKey ?? "agent:main:main", - scopeKind: "session", + requesterSessionKey: partial.requesterSessionKey ?? partial.ownerKey ?? "agent:main:main", + ownerKey: partial.ownerKey ?? partial.requesterSessionKey ?? "agent:main:main", + scopeKind: partial.scopeKind ?? "session", task: partial.task ?? "Investigate issue", status: partial.status ?? "running", deliveryStatus: partial.deliveryStatus ?? "pending", diff --git a/src/tasks/task-executor.ts b/src/tasks/task-executor.ts index fd984ed638b..958c4289a53 100644 --- a/src/tasks/task-executor.ts +++ b/src/tasks/task-executor.ts @@ -14,6 +14,7 @@ import type { TaskNotifyPolicy, TaskRecord, TaskRuntime, + TaskScopeKind, TaskStatus, TaskTerminalOutcome, } from "./task-registry.types.js"; @@ -21,7 +22,9 @@ import type { export function createQueuedTaskRun(params: { runtime: TaskRuntime; sourceId?: string; - requesterSessionKey: string; + requesterSessionKey?: string; + ownerKey?: string; + scopeKind?: TaskScopeKind; requesterOrigin?: TaskDeliveryState["requesterOrigin"]; childSessionKey?: string; parentTaskId?: string; @@ -42,7 +45,9 @@ export function createQueuedTaskRun(params: { export function createRunningTaskRun(params: { runtime: TaskRuntime; sourceId?: string; - requesterSessionKey: string; + requesterSessionKey?: string; + ownerKey?: string; + scopeKind?: TaskScopeKind; requesterOrigin?: TaskDeliveryState["requesterOrigin"]; childSessionKey?: string; parentTaskId?: string; diff --git a/src/tasks/task-registry.audit.test.ts b/src/tasks/task-registry.audit.test.ts index 583d5d21a46..71cdbcf451f 100644 --- a/src/tasks/task-registry.audit.test.ts +++ b/src/tasks/task-registry.audit.test.ts @@ -6,8 +6,9 @@ function createTask(partial: Partial): TaskRecord { return { taskId: partial.taskId ?? "task-1", runtime: partial.runtime ?? "acp", - ownerKey: partial.ownerKey ?? "agent:main:main", - scopeKind: "session", + requesterSessionKey: partial.requesterSessionKey ?? partial.ownerKey ?? "agent:main:main", + ownerKey: partial.ownerKey ?? partial.requesterSessionKey ?? "agent:main:main", + scopeKind: partial.scopeKind ?? "session", task: partial.task ?? "Background task", status: partial.status ?? "queued", deliveryStatus: partial.deliveryStatus ?? "pending", diff --git a/src/tasks/task-registry.store.sqlite.ts b/src/tasks/task-registry.store.sqlite.ts index e3c9a0f5fa5..f00e792b55a 100644 --- a/src/tasks/task-registry.store.sqlite.ts +++ b/src/tasks/task-registry.store.sqlite.ts @@ -95,6 +95,7 @@ function rowToTaskRecord(row: TaskRegistryRow): TaskRecord { taskId: row.task_id, runtime: row.runtime, ...(row.source_id ? { sourceId: row.source_id } : {}), + requesterSessionKey: row.scope_kind === "system" ? "" : row.owner_key, ownerKey: row.owner_key, scopeKind: row.scope_kind, ...(row.child_session_key ? { childSessionKey: row.child_session_key } : {}), diff --git a/src/tasks/task-registry.store.test.ts b/src/tasks/task-registry.store.test.ts index 3ef4025db69..61653e30201 100644 --- a/src/tasks/task-registry.store.test.ts +++ b/src/tasks/task-registry.store.test.ts @@ -19,6 +19,7 @@ function createStoredTask(): TaskRecord { taskId: "task-restored", runtime: "acp", sourceId: "run-restored", + requesterSessionKey: "agent:main:main", ownerKey: "agent:main:main", scopeKind: "session", childSessionKey: "agent:codex:acp:restored", diff --git a/src/tasks/task-registry.test.ts b/src/tasks/task-registry.test.ts index 41e04c5611e..e0a9fffd866 100644 --- a/src/tasks/task-registry.test.ts +++ b/src/tasks/task-registry.test.ts @@ -1051,6 +1051,8 @@ describe("task-registry", () => { taskId: "task-missing-cleanup", runtime: "cron", requesterSessionKey: "", + ownerKey: "system:cron:task-missing-cleanup", + scopeKind: "system", runId: "run-maintenance-cleanup", task: "Finished cron", status: "failed", @@ -1098,6 +1100,8 @@ describe("task-registry", () => { taskId: "task-audit-summary", runtime: "acp", requesterSessionKey: "agent:main:main", + ownerKey: "agent:main:main", + scopeKind: "session", runId: "run-audit-summary", task: "Hung task", status: "running", diff --git a/src/tasks/task-registry.ts b/src/tasks/task-registry.ts index c04a3ec1870..217cb404f7c 100644 --- a/src/tasks/task-registry.ts +++ b/src/tasks/task-registry.ts @@ -35,6 +35,7 @@ import type { TaskRegistrySummary, TaskRegistrySnapshot, TaskRuntime, + TaskScopeKind, TaskStatus, TaskTerminalOutcome, } from "./task-registry.types.js"; @@ -97,6 +98,14 @@ function persistTaskRegistry() { function persistTaskUpsert(task: TaskRecord) { const store = getTaskRegistryStore(); + const deliveryState = taskDeliveryStates.get(task.taskId); + if (store.upsertTaskWithDeliveryState) { + store.upsertTaskWithDeliveryState({ + task, + ...(deliveryState ? { deliveryState } : {}), + }); + return; + } if (store.upsertTask) { store.upsertTask(task); return; @@ -109,6 +118,10 @@ function persistTaskUpsert(task: TaskRecord) { function persistTaskDelete(taskId: string) { const store = getTaskRegistryStore(); + if (store.deleteTaskWithDeliveryState) { + store.deleteTaskWithDeliveryState(taskId); + return; + } if (store.deleteTask) { store.deleteTask(taskId); return; @@ -159,6 +172,35 @@ function ensureNotifyPolicy(params: { return deliveryStatus === "not_applicable" ? "silent" : "done_only"; } +function resolveTaskScopeKind(params: { + scopeKind?: TaskScopeKind; + requesterSessionKey: string; +}): TaskScopeKind { + if (params.scopeKind) { + return params.scopeKind; + } + return params.requesterSessionKey.trim() ? "session" : "system"; +} + +function resolveTaskRequesterSessionKey(params: { + requesterSessionKey?: string; + ownerKey?: string; + scopeKind?: TaskScopeKind; +}): string { + const requesterSessionKey = params.requesterSessionKey?.trim(); + if (requesterSessionKey) { + return requesterSessionKey; + } + if (params.scopeKind === "system") { + return ""; + } + return params.ownerKey?.trim() ?? ""; +} + +function resolveTaskOwnerKey(params: { requesterSessionKey: string; ownerKey?: string }): string { + return params.ownerKey?.trim() || params.requesterSessionKey.trim(); +} + function normalizeTaskSummary(value: string | null | undefined): string | undefined { const normalized = value?.replace(/\s+/g, " ").trim(); return normalized || undefined; @@ -989,7 +1031,9 @@ function ensureListener() { export function createTaskRecord(params: { runtime: TaskRuntime; sourceId?: string; - requesterSessionKey: string; + requesterSessionKey?: string; + ownerKey?: string; + scopeKind?: TaskScopeKind; requesterOrigin?: TaskDeliveryState["requesterOrigin"]; childSessionKey?: string; parentTaskId?: string; @@ -1009,25 +1053,39 @@ export function createTaskRecord(params: { terminalOutcome?: TaskTerminalOutcome | null; }): TaskRecord { ensureTaskRegistryReady(); - const existing = findExistingTaskForCreate(params); + const requesterSessionKey = resolveTaskRequesterSessionKey(params); + const scopeKind = resolveTaskScopeKind({ + scopeKind: params.scopeKind, + requesterSessionKey, + }); + const ownerKey = resolveTaskOwnerKey({ + requesterSessionKey, + ownerKey: params.ownerKey, + }); + const existing = findExistingTaskForCreate({ + ...params, + requesterSessionKey, + }); if (existing) { return mergeExistingTaskForCreate(existing, params); } const now = Date.now(); const taskId = crypto.randomUUID(); const status = normalizeTaskStatus(params.status); - const deliveryStatus = params.deliveryStatus ?? ensureDeliveryStatus(params.requesterSessionKey); + const deliveryStatus = params.deliveryStatus ?? ensureDeliveryStatus(requesterSessionKey); const notifyPolicy = ensureNotifyPolicy({ notifyPolicy: params.notifyPolicy, deliveryStatus, - requesterSessionKey: params.requesterSessionKey, + requesterSessionKey, }); const lastEventAt = params.lastEventAt ?? params.startedAt ?? now; const record: TaskRecord = { taskId, runtime: params.runtime, sourceId: params.sourceId?.trim() || undefined, - requesterSessionKey: params.requesterSessionKey, + requesterSessionKey, + ownerKey, + scopeKind, childSessionKey: params.childSessionKey, parentTaskId: params.parentTaskId?.trim() || undefined, agentId: params.agentId?.trim() || undefined, @@ -1376,6 +1434,14 @@ export function findLatestTaskForSessionKey(sessionKey: string): TaskRecord | un return task ? cloneTaskRecord(task) : undefined; } +export function findLatestTaskForOwnerKey(ownerKey: string): TaskRecord | undefined { + return findLatestTaskForSessionKey(ownerKey); +} + +export function findLatestTaskForRelatedSessionKey(sessionKey: string): TaskRecord | undefined { + return findLatestTaskForSessionKey(sessionKey); +} + export function listTasksForSessionKey(sessionKey: string): TaskRecord[] { ensureTaskRegistryReady(); const key = normalizeSessionIndexKey(sessionKey); @@ -1402,6 +1468,14 @@ export function listTasksForSessionKey(sessionKey: string): TaskRecord[] { .map(({ insertionIndex: _, ...task }) => task); } +export function listTasksForOwnerKey(ownerKey: string): TaskRecord[] { + return listTasksForSessionKey(ownerKey); +} + +export function listTasksForRelatedSessionKey(sessionKey: string): TaskRecord[] { + return listTasksForSessionKey(sessionKey); +} + export function resolveTaskForLookupToken(token: string): TaskRecord | undefined { const lookup = token.trim(); if (!lookup) { diff --git a/src/tasks/task-registry.types.ts b/src/tasks/task-registry.types.ts index 677a25f4371..d31e3d2c884 100644 --- a/src/tasks/task-registry.types.ts +++ b/src/tasks/task-registry.types.ts @@ -54,6 +54,7 @@ export type TaskRecord = { taskId: string; runtime: TaskRuntime; sourceId?: string; + requesterSessionKey: string; ownerKey: string; scopeKind: TaskScopeKind; childSessionKey?: string;