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.
- package/README.md +3 -3
- package/package.json +2 -2
- package/template/bin/dev.sh +7 -1
- package/template/bin/setup.js +53 -9
- package/template/bin/sync-images.js +53 -0
- package/template/builder-journal.md +17 -0
- package/template/claude-task-manager/api-prompts.js +98 -13
- package/template/claude-task-manager/api-reviews.js +82 -5
- package/template/claude-task-manager/db.js +32 -5
- package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
- package/template/claude-task-manager/lib/session-capture.js +421 -0
- package/template/claude-task-manager/lib/session-history.js +135 -15
- package/template/claude-task-manager/lib/session-jobs.js +10 -5
- package/template/claude-task-manager/lib/session-stream.js +87 -19
- package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
- package/template/claude-task-manager/lib/walle-session-context.js +61 -0
- package/template/claude-task-manager/lib/walle-transcript.js +176 -0
- package/template/claude-task-manager/public/css/setup.css +35 -8
- package/template/claude-task-manager/public/css/walle-session.css +56 -0
- package/template/claude-task-manager/public/css/walle.css +120 -0
- package/template/claude-task-manager/public/index.html +814 -181
- package/template/claude-task-manager/public/js/message-renderer.js +148 -19
- package/template/claude-task-manager/public/js/reviews.js +120 -62
- package/template/claude-task-manager/public/js/setup.js +75 -31
- package/template/claude-task-manager/public/js/stream-view.js +115 -55
- package/template/claude-task-manager/public/js/walle-session.js +84 -2
- package/template/claude-task-manager/public/js/walle.js +308 -54
- package/template/claude-task-manager/server.js +1092 -146
- package/template/claude-task-manager/session-integrity.js +181 -54
- package/template/claude-task-manager/session-utils.js +123 -41
- package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
- package/template/package.json +1 -1
- package/template/wall-e/adapters/ctm.js +39 -18
- package/template/wall-e/agent-runners/contract.js +17 -0
- package/template/wall-e/agent-runners/index.js +22 -0
- package/template/wall-e/agent-runtime/harness.js +212 -0
- package/template/wall-e/agent-runtime/index.js +8 -0
- package/template/wall-e/agent-runtime/registry.js +67 -0
- package/template/wall-e/agent-runtime/session-store.js +179 -0
- package/template/wall-e/agent-runtime/spawn.js +208 -0
- package/template/wall-e/api-walle.js +174 -7
- package/template/wall-e/brain.js +266 -28
- package/template/wall-e/channels/policy.js +88 -0
- package/template/wall-e/channels/registry.js +15 -1
- package/template/wall-e/channels/reply-dispatcher.js +70 -0
- package/template/wall-e/channels/session-bindings.js +51 -0
- package/template/wall-e/chat/code-review-context.js +29 -0
- package/template/wall-e/chat.js +188 -42
- package/template/wall-e/coding/acp-adapter.js +188 -0
- package/template/wall-e/coding/agent-catalog.js +129 -0
- package/template/wall-e/coding/compaction-service.js +247 -0
- package/template/wall-e/coding/execution-trace.js +3 -0
- package/template/wall-e/coding/instruction-service.js +224 -0
- package/template/wall-e/coding/model-message.js +67 -0
- package/template/wall-e/coding/permission-rules-store.js +111 -0
- package/template/wall-e/coding/permission-service.js +266 -0
- package/template/wall-e/coding/prompt-bundle.js +67 -0
- package/template/wall-e/coding/prompt-runtime.js +243 -0
- package/template/wall-e/coding/provider-transform.js +188 -0
- package/template/wall-e/coding/runtime-mode.js +132 -0
- package/template/wall-e/coding/snapshot-service.js +155 -0
- package/template/wall-e/coding/stream-processor.js +268 -0
- package/template/wall-e/coding/task-tool.js +255 -0
- package/template/wall-e/coding/tool-registry.js +361 -0
- package/template/wall-e/coding/transcript-writer.js +143 -0
- package/template/wall-e/coding/workspace-replay.js +324 -0
- package/template/wall-e/coding-context.js +4 -22
- package/template/wall-e/coding-orchestrator.js +307 -18
- package/template/wall-e/coding-prompts.js +44 -3
- package/template/wall-e/context/context-builder.js +43 -1
- package/template/wall-e/context/topic-matcher.js +1 -1
- package/template/wall-e/eval/agent-runner.js +59 -13
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
- package/template/wall-e/eval/benchmarks.js +100 -16
- package/template/wall-e/eval/eval-orchestrator.js +218 -8
- package/template/wall-e/eval/harvester.js +62 -5
- package/template/wall-e/eval/head-to-head.js +23 -2
- package/template/wall-e/eval/humaneval-adapter.js +30 -5
- package/template/wall-e/eval/livecodebench-adapter.js +29 -5
- package/template/wall-e/eval/manifest.js +186 -0
- package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
- package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
- package/template/wall-e/eval/session-transcripts.js +57 -4
- package/template/wall-e/eval/swebench-adapter.js +109 -3
- package/template/wall-e/evaluation/agent-router.js +53 -1
- package/template/wall-e/evaluation/coding-quorum.js +48 -1
- package/template/wall-e/evaluation/router.js +4 -2
- package/template/wall-e/evaluation/tier-selector.js +11 -1
- package/template/wall-e/extraction/contradiction.js +2 -2
- package/template/wall-e/extraction/indexer.js +2 -1
- package/template/wall-e/extraction/knowledge-extractor.js +2 -2
- package/template/wall-e/hooks/cli.js +92 -0
- package/template/wall-e/hooks/discovery.js +119 -0
- package/template/wall-e/hooks/index.js +7 -0
- package/template/wall-e/hooks/manifest.js +55 -0
- package/template/wall-e/hooks/runtime.js +84 -0
- package/template/wall-e/hooks/session-memory.js +225 -0
- package/template/wall-e/http/auth.js +6 -2
- package/template/wall-e/http/chat-api.js +54 -8
- package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
- package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
- package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
- package/template/wall-e/listening/calendar.js +3 -1
- package/template/wall-e/llm/client.js +64 -10
- package/template/wall-e/llm/google.js +39 -5
- package/template/wall-e/llm/ollama.js +1 -1
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/provider-availability.js +10 -0
- package/template/wall-e/llm/provider-error.js +269 -0
- package/template/wall-e/llm/tool-adapter.js +48 -12
- package/template/wall-e/loops/boot.js +2 -1
- package/template/wall-e/loops/initiative.js +2 -2
- package/template/wall-e/loops/tasks.js +8 -47
- package/template/wall-e/loops/workspace-prompts.js +20 -0
- package/template/wall-e/mcp-server.js +442 -1
- package/template/wall-e/memory/session-ingest-service.js +159 -0
- package/template/wall-e/memory/source-indexer.js +289 -0
- package/template/wall-e/plugins/discovery.js +83 -0
- package/template/wall-e/plugins/manifest-loader.js +50 -10
- package/template/wall-e/plugins/manifest-schema.js +69 -0
- package/template/wall-e/plugins/model-catalog.js +55 -0
- package/template/wall-e/prompts/coding/base.txt +2 -0
- package/template/wall-e/prompts/coding/deepseek.txt +1 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
- package/template/wall-e/prompts/coding/plan.txt +1 -0
- package/template/wall-e/runtime/execution-trace.js +220 -0
- package/template/wall-e/security/audit.js +266 -0
- package/template/wall-e/security/ssrf.js +236 -0
- package/template/wall-e/session-files.js +303 -0
- package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
- package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
- package/template/wall-e/skills/internal-skill-registry.js +2 -2
- package/template/wall-e/skills/script-skill-runner.js +143 -0
- package/template/wall-e/skills/skill-executor.js +5 -6
- package/template/wall-e/skills/skill-fallback.js +3 -1
- package/template/wall-e/skills/skill-harness-registry.js +7 -8
- package/template/wall-e/skills/skill-planner.js +52 -4
- package/template/wall-e/skills/slack-ingest.js +11 -3
- package/template/wall-e/sources/base.js +90 -0
- package/template/wall-e/sources/builtin.js +33 -0
- package/template/wall-e/sources/claude-code-jsonl.js +78 -0
- package/template/wall-e/sources/codex-jsonl.js +125 -0
- package/template/wall-e/sources/coding-session-utils.js +117 -0
- package/template/wall-e/sources/contract-suite.js +59 -0
- package/template/wall-e/sources/gemini-jsonl.js +85 -0
- package/template/wall-e/sources/index.js +9 -0
- package/template/wall-e/sources/jsonl-utils.js +181 -0
- package/template/wall-e/sources/record-types.js +252 -0
- package/template/wall-e/sources/registry.js +92 -0
- package/template/wall-e/sources/transforms.js +100 -0
- package/template/wall-e/sources/walle-jsonl.js +108 -0
- package/template/wall-e/tools/coding-middleware.js +31 -1
- package/template/wall-e/tools/file-tracker.js +25 -1
- package/template/wall-e/tools/local-tools.js +75 -47
- package/template/wall-e/tools/session-sharing.js +68 -1
- package/template/wall-e/tools/shell-analyzer.js +1 -1
- package/template/wall-e/tools/shell-policy.js +47 -0
- package/template/wall-e/tools/snapshot.js +42 -0
- package/template/wall-e/training/harvester.js +62 -5
- package/template/wall-e/utils/repair.js +253 -1
- package/template/website/index.html +3 -3
- 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
|
|
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
|
-
|
|
116
|
-
|
|
121
|
+
if (fs.existsSync(this.projectsDir)) {
|
|
122
|
+
let entries;
|
|
123
|
+
try { entries = fs.readdirSync(this.projectsDir); } catch { entries = []; }
|
|
117
124
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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,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
|
+
};
|