create-walle 0.9.11 → 0.9.13

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 (167) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/template/bin/dev.sh +7 -1
  4. package/template/bin/setup.js +53 -9
  5. package/template/bin/sync-images.js +53 -0
  6. package/template/builder-journal.md +17 -0
  7. package/template/claude-task-manager/api-prompts.js +98 -13
  8. package/template/claude-task-manager/api-reviews.js +82 -5
  9. package/template/claude-task-manager/db.js +32 -5
  10. package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
  11. package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
  12. package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
  13. package/template/claude-task-manager/lib/session-capture.js +421 -0
  14. package/template/claude-task-manager/lib/session-history.js +135 -15
  15. package/template/claude-task-manager/lib/session-jobs.js +10 -5
  16. package/template/claude-task-manager/lib/session-stream.js +87 -19
  17. package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
  18. package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
  19. package/template/claude-task-manager/lib/walle-session-context.js +61 -0
  20. package/template/claude-task-manager/lib/walle-transcript.js +176 -0
  21. package/template/claude-task-manager/public/css/setup.css +35 -8
  22. package/template/claude-task-manager/public/css/walle-session.css +56 -0
  23. package/template/claude-task-manager/public/css/walle.css +120 -0
  24. package/template/claude-task-manager/public/index.html +814 -181
  25. package/template/claude-task-manager/public/js/message-renderer.js +148 -19
  26. package/template/claude-task-manager/public/js/reviews.js +120 -62
  27. package/template/claude-task-manager/public/js/setup.js +75 -31
  28. package/template/claude-task-manager/public/js/stream-view.js +115 -55
  29. package/template/claude-task-manager/public/js/walle-session.js +84 -2
  30. package/template/claude-task-manager/public/js/walle.js +308 -54
  31. package/template/claude-task-manager/server.js +1092 -146
  32. package/template/claude-task-manager/session-integrity.js +181 -54
  33. package/template/claude-task-manager/session-utils.js +123 -41
  34. package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
  35. package/template/package.json +1 -1
  36. package/template/wall-e/adapters/ctm.js +39 -18
  37. package/template/wall-e/agent-runners/contract.js +17 -0
  38. package/template/wall-e/agent-runners/index.js +22 -0
  39. package/template/wall-e/agent-runtime/harness.js +212 -0
  40. package/template/wall-e/agent-runtime/index.js +8 -0
  41. package/template/wall-e/agent-runtime/registry.js +67 -0
  42. package/template/wall-e/agent-runtime/session-store.js +179 -0
  43. package/template/wall-e/agent-runtime/spawn.js +208 -0
  44. package/template/wall-e/api-walle.js +174 -7
  45. package/template/wall-e/brain.js +266 -28
  46. package/template/wall-e/channels/policy.js +88 -0
  47. package/template/wall-e/channels/registry.js +15 -1
  48. package/template/wall-e/channels/reply-dispatcher.js +70 -0
  49. package/template/wall-e/channels/session-bindings.js +51 -0
  50. package/template/wall-e/chat/code-review-context.js +29 -0
  51. package/template/wall-e/chat.js +188 -42
  52. package/template/wall-e/coding/acp-adapter.js +188 -0
  53. package/template/wall-e/coding/agent-catalog.js +129 -0
  54. package/template/wall-e/coding/compaction-service.js +247 -0
  55. package/template/wall-e/coding/execution-trace.js +3 -0
  56. package/template/wall-e/coding/instruction-service.js +224 -0
  57. package/template/wall-e/coding/model-message.js +67 -0
  58. package/template/wall-e/coding/permission-rules-store.js +111 -0
  59. package/template/wall-e/coding/permission-service.js +266 -0
  60. package/template/wall-e/coding/prompt-bundle.js +67 -0
  61. package/template/wall-e/coding/prompt-runtime.js +243 -0
  62. package/template/wall-e/coding/provider-transform.js +188 -0
  63. package/template/wall-e/coding/runtime-mode.js +132 -0
  64. package/template/wall-e/coding/snapshot-service.js +155 -0
  65. package/template/wall-e/coding/stream-processor.js +268 -0
  66. package/template/wall-e/coding/task-tool.js +255 -0
  67. package/template/wall-e/coding/tool-registry.js +361 -0
  68. package/template/wall-e/coding/transcript-writer.js +143 -0
  69. package/template/wall-e/coding/workspace-replay.js +324 -0
  70. package/template/wall-e/coding-context.js +4 -22
  71. package/template/wall-e/coding-orchestrator.js +307 -18
  72. package/template/wall-e/coding-prompts.js +44 -3
  73. package/template/wall-e/context/context-builder.js +43 -1
  74. package/template/wall-e/context/topic-matcher.js +1 -1
  75. package/template/wall-e/eval/agent-runner.js +59 -13
  76. package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
  77. package/template/wall-e/eval/benchmarks.js +100 -16
  78. package/template/wall-e/eval/eval-orchestrator.js +218 -8
  79. package/template/wall-e/eval/harvester.js +62 -5
  80. package/template/wall-e/eval/head-to-head.js +23 -2
  81. package/template/wall-e/eval/humaneval-adapter.js +30 -5
  82. package/template/wall-e/eval/livecodebench-adapter.js +29 -5
  83. package/template/wall-e/eval/manifest.js +186 -0
  84. package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
  85. package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
  86. package/template/wall-e/eval/session-transcripts.js +57 -4
  87. package/template/wall-e/eval/swebench-adapter.js +109 -3
  88. package/template/wall-e/evaluation/agent-router.js +53 -1
  89. package/template/wall-e/evaluation/coding-quorum.js +48 -1
  90. package/template/wall-e/evaluation/router.js +4 -2
  91. package/template/wall-e/evaluation/tier-selector.js +11 -1
  92. package/template/wall-e/extraction/contradiction.js +2 -2
  93. package/template/wall-e/extraction/indexer.js +2 -1
  94. package/template/wall-e/extraction/knowledge-extractor.js +2 -2
  95. package/template/wall-e/hooks/cli.js +92 -0
  96. package/template/wall-e/hooks/discovery.js +119 -0
  97. package/template/wall-e/hooks/index.js +7 -0
  98. package/template/wall-e/hooks/manifest.js +55 -0
  99. package/template/wall-e/hooks/runtime.js +84 -0
  100. package/template/wall-e/hooks/session-memory.js +225 -0
  101. package/template/wall-e/http/auth.js +6 -2
  102. package/template/wall-e/http/chat-api.js +54 -8
  103. package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
  104. package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
  105. package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
  106. package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
  107. package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
  108. package/template/wall-e/listening/calendar.js +3 -1
  109. package/template/wall-e/llm/client.js +64 -10
  110. package/template/wall-e/llm/google.js +39 -5
  111. package/template/wall-e/llm/ollama.js +1 -1
  112. package/template/wall-e/llm/ollama.plugin.json +1 -1
  113. package/template/wall-e/llm/provider-availability.js +10 -0
  114. package/template/wall-e/llm/provider-error.js +269 -0
  115. package/template/wall-e/llm/tool-adapter.js +48 -12
  116. package/template/wall-e/loops/boot.js +2 -1
  117. package/template/wall-e/loops/initiative.js +2 -2
  118. package/template/wall-e/loops/tasks.js +8 -47
  119. package/template/wall-e/loops/workspace-prompts.js +20 -0
  120. package/template/wall-e/mcp-server.js +442 -1
  121. package/template/wall-e/memory/session-ingest-service.js +159 -0
  122. package/template/wall-e/memory/source-indexer.js +289 -0
  123. package/template/wall-e/plugins/discovery.js +83 -0
  124. package/template/wall-e/plugins/manifest-loader.js +50 -10
  125. package/template/wall-e/plugins/manifest-schema.js +69 -0
  126. package/template/wall-e/plugins/model-catalog.js +55 -0
  127. package/template/wall-e/prompts/coding/base.txt +2 -0
  128. package/template/wall-e/prompts/coding/deepseek.txt +1 -0
  129. package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
  130. package/template/wall-e/prompts/coding/plan.txt +1 -0
  131. package/template/wall-e/runtime/execution-trace.js +220 -0
  132. package/template/wall-e/security/audit.js +266 -0
  133. package/template/wall-e/security/ssrf.js +236 -0
  134. package/template/wall-e/session-files.js +303 -0
  135. package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
  136. package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
  137. package/template/wall-e/skills/internal-skill-registry.js +2 -2
  138. package/template/wall-e/skills/script-skill-runner.js +143 -0
  139. package/template/wall-e/skills/skill-executor.js +5 -6
  140. package/template/wall-e/skills/skill-fallback.js +3 -1
  141. package/template/wall-e/skills/skill-harness-registry.js +7 -8
  142. package/template/wall-e/skills/skill-planner.js +52 -4
  143. package/template/wall-e/skills/slack-ingest.js +11 -3
  144. package/template/wall-e/sources/base.js +90 -0
  145. package/template/wall-e/sources/builtin.js +33 -0
  146. package/template/wall-e/sources/claude-code-jsonl.js +78 -0
  147. package/template/wall-e/sources/codex-jsonl.js +125 -0
  148. package/template/wall-e/sources/coding-session-utils.js +117 -0
  149. package/template/wall-e/sources/contract-suite.js +59 -0
  150. package/template/wall-e/sources/gemini-jsonl.js +85 -0
  151. package/template/wall-e/sources/index.js +9 -0
  152. package/template/wall-e/sources/jsonl-utils.js +181 -0
  153. package/template/wall-e/sources/record-types.js +252 -0
  154. package/template/wall-e/sources/registry.js +92 -0
  155. package/template/wall-e/sources/transforms.js +100 -0
  156. package/template/wall-e/sources/walle-jsonl.js +108 -0
  157. package/template/wall-e/tools/coding-middleware.js +31 -1
  158. package/template/wall-e/tools/file-tracker.js +25 -1
  159. package/template/wall-e/tools/local-tools.js +75 -47
  160. package/template/wall-e/tools/session-sharing.js +68 -1
  161. package/template/wall-e/tools/shell-analyzer.js +1 -1
  162. package/template/wall-e/tools/shell-policy.js +47 -0
  163. package/template/wall-e/tools/snapshot.js +42 -0
  164. package/template/wall-e/training/harvester.js +62 -5
  165. package/template/wall-e/utils/repair.js +253 -1
  166. package/template/website/index.html +3 -3
  167. package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
@@ -1,13 +1,19 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const AdapterBase = require('./adapter-base');
4
+ const claudeDesktopSessions = require('../../claude-task-manager/lib/claude-desktop-sessions');
4
5
 
5
6
  const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB — skip files larger than this
6
7
 
7
8
  class CtmAdapter extends AdapterBase {
8
- constructor({ projectsDir } = {}) {
9
+ constructor({ projectsDir, desktopAppSupportDirs, includeClaudeDesktop } = {}) {
9
10
  super('ctm');
11
+ const hasCustomProjectsDir = !!projectsDir;
10
12
  this.projectsDir = projectsDir || path.join(process.env.HOME, '.claude', 'projects');
13
+ this.desktopAppSupportDirs = desktopAppSupportDirs || null;
14
+ this.includeClaudeDesktop = includeClaudeDesktop !== undefined
15
+ ? includeClaudeDesktop
16
+ : (!hasCustomProjectsDir || !!desktopAppSupportDirs);
11
17
  this._processedFiles = new Map(); // filePath -> { size, mtime }
12
18
  }
13
19
 
@@ -17,7 +23,7 @@ class CtmAdapter extends AdapterBase {
17
23
 
18
24
  for (const { filePath, projectPath, projectEntry } of sessionFiles) {
19
25
  let stat;
20
- try { stat = fs.statSync(filePath); } catch { continue; }
26
+ try { stat = fs.statSync(claudeDesktopSessions.sourcePathForStat(filePath)); } catch { continue; }
21
27
 
22
28
  // Skip files that are too large to avoid OOM
23
29
  if (stat.size > MAX_FILE_SIZE) continue;
@@ -81,8 +87,9 @@ class CtmAdapter extends AdapterBase {
81
87
  const sourceId = rawData.uuid || rawData._filePath || `ctm-${Date.now()}`;
82
88
  const timestamp = rawData.timestamp || new Date().toISOString();
83
89
 
90
+ const source = rawData._source === claudeDesktopSessions.DESKTOP_AGENT ? claudeDesktopSessions.DESKTOP_AGENT : 'ctm';
84
91
  const result = {
85
- source: 'ctm',
92
+ source,
86
93
  source_id: sourceId,
87
94
  memory_type: memoryType,
88
95
  direction,
@@ -110,31 +117,45 @@ class CtmAdapter extends AdapterBase {
110
117
 
111
118
  _getAllSessionFiles() {
112
119
  const results = [];
113
- if (!fs.existsSync(this.projectsDir)) return results;
114
120
 
115
- let entries;
116
- try { entries = fs.readdirSync(this.projectsDir); } catch { return results; }
121
+ if (fs.existsSync(this.projectsDir)) {
122
+ let entries;
123
+ try { entries = fs.readdirSync(this.projectsDir); } catch { entries = []; }
117
124
 
118
- for (const projectEntry of entries) {
119
- const projectDir = path.join(this.projectsDir, projectEntry);
120
- let stat;
121
- try { stat = fs.statSync(projectDir); } catch { continue; }
122
- if (!stat.isDirectory()) continue;
125
+ for (const projectEntry of entries) {
126
+ const projectDir = path.join(this.projectsDir, projectEntry);
127
+ let stat;
128
+ try { stat = fs.statSync(projectDir); } catch { continue; }
129
+ if (!stat.isDirectory()) continue;
130
+
131
+ const projectPath = this._decodeProjectEntry(projectEntry);
132
+ let files;
133
+ try { files = fs.readdirSync(projectDir); } catch { continue; }
123
134
 
124
- const projectPath = this._decodeProjectEntry(projectEntry);
125
- let files;
126
- try { files = fs.readdirSync(projectDir); } catch { continue; }
135
+ for (const file of files) {
136
+ if (!file.endsWith('.jsonl') && !file.endsWith('.jsonl.bak')) continue;
137
+ const filePath = path.join(projectDir, file);
138
+ results.push({ filePath, projectPath, projectEntry });
139
+ }
140
+ }
141
+ }
127
142
 
128
- for (const file of files) {
129
- if (!file.endsWith('.jsonl') && !file.endsWith('.jsonl.bak')) continue;
130
- const filePath = path.join(projectDir, file);
131
- results.push({ filePath, projectPath, projectEntry });
143
+ if (this.includeClaudeDesktop) {
144
+ for (const entry of claudeDesktopSessions.listSessionFileEntries({
145
+ appSupportDirs: this.desktopAppSupportDirs || undefined,
146
+ })) {
147
+ results.push(entry);
132
148
  }
133
149
  }
134
150
  return results;
135
151
  }
136
152
 
137
153
  _parseSessionFile(filePath) {
154
+ const desktopSession = claudeDesktopSessions.getSessionFromVirtualPath(filePath, {
155
+ appSupportDirs: this.desktopAppSupportDirs || undefined,
156
+ });
157
+ if (desktopSession) return claudeDesktopSessions.toClaudeCodeEntries(desktopSession);
158
+
138
159
  let data;
139
160
  try { data = fs.readFileSync(filePath, 'utf8'); } catch { return []; }
140
161
 
@@ -14,8 +14,15 @@ function normalizeAgentMode(mode) {
14
14
 
15
15
  function normalizeAgentRunResult(result = {}, fallback = {}) {
16
16
  const sessionId = result.sessionId || fallback.sessionId || crypto.randomUUID();
17
+ const runId = result.runId || fallback.runId || createAgentRunId();
17
18
  const exitCode = Number.isInteger(result.exitCode) ? result.exitCode : (result.success ? 0 : -1);
19
+ const errors = Array.isArray(result.errors)
20
+ ? result.errors
21
+ : (result.error ? [{ message: String(result.error) }] : []);
18
22
  return {
23
+ id: runId,
24
+ runId,
25
+ parentRunId: result.parentRunId || fallback.parentRunId || null,
19
26
  runnerId: result.runnerId || fallback.runnerId || 'unknown',
20
27
  providerType: result.providerType || fallback.providerType || 'unknown',
21
28
  kind: 'cli-agent',
@@ -25,6 +32,14 @@ function normalizeAgentRunResult(result = {}, fallback = {}) {
25
32
  stderr: result.stderr || '',
26
33
  sessionId,
27
34
  resumeHandle: result.resumeHandle || sessionId,
35
+ session: {
36
+ id: sessionId,
37
+ resumeToken: result.resumeHandle || sessionId,
38
+ persistent: Boolean(result.persistent ?? fallback.persistent),
39
+ cleanupPolicy: result.cleanupPolicy || fallback.cleanupPolicy || 'delete-on-complete',
40
+ threadId: result.threadId || fallback.threadId || null,
41
+ status: result.sessionStatus || fallback.sessionStatus || null,
42
+ },
28
43
  exitCode,
29
44
  timedOut: Boolean(result.timedOut),
30
45
  signal: result.signal || null,
@@ -34,7 +49,9 @@ function normalizeAgentRunResult(result = {}, fallback = {}) {
34
49
  diff: result.diff || '',
35
50
  usage: result.usage || null,
36
51
  costEstimate: result.costEstimate || null,
52
+ trace: result.trace || fallback.trace || null,
37
53
  artifacts: result.artifacts || {},
54
+ errors,
38
55
  };
39
56
  }
40
57
 
@@ -26,6 +26,27 @@ function listAgentRunners() {
26
26
  }));
27
27
  }
28
28
 
29
+ function listAgentHarnesses() {
30
+ return [...RUNNERS.values()].map(runner => ({
31
+ id: runner.id,
32
+ runnerId: runner.id,
33
+ providerType: runner.providerType,
34
+ kind: runner.kind || 'cli-agent',
35
+ priority: Number.isFinite(Number(runner.priority)) ? Number(runner.priority) : 0,
36
+ capabilities: {
37
+ modes: runner.capabilities?.modes || ['plan', 'build', 'review', 'analysis'],
38
+ persistentSessions: Boolean(runner.capabilities?.resume),
39
+ streaming: Boolean(runner.capabilities?.streamJson),
40
+ codeEditing: true,
41
+ reviewOnly: false,
42
+ sandboxModes: runner.capabilities?.sandboxModes || [],
43
+ supportsImages: Boolean(runner.capabilities?.images),
44
+ supportsPatch: runner.capabilities?.patch !== false,
45
+ ...(runner.capabilities || {}),
46
+ },
47
+ }));
48
+ }
49
+
29
50
  async function runAgentTask(runnerId, options = {}) {
30
51
  const id = runnerId || 'claude-code';
31
52
  const runner = getAgentRunner(id);
@@ -57,6 +78,7 @@ module.exports = {
57
78
  registerAgentRunner,
58
79
  getAgentRunner,
59
80
  listAgentRunners,
81
+ listAgentHarnesses,
60
82
  runAgentTask,
61
83
  runControlledMultiAgent,
62
84
  };
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ const { normalizeAgentMode } = require('../agent-runners/contract');
4
+
5
+ function normalizeHarness(harness = {}) {
6
+ const id = harness.id || harness.runnerId;
7
+ if (!id) throw new Error('Harness id is required');
8
+ const providerType = harness.providerType || harness.provider || 'unknown';
9
+ const capabilities = {
10
+ modes: ['build'],
11
+ persistentSessions: false,
12
+ streaming: false,
13
+ codeEditing: true,
14
+ reviewOnly: false,
15
+ sandboxModes: [],
16
+ supportsImages: false,
17
+ supportsPatch: true,
18
+ ...(harness.capabilities || {}),
19
+ };
20
+ return {
21
+ ...harness,
22
+ id,
23
+ runnerId: harness.runnerId || id,
24
+ providerType,
25
+ kind: harness.kind || 'cli-agent',
26
+ priority: Number.isFinite(Number(harness.priority)) ? Number(harness.priority) : 0,
27
+ capabilities,
28
+ };
29
+ }
30
+
31
+ function createHarnessFromRunner(runner = {}, extra = {}) {
32
+ const normalized = normalizeHarness({
33
+ id: runner.id,
34
+ runnerId: runner.id,
35
+ providerType: runner.providerType,
36
+ kind: runner.kind || 'cli-agent',
37
+ capabilities: {
38
+ modes: runner.capabilities?.modes || ['plan', 'build', 'review', 'analysis'],
39
+ persistentSessions: Boolean(runner.capabilities?.resume),
40
+ streaming: Boolean(runner.capabilities?.streamJson),
41
+ codeEditing: true,
42
+ reviewOnly: false,
43
+ sandboxModes: runner.capabilities?.sandboxModes || [],
44
+ supportsImages: Boolean(runner.capabilities?.images),
45
+ supportsPatch: runner.capabilities?.patch !== false,
46
+ ...(runner.capabilities || {}),
47
+ },
48
+ run: async (request) => runner.runTask(request),
49
+ ...extra,
50
+ });
51
+ return normalized;
52
+ }
53
+
54
+ class AgentHarnessRegistry {
55
+ constructor() {
56
+ this.harnesses = new Map();
57
+ }
58
+
59
+ registerHarness(harness) {
60
+ const normalized = normalizeHarness(harness);
61
+ if (typeof normalized.run !== 'function' && !normalized.runnerId) {
62
+ throw new Error(`Harness ${normalized.id} needs a run function or runnerId`);
63
+ }
64
+ this.harnesses.set(normalized.id, normalized);
65
+ return normalized;
66
+ }
67
+
68
+ getHarness(id) {
69
+ return this.harnesses.get(id) || null;
70
+ }
71
+
72
+ listHarnesses() {
73
+ return [...this.harnesses.values()].map((harness) => ({
74
+ id: harness.id,
75
+ runnerId: harness.runnerId,
76
+ providerType: harness.providerType,
77
+ kind: harness.kind,
78
+ priority: harness.priority,
79
+ capabilities: harness.capabilities,
80
+ }));
81
+ }
82
+
83
+ candidates() {
84
+ return [...this.harnesses.values()];
85
+ }
86
+ }
87
+
88
+ function createDefaultHarnessRegistry({ runners = null } = {}) {
89
+ const registry = new AgentHarnessRegistry();
90
+ const source = runners || lazyListRunners();
91
+ for (const runner of source) {
92
+ registry.registerHarness(createHarnessFromRunner(runner));
93
+ }
94
+ return registry;
95
+ }
96
+
97
+ function harnessSupportsRequest(harness, request = {}) {
98
+ const normalized = normalizeHarness(harness);
99
+ const mode = normalizeAgentMode(request.mode || 'build');
100
+ const modes = normalized.capabilities.modes || [];
101
+ if (modes.length > 0 && !modes.includes(mode)) return false;
102
+ if (normalized.capabilities.reviewOnly && mode !== 'review') return false;
103
+ if (request.providerType && normalized.providerType !== request.providerType) return false;
104
+ if (request.provider && normalized.providerType !== request.provider) return false;
105
+ if (request.runnerId && normalized.runnerId !== request.runnerId && normalized.id !== request.runnerId) return false;
106
+ if (request.harnessId && normalized.id !== request.harnessId) return false;
107
+ if (request.sandbox && Array.isArray(normalized.capabilities.sandboxModes) && normalized.capabilities.sandboxModes.length > 0) {
108
+ if (!normalized.capabilities.sandboxModes.includes(request.sandbox)) return false;
109
+ }
110
+ return true;
111
+ }
112
+
113
+ function scoreHarness(harness, request = {}, opts = {}) {
114
+ const normalized = normalizeHarness(harness);
115
+ let score = 0.5 + normalized.priority / 100;
116
+ const mode = normalizeAgentMode(request.mode || 'build');
117
+ if (request.harnessId === normalized.id) score += 0.35;
118
+ if (request.runnerId && (request.runnerId === normalized.runnerId || request.runnerId === normalized.id)) score += 0.20;
119
+ if (request.providerType === normalized.providerType || request.provider === normalized.providerType) score += 0.15;
120
+ if (request.model && normalized.model === request.model) score += 0.05;
121
+ if (mode === 'review' && normalized.providerType && request.builderProviderType && normalized.providerType !== request.builderProviderType) score += 0.08;
122
+ if (normalized.capabilities.persistentSessions) score += request.persistent ? 0.08 : 0.02;
123
+ if (normalized.capabilities.streaming) score += 0.02;
124
+
125
+ const availability = opts.providerAvailability;
126
+ if (availability?.isProviderUsable) {
127
+ if (!availability.isProviderUsable(normalized.providerType)) score -= 0.40;
128
+ } else if (availability?.getProviderState) {
129
+ const state = availability.getProviderState(normalized.providerType);
130
+ if (state?.status === 'unhealthy') score -= 0.40;
131
+ }
132
+
133
+ const quality = Number(opts.qualityScore ?? normalized.qualityScore);
134
+ if (Number.isFinite(quality)) score += (quality - 0.5) * 0.25;
135
+
136
+ return Math.max(0, Math.min(1, score));
137
+ }
138
+
139
+ function selectAgentHarness(request = {}, candidates = [], opts = {}) {
140
+ const list = candidates.length > 0
141
+ ? candidates.map(normalizeHarness)
142
+ : (opts.registry ? opts.registry.candidates() : []);
143
+ const supported = list.filter(harness => harnessSupportsRequest(harness, request));
144
+ const scored = supported.map((harness, index) => ({
145
+ harness,
146
+ score: scoreHarness(harness, request, opts),
147
+ index,
148
+ }));
149
+ scored.sort((a, b) => {
150
+ if (b.score !== a.score) return b.score - a.score;
151
+ if (b.harness.priority !== a.harness.priority) return b.harness.priority - a.harness.priority;
152
+ return a.index - b.index;
153
+ });
154
+ const winner = scored[0] || null;
155
+ return {
156
+ selected: winner?.harness || null,
157
+ candidates: scored.map(entry => ({
158
+ ...entry.harness,
159
+ score: entry.score,
160
+ })),
161
+ reason: winner ? 'selected-highest-scoring-supported-harness' : 'no-supported-harness',
162
+ };
163
+ }
164
+
165
+ async function runSelectedHarness(request = {}, opts = {}) {
166
+ const registry = opts.registry || createDefaultHarnessRegistry(opts);
167
+ const selection = selectAgentHarness(request, [], { ...opts, registry });
168
+ if (!selection.selected) {
169
+ return {
170
+ success: false,
171
+ error: selection.reason,
172
+ selection,
173
+ };
174
+ }
175
+ const harness = selection.selected;
176
+ if (typeof harness.run === 'function') {
177
+ const result = await harness.run({
178
+ ...request,
179
+ runnerId: harness.runnerId,
180
+ providerType: harness.providerType,
181
+ });
182
+ return { ...result, harnessId: harness.id, selection };
183
+ }
184
+ const runner = opts.runTask || lazyRunAgentTask();
185
+ const result = await runner(harness.runnerId, {
186
+ ...request,
187
+ providerType: harness.providerType,
188
+ });
189
+ return { ...result, harnessId: harness.id, selection };
190
+ }
191
+
192
+ function lazyListRunners() {
193
+ const runners = require('../agent-runners');
194
+ return ['claude-code', 'codex', 'gemini']
195
+ .map(id => runners.getAgentRunner(id))
196
+ .filter(Boolean);
197
+ }
198
+
199
+ function lazyRunAgentTask() {
200
+ return require('../agent-runners').runAgentTask;
201
+ }
202
+
203
+ module.exports = {
204
+ AgentHarnessRegistry,
205
+ createHarnessFromRunner,
206
+ createDefaultHarnessRegistry,
207
+ harnessSupportsRequest,
208
+ normalizeHarness,
209
+ runSelectedHarness,
210
+ scoreHarness,
211
+ selectAgentHarness,
212
+ };
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ ...require('./harness'),
5
+ ...require('./registry'),
6
+ ...require('./session-store'),
7
+ ...require('./spawn'),
8
+ };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ class AgentRuntimeRegistry {
4
+ constructor() {
5
+ this.runtimes = new Map();
6
+ }
7
+
8
+ registerRuntime(runtime) {
9
+ if (!runtime || !runtime.id || typeof runtime.run !== 'function') {
10
+ throw new Error('Invalid agent runtime');
11
+ }
12
+ this.runtimes.set(runtime.id, runtime);
13
+ return runtime;
14
+ }
15
+
16
+ getRuntime(id = 'cli-agent') {
17
+ return this.runtimes.get(id) || null;
18
+ }
19
+
20
+ listRuntimes() {
21
+ return [...this.runtimes.values()].map((runtime) => ({
22
+ id: runtime.id,
23
+ kind: runtime.kind || runtime.id,
24
+ capabilities: runtime.capabilities || {},
25
+ }));
26
+ }
27
+
28
+ clear() {
29
+ this.runtimes.clear();
30
+ }
31
+ }
32
+
33
+ function createDefaultRuntimeRegistry({ runTask } = {}) {
34
+ const registry = new AgentRuntimeRegistry();
35
+ registry.registerRuntime({
36
+ id: 'cli-agent',
37
+ kind: 'cli-agent',
38
+ capabilities: {
39
+ oneshot: true,
40
+ persistentSessions: false,
41
+ streaming: false,
42
+ },
43
+ run: async (request) => {
44
+ const runner = runTask || lazyRunAgentTask();
45
+ return runner(request.runnerId, {
46
+ prompt: request.task || request.prompt || '',
47
+ cwd: request.cwd,
48
+ timeoutMs: request.timeoutMs,
49
+ model: request.model,
50
+ mode: request.mode,
51
+ sessionId: request.sessionId,
52
+ writeScope: request.writeScope,
53
+ sandbox: request.sandbox,
54
+ });
55
+ },
56
+ });
57
+ return registry;
58
+ }
59
+
60
+ function lazyRunAgentTask() {
61
+ return require('../agent-runners').runAgentTask;
62
+ }
63
+
64
+ module.exports = {
65
+ AgentRuntimeRegistry,
66
+ createDefaultRuntimeRegistry,
67
+ };
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ function createSessionId(prefix = 'agent-session') {
8
+ return `${prefix}-${crypto.randomUUID()}`;
9
+ }
10
+
11
+ function nowIso() {
12
+ return new Date().toISOString();
13
+ }
14
+
15
+ function clone(value) {
16
+ return JSON.parse(JSON.stringify(value));
17
+ }
18
+
19
+ class AgentSessionStore {
20
+ constructor({ persistencePath = null, clock = nowIso } = {}) {
21
+ this.persistencePath = persistencePath;
22
+ this.clock = clock;
23
+ this.sessions = new Map();
24
+ this._load();
25
+ }
26
+
27
+ createSession(data = {}) {
28
+ const timestamp = this.clock();
29
+ const id = data.id || data.sessionId || createSessionId();
30
+ const session = {
31
+ id,
32
+ parentRunId: data.parentRunId || null,
33
+ runtime: data.runtime || data.kind || 'cli-agent',
34
+ runnerId: data.runnerId || data.runner || 'unknown',
35
+ providerType: data.providerType || data.provider || 'unknown',
36
+ model: data.model || null,
37
+ mode: data.mode || 'build',
38
+ cwd: data.cwd || process.cwd(),
39
+ sandbox: data.sandbox || null,
40
+ task: data.task || data.prompt || '',
41
+ status: data.status || 'created',
42
+ persistent: Boolean(data.persistent),
43
+ cleanupPolicy: data.cleanupPolicy || (data.persistent ? 'manual' : 'delete-on-complete'),
44
+ threadId: data.threadId || null,
45
+ resumeHandle: data.resumeHandle || data.resumeToken || id,
46
+ createdAt: data.createdAt || timestamp,
47
+ updatedAt: data.updatedAt || timestamp,
48
+ startedAt: data.startedAt || null,
49
+ completedAt: data.completedAt || null,
50
+ lastError: data.lastError || null,
51
+ metadata: { ...(data.metadata || {}) },
52
+ };
53
+ this.sessions.set(id, session);
54
+ this._persist();
55
+ return clone(session);
56
+ }
57
+
58
+ getSession(id) {
59
+ if (!id) return null;
60
+ const session = this.sessions.get(id);
61
+ return session ? clone(session) : null;
62
+ }
63
+
64
+ listSessions(filter = {}) {
65
+ return [...this.sessions.values()]
66
+ .filter((session) => {
67
+ if (filter.parentRunId && session.parentRunId !== filter.parentRunId) return false;
68
+ if (filter.runnerId && session.runnerId !== filter.runnerId) return false;
69
+ if (filter.status && session.status !== filter.status) return false;
70
+ return true;
71
+ })
72
+ .map(clone);
73
+ }
74
+
75
+ updateSession(id, patch = {}) {
76
+ const session = this.sessions.get(id);
77
+ if (!session) return null;
78
+ const next = {
79
+ ...session,
80
+ ...patch,
81
+ metadata: {
82
+ ...(session.metadata || {}),
83
+ ...(patch.metadata || {}),
84
+ },
85
+ updatedAt: this.clock(),
86
+ };
87
+ this.sessions.set(id, next);
88
+ this._persist();
89
+ return clone(next);
90
+ }
91
+
92
+ markRunning(id, patch = {}) {
93
+ return this.updateSession(id, {
94
+ ...patch,
95
+ status: 'running',
96
+ startedAt: patch.startedAt || this.clock(),
97
+ lastError: null,
98
+ });
99
+ }
100
+
101
+ markCompleted(id, result = {}) {
102
+ return this.updateSession(id, {
103
+ status: result.success === false ? 'failed' : 'completed',
104
+ completedAt: this.clock(),
105
+ resumeHandle: result.resumeHandle || result.sessionId || id,
106
+ lastError: result.success === false
107
+ ? String(result.error || result.stderr || 'Agent run failed')
108
+ : null,
109
+ metadata: {
110
+ exitCode: result.exitCode,
111
+ changedFiles: Array.isArray(result.changedFiles) ? result.changedFiles : [],
112
+ timedOut: Boolean(result.timedOut),
113
+ },
114
+ });
115
+ }
116
+
117
+ markFailed(id, error) {
118
+ return this.updateSession(id, {
119
+ status: 'failed',
120
+ completedAt: this.clock(),
121
+ lastError: error?.message || String(error || 'Agent run failed'),
122
+ });
123
+ }
124
+
125
+ deleteSession(id) {
126
+ const existed = this.sessions.delete(id);
127
+ if (existed) this._persist();
128
+ return existed;
129
+ }
130
+
131
+ cleanupCompleted({ includePersistent = false } = {}) {
132
+ let count = 0;
133
+ for (const session of this.sessions.values()) {
134
+ const terminal = session.status === 'completed' || session.status === 'failed' || session.status === 'canceled';
135
+ const cleanup = session.cleanupPolicy === 'delete-on-complete' || (includePersistent && session.persistent);
136
+ if (terminal && cleanup) {
137
+ this.sessions.delete(session.id);
138
+ count += 1;
139
+ }
140
+ }
141
+ if (count > 0) this._persist();
142
+ return count;
143
+ }
144
+
145
+ clear() {
146
+ this.sessions.clear();
147
+ this._persist();
148
+ }
149
+
150
+ _load() {
151
+ if (!this.persistencePath) return;
152
+ try {
153
+ const raw = fs.readFileSync(this.persistencePath, 'utf8');
154
+ const parsed = JSON.parse(raw);
155
+ for (const session of parsed.sessions || []) {
156
+ if (session?.id) this.sessions.set(session.id, session);
157
+ }
158
+ } catch (err) {
159
+ if (err.code !== 'ENOENT') throw err;
160
+ }
161
+ }
162
+
163
+ _persist() {
164
+ if (!this.persistencePath) return;
165
+ const dir = path.dirname(this.persistencePath);
166
+ fs.mkdirSync(dir, { recursive: true });
167
+ const tmp = `${this.persistencePath}.tmp`;
168
+ fs.writeFileSync(tmp, JSON.stringify({ sessions: [...this.sessions.values()] }, null, 2));
169
+ fs.renameSync(tmp, this.persistencePath);
170
+ }
171
+ }
172
+
173
+ const defaultSessionStore = new AgentSessionStore();
174
+
175
+ module.exports = {
176
+ AgentSessionStore,
177
+ createSessionId,
178
+ defaultSessionStore,
179
+ };