@pixelbyte-software/pixcode 1.34.0 → 1.35.1
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/LICENSE +718 -718
- package/README.de.md +248 -248
- package/README.ja.md +240 -240
- package/README.ko.md +240 -240
- package/README.md +303 -303
- package/README.ru.md +248 -248
- package/README.tr.md +250 -250
- package/README.zh-CN.md +240 -240
- package/dist/api-docs.html +548 -395
- package/dist/assets/index-B8w57E1r.css +32 -0
- package/dist/assets/index-CBdsvGSR.js +854 -0
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.svg +8 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +59 -59
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1693 -1311
- package/dist/sw.js +124 -124
- package/dist-server/server/claude-sdk.js +38 -7
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +107 -112
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +159 -112
- package/dist-server/server/daemon-manager.js.map +1 -1
- package/dist-server/server/database/json-store.js +8 -5
- package/dist-server/server/database/json-store.js.map +1 -1
- package/dist-server/server/index.js +31 -10
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapter-registry.js +45 -19
- package/dist-server/server/modules/orchestration/a2a/adapter-registry.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js +202 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js +205 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js +205 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js +205 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js +205 -0
- package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/routes.js +298 -34
- package/dist-server/server/modules/orchestration/a2a/routes.js.map +1 -1
- package/dist-server/server/modules/orchestration/a2a/task-store.js +144 -0
- package/dist-server/server/modules/orchestration/a2a/task-store.js.map +1 -0
- package/dist-server/server/modules/orchestration/a2a/validator.js +16 -0
- package/dist-server/server/modules/orchestration/a2a/validator.js.map +1 -1
- package/dist-server/server/modules/orchestration/index.js +14 -0
- package/dist-server/server/modules/orchestration/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/preview/port-watcher.js +90 -0
- package/dist-server/server/modules/orchestration/preview/port-watcher.js.map +1 -0
- package/dist-server/server/modules/orchestration/preview/preview-proxy.js +58 -0
- package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -0
- package/dist-server/server/modules/orchestration/preview/types.js +2 -0
- package/dist-server/server/modules/orchestration/preview/types.js.map +1 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js +37 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js.map +1 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +68 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +128 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js +2 -0
- package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js +126 -0
- package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1047 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-store.js +76 -0
- package/dist-server/server/modules/orchestration/workflows/workflow-store.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +151 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.types.js +2 -0
- package/dist-server/server/modules/orchestration/workflows/workflow.types.js.map +1 -0
- package/dist-server/server/modules/orchestration/workflows/workspace-target.js +98 -0
- package/dist-server/server/modules/orchestration/workflows/workspace-target.js.map +1 -0
- package/dist-server/server/modules/orchestration/workspace/docker-workspace.js +122 -0
- package/dist-server/server/modules/orchestration/workspace/docker-workspace.js.map +1 -0
- package/dist-server/server/modules/orchestration/workspace/path-safety.js +48 -0
- package/dist-server/server/modules/orchestration/workspace/path-safety.js.map +1 -0
- package/dist-server/server/modules/orchestration/workspace/types.js +11 -0
- package/dist-server/server/modules/orchestration/workspace/types.js.map +1 -0
- package/dist-server/server/modules/orchestration/workspace/workspace-manager.js +80 -0
- package/dist-server/server/modules/orchestration/workspace/workspace-manager.js.map +1 -0
- package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js +96 -0
- package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js.map +1 -0
- package/dist-server/server/modules/providers/index.js +3 -0
- package/dist-server/server/modules/providers/index.js.map +1 -0
- package/dist-server/server/openai-codex.js +35 -4
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +17 -17
- package/dist-server/server/routes/taskmaster.js +525 -508
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/package.json +180 -178
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/smoke/a2a-roundtrip.mjs +86 -17
- package/scripts/smoke/orchestration-api.mjs +172 -0
- package/scripts/smoke/orchestration-live-run.mjs +176 -0
- package/server/claude-sdk.js +898 -857
- package/server/cli.js +935 -940
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +342 -342
- package/server/daemon/manager.js +564 -564
- package/server/daemon-manager.js +959 -920
- package/server/database/db.js +794 -794
- package/server/database/json-store.js +197 -194
- package/server/gemini-cli.js +535 -535
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +3135 -3104
- package/server/load-env.js +34 -34
- package/server/middleware/auth.js +173 -173
- package/server/modules/orchestration/a2a/adapter-registry.ts +72 -22
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +9 -3
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +1 -0
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -0
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -0
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -0
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -0
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -0
- package/server/modules/orchestration/a2a/routes.ts +349 -36
- package/server/modules/orchestration/a2a/task-store.ts +178 -0
- package/server/modules/orchestration/a2a/types.ts +14 -0
- package/server/modules/orchestration/a2a/validator.ts +25 -2
- package/server/modules/orchestration/index.ts +40 -0
- package/server/modules/orchestration/preview/port-watcher.ts +112 -0
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -0
- package/server/modules/orchestration/preview/types.ts +19 -0
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -0
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -0
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -0
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -0
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -0
- package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -0
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -0
- package/server/modules/orchestration/workflows/workflow.routes.ts +169 -0
- package/server/modules/orchestration/workflows/workflow.types.ts +70 -0
- package/server/modules/orchestration/workflows/workspace-target.ts +120 -0
- package/server/modules/orchestration/workspace/docker-workspace.ts +135 -0
- package/server/modules/orchestration/workspace/path-safety.ts +55 -0
- package/server/modules/orchestration/workspace/types.ts +52 -0
- package/server/modules/orchestration/workspace/workspace-manager.ts +97 -0
- package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -0
- package/server/modules/providers/index.ts +2 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +145 -145
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +115 -115
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +163 -163
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +232 -232
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +819 -819
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -426
- package/server/opencode-cli.js +459 -459
- package/server/opencode-response-handler.js +107 -107
- package/server/projects.js +3105 -3105
- package/server/qwen-code-cli.js +395 -395
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1365 -1365
- package/server/routes/auth.js +138 -138
- package/server/routes/codex.js +19 -19
- package/server/routes/commands.js +554 -554
- package/server/routes/cursor.js +52 -52
- package/server/routes/gemini.js +24 -24
- package/server/routes/git.js +1488 -1488
- package/server/routes/mcp-utils.js +31 -31
- package/server/routes/messages.js +61 -61
- package/server/routes/network.js +120 -120
- package/server/routes/plugins.js +318 -318
- package/server/routes/projects.js +915 -915
- package/server/routes/qwen.js +27 -27
- package/server/routes/settings.js +286 -286
- package/server/routes/taskmaster.js +1496 -1471
- package/server/routes/telegram.js +125 -125
- package/server/routes/user.js +123 -123
- package/server/services/external-access.js +171 -171
- package/server/services/install-jobs.js +571 -571
- package/server/services/notification-orchestrator.js +242 -242
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +381 -381
- package/server/services/telegram/bot.js +279 -279
- package/server/services/telegram/telegram-http-client.js +130 -130
- package/server/services/telegram/translations.js +170 -170
- package/server/services/vapid-keys.js +36 -36
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +303 -303
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/mcp-detector.js +147 -147
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +184 -184
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/taskmaster-websocket.js +128 -128
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +78 -78
- package/shared/modelConstants.js +162 -162
- package/shared/networkHosts.js +22 -22
- package/dist/assets/index-B1ghfb4w.css +0 -32
- package/dist/assets/index-BvClqlMf.js +0 -852
|
@@ -10,6 +10,7 @@ import { adapterRegistry } from '@/modules/orchestration/a2a/adapter-registry.js
|
|
|
10
10
|
import { buildPixcodeAgentCard } from '@/modules/orchestration/a2a/agent-card.js';
|
|
11
11
|
import { a2aAuth } from '@/modules/orchestration/a2a/auth.middleware.js';
|
|
12
12
|
import { a2aBus } from '@/modules/orchestration/a2a/bus.js';
|
|
13
|
+
import { A2ATaskStore } from '@/modules/orchestration/a2a/task-store.js';
|
|
13
14
|
import type {
|
|
14
15
|
BusEvent,
|
|
15
16
|
Message,
|
|
@@ -21,21 +22,58 @@ import {
|
|
|
21
22
|
assertMessage,
|
|
22
23
|
assertSubmitTaskInput,
|
|
23
24
|
} from '@/modules/orchestration/a2a/validator.js';
|
|
25
|
+
import { portWatcher } from '@/modules/orchestration/preview/port-watcher.js';
|
|
26
|
+
import type { PreviewArtifactData } from '@/modules/orchestration/preview/types.js';
|
|
27
|
+
import { workspaceManager } from '@/modules/orchestration/workspace/workspace-manager.js';
|
|
28
|
+
import type {
|
|
29
|
+
WorkspaceHandle,
|
|
30
|
+
WorkspaceKind,
|
|
31
|
+
WorkspaceMetadata,
|
|
32
|
+
} from '@/modules/orchestration/workspace/types.js';
|
|
33
|
+
import { WorkspaceError } from '@/modules/orchestration/workspace/types.js';
|
|
34
|
+
|
|
35
|
+
type RoutingHints = {
|
|
36
|
+
preferredAdapterId?: string;
|
|
37
|
+
preferredProvider?: string;
|
|
38
|
+
preferredSkillId?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function readRoutingHints(value: unknown): RoutingHints {
|
|
42
|
+
if (!value || typeof value !== 'object') {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
const source = value as Record<string, unknown>;
|
|
47
|
+
return {
|
|
48
|
+
preferredAdapterId:
|
|
49
|
+
typeof source.preferredAdapterId === 'string' ? source.preferredAdapterId : undefined,
|
|
50
|
+
preferredProvider:
|
|
51
|
+
typeof source.preferredProvider === 'string' ? source.preferredProvider : undefined,
|
|
52
|
+
preferredSkillId:
|
|
53
|
+
typeof source.preferredSkillId === 'string' ? source.preferredSkillId : undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const TERMINAL: TaskState[] = ['completed', 'canceled', 'failed'];
|
|
28
58
|
// Per-task bus unsubscribe handles; called on terminal state.
|
|
29
59
|
const taskUnsubs = new Map<string, () => void>();
|
|
30
60
|
// Eviction timeouts (terminal tasks live for 1 hour before being purged).
|
|
31
61
|
const taskEvictions = new Map<string, NodeJS.Timeout>();
|
|
32
62
|
const TERMINAL_TASK_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
33
63
|
const MAX_TASKS = 1000;
|
|
64
|
+
const taskStore = new A2ATaskStore({ terminalTaskTtlMs: TERMINAL_TASK_TTL_MS });
|
|
65
|
+
const activeWorkspaces = new Map<string, WorkspaceHandle>();
|
|
66
|
+
const previewStops = new Map<string, () => void>();
|
|
67
|
+
const finalizingTasks = new Set<string>();
|
|
34
68
|
|
|
35
69
|
function newId(prefix: string): string {
|
|
36
70
|
return `${prefix}_${crypto.randomBytes(8).toString('hex')}`;
|
|
37
71
|
}
|
|
38
72
|
|
|
73
|
+
function isTerminalTaskState(state: TaskState): boolean {
|
|
74
|
+
return TERMINAL.includes(state);
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
function getBaseUrl(req: Request): string {
|
|
40
78
|
// TODO: this trusts X-Forwarded-Proto/Host without checking app's
|
|
41
79
|
// trust-proxy setting. Same posture as auth.middleware.ts; revisit
|
|
@@ -45,40 +83,174 @@ function getBaseUrl(req: Request): string {
|
|
|
45
83
|
return `${proto}://${host}`;
|
|
46
84
|
}
|
|
47
85
|
|
|
86
|
+
function scheduleTaskEviction(taskId: string): void {
|
|
87
|
+
const existingTimeout = taskEvictions.get(taskId);
|
|
88
|
+
if (existingTimeout) {
|
|
89
|
+
clearTimeout(existingTimeout);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const task = taskStore.get(taskId);
|
|
93
|
+
if (!task) {
|
|
94
|
+
taskEvictions.delete(taskId);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const remainingMs = Math.max(0, task.updatedAt + TERMINAL_TASK_TTL_MS - Date.now());
|
|
99
|
+
taskEvictions.set(
|
|
100
|
+
taskId,
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
taskStore.delete(taskId);
|
|
103
|
+
taskEvictions.delete(taskId);
|
|
104
|
+
}, remainingMs),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseTaskState(value: unknown): TaskState | undefined {
|
|
109
|
+
if (typeof value !== 'string') {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const normalized = value.trim();
|
|
114
|
+
if (
|
|
115
|
+
normalized === 'submitted' ||
|
|
116
|
+
normalized === 'working' ||
|
|
117
|
+
normalized === 'input-required' ||
|
|
118
|
+
normalized === 'completed' ||
|
|
119
|
+
normalized === 'canceled' ||
|
|
120
|
+
normalized === 'failed'
|
|
121
|
+
) {
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parsePositiveInt(value: unknown, fallback: number): number {
|
|
129
|
+
if (typeof value !== 'string') {
|
|
130
|
+
return fallback;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parsed = Number.parseInt(value, 10);
|
|
134
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
135
|
+
return fallback;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readString(value: unknown): string | undefined {
|
|
142
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function readBoolean(value: unknown): boolean | undefined {
|
|
146
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function readObject(value: unknown): Record<string, unknown> | undefined {
|
|
150
|
+
return value && typeof value === 'object' ? value as Record<string, unknown> : undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readWorkspaceKind(value: unknown): WorkspaceKind | undefined {
|
|
154
|
+
return value === 'host' || value === 'worktree' || value === 'docker' ? value : undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function workspaceMetadata(workspace: WorkspaceHandle, keepAfterCompletion?: boolean): WorkspaceMetadata {
|
|
158
|
+
return {
|
|
159
|
+
id: workspace.id,
|
|
160
|
+
kind: workspace.kind,
|
|
161
|
+
path: workspace.path,
|
|
162
|
+
baseRef: workspace.baseRef,
|
|
163
|
+
branchName: workspace.branchName,
|
|
164
|
+
keepAfterCompletion,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function finalizeTerminalTask(task: Task): Promise<void> {
|
|
169
|
+
if (finalizingTasks.has(task.id)) return;
|
|
170
|
+
finalizingTasks.add(task.id);
|
|
171
|
+
|
|
172
|
+
const stopPreview = previewStops.get(task.id);
|
|
173
|
+
if (stopPreview) {
|
|
174
|
+
stopPreview();
|
|
175
|
+
previewStops.delete(task.id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const workspace = activeWorkspaces.get(task.id);
|
|
179
|
+
try {
|
|
180
|
+
if (workspace) {
|
|
181
|
+
const diff = await workspace.diff();
|
|
182
|
+
a2aBus.publish({
|
|
183
|
+
kind: 'artifact',
|
|
184
|
+
taskId: task.id,
|
|
185
|
+
artifact: {
|
|
186
|
+
artifactId: newId('art'),
|
|
187
|
+
type: 'file-diff',
|
|
188
|
+
parts: [{ kind: 'text', text: diff }],
|
|
189
|
+
metadata: {
|
|
190
|
+
source: 'workspace-diff',
|
|
191
|
+
workspaceId: workspace.id,
|
|
192
|
+
workspaceKind: workspace.kind,
|
|
193
|
+
baseRef: workspace.baseRef,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const keepAfterCompletion = task.metadata?.workspace &&
|
|
199
|
+
typeof task.metadata.workspace === 'object' &&
|
|
200
|
+
readBoolean((task.metadata.workspace as Record<string, unknown>).keepAfterCompletion);
|
|
201
|
+
if (workspace.kind !== 'host' && keepAfterCompletion !== true) {
|
|
202
|
+
await workspace.destroy();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
207
|
+
task.metadata = {
|
|
208
|
+
...task.metadata,
|
|
209
|
+
workspaceFinalizationError: message,
|
|
210
|
+
};
|
|
211
|
+
task.updatedAt = Date.now();
|
|
212
|
+
taskStore.set(task);
|
|
213
|
+
} finally {
|
|
214
|
+
activeWorkspaces.delete(task.id);
|
|
215
|
+
const unsub = taskUnsubs.get(task.id);
|
|
216
|
+
if (unsub) {
|
|
217
|
+
unsub();
|
|
218
|
+
taskUnsubs.delete(task.id);
|
|
219
|
+
}
|
|
220
|
+
scheduleTaskEviction(task.id);
|
|
221
|
+
finalizingTasks.delete(task.id);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
48
225
|
function attachBusToTask(task: Task): void {
|
|
49
226
|
const unsubscribe = a2aBus.subscribe(task.id, (event: BusEvent) => {
|
|
50
227
|
if (event.kind === 'task-state') {
|
|
51
228
|
task.state = event.state;
|
|
52
229
|
if (event.error) task.error = event.error;
|
|
53
230
|
task.updatedAt = Date.now();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (unsub) {
|
|
58
|
-
unsub();
|
|
59
|
-
taskUnsubs.delete(task.id);
|
|
60
|
-
}
|
|
61
|
-
const existingTimeout = taskEvictions.get(task.id);
|
|
62
|
-
if (existingTimeout) clearTimeout(existingTimeout);
|
|
63
|
-
taskEvictions.set(
|
|
64
|
-
task.id,
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
tasks.delete(task.id);
|
|
67
|
-
taskEvictions.delete(task.id);
|
|
68
|
-
}, TERMINAL_TASK_TTL_MS),
|
|
69
|
-
);
|
|
231
|
+
taskStore.set(task);
|
|
232
|
+
if (isTerminalTaskState(event.state)) {
|
|
233
|
+
void finalizeTerminalTask(task);
|
|
70
234
|
}
|
|
71
235
|
} else if (event.kind === 'message') {
|
|
72
236
|
task.history.push(event.message);
|
|
73
237
|
task.updatedAt = Date.now();
|
|
238
|
+
taskStore.set(task);
|
|
74
239
|
} else if (event.kind === 'artifact') {
|
|
75
240
|
task.artifacts.push(event.artifact);
|
|
76
241
|
task.updatedAt = Date.now();
|
|
242
|
+
taskStore.set(task);
|
|
77
243
|
}
|
|
78
244
|
});
|
|
79
245
|
taskUnsubs.set(task.id, unsubscribe);
|
|
80
246
|
}
|
|
81
247
|
|
|
248
|
+
for (const task of taskStore.values()) {
|
|
249
|
+
if (isTerminalTaskState(task.state)) {
|
|
250
|
+
scheduleTaskEviction(task.id);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
82
254
|
export function createA2ARouter(): Router {
|
|
83
255
|
const router: Router = express.Router();
|
|
84
256
|
|
|
@@ -103,6 +275,57 @@ export function createA2ARouter(): Router {
|
|
|
103
275
|
res.json(adapter.agentCard);
|
|
104
276
|
});
|
|
105
277
|
|
|
278
|
+
router.post('/adapters/resolve', (req, res) => {
|
|
279
|
+
const selector = typeof req.body?.adapterId === 'string' ? req.body.adapterId : '';
|
|
280
|
+
if (!selector.trim()) {
|
|
281
|
+
res.status(400).json({
|
|
282
|
+
error: { code: 'ADAPTER_ID_REQUIRED', message: 'adapterId is required.' },
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const routing = readRoutingHints(req.body?.routing);
|
|
288
|
+
const adapter = adapterRegistry.resolve(selector, routing);
|
|
289
|
+
if (!adapter) {
|
|
290
|
+
res.status(404).json({
|
|
291
|
+
error: {
|
|
292
|
+
code: 'ADAPTER_NOT_FOUND',
|
|
293
|
+
message: selector,
|
|
294
|
+
availableAdapters: adapterRegistry.list().map((candidate) => candidate.id),
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
res.json({
|
|
301
|
+
selector,
|
|
302
|
+
resolvedAdapterId: adapter.id,
|
|
303
|
+
agentCard: adapter.agentCard,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
router.get('/tasks', (req, res) => {
|
|
308
|
+
const state = parseTaskState(req.query.state);
|
|
309
|
+
const contextId = typeof req.query.contextId === 'string' ? req.query.contextId : undefined;
|
|
310
|
+
const adapterId = typeof req.query.adapterId === 'string' ? req.query.adapterId : undefined;
|
|
311
|
+
const limit = parsePositiveInt(req.query.limit, 50);
|
|
312
|
+
|
|
313
|
+
const tasks = taskStore
|
|
314
|
+
.list({ state, contextId, adapterId, limit })
|
|
315
|
+
.map((task) => taskStore.summarize(task));
|
|
316
|
+
|
|
317
|
+
res.json({
|
|
318
|
+
tasks,
|
|
319
|
+
count: tasks.length,
|
|
320
|
+
filters: {
|
|
321
|
+
state,
|
|
322
|
+
contextId,
|
|
323
|
+
adapterId,
|
|
324
|
+
limit,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
106
329
|
// Task lifecycle
|
|
107
330
|
router.post('/tasks', async (req: Request, res: Response) => {
|
|
108
331
|
try {
|
|
@@ -113,20 +336,27 @@ export function createA2ARouter(): Router {
|
|
|
113
336
|
return;
|
|
114
337
|
}
|
|
115
338
|
|
|
116
|
-
const
|
|
339
|
+
const metadata = req.body.metadata as Record<string, unknown> | undefined;
|
|
340
|
+
const routing = readRoutingHints(metadata?.routing);
|
|
341
|
+
|
|
342
|
+
const adapter = adapterRegistry.resolve(req.body.adapterId, routing);
|
|
117
343
|
if (!adapter) {
|
|
118
344
|
res.status(404).json({
|
|
119
|
-
error: {
|
|
345
|
+
error: {
|
|
346
|
+
code: 'ADAPTER_NOT_FOUND',
|
|
347
|
+
message: req.body.adapterId,
|
|
348
|
+
availableAdapters: adapterRegistry.list().map((candidate) => candidate.id),
|
|
349
|
+
},
|
|
120
350
|
});
|
|
121
351
|
return;
|
|
122
352
|
}
|
|
123
353
|
|
|
124
354
|
// Enforce MAX_TASKS cap. Evict the oldest terminal task first; if all
|
|
125
355
|
// active, fail closed with 503.
|
|
126
|
-
if (
|
|
356
|
+
if (taskStore.size >= MAX_TASKS) {
|
|
127
357
|
let evicted = false;
|
|
128
|
-
for (const [tid, t] of
|
|
129
|
-
if (t.state
|
|
358
|
+
for (const [tid, t] of taskStore.entries()) {
|
|
359
|
+
if (isTerminalTaskState(t.state)) {
|
|
130
360
|
const timeout = taskEvictions.get(tid);
|
|
131
361
|
if (timeout) clearTimeout(timeout);
|
|
132
362
|
taskEvictions.delete(tid);
|
|
@@ -135,7 +365,7 @@ export function createA2ARouter(): Router {
|
|
|
135
365
|
unsub();
|
|
136
366
|
taskUnsubs.delete(tid);
|
|
137
367
|
}
|
|
138
|
-
|
|
368
|
+
taskStore.delete(tid);
|
|
139
369
|
evicted = true;
|
|
140
370
|
break;
|
|
141
371
|
}
|
|
@@ -153,20 +383,98 @@ export function createA2ARouter(): Router {
|
|
|
153
383
|
id: newId('task'),
|
|
154
384
|
contextId: req.body.contextId,
|
|
155
385
|
state: 'submitted',
|
|
156
|
-
history: [
|
|
386
|
+
history: [],
|
|
157
387
|
artifacts: [],
|
|
158
388
|
metadata: req.body.metadata,
|
|
159
389
|
createdAt: Date.now(),
|
|
160
390
|
updatedAt: Date.now(),
|
|
161
391
|
};
|
|
162
|
-
|
|
392
|
+
task.history.push({ ...userMessage, taskId: task.id });
|
|
393
|
+
const workspaceOptions = (metadata?.workspace && typeof metadata.workspace === 'object'
|
|
394
|
+
? metadata.workspace
|
|
395
|
+
: {}) as Record<string, unknown>;
|
|
163
396
|
// Persist adapterId in metadata so cancel can resolve the owning adapter
|
|
164
397
|
// even when the original request body is no longer available.
|
|
165
|
-
task.metadata = {
|
|
398
|
+
task.metadata = {
|
|
399
|
+
...task.metadata,
|
|
400
|
+
adapterId: adapter.id,
|
|
401
|
+
adapterSelector: req.body.adapterId,
|
|
402
|
+
};
|
|
403
|
+
taskStore.set(task);
|
|
166
404
|
attachBusToTask(task);
|
|
167
405
|
|
|
406
|
+
let workspace: WorkspaceHandle;
|
|
168
407
|
try {
|
|
169
|
-
await
|
|
408
|
+
workspace = await workspaceManager.create({
|
|
409
|
+
taskId: task.id,
|
|
410
|
+
projectPath: readString(workspaceOptions.projectPath) ?? process.cwd(),
|
|
411
|
+
kind: readWorkspaceKind(workspaceOptions.kind) ?? readWorkspaceKind(metadata?.isolation),
|
|
412
|
+
baseRef: readString(workspaceOptions.baseRef) ?? readString(metadata?.baseRef) ?? 'HEAD',
|
|
413
|
+
keepAfterCompletion: readBoolean(workspaceOptions.keepAfterCompletion),
|
|
414
|
+
metadata: workspaceOptions,
|
|
415
|
+
});
|
|
416
|
+
} catch (err) {
|
|
417
|
+
const workspaceError = err instanceof WorkspaceError ? err : undefined;
|
|
418
|
+
a2aBus.publish({
|
|
419
|
+
kind: 'task-state',
|
|
420
|
+
taskId: task.id,
|
|
421
|
+
state: 'failed',
|
|
422
|
+
error: {
|
|
423
|
+
code: workspaceError?.code ?? 'WORKSPACE_CREATE_FAILED',
|
|
424
|
+
message: err instanceof Error ? err.message : String(err),
|
|
425
|
+
details: workspaceError?.details,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
res.status(202).json(task);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
activeWorkspaces.set(task.id, workspace);
|
|
433
|
+
task.metadata = {
|
|
434
|
+
...task.metadata,
|
|
435
|
+
workspace: workspaceMetadata(workspace, readBoolean(workspaceOptions.keepAfterCompletion)),
|
|
436
|
+
};
|
|
437
|
+
taskStore.set(task);
|
|
438
|
+
|
|
439
|
+
previewStops.set(
|
|
440
|
+
task.id,
|
|
441
|
+
portWatcher.watch({
|
|
442
|
+
taskId: task.id,
|
|
443
|
+
workspace,
|
|
444
|
+
onPort: (event) => {
|
|
445
|
+
const data: PreviewArtifactData = {
|
|
446
|
+
url: event.url,
|
|
447
|
+
proxiedUrl: `/preview/${event.port}/`,
|
|
448
|
+
port: event.port,
|
|
449
|
+
host: event.host,
|
|
450
|
+
processName: event.processName,
|
|
451
|
+
confidence: event.confidence,
|
|
452
|
+
};
|
|
453
|
+
a2aBus.publish({
|
|
454
|
+
kind: 'artifact',
|
|
455
|
+
taskId: task.id,
|
|
456
|
+
artifact: {
|
|
457
|
+
artifactId: newId('art'),
|
|
458
|
+
type: 'preview-url',
|
|
459
|
+
parts: [{ kind: 'data', data: { ...data } }],
|
|
460
|
+
metadata: {
|
|
461
|
+
source: 'port-watcher',
|
|
462
|
+
workspaceId: workspace.id,
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
await adapter.submitTask(task, {
|
|
472
|
+
cwd: workspace.path,
|
|
473
|
+
workspace,
|
|
474
|
+
model: readString(metadata?.model),
|
|
475
|
+
permissionMode: readString(metadata?.permissionMode),
|
|
476
|
+
toolsSettings: readObject(metadata?.toolsSettings),
|
|
477
|
+
});
|
|
170
478
|
} catch (err) {
|
|
171
479
|
// Publish to bus so SSE subscribers and the attachBusToTask listener
|
|
172
480
|
// both see the failure transition. The listener mutates the stored
|
|
@@ -186,7 +494,7 @@ export function createA2ARouter(): Router {
|
|
|
186
494
|
});
|
|
187
495
|
|
|
188
496
|
router.get('/tasks/:id', (req, res) => {
|
|
189
|
-
const task =
|
|
497
|
+
const task = taskStore.get(req.params.id);
|
|
190
498
|
if (!task) {
|
|
191
499
|
res.status(404).json({ error: { code: 'TASK_NOT_FOUND', message: req.params.id } });
|
|
192
500
|
return;
|
|
@@ -195,7 +503,7 @@ export function createA2ARouter(): Router {
|
|
|
195
503
|
});
|
|
196
504
|
|
|
197
505
|
router.get('/tasks/:id/stream', (req, res) => {
|
|
198
|
-
const task =
|
|
506
|
+
const task = taskStore.get(req.params.id);
|
|
199
507
|
if (!task) {
|
|
200
508
|
res.status(404).json({ error: { code: 'TASK_NOT_FOUND', message: req.params.id } });
|
|
201
509
|
return;
|
|
@@ -209,11 +517,10 @@ export function createA2ARouter(): Router {
|
|
|
209
517
|
const initial = { kind: 'task-snapshot' as const, task };
|
|
210
518
|
res.write(`event: snapshot\ndata: ${JSON.stringify(initial)}\n\n`);
|
|
211
519
|
|
|
212
|
-
const TERMINAL: TaskState[] = ['completed', 'canceled', 'failed'];
|
|
213
520
|
const unsubscribe = a2aBus.subscribe(task.id, (event) => {
|
|
214
521
|
res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
215
522
|
if (event.kind === 'task-state' && TERMINAL.includes(event.state)) {
|
|
216
|
-
res.end();
|
|
523
|
+
setTimeout(() => res.end(), 1500);
|
|
217
524
|
}
|
|
218
525
|
});
|
|
219
526
|
|
|
@@ -223,14 +530,14 @@ export function createA2ARouter(): Router {
|
|
|
223
530
|
});
|
|
224
531
|
|
|
225
532
|
router.post('/tasks/:id/cancel', async (req, res) => {
|
|
226
|
-
const task =
|
|
533
|
+
const task = taskStore.get(req.params.id);
|
|
227
534
|
if (!task) {
|
|
228
535
|
res.status(404).json({ error: { code: 'TASK_NOT_FOUND', message: req.params.id } });
|
|
229
536
|
return;
|
|
230
537
|
}
|
|
231
538
|
// Look up the adapter that owns this task. We stored adapterId in metadata.
|
|
232
539
|
const adapterId = req.body?.adapterId ?? task.metadata?.adapterId;
|
|
233
|
-
const adapter = typeof adapterId === 'string' ? adapterRegistry.
|
|
540
|
+
const adapter = typeof adapterId === 'string' ? adapterRegistry.resolve(adapterId) : undefined;
|
|
234
541
|
if (!adapter) {
|
|
235
542
|
res.status(400).json({
|
|
236
543
|
error: {
|
|
@@ -241,7 +548,7 @@ export function createA2ARouter(): Router {
|
|
|
241
548
|
return;
|
|
242
549
|
}
|
|
243
550
|
await adapter.cancelTask(task.id);
|
|
244
|
-
res.json(
|
|
551
|
+
res.json(taskStore.get(task.id));
|
|
245
552
|
});
|
|
246
553
|
|
|
247
554
|
router.post('/messages', (req, res) => {
|
|
@@ -252,6 +559,12 @@ export function createA2ARouter(): Router {
|
|
|
252
559
|
res.status(400).json({ error: { code: 'INVALID_INPUT', message: e.message, path: e.path } });
|
|
253
560
|
return;
|
|
254
561
|
}
|
|
562
|
+
if (typeof req.body.taskId === 'string' && !taskStore.get(req.body.taskId)) {
|
|
563
|
+
res.status(404).json({
|
|
564
|
+
error: { code: 'TASK_NOT_FOUND', message: req.body.taskId },
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
255
568
|
a2aBus.publish({
|
|
256
569
|
kind: 'message',
|
|
257
570
|
taskId: req.body.taskId ?? 'broadcast',
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// server/modules/orchestration/a2a/task-store.ts
|
|
2
|
+
// A2A task persistence backed by shared JsonStore for atomic writes and corruption recovery.
|
|
3
|
+
// Exposes a Map-compatible surface (get/set/delete/size/entries/values) so routes.ts
|
|
4
|
+
// can treat the store as a typed Map while still benefiting from durable JSON storage.
|
|
5
|
+
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
import { JsonStore } from '@/database/json-store.js';
|
|
10
|
+
import type { Message, Task, TaskState, TaskSummary } from '@/modules/orchestration/a2a/types.js';
|
|
11
|
+
|
|
12
|
+
const filePath = path.join(os.homedir(), '.pixcode', 'a2a-tasks.json');
|
|
13
|
+
|
|
14
|
+
export interface A2ATaskStoreOptions {
|
|
15
|
+
terminalTaskTtlMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ListOptions {
|
|
19
|
+
state?: TaskState;
|
|
20
|
+
contextId?: string;
|
|
21
|
+
adapterId?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class A2ATaskStore {
|
|
26
|
+
private store: JsonStore;
|
|
27
|
+
|
|
28
|
+
constructor(_options?: A2ATaskStoreOptions) {
|
|
29
|
+
this.store = new JsonStore(filePath, { a2a_tasks: [] });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Map-compatible API ─────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
get(id: string): Task | undefined {
|
|
35
|
+
return this.store.findWhere('a2a_tasks', (t: Task) => t.id === id) ?? undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set(task: Task): void {
|
|
39
|
+
const existing = this.get(task.id);
|
|
40
|
+
if (existing) {
|
|
41
|
+
this.store.updateWhere('a2a_tasks', (t: Task) => t.id === task.id, task);
|
|
42
|
+
} else {
|
|
43
|
+
this.store.insert('a2a_tasks', task, { autoId: false });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
delete(id: string): void {
|
|
48
|
+
this.store.deleteWhere('a2a_tasks', (t: Task) => t.id === id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get size(): number {
|
|
52
|
+
return (this.store.filterWhere('a2a_tasks', () => true) as Task[]).length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
*entries(): IterableIterator<[string, Task]> {
|
|
56
|
+
const tasks = this.store.filterWhere('a2a_tasks', () => true) as Task[];
|
|
57
|
+
for (const task of tasks) {
|
|
58
|
+
yield [task.id, task];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
*values(): IterableIterator<Task> {
|
|
63
|
+
const tasks = this.store.filterWhere('a2a_tasks', () => true) as Task[];
|
|
64
|
+
for (const task of tasks) {
|
|
65
|
+
yield task;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Query helpers ──────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
list(options: ListOptions = {}): Task[] {
|
|
72
|
+
const { state, contextId, adapterId, limit = 50 } = options;
|
|
73
|
+
let tasks = this.store.filterWhere('a2a_tasks', (t: Task) => {
|
|
74
|
+
if (state && t.state !== state) return false;
|
|
75
|
+
if (contextId && t.contextId !== contextId) return false;
|
|
76
|
+
if (adapterId && (t.metadata?.adapterId as string | undefined) !== adapterId) return false;
|
|
77
|
+
return true;
|
|
78
|
+
}) as Task[];
|
|
79
|
+
if (limit > 0) tasks = tasks.slice(0, limit);
|
|
80
|
+
return tasks;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
summarize(task: Task): TaskSummary {
|
|
84
|
+
return {
|
|
85
|
+
id: task.id,
|
|
86
|
+
contextId: task.contextId,
|
|
87
|
+
state: task.state,
|
|
88
|
+
adapterId: task.metadata?.adapterId as string | undefined,
|
|
89
|
+
adapterSelector: task.metadata?.adapterSelector as string | undefined,
|
|
90
|
+
error: task.error,
|
|
91
|
+
createdAt: task.createdAt,
|
|
92
|
+
updatedAt: task.updatedAt,
|
|
93
|
+
messageCount: task.history?.length ?? 0,
|
|
94
|
+
artifactCount: task.artifacts?.length ?? 0,
|
|
95
|
+
lastMessage: task.history?.at(-1),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Legacy named methods (kept for any future internal callers) ────────────
|
|
100
|
+
|
|
101
|
+
getTask(id: string): Task | undefined {
|
|
102
|
+
return this.get(id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getTaskByIndex(index: number): Task | undefined {
|
|
106
|
+
const tasks = this.store.filterWhere('a2a_tasks', () => true) as Task[];
|
|
107
|
+
return tasks[index] ?? undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getTasks(): Task[] {
|
|
111
|
+
return this.store.filterWhere('a2a_tasks', () => true) as Task[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
createTask(task: Task): void {
|
|
115
|
+
this.store.insert('a2a_tasks', task, { autoId: false });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
updateTask(id: string, updates: Partial<Task>): void {
|
|
119
|
+
const existing = this.get(id);
|
|
120
|
+
if (!existing) return;
|
|
121
|
+
const updated = { ...existing, ...updates, id: existing.id };
|
|
122
|
+
this.store.updateWhere('a2a_tasks', (t: Task) => t.id === id, updated);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
updateTaskState(id: string, state: TaskState): void {
|
|
126
|
+
this.updateTask(id, { status: { state } } as Partial<Task>);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
deleteTask(id: string): void {
|
|
130
|
+
this.delete(id);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
appendHistory(id: string, entry: { state: TaskState; timestamp: string }): void {
|
|
134
|
+
const task = this.get(id);
|
|
135
|
+
if (!task) return;
|
|
136
|
+
const history = [...(task.history ?? []), entry as unknown as Message];
|
|
137
|
+
this.updateTask(id, { history } as Partial<Task>);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
addArtifact(id: string, artifact: NonNullable<Task['artifacts']>[number]): void {
|
|
141
|
+
const task = this.get(id);
|
|
142
|
+
if (!task) return;
|
|
143
|
+
const artifacts = [...(task.artifacts ?? []), artifact];
|
|
144
|
+
this.updateTask(id, { artifacts } as Partial<Task>);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
addMessage(id: string, message: Message): void {
|
|
148
|
+
const task = this.get(id);
|
|
149
|
+
if (!task) return;
|
|
150
|
+
const history = [...(task.history ?? []), message];
|
|
151
|
+
this.updateTask(id, { history } as Partial<Task>);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
updateMessage(id: string, messageId: string, updates: Partial<Message>): void {
|
|
155
|
+
const task = this.get(id);
|
|
156
|
+
if (!task?.history) return;
|
|
157
|
+
const history = task.history.map((m) =>
|
|
158
|
+
m.messageId === messageId ? { ...m, ...updates } : m,
|
|
159
|
+
);
|
|
160
|
+
this.updateTask(id, { history } as Partial<Task>);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getMessage(id: string, messageId: string): Message | undefined {
|
|
164
|
+
const task = this.get(id);
|
|
165
|
+
return task?.history?.find((m) => m.messageId === messageId);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
getMessages(id: string): Message[] {
|
|
169
|
+
const task = this.get(id);
|
|
170
|
+
return task?.history ?? [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
clearMessages(id: string): void {
|
|
174
|
+
this.updateTask(id, { history: [] } as Partial<Task>);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const a2aTaskStore = new A2ATaskStore();
|