@pixelbyte-software/pixcode 1.33.11 → 1.35.0

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.
Files changed (164) hide show
  1. package/dist/api-docs.html +162 -9
  2. package/dist/assets/index-B8w57E1r.css +32 -0
  3. package/dist/assets/index-Djuh0wHV.js +854 -0
  4. package/dist/favicon.svg +8 -8
  5. package/dist/icons/icon-128x128.svg +9 -9
  6. package/dist/icons/icon-144x144.svg +9 -9
  7. package/dist/icons/icon-152x152.svg +9 -9
  8. package/dist/icons/icon-192x192.svg +9 -9
  9. package/dist/icons/icon-384x384.svg +9 -9
  10. package/dist/icons/icon-512x512.svg +9 -9
  11. package/dist/icons/icon-72x72.svg +9 -9
  12. package/dist/icons/icon-96x96.svg +9 -9
  13. package/dist/icons/icon-template.svg +9 -9
  14. package/dist/index.html +2 -2
  15. package/dist/logo.svg +12 -12
  16. package/dist/openapi.yaml +383 -1
  17. package/dist-server/server/claude-sdk.js +38 -7
  18. package/dist-server/server/claude-sdk.js.map +1 -1
  19. package/dist-server/server/cli.js +12 -17
  20. package/dist-server/server/cli.js.map +1 -1
  21. package/dist-server/server/daemon-manager.js +98 -51
  22. package/dist-server/server/daemon-manager.js.map +1 -1
  23. package/dist-server/server/database/json-store.js +8 -5
  24. package/dist-server/server/database/json-store.js.map +1 -1
  25. package/dist-server/server/index.js +34 -9
  26. package/dist-server/server/index.js.map +1 -1
  27. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js +73 -0
  28. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js.map +1 -0
  29. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js +17 -0
  30. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js.map +1 -0
  31. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js +234 -0
  32. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js.map +1 -0
  33. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js +202 -0
  34. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js.map +1 -0
  35. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js +205 -0
  36. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js.map +1 -0
  37. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js +205 -0
  38. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js.map +1 -0
  39. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js +205 -0
  40. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js.map +1 -0
  41. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js +205 -0
  42. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js.map +1 -0
  43. package/dist-server/server/modules/orchestration/a2a/agent-card.js +50 -0
  44. package/dist-server/server/modules/orchestration/a2a/agent-card.js.map +1 -0
  45. package/dist-server/server/modules/orchestration/a2a/auth.middleware.js +25 -0
  46. package/dist-server/server/modules/orchestration/a2a/auth.middleware.js.map +1 -0
  47. package/dist-server/server/modules/orchestration/a2a/bus.js +34 -0
  48. package/dist-server/server/modules/orchestration/a2a/bus.js.map +1 -0
  49. package/dist-server/server/modules/orchestration/a2a/routes.js +497 -0
  50. package/dist-server/server/modules/orchestration/a2a/routes.js.map +1 -0
  51. package/dist-server/server/modules/orchestration/a2a/task-store.js +144 -0
  52. package/dist-server/server/modules/orchestration/a2a/task-store.js.map +1 -0
  53. package/dist-server/server/modules/orchestration/a2a/types.js +6 -0
  54. package/dist-server/server/modules/orchestration/a2a/types.js.map +1 -0
  55. package/dist-server/server/modules/orchestration/a2a/validator.js +101 -0
  56. package/dist-server/server/modules/orchestration/a2a/validator.js.map +1 -0
  57. package/dist-server/server/modules/orchestration/index.js +24 -0
  58. package/dist-server/server/modules/orchestration/index.js.map +1 -0
  59. package/dist-server/server/modules/orchestration/preview/port-watcher.js +90 -0
  60. package/dist-server/server/modules/orchestration/preview/port-watcher.js.map +1 -0
  61. package/dist-server/server/modules/orchestration/preview/preview-proxy.js +58 -0
  62. package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -0
  63. package/dist-server/server/modules/orchestration/preview/types.js +2 -0
  64. package/dist-server/server/modules/orchestration/preview/types.js.map +1 -0
  65. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js +37 -0
  66. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js.map +1 -0
  67. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +68 -0
  68. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -0
  69. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +128 -0
  70. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -0
  71. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js +2 -0
  72. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js.map +1 -0
  73. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js +126 -0
  74. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js.map +1 -0
  75. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1047 -0
  76. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -0
  77. package/dist-server/server/modules/orchestration/workflows/workflow-store.js +76 -0
  78. package/dist-server/server/modules/orchestration/workflows/workflow-store.js.map +1 -0
  79. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +151 -0
  80. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -0
  81. package/dist-server/server/modules/orchestration/workflows/workflow.types.js +2 -0
  82. package/dist-server/server/modules/orchestration/workflows/workflow.types.js.map +1 -0
  83. package/dist-server/server/modules/orchestration/workflows/workspace-target.js +98 -0
  84. package/dist-server/server/modules/orchestration/workflows/workspace-target.js.map +1 -0
  85. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js +122 -0
  86. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js.map +1 -0
  87. package/dist-server/server/modules/orchestration/workspace/path-safety.js +48 -0
  88. package/dist-server/server/modules/orchestration/workspace/path-safety.js.map +1 -0
  89. package/dist-server/server/modules/orchestration/workspace/types.js +11 -0
  90. package/dist-server/server/modules/orchestration/workspace/types.js.map +1 -0
  91. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js +80 -0
  92. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js.map +1 -0
  93. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js +96 -0
  94. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js.map +1 -0
  95. package/dist-server/server/modules/providers/index.js +3 -0
  96. package/dist-server/server/modules/providers/index.js.map +1 -0
  97. package/dist-server/server/openai-codex.js +35 -4
  98. package/dist-server/server/openai-codex.js.map +1 -1
  99. package/dist-server/server/routes/taskmaster.js +106 -89
  100. package/dist-server/server/routes/taskmaster.js.map +1 -1
  101. package/package.json +3 -1
  102. package/scripts/smoke/a2a-roundtrip.mjs +167 -0
  103. package/scripts/smoke/orchestration-api.mjs +172 -0
  104. package/scripts/smoke/orchestration-live-run.mjs +176 -0
  105. package/server/claude-sdk.js +48 -7
  106. package/server/cli.js +12 -17
  107. package/server/daemon-manager.js +90 -51
  108. package/server/database/db.js +794 -794
  109. package/server/database/json-store.js +8 -5
  110. package/server/index.js +49 -9
  111. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -0
  112. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +55 -0
  113. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +284 -0
  114. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -0
  115. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -0
  116. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -0
  117. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -0
  118. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -0
  119. package/server/modules/orchestration/a2a/agent-card.ts +55 -0
  120. package/server/modules/orchestration/a2a/auth.middleware.ts +29 -0
  121. package/server/modules/orchestration/a2a/bus.ts +46 -0
  122. package/server/modules/orchestration/a2a/routes.ts +577 -0
  123. package/server/modules/orchestration/a2a/task-store.ts +178 -0
  124. package/server/modules/orchestration/a2a/types.ts +125 -0
  125. package/server/modules/orchestration/a2a/validator.ts +113 -0
  126. package/server/modules/orchestration/index.ts +66 -0
  127. package/server/modules/orchestration/preview/port-watcher.ts +112 -0
  128. package/server/modules/orchestration/preview/preview-proxy.ts +60 -0
  129. package/server/modules/orchestration/preview/types.ts +19 -0
  130. package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -0
  131. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -0
  132. package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -0
  133. package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -0
  134. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -0
  135. package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -0
  136. package/server/modules/orchestration/workflows/workflow-store.ts +97 -0
  137. package/server/modules/orchestration/workflows/workflow.routes.ts +169 -0
  138. package/server/modules/orchestration/workflows/workflow.types.ts +70 -0
  139. package/server/modules/orchestration/workflows/workspace-target.ts +120 -0
  140. package/server/modules/orchestration/workspace/docker-workspace.ts +135 -0
  141. package/server/modules/orchestration/workspace/path-safety.ts +55 -0
  142. package/server/modules/orchestration/workspace/types.ts +52 -0
  143. package/server/modules/orchestration/workspace/workspace-manager.ts +97 -0
  144. package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -0
  145. package/server/modules/providers/index.ts +2 -0
  146. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
  147. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  148. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  149. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
  150. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  151. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  152. package/server/modules/providers/shared/provider-configs.ts +142 -142
  153. package/server/openai-codex.js +40 -4
  154. package/server/qwen-code-cli.js +395 -395
  155. package/server/qwen-response-handler.js +73 -73
  156. package/server/routes/qwen.js +27 -27
  157. package/server/routes/taskmaster.js +116 -91
  158. package/server/services/external-access.js +171 -171
  159. package/server/services/provider-models.js +381 -381
  160. package/server/services/telegram/telegram-http-client.js +130 -130
  161. package/server/services/vapid-keys.js +36 -36
  162. package/server/utils/port-access.js +209 -209
  163. package/dist/assets/index-B1ghfb4w.css +0 -32
  164. package/dist/assets/index-oLYHJ2X5.js +0 -852
@@ -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();
@@ -0,0 +1,125 @@
1
+ // server/modules/orchestration/a2a/types.ts
2
+ // A2A protocol v0.2 types — minimal surface used by pixcode.
3
+ // See https://a2a-protocol.org for the full spec; this file
4
+ // keeps only what the orchestrator actually exchanges.
5
+
6
+ export type TaskState =
7
+ | 'submitted'
8
+ | 'working'
9
+ | 'input-required'
10
+ | 'completed'
11
+ | 'canceled'
12
+ | 'failed';
13
+
14
+ export type Role = 'user' | 'agent';
15
+
16
+ export type PartKind = 'text' | 'file' | 'data';
17
+
18
+ export interface TextPart {
19
+ kind: 'text';
20
+ text: string;
21
+ }
22
+
23
+ export interface FilePart {
24
+ kind: 'file';
25
+ name: string;
26
+ mimeType?: string;
27
+ bytesBase64?: string;
28
+ uri?: string;
29
+ }
30
+
31
+ export interface DataPart {
32
+ kind: 'data';
33
+ data: Record<string, unknown>;
34
+ }
35
+
36
+ export type Part = TextPart | FilePart | DataPart;
37
+
38
+ export interface Message {
39
+ messageId: string;
40
+ role: Role;
41
+ parts: Part[];
42
+ /** Required for task-scoped messages. Omit only for broadcast/standalone messages. */
43
+ taskId?: string;
44
+ }
45
+
46
+ export type ArtifactType =
47
+ | 'file-diff'
48
+ | 'command-output'
49
+ | 'preview-url'
50
+ | 'data';
51
+
52
+ // Note: Artifact and AuthScheme use `type` discriminator (matches A2A
53
+ // v0.2 wire format). Part and BusEvent use `kind` per the same spec.
54
+ export interface Artifact {
55
+ artifactId: string;
56
+ type: ArtifactType;
57
+ parts: Part[];
58
+ metadata?: Record<string, unknown>;
59
+ }
60
+
61
+ export interface TaskError {
62
+ code: string;
63
+ message: string;
64
+ details?: Record<string, unknown>;
65
+ }
66
+
67
+ export interface Task {
68
+ id: string;
69
+ contextId?: string;
70
+ state: TaskState;
71
+ history: Message[];
72
+ artifacts: Artifact[];
73
+ error?: TaskError;
74
+ metadata?: Record<string, unknown>;
75
+ createdAt: number;
76
+ updatedAt: number;
77
+ }
78
+
79
+ export interface TaskSummary {
80
+ id: string;
81
+ contextId?: string;
82
+ state: TaskState;
83
+ adapterId?: string;
84
+ adapterSelector?: string;
85
+ error?: TaskError;
86
+ createdAt: number;
87
+ updatedAt: number;
88
+ messageCount: number;
89
+ artifactCount: number;
90
+ lastMessage?: Message;
91
+ }
92
+
93
+ export interface AgentSkill {
94
+ id: string;
95
+ description: string;
96
+ examples?: string[];
97
+ }
98
+
99
+ export type AuthScheme =
100
+ | { type: 'none' }
101
+ | { type: 'bearer' }
102
+ | { type: 'mtls' };
103
+
104
+ export interface AgentCard {
105
+ name: string;
106
+ description: string;
107
+ url: string;
108
+ version: string;
109
+ capabilities: string[];
110
+ skills: AgentSkill[];
111
+ authentication: AuthScheme;
112
+ }
113
+
114
+ export interface SubmitTaskInput {
115
+ message: Message;
116
+ contextId?: string;
117
+ metadata?: Record<string, unknown>;
118
+ /** Adapter id, "auto", or "skill:<id>". Resolved by the adapter registry. */
119
+ adapterId: string;
120
+ }
121
+
122
+ export type BusEvent =
123
+ | { kind: 'task-state'; taskId: string; state: TaskState; error?: TaskError }
124
+ | { kind: 'message'; taskId: string; message: Message }
125
+ | { kind: 'artifact'; taskId: string; artifact: Artifact };
@@ -0,0 +1,113 @@
1
+ // server/modules/orchestration/a2a/validator.ts
2
+ // Hand-written validators for incoming A2A payloads.
3
+ // We deliberately avoid adding a new dep (zod, ajv) for the
4
+ // foundation; a follow-on plan can swap to a schema lib if needed.
5
+ //
6
+ // All path strings use JSONPath-style "$" as the document root so
7
+ // callers can map errors to wire-payload locations consistently.
8
+
9
+ import type { AgentCard, DataPart, FilePart, Message, Part, SubmitTaskInput, TextPart } from '@/modules/orchestration/a2a/types.js';
10
+
11
+ export class A2AValidationError extends Error {
12
+ constructor(message: string, public readonly path: string) {
13
+ super(`${path}: ${message}`);
14
+ this.name = 'A2AValidationError';
15
+ }
16
+ }
17
+
18
+ function assertNonEmptyString(value: unknown, path: string): asserts value is string {
19
+ if (typeof value !== 'string' || value.length === 0) {
20
+ throw new A2AValidationError('expected non-empty string', path);
21
+ }
22
+ }
23
+
24
+ function assertOptionalNonEmptyString(value: unknown, path: string): void {
25
+ if (value === undefined) {
26
+ return;
27
+ }
28
+ assertNonEmptyString(value, path);
29
+ }
30
+
31
+ function assertPlainObject(value: unknown, path: string): void {
32
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
33
+ throw new A2AValidationError('expected plain object', path);
34
+ }
35
+ }
36
+
37
+ function assertPart(value: unknown, path: string): asserts value is Part {
38
+ if (!value || typeof value !== 'object') {
39
+ throw new A2AValidationError('expected object', path);
40
+ }
41
+ const part = value as { kind?: unknown };
42
+ if (part.kind === 'text') {
43
+ assertNonEmptyString((part as Partial<TextPart>).text, `${path}.text`);
44
+ return;
45
+ }
46
+ if (part.kind === 'file') {
47
+ assertNonEmptyString((part as Partial<FilePart>).name, `${path}.name`);
48
+ return;
49
+ }
50
+ if (part.kind === 'data') {
51
+ const data = (part as Partial<DataPart>).data;
52
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
53
+ throw new A2AValidationError('data must be a plain object', `${path}.data`);
54
+ }
55
+ return;
56
+ }
57
+ throw new A2AValidationError('part.kind must be text|file|data', `${path}.kind`);
58
+ }
59
+
60
+ export function assertMessage(value: unknown, path = '$'): asserts value is Message {
61
+ if (!value || typeof value !== 'object') {
62
+ throw new A2AValidationError('expected object', path);
63
+ }
64
+ const m = value as { messageId?: unknown; role?: unknown; parts?: unknown; taskId?: unknown };
65
+ assertNonEmptyString(m.messageId, `${path}.messageId`);
66
+ if (m.role !== 'user' && m.role !== 'agent') {
67
+ throw new A2AValidationError('role must be user|agent', `${path}.role`);
68
+ }
69
+ if (!Array.isArray(m.parts) || m.parts.length === 0) {
70
+ throw new A2AValidationError('parts must be non-empty array', `${path}.parts`);
71
+ }
72
+ assertOptionalNonEmptyString(m.taskId, `${path}.taskId`);
73
+ m.parts.forEach((p, i) => assertPart(p, `${path}.parts[${i}]`));
74
+ }
75
+
76
+ export function assertSubmitTaskInput(value: unknown): asserts value is SubmitTaskInput {
77
+ if (!value || typeof value !== 'object') {
78
+ throw new A2AValidationError('expected object', '$');
79
+ }
80
+ const v = value as {
81
+ message?: unknown;
82
+ adapterId?: unknown;
83
+ contextId?: unknown;
84
+ metadata?: unknown;
85
+ };
86
+ assertMessage(v.message, '$.message');
87
+ assertNonEmptyString(v.adapterId, '$.adapterId');
88
+ assertOptionalNonEmptyString(v.contextId, '$.contextId');
89
+ if (v.metadata !== undefined) {
90
+ assertPlainObject(v.metadata, '$.metadata');
91
+ }
92
+ }
93
+
94
+ export function assertAgentCard(value: unknown): asserts value is AgentCard {
95
+ if (!value || typeof value !== 'object') {
96
+ throw new A2AValidationError('expected object', '$');
97
+ }
98
+ const card = value as Partial<AgentCard>;
99
+ assertNonEmptyString(card.name, '$.name');
100
+ assertNonEmptyString(card.description, '$.description');
101
+ assertNonEmptyString(card.url, '$.url');
102
+ assertNonEmptyString(card.version, '$.version');
103
+ if (!Array.isArray(card.capabilities)) {
104
+ throw new A2AValidationError('capabilities must be array', '$.capabilities');
105
+ }
106
+ if (!Array.isArray(card.skills)) {
107
+ throw new A2AValidationError('skills must be array', '$.skills');
108
+ }
109
+ card.skills.forEach((s, i) => {
110
+ assertNonEmptyString(s.id, `$.skills[${i}].id`);
111
+ assertNonEmptyString(s.description, `$.skills[${i}].description`);
112
+ });
113
+ }
@@ -0,0 +1,66 @@
1
+ // server/modules/orchestration/index.ts
2
+ // Public surface for the orchestration module.
3
+ // All cross-module consumers must import from here per
4
+ // eslint.config.js boundaries rules.
5
+
6
+ export { createA2ARouter } from './a2a/routes.js';
7
+ export { adapterRegistry } from './a2a/adapter-registry.js';
8
+ export { ClaudeCodeA2AAdapter } from './a2a/adapters/claude-code.adapter.js';
9
+ export { CodexA2AAdapter } from './a2a/adapters/codex.adapter.js';
10
+ export { CursorA2AAdapter } from './a2a/adapters/cursor.adapter.js';
11
+ export { GeminiA2AAdapter } from './a2a/adapters/gemini.adapter.js';
12
+ export { QwenA2AAdapter } from './a2a/adapters/qwen.adapter.js';
13
+ export { OpenCodeA2AAdapter } from './a2a/adapters/opencode.adapter.js';
14
+ export type {
15
+ AdapterContext,
16
+ TaskHandle,
17
+ } from './a2a/adapters/abstract-a2a.adapter.js';
18
+ export { AbstractA2AAdapter } from './a2a/adapters/abstract-a2a.adapter.js';
19
+ export { a2aBus } from './a2a/bus.js';
20
+ export { portWatcher } from './preview/port-watcher.js';
21
+ export { createPreviewProxyRouter } from './preview/preview-proxy.js';
22
+ export { createOrchestrationTaskRouter } from './tasks/orchestration-task.routes.js';
23
+ export { orchestrationTaskService } from './tasks/orchestration-task.service.js';
24
+ export { createWorkflowRouter } from './workflows/workflow.routes.js';
25
+ export { workflowRunner } from './workflows/workflow-runner.js';
26
+ export { workflowStore } from './workflows/workflow-store.js';
27
+ export { workspaceManager } from './workspace/workspace-manager.js';
28
+ export type {
29
+ AgentCard,
30
+ Artifact,
31
+ ArtifactType,
32
+ BusEvent,
33
+ Message,
34
+ Part,
35
+ SubmitTaskInput,
36
+ Task,
37
+ TaskSummary,
38
+ TaskError,
39
+ TaskState,
40
+ } from './a2a/types.js';
41
+ export type {
42
+ PortEvent,
43
+ PreviewArtifactData,
44
+ } from './preview/types.js';
45
+ export type {
46
+ CreateOrchestrationTaskInput,
47
+ DispatchOrchestrationTaskInput,
48
+ OrchestrationTask,
49
+ OrchestrationTaskState,
50
+ } from './tasks/orchestration-task.types.js';
51
+ export type {
52
+ Workflow,
53
+ WorkflowNode,
54
+ WorkflowNodeRun,
55
+ WorkflowNodeStatus,
56
+ WorkflowRun,
57
+ WorkflowRunStatus,
58
+ } from './workflows/workflow.types.js';
59
+ export type {
60
+ ExecResult,
61
+ WorkspaceHandle,
62
+ WorkspaceKind,
63
+ WorkspaceMetadata,
64
+ WorkspaceRequest,
65
+ } from './workspace/types.js';
66
+ export { WorkspaceError } from './workspace/types.js';
@@ -0,0 +1,112 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+
4
+ import type { PortEvent } from '@/modules/orchestration/preview/types.js';
5
+ import type { WorkspaceHandle } from '@/modules/orchestration/workspace/types.js';
6
+
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ interface WatchOptions {
10
+ taskId: string;
11
+ workspace: WorkspaceHandle;
12
+ intervalMs?: number;
13
+ onPort(event: PortEvent): void;
14
+ }
15
+
16
+ interface ListeningPort {
17
+ port: number;
18
+ host: string;
19
+ processName?: string;
20
+ }
21
+
22
+ const knownPreviewPorts = new Map<number, PortEvent>();
23
+
24
+ function parseAddress(address: string): { host: string; port: number } | null {
25
+ const normalized = address.trim();
26
+ const idx = normalized.lastIndexOf(':');
27
+ if (idx === -1) return null;
28
+ const port = Number.parseInt(normalized.slice(idx + 1), 10);
29
+ if (!Number.isFinite(port) || port <= 0) return null;
30
+ const host = normalized.slice(0, idx).replace(/^\[|\]$/g, '') || '127.0.0.1';
31
+ return { host: host === '*' || host === '0.0.0.0' ? '127.0.0.1' : host, port };
32
+ }
33
+
34
+ function parseSs(output: string): ListeningPort[] {
35
+ const ports: ListeningPort[] = [];
36
+ for (const line of output.split('\n')) {
37
+ if (!line.includes('LISTEN')) continue;
38
+ const parts = line.trim().split(/\s+/);
39
+ const address = parts[3] ?? parts[4];
40
+ const parsed = address ? parseAddress(address) : null;
41
+ if (!parsed) continue;
42
+ const processMatch = line.match(/users:\(\("([^"]+)"/);
43
+ ports.push({
44
+ ...parsed,
45
+ processName: processMatch?.[1],
46
+ });
47
+ }
48
+ return ports;
49
+ }
50
+
51
+ async function readListeningPorts(): Promise<ListeningPort[]> {
52
+ try {
53
+ const { stdout } = await execFileAsync('ss', ['-ltnp'], {
54
+ maxBuffer: 1024 * 1024,
55
+ });
56
+ return parseSs(String(stdout));
57
+ } catch {
58
+ return [];
59
+ }
60
+ }
61
+
62
+ export function getKnownPreviewPort(port: number): PortEvent | undefined {
63
+ return knownPreviewPorts.get(port);
64
+ }
65
+
66
+ export class PortWatcher {
67
+ watch(options: WatchOptions): () => void {
68
+ const {
69
+ taskId,
70
+ workspace,
71
+ intervalMs = 1000,
72
+ onPort,
73
+ } = options;
74
+ const seen = new Set<number>();
75
+ let stopped = false;
76
+ let timer: NodeJS.Timeout | undefined;
77
+ let initialized = false;
78
+
79
+ const tick = async () => {
80
+ if (stopped) return;
81
+ const ports = await readListeningPorts();
82
+ for (const port of ports) {
83
+ if (seen.has(port.port)) continue;
84
+ seen.add(port.port);
85
+ if (!initialized) continue;
86
+ const event: PortEvent = {
87
+ taskId,
88
+ workspaceId: workspace.id,
89
+ port: port.port,
90
+ host: port.host,
91
+ url: `http://${port.host}:${port.port}`,
92
+ processName: port.processName,
93
+ confidence: 'low',
94
+ detectedAt: Date.now(),
95
+ };
96
+ knownPreviewPorts.set(port.port, event);
97
+ onPort(event);
98
+ }
99
+ initialized = true;
100
+ timer = setTimeout(tick, intervalMs);
101
+ };
102
+
103
+ void tick();
104
+
105
+ return () => {
106
+ stopped = true;
107
+ if (timer) clearTimeout(timer);
108
+ };
109
+ }
110
+ }
111
+
112
+ export const portWatcher = new PortWatcher();
@@ -0,0 +1,60 @@
1
+ import type { RequestHandler, Router } from 'express';
2
+ import express from 'express';
3
+
4
+ import { getKnownPreviewPort } from '@/modules/orchestration/preview/port-watcher.js';
5
+
6
+ function buildTargetUrl(port: number, path: string): string {
7
+ const event = getKnownPreviewPort(port);
8
+ const host = event?.host ?? '127.0.0.1';
9
+ return `http://${host}:${port}${path}`;
10
+ }
11
+
12
+ function proxyHandler(): RequestHandler {
13
+ return async (req, res) => {
14
+ const rawPort = Array.isArray(req.params.port) ? req.params.port[0] : req.params.port;
15
+ const port = Number.parseInt(rawPort, 10);
16
+ if (!Number.isFinite(port) || port <= 0 || !getKnownPreviewPort(port)) {
17
+ res.status(404).json({
18
+ error: {
19
+ code: 'PREVIEW_PORT_NOT_FOUND',
20
+ message: 'Preview port is not registered for an orchestration task.',
21
+ },
22
+ });
23
+ return;
24
+ }
25
+
26
+ const path = req.originalUrl.replace(/^\/preview\/\d+/, '') || '/';
27
+ const target = buildTargetUrl(port, path);
28
+ try {
29
+ const upstream = await fetch(target, {
30
+ method: req.method,
31
+ headers: {
32
+ accept: req.header('accept') ?? '*/*',
33
+ 'user-agent': req.header('user-agent') ?? 'pixcode-preview-proxy',
34
+ },
35
+ });
36
+ res.status(upstream.status);
37
+ upstream.headers.forEach((value, key) => {
38
+ const lower = key.toLowerCase();
39
+ if (lower === 'content-encoding' || lower === 'content-length') return;
40
+ if (lower === 'x-frame-options' || lower === 'content-security-policy') return;
41
+ res.setHeader(key, value);
42
+ });
43
+ res.send(Buffer.from(await upstream.arrayBuffer()));
44
+ } catch (error) {
45
+ res.status(502).json({
46
+ error: {
47
+ code: 'PREVIEW_PROXY_FAILED',
48
+ message: error instanceof Error ? error.message : String(error),
49
+ },
50
+ });
51
+ }
52
+ };
53
+ }
54
+
55
+ export function createPreviewProxyRouter(): Router {
56
+ const router = express.Router();
57
+ router.use('/:port', proxyHandler());
58
+ router.use('/:port/*', proxyHandler());
59
+ return router;
60
+ }
@@ -0,0 +1,19 @@
1
+ export interface PortEvent {
2
+ taskId: string;
3
+ workspaceId?: string;
4
+ port: number;
5
+ host: string;
6
+ url: string;
7
+ processName?: string;
8
+ confidence: 'low' | 'medium' | 'high';
9
+ detectedAt: number;
10
+ }
11
+
12
+ export interface PreviewArtifactData {
13
+ url: string;
14
+ proxiedUrl: string;
15
+ port: number;
16
+ host: string;
17
+ processName?: string;
18
+ confidence: 'low' | 'medium' | 'high';
19
+ }