@xopcai/xopc 0.0.199 → 0.0.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Bt70bZjv.js → agent-avatar-dicebear-eMarz29u.js} +1 -1
- package/dist/gateway/static/root/assets/agents-DeLW-454.js +68 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-B4x7JfL8.js → app-management-settings-panel-yfb68wq9.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-Dwdagf-Q.js → appearance-settings-B8SPknFM.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-DkTiesxe.js → apps-page-Bmor22Ny.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-Cv0VXKLm.js → archive-plugin-DmYRiOP3.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CgidKrXn.js → attachment-load-D66JvcPi.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-Q4e8m11h.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DN3CQgIq.js → binary-plugins-ChIsZArT.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-BwkSl1F_.js → block-editor-Ck-k_d0Z.js} +5 -5
- package/dist/gateway/static/root/assets/browser-settings-page--uVL73Sb.js +8 -0
- package/dist/gateway/static/root/assets/{browser-workflows-page-C_I31wLm.js → browser-workflows-page-BC5jm1zL.js} +2 -2
- package/dist/gateway/static/root/assets/capabilities-settings-panel-DXpf3QYN.js +7 -0
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-NAjCsrTi.js +3 -0
- package/dist/gateway/static/root/assets/{channels-settings-DV5BhAeu.js → channels-settings-CiWUo7jW.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-api-5FEBYguL.js → connectors-api-5xiCRVUS.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-DOwlMseU.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-SEFx5rnr.js → cron-expression-B3PebXKZ.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-D4zCUQJL.js → date-picker-C1Vke7Xn.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-DpQAxS2k.js → dependency-picker-FrQ0tsBC.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-B_5G3lQl.js → desktop-pet-CPBAzNJi.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-ClEyoUJe.js → desktop-pet-settings-D6s3glDP.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-AsFWmOjG.js → directory-picker-path-field-CbNQ7QQS.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-C22Y2o5l.js → extension-debug-page-dTK14hUv.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C61Ikawk.js → extension-page-Cpdj1Kip.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-WlWWcDFr.js → extension-settings-page-iNHUZGd5.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-ByIOMQdc.js → gateway-config-api-SvMvWqMM.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-CzzS9A17.js → gateway-settings-BilR3dGZ.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-zK40dcQ5.js +1 -0
- package/dist/gateway/static/root/assets/home-page-NyXy5MLr.js +3 -0
- package/dist/gateway/static/root/assets/{index-CR233S0Q.css → index-Bp8I_eX4.css} +1 -1
- package/dist/gateway/static/root/assets/index-CCK8zdlS.js +152 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BD_TFz0_.js → keyboard-shortcuts-settings-nKzsroud.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-Ds3sPXTh.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-BwbUeLfm.js → local-apps-page-DV1s_FYh.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-Biz4eA02.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-CW2VpqtT.js → management-settings-D7KjYgHu.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-B1M9KO16.js → markdown-split-CTYbpkrd.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-B_QlNdqZ.js → media-plugins-BXYnaiZ6.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-BTbD4GmH.js → notes-page-CWWVXpw5.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-DnBkQihH.js +4 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-CeqtArlZ.js → preview-open-alternatives-pimQAkeI.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-DnalBZZs.js → product-open-page-BoYSS_YR.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-ZO7t1UkB.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-By-vq41g.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-C0C_Y9d0.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-cGkBEfg4.js → runtime-tools-settings-panel-DT8flaX1.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-CmsrLlxu.js → schema-form-DabEP-UI.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-BNlVwUzV.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CxYBQDaM.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-DUWCbaMJ.js → settings-form-section-DCsfZAtC.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-Bvy5rdMY.js → settings-loading-skeleton-Dac6Yd0T.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-cZSAwsRR.js → settings-page-BDoYoEar.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DWijJ1rw.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-BzA2NCAH.js → share-preview-page-ie9I2_7h.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-DU9VrJLf.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-C5RaIvoE.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-Bvde7_i5.js → skills-page-N0YhaR5S.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-vXSqHR7g.js → skills-page.utils-C639DLXr.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-9cQGOGCK.js → system-settings-panel-CpshpQtC.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-DsTocYkK.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-7XfKULPI.js → use-autosave-ms6rDy59.js} +1 -1
- package/dist/gateway/static/root/assets/{user-context-page-CZ9m9aC_.js → user-context-page-BUxIemwy.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-DXr21v-3.js → voice-api-key-field-CObrQWh2.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-CeG7g_DZ.js → work-discovery-overlay-BBxrnEFt.js} +1 -1
- package/dist/gateway/static/root/assets/work-discovery-page-DN3l-HW3.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DSQQZP9T.js → workflow-run-setup-panel-BxXhe3IV.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-BCHkjd1i.js +5 -0
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/tasks.js +51 -9
- package/dist/src/agent/context/execution-scope.js +3 -1
- package/dist/src/agent/tasks/task-judge-service.d.ts +1 -1
- package/dist/src/agent/tasks/task-judge-service.js +3 -2
- package/dist/src/agent/tools/xopc-use-tool.js +13 -3
- package/dist/src/automations/domain/validation.d.ts +0 -12
- package/dist/src/automations/draft/automation-draft-validator.d.ts +0 -3
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/routes/agent-stream.js +2 -74
- package/dist/src/gateway/hono/routes/session-input-handler.d.ts +62 -0
- package/dist/src/gateway/hono/routes/session-input-handler.js +82 -0
- package/dist/src/gateway/hono/routes/tasks.js +161 -2
- package/dist/src/gateway/service/task-conversation-query-service.d.ts +58 -0
- package/dist/src/gateway/service/task-conversation-query-service.js +84 -0
- package/dist/src/gateway/service.d.ts +10 -0
- package/dist/src/gateway/service.js +70 -37
- package/dist/src/projects/project-agent.js +1 -1
- package/dist/src/storage/sqlite/migrations/120_task_conversation.sql +112 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tasks/execution-context.js +8 -4
- package/dist/src/tasks/index.d.ts +2 -0
- package/dist/src/tasks/index.js +3 -1
- package/dist/src/tasks/task-application-service.js +10 -17
- package/dist/src/tasks/task-context-assembler.js +9 -4
- package/dist/src/tasks/task-context-repository.d.ts +0 -6
- package/dist/src/tasks/task-context-repository.js +0 -4
- package/dist/src/tasks/task-conversation-repository.d.ts +71 -0
- package/dist/src/tasks/task-conversation-repository.js +194 -0
- package/dist/src/tasks/task-handoff-service.d.ts +31 -0
- package/dist/src/tasks/task-handoff-service.js +143 -0
- package/dist/src/tasks/task-read-model-projector.js +1 -5
- package/dist/src/tasks/task-repository.d.ts +0 -2
- package/dist/src/tasks/task-repository.js +2 -9
- package/dist/src/tasks/task-run-coordinator.js +3 -1
- package/dist/src/workflows/service/workflow-run-service.js +4 -2
- package/package.json +3 -3
- package/dist/gateway/static/root/assets/agents-D19uNmJf.js +0 -68
- package/dist/gateway/static/root/assets/automations-page-BR6qMOVr.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CDFQWAxI.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-D41mssOq.js +0 -7
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-BXgCyuu2.js +0 -3
- package/dist/gateway/static/root/assets/connectors-page-C_qkEN0T.js +0 -2
- package/dist/gateway/static/root/assets/heartbeat-settings-B4A_CXUL.js +0 -1
- package/dist/gateway/static/root/assets/home-page-FVGYYPWo.js +0 -3
- package/dist/gateway/static/root/assets/index-CGbKTCST.js +0 -152
- package/dist/gateway/static/root/assets/local-app-workbench-page-BNpkigNb.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-DXP9OQQt.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-JRiq7QDW.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-CjWA13aE.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-jsCkScbG.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CGPJTEkM.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BBHzziMd.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-2cGg8-9h.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Cf06b66-.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-xlNKydYY.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-C5Xu93c_.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-Bkno4tTe.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-page-CQWZD03N.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-Cs-MXFJ1.js +0 -5
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
|
|
2
2
|
import { TaskRepository } from "../../../tasks/task-repository.js";
|
|
3
|
+
import { TaskConversationRepository } from "../../../tasks/task-conversation-repository.js";
|
|
3
4
|
import { TaskRunRepository } from "../../../tasks/task-run-repository.js";
|
|
4
5
|
import { TaskReadModelProjector } from "../../../tasks/task-read-model-projector.js";
|
|
5
6
|
import { TaskPhaseSchema } from "../../../../packages/gateway-contract/src/task-lifecycle.js";
|
|
6
|
-
import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
|
|
7
|
+
import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskHandoffRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
|
|
7
8
|
import { ProjectMonitoringUpdateSchema } from "../../../../packages/gateway-contract/src/project-monitoring.js";
|
|
9
|
+
import { resolveProjectAgentId } from "../../../projects/project-agent.js";
|
|
8
10
|
import { TaskContextRepository } from "../../../tasks/task-context-repository.js";
|
|
9
11
|
import { enqueueTaskChangedEvent } from "../../../tasks/task-change-events.js";
|
|
10
12
|
import { TaskDependencyError, TaskDependencyService } from "../../../tasks/task-dependency-service.js";
|
|
@@ -12,7 +14,10 @@ import { TaskApplicationService } from "../../../tasks/task-application-service.
|
|
|
12
14
|
import { ProjectMonitoringService } from "../../../tasks/project-monitoring-service.js";
|
|
13
15
|
import { ProjectOperatingViewService } from "../../../tasks/project-operating-view-service.js";
|
|
14
16
|
import { TaskValueMetricsService } from "../../../tasks/task-value-metrics-service.js";
|
|
17
|
+
import { TaskHandoffService } from "../../../tasks/task-handoff-service.js";
|
|
15
18
|
import { TaskSignalService } from "../../../tasks/task-signal-service.js";
|
|
19
|
+
import { submitSessionInput } from "./session-input-handler.js";
|
|
20
|
+
import { TaskConversationQueryService } from "../../service/task-conversation-query-service.js";
|
|
16
21
|
//#region src/gateway/hono/routes/tasks.ts
|
|
17
22
|
function registerTaskRoutes(authenticated, deps) {
|
|
18
23
|
const taskRateLimit = deps.taskRateLimitMiddleware ?? deps.strictRateLimitMiddleware;
|
|
@@ -23,9 +28,17 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
23
28
|
const tasks = new TaskRepository();
|
|
24
29
|
const runs = new TaskRunRepository();
|
|
25
30
|
const context = new TaskContextRepository();
|
|
31
|
+
const conversations = new TaskConversationRepository();
|
|
32
|
+
const conversationQuery = new TaskConversationQueryService(deps.service.sessions);
|
|
26
33
|
const projector = new TaskReadModelProjector();
|
|
27
34
|
const signals = new TaskSignalService(() => deps.service.dispatchTaskRuns());
|
|
28
35
|
const dependencies = new TaskDependencyService();
|
|
36
|
+
const handoffs = new TaskHandoffService({
|
|
37
|
+
getConfig: () => deps.service.currentConfig,
|
|
38
|
+
sessionIndex: deps.service.sessionIndexInstance,
|
|
39
|
+
getActiveRunId: (sessionKey) => deps.service.getActiveWebchatRunId(sessionKey),
|
|
40
|
+
abortRun: (runId) => deps.service.abortAgentRun(runId)
|
|
41
|
+
});
|
|
29
42
|
authenticated.get("/api/tasks", (c) => {
|
|
30
43
|
const phase = TaskPhaseSchema.safeParse(c.req.query("phase"));
|
|
31
44
|
const rawLimit = Number(c.req.query("limit") ?? 50);
|
|
@@ -53,7 +66,25 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
53
66
|
error: "Invalid task request"
|
|
54
67
|
}, 400);
|
|
55
68
|
try {
|
|
56
|
-
const
|
|
69
|
+
const requestedAgentId = parsed.data.delegateAgentId ?? (parsed.data.activation.mode === "start" && parsed.data.activation.executor?.kind === "agent" ? parsed.data.activation.executor.agentId : void 0);
|
|
70
|
+
const agentId = resolveProjectAgentId({
|
|
71
|
+
config: deps.service.currentConfig,
|
|
72
|
+
projects: deps.service.projects,
|
|
73
|
+
explicitAgentId: requestedAgentId,
|
|
74
|
+
projectId: parsed.data.projectId
|
|
75
|
+
});
|
|
76
|
+
const input = {
|
|
77
|
+
...parsed.data,
|
|
78
|
+
delegateAgentId: agentId,
|
|
79
|
+
activation: parsed.data.activation.mode === "start" ? {
|
|
80
|
+
...parsed.data.activation,
|
|
81
|
+
executor: parsed.data.activation.executor?.kind === "agent" || !parsed.data.activation.executor ? {
|
|
82
|
+
kind: "agent",
|
|
83
|
+
agentId
|
|
84
|
+
} : parsed.data.activation.executor
|
|
85
|
+
} : parsed.data.activation
|
|
86
|
+
};
|
|
87
|
+
const created = application.create(input);
|
|
57
88
|
if (created.ok === false) return c.json({
|
|
58
89
|
ok: false,
|
|
59
90
|
code: created.reason,
|
|
@@ -95,12 +126,140 @@ function registerTaskRoutes(authenticated, deps) {
|
|
|
95
126
|
runs: runs.listByTask(task.id),
|
|
96
127
|
receipts: runs.listReceipts(task.id),
|
|
97
128
|
context: context.list(task.id),
|
|
129
|
+
conversation: conversations.requireState(task.id),
|
|
130
|
+
sessions: conversations.listSessions(task.id),
|
|
98
131
|
authorityGrants: context.listActiveGrants(task.id),
|
|
99
132
|
dependencies: dependencies.listDependencies(task.id),
|
|
100
133
|
dependents: dependencies.listDependents(task.id),
|
|
101
134
|
allowedCommands: model.allowedCommands
|
|
102
135
|
});
|
|
103
136
|
});
|
|
137
|
+
authenticated.post("/api/tasks/:id/conversation", taskRateLimit, async (c) => {
|
|
138
|
+
try {
|
|
139
|
+
const result = await deps.service.ensureTaskConversation(c.req.param("id"));
|
|
140
|
+
if (result.created) {
|
|
141
|
+
const task = tasks.require(c.req.param("id"));
|
|
142
|
+
runSqliteWriteTransaction((db) => enqueueTaskChangedEvent(db, {
|
|
143
|
+
taskId: task.id,
|
|
144
|
+
projectId: task.projectId,
|
|
145
|
+
version: task.version,
|
|
146
|
+
changedFields: ["conversation"],
|
|
147
|
+
actor: { kind: "user" }
|
|
148
|
+
}));
|
|
149
|
+
deps.service.dispatchTaskEvents();
|
|
150
|
+
}
|
|
151
|
+
return c.json({
|
|
152
|
+
ok: true,
|
|
153
|
+
...result
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
157
|
+
return c.json({
|
|
158
|
+
ok: false,
|
|
159
|
+
error: message
|
|
160
|
+
}, message.startsWith("Task not found") ? 404 : 409);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
authenticated.post("/api/tasks/:id/handoff", taskRateLimit, async (c) => {
|
|
164
|
+
const parsed = TaskHandoffRequestSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
165
|
+
if (!parsed.success) return c.json({
|
|
166
|
+
ok: false,
|
|
167
|
+
error: "Invalid task handoff request"
|
|
168
|
+
}, 400);
|
|
169
|
+
try {
|
|
170
|
+
const result = await handoffs.handoff({
|
|
171
|
+
taskId: c.req.param("id"),
|
|
172
|
+
...parsed.data
|
|
173
|
+
});
|
|
174
|
+
runSqliteWriteTransaction((db) => enqueueTaskChangedEvent(db, {
|
|
175
|
+
taskId: result.task.id,
|
|
176
|
+
projectId: result.task.projectId,
|
|
177
|
+
version: result.task.version,
|
|
178
|
+
changedFields: ["delegateAgentId", "conversation"],
|
|
179
|
+
actor: { kind: "user" }
|
|
180
|
+
}));
|
|
181
|
+
deps.service.dispatchTaskEvents();
|
|
182
|
+
return c.json({
|
|
183
|
+
ok: true,
|
|
184
|
+
task: result.task,
|
|
185
|
+
conversation: result.conversation,
|
|
186
|
+
...result.fromAgentId ? { fromAgentId: result.fromAgentId } : {},
|
|
187
|
+
toAgentId: result.toAgentId,
|
|
188
|
+
activeSessionKey: result.activeSessionKey,
|
|
189
|
+
assignmentEpoch: result.assignmentEpoch
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
return c.json({
|
|
194
|
+
ok: false,
|
|
195
|
+
error: message
|
|
196
|
+
}, message.startsWith("Agent not found") || message.startsWith("Task not found") ? 404 : 409);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
authenticated.post("/api/tasks/:id/inputs", deps.chatRateLimitMiddleware, async (c) => {
|
|
200
|
+
const active = conversations.getActiveSession(c.req.param("id"));
|
|
201
|
+
if (!active?.sessionKey) return c.json({
|
|
202
|
+
ok: false,
|
|
203
|
+
error: {
|
|
204
|
+
code: "NOT_FOUND",
|
|
205
|
+
message: "Task has no active conversation"
|
|
206
|
+
}
|
|
207
|
+
}, 404);
|
|
208
|
+
const expectedSessionKey = c.req.header("X-Xopc-Expected-Session-Key")?.trim();
|
|
209
|
+
if (expectedSessionKey && expectedSessionKey !== active.sessionKey) return c.json({
|
|
210
|
+
ok: false,
|
|
211
|
+
error: {
|
|
212
|
+
code: "CONFLICT",
|
|
213
|
+
message: "Task executor changed; refresh the conversation"
|
|
214
|
+
}
|
|
215
|
+
}, 409);
|
|
216
|
+
return submitSessionInput(c, deps, active.sessionKey);
|
|
217
|
+
});
|
|
218
|
+
authenticated.patch("/api/tasks/:id/conversation/config", taskRateLimit, async (c) => {
|
|
219
|
+
const body = await c.req.json().catch(() => null);
|
|
220
|
+
const model = typeof body?.model === "string" ? body.model.trim() : "";
|
|
221
|
+
if (!model) return c.json({
|
|
222
|
+
ok: false,
|
|
223
|
+
error: "Missing model"
|
|
224
|
+
}, 400);
|
|
225
|
+
const active = conversations.getActiveSession(c.req.param("id"));
|
|
226
|
+
if (!active?.sessionKey) return c.json({
|
|
227
|
+
ok: false,
|
|
228
|
+
error: "Task has no active conversation"
|
|
229
|
+
}, 404);
|
|
230
|
+
const result = await deps.service.sessions.patchAgentConfig(active.sessionKey, { model });
|
|
231
|
+
if (!result.ok) return c.json({
|
|
232
|
+
ok: false,
|
|
233
|
+
error: result.error
|
|
234
|
+
}, 400);
|
|
235
|
+
return c.json({
|
|
236
|
+
ok: true,
|
|
237
|
+
activeSessionKey: active.sessionKey
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
authenticated.get("/api/tasks/:id/conversation/history", async (c) => {
|
|
241
|
+
const parsedLimit = Number.parseInt(c.req.query("limit") ?? "50", 10);
|
|
242
|
+
const parsedOffset = Number.parseInt(c.req.query("offset") ?? "0", 10);
|
|
243
|
+
const rawBefore = c.req.query("before")?.trim();
|
|
244
|
+
const before = rawBefore === void 0 ? void 0 : Number.parseInt(rawBefore, 10);
|
|
245
|
+
if (rawBefore !== void 0 && (!Number.isInteger(before) || before < 0)) return c.json({ error: "Invalid conversation history cursor" }, 400);
|
|
246
|
+
const result = await conversationQuery.getMessagePage(c.req.param("id"), {
|
|
247
|
+
limit: Number.isFinite(parsedLimit) ? Math.min(200, Math.max(1, parsedLimit)) : 50,
|
|
248
|
+
offset: Number.isFinite(parsedOffset) ? Math.max(0, parsedOffset) : 0,
|
|
249
|
+
...before === void 0 ? {} : { before }
|
|
250
|
+
});
|
|
251
|
+
return result ? c.json(result) : c.json({ error: "Task conversation not found" }, 404);
|
|
252
|
+
});
|
|
253
|
+
authenticated.get("/api/tasks/:id/conversation/timeline", async (c) => {
|
|
254
|
+
const items = await conversationQuery.getTimeline(c.req.param("id"));
|
|
255
|
+
return items ? c.json({
|
|
256
|
+
ok: true,
|
|
257
|
+
items
|
|
258
|
+
}) : c.json({
|
|
259
|
+
ok: false,
|
|
260
|
+
error: "Task conversation not found"
|
|
261
|
+
}, 404);
|
|
262
|
+
});
|
|
104
263
|
authenticated.patch("/api/tasks/:id", taskRateLimit, async (c) => {
|
|
105
264
|
const parsed = TaskPatchRequestSchema.safeParse(await c.req.json().catch(() => ({})));
|
|
106
265
|
if (!parsed.success) return c.json({
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SessionTimelineItem } from '../../session/transcript-outline.js';
|
|
2
|
+
import { TaskConversationRepository } from '../../tasks/task-conversation-repository.js';
|
|
3
|
+
import type { GatewaySessionsApi } from './sessions-api.js';
|
|
4
|
+
export declare class TaskConversationQueryService {
|
|
5
|
+
#private;
|
|
6
|
+
private readonly sessions;
|
|
7
|
+
private readonly conversations;
|
|
8
|
+
constructor(sessions: Pick<GatewaySessionsApi, 'getMessagePage' | 'getTimeline'>, conversations?: Pick<TaskConversationRepository, 'listSessions'>);
|
|
9
|
+
getMessagePage(taskId: string, options: {
|
|
10
|
+
limit: number;
|
|
11
|
+
offset: number;
|
|
12
|
+
before?: number;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
session: {
|
|
15
|
+
messages: import("../../session/types.js").Message[];
|
|
16
|
+
transcriptSummary?: import("../../session/types.js").SessionTranscriptSummary;
|
|
17
|
+
transcriptRows?: import("../../session/session-context-for-llm.js").TranscriptStoredRow[];
|
|
18
|
+
key: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
status: import("../../session/types.js").SessionStatus;
|
|
21
|
+
tags: string[];
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
lastAccessedAt: string;
|
|
25
|
+
messageCount: number;
|
|
26
|
+
estimatedTokens: number;
|
|
27
|
+
compactedCount: number;
|
|
28
|
+
lastFlushedAt?: string;
|
|
29
|
+
flushCount?: number;
|
|
30
|
+
sourceChannel: string;
|
|
31
|
+
sourceChatId: string;
|
|
32
|
+
customData?: Record<string, unknown>;
|
|
33
|
+
projectId?: string;
|
|
34
|
+
routing?: import("../../session/types.js").SessionRoutingMeta;
|
|
35
|
+
stats?: import("../../session/types.js").SessionStats;
|
|
36
|
+
sessionType: import("../../session/types.js").SessionType;
|
|
37
|
+
hiddenFromSessionList?: boolean;
|
|
38
|
+
parentSessionKey?: string;
|
|
39
|
+
workflowRunId?: string;
|
|
40
|
+
workflowDefinitionId?: string;
|
|
41
|
+
workflowAgentId?: string;
|
|
42
|
+
workflowAgentLabel?: string;
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
cwd?: string;
|
|
45
|
+
sessionStartedAt?: string;
|
|
46
|
+
lastInteractionAt?: string;
|
|
47
|
+
};
|
|
48
|
+
pagination: {
|
|
49
|
+
nextBeforeCursor?: string;
|
|
50
|
+
before?: string;
|
|
51
|
+
total: number;
|
|
52
|
+
limit: number;
|
|
53
|
+
offset: number;
|
|
54
|
+
hasMore: boolean;
|
|
55
|
+
};
|
|
56
|
+
}>;
|
|
57
|
+
getTimeline(taskId: string): Promise<SessionTimelineItem[] | null>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { TaskConversationRepository } from "../../tasks/task-conversation-repository.js";
|
|
2
|
+
//#region src/gateway/service/task-conversation-query-service.ts
|
|
3
|
+
var TaskConversationQueryService = class {
|
|
4
|
+
constructor(sessions, conversations = new TaskConversationRepository()) {
|
|
5
|
+
this.sessions = sessions;
|
|
6
|
+
this.conversations = conversations;
|
|
7
|
+
}
|
|
8
|
+
async getMessagePage(taskId, options) {
|
|
9
|
+
const links = this.#executionSessions(taskId);
|
|
10
|
+
if (links.length === 0) return null;
|
|
11
|
+
const probes = await Promise.all(links.map((link) => this.sessions.getMessagePage(link.sessionKey, { limit: 1 })));
|
|
12
|
+
if (probes.some((page) => !page)) return null;
|
|
13
|
+
const pages = probes;
|
|
14
|
+
const total = pages.reduce((sum, page) => sum + page.pagination.total, 0);
|
|
15
|
+
const end = Math.min(total, Math.max(0, options.before ?? total - options.offset));
|
|
16
|
+
const start = Math.max(0, end - options.limit);
|
|
17
|
+
const messages = [];
|
|
18
|
+
let cursor = 0;
|
|
19
|
+
for (let index = 0; index < links.length; index += 1) {
|
|
20
|
+
const sessionTotal = pages[index].pagination.total;
|
|
21
|
+
const localStart = Math.max(0, start - cursor);
|
|
22
|
+
const localEnd = Math.min(sessionTotal, end - cursor);
|
|
23
|
+
if (localStart < localEnd) {
|
|
24
|
+
const page = await this.sessions.getMessagePage(links[index].sessionKey, {
|
|
25
|
+
offset: sessionTotal - localEnd,
|
|
26
|
+
limit: localEnd - localStart
|
|
27
|
+
});
|
|
28
|
+
if (!page) return null;
|
|
29
|
+
messages.push(...page.session.messages);
|
|
30
|
+
}
|
|
31
|
+
cursor += sessionTotal;
|
|
32
|
+
}
|
|
33
|
+
const activeIndex = links.findIndex((link) => link.status === "active");
|
|
34
|
+
return {
|
|
35
|
+
session: {
|
|
36
|
+
...pages[activeIndex >= 0 ? activeIndex : pages.length - 1].session,
|
|
37
|
+
messages
|
|
38
|
+
},
|
|
39
|
+
pagination: {
|
|
40
|
+
total,
|
|
41
|
+
limit: options.limit,
|
|
42
|
+
offset: options.offset,
|
|
43
|
+
hasMore: start > 0,
|
|
44
|
+
...options.before === void 0 ? {} : { before: String(end) },
|
|
45
|
+
...start > 0 ? { nextBeforeCursor: String(start) } : {}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async getTimeline(taskId) {
|
|
50
|
+
const links = this.#executionSessions(taskId);
|
|
51
|
+
if (links.length === 0) return null;
|
|
52
|
+
const output = [];
|
|
53
|
+
let displayOffset = 0;
|
|
54
|
+
let turnOffset = 0;
|
|
55
|
+
for (const [index, link] of links.entries()) {
|
|
56
|
+
const [items, page] = await Promise.all([this.sessions.getTimeline(link.sessionKey), this.sessions.getMessagePage(link.sessionKey, { limit: 1 })]);
|
|
57
|
+
if (!items || !page) return null;
|
|
58
|
+
if (index > 0) output.push({
|
|
59
|
+
id: `assignment:${link.id}`,
|
|
60
|
+
kind: "branch",
|
|
61
|
+
title: link.agentId ? `Execution continued by ${link.agentId}` : "Execution continued",
|
|
62
|
+
timestamp: link.startedAt,
|
|
63
|
+
depth: 0,
|
|
64
|
+
turn: turnOffset,
|
|
65
|
+
displayIndex: displayOffset
|
|
66
|
+
});
|
|
67
|
+
output.push(...items.map((item) => ({
|
|
68
|
+
...item,
|
|
69
|
+
id: `${link.id}:${item.id}`,
|
|
70
|
+
turn: item.turn + turnOffset,
|
|
71
|
+
...item.displayIndex === void 0 ? {} : { displayIndex: item.displayIndex + displayOffset }
|
|
72
|
+
})));
|
|
73
|
+
displayOffset += page.pagination.total;
|
|
74
|
+
const highestTurn = items.reduce((max, item) => Math.max(max, item.turn), -1);
|
|
75
|
+
turnOffset += highestTurn + 1;
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
#executionSessions(taskId) {
|
|
80
|
+
return this.conversations.listSessions(taskId).filter((link) => link.role === "execution").sort((a, b) => a.assignmentEpoch - b.assignmentEpoch || a.startedAt - b.startedAt);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { TaskConversationQueryService };
|
|
@@ -12,6 +12,7 @@ import { type ResolvedGatewayAuth } from './auth.js';
|
|
|
12
12
|
import { ModelCatalogSyncService } from '../providers/model-catalog-sync-service.js';
|
|
13
13
|
import { ProjectService } from '../projects/index.js';
|
|
14
14
|
import { LocalAppService } from '../local-apps/index.js';
|
|
15
|
+
import { TaskConversationRepository } from '../tasks/task-conversation-repository.js';
|
|
15
16
|
import { type BrowserRecipeService } from '../browser/recipes/index.js';
|
|
16
17
|
import { listInsights, ProactiveEventService, ProactiveInboxService, ProactiveInboxWorker, ProactiveScenarioService, ProactiveTemporalWorker, ProactiveWorker } from '../proactive/index.js';
|
|
17
18
|
import { GatewaySessionsApi } from './service/sessions-api.js';
|
|
@@ -120,6 +121,15 @@ export declare class GatewayService {
|
|
|
120
121
|
private createTaskRunDispatcher;
|
|
121
122
|
private createMobileNotificationService;
|
|
122
123
|
dispatchTaskRuns(): void;
|
|
124
|
+
ensureTaskConversation(taskId: string, options?: {
|
|
125
|
+
runId?: string;
|
|
126
|
+
requestedAgentId?: string;
|
|
127
|
+
}): Promise<{
|
|
128
|
+
sessionKey: string;
|
|
129
|
+
agentId: string;
|
|
130
|
+
created: boolean;
|
|
131
|
+
conversation: ReturnType<TaskConversationRepository['requireState']>;
|
|
132
|
+
}>;
|
|
123
133
|
dispatchTaskEvents(): void;
|
|
124
134
|
runAgent(...args: Parameters<GatewayAgentRunner['runAgent']>): ReturnType<GatewayAgentRunner['runAgent']>;
|
|
125
135
|
submitSessionInput(...args: Parameters<GatewayAgentRunner['submitSessionInput']>): Promise<{
|
|
@@ -19,19 +19,20 @@ import { loadConfig, saveConfig } from "../config/loader.js";
|
|
|
19
19
|
import { prewarmModelRegistry } from "../providers/model-registry.js";
|
|
20
20
|
import { init_providers, providers_exports } from "../providers/index.js";
|
|
21
21
|
import { TaskRepository } from "../tasks/task-repository.js";
|
|
22
|
+
import { TaskConversationRepository } from "../tasks/task-conversation-repository.js";
|
|
22
23
|
import { disposeAllSessionMcpRuntimes } from "../agent/mcp/bundle-mcp-runtime.js";
|
|
23
24
|
import { resolveEffectiveGatewayPort } from "./host.js";
|
|
24
25
|
import { AutomationService } from "../automations/service/automation-service.js";
|
|
25
26
|
import { onAutomationProductEvent, publishAutomationProductEvent } from "../automations/product-events.js";
|
|
26
27
|
import "../automations/index.js";
|
|
27
28
|
import { ProjectService } from "../projects/project-service.js";
|
|
29
|
+
import { resolveProjectAgentId } from "../projects/project-agent.js";
|
|
28
30
|
import "../projects/index.js";
|
|
29
31
|
import { sanitizeTunnelConfig } from "../tunnel/tunnel-config.js";
|
|
30
32
|
import { wireTunnelEventsToGateway } from "../tunnel/gateway-lifecycle.js";
|
|
31
33
|
import { buckets } from "./rate-limit/buckets.js";
|
|
32
34
|
import { isGatewayStrictSecurityEnabled } from "./rate-limit/env-flags.js";
|
|
33
35
|
import "./rate-limit/index.js";
|
|
34
|
-
import { TaskContextRepository } from "../tasks/task-context-repository.js";
|
|
35
36
|
import { TaskApplicationService } from "../tasks/task-application-service.js";
|
|
36
37
|
import { ProactiveScenarioService } from "../proactive/scenarios/service.js";
|
|
37
38
|
import { TaskOutboxDispatcher } from "../tasks/task-outbox-dispatcher.js";
|
|
@@ -541,42 +542,10 @@ var GatewayService = class {
|
|
|
541
542
|
if (!this.taskRunDispatcher) this.taskRunDispatcher = new TaskRunDispatcher({
|
|
542
543
|
workerId: "gateway-agent",
|
|
543
544
|
ensureSession: async (taskId, runId, requestedAgentId) => {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
agentId,
|
|
549
|
-
source: "webchat",
|
|
550
|
-
accountId: "default",
|
|
551
|
-
peerKind: "direct",
|
|
552
|
-
peerId
|
|
553
|
-
});
|
|
554
|
-
if (!await this.sessionIndex.getSessionMetadata(sessionKey)) await this.sessionIndex.saveMessages(sessionKey, [], { metadata: {
|
|
555
|
-
sourceChannel: "webchat",
|
|
556
|
-
sourceChatId: `default:direct:${peerId}`,
|
|
557
|
-
sessionType: "chat",
|
|
558
|
-
routing: {
|
|
559
|
-
agentId,
|
|
560
|
-
source: "webchat",
|
|
561
|
-
accountId: "default",
|
|
562
|
-
peerKind: "direct",
|
|
563
|
-
peerId
|
|
564
|
-
},
|
|
565
|
-
projectId: task.projectId,
|
|
566
|
-
customData: {
|
|
567
|
-
taskId,
|
|
568
|
-
origin: "task",
|
|
569
|
-
triggerKind: "user",
|
|
570
|
-
taskRunId: runId
|
|
571
|
-
}
|
|
572
|
-
} });
|
|
573
|
-
if (task.projectId) this.projects.attachSession(sessionKey, task.projectId);
|
|
574
|
-
new TaskContextRepository().linkSession({
|
|
575
|
-
taskId,
|
|
576
|
-
sessionKey,
|
|
577
|
-
role: "execution"
|
|
578
|
-
});
|
|
579
|
-
return sessionKey;
|
|
545
|
+
return (await this.ensureTaskConversation(taskId, {
|
|
546
|
+
runId,
|
|
547
|
+
requestedAgentId
|
|
548
|
+
})).sessionKey;
|
|
580
549
|
},
|
|
581
550
|
runAgent: async (runId, sessionKey, message) => {
|
|
582
551
|
const stream = this.agentRunner.runAgent(message, "webchat", sessionKey, {
|
|
@@ -598,6 +567,70 @@ var GatewayService = class {
|
|
|
598
567
|
this.createTaskRunDispatcher().dispatch();
|
|
599
568
|
this.createWorkflowRunService().dispatchTaskRuns();
|
|
600
569
|
}
|
|
570
|
+
async ensureTaskConversation(taskId, options = {}) {
|
|
571
|
+
const task = new TaskRepository().require(taskId);
|
|
572
|
+
const agentId = resolveProjectAgentId({
|
|
573
|
+
config: this.config,
|
|
574
|
+
projects: this.projects,
|
|
575
|
+
explicitAgentId: options.requestedAgentId ?? task.delegateAgentId,
|
|
576
|
+
projectId: task.projectId
|
|
577
|
+
});
|
|
578
|
+
const conversations = new TaskConversationRepository();
|
|
579
|
+
const active = conversations.getActiveSession(task.id);
|
|
580
|
+
if (active) {
|
|
581
|
+
if (active.agentId && active.agentId !== agentId) throw new Error("Task executor differs from the active conversation");
|
|
582
|
+
const conversation = conversations.activateExecutionSession({
|
|
583
|
+
taskId: task.id,
|
|
584
|
+
sessionKey: active.sessionKey,
|
|
585
|
+
agentId,
|
|
586
|
+
runId: options.runId
|
|
587
|
+
});
|
|
588
|
+
return {
|
|
589
|
+
sessionKey: active.sessionKey,
|
|
590
|
+
agentId,
|
|
591
|
+
created: false,
|
|
592
|
+
conversation
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const peerId = `task-${sanitizeSegment(task.id) || Date.now()}`;
|
|
596
|
+
const sessionKey = buildSessionKey({
|
|
597
|
+
agentId,
|
|
598
|
+
source: "webchat",
|
|
599
|
+
accountId: "default",
|
|
600
|
+
peerKind: "direct",
|
|
601
|
+
peerId
|
|
602
|
+
});
|
|
603
|
+
if (!await this.sessionIndex.getSessionMetadata(sessionKey)) await this.sessionIndex.saveMessages(sessionKey, [], { metadata: {
|
|
604
|
+
sourceChannel: "webchat",
|
|
605
|
+
sourceChatId: `default:direct:${peerId}`,
|
|
606
|
+
sessionType: "chat",
|
|
607
|
+
routing: {
|
|
608
|
+
agentId,
|
|
609
|
+
source: "webchat",
|
|
610
|
+
accountId: "default",
|
|
611
|
+
peerKind: "direct",
|
|
612
|
+
peerId
|
|
613
|
+
},
|
|
614
|
+
projectId: task.projectId,
|
|
615
|
+
customData: {
|
|
616
|
+
origin: "task",
|
|
617
|
+
triggerKind: "user",
|
|
618
|
+
...options.runId ? { taskRunId: options.runId } : {}
|
|
619
|
+
}
|
|
620
|
+
} });
|
|
621
|
+
if (task.projectId) this.projects.attachSession(sessionKey, task.projectId);
|
|
622
|
+
return {
|
|
623
|
+
sessionKey,
|
|
624
|
+
agentId,
|
|
625
|
+
created: true,
|
|
626
|
+
conversation: conversations.activateExecutionSession({
|
|
627
|
+
taskId: task.id,
|
|
628
|
+
sessionKey,
|
|
629
|
+
agentId,
|
|
630
|
+
runId: options.runId
|
|
631
|
+
})
|
|
632
|
+
};
|
|
633
|
+
}
|
|
601
634
|
dispatchTaskEvents() {
|
|
602
635
|
new TaskOutboxDispatcher(publishAutomationProductEvent).drain();
|
|
603
636
|
}
|
|
@@ -8,7 +8,7 @@ function normalizeAgentId(value) {
|
|
|
8
8
|
function resolveProjectAgentId(input) {
|
|
9
9
|
const fallback = getDefaultAgentId(input.config);
|
|
10
10
|
const explicitAgentId = normalizeAgentId(input.explicitAgentId);
|
|
11
|
-
if (explicitAgentId
|
|
11
|
+
if (explicitAgentId && agentExists(explicitAgentId, input.config)) return explicitAgentId;
|
|
12
12
|
const projectId = input.projectId?.trim();
|
|
13
13
|
const projectAgentId = projectId ? normalizeAgentId(input.projects.get(projectId)?.defaultAgentId) : void 0;
|
|
14
14
|
if (projectAgentId && agentExists(projectAgentId, input.config)) return projectAgentId;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
ALTER TABLE task_sessions ADD COLUMN agent_id TEXT;
|
|
2
|
+
ALTER TABLE task_sessions ADD COLUMN run_id TEXT;
|
|
3
|
+
ALTER TABLE task_sessions ADD COLUMN assignment_epoch INTEGER NOT NULL DEFAULT 0;
|
|
4
|
+
ALTER TABLE task_sessions ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'
|
|
5
|
+
CHECK (status IN ('active', 'completed', 'superseded', 'failed'));
|
|
6
|
+
ALTER TABLE task_sessions ADD COLUMN started_at INTEGER;
|
|
7
|
+
ALTER TABLE task_sessions ADD COLUMN ended_at INTEGER;
|
|
8
|
+
|
|
9
|
+
UPDATE task_sessions
|
|
10
|
+
SET agent_id = (
|
|
11
|
+
SELECT sessions.agent_id FROM sessions WHERE sessions.session_key = task_sessions.session_key
|
|
12
|
+
), started_at = created_at;
|
|
13
|
+
|
|
14
|
+
UPDATE task_sessions
|
|
15
|
+
SET run_id = (
|
|
16
|
+
SELECT task_runs.run_id
|
|
17
|
+
FROM task_runs
|
|
18
|
+
WHERE task_runs.task_id = task_sessions.task_id
|
|
19
|
+
AND task_runs.session_key = task_sessions.session_key
|
|
20
|
+
ORDER BY task_runs.queued_at DESC
|
|
21
|
+
LIMIT 1
|
|
22
|
+
)
|
|
23
|
+
WHERE role = 'execution';
|
|
24
|
+
|
|
25
|
+
UPDATE task_sessions
|
|
26
|
+
SET status = 'superseded', ended_at = created_at
|
|
27
|
+
WHERE role = 'execution';
|
|
28
|
+
|
|
29
|
+
DELETE FROM task_sessions
|
|
30
|
+
WHERE role = 'execution'
|
|
31
|
+
AND session_key IS NOT NULL
|
|
32
|
+
AND task_session_id NOT IN (
|
|
33
|
+
SELECT canonical.task_session_id
|
|
34
|
+
FROM task_sessions AS canonical
|
|
35
|
+
WHERE canonical.role = 'execution'
|
|
36
|
+
AND canonical.session_key IS NOT NULL
|
|
37
|
+
AND canonical.task_session_id = (
|
|
38
|
+
SELECT candidate.task_session_id
|
|
39
|
+
FROM task_sessions AS candidate
|
|
40
|
+
WHERE candidate.role = 'execution'
|
|
41
|
+
AND candidate.session_key = canonical.session_key
|
|
42
|
+
ORDER BY candidate.created_at DESC, candidate.task_session_id DESC
|
|
43
|
+
LIMIT 1
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
UPDATE task_sessions
|
|
48
|
+
SET status = 'active', ended_at = NULL, assignment_epoch = 1
|
|
49
|
+
WHERE task_session_id IN (
|
|
50
|
+
SELECT latest.task_session_id FROM task_sessions AS latest
|
|
51
|
+
WHERE latest.role = 'execution'
|
|
52
|
+
AND latest.task_session_id = (
|
|
53
|
+
SELECT candidate.task_session_id
|
|
54
|
+
FROM task_sessions AS candidate
|
|
55
|
+
WHERE candidate.task_id = latest.task_id AND candidate.role = 'execution'
|
|
56
|
+
ORDER BY candidate.created_at DESC, candidate.task_session_id DESC
|
|
57
|
+
LIMIT 1
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE UNIQUE INDEX idx_task_sessions_active_execution
|
|
62
|
+
ON task_sessions(task_id)
|
|
63
|
+
WHERE role = 'execution' AND status = 'active';
|
|
64
|
+
CREATE UNIQUE INDEX idx_task_sessions_execution_owner
|
|
65
|
+
ON task_sessions(session_key)
|
|
66
|
+
WHERE role = 'execution' AND session_key IS NOT NULL;
|
|
67
|
+
|
|
68
|
+
CREATE TABLE task_conversation_state (
|
|
69
|
+
task_id TEXT PRIMARY KEY,
|
|
70
|
+
active_session_key TEXT,
|
|
71
|
+
current_executor_agent_id TEXT,
|
|
72
|
+
assignment_epoch INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
status TEXT NOT NULL DEFAULT 'idle'
|
|
74
|
+
CHECK (status IN ('idle', 'active')),
|
|
75
|
+
updated_at INTEGER NOT NULL,
|
|
76
|
+
FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE,
|
|
77
|
+
FOREIGN KEY (active_session_key) REFERENCES sessions(session_key) ON DELETE SET NULL
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
INSERT INTO task_conversation_state (
|
|
81
|
+
task_id, active_session_key, current_executor_agent_id,
|
|
82
|
+
assignment_epoch, status, updated_at
|
|
83
|
+
)
|
|
84
|
+
SELECT
|
|
85
|
+
tasks.task_id,
|
|
86
|
+
active.session_key,
|
|
87
|
+
COALESCE(active.agent_id, tasks.delegate_agent_id, tasks.owner_id),
|
|
88
|
+
COALESCE(active.assignment_epoch, 0),
|
|
89
|
+
CASE WHEN active.session_key IS NULL THEN 'idle' ELSE 'active' END,
|
|
90
|
+
tasks.updated_at
|
|
91
|
+
FROM tasks
|
|
92
|
+
LEFT JOIN task_sessions AS active
|
|
93
|
+
ON active.task_id = tasks.task_id
|
|
94
|
+
AND active.role = 'execution'
|
|
95
|
+
AND active.status = 'active';
|
|
96
|
+
|
|
97
|
+
CREATE TABLE task_handoff_snapshots (
|
|
98
|
+
snapshot_id TEXT PRIMARY KEY,
|
|
99
|
+
task_id TEXT NOT NULL,
|
|
100
|
+
from_session_key TEXT,
|
|
101
|
+
to_session_key TEXT NOT NULL,
|
|
102
|
+
from_agent_id TEXT,
|
|
103
|
+
to_agent_id TEXT NOT NULL,
|
|
104
|
+
assignment_epoch INTEGER NOT NULL,
|
|
105
|
+
payload_json TEXT NOT NULL,
|
|
106
|
+
created_at INTEGER NOT NULL,
|
|
107
|
+
FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE,
|
|
108
|
+
FOREIGN KEY (from_session_key) REFERENCES sessions(session_key) ON DELETE SET NULL,
|
|
109
|
+
FOREIGN KEY (to_session_key) REFERENCES sessions(session_key) ON DELETE CASCADE
|
|
110
|
+
);
|
|
111
|
+
CREATE INDEX idx_task_handoffs_task
|
|
112
|
+
ON task_handoff_snapshots(task_id, assignment_epoch DESC);
|
|
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
|
|
|
3
3
|
/** Baseline schema version applied from schema.sql on first open. */
|
|
4
4
|
export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
|
|
5
5
|
/** Latest schema version this release supports (increment when adding migrations). */
|
|
6
|
-
export declare const XOPC_DB_SCHEMA_VERSION =
|
|
6
|
+
export declare const XOPC_DB_SCHEMA_VERSION = 120;
|
|
7
7
|
export declare function resolveMigrationsDir(override?: string): string;
|
|
8
8
|
/**
|
|
9
9
|
* Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
|
|
@@ -12,7 +12,7 @@ const log = createLogger("Sqlite:Migrations");
|
|
|
12
12
|
/** Baseline schema version applied from schema.sql on first open. */
|
|
13
13
|
const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
|
|
14
14
|
/** Latest schema version this release supports (increment when adding migrations). */
|
|
15
|
-
const XOPC_DB_SCHEMA_VERSION =
|
|
15
|
+
const XOPC_DB_SCHEMA_VERSION = 120;
|
|
16
16
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
function backupBeforeTaskCutover(db, databasePath) {
|
|
18
18
|
db.exec("PRAGMA wal_checkpoint(FULL)");
|
|
@@ -57,7 +57,7 @@ function applySingleMigration(db, migration) {
|
|
|
57
57
|
* Each step runs in its own transaction and updates schema_meta on success.
|
|
58
58
|
*/
|
|
59
59
|
function applyPendingMigrations(db, options = {}) {
|
|
60
|
-
const targetVersion = options.targetVersion ??
|
|
60
|
+
const targetVersion = options.targetVersion ?? 120;
|
|
61
61
|
let currentVersion = readSchemaVersion(db);
|
|
62
62
|
if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
|
|
63
63
|
if (currentVersion >= targetVersion) return currentVersion;
|
|
@@ -94,7 +94,7 @@ function applyPendingMigrations(db, options = {}) {
|
|
|
94
94
|
}
|
|
95
95
|
/** Inspect schema version without mutating the database. */
|
|
96
96
|
function inspectSchemaMigrationStatus(db, options = {}) {
|
|
97
|
-
const appVersion = options.targetVersion ??
|
|
97
|
+
const appVersion = options.targetVersion ?? 120;
|
|
98
98
|
const dbVersion = readSchemaVersion(db);
|
|
99
99
|
if (dbVersion > appVersion) return {
|
|
100
100
|
dbVersion,
|