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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { makePrincipalKey, normalizePrincipal } = require('./policy');
|
|
4
|
+
|
|
5
|
+
class ChannelSessionBindings {
|
|
6
|
+
constructor({ initial = [] } = {}) {
|
|
7
|
+
this.bindings = new Map();
|
|
8
|
+
for (const binding of initial || []) {
|
|
9
|
+
this.bind(binding.principal || binding, binding.sessionId, binding.metadata || {});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
bind(principal, sessionId, metadata = {}) {
|
|
14
|
+
if (!sessionId) throw new Error('sessionId is required');
|
|
15
|
+
const normalized = normalizePrincipal(principal);
|
|
16
|
+
const key = makePrincipalKey(normalized);
|
|
17
|
+
const record = {
|
|
18
|
+
key,
|
|
19
|
+
principal: normalized,
|
|
20
|
+
sessionId,
|
|
21
|
+
metadata: { ...metadata },
|
|
22
|
+
updatedAt: new Date().toISOString(),
|
|
23
|
+
};
|
|
24
|
+
this.bindings.set(key, record);
|
|
25
|
+
return { ...record, principal: { ...record.principal }, metadata: { ...record.metadata } };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolve(principal) {
|
|
29
|
+
const key = makePrincipalKey(principal);
|
|
30
|
+
const record = this.bindings.get(key);
|
|
31
|
+
return record ? { ...record, principal: { ...record.principal }, metadata: { ...record.metadata } } : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
unbind(principal) {
|
|
35
|
+
return this.bindings.delete(makePrincipalKey(principal));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
list({ channel = null } = {}) {
|
|
39
|
+
return [...this.bindings.values()]
|
|
40
|
+
.filter(record => !channel || record.principal.channel === channel)
|
|
41
|
+
.map(record => ({ ...record, principal: { ...record.principal }, metadata: { ...record.metadata } }));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toJSON() {
|
|
45
|
+
return this.list();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
ChannelSessionBindings,
|
|
51
|
+
};
|
|
@@ -12,6 +12,10 @@ function isCodeReviewRequest(message) {
|
|
|
12
12
|
const text = String(message || '').toLowerCase();
|
|
13
13
|
return /\bcode\s+review\b/.test(text)
|
|
14
14
|
|| /\breview\s+(?:the\s+)?(?:code|diff|patch|changes?|changed files|pull request|pr)\b/.test(text)
|
|
15
|
+
|| /\breview\s+(?:my\s+|the\s+)?(?:local\s+|uncommitted\s+|unstaged\s+|staged\s+|working\s+tree\s+|workspace\s+)?changes?\b/.test(text)
|
|
16
|
+
|| /\b(?:review|inspect|check|audit)\s+(?:my\s+|the\s+)?(?:local\s+|working\s+tree\s+|workspace\s+|uncommitted\s+|unstaged\s+|staged\s+)?(?:diff|patch|changes?)\b/.test(text)
|
|
17
|
+
|| /\b(?:what|show|tell\s+me)\s+(?:changed|is\s+changed|local\s+changes|unstaged\s+changes|uncommitted\s+changes)\b/.test(text)
|
|
18
|
+
|| /\b(?:check|show|run)\s+(?:git\s+)?status\b/.test(text)
|
|
15
19
|
|| /\b(?:do|run|perform|take)\s+(?:a\s+)?(?:close\s+|thorough\s+)?code\s+review\b/.test(text)
|
|
16
20
|
|| /\b(?:review|audit)\s+my\s+(?:changes?|diff|patch|code)\b/.test(text);
|
|
17
21
|
}
|
|
@@ -25,6 +29,27 @@ function looksLikePrematureCodeReviewReply(text) {
|
|
|
25
29
|
return promisesInspection && !hasFindingsShape;
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
function snapshotIndicatesDirty(codeReviewContextBlock) {
|
|
33
|
+
const block = String(codeReviewContextBlock || '');
|
|
34
|
+
if (!/## Code Review Workspace Snapshot/.test(block)) return false;
|
|
35
|
+
const statusMatch = block.match(/### Git status\s*\n([\s\S]*?)(?:\n### |\n\n### |$)/);
|
|
36
|
+
if (!statusMatch) return false;
|
|
37
|
+
const status = statusMatch[1].trim();
|
|
38
|
+
return !!status && status !== '(clean)';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function looksLikeContradictoryCleanReviewReply(text, codeReviewContextBlock) {
|
|
42
|
+
if (!snapshotIndicatesDirty(codeReviewContextBlock)) return false;
|
|
43
|
+
const lower = String(text || '').toLowerCase();
|
|
44
|
+
if (!lower.trim()) return false;
|
|
45
|
+
return /\bworking tree (?:is )?clean\b/.test(lower)
|
|
46
|
+
|| /\bworktree (?:is )?clean\b/.test(lower)
|
|
47
|
+
|| /\bno (?:local |uncommitted |unstaged |staged )?changes\b/.test(lower)
|
|
48
|
+
|| /\bnothing (?:is )?(?:staged|unstaged|changed|to review)\b/.test(lower)
|
|
49
|
+
|| /\bno staged,?\s+unstaged,?\s+(?:or\s+)?untracked\b/.test(lower)
|
|
50
|
+
|| /\bindex (?:is )?clean\b/.test(lower);
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
function runGit(cwd, args, opts = {}) {
|
|
29
54
|
try {
|
|
30
55
|
return execFileSync('git', args, {
|
|
@@ -166,6 +191,7 @@ function buildCodeReviewContextBlock({ message, cwd, maxContextBytes = DEFAULT_M
|
|
|
166
191
|
|
|
167
192
|
const block = [
|
|
168
193
|
'## Code Review Workspace Snapshot',
|
|
194
|
+
`Requested cwd: ${path.resolve(cwd)}`,
|
|
169
195
|
`Project: ${root}`,
|
|
170
196
|
`Branch: ${branch}`,
|
|
171
197
|
`HEAD: ${head}`,
|
|
@@ -177,6 +203,7 @@ function buildCodeReviewContextBlock({ message, cwd, maxContextBytes = DEFAULT_M
|
|
|
177
203
|
'',
|
|
178
204
|
'### Code Review Instructions',
|
|
179
205
|
'You already have the git status and diff above. Do not say you will inspect, read, or fetch diffs.',
|
|
206
|
+
`Start with: Checked ${root} (requested cwd ${path.resolve(cwd)}, branch ${branch}, status ${status === '(clean)' ? 'clean' : 'dirty'}).`,
|
|
180
207
|
'Produce the review now. Lead with findings ordered by severity. Cite file paths and line references when the diff gives enough context.',
|
|
181
208
|
'If there are no issues, say that clearly and include remaining test gaps or residual risk.',
|
|
182
209
|
].join('\n');
|
|
@@ -187,6 +214,8 @@ function buildCodeReviewContextBlock({ message, cwd, maxContextBytes = DEFAULT_M
|
|
|
187
214
|
module.exports = {
|
|
188
215
|
isCodeReviewRequest,
|
|
189
216
|
looksLikePrematureCodeReviewReply,
|
|
217
|
+
snapshotIndicatesDirty,
|
|
218
|
+
looksLikeContradictoryCleanReviewReply,
|
|
190
219
|
buildCodeReviewContextBlock,
|
|
191
220
|
gitRootFor,
|
|
192
221
|
};
|
package/template/wall-e/chat.js
CHANGED
|
@@ -4,7 +4,9 @@ const {
|
|
|
4
4
|
createClient,
|
|
5
5
|
detectProviderForModel,
|
|
6
6
|
getDefaultClient,
|
|
7
|
+
getDefaultModelForProvider,
|
|
7
8
|
getDefaultProviderType,
|
|
9
|
+
resolveCompatibleModel,
|
|
8
10
|
} = require('./llm/client');
|
|
9
11
|
const { executeLocalTool, LOCAL_TOOL_DEFINITIONS, resolveProjectPath } = require('./tools/local-tools');
|
|
10
12
|
const slackMcp = require('./tools/slack-mcp');
|
|
@@ -17,8 +19,15 @@ const { runShadow } = require('./eval/shadow');
|
|
|
17
19
|
const {
|
|
18
20
|
buildCodeReviewContextBlock,
|
|
19
21
|
isCodeReviewRequest,
|
|
22
|
+
looksLikeContradictoryCleanReviewReply,
|
|
20
23
|
looksLikePrematureCodeReviewReply,
|
|
21
24
|
} = require('./chat/code-review-context');
|
|
25
|
+
const { createSessionRecorder } = require('./session-files');
|
|
26
|
+
const {
|
|
27
|
+
decorateProviderError,
|
|
28
|
+
recordProviderFailureAlert,
|
|
29
|
+
unavailableProviderError,
|
|
30
|
+
} = require('./llm/provider-error');
|
|
22
31
|
let _telemetry;
|
|
23
32
|
try { _telemetry = require('./telemetry'); } catch { _telemetry = { trackError() {}, track() {} }; }
|
|
24
33
|
let _embeddings;
|
|
@@ -96,8 +105,12 @@ function _getCommandRegistry() {
|
|
|
96
105
|
_commandRegistry.registerBuiltins();
|
|
97
106
|
try {
|
|
98
107
|
const { findSkill } = require('./skills/skill-loader');
|
|
99
|
-
const {
|
|
100
|
-
_commandRegistry.registerSkills(findSkill,
|
|
108
|
+
const { runScriptSkillByName } = require('./skills/script-skill-runner');
|
|
109
|
+
_commandRegistry.registerSkills(findSkill, async (taskId, task) => {
|
|
110
|
+
return runScriptSkillByName(task.skill, task, {
|
|
111
|
+
log: line => console.error(`[skill:${taskId}] ${line}`),
|
|
112
|
+
});
|
|
113
|
+
});
|
|
101
114
|
} catch { /* skill loader not available */ }
|
|
102
115
|
return _commandRegistry;
|
|
103
116
|
}
|
|
@@ -294,6 +307,20 @@ function resolveModelSelection(model, explicitProvider) {
|
|
|
294
307
|
LIMIT 1
|
|
295
308
|
`).get(raw, raw, explicitProvider || null, explicitProvider || null, raw);
|
|
296
309
|
if (row) {
|
|
310
|
+
if (explicitProvider) {
|
|
311
|
+
const compatibleModel = resolveCompatibleModel(row.model_id, explicitProvider);
|
|
312
|
+
if (compatibleModel !== row.model_id) {
|
|
313
|
+
return {
|
|
314
|
+
input: raw,
|
|
315
|
+
model: compatibleModel,
|
|
316
|
+
provider: explicitProvider,
|
|
317
|
+
providerConfig: null,
|
|
318
|
+
registryId: null,
|
|
319
|
+
coercedFrom: row.model_id,
|
|
320
|
+
detectedProvider: row.provider_type || detectProviderForModel(row.model_id) || null,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
297
324
|
return {
|
|
298
325
|
input: raw,
|
|
299
326
|
model: row.model_id,
|
|
@@ -306,10 +333,24 @@ function resolveModelSelection(model, explicitProvider) {
|
|
|
306
333
|
}
|
|
307
334
|
} catch {}
|
|
308
335
|
|
|
336
|
+
const detectedProvider = detectProviderForModel(raw);
|
|
337
|
+
if (explicitProvider) {
|
|
338
|
+
const compatibleModel = resolveCompatibleModel(raw, explicitProvider);
|
|
339
|
+
return {
|
|
340
|
+
input: raw,
|
|
341
|
+
model: compatibleModel,
|
|
342
|
+
provider: explicitProvider,
|
|
343
|
+
providerConfig: null,
|
|
344
|
+
registryId: null,
|
|
345
|
+
coercedFrom: compatibleModel === raw ? null : raw,
|
|
346
|
+
detectedProvider,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
309
350
|
return {
|
|
310
351
|
input: raw,
|
|
311
352
|
model: raw,
|
|
312
|
-
provider:
|
|
353
|
+
provider: detectedProvider || null,
|
|
313
354
|
providerConfig: null,
|
|
314
355
|
registryId: null,
|
|
315
356
|
};
|
|
@@ -363,28 +404,12 @@ async function chat(message, opts = {}) {
|
|
|
363
404
|
// backward compatibility (the LLM client will use its own env-based config).
|
|
364
405
|
const { default: providerAvailability } = require('./llm/provider-availability');
|
|
365
406
|
if (providerAvailability.getConfiguredProviders().length > 0 && !providerAvailability.isAnyProviderAvailable()) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
} else if (detection.setupAction === 'ollama-running') {
|
|
373
|
-
guidance += 'I see Ollama is already running on your system. Use the setup button to connect it.';
|
|
374
|
-
} else if (detection.ollamaRecommendation) {
|
|
375
|
-
guidance += `I can set up a free local AI (${detection.ollamaRecommendation.label}, ${detection.ollamaRecommendation.size} download). Use the setup button in the toolbar.`;
|
|
376
|
-
} else {
|
|
377
|
-
guidance += 'Please add an API key in the setup page, or install Ollama for free local AI.';
|
|
378
|
-
}
|
|
379
|
-
} catch {
|
|
380
|
-
guidance += 'Please configure an AI provider in the setup page.';
|
|
381
|
-
}
|
|
382
|
-
return {
|
|
383
|
-
reply: guidance,
|
|
384
|
-
model: 'system', provider: 'none', latencyMs: 0,
|
|
385
|
-
tokens: { input: 0, output: 0 }, cost: 0, toolCalls: [],
|
|
386
|
-
providerStatus: { configured: false },
|
|
387
|
-
};
|
|
407
|
+
const unavailableErr = unavailableProviderError(providerAvailability.getConfiguredProviders(), {
|
|
408
|
+
provider: opts.provider || getDefaultProviderType(),
|
|
409
|
+
model: opts.model || null,
|
|
410
|
+
});
|
|
411
|
+
recordProviderFailureAlert(unavailableErr.providerError, brain);
|
|
412
|
+
throw unavailableErr;
|
|
388
413
|
}
|
|
389
414
|
|
|
390
415
|
// Detect user signals from follow-up messages (retroactive quality scoring)
|
|
@@ -429,7 +454,7 @@ async function chat(message, opts = {}) {
|
|
|
429
454
|
const modelSelection = explicitModelSelection
|
|
430
455
|
? { model: explicitModelSelection.model, modelTier: 'balanced', scorecardUsed: false }
|
|
431
456
|
: selectModelForMessage(message, opts.taskType);
|
|
432
|
-
|
|
457
|
+
let selectedModel = modelSelection.model;
|
|
433
458
|
const selectedRoute = explicitModelSelection || resolveModelSelection(selectedModel, opts.provider);
|
|
434
459
|
const effectiveCwd = opts.cwd || opts.context?.cwd || opts.context?.projectPath || null;
|
|
435
460
|
|
|
@@ -471,6 +496,25 @@ async function chat(message, opts = {}) {
|
|
|
471
496
|
brain,
|
|
472
497
|
});
|
|
473
498
|
const existingSession = brain.getSession(sessionId);
|
|
499
|
+
const sessionRecorder = createSessionRecorder({
|
|
500
|
+
sessionId,
|
|
501
|
+
channel,
|
|
502
|
+
cwd: effectiveCwd || opts.cwd || process.cwd(),
|
|
503
|
+
metadata: {
|
|
504
|
+
taskType: opts.taskType || 'chat',
|
|
505
|
+
source: opts.source || channel || 'chat',
|
|
506
|
+
model: selectedModel,
|
|
507
|
+
provider: selectedRoute.provider || opts.provider || null,
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
const recordSessionMessage = (role, content, extra) => {
|
|
511
|
+
try {
|
|
512
|
+
return sessionRecorder.appendMessage(role, content, extra);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
console.error('[chat] Failed to append session message:', err.message);
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
474
518
|
|
|
475
519
|
// Review channel: build a focused code review system prompt
|
|
476
520
|
let reviewContextBlock = '';
|
|
@@ -554,6 +598,8 @@ async function chat(message, opts = {}) {
|
|
|
554
598
|
}
|
|
555
599
|
}
|
|
556
600
|
|
|
601
|
+
selectedModel = resolveCompatibleModel(selectedModel || getDefaultModelForProvider(targetProviderType), provider.type || targetProviderType);
|
|
602
|
+
|
|
557
603
|
const codeReviewFastPath = codeReviewRequested
|
|
558
604
|
&& isUsableCodeReviewSnapshot(codeReviewContextBlock)
|
|
559
605
|
&& opts.codeReviewMode !== 'deep';
|
|
@@ -571,19 +617,31 @@ async function chat(message, opts = {}) {
|
|
|
571
617
|
};
|
|
572
618
|
}
|
|
573
619
|
|
|
574
|
-
// Per-turn abort controller
|
|
575
|
-
//
|
|
576
|
-
// as long as each individual turn completes within the timeout.
|
|
620
|
+
// Per-turn abort controller. Each API call is capped by the remaining
|
|
621
|
+
// per-message budget, with a 2-minute ceiling for long-running intents.
|
|
577
622
|
let controller = new AbortController();
|
|
578
623
|
let timeout = null;
|
|
579
|
-
|
|
624
|
+
let messageDeadline = null;
|
|
625
|
+
const MAX_TURN_TIMEOUT_MS = 120000;
|
|
626
|
+
const MIN_TURN_TIMEOUT_MS = 50;
|
|
627
|
+
function resolveTurnTimeoutMs(requestedMs) {
|
|
628
|
+
if (Number.isFinite(requestedMs)) {
|
|
629
|
+
return Math.max(MIN_TURN_TIMEOUT_MS, Math.min(MAX_TURN_TIMEOUT_MS, requestedMs));
|
|
630
|
+
}
|
|
631
|
+
if (!messageDeadline) return MAX_TURN_TIMEOUT_MS;
|
|
632
|
+
return Math.max(
|
|
633
|
+
MIN_TURN_TIMEOUT_MS,
|
|
634
|
+
Math.min(MAX_TURN_TIMEOUT_MS, messageDeadline - Date.now()),
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
function resetTurnTimeout(requestedMs) {
|
|
580
638
|
if (timeout) clearTimeout(timeout);
|
|
581
639
|
controller = new AbortController();
|
|
582
640
|
if (opts.abortSignal?.aborted) controller.abort();
|
|
583
641
|
else if (opts.abortSignal) {
|
|
584
642
|
opts.abortSignal.addEventListener('abort', () => controller.abort(), { once: true });
|
|
585
643
|
}
|
|
586
|
-
timeout = setTimeout(() => controller.abort(),
|
|
644
|
+
timeout = setTimeout(() => controller.abort(), resolveTurnTimeoutMs(requestedMs));
|
|
587
645
|
}
|
|
588
646
|
|
|
589
647
|
// Define internal tools WALL-E can use during chat
|
|
@@ -823,7 +881,13 @@ async function chat(message, opts = {}) {
|
|
|
823
881
|
return tools;
|
|
824
882
|
}
|
|
825
883
|
|
|
826
|
-
const
|
|
884
|
+
const progressSink = opts.onProgress || (() => {});
|
|
885
|
+
const onProgress = (event) => {
|
|
886
|
+
try { sessionRecorder.appendProgress(event); } catch (err) {
|
|
887
|
+
console.error('[chat] Failed to append session progress:', err.message);
|
|
888
|
+
}
|
|
889
|
+
return progressSink(event);
|
|
890
|
+
};
|
|
827
891
|
|
|
828
892
|
// Execute a chat tool call
|
|
829
893
|
async function executeChatTool(name, input) {
|
|
@@ -909,9 +973,12 @@ async function chat(message, opts = {}) {
|
|
|
909
973
|
const bundledSkill = findSkill(input.skill_name);
|
|
910
974
|
if (bundledSkill && bundledSkill.execution === 'script') {
|
|
911
975
|
try {
|
|
912
|
-
const {
|
|
976
|
+
const { runScriptSkillByName } = require('./skills/script-skill-runner');
|
|
913
977
|
const fakeTaskId = `chat-${Date.now()}`;
|
|
914
|
-
const result = await
|
|
978
|
+
const result = await runScriptSkillByName(input.skill_name, { id: fakeTaskId, skill: input.skill_name, title: bundledSkill.name }, {
|
|
979
|
+
skill: bundledSkill,
|
|
980
|
+
log: line => console.error(`[skill:${fakeTaskId}] ${line}`),
|
|
981
|
+
});
|
|
915
982
|
return { success: true, output: result.slice(0, 5000) };
|
|
916
983
|
} catch (err) {
|
|
917
984
|
return { error: err.message };
|
|
@@ -1324,8 +1391,13 @@ async function chat(message, opts = {}) {
|
|
|
1324
1391
|
channel, session_id: sessionId,
|
|
1325
1392
|
attachments: persisted,
|
|
1326
1393
|
});
|
|
1394
|
+
recordSessionMessage('user', message, {
|
|
1395
|
+
dbMessageId: _userMessageId,
|
|
1396
|
+
attachments: persisted,
|
|
1397
|
+
});
|
|
1327
1398
|
} else if (persistUserTurn) {
|
|
1328
1399
|
brain.insertChatMessage({ role: 'user', content: message, channel, session_id: sessionId });
|
|
1400
|
+
recordSessionMessage('user', message);
|
|
1329
1401
|
}
|
|
1330
1402
|
if (persistUserTurn) {
|
|
1331
1403
|
brain.insertMemory({
|
|
@@ -1391,6 +1463,7 @@ async function chat(message, opts = {}) {
|
|
|
1391
1463
|
let finalText = '';
|
|
1392
1464
|
let lastTurnText = ''; // Only the last turn's text (prevents error accumulation across turns)
|
|
1393
1465
|
let lastTurn = 0;
|
|
1466
|
+
let finalResponseMeta = null;
|
|
1394
1467
|
|
|
1395
1468
|
// Adaptive limits based on intent classification
|
|
1396
1469
|
const INTENT_LIMITS = {
|
|
@@ -1403,7 +1476,7 @@ async function chat(message, opts = {}) {
|
|
|
1403
1476
|
const MAX_TOOL_CALLS = opts.maxToolCalls != null ? opts.maxToolCalls : limits.maxTools;
|
|
1404
1477
|
let toolCallCount = 0;
|
|
1405
1478
|
const MESSAGE_TIMEOUT_MS = opts.timeoutMs || limits.timeoutMs;
|
|
1406
|
-
|
|
1479
|
+
messageDeadline = Date.now() + MESSAGE_TIMEOUT_MS;
|
|
1407
1480
|
console.log('[chat] Intent:', intent, '| topics:', queryTopics.join(','), '| limits: turns=', MAX_TURNS, 'tools=', MAX_TOOL_CALLS, 'timeout=', MESSAGE_TIMEOUT_MS, 'ms');
|
|
1408
1481
|
|
|
1409
1482
|
const chatStart = Date.now();
|
|
@@ -1500,11 +1573,16 @@ async function chat(message, opts = {}) {
|
|
|
1500
1573
|
} catch (llmErr) {
|
|
1501
1574
|
// Track provider health — failed LLM call
|
|
1502
1575
|
try {
|
|
1503
|
-
const provType = usedProvider || getDefaultProviderType();
|
|
1576
|
+
const provType = usedProvider || targetProviderType || getDefaultProviderType();
|
|
1504
1577
|
const registeredProv = providerAvailability.getConfiguredProviders().find(p => p.providerType === provType);
|
|
1505
1578
|
if (registeredProv) providerAvailability.recordFailure(registeredProv.providerId, llmErr.message);
|
|
1506
1579
|
} catch {}
|
|
1507
|
-
|
|
1580
|
+
const decorated = decorateProviderError(llmErr, {
|
|
1581
|
+
provider: usedProvider || targetProviderType || getDefaultProviderType(),
|
|
1582
|
+
model: selectedModel,
|
|
1583
|
+
});
|
|
1584
|
+
recordProviderFailureAlert(decorated.providerError, brain);
|
|
1585
|
+
throw decorated;
|
|
1508
1586
|
}
|
|
1509
1587
|
const modelElapsed = Date.now() - turnStart;
|
|
1510
1588
|
timings.modelMs += modelElapsed;
|
|
@@ -1537,6 +1615,12 @@ async function chat(message, opts = {}) {
|
|
|
1537
1615
|
// If no tool calls, we have our final answer
|
|
1538
1616
|
if (response.toolCalls.length === 0) {
|
|
1539
1617
|
finalText = response.content || '';
|
|
1618
|
+
finalResponseMeta = {
|
|
1619
|
+
model: response.model || selectedModel,
|
|
1620
|
+
provider: response.provider || usedProvider || targetProviderType,
|
|
1621
|
+
usage: response.usage || null,
|
|
1622
|
+
stopReason: response.stopReason || null,
|
|
1623
|
+
};
|
|
1540
1624
|
console.log('[chat] Total time:', Date.now() - chatStart, 'ms across', turn + 1, 'turns');
|
|
1541
1625
|
break;
|
|
1542
1626
|
}
|
|
@@ -1653,6 +1737,16 @@ async function chat(message, opts = {}) {
|
|
|
1653
1737
|
for (const tc of response.toolCalls) {
|
|
1654
1738
|
assistantContent.push({ type: 'tool_use', id: tc.id, name: tc.name, input: tc.input });
|
|
1655
1739
|
}
|
|
1740
|
+
recordSessionMessage('assistant', assistantContent, {
|
|
1741
|
+
model: response.model || selectedModel,
|
|
1742
|
+
provider: response.provider || usedProvider || targetProviderType,
|
|
1743
|
+
usage: response.usage || null,
|
|
1744
|
+
stopReason: response.stopReason || null,
|
|
1745
|
+
});
|
|
1746
|
+
recordSessionMessage('user', toolResults, {
|
|
1747
|
+
synthetic: true,
|
|
1748
|
+
reason: 'tool_results',
|
|
1749
|
+
});
|
|
1656
1750
|
messages.push({ role: 'assistant', content: assistantContent });
|
|
1657
1751
|
messages.push({ role: 'user', content: toolResults });
|
|
1658
1752
|
|
|
@@ -1678,9 +1772,12 @@ async function chat(message, opts = {}) {
|
|
|
1678
1772
|
// Otherwise, continue: the model needs to see tool results before responding.
|
|
1679
1773
|
}
|
|
1680
1774
|
|
|
1681
|
-
|
|
1775
|
+
const needsCodeReviewRepair = codeReviewContextBlock
|
|
1776
|
+
&& (looksLikePrematureCodeReviewReply(finalText)
|
|
1777
|
+
|| looksLikeContradictoryCleanReviewReply(finalText, codeReviewContextBlock));
|
|
1778
|
+
if (needsCodeReviewRepair) {
|
|
1682
1779
|
try {
|
|
1683
|
-
console.log('[chat]
|
|
1780
|
+
console.log('[chat] Invalid code-review reply detected; forcing final review follow-up');
|
|
1684
1781
|
resetTurnTimeout();
|
|
1685
1782
|
const repairStart = Date.now();
|
|
1686
1783
|
const repairMessages = [
|
|
@@ -1706,13 +1803,29 @@ async function chat(message, opts = {}) {
|
|
|
1706
1803
|
}
|
|
1707
1804
|
timings.repairMs += Date.now() - repairStart;
|
|
1708
1805
|
if (!usedModel) { usedModel = repairResponse.model; usedProvider = repairResponse.provider; }
|
|
1709
|
-
if (repairResponse.content
|
|
1806
|
+
if (repairResponse.content
|
|
1807
|
+
&& !looksLikePrematureCodeReviewReply(repairResponse.content)
|
|
1808
|
+
&& !looksLikeContradictoryCleanReviewReply(repairResponse.content, codeReviewContextBlock)) {
|
|
1710
1809
|
finalText = repairResponse.content;
|
|
1711
1810
|
lastTurnText = repairResponse.content;
|
|
1811
|
+
finalResponseMeta = {
|
|
1812
|
+
model: repairResponse.model || selectedModel,
|
|
1813
|
+
provider: repairResponse.provider || usedProvider || targetProviderType,
|
|
1814
|
+
usage: repairResponse.usage || null,
|
|
1815
|
+
stopReason: repairResponse.stopReason || null,
|
|
1816
|
+
repair: true,
|
|
1817
|
+
};
|
|
1712
1818
|
}
|
|
1713
1819
|
} catch (repairErr) {
|
|
1714
1820
|
timings.repairMs = timings.repairMs || 0;
|
|
1715
1821
|
console.error('[chat] Code-review repair call failed:', repairErr.message);
|
|
1822
|
+
try {
|
|
1823
|
+
const decorated = decorateProviderError(repairErr, {
|
|
1824
|
+
provider: usedProvider || targetProviderType || getDefaultProviderType(),
|
|
1825
|
+
model: selectedModel,
|
|
1826
|
+
});
|
|
1827
|
+
recordProviderFailureAlert(decorated.providerError, brain);
|
|
1828
|
+
} catch {}
|
|
1716
1829
|
}
|
|
1717
1830
|
}
|
|
1718
1831
|
|
|
@@ -1728,15 +1841,36 @@ async function chat(message, opts = {}) {
|
|
|
1728
1841
|
signal: controller.signal,
|
|
1729
1842
|
});
|
|
1730
1843
|
finalText = summaryResponse.content || '';
|
|
1844
|
+
finalResponseMeta = {
|
|
1845
|
+
model: summaryResponse.model || selectedModel,
|
|
1846
|
+
provider: summaryResponse.provider || usedProvider || targetProviderType,
|
|
1847
|
+
usage: summaryResponse.usage || null,
|
|
1848
|
+
stopReason: summaryResponse.stopReason || null,
|
|
1849
|
+
summaryFallback: true,
|
|
1850
|
+
};
|
|
1731
1851
|
} catch (summaryErr) {
|
|
1732
1852
|
console.error('[chat] Summary call failed:', summaryErr.message);
|
|
1853
|
+
let summaryProviderError = null;
|
|
1854
|
+
try {
|
|
1855
|
+
const decorated = decorateProviderError(summaryErr, {
|
|
1856
|
+
provider: usedProvider || targetProviderType || getDefaultProviderType(),
|
|
1857
|
+
model: selectedModel,
|
|
1858
|
+
});
|
|
1859
|
+
summaryProviderError = decorated.providerError;
|
|
1860
|
+
recordProviderFailureAlert(summaryProviderError, brain);
|
|
1861
|
+
} catch {}
|
|
1733
1862
|
// Include the tool results directly so the user at least sees what the tool returned
|
|
1734
1863
|
const lastToolResults = messages.filter(m => m.role === 'user' && Array.isArray(m.content))
|
|
1735
1864
|
.pop()?.content?.map(r => {
|
|
1736
1865
|
try { const parsed = JSON.parse(r.content); return JSON.stringify(parsed, null, 2); } catch { return r.content; }
|
|
1737
1866
|
}).join('\n');
|
|
1867
|
+
const summaryFailureText = summaryProviderError
|
|
1868
|
+
? `${summaryProviderError.title}: ${summaryProviderError.userMessage}`
|
|
1869
|
+
: `Summary generation failed: ${summaryErr.message}`;
|
|
1738
1870
|
if (lastToolResults) {
|
|
1739
|
-
finalText = '
|
|
1871
|
+
finalText = summaryFailureText + '\n\nHere are the raw tool results:\n\n```\n' + lastToolResults.slice(0, 3000) + '\n```';
|
|
1872
|
+
} else {
|
|
1873
|
+
finalText = summaryFailureText;
|
|
1740
1874
|
}
|
|
1741
1875
|
}
|
|
1742
1876
|
}
|
|
@@ -1796,7 +1930,13 @@ async function chat(message, opts = {}) {
|
|
|
1796
1930
|
|
|
1797
1931
|
// Save assistant response (user message was already saved before calling Claude)
|
|
1798
1932
|
const persistStart = Date.now();
|
|
1799
|
-
brain.insertChatMessage({ role: 'assistant', content: text, channel, session_id: sessionId });
|
|
1933
|
+
const assistantMessage = brain.insertChatMessage({ role: 'assistant', content: text, channel, session_id: sessionId });
|
|
1934
|
+
recordSessionMessage('assistant', text, {
|
|
1935
|
+
...(finalResponseMeta || {}),
|
|
1936
|
+
dbMessageId: assistantMessage.id,
|
|
1937
|
+
selfCritique: selfCritiqueOutcome,
|
|
1938
|
+
final: true,
|
|
1939
|
+
});
|
|
1800
1940
|
|
|
1801
1941
|
brain.insertMemory({
|
|
1802
1942
|
source: 'wall-e-chat',
|
|
@@ -1926,7 +2066,13 @@ async function chat(message, opts = {}) {
|
|
|
1926
2066
|
cost, toolCalls: allToolCalls,
|
|
1927
2067
|
latencyBreakdown: timings,
|
|
1928
2068
|
silentReply,
|
|
2069
|
+
...(opts.includeSessionFile ? { sessionFile: sessionRecorder.filePath } : {}),
|
|
1929
2070
|
};
|
|
2071
|
+
} catch (err) {
|
|
2072
|
+
try { sessionRecorder.appendError(err); } catch (sessionErr) {
|
|
2073
|
+
console.error('[chat] Failed to append session error:', sessionErr.message);
|
|
2074
|
+
}
|
|
2075
|
+
throw err;
|
|
1930
2076
|
} finally {
|
|
1931
2077
|
clearTimeout(timeout);
|
|
1932
2078
|
}
|