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
|
@@ -16,6 +16,7 @@ const path = require('path');
|
|
|
16
16
|
const os = require('os');
|
|
17
17
|
const { verifyCandidateForTab, readFirstUserMessage, wordOverlap } = require('./session-verify.js');
|
|
18
18
|
const { verifyLineage } = require('./session-lineage.js');
|
|
19
|
+
const claudeDesktopSessions = require('./claude-desktop-sessions');
|
|
19
20
|
|
|
20
21
|
const HOSTNAME = os.hostname();
|
|
21
22
|
const fsp = fs.promises;
|
|
@@ -133,8 +134,10 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
133
134
|
for (let index = 0; index < files.length; index++) {
|
|
134
135
|
const { filePath, projectPath, projectEntry } = files[index];
|
|
135
136
|
try {
|
|
136
|
-
const
|
|
137
|
-
const
|
|
137
|
+
const virtualSession = claudeDesktopSessions.parseVirtualSessionPath(filePath);
|
|
138
|
+
const sessionId = virtualSession ? virtualSession.sessionId : path.basename(filePath).replace(/\.jsonl(\.bak)?$/, '');
|
|
139
|
+
const statPath = claudeDesktopSessions.sourcePathForStat(filePath);
|
|
140
|
+
const stat = await fsp.stat(statPath);
|
|
138
141
|
const modifiedAt = stat.mtime.toISOString();
|
|
139
142
|
const existing = existingMap.get(sessionId);
|
|
140
143
|
|
|
@@ -174,6 +177,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
174
177
|
fileSize: parsed.fileSize,
|
|
175
178
|
modifiedAt: parsed.modifiedAt,
|
|
176
179
|
firstMessage: parsed.firstMessage || '',
|
|
180
|
+
createdAt: parsed.timestamp || '',
|
|
177
181
|
provider: parsed.agent || 'claude',
|
|
178
182
|
model: parsed.modelId || '',
|
|
179
183
|
gitBranch: parsed.gitBranch || '',
|
|
@@ -191,6 +195,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
191
195
|
fileSize: parsed.fileSize,
|
|
192
196
|
modifiedAt: parsed.modifiedAt,
|
|
193
197
|
firstMessage: parsed.firstMessage || '',
|
|
198
|
+
createdAt: parsed.timestamp || '',
|
|
194
199
|
provider: parsed.agent || 'claude',
|
|
195
200
|
model: parsed.modelId || '',
|
|
196
201
|
gitBranch: parsed.gitBranch || '',
|
|
@@ -234,7 +239,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
234
239
|
const rows = db.prepare(
|
|
235
240
|
"SELECT id, jsonl_path FROM sessions WHERE jsonl_path != '' AND jsonl_path IS NOT NULL AND (user_msg_count = 0 OR user_msg_count IS NULL)"
|
|
236
241
|
).all();
|
|
237
|
-
const toDelete = rows.filter(r => !fs.existsSync(r.jsonl_path)).map(r => r.id);
|
|
242
|
+
const toDelete = rows.filter(r => !fs.existsSync(claudeDesktopSessions.sourcePathForStat(r.jsonl_path))).map(r => r.id);
|
|
238
243
|
if (toDelete.length > 0) {
|
|
239
244
|
const txn = db.transaction(() => {
|
|
240
245
|
const stmt = db.prepare("DELETE FROM sessions WHERE id = ?");
|
|
@@ -249,7 +254,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
249
254
|
const rows = db.prepare(
|
|
250
255
|
"SELECT a.agent_session_id, a.jsonl_path, a.ctm_session_id FROM agent_sessions a WHERE a.jsonl_path != '' AND a.jsonl_path IS NOT NULL"
|
|
251
256
|
).all();
|
|
252
|
-
const missingFiles = rows.filter(r => !fs.existsSync(r.jsonl_path));
|
|
257
|
+
const missingFiles = rows.filter(r => !fs.existsSync(claudeDesktopSessions.sourcePathForStat(r.jsonl_path)));
|
|
253
258
|
if (missingFiles.length > 0) {
|
|
254
259
|
const txn = db.transaction(() => {
|
|
255
260
|
const delAgent = db.prepare("DELETE FROM agent_sessions WHERE agent_session_id = ?");
|
|
@@ -296,7 +301,7 @@ function registerSessionJobs(scheduler, deps) {
|
|
|
296
301
|
try {
|
|
297
302
|
if (!row.jsonl_path) continue;
|
|
298
303
|
let stat;
|
|
299
|
-
try { stat = await fsp.stat(row.jsonl_path); } catch { continue; }
|
|
304
|
+
try { stat = await fsp.stat(claudeDesktopSessions.sourcePathForStat(row.jsonl_path)); } catch { continue; }
|
|
300
305
|
const newMtime = stat.mtime.toISOString();
|
|
301
306
|
if (row.file_size !== stat.size || row.modified_at !== newMtime) {
|
|
302
307
|
updateMetadata.run(stat.size, newMtime, row.agent_session_id);
|
|
@@ -11,6 +11,7 @@ const PTY_ACTIVE_WINDOW_MS = 5000;
|
|
|
11
11
|
const JSONL_ACTIVE_WINDOW_MS = 10000;
|
|
12
12
|
const IDLE_THRESHOLD_MS = 30000;
|
|
13
13
|
const SUMMARY_DEBOUNCE_MS = 2000;
|
|
14
|
+
const SUMMARY_RETRY_COOLDOWN_MS = 60000;
|
|
14
15
|
const PROMPT_CACHE_TEXT_LIMIT = 4000;
|
|
15
16
|
const SUMMARY_TEXT_LIMIT = 220;
|
|
16
17
|
const PROGRESS_BULLET_LIMIT = 3;
|
|
@@ -149,6 +150,35 @@ function truncateText(text, limit) {
|
|
|
149
150
|
return t.slice(0, Math.max(0, limit - 3)).trimEnd() + '...';
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
function roleForEventType(type) {
|
|
154
|
+
if (type === 'user' || type === 'assistant') return type;
|
|
155
|
+
if (type === 'tool_result') return 'tool';
|
|
156
|
+
if (type === 'summary') return 'system';
|
|
157
|
+
return 'event';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function captureKindForEventType(type) {
|
|
161
|
+
if (type === 'user' || type === 'assistant') return 'message';
|
|
162
|
+
if (type === 'tool_result') return 'tool_result';
|
|
163
|
+
if (type === 'summary') return 'summary';
|
|
164
|
+
return 'event';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function decorateStreamEvent(evt, source = 'jsonl') {
|
|
168
|
+
if (!evt || typeof evt !== 'object') return evt;
|
|
169
|
+
const out = {
|
|
170
|
+
...evt,
|
|
171
|
+
schemaVersion: 1,
|
|
172
|
+
eventId: evt.eventId || `${evt.sessionId || 'session'}:${evt.seq ?? 'x'}:${evt.type || 'event'}`,
|
|
173
|
+
captureKind: evt.captureKind || captureKindForEventType(evt.type),
|
|
174
|
+
role: evt.role || roleForEventType(evt.type),
|
|
175
|
+
source: evt.source || source,
|
|
176
|
+
agentSessionId: evt.agentSessionId || evt.sessionId,
|
|
177
|
+
};
|
|
178
|
+
if (out.text == null && out.data && typeof out.data.text === 'string') out.text = out.data.text;
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
152
182
|
// --- SessionStream: EventEmitter-based structured event stream ---
|
|
153
183
|
|
|
154
184
|
class SessionStream extends EventEmitter {
|
|
@@ -158,12 +188,13 @@ class SessionStream extends EventEmitter {
|
|
|
158
188
|
* @param {Map} opts.sessions - CTM in-memory sessions map
|
|
159
189
|
* @param {number} [opts.ringSize=200]
|
|
160
190
|
*/
|
|
161
|
-
constructor({ jsonlWatcher, sessions, ringSize = DEFAULT_RING_SIZE, dbModule } = {}) {
|
|
191
|
+
constructor({ jsonlWatcher, sessions, ringSize = DEFAULT_RING_SIZE, dbModule, summaryProvider } = {}) {
|
|
162
192
|
super();
|
|
163
193
|
this._jsonlWatcher = jsonlWatcher;
|
|
164
194
|
this._sessions = sessions;
|
|
165
195
|
this._ringSize = ringSize;
|
|
166
196
|
this._dbModule = dbModule || null;
|
|
197
|
+
this._summaryProvider = typeof summaryProvider === 'function' ? summaryProvider : null;
|
|
167
198
|
this._tailer = new JsonlTailer();
|
|
168
199
|
this._stopped = false;
|
|
169
200
|
|
|
@@ -285,6 +316,9 @@ class SessionStream extends EventEmitter {
|
|
|
285
316
|
const summary = summaryRecord?.text || (st.cachedSummary?.model === 'fallback' ? st.cachedSummary.text : null);
|
|
286
317
|
const intent = this._buildIntent(summaryRecord, displayPromptEntry, lastPromptEntry);
|
|
287
318
|
const progress = this._buildProgress(st, intent);
|
|
319
|
+
if (!summaryRecord && st.cachedSummary?.model === 'fallback') {
|
|
320
|
+
this._maybeRetryFallbackSummary(sessionId, st);
|
|
321
|
+
}
|
|
288
322
|
|
|
289
323
|
return {
|
|
290
324
|
sessionId,
|
|
@@ -374,12 +408,14 @@ class SessionStream extends EventEmitter {
|
|
|
374
408
|
if (jsonlPath) {
|
|
375
409
|
this._readAndProcess(agentSessionId, jsonlPath, { drain: true, rememberStat: true }).catch(() => {});
|
|
376
410
|
}
|
|
411
|
+
this.emit('link', { ctmSessionId, agentSessionId, jsonlPath });
|
|
377
412
|
}
|
|
378
413
|
|
|
379
414
|
/** Clean up on session exit */
|
|
380
415
|
removeSession(agentSessionId) {
|
|
381
416
|
const st = this._state.get(agentSessionId);
|
|
382
417
|
if (!st) return;
|
|
418
|
+
const ctmSessionId = st.ctmSessionId;
|
|
383
419
|
// Emit final status
|
|
384
420
|
const oldStatus = this._computeStatus(st);
|
|
385
421
|
if (oldStatus !== 'exited') {
|
|
@@ -391,6 +427,7 @@ class SessionStream extends EventEmitter {
|
|
|
391
427
|
const timer = this._summaryTimers.get(agentSessionId);
|
|
392
428
|
if (timer) { clearTimeout(timer); this._summaryTimers.delete(agentSessionId); }
|
|
393
429
|
this._state.delete(agentSessionId);
|
|
430
|
+
this.emit('unlink', { ctmSessionId, agentSessionId });
|
|
394
431
|
}
|
|
395
432
|
|
|
396
433
|
/** Get the set of actively tracked session IDs */
|
|
@@ -444,6 +481,7 @@ class SessionStream extends EventEmitter {
|
|
|
444
481
|
lastJsonlActivity: 0,
|
|
445
482
|
lastAssistantParent: null,
|
|
446
483
|
cachedSummary: null,
|
|
484
|
+
lastSummaryAttempt: 0,
|
|
447
485
|
lastKnownSize: 0,
|
|
448
486
|
lastKnownMtime: 0,
|
|
449
487
|
};
|
|
@@ -514,7 +552,7 @@ class SessionStream extends EventEmitter {
|
|
|
514
552
|
let text = extractText(content);
|
|
515
553
|
if (text.length > MAX_TEXT_LEN) text = text.slice(0, MAX_TEXT_LEN) + `...truncated (${text.length} chars)`;
|
|
516
554
|
|
|
517
|
-
const evt = {
|
|
555
|
+
const evt = decorateStreamEvent({
|
|
518
556
|
type: 'user',
|
|
519
557
|
sessionId: agentSessionId,
|
|
520
558
|
ctmSessionId: st.ctmSessionId,
|
|
@@ -527,7 +565,7 @@ class SessionStream extends EventEmitter {
|
|
|
527
565
|
gitBranch: entry.gitBranch || undefined,
|
|
528
566
|
parentUuid: entry.uuid || undefined,
|
|
529
567
|
},
|
|
530
|
-
};
|
|
568
|
+
}, 'claude-jsonl');
|
|
531
569
|
st.ring.push(evt);
|
|
532
570
|
if (st.ring.length > this._ringSize) st.ring.shift();
|
|
533
571
|
this._markJsonlActivity(st, timestamp);
|
|
@@ -550,7 +588,7 @@ class SessionStream extends EventEmitter {
|
|
|
550
588
|
// Find and replace last assistant event with this parentUuid
|
|
551
589
|
for (let i = st.ring.length - 1; i >= 0; i--) {
|
|
552
590
|
if (st.ring[i].type === 'assistant' && st.ring[i].data.parentUuid === parentUuid) {
|
|
553
|
-
st.ring[i] = {
|
|
591
|
+
st.ring[i] = decorateStreamEvent({
|
|
554
592
|
...st.ring[i],
|
|
555
593
|
timestamp,
|
|
556
594
|
data: {
|
|
@@ -560,7 +598,7 @@ class SessionStream extends EventEmitter {
|
|
|
560
598
|
toolUses: toolUses.length ? toolUses : undefined,
|
|
561
599
|
parentUuid,
|
|
562
600
|
},
|
|
563
|
-
};
|
|
601
|
+
}, 'claude-jsonl');
|
|
564
602
|
this._markJsonlActivity(st, timestamp);
|
|
565
603
|
this._emitEvent(agentSessionId, { ...st.ring[i], _update: true }, 'event:update');
|
|
566
604
|
return;
|
|
@@ -569,7 +607,7 @@ class SessionStream extends EventEmitter {
|
|
|
569
607
|
}
|
|
570
608
|
|
|
571
609
|
st.lastAssistantParent = parentUuid;
|
|
572
|
-
const evt = {
|
|
610
|
+
const evt = decorateStreamEvent({
|
|
573
611
|
type: 'assistant',
|
|
574
612
|
sessionId: agentSessionId,
|
|
575
613
|
ctmSessionId: st.ctmSessionId,
|
|
@@ -582,7 +620,7 @@ class SessionStream extends EventEmitter {
|
|
|
582
620
|
toolUses: toolUses.length ? toolUses : undefined,
|
|
583
621
|
parentUuid,
|
|
584
622
|
},
|
|
585
|
-
};
|
|
623
|
+
}, 'claude-jsonl');
|
|
586
624
|
st.ring.push(evt);
|
|
587
625
|
if (st.ring.length > this._ringSize) st.ring.shift();
|
|
588
626
|
|
|
@@ -601,7 +639,7 @@ class SessionStream extends EventEmitter {
|
|
|
601
639
|
let text = extractText(content || '');
|
|
602
640
|
if (text.length > MAX_TEXT_LEN) text = text.slice(0, MAX_TEXT_LEN) + `...truncated (${text.length} chars)`;
|
|
603
641
|
|
|
604
|
-
const evt = {
|
|
642
|
+
const evt = decorateStreamEvent({
|
|
605
643
|
type: 'tool_result',
|
|
606
644
|
sessionId: agentSessionId,
|
|
607
645
|
ctmSessionId: st.ctmSessionId,
|
|
@@ -611,7 +649,7 @@ class SessionStream extends EventEmitter {
|
|
|
611
649
|
text,
|
|
612
650
|
contentBlocks: stripBase64Blobs(Array.isArray(content) ? content : []),
|
|
613
651
|
},
|
|
614
|
-
};
|
|
652
|
+
}, 'claude-jsonl');
|
|
615
653
|
st.ring.push(evt);
|
|
616
654
|
if (st.ring.length > this._ringSize) st.ring.shift();
|
|
617
655
|
this._markJsonlActivity(st, timestamp);
|
|
@@ -628,7 +666,7 @@ class SessionStream extends EventEmitter {
|
|
|
628
666
|
if (!key || st.codexUserSeen.has(key)) return true;
|
|
629
667
|
st.codexUserSeen.add(key);
|
|
630
668
|
const timestamp = eventTimestampMs({ timestamp: msg.timestamp });
|
|
631
|
-
const evt = {
|
|
669
|
+
const evt = decorateStreamEvent({
|
|
632
670
|
type: 'user',
|
|
633
671
|
sessionId: agentSessionId,
|
|
634
672
|
ctmSessionId: st.ctmSessionId,
|
|
@@ -638,7 +676,7 @@ class SessionStream extends EventEmitter {
|
|
|
638
676
|
text: msg.text.length > MAX_TEXT_LEN ? msg.text.slice(0, MAX_TEXT_LEN) + `...truncated (${msg.text.length} chars)` : msg.text,
|
|
639
677
|
contentBlocks: [{ type: 'text', text: msg.text }],
|
|
640
678
|
},
|
|
641
|
-
};
|
|
679
|
+
}, 'codex-jsonl');
|
|
642
680
|
st.ring.push(evt);
|
|
643
681
|
if (st.ring.length > this._ringSize) st.ring.shift();
|
|
644
682
|
this._markJsonlActivity(st, timestamp);
|
|
@@ -651,14 +689,14 @@ class SessionStream extends EventEmitter {
|
|
|
651
689
|
const timestamp = eventTimestampMs({ timestamp: msg.timestamp });
|
|
652
690
|
const text = msg.text.length > MAX_TEXT_LEN ? msg.text.slice(0, MAX_TEXT_LEN) + `...truncated (${msg.text.length} chars)` : msg.text;
|
|
653
691
|
const oldStatus = this._computeStatus(st);
|
|
654
|
-
const evt = {
|
|
692
|
+
const evt = decorateStreamEvent({
|
|
655
693
|
type: 'assistant',
|
|
656
694
|
sessionId: agentSessionId,
|
|
657
695
|
ctmSessionId: st.ctmSessionId,
|
|
658
696
|
timestamp,
|
|
659
697
|
seq: st.seq++,
|
|
660
698
|
data: { text, contentBlocks: [{ type: 'text', text }] },
|
|
661
|
-
};
|
|
699
|
+
}, 'codex-jsonl');
|
|
662
700
|
st.ring.push(evt);
|
|
663
701
|
if (st.ring.length > this._ringSize) st.ring.shift();
|
|
664
702
|
this._markJsonlActivity(st, timestamp);
|
|
@@ -1007,7 +1045,17 @@ class SessionStream extends EventEmitter {
|
|
|
1007
1045
|
}, SUMMARY_DEBOUNCE_MS));
|
|
1008
1046
|
}
|
|
1009
1047
|
|
|
1048
|
+
_maybeRetryFallbackSummary(agentSessionId, st) {
|
|
1049
|
+
if (!this._summaryProvider || !st) return;
|
|
1050
|
+
if (this._summaryTimers.has(agentSessionId)) return;
|
|
1051
|
+
const now = Date.now();
|
|
1052
|
+
const lastAttempt = st.lastSummaryAttempt || st.cachedSummary?.timestamp || 0;
|
|
1053
|
+
if (lastAttempt && now - lastAttempt < SUMMARY_RETRY_COOLDOWN_MS) return;
|
|
1054
|
+
this._debounceSummary(agentSessionId, st);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1010
1057
|
async _generateSummary(agentSessionId, st) {
|
|
1058
|
+
st.lastSummaryAttempt = Date.now();
|
|
1011
1059
|
// Use userPromptCache (survives ring buffer churn from tool_result-only events)
|
|
1012
1060
|
const userPrompts = st.userPromptCache.map((entry) => this._promptText(entry)).filter(Boolean);
|
|
1013
1061
|
if (userPrompts.length === 0) return;
|
|
@@ -1015,11 +1063,18 @@ class SessionStream extends EventEmitter {
|
|
|
1015
1063
|
const turnsText = userPrompts.map((t, i) => `Prompt ${i + 1}: ${t}`).join('\n');
|
|
1016
1064
|
const sysPrompt = 'Summarize what the user is working on based on their prompts. 10-15 words. Return ONLY the summary, no quotes or prefix.';
|
|
1017
1065
|
|
|
1018
|
-
// Tier
|
|
1066
|
+
// Tier 0: configured Wall-E provider (DeepSeek/OpenAI/Claude/Gemini/etc.).
|
|
1019
1067
|
let summaryText = '';
|
|
1020
1068
|
let usedModel = null;
|
|
1021
|
-
|
|
1022
|
-
|
|
1069
|
+
|
|
1070
|
+
const configured = await this._tryConfiguredSummary(turnsText, sysPrompt);
|
|
1071
|
+
if (configured) { summaryText = configured.text; usedModel = configured.model; }
|
|
1072
|
+
|
|
1073
|
+
// Tier 1: legacy cloud Anthropic (when API key is present)
|
|
1074
|
+
if (!summaryText) {
|
|
1075
|
+
const cloud = await this._tryCloudSummary(turnsText, sysPrompt);
|
|
1076
|
+
if (cloud) { summaryText = cloud.text; usedModel = cloud.model; }
|
|
1077
|
+
}
|
|
1023
1078
|
|
|
1024
1079
|
// Tier 2: local Ollama fallback (when cloud is unavailable or failed)
|
|
1025
1080
|
if (!summaryText) {
|
|
@@ -1034,14 +1089,14 @@ class SessionStream extends EventEmitter {
|
|
|
1034
1089
|
model: usedModel,
|
|
1035
1090
|
timestamp: Date.now(),
|
|
1036
1091
|
};
|
|
1037
|
-
const summaryEvt = {
|
|
1092
|
+
const summaryEvt = decorateStreamEvent({
|
|
1038
1093
|
type: 'summary',
|
|
1039
1094
|
sessionId: agentSessionId,
|
|
1040
1095
|
ctmSessionId: st.ctmSessionId,
|
|
1041
1096
|
timestamp: Date.now(),
|
|
1042
1097
|
seq: st.seq++,
|
|
1043
1098
|
data: st.cachedSummary,
|
|
1044
|
-
};
|
|
1099
|
+
}, 'summary');
|
|
1045
1100
|
st.ring.push(summaryEvt);
|
|
1046
1101
|
if (st.ring.length > this._ringSize) st.ring.shift();
|
|
1047
1102
|
this._emitEvent(agentSessionId, summaryEvt);
|
|
@@ -1051,6 +1106,19 @@ class SessionStream extends EventEmitter {
|
|
|
1051
1106
|
}
|
|
1052
1107
|
}
|
|
1053
1108
|
|
|
1109
|
+
/** Tier 0: CTM-injected Wall-E provider. Returns { text, model } or null. */
|
|
1110
|
+
async _tryConfiguredSummary(turnsText, sysPrompt) {
|
|
1111
|
+
if (!this._summaryProvider) return null;
|
|
1112
|
+
try {
|
|
1113
|
+
const result = await this._summaryProvider({ turnsText, sysPrompt });
|
|
1114
|
+
const text = this._sanitizeSummary(result?.text || '');
|
|
1115
|
+
return text ? { text, model: result?.model || 'configured' } : null;
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
console.warn('[session-stream] configured summary failed:', e.message);
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1054
1122
|
/** Tier 1: Anthropic cloud. Returns { text, model } or null. */
|
|
1055
1123
|
async _tryCloudSummary(turnsText, sysPrompt) {
|
|
1056
1124
|
const apiKey = process.env.ANTHROPIC_API_KEY || '';
|
|
@@ -1186,4 +1254,4 @@ SessionStream._ACK_TOKENS = new Set([
|
|
|
1186
1254
|
'a', 'an', 'the', 'to', 'main', 'that', 'this',
|
|
1187
1255
|
]);
|
|
1188
1256
|
|
|
1189
|
-
module.exports = { SessionStream, JsonlTailer };
|
|
1257
|
+
module.exports = { SessionStream, JsonlTailer, decorateStreamEvent };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SETUP_PROVIDER_TYPES = Object.freeze([
|
|
4
|
+
'anthropic',
|
|
5
|
+
'openai',
|
|
6
|
+
'google',
|
|
7
|
+
'ollama',
|
|
8
|
+
'deepseek',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const SETUP_PROVIDER_NAMES = Object.freeze({
|
|
12
|
+
anthropic: 'Anthropic',
|
|
13
|
+
openai: 'OpenAI',
|
|
14
|
+
google: 'Google Gemini',
|
|
15
|
+
ollama: 'Ollama (Local)',
|
|
16
|
+
deepseek: 'DeepSeek',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const SETUP_PROVIDER_ENV_KEYS = Object.freeze({
|
|
20
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
21
|
+
openai: 'OPENAI_API_KEY',
|
|
22
|
+
google: 'GOOGLE_API_KEY',
|
|
23
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const SETUP_LOCAL_PROVIDER_TYPES = Object.freeze([
|
|
27
|
+
'ollama',
|
|
28
|
+
'mlx',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const SETUP_KEYLESS_AUTH_METHODS = Object.freeze({
|
|
32
|
+
anthropic: ['claude_cli', 'oauth_proxy', 'devbox'],
|
|
33
|
+
openai: ['codex_cli'],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function sanitizeSetupProviderType(value) {
|
|
37
|
+
return typeof value === 'string'
|
|
38
|
+
? value.toLowerCase().replace(/[^a-z]/g, '').slice(0, 20)
|
|
39
|
+
: '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sanitizeSetupModel(value) {
|
|
43
|
+
return typeof value === 'string'
|
|
44
|
+
? value.replace(/[\r\n\s]/g, '').slice(0, 100)
|
|
45
|
+
: '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isSetupProviderType(value) {
|
|
49
|
+
return SETUP_PROVIDER_TYPES.includes(sanitizeSetupProviderType(value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setupProviderTypeList() {
|
|
53
|
+
return SETUP_PROVIDER_TYPES.join(', ');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveSetupDefaultSelection({ type, model, storedModel }) {
|
|
57
|
+
const providerType = sanitizeSetupProviderType(type);
|
|
58
|
+
if (!isSetupProviderType(providerType)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
type: providerType,
|
|
62
|
+
error: 'Invalid provider type. Must be one of: ' + setupProviderTypeList(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const requestedModel = sanitizeSetupModel(model);
|
|
66
|
+
const fallbackModel = sanitizeSetupModel(storedModel);
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
type: providerType,
|
|
70
|
+
requestedModel,
|
|
71
|
+
targetModel: requestedModel || fallbackModel,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setupProviderAuthMethodHasRuntimeAccess(type, authMethod) {
|
|
76
|
+
const providerType = sanitizeSetupProviderType(type);
|
|
77
|
+
const method = typeof authMethod === 'string' ? authMethod.trim() : '';
|
|
78
|
+
return (SETUP_KEYLESS_AUTH_METHODS[providerType] || []).includes(method);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function setupProviderHasRuntimeAccess({ type, env = process.env, authMethod = '', hasStoredKey = false } = {}) {
|
|
82
|
+
const providerType = sanitizeSetupProviderType(type);
|
|
83
|
+
if (SETUP_LOCAL_PROVIDER_TYPES.includes(providerType)) return true;
|
|
84
|
+
if (hasStoredKey) return true;
|
|
85
|
+
if (setupProviderAuthMethodHasRuntimeAccess(providerType, authMethod)) return true;
|
|
86
|
+
|
|
87
|
+
if (providerType === 'anthropic') {
|
|
88
|
+
return !!(env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_BASE_URL);
|
|
89
|
+
}
|
|
90
|
+
if (providerType === 'openai') {
|
|
91
|
+
return !!env.OPENAI_API_KEY;
|
|
92
|
+
}
|
|
93
|
+
if (providerType === 'google') {
|
|
94
|
+
return !!(env.GOOGLE_API_KEY || env.GEMINI_API_KEY);
|
|
95
|
+
}
|
|
96
|
+
if (providerType === 'deepseek') {
|
|
97
|
+
return !!env.DEEPSEEK_API_KEY;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
SETUP_PROVIDER_TYPES,
|
|
104
|
+
SETUP_PROVIDER_NAMES,
|
|
105
|
+
SETUP_PROVIDER_ENV_KEYS,
|
|
106
|
+
SETUP_LOCAL_PROVIDER_TYPES,
|
|
107
|
+
SETUP_KEYLESS_AUTH_METHODS,
|
|
108
|
+
sanitizeSetupProviderType,
|
|
109
|
+
sanitizeSetupModel,
|
|
110
|
+
isSetupProviderType,
|
|
111
|
+
setupProviderTypeList,
|
|
112
|
+
resolveSetupDefaultSelection,
|
|
113
|
+
setupProviderAuthMethodHasRuntimeAccess,
|
|
114
|
+
setupProviderHasRuntimeAccess,
|
|
115
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
function cloneToolCalls(toolCalls) {
|
|
6
|
+
return JSON.parse(JSON.stringify(toolCalls || []));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function applyWalleToolEvent(toolCalls, event) {
|
|
10
|
+
if (!event || typeof event !== 'object') return toolCalls;
|
|
11
|
+
if (event.type === 'tool_call') {
|
|
12
|
+
toolCalls.push({
|
|
13
|
+
name: event.tool || event.name || 'tool',
|
|
14
|
+
summary: event.summary || '',
|
|
15
|
+
args: event.args || null,
|
|
16
|
+
status: 'working',
|
|
17
|
+
output: '',
|
|
18
|
+
});
|
|
19
|
+
return toolCalls;
|
|
20
|
+
}
|
|
21
|
+
if (event.type === 'tool_result' || event.type === 'tool_done') {
|
|
22
|
+
if (!toolCalls.length) return toolCalls;
|
|
23
|
+
const idx = toolCalls.length - 1;
|
|
24
|
+
const tc = toolCalls[idx];
|
|
25
|
+
tc.status = event.error ? 'error' : 'done';
|
|
26
|
+
tc.summary = event.summary || tc.summary || '';
|
|
27
|
+
tc.output = event.output || event.result || '';
|
|
28
|
+
}
|
|
29
|
+
return toolCalls;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readWalleCtmHistory(filePath) {
|
|
33
|
+
let raw = '';
|
|
34
|
+
try { raw = fs.readFileSync(filePath, 'utf8'); } catch { return []; }
|
|
35
|
+
const messages = [];
|
|
36
|
+
let pendingToolCalls = [];
|
|
37
|
+
for (const line of raw.split('\n')) {
|
|
38
|
+
if (!line.trim()) continue;
|
|
39
|
+
let entry;
|
|
40
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
41
|
+
if (entry.type === 'user') {
|
|
42
|
+
messages.push({
|
|
43
|
+
role: 'user',
|
|
44
|
+
content: entry.content || '',
|
|
45
|
+
timestamp: entry.timestamp || 0,
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (entry.type === 'tool_call' || entry.type === 'tool_result' || entry.type === 'tool_done') {
|
|
50
|
+
applyWalleToolEvent(pendingToolCalls, entry);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (entry.type === 'assistant') {
|
|
54
|
+
messages.push({
|
|
55
|
+
role: 'assistant',
|
|
56
|
+
content: entry.content || '',
|
|
57
|
+
model: entry.model || '',
|
|
58
|
+
latency_ms: entry.latencyMs || entry.latency_ms || 0,
|
|
59
|
+
timestamp: entry.timestamp || 0,
|
|
60
|
+
toolCalls: entry.toolCalls || cloneToolCalls(pendingToolCalls),
|
|
61
|
+
});
|
|
62
|
+
pendingToolCalls = [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return messages;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
applyWalleToolEvent,
|
|
70
|
+
cloneToolCalls,
|
|
71
|
+
readWalleCtmHistory,
|
|
72
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function pathExists(p) {
|
|
7
|
+
if (!p || typeof p !== 'string') return false;
|
|
8
|
+
try { return fs.existsSync(path.resolve(p)); } catch { return false; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function firstExistingPath(paths) {
|
|
12
|
+
for (const p of paths) {
|
|
13
|
+
if (pathExists(p)) return path.resolve(p);
|
|
14
|
+
}
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sessionLabel(session) {
|
|
19
|
+
return session?.label || session?.meta?.label || '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveWalleChatContext({ session, contextSession, requestedCwd } = {}) {
|
|
23
|
+
const candidates = [];
|
|
24
|
+
if (requestedCwd) candidates.push({ cwd: requestedCwd, source: 'request', sessionId: null, label: '' });
|
|
25
|
+
if (contextSession && contextSession.id !== session?.id) {
|
|
26
|
+
candidates.push(
|
|
27
|
+
{ cwd: contextSession.worktree_path, source: 'context_worktree', sessionId: contextSession.id, label: sessionLabel(contextSession) },
|
|
28
|
+
{ cwd: contextSession.cwd, source: 'context_session', sessionId: contextSession.id, label: sessionLabel(contextSession) },
|
|
29
|
+
{ cwd: contextSession.meta?.worktree_path, source: 'context_worktree', sessionId: contextSession.id, label: sessionLabel(contextSession) },
|
|
30
|
+
{ cwd: contextSession.meta?.cwd, source: 'context_session', sessionId: contextSession.id, label: sessionLabel(contextSession) },
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
candidates.push(
|
|
34
|
+
{ cwd: session?.worktree_path, source: 'walle_worktree', sessionId: session?.id || null, label: sessionLabel(session) },
|
|
35
|
+
{ cwd: session?.cwd, source: 'walle_session', sessionId: session?.id || null, label: sessionLabel(session) },
|
|
36
|
+
{ cwd: session?.meta?.worktree_path, source: 'walle_worktree', sessionId: session?.id || null, label: sessionLabel(session) },
|
|
37
|
+
{ cwd: session?.meta?.cwd, source: 'walle_session', sessionId: session?.id || null, label: sessionLabel(session) },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
const cwd = firstExistingPath([candidate.cwd]);
|
|
42
|
+
if (!cwd) continue;
|
|
43
|
+
return {
|
|
44
|
+
cwd,
|
|
45
|
+
source: candidate.source,
|
|
46
|
+
contextSessionId: candidate.sessionId,
|
|
47
|
+
contextSessionLabel: candidate.label,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
cwd: '',
|
|
53
|
+
source: 'none',
|
|
54
|
+
contextSessionId: null,
|
|
55
|
+
contextSessionLabel: '',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
resolveWalleChatContext,
|
|
61
|
+
};
|