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
|
@@ -9,7 +9,37 @@ const GEMINI_CLIENT_SECRET = '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';
|
|
|
9
9
|
let _cachedToken = null;
|
|
10
10
|
let _tokenExpiresAt = 0;
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function providerAbortError() {
|
|
13
|
+
const err = new Error('provider request aborted');
|
|
14
|
+
err.name = 'AbortError';
|
|
15
|
+
return err;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function throwIfAborted(signal) {
|
|
19
|
+
if (signal?.aborted) throw providerAbortError();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function delayWithAbort(ms, signal) {
|
|
23
|
+
throwIfAborted(signal);
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let timer = null;
|
|
26
|
+
const cleanup = () => {
|
|
27
|
+
if (timer) clearTimeout(timer);
|
|
28
|
+
if (signal) signal.removeEventListener('abort', onAbort);
|
|
29
|
+
};
|
|
30
|
+
const onAbort = () => {
|
|
31
|
+
cleanup();
|
|
32
|
+
reject(providerAbortError());
|
|
33
|
+
};
|
|
34
|
+
timer = setTimeout(() => {
|
|
35
|
+
cleanup();
|
|
36
|
+
resolve();
|
|
37
|
+
}, ms);
|
|
38
|
+
if (signal) signal.addEventListener('abort', onAbort, { once: true });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function refreshOAuthToken(refreshToken, signal) {
|
|
13
43
|
if (_cachedToken && Date.now() < _tokenExpiresAt - 60000) return _cachedToken;
|
|
14
44
|
const resp = await fetch('https://oauth2.googleapis.com/token', {
|
|
15
45
|
method: 'POST',
|
|
@@ -18,6 +48,7 @@ async function refreshOAuthToken(refreshToken) {
|
|
|
18
48
|
client_id: GEMINI_CLIENT_ID, client_secret: GEMINI_CLIENT_SECRET,
|
|
19
49
|
refresh_token: refreshToken, grant_type: 'refresh_token',
|
|
20
50
|
}),
|
|
51
|
+
...(signal ? { signal } : {}),
|
|
21
52
|
});
|
|
22
53
|
const data = await resp.json();
|
|
23
54
|
if (!data.access_token) throw new Error('Token refresh failed: ' + (data.error_description || data.error));
|
|
@@ -107,6 +138,7 @@ function messagesToGemini(messages) {
|
|
|
107
138
|
*/
|
|
108
139
|
function createGoogleProvider(config = {}) {
|
|
109
140
|
const isOAuth = config.authMode === 'oauth';
|
|
141
|
+
const GenAI = config._GoogleGenAI || GoogleGenAI;
|
|
110
142
|
if (!isOAuth && config.apiKey && config.apiKey.startsWith('ya29.')) {
|
|
111
143
|
console.warn('[google] Stored key is a Gemini CLI OAuth token (ya29.*). ' +
|
|
112
144
|
'These tokens use Google\'s private Code Assist API and cannot authenticate ' +
|
|
@@ -117,7 +149,7 @@ function createGoogleProvider(config = {}) {
|
|
|
117
149
|
const httpOptions = accessToken
|
|
118
150
|
? { headers: { 'Authorization': `Bearer ${accessToken}` } }
|
|
119
151
|
: undefined;
|
|
120
|
-
return new
|
|
152
|
+
return new GenAI({ apiKey: 'unused', httpOptions });
|
|
121
153
|
}
|
|
122
154
|
|
|
123
155
|
// Build GenAI client — for OAuth we pass Bearer header instead of apiKey
|
|
@@ -125,7 +157,7 @@ function createGoogleProvider(config = {}) {
|
|
|
125
157
|
if (isOAuth) {
|
|
126
158
|
genai = makeOAuthClient(config.apiKey);
|
|
127
159
|
} else {
|
|
128
|
-
genai = new
|
|
160
|
+
genai = new GenAI({ apiKey: config.apiKey });
|
|
129
161
|
}
|
|
130
162
|
|
|
131
163
|
return {
|
|
@@ -170,7 +202,7 @@ function createGoogleProvider(config = {}) {
|
|
|
170
202
|
let activeGenai = genai;
|
|
171
203
|
if (isOAuth && config.refreshToken) {
|
|
172
204
|
try {
|
|
173
|
-
const freshToken = await refreshOAuthToken(config.refreshToken);
|
|
205
|
+
const freshToken = await refreshOAuthToken(config.refreshToken, signal);
|
|
174
206
|
activeGenai = makeOAuthClient(freshToken);
|
|
175
207
|
} catch (e) {
|
|
176
208
|
console.warn('[google] OAuth token refresh failed:', e.message);
|
|
@@ -186,16 +218,18 @@ function createGoogleProvider(config = {}) {
|
|
|
186
218
|
let raw;
|
|
187
219
|
const MAX_RETRIES = 3;
|
|
188
220
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
221
|
+
throwIfAborted(signal);
|
|
189
222
|
try {
|
|
190
223
|
raw = await activeGenai.models.generateContent(requestConfig);
|
|
191
224
|
break;
|
|
192
225
|
} catch (err) {
|
|
226
|
+
throwIfAborted(signal);
|
|
193
227
|
const status = err.status || err.code || 0;
|
|
194
228
|
const message = err.message || '';
|
|
195
229
|
if (attempt < MAX_RETRIES && isRetryable(status, message)) {
|
|
196
230
|
const delay = parseRetryDelay(null, attempt);
|
|
197
231
|
console.log(`[google] Retrying (${attempt + 1}/${MAX_RETRIES}) after ${delay}ms: ${message.slice(0, 100)}`);
|
|
198
|
-
await
|
|
232
|
+
await delayWithAbort(delay, signal);
|
|
199
233
|
continue;
|
|
200
234
|
}
|
|
201
235
|
throw err;
|
|
@@ -42,6 +42,16 @@ class ProviderAvailability {
|
|
|
42
42
|
return this._providers.get(id) || null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
getProviderForType(providerType) {
|
|
46
|
+
return [...this._providers.values()].find(p => p.providerType === providerType || p.providerId === providerType) || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
isProviderUsable(idOrType) {
|
|
50
|
+
const state = this.getProviderState(idOrType) || this.getProviderForType(idOrType);
|
|
51
|
+
if (!state) return true;
|
|
52
|
+
return state.status !== 'unhealthy';
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
/**
|
|
46
56
|
* Return all registered providers.
|
|
47
57
|
*/
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MAX_RAW_MESSAGE_CHARS = 2000;
|
|
4
|
+
|
|
5
|
+
const PROVIDER_LABELS = {
|
|
6
|
+
anthropic: 'Anthropic',
|
|
7
|
+
openai: 'OpenAI',
|
|
8
|
+
google: 'Google',
|
|
9
|
+
deepseek: 'DeepSeek',
|
|
10
|
+
ollama: 'Ollama',
|
|
11
|
+
mlx: 'MLX',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function providerLabel(provider) {
|
|
15
|
+
const key = String(provider || '').toLowerCase();
|
|
16
|
+
return PROVIDER_LABELS[key] || provider || 'AI provider';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function redactSecrets(value) {
|
|
20
|
+
return String(value || '')
|
|
21
|
+
.replace(/\bsk-[A-Za-z0-9_-]{12,}\b/g, 'sk-...redacted')
|
|
22
|
+
.replace(/\bya29\.[A-Za-z0-9._-]{12,}\b/g, 'ya29...redacted')
|
|
23
|
+
.replace(/\bAIza[A-Za-z0-9_-]{20,}\b/g, 'AIza...redacted')
|
|
24
|
+
.replace(/(api[_-]?key["'\s:=]+)[^"',\s]+/gi, '$1...redacted')
|
|
25
|
+
.slice(0, MAX_RAW_MESSAGE_CHARS);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function tryJson(value) {
|
|
29
|
+
if (!value || typeof value !== 'string') return null;
|
|
30
|
+
try { return JSON.parse(value); } catch { return null; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectStrings(value, out = []) {
|
|
34
|
+
if (value == null) return out;
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
out.push(value);
|
|
37
|
+
const parsed = tryJson(value);
|
|
38
|
+
if (parsed) collectStrings(parsed, out);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
42
|
+
out.push(String(value));
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
value.forEach((item) => collectStrings(item, out));
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === 'object') {
|
|
50
|
+
for (const key of ['message', 'error', 'type', 'code', 'status', 'statusCode', 'details', 'body']) {
|
|
51
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) collectStrings(value[key], out);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractStatus(err) {
|
|
58
|
+
const candidates = [
|
|
59
|
+
err?.status,
|
|
60
|
+
err?.statusCode,
|
|
61
|
+
err?.response?.status,
|
|
62
|
+
err?.response?.statusCode,
|
|
63
|
+
err?.error?.status,
|
|
64
|
+
err?.error?.statusCode,
|
|
65
|
+
];
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
const n = Number(candidate);
|
|
68
|
+
if (Number.isFinite(n) && n >= 100 && n < 600) return n;
|
|
69
|
+
}
|
|
70
|
+
const message = String(err?.message || err || '');
|
|
71
|
+
const m = message.match(/\b(?:api error|http|status)\s*[:#]?\s*(\d{3})\b/i);
|
|
72
|
+
return m ? Number(m[1]) : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function extractRetryAfter(err) {
|
|
76
|
+
const headers = err?.headers || err?.response?.headers;
|
|
77
|
+
if (!headers) return null;
|
|
78
|
+
const get = typeof headers.get === 'function'
|
|
79
|
+
? (name) => headers.get(name)
|
|
80
|
+
: (name) => headers[name] || headers[name.toLowerCase()];
|
|
81
|
+
const retryAfterMs = get('retry-after-ms');
|
|
82
|
+
if (retryAfterMs && !Number.isNaN(Number(retryAfterMs))) return `${Math.round(Number(retryAfterMs) / 1000)}s`;
|
|
83
|
+
const retryAfter = get('retry-after');
|
|
84
|
+
if (!retryAfter) return null;
|
|
85
|
+
const seconds = Number(retryAfter);
|
|
86
|
+
if (!Number.isNaN(seconds)) return `${Math.round(seconds)}s`;
|
|
87
|
+
const date = Date.parse(retryAfter);
|
|
88
|
+
if (!Number.isNaN(date)) {
|
|
89
|
+
const delta = Math.max(0, date - Date.now());
|
|
90
|
+
return `${Math.ceil(delta / 1000)}s`;
|
|
91
|
+
}
|
|
92
|
+
return String(retryAfter).slice(0, 80);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildRawMessage(err) {
|
|
96
|
+
const pieces = [];
|
|
97
|
+
collectStrings(err?.responseBody, pieces);
|
|
98
|
+
collectStrings(err?.body, pieces);
|
|
99
|
+
collectStrings(err?.error, pieces);
|
|
100
|
+
collectStrings(err?.cause, pieces);
|
|
101
|
+
collectStrings(err?.message || err, pieces);
|
|
102
|
+
const unique = [...new Set(pieces.map((part) => String(part || '').trim()).filter(Boolean))];
|
|
103
|
+
return redactSecrets(unique.join('\n'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function classifyType(status, lower) {
|
|
107
|
+
if (/insufficient[_ -]?quota|quota exceeded|quota_exceeded|resource[_ -]?exhausted|billing|credit|prepayment credits are depleted/i.test(lower)) return 'quota_exceeded';
|
|
108
|
+
if (status === 429 || /rate[_ -]?limit|too many requests|too_many_requests|rate_limit/i.test(lower)) return 'rate_limited';
|
|
109
|
+
if (status === 401 || status === 403 || /invalid[_ -]?(api[_ -]?)?key|unauthori[sz]ed|forbidden|authentication|auth token|token refresh failed|permission denied/i.test(lower)) return 'auth_error';
|
|
110
|
+
if (status === 404 || /model .*not found|not_found_error|model_not_found|does not exist|unknown model/i.test(lower)) return 'model_unavailable';
|
|
111
|
+
if (/context length exceeded|context_length_exceeded|maximum context|token limit/i.test(lower)) return 'context_window';
|
|
112
|
+
if (status === 408 || /timeout|timed out|etimedout|aborterror|aborted/i.test(lower)) return 'timeout';
|
|
113
|
+
if (status === 529 || status === 503 || /overloaded|service unavailable|temporarily unavailable|unavailable/i.test(lower)) return 'provider_unavailable';
|
|
114
|
+
if (/fetch failed|network|eai_again|enotfound|econnreset|socket hang up|connection refused|econnrefused/i.test(lower)) return 'network';
|
|
115
|
+
if (status && status >= 500) return 'provider_unavailable';
|
|
116
|
+
if (status && status >= 400) return 'provider_rejected';
|
|
117
|
+
return 'provider_error';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function messageForType(type) {
|
|
121
|
+
switch (type) {
|
|
122
|
+
case 'rate_limited':
|
|
123
|
+
return {
|
|
124
|
+
title: 'AI provider rate limited',
|
|
125
|
+
body: 'The provider rejected the request because it is rate limited. Wait and retry, or switch provider/model in Setup.',
|
|
126
|
+
};
|
|
127
|
+
case 'quota_exceeded':
|
|
128
|
+
return {
|
|
129
|
+
title: 'AI provider quota exhausted',
|
|
130
|
+
body: 'The provider rejected the request because the account quota or credits are exhausted. Update billing or switch provider/model in Setup.',
|
|
131
|
+
};
|
|
132
|
+
case 'auth_error':
|
|
133
|
+
return {
|
|
134
|
+
title: 'AI provider authentication failed',
|
|
135
|
+
body: 'The provider rejected the configured credential. Update the API key/token in Setup, then retry.',
|
|
136
|
+
};
|
|
137
|
+
case 'model_unavailable':
|
|
138
|
+
return {
|
|
139
|
+
title: 'AI model unavailable',
|
|
140
|
+
body: 'The selected model is not available from the configured provider. Pick another model in Setup, then retry.',
|
|
141
|
+
};
|
|
142
|
+
case 'context_window':
|
|
143
|
+
return {
|
|
144
|
+
title: 'AI request exceeded context',
|
|
145
|
+
body: 'The provider rejected the request because the conversation or attachments exceed the model context window. Shorten the prompt or switch to a larger-context model.',
|
|
146
|
+
};
|
|
147
|
+
case 'timeout':
|
|
148
|
+
return {
|
|
149
|
+
title: 'AI provider timed out',
|
|
150
|
+
body: 'The provider did not answer before the timeout. Retry, or switch provider/model if this keeps happening.',
|
|
151
|
+
};
|
|
152
|
+
case 'network':
|
|
153
|
+
return {
|
|
154
|
+
title: 'AI provider network error',
|
|
155
|
+
body: 'Wall-E could not reach the provider endpoint. Check connectivity, base URL, or local provider status, then retry.',
|
|
156
|
+
};
|
|
157
|
+
case 'provider_unavailable':
|
|
158
|
+
return {
|
|
159
|
+
title: 'AI provider unavailable',
|
|
160
|
+
body: 'The provider is currently unavailable or overloaded. Retry later, or switch provider/model in Setup.',
|
|
161
|
+
};
|
|
162
|
+
case 'provider_unavailable_local':
|
|
163
|
+
return {
|
|
164
|
+
title: 'No AI provider available',
|
|
165
|
+
body: 'Providers are configured, but every provider is currently marked unhealthy. Check Setup or wait for health checks to recover.',
|
|
166
|
+
};
|
|
167
|
+
default:
|
|
168
|
+
return {
|
|
169
|
+
title: 'AI provider failed',
|
|
170
|
+
body: 'Wall-E could not get a response from the configured provider. Review the raw provider error, then retry or switch provider/model in Setup.',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function classifyProviderError(err, context = {}) {
|
|
176
|
+
if (err?.providerError && typeof err.providerError === 'object') return err.providerError;
|
|
177
|
+
const status = extractStatus(err);
|
|
178
|
+
const rawMessage = buildRawMessage(err);
|
|
179
|
+
const lower = rawMessage.toLowerCase();
|
|
180
|
+
const type = context.type || classifyType(status, lower);
|
|
181
|
+
const copy = messageForType(type);
|
|
182
|
+
const provider = context.provider || err?.provider || err?.providerType || null;
|
|
183
|
+
const model = context.model || err?.model || null;
|
|
184
|
+
const providerName = providerLabel(provider);
|
|
185
|
+
const modelSuffix = model ? ` (${model})` : '';
|
|
186
|
+
const retryAfter = extractRetryAfter(err);
|
|
187
|
+
const retrySuffix = retryAfter ? ` Retry after ${retryAfter}.` : '';
|
|
188
|
+
return {
|
|
189
|
+
code: 'AI_PROVIDER_ERROR',
|
|
190
|
+
type,
|
|
191
|
+
severity: type === 'context_window' ? 'warning' : 'error',
|
|
192
|
+
title: copy.title,
|
|
193
|
+
message: copy.body,
|
|
194
|
+
userMessage: `${providerName}${modelSuffix}: ${copy.body}${retrySuffix}`,
|
|
195
|
+
rawMessage,
|
|
196
|
+
status,
|
|
197
|
+
provider,
|
|
198
|
+
model,
|
|
199
|
+
retryAfter,
|
|
200
|
+
actionLabel: 'Open Setup',
|
|
201
|
+
actionUrl: '/setup.html',
|
|
202
|
+
createdAt: new Date().toISOString(),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function decorateProviderError(err, context = {}) {
|
|
207
|
+
const base = err instanceof Error ? err : new Error(String(err || 'AI provider failed'));
|
|
208
|
+
base.providerError = classifyProviderError(base, context);
|
|
209
|
+
base.code = 'AI_PROVIDER_ERROR';
|
|
210
|
+
if (base.providerError.status && !base.status) base.status = base.providerError.status;
|
|
211
|
+
return base;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function unavailableProviderError(providers = [], context = {}) {
|
|
215
|
+
const details = providers
|
|
216
|
+
.map((provider) => {
|
|
217
|
+
const label = provider.providerType || provider.providerId || 'provider';
|
|
218
|
+
return `${label}: ${provider.lastError || provider.status || 'unhealthy'}`;
|
|
219
|
+
})
|
|
220
|
+
.join('\n');
|
|
221
|
+
const err = new Error(details || 'No configured AI provider is currently available');
|
|
222
|
+
err.status = 503;
|
|
223
|
+
return decorateProviderError(err, {
|
|
224
|
+
...context,
|
|
225
|
+
type: 'provider_unavailable_local',
|
|
226
|
+
provider: context.provider || providers[0]?.providerType || null,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function toApiPayload(err, context = {}) {
|
|
231
|
+
const providerError = classifyProviderError(err, context);
|
|
232
|
+
return {
|
|
233
|
+
error: providerError.title,
|
|
234
|
+
message: providerError.userMessage,
|
|
235
|
+
code: 'AI_PROVIDER_ERROR',
|
|
236
|
+
providerError,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function recordProviderFailureAlert(providerError, brain) {
|
|
241
|
+
if (!providerError) return { alerted: false };
|
|
242
|
+
try {
|
|
243
|
+
const planner = require('../skills/skill-planner');
|
|
244
|
+
planner.addServiceAlert({
|
|
245
|
+
service: 'ai_provider',
|
|
246
|
+
type: `ai_provider:${providerError.provider || 'default'}:${providerError.type}`,
|
|
247
|
+
title: providerError.title,
|
|
248
|
+
severity: providerError.severity || 'error',
|
|
249
|
+
provider: providerError.provider || null,
|
|
250
|
+
model: providerError.model || null,
|
|
251
|
+
message: `${providerError.title}: ${providerError.userMessage}`,
|
|
252
|
+
action_url: providerError.actionUrl || '/setup.html',
|
|
253
|
+
provider_error: providerError,
|
|
254
|
+
});
|
|
255
|
+
return { alerted: true };
|
|
256
|
+
} catch (err) {
|
|
257
|
+
return { alerted: false, error: err.message };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
classifyProviderError,
|
|
263
|
+
decorateProviderError,
|
|
264
|
+
providerLabel,
|
|
265
|
+
recordProviderFailureAlert,
|
|
266
|
+
redactSecrets,
|
|
267
|
+
toApiPayload,
|
|
268
|
+
unavailableProviderError,
|
|
269
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
let geminiToolCallSeq = 0;
|
|
4
|
+
let openAIToolCallSeq = 0;
|
|
4
5
|
|
|
5
6
|
// ============================================================
|
|
6
7
|
// Tool definition converters
|
|
@@ -63,10 +64,19 @@ function toAnthropic(tools) {
|
|
|
63
64
|
*/
|
|
64
65
|
function messagesToOpenAI(messages) {
|
|
65
66
|
const out = [];
|
|
67
|
+
const pendingToolCallIds = [];
|
|
68
|
+
|
|
66
69
|
for (const msg of messages) {
|
|
67
70
|
// Simple string content — pass through
|
|
68
71
|
if (typeof msg.content === 'string') {
|
|
69
|
-
|
|
72
|
+
const converted = { role: msg.role, content: msg.content };
|
|
73
|
+
if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
74
|
+
converted.tool_calls = normalizeOpenAIToolCalls(msg.tool_calls, pendingToolCallIds);
|
|
75
|
+
}
|
|
76
|
+
if (msg.role === 'tool') {
|
|
77
|
+
converted.tool_call_id = consumeToolCallId(msg.tool_call_id, pendingToolCallIds);
|
|
78
|
+
}
|
|
79
|
+
out.push(converted);
|
|
70
80
|
continue;
|
|
71
81
|
}
|
|
72
82
|
|
|
@@ -82,7 +92,7 @@ function messagesToOpenAI(messages) {
|
|
|
82
92
|
for (const tr of toolResults) {
|
|
83
93
|
out.push({
|
|
84
94
|
role: 'tool',
|
|
85
|
-
tool_call_id: tr.tool_use_id,
|
|
95
|
+
tool_call_id: consumeToolCallId(tr.tool_use_id || tr.id || tr.toolCallId, pendingToolCallIds),
|
|
86
96
|
content: typeof tr.content === 'string' ? tr.content : JSON.stringify(tr.content),
|
|
87
97
|
});
|
|
88
98
|
}
|
|
@@ -138,14 +148,18 @@ function messagesToOpenAI(messages) {
|
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
if (toolUses.length > 0) {
|
|
141
|
-
converted.tool_calls = toolUses.map((tu) =>
|
|
142
|
-
id
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
converted.tool_calls = toolUses.map((tu, index) => {
|
|
152
|
+
const id = tu.id || tu.tool_use_id || tu.toolCallId || synthesizeOpenAIToolCallId(index);
|
|
153
|
+
pendingToolCallIds.push(id);
|
|
154
|
+
return {
|
|
155
|
+
id,
|
|
156
|
+
type: 'function',
|
|
157
|
+
function: {
|
|
158
|
+
name: tu.name,
|
|
159
|
+
arguments: JSON.stringify(tu.input ?? {}),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
});
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
if (reasoningParts.length > 0) {
|
|
@@ -191,8 +205,8 @@ function responseFromOpenAI(resp) {
|
|
|
191
205
|
|
|
192
206
|
return {
|
|
193
207
|
content: msg.content || null,
|
|
194
|
-
toolCalls: (msg.tool_calls || []).map((tc) => ({
|
|
195
|
-
id: tc.id,
|
|
208
|
+
toolCalls: (msg.tool_calls || []).map((tc, index) => ({
|
|
209
|
+
id: tc.id || synthesizeOpenAIToolCallId(index),
|
|
196
210
|
name: tc.function.name,
|
|
197
211
|
input: safeJsonParse(tc.function.arguments),
|
|
198
212
|
})),
|
|
@@ -242,6 +256,28 @@ function safeJsonParse(str) {
|
|
|
242
256
|
}
|
|
243
257
|
}
|
|
244
258
|
|
|
259
|
+
function synthesizeOpenAIToolCallId(index = 0) {
|
|
260
|
+
openAIToolCallSeq += 1;
|
|
261
|
+
return `call_walle_${openAIToolCallSeq}_${index}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function normalizeOpenAIToolCalls(toolCalls = [], pendingToolCallIds = []) {
|
|
265
|
+
return toolCalls.map((tc, index) => {
|
|
266
|
+
const id = tc.id || synthesizeOpenAIToolCallId(index);
|
|
267
|
+
pendingToolCallIds.push(id);
|
|
268
|
+
return { ...tc, id };
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function consumeToolCallId(explicitId, pendingToolCallIds = []) {
|
|
273
|
+
if (explicitId) {
|
|
274
|
+
const pendingIndex = pendingToolCallIds.indexOf(explicitId);
|
|
275
|
+
if (pendingIndex >= 0) pendingToolCallIds.splice(pendingIndex, 1);
|
|
276
|
+
return explicitId;
|
|
277
|
+
}
|
|
278
|
+
return pendingToolCallIds.shift() || synthesizeOpenAIToolCallId(0);
|
|
279
|
+
}
|
|
280
|
+
|
|
245
281
|
module.exports = {
|
|
246
282
|
toOpenAI,
|
|
247
283
|
toGemini,
|
|
@@ -84,7 +84,8 @@ async function runBootCheck(opts = {}) {
|
|
|
84
84
|
} catch (e) {
|
|
85
85
|
return { status: 'failed', reason: 'no-client', error: e.message };
|
|
86
86
|
}
|
|
87
|
-
const
|
|
87
|
+
const { getDefaultModel, resolveCompatibleModel } = require('../llm/client');
|
|
88
|
+
const model = resolveCompatibleModel(opts.model || process.env.WALLE_MODEL || getDefaultModel(), provider.type);
|
|
88
89
|
|
|
89
90
|
const prompt = _buildBootPrompt(file.content);
|
|
90
91
|
const controller = new AbortController();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const brain = require('../brain');
|
|
4
4
|
const { buildStateSnapshot, isStateEmpty, formatSnapshot } = require('../context/state-snapshot');
|
|
5
5
|
const { canActAutonomously, getDomainConfidence } = require('../decision/confidence');
|
|
6
|
-
const { getDefaultClient } = require('../llm/client');
|
|
6
|
+
const { getDefaultClient, getDefaultModel, resolveCompatibleModel } = require('../llm/client');
|
|
7
7
|
const { v4: uuidv4 } = require('uuid');
|
|
8
8
|
const { buildDisciplineHeader } = require('./loop-prompt-discipline');
|
|
9
9
|
const { augmentSystemPrompt } = require('./loop-directives');
|
|
@@ -287,7 +287,7 @@ async function runInitiativeLoop(opts = {}) {
|
|
|
287
287
|
|
|
288
288
|
// Call Claude with a focused prompt
|
|
289
289
|
const provider = opts.client || getDefaultClient();
|
|
290
|
-
const model = opts.model || process.env.WALLE_MODEL ||
|
|
290
|
+
const model = resolveCompatibleModel(opts.model || process.env.WALLE_MODEL || getDefaultModel(), provider.type);
|
|
291
291
|
|
|
292
292
|
const controller = new AbortController();
|
|
293
293
|
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
@@ -293,53 +293,14 @@ async function executeSkill(taskId, task) {
|
|
|
293
293
|
appendLog(taskId, `Skill: ${skill.name} v${skill.version} (${skill.execution})`);
|
|
294
294
|
|
|
295
295
|
if (skill.execution === 'script') {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// Merge task-level skill_config with skill defaults
|
|
306
|
-
const skillConfig = {};
|
|
307
|
-
if (skill.config) {
|
|
308
|
-
for (const [k, v] of Object.entries(skill.config)) {
|
|
309
|
-
if (v && typeof v === 'object' && v.default !== undefined) {
|
|
310
|
-
skillConfig[k] = v.default;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
// Task overrides
|
|
315
|
-
let taskConfig = {};
|
|
316
|
-
if (task.skill_config) {
|
|
317
|
-
try {
|
|
318
|
-
taskConfig = typeof task.skill_config === 'string'
|
|
319
|
-
? JSON.parse(task.skill_config)
|
|
320
|
-
: task.skill_config;
|
|
321
|
-
} catch {}
|
|
322
|
-
}
|
|
323
|
-
Object.assign(skillConfig, taskConfig);
|
|
324
|
-
|
|
325
|
-
// For slack-backfill: pass mode/month as CLI args if configured
|
|
326
|
-
if (skillConfig.mode && !args.includes(skillConfig.mode)) {
|
|
327
|
-
// Only add mode as arg if the script expects it as argv[2]
|
|
328
|
-
// (slack-backfill uses process.argv[2] as mode)
|
|
329
|
-
if (skill.name === 'slack-backfill') {
|
|
330
|
-
args.push(skillConfig.mode);
|
|
331
|
-
if (skillConfig.month) args.push(skillConfig.month);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const scriptCmd = `node ${entryPath} ${args.join(' ')}`.trim();
|
|
336
|
-
appendLog(taskId, `Running: ${scriptCmd}`);
|
|
337
|
-
|
|
338
|
-
// Pass config as env var + checkpoint support
|
|
339
|
-
return executeScript(taskId, scriptCmd, task.checkpoint, {
|
|
340
|
-
WALL_E_SKILL_CONFIG: JSON.stringify(skillConfig),
|
|
341
|
-
WALL_E_SKILL_DIR: skill.dir,
|
|
342
|
-
WALL_E_SRC_DIR: WALL_E_DIR,
|
|
296
|
+
const { runScriptSkill } = require('../skills/script-skill-runner');
|
|
297
|
+
return runScriptSkill(skill, task, {
|
|
298
|
+
log: line => appendLog(taskId, line),
|
|
299
|
+
onCheckpoint: checkpoint => brain.updateTask(taskId, { checkpoint }),
|
|
300
|
+
onProcess: child => taskProcesses.set(taskId, child),
|
|
301
|
+
onClose: child => {
|
|
302
|
+
if (taskProcesses.get(taskId) === child) taskProcesses.delete(taskId);
|
|
303
|
+
},
|
|
343
304
|
});
|
|
344
305
|
}
|
|
345
306
|
|
|
@@ -151,6 +151,25 @@ function composePersonaBlock() {
|
|
|
151
151
|
return sections.length ? sections.join('\n\n') : '';
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
function getWorkspacePersonaSections({ recipient = 'agent' } = {}) {
|
|
155
|
+
const sections = [];
|
|
156
|
+
for (const name of ['IDENTITY', 'SOUL', 'USER', 'TOOLS', 'AGENTS']) {
|
|
157
|
+
const content = readWorkspacePrompt(name);
|
|
158
|
+
if (!content || isContentEffectivelyEmpty(content)) continue;
|
|
159
|
+
sections.push({
|
|
160
|
+
id: `workspace-${name.toLowerCase()}`,
|
|
161
|
+
source: `workspace:${name}.md`,
|
|
162
|
+
content: content.trim(),
|
|
163
|
+
recipient,
|
|
164
|
+
cacheable: true,
|
|
165
|
+
visibility: 'llm',
|
|
166
|
+
mutableByHooks: true,
|
|
167
|
+
redactionPolicy: name === 'USER' ? 'standard' : 'secrets',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return sections;
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
/** Test helper. */
|
|
155
174
|
function clearWorkspacePromptsCache() {
|
|
156
175
|
_cache.clear();
|
|
@@ -204,6 +223,7 @@ module.exports = {
|
|
|
204
223
|
readWorkspacePrompt,
|
|
205
224
|
isContentEffectivelyEmpty,
|
|
206
225
|
composePersonaBlock,
|
|
226
|
+
getWorkspacePersonaSections,
|
|
207
227
|
initWorkspaceFromTemplates,
|
|
208
228
|
clearWorkspacePromptsCache,
|
|
209
229
|
_resolveWorkspacePath,
|