@pixelbyte-software/pixcode 1.34.0 → 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 (155) 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 +31 -10
  26. package/dist-server/server/index.js.map +1 -1
  27. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js +45 -19
  28. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js.map +1 -1
  29. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js.map +1 -1
  30. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js +1 -0
  31. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js.map +1 -1
  32. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js +202 -0
  33. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js.map +1 -0
  34. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js +205 -0
  35. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js.map +1 -0
  36. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js +205 -0
  37. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js.map +1 -0
  38. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js +205 -0
  39. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js.map +1 -0
  40. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js +205 -0
  41. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js.map +1 -0
  42. package/dist-server/server/modules/orchestration/a2a/routes.js +298 -34
  43. package/dist-server/server/modules/orchestration/a2a/routes.js.map +1 -1
  44. package/dist-server/server/modules/orchestration/a2a/task-store.js +144 -0
  45. package/dist-server/server/modules/orchestration/a2a/task-store.js.map +1 -0
  46. package/dist-server/server/modules/orchestration/a2a/validator.js +16 -0
  47. package/dist-server/server/modules/orchestration/a2a/validator.js.map +1 -1
  48. package/dist-server/server/modules/orchestration/index.js +14 -0
  49. package/dist-server/server/modules/orchestration/index.js.map +1 -1
  50. package/dist-server/server/modules/orchestration/preview/port-watcher.js +90 -0
  51. package/dist-server/server/modules/orchestration/preview/port-watcher.js.map +1 -0
  52. package/dist-server/server/modules/orchestration/preview/preview-proxy.js +58 -0
  53. package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -0
  54. package/dist-server/server/modules/orchestration/preview/types.js +2 -0
  55. package/dist-server/server/modules/orchestration/preview/types.js.map +1 -0
  56. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js +37 -0
  57. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js.map +1 -0
  58. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +68 -0
  59. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -0
  60. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +128 -0
  61. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -0
  62. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js +2 -0
  63. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js.map +1 -0
  64. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js +126 -0
  65. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js.map +1 -0
  66. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1047 -0
  67. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -0
  68. package/dist-server/server/modules/orchestration/workflows/workflow-store.js +76 -0
  69. package/dist-server/server/modules/orchestration/workflows/workflow-store.js.map +1 -0
  70. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +151 -0
  71. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -0
  72. package/dist-server/server/modules/orchestration/workflows/workflow.types.js +2 -0
  73. package/dist-server/server/modules/orchestration/workflows/workflow.types.js.map +1 -0
  74. package/dist-server/server/modules/orchestration/workflows/workspace-target.js +98 -0
  75. package/dist-server/server/modules/orchestration/workflows/workspace-target.js.map +1 -0
  76. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js +122 -0
  77. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js.map +1 -0
  78. package/dist-server/server/modules/orchestration/workspace/path-safety.js +48 -0
  79. package/dist-server/server/modules/orchestration/workspace/path-safety.js.map +1 -0
  80. package/dist-server/server/modules/orchestration/workspace/types.js +11 -0
  81. package/dist-server/server/modules/orchestration/workspace/types.js.map +1 -0
  82. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js +80 -0
  83. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js.map +1 -0
  84. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js +96 -0
  85. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js.map +1 -0
  86. package/dist-server/server/modules/providers/index.js +3 -0
  87. package/dist-server/server/modules/providers/index.js.map +1 -0
  88. package/dist-server/server/openai-codex.js +35 -4
  89. package/dist-server/server/openai-codex.js.map +1 -1
  90. package/dist-server/server/routes/taskmaster.js +106 -89
  91. package/dist-server/server/routes/taskmaster.js.map +1 -1
  92. package/package.json +3 -1
  93. package/scripts/smoke/a2a-roundtrip.mjs +167 -98
  94. package/scripts/smoke/orchestration-api.mjs +172 -0
  95. package/scripts/smoke/orchestration-live-run.mjs +176 -0
  96. package/server/claude-sdk.js +48 -7
  97. package/server/cli.js +12 -17
  98. package/server/daemon-manager.js +90 -51
  99. package/server/database/db.js +794 -794
  100. package/server/database/json-store.js +8 -5
  101. package/server/index.js +40 -9
  102. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -58
  103. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +55 -49
  104. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +284 -283
  105. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -0
  106. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -0
  107. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -0
  108. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -0
  109. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -0
  110. package/server/modules/orchestration/a2a/agent-card.ts +55 -55
  111. package/server/modules/orchestration/a2a/auth.middleware.ts +29 -29
  112. package/server/modules/orchestration/a2a/bus.ts +46 -46
  113. package/server/modules/orchestration/a2a/routes.ts +577 -264
  114. package/server/modules/orchestration/a2a/task-store.ts +178 -0
  115. package/server/modules/orchestration/a2a/types.ts +125 -111
  116. package/server/modules/orchestration/a2a/validator.ts +113 -90
  117. package/server/modules/orchestration/index.ts +66 -26
  118. package/server/modules/orchestration/preview/port-watcher.ts +112 -0
  119. package/server/modules/orchestration/preview/preview-proxy.ts +60 -0
  120. package/server/modules/orchestration/preview/types.ts +19 -0
  121. package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -0
  122. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -0
  123. package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -0
  124. package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -0
  125. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -0
  126. package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -0
  127. package/server/modules/orchestration/workflows/workflow-store.ts +97 -0
  128. package/server/modules/orchestration/workflows/workflow.routes.ts +169 -0
  129. package/server/modules/orchestration/workflows/workflow.types.ts +70 -0
  130. package/server/modules/orchestration/workflows/workspace-target.ts +120 -0
  131. package/server/modules/orchestration/workspace/docker-workspace.ts +135 -0
  132. package/server/modules/orchestration/workspace/path-safety.ts +55 -0
  133. package/server/modules/orchestration/workspace/types.ts +52 -0
  134. package/server/modules/orchestration/workspace/workspace-manager.ts +97 -0
  135. package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -0
  136. package/server/modules/providers/index.ts +2 -0
  137. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
  138. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  139. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  140. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
  141. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  142. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  143. package/server/modules/providers/shared/provider-configs.ts +142 -142
  144. package/server/openai-codex.js +40 -4
  145. package/server/qwen-code-cli.js +395 -395
  146. package/server/qwen-response-handler.js +73 -73
  147. package/server/routes/qwen.js +27 -27
  148. package/server/routes/taskmaster.js +116 -91
  149. package/server/services/external-access.js +171 -171
  150. package/server/services/provider-models.js +381 -381
  151. package/server/services/telegram/telegram-http-client.js +130 -130
  152. package/server/services/vapid-keys.js +36 -36
  153. package/server/utils/port-access.js +209 -209
  154. package/dist/assets/index-B1ghfb4w.css +0 -32
  155. package/dist/assets/index-BvClqlMf.js +0 -852
@@ -29,7 +29,7 @@ import path from 'node:path';
29
29
  const CURRENT_VERSION = 1;
30
30
 
31
31
  // Tables the store manages — empty arrays on a fresh file.
32
- const EMPTY_STORE = () => ({
32
+ const EMPTY_STORE = (extraTables = {}) => ({
33
33
  _version: CURRENT_VERSION,
34
34
  _sequences: {
35
35
  users: 0,
@@ -37,6 +37,7 @@ const EMPTY_STORE = () => ({
37
37
  user_credentials: 0,
38
38
  vapid_keys: 0,
39
39
  push_subscriptions: 0,
40
+ ...Object.fromEntries(Object.keys(extraTables).map((k) => [k, 0])),
40
41
  },
41
42
  users: [],
42
43
  api_keys: [],
@@ -48,12 +49,14 @@ const EMPTY_STORE = () => ({
48
49
  app_config: [], // each row: { key, value, created_at }
49
50
  telegram_config: [], // 0 or 1 row: { id=1, bot_token, bot_username, updated_at }
50
51
  telegram_links: [], // each row: { user_id, chat_id, ... }
52
+ ...Object.fromEntries(Object.entries(extraTables).map(([k, v]) => [k, v ?? []])),
51
53
  });
52
54
 
53
55
  export class JsonStore {
54
- constructor(filePath) {
56
+ constructor(filePath, extraTables = {}) {
55
57
  this.filePath = filePath;
56
58
  this.tmpPath = `${filePath}.tmp`;
59
+ this.extraTables = extraTables;
57
60
  this.data = null;
58
61
  this._ensureLoaded();
59
62
  }
@@ -69,7 +72,7 @@ export class JsonStore {
69
72
  }
70
73
 
71
74
  if (!fs.existsSync(this.filePath)) {
72
- this.data = EMPTY_STORE();
75
+ this.data = EMPTY_STORE(this.extraTables);
73
76
  this._flush();
74
77
  return;
75
78
  }
@@ -79,7 +82,7 @@ export class JsonStore {
79
82
  const parsed = JSON.parse(raw);
80
83
  // Fill missing keys from EMPTY_STORE so adding a new "table" in a
81
84
  // later schema doesn't crash a fresh deploy reading an old file.
82
- this.data = { ...EMPTY_STORE(), ...parsed };
85
+ this.data = { ...EMPTY_STORE(this.extraTables), ...parsed };
83
86
  // Ensure each well-known array key is actually an array — defends
84
87
  // against a hand-edited file that set one to null or an object.
85
88
  const empty = EMPTY_STORE();
@@ -96,7 +99,7 @@ export class JsonStore {
96
99
  const backup = `${this.filePath}.corrupt-${Date.now()}`;
97
100
  console.error(`[JsonStore] Failed to read ${this.filePath}: ${err.message}. Backing up to ${backup}.`);
98
101
  try { fs.renameSync(this.filePath, backup); } catch { /* noop */ }
99
- this.data = EMPTY_STORE();
102
+ this.data = EMPTY_STORE(this.extraTables);
100
103
  this._flush();
101
104
  }
102
105
  }
package/server/index.js CHANGED
@@ -75,6 +75,14 @@ import {
75
75
  createA2ARouter,
76
76
  adapterRegistry,
77
77
  ClaudeCodeA2AAdapter,
78
+ CodexA2AAdapter,
79
+ CursorA2AAdapter,
80
+ GeminiA2AAdapter,
81
+ OpenCodeA2AAdapter,
82
+ QwenA2AAdapter,
83
+ createPreviewProxyRouter,
84
+ createOrchestrationTaskRouter,
85
+ createWorkflowRouter,
78
86
  } from './modules/orchestration/index.js';
79
87
  import networkRoutes from './routes/network.js';
80
88
  import telegramRoutes from './routes/telegram.js';
@@ -383,7 +391,15 @@ app.use('/api/providers', authenticateToken, providerRoutes);
383
391
 
384
392
  // A2A protocol router — has its own auth middleware, do NOT wrap with authenticateToken
385
393
  adapterRegistry.register(new ClaudeCodeA2AAdapter());
394
+ adapterRegistry.register(new CodexA2AAdapter());
395
+ adapterRegistry.register(new CursorA2AAdapter());
396
+ adapterRegistry.register(new GeminiA2AAdapter());
397
+ adapterRegistry.register(new QwenA2AAdapter());
398
+ adapterRegistry.register(new OpenCodeA2AAdapter());
386
399
  app.use('/a2a', createA2ARouter());
400
+ app.use('/preview', authenticateToken, createPreviewProxyRouter());
401
+ app.use('/api/orchestration', authenticateToken, createOrchestrationTaskRouter());
402
+ app.use('/api/orchestration', authenticateToken, createWorkflowRouter());
387
403
 
388
404
  // Network discovery / QR endpoints (protected)
389
405
  app.use('/api/network', authenticateToken, networkRoutes);
@@ -2831,6 +2847,7 @@ const SERVER_PORT = process.env.SERVER_PORT || 3001;
2831
2847
  const HOST = process.env.HOST || '0.0.0.0';
2832
2848
  const DISPLAY_HOST = getConnectableHost(HOST);
2833
2849
  const VITE_PORT = process.env.VITE_PORT || 5173;
2850
+ const SEPARATE_FRONTEND = process.env.PIXCODE_SEPARATE_FRONTEND === '1';
2834
2851
 
2835
2852
  async function isPortOpen(port, timeoutMs = 800) {
2836
2853
  return await new Promise((resolve) => {
@@ -2882,7 +2899,17 @@ function printSystemDaemonActiveNotice(port) {
2882
2899
  console.log(`${c.info('[INFO]')} Logs: ${c.bright(logsCommand)}`);
2883
2900
  }
2884
2901
 
2885
- function printUserDaemonActiveNotice(port, frontendPort) {
2902
+ function daemonFrontendArgs() {
2903
+ return SEPARATE_FRONTEND
2904
+ ? ['--frontend-port', String(VITE_PORT)]
2905
+ : ['--single-port'];
2906
+ }
2907
+
2908
+ function daemonInstallArgs(mode) {
2909
+ return ['install', '--mode', mode, '--port', String(SERVER_PORT), ...daemonFrontendArgs()];
2910
+ }
2911
+
2912
+ function printUserDaemonActiveNotice(port, frontendPort, frontendEnabled = SEPARATE_FRONTEND) {
2886
2913
  const effectivePort = Number(port) || 3001;
2887
2914
  const effectiveFrontendPort = Number(frontendPort) || 5173;
2888
2915
  const statusCommand = buildDaemonCliCommand(
@@ -2898,8 +2925,10 @@ function printUserDaemonActiveNotice(port, frontendPort) {
2898
2925
  DAEMON_COMMAND_CONTEXT
2899
2926
  );
2900
2927
  console.log(`${c.ok('[OK]')} User daemon is active for this account.`);
2901
- console.log(`${c.info('[INFO]')} Backend: ${c.bright(`http://localhost:${effectivePort}`)}`);
2902
- console.log(`${c.info('[INFO]')} Frontend: ${c.bright(`http://localhost:${effectiveFrontendPort}`)}`);
2928
+ console.log(`${c.info('[INFO]')} App URL: ${c.bright(`http://localhost:${effectivePort}`)}`);
2929
+ if (frontendEnabled) {
2930
+ console.log(`${c.info('[INFO]')} Frontend: ${c.bright(`http://localhost:${effectiveFrontendPort}`)}`);
2931
+ }
2903
2932
  console.log(`${c.info('[INFO]')} Status: ${c.bright(statusCommand)}`);
2904
2933
  console.log(`${c.info('[INFO]')} Stop: ${c.bright(stopCommand)}`);
2905
2934
  console.log(`${c.info('[INFO]')} Logs: ${c.bright(logsCommand)}`);
@@ -2919,8 +2948,8 @@ async function maybeAutoDaemonBootstrapFromIndex() {
2919
2948
 
2920
2949
  process.env.PIXCODE_DAEMON_ATTEMPTED = '1';
2921
2950
 
2922
- const systemArgs = ['install', '--mode=system', '--port', String(SERVER_PORT), '--frontend-port', String(VITE_PORT)];
2923
- const userArgs = ['install', '--mode=user', '--port', String(SERVER_PORT), '--frontend-port', String(VITE_PORT)];
2951
+ const systemArgs = daemonInstallArgs('system');
2952
+ const userArgs = daemonInstallArgs('user');
2924
2953
 
2925
2954
  try {
2926
2955
  console.log(`${c.info('[INFO]')} Linux detected. Enforcing system daemon mode for Pixcode...`);
@@ -2944,7 +2973,7 @@ async function maybeAutoDaemonBootstrapFromIndex() {
2944
2973
  {
2945
2974
  subcommand: 'install',
2946
2975
  mode: 'system',
2947
- extraArgs: ['--port', String(SERVER_PORT), '--frontend-port', String(VITE_PORT)],
2976
+ extraArgs: ['--port', String(SERVER_PORT), ...daemonFrontendArgs()],
2948
2977
  },
2949
2978
  DAEMON_COMMAND_CONTEXT
2950
2979
  );
@@ -2978,7 +3007,7 @@ async function maybeAutoDaemonBootstrapFromIndex() {
2978
3007
  {
2979
3008
  subcommand: 'install',
2980
3009
  mode: 'system',
2981
- extraArgs: ['--port', String(SERVER_PORT), '--frontend-port', String(VITE_PORT)],
3010
+ extraArgs: ['--port', String(SERVER_PORT), ...daemonFrontendArgs()],
2982
3011
  },
2983
3012
  DAEMON_COMMAND_CONTEXT
2984
3013
  );
@@ -2986,7 +3015,7 @@ async function maybeAutoDaemonBootstrapFromIndex() {
2986
3015
  {
2987
3016
  subcommand: 'install',
2988
3017
  mode: 'user',
2989
- extraArgs: ['--port', String(SERVER_PORT), '--frontend-port', String(VITE_PORT)],
3018
+ extraArgs: ['--port', String(SERVER_PORT), ...daemonFrontendArgs()],
2990
3019
  },
2991
3020
  DAEMON_COMMAND_CONTEXT
2992
3021
  );
@@ -3054,7 +3083,9 @@ async function startServer() {
3054
3083
  console.log(`${c.info('[INFO]')} To run in production mode, go to http://${DISPLAY_HOST}:${SERVER_PORT}`);
3055
3084
  }
3056
3085
 
3057
- console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
3086
+ if (SEPARATE_FRONTEND) {
3087
+ console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
3088
+ }
3058
3089
 
3059
3090
  server.listen(SERVER_PORT, HOST, async () => {
3060
3091
  const appInstallPath = APP_ROOT;
@@ -1,58 +1,108 @@
1
- // server/modules/orchestration/a2a/adapter-registry.ts
2
- // In-process registry mapping adapter ids to AbstractA2AAdapter
3
- // instances. Resolution supports three id forms:
4
- // - "claude-code" explicit
5
- // - "skill:<skillId>" first REGISTERED adapter advertising that skill
6
- // (Map iteration is insertion-ordered per ES spec).
7
- // - "auto" first registered adapter (placeholder until
8
- // AI-suggested routing arrives in a later plan)
9
-
10
- import type { AbstractA2AAdapter } from '@/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js';
11
- import type { AgentCard } from '@/modules/orchestration/a2a/types.js';
12
-
13
- class AdapterRegistry {
14
- // Map iteration order is insertion-ordered (ES spec); auto and skill: resolution depend on this.
15
- private readonly byId = new Map<string, AbstractA2AAdapter>();
16
-
17
- register(adapter: AbstractA2AAdapter): void {
18
- if (this.byId.has(adapter.id)) {
19
- throw new Error(`A2A adapter already registered: ${adapter.id}`);
20
- }
21
- this.byId.set(adapter.id, adapter);
22
- }
23
-
24
- get(idOrSelector: string): AbstractA2AAdapter | undefined {
25
- if (idOrSelector === 'auto') {
26
- if (this.byId.size > 1) {
27
- throw new Error(
28
- 'A2A adapter selector "auto" is not yet implemented for multi-adapter registries. ' +
29
- 'Pass an explicit adapter id ("claude-code") or a "skill:<id>" selector. ' +
30
- 'AI-suggested routing will replace this stub in a later plan.',
31
- );
32
- }
33
- const first = this.byId.values().next().value;
34
- return first ?? undefined;
35
- }
36
- if (idOrSelector.startsWith('skill:')) {
37
- const skill = idOrSelector.slice('skill:'.length);
38
- for (const adapter of this.byId.values()) {
39
- if (adapter.agentCard.skills.some((s) => s.id === skill)) {
40
- return adapter;
41
- }
42
- }
43
- return undefined;
44
- }
45
- return this.byId.get(idOrSelector);
46
- }
47
-
48
- list(): AbstractA2AAdapter[] {
49
- return [...this.byId.values()];
50
- }
51
-
52
- agentCards(): AgentCard[] {
53
- return this.list().map((a) => a.agentCard);
54
- }
55
- }
56
-
57
- export const adapterRegistry = new AdapterRegistry();
58
- export type { AdapterRegistry };
1
+ // server/modules/orchestration/a2a/adapter-registry.ts
2
+ // In-process registry mapping adapter ids to AbstractA2AAdapter
3
+ // instances. Resolution supports three id forms:
4
+ // - "claude-code" explicit
5
+ // - "skill:<skillId>" first REGISTERED adapter advertising that skill
6
+ // (Map iteration is insertion-ordered per ES spec).
7
+ // - "auto" first registered adapter (deterministic fallback
8
+ // until smarter routing arrives in a later plan)
9
+
10
+ import type { AbstractA2AAdapter } from '@/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js';
11
+ import type { AgentCard } from '@/modules/orchestration/a2a/types.js';
12
+
13
+ interface ResolveAdapterOptions {
14
+ preferredAdapterId?: string;
15
+ preferredProvider?: string;
16
+ preferredSkillId?: string;
17
+ }
18
+
19
+ class AdapterRegistry {
20
+ // Map iteration order is insertion-ordered (ES spec); auto and skill: resolution depend on this.
21
+ private readonly byId = new Map<string, AbstractA2AAdapter>();
22
+
23
+ register(adapter: AbstractA2AAdapter): void {
24
+ if (this.byId.has(adapter.id)) {
25
+ throw new Error(`A2A adapter already registered: ${adapter.id}`);
26
+ }
27
+ this.byId.set(adapter.id, adapter);
28
+ }
29
+
30
+ get(id: string): AbstractA2AAdapter | undefined {
31
+ return this.byId.get(id);
32
+ }
33
+
34
+ resolve(idOrSelector: string, options: ResolveAdapterOptions = {}): AbstractA2AAdapter | undefined {
35
+ const normalizedSelector = idOrSelector.trim();
36
+ if (!normalizedSelector) {
37
+ return undefined;
38
+ }
39
+
40
+ if (normalizedSelector === 'auto') {
41
+ return this.pickPreferred(this.list(), options);
42
+ }
43
+
44
+ if (normalizedSelector.startsWith('skill:')) {
45
+ const skill = normalizedSelector.slice('skill:'.length);
46
+ const matches = this.list().filter((adapter) =>
47
+ adapter.agentCard.skills.some((s) => s.id === skill),
48
+ );
49
+ if (matches.length === 0) {
50
+ return undefined;
51
+ }
52
+ return this.pickPreferred(matches, {
53
+ ...options,
54
+ preferredSkillId: options.preferredSkillId ?? skill,
55
+ });
56
+ }
57
+
58
+ return this.byId.get(normalizedSelector);
59
+ }
60
+
61
+ list(): AbstractA2AAdapter[] {
62
+ return [...this.byId.values()];
63
+ }
64
+
65
+ agentCards(): AgentCard[] {
66
+ return this.list().map((a) => a.agentCard);
67
+ }
68
+
69
+ private pickPreferred(
70
+ adapters: AbstractA2AAdapter[],
71
+ options: ResolveAdapterOptions,
72
+ ): AbstractA2AAdapter | undefined {
73
+ const {
74
+ preferredAdapterId,
75
+ preferredProvider,
76
+ preferredSkillId,
77
+ } = options;
78
+
79
+ if (preferredAdapterId) {
80
+ const byAdapterId = adapters.find((adapter) => adapter.id === preferredAdapterId);
81
+ if (byAdapterId) {
82
+ return byAdapterId;
83
+ }
84
+ }
85
+
86
+ if (preferredProvider) {
87
+ const normalizedProvider = preferredProvider.trim().toLowerCase();
88
+ const byProvider = adapters.find((adapter) => adapter.id === normalizedProvider);
89
+ if (byProvider) {
90
+ return byProvider;
91
+ }
92
+ }
93
+
94
+ if (preferredSkillId) {
95
+ const bySkill = adapters.find((adapter) =>
96
+ adapter.agentCard.skills.some((skill) => skill.id === preferredSkillId),
97
+ );
98
+ if (bySkill) {
99
+ return bySkill;
100
+ }
101
+ }
102
+
103
+ return adapters[0];
104
+ }
105
+ }
106
+
107
+ export const adapterRegistry = new AdapterRegistry();
108
+ export type { AdapterRegistry, ResolveAdapterOptions };
@@ -1,49 +1,55 @@
1
- // server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts
2
- // Base class every CLI adapter extends. Adapters wrap the
3
- // existing per-CLI runtime files (claude-sdk.js, openai-codex.js, ...)
4
- // and translate between A2A messages and the CLI's native I/O.
5
-
6
- import { a2aBus } from '@/modules/orchestration/a2a/bus.js';
7
- import type {
8
- AgentCard,
9
- Artifact,
10
- Message,
11
- Task,
12
- TaskError,
13
- TaskState,
14
- } from '@/modules/orchestration/a2a/types.js';
15
-
16
- export interface AdapterContext {
17
- /** Where the adapter executes — for now this is the project cwd; a future
18
- * plan introduces WorkspaceHandle (worktree / docker). */
19
- cwd: string;
20
- /** pixcode permission mode passed through to the underlying CLI. */
21
- permissionMode?: 'acceptEdits' | 'plan' | 'bypassPermissions' | 'default';
22
- /** Optional parent task id when this adapter is invoked inside a workflow. */
23
- parentTaskId?: string;
24
- }
25
-
26
- export interface TaskHandle {
27
- cancel(): Promise<void>;
28
- finished: Promise<void>;
29
- }
30
-
31
- export abstract class AbstractA2AAdapter {
32
- abstract readonly id: string;
33
- abstract readonly agentCard: AgentCard;
34
-
35
- abstract submitTask(task: Task, ctx: AdapterContext): Promise<TaskHandle>;
36
- abstract cancelTask(taskId: string): Promise<void>;
37
-
38
- protected emitState(taskId: string, state: TaskState, error?: TaskError): void {
39
- a2aBus.publish({ kind: 'task-state', taskId, state, error });
40
- }
41
-
42
- protected emitMessage(taskId: string, message: Message): void {
43
- a2aBus.publish({ kind: 'message', taskId, message });
44
- }
45
-
46
- protected emitArtifact(taskId: string, artifact: Artifact): void {
47
- a2aBus.publish({ kind: 'artifact', taskId, artifact });
48
- }
49
- }
1
+ // server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts
2
+ // Base class every CLI adapter extends. Adapters wrap the
3
+ // existing per-CLI runtime files (claude-sdk.js, openai-codex.js, ...)
4
+ // and translate between A2A messages and the CLI's native I/O.
5
+
6
+ import { a2aBus } from '@/modules/orchestration/a2a/bus.js';
7
+ import type {
8
+ AgentCard,
9
+ Artifact,
10
+ Message,
11
+ Task,
12
+ TaskError,
13
+ TaskState,
14
+ } from '@/modules/orchestration/a2a/types.js';
15
+ import type { WorkspaceHandle } from '@/modules/orchestration/workspace/types.js';
16
+
17
+ export interface AdapterContext {
18
+ /** Isolated execution workspace for the task. */
19
+ workspace: WorkspaceHandle;
20
+ /** Compatibility alias while legacy adapters still accept cwd directly. */
21
+ cwd: string;
22
+ /** pixcode permission mode passed through to the underlying CLI. */
23
+ permissionMode?: string;
24
+ /** Provider model selected by the user in Pixcode. */
25
+ model?: string;
26
+ /** Provider-specific tool / permission settings from Pixcode Settings. */
27
+ toolsSettings?: Record<string, unknown>;
28
+ /** Optional parent task id when this adapter is invoked inside a workflow. */
29
+ parentTaskId?: string;
30
+ }
31
+
32
+ export interface TaskHandle {
33
+ cancel(): Promise<void>;
34
+ finished: Promise<void>;
35
+ }
36
+
37
+ export abstract class AbstractA2AAdapter {
38
+ abstract readonly id: string;
39
+ abstract readonly agentCard: AgentCard;
40
+
41
+ abstract submitTask(task: Task, ctx: AdapterContext): Promise<TaskHandle>;
42
+ abstract cancelTask(taskId: string): Promise<void>;
43
+
44
+ protected emitState(taskId: string, state: TaskState, error?: TaskError): void {
45
+ a2aBus.publish({ kind: 'task-state', taskId, state, error });
46
+ }
47
+
48
+ protected emitMessage(taskId: string, message: Message): void {
49
+ a2aBus.publish({ kind: 'message', taskId, message });
50
+ }
51
+
52
+ protected emitArtifact(taskId: string, artifact: Artifact): void {
53
+ a2aBus.publish({ kind: 'artifact', taskId, artifact });
54
+ }
55
+ }