create-walle 0.9.13 → 0.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +8 -3
  2. package/bin/create-walle.js +232 -32
  3. package/bin/mcp-inject.js +18 -53
  4. package/package.json +3 -1
  5. package/template/claude-task-manager/api-prompts.js +11 -2
  6. package/template/claude-task-manager/approval-agent.js +7 -0
  7. package/template/claude-task-manager/db.js +94 -75
  8. package/template/claude-task-manager/docs/session-standup-command-center-design.md +242 -0
  9. package/template/claude-task-manager/docs/session-tooltip-freshness-design.md +224 -0
  10. package/template/claude-task-manager/docs/session-ux-issue-review-2026-05-01.md +369 -0
  11. package/template/claude-task-manager/fuzzy-utils.js +10 -2
  12. package/template/claude-task-manager/git-utils.js +140 -10
  13. package/template/claude-task-manager/lib/agent-capabilities.js +1 -1
  14. package/template/claude-task-manager/lib/agent-presets.js +38 -5
  15. package/template/claude-task-manager/lib/codex-terminal-final.js +53 -0
  16. package/template/claude-task-manager/lib/ctm-session-context-api.js +222 -0
  17. package/template/claude-task-manager/lib/session-diagnostics.js +56 -0
  18. package/template/claude-task-manager/lib/session-history.js +309 -16
  19. package/template/claude-task-manager/lib/session-standup.js +409 -0
  20. package/template/claude-task-manager/lib/session-stream.js +253 -20
  21. package/template/claude-task-manager/lib/standup-attention.js +200 -0
  22. package/template/claude-task-manager/lib/status-hooks.js +8 -2
  23. package/template/claude-task-manager/lib/update-telemetry.js +114 -0
  24. package/template/claude-task-manager/lib/walle-ctm-history.js +49 -6
  25. package/template/claude-task-manager/lib/walle-default-model.js +55 -0
  26. package/template/claude-task-manager/lib/walle-mcp-auto-config.js +66 -0
  27. package/template/claude-task-manager/lib/walle-supervisor.js +86 -19
  28. package/template/claude-task-manager/lib/walle-transcript.js +1 -3
  29. package/template/claude-task-manager/lib/worktree-cwd.js +82 -0
  30. package/template/claude-task-manager/package.json +1 -0
  31. package/template/claude-task-manager/providers/codex-mcp.js +104 -0
  32. package/template/claude-task-manager/providers/index.js +2 -0
  33. package/template/claude-task-manager/public/css/setup.css +2 -1
  34. package/template/claude-task-manager/public/css/walle.css +71 -0
  35. package/template/claude-task-manager/public/index.html +2388 -429
  36. package/template/claude-task-manager/public/js/message-renderer.js +314 -35
  37. package/template/claude-task-manager/public/js/session-search-utils.js +185 -3
  38. package/template/claude-task-manager/public/js/session-status-precedence.js +125 -0
  39. package/template/claude-task-manager/public/js/setup.js +62 -19
  40. package/template/claude-task-manager/public/js/stream-view.js +396 -55
  41. package/template/claude-task-manager/public/js/terminal-restore-state.js +57 -0
  42. package/template/claude-task-manager/public/js/walle-session.js +234 -26
  43. package/template/claude-task-manager/public/js/walle.js +143 -2
  44. package/template/claude-task-manager/server.js +1402 -433
  45. package/template/claude-task-manager/session-integrity.js +77 -28
  46. package/template/claude-task-manager/workers/approval-widget-validator.js +15 -5
  47. package/template/claude-task-manager/workers/scrollback-worker.js +5 -6
  48. package/template/claude-task-manager/workers/state-detectors/codex.js +6 -0
  49. package/template/package.json +1 -1
  50. package/template/wall-e/agent-runners/claude-code.js +2 -0
  51. package/template/wall-e/agent.js +63 -8
  52. package/template/wall-e/api-walle.js +330 -52
  53. package/template/wall-e/brain.js +291 -42
  54. package/template/wall-e/chat.js +172 -15
  55. package/template/wall-e/coding/compaction-service.js +19 -5
  56. package/template/wall-e/coding/stream-processor.js +22 -2
  57. package/template/wall-e/coding/workspace-replay.js +1 -4
  58. package/template/wall-e/coding-orchestrator.js +250 -80
  59. package/template/wall-e/compat.js +0 -28
  60. package/template/wall-e/context/context-builder.js +3 -1
  61. package/template/wall-e/embeddings.js +2 -7
  62. package/template/wall-e/eval/agent-runner.js +30 -9
  63. package/template/wall-e/eval/benchmark-generator.js +21 -1
  64. package/template/wall-e/eval/benchmarks/chat-eval.json +66 -6
  65. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -596
  66. package/template/wall-e/eval/cc-replay.js +1 -0
  67. package/template/wall-e/eval/codex-cli-baseline.js +633 -0
  68. package/template/wall-e/eval/debug-agent003.js +1 -0
  69. package/template/wall-e/eval/eval-orchestrator.js +3 -3
  70. package/template/wall-e/eval/run-agent-benchmarks.js +11 -3
  71. package/template/wall-e/eval/run-codex-cli-baseline.js +177 -0
  72. package/template/wall-e/eval/run-model-comparison.js +1 -0
  73. package/template/wall-e/eval/swebench-adapter.js +1 -0
  74. package/template/wall-e/evaluation/quorum-evaluator.js +0 -1
  75. package/template/wall-e/extraction/knowledge-extractor.js +1 -2
  76. package/template/wall-e/lib/mcp-integration.js +336 -0
  77. package/template/wall-e/llm/ollama.js +47 -8
  78. package/template/wall-e/llm/ollama.plugin.json +1 -1
  79. package/template/wall-e/llm/tool-adapter.js +1 -0
  80. package/template/wall-e/loops/ingest.js +42 -8
  81. package/template/wall-e/loops/initiative.js +87 -2
  82. package/template/wall-e/mcp-server.js +872 -19
  83. package/template/wall-e/memory/ctm-context-client.js +230 -0
  84. package/template/wall-e/memory/ctm-session-context.js +1376 -0
  85. package/template/wall-e/prompts/coding/memory-protocol.md +6 -0
  86. package/template/wall-e/server.js +30 -1
  87. package/template/wall-e/skills/_bundled/memory-search/SKILL.md +8 -0
  88. package/template/wall-e/skills/_bundled/scan-ctm-sessions/SKILL.md +20 -0
  89. package/template/wall-e/skills/_bundled/scan-ctm-sessions/run.js +43 -0
  90. package/template/wall-e/skills/_bundled/slack-mentions/run.js +471 -188
  91. package/template/wall-e/skills/skill-planner.js +86 -4
  92. package/template/wall-e/slack/socket-mode-listener.js +276 -0
  93. package/template/wall-e/telemetry.js +70 -2
  94. package/template/wall-e/tools/builtin-middleware.js +55 -2
  95. package/template/wall-e/tools/shell-policy.js +1 -1
  96. package/template/wall-e/tools/slack-owner.js +104 -0
  97. package/template/website/index.html +4 -4
  98. package/template/builder-journal.md +0 -17
@@ -0,0 +1,409 @@
1
+ 'use strict';
2
+
3
+ const { normalizeAttention } = require('./standup-attention');
4
+
5
+ const LANE_DEFS = [
6
+ {
7
+ id: 'needs_user',
8
+ title: 'Needs User',
9
+ description: 'Approvals, questions, blockers, or failed work that need operator attention.',
10
+ },
11
+ {
12
+ id: 'ready_review',
13
+ title: 'Ready Review',
14
+ description: 'Sessions with reviewable work, finished changes, or worktree cleanup to handle.',
15
+ },
16
+ {
17
+ id: 'running',
18
+ title: 'Running',
19
+ description: 'Active sessions that are still producing work.',
20
+ },
21
+ {
22
+ id: 'continue_later',
23
+ title: 'Continue Later',
24
+ description: 'Idle sessions worth preserving for context reuse.',
25
+ },
26
+ ];
27
+
28
+ const LANE_ORDER = new Map(LANE_DEFS.map((lane, index) => [lane.id, index]));
29
+ const STALE_AFTER_MS = 30 * 60 * 1000;
30
+
31
+ function truncateText(value, max = 180) {
32
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
33
+ if (!text) return '';
34
+ if (text.length <= max) return text;
35
+ return `${text.slice(0, Math.max(0, max - 1)).trim()}...`;
36
+ }
37
+
38
+ function valueText(value) {
39
+ if (!value) return '';
40
+ if (typeof value === 'string') return value;
41
+ if (typeof value === 'object') {
42
+ return value.text || value.summary || value.phase || value.next || '';
43
+ }
44
+ return String(value);
45
+ }
46
+
47
+ function progressText(progress) {
48
+ if (!progress) return '';
49
+ if (typeof progress === 'string') return progress;
50
+ if (typeof progress === 'object') {
51
+ return progress.summary || progress.next || progress.phase || '';
52
+ }
53
+ return String(progress);
54
+ }
55
+
56
+ function toMs(value) {
57
+ if (!value) return 0;
58
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
59
+ const parsed = Date.parse(String(value));
60
+ return Number.isFinite(parsed) ? parsed : 0;
61
+ }
62
+
63
+ function toIso(value) {
64
+ const ms = toMs(value);
65
+ return ms ? new Date(ms).toISOString() : null;
66
+ }
67
+
68
+ function ageLabel(now, value) {
69
+ const ms = toMs(value);
70
+ if (!ms) return '';
71
+ const age = Math.max(0, now - ms);
72
+ if (age < 60 * 1000) return 'just now';
73
+ const minutes = Math.round(age / (60 * 1000));
74
+ if (minutes < 60) return `${minutes}m ago`;
75
+ const hours = Math.round(minutes / 60);
76
+ if (hours < 48) return `${hours}h ago`;
77
+ const days = Math.round(hours / 24);
78
+ return `${days}d ago`;
79
+ }
80
+
81
+ function normalizeStatus(status) {
82
+ const text = String(status || '').toLowerCase();
83
+ if (!text) return 'unknown';
84
+ if (text === 'busy') return 'running';
85
+ if (text === 'waiting_input') return 'waiting_input';
86
+ if (text === 'waiting-for-input') return 'waiting';
87
+ if (['running', 'waiting', 'idle', 'exited', 'unknown'].includes(text)) return text;
88
+ return text;
89
+ }
90
+
91
+ function addIndexEntries(map, item) {
92
+ if (!item || typeof item !== 'object') return;
93
+ for (const key of ['id', 'sessionId', 'ctmSessionId', 'agentSessionId', 'claudeSessionId']) {
94
+ const value = item[key];
95
+ if (value) map.set(String(value), item);
96
+ }
97
+ }
98
+
99
+ function buildIndex(items) {
100
+ const map = new Map();
101
+ for (const item of items || []) addIndexEntries(map, item);
102
+ return map;
103
+ }
104
+
105
+ function lookupForSession(index, session) {
106
+ if (!index || !session) return null;
107
+ const ids = [
108
+ session.id,
109
+ session.sessionId,
110
+ session.ctmSessionId,
111
+ session.agentSessionId,
112
+ session.claudeSessionId,
113
+ ];
114
+ for (const id of ids) {
115
+ if (id && index.has(String(id))) return index.get(String(id));
116
+ }
117
+ return null;
118
+ }
119
+
120
+ function statusForSession(session, status, summary) {
121
+ if (session?.waitingForInput) return 'waiting_input';
122
+ const candidates = [
123
+ session?.standupStatus,
124
+ session?.liveStatus,
125
+ session?.computedStatus,
126
+ session?.state,
127
+ status?.captureStatus,
128
+ status?.status,
129
+ summary?.captureStatus,
130
+ summary?.status,
131
+ session?.serverState,
132
+ session?.status,
133
+ ];
134
+ for (const candidate of candidates) {
135
+ const normalized = normalizeStatus(candidate);
136
+ if (normalized && normalized !== 'unknown') return normalized;
137
+ }
138
+ return 'unknown';
139
+ }
140
+
141
+ function isWalleSession(session) {
142
+ return session?.type === 'walle' || session?.agentType === 'walle' || session?.agent === 'walle';
143
+ }
144
+
145
+ function standupStatusForSession(session, status, summary) {
146
+ const normalized = statusForSession(session, status, summary);
147
+ // A quiet Wall-E chat is available for the next message, not blocked on
148
+ // operator input. Older live projections used "waiting" for that idle state.
149
+ if (normalized === 'waiting' && isWalleSession(session) && !session?.waitingForInput) return 'idle';
150
+ return normalized;
151
+ }
152
+
153
+ function worktreeEvidence(worktree) {
154
+ if (!worktree) return '';
155
+ const dirtyFiles = Number(worktree.dirtyFiles || 0);
156
+ const unmergedCommits = Number(worktree.unmergedCommits || 0);
157
+ const parts = [];
158
+ if (dirtyFiles) parts.push(`${dirtyFiles} dirty`);
159
+ if (unmergedCommits) parts.push(`${unmergedCommits} unmerged`);
160
+ if (!parts.length && worktree.summary) parts.push(worktree.summary);
161
+ return parts.length ? `worktree ${parts.join(', ')}` : '';
162
+ }
163
+
164
+ function hasReviewableWork(session, summaryText, progress) {
165
+ const worktree = session?.worktreeStatus || session?.worktree || null;
166
+ if (worktree) {
167
+ if (worktree.needsAttention) return true;
168
+ if (Number(worktree.dirtyFiles || 0) > 0) return true;
169
+ if (Number(worktree.unmergedCommits || 0) > 0) return true;
170
+ }
171
+ const text = `${summaryText || ''} ${progressText(progress)}`.toLowerCase();
172
+ return /\b(done|completed|complete|verified|passed|ready for review|all work done)\b/.test(text);
173
+ }
174
+
175
+ function hasFailureSignal(summaryText, progress) {
176
+ const text = `${summaryText || ''} ${progressText(progress)}`.toLowerCase();
177
+ return /\b(cannot proceed|blocked|blocker|stuck|permission denied|fatal|panic|segmentation fault|uncaught|unhandled exception|traceback|command failed|build failed|tests? failed|npm err!)\b/.test(text);
178
+ }
179
+
180
+ function attentionForSession(session, summary) {
181
+ return normalizeAttention(summary?.attention || session?.attention || null);
182
+ }
183
+
184
+ function prependEvidence(evidence, items) {
185
+ const out = [];
186
+ for (const item of items || []) {
187
+ const text = truncateText(item, 90);
188
+ if (text && !out.some(existing => existing.toLowerCase() === text.toLowerCase())) out.push(text);
189
+ }
190
+ for (const item of evidence || []) {
191
+ const text = truncateText(item, 90);
192
+ if (text && !out.some(existing => existing.toLowerCase() === text.toLowerCase())) out.push(text);
193
+ }
194
+ return out.slice(0, 5);
195
+ }
196
+
197
+ function baseEvidence({ session, status, summary, now, intentText, progress }) {
198
+ const evidence = [];
199
+ if (status === 'waiting_input') evidence.push('waiting input');
200
+ else if (status === 'waiting') evidence.push('waiting');
201
+ else if (status === 'running') evidence.push('running');
202
+ const branch = session?.branch || session?.gitBranch || session?.worktreeStatus?.branch || '';
203
+ if (branch) evidence.push(`branch ${branch}`);
204
+ const wtEvidence = worktreeEvidence(session?.worktreeStatus || session?.worktree);
205
+ if (wtEvidence) evidence.push(wtEvidence);
206
+ const age = ageLabel(now, latestActivity(session, summary));
207
+ if (age) evidence.push(`last activity ${age}`);
208
+ if (intentText && summary?.intent?.source === 'prompt-fallback') {
209
+ evidence.push(`prompt: ${truncateText(intentText, 80)}`);
210
+ }
211
+ const progressPhase = progress && typeof progress === 'object' ? progress.phase : '';
212
+ if (progressPhase && !['unknown', status].includes(String(progressPhase))) {
213
+ evidence.push(`phase ${progressPhase}`);
214
+ }
215
+ return evidence.slice(0, 5);
216
+ }
217
+
218
+ function latestActivity(session, summary) {
219
+ const values = [
220
+ summary?.lastActivity,
221
+ summary?.progress?.updatedAt,
222
+ summary?.intent?.updatedAt,
223
+ session?.lastPtyActivity,
224
+ session?.lastActivity,
225
+ session?.modifiedAt,
226
+ session?.fileModifiedAt,
227
+ session?.createdAt,
228
+ ];
229
+ let latest = 0;
230
+ for (const value of values) {
231
+ const ms = toMs(value);
232
+ if (ms > latest) latest = ms;
233
+ }
234
+ return latest;
235
+ }
236
+
237
+ function classifySessionStandup(session, signals = {}, now = Date.now()) {
238
+ const summary = signals.summary || {};
239
+ const streamStatus = signals.status || {};
240
+ const status = standupStatusForSession(session, streamStatus, summary);
241
+ const intent = summary.intent || summary.summary || summary.displayPrompt || summary.lastPrompt || session?.label || '';
242
+ const intentString = truncateText(valueText(intent), 160);
243
+ const progress = summary.progress || '';
244
+ const progressString = truncateText(progressText(progress), 180);
245
+ const summaryText = valueText(summary.summary) || intentString;
246
+ const lastActivityMs = latestActivity(session, summary);
247
+ const ageMs = lastActivityMs ? Math.max(0, now - lastActivityMs) : null;
248
+ const waitingReason = session?.waitingReason || streamStatus?.reason || summary?.statusEvidence?.[0] || '';
249
+ const reviewable = hasReviewableWork(session, summaryText, progress);
250
+ const attention = attentionForSession(session, summary);
251
+ const hasStructuredAttention = !!(summary?.attention || session?.attention);
252
+ const attentionSeverity = attention?.severity || 'none';
253
+ const failed = attentionSeverity === 'failure'
254
+ || (!hasStructuredAttention && hasFailureSignal(summaryText, progress));
255
+ const warning = attentionSeverity === 'warning';
256
+ const isWaiting = status === 'waiting_input' || status === 'waiting';
257
+ const isRunning = status === 'running' || status === 'busy';
258
+
259
+ let lane = 'continue_later';
260
+ let actionKind = 'resume';
261
+ let actionLabel = 'Continue';
262
+ let recommendation = 'Session is idle; reuse this context when you have the next instruction.';
263
+ let confidence = 'medium';
264
+
265
+ if (isWaiting) {
266
+ lane = 'needs_user';
267
+ actionKind = status === 'waiting_input' || /approval|choice|input/i.test(waitingReason)
268
+ ? 'approval_needed'
269
+ : 'needs_input';
270
+ actionLabel = 'Respond';
271
+ recommendation = actionKind === 'approval_needed'
272
+ ? 'Approval or input is waiting in the terminal.'
273
+ : 'The session appears to need user input before it can continue.';
274
+ confidence = 'high';
275
+ } else if (isRunning) {
276
+ lane = 'running';
277
+ actionKind = 'watch';
278
+ actionLabel = 'Open';
279
+ recommendation = 'Work is still running; no operator action is needed yet.';
280
+ confidence = status === 'running' ? 'high' : 'medium';
281
+ } else if (failed) {
282
+ lane = 'needs_user';
283
+ actionKind = 'investigate';
284
+ actionLabel = 'Inspect';
285
+ recommendation = attention?.recommendation || 'A current blocker or failure appears to need attention.';
286
+ confidence = attention?.confidence || 'medium';
287
+ } else if (warning) {
288
+ lane = 'needs_user';
289
+ actionKind = 'warning';
290
+ actionLabel = 'Review';
291
+ recommendation = attention?.recommendation || 'A current warning appears to need attention.';
292
+ confidence = attention?.confidence || 'medium';
293
+ } else if (reviewable) {
294
+ lane = 'ready_review';
295
+ actionKind = 'review';
296
+ actionLabel = 'Review';
297
+ recommendation = 'Review the session output or finish the worktree changes.';
298
+ confidence = session?.worktreeStatus ? 'high' : 'medium';
299
+ } else if (ageMs != null && ageMs > STALE_AFTER_MS) {
300
+ lane = 'continue_later';
301
+ actionKind = 'resume';
302
+ actionLabel = 'Resume';
303
+ recommendation = 'Session is quiet; resume it only if this preserved context is still useful.';
304
+ confidence = 'medium';
305
+ }
306
+
307
+ let evidence = baseEvidence({ session, status, summary, now, intentText: intentString, progress });
308
+ if ((failed || warning) && attention?.evidence?.length) {
309
+ evidence = prependEvidence(evidence, attention.evidence);
310
+ }
311
+ if (failed && !isRunning && !evidence.some(item => /failed|blocked|blocker|error|failure/i.test(item))) {
312
+ evidence.unshift('possible blocker');
313
+ }
314
+ if (warning && !isRunning && !evidence.some(item => /warn|caution|risk|attention/i.test(item))) {
315
+ evidence.unshift('warning');
316
+ }
317
+
318
+ const title = truncateText(
319
+ session?.label || summary?.displayPrompt || summary?.lastPrompt || intentString || session?.id || 'Session',
320
+ 90
321
+ );
322
+
323
+ return {
324
+ id: session?.id || session?.sessionId || '',
325
+ agentSessionId: session?.agentSessionId || session?.claudeSessionId || null,
326
+ title,
327
+ agent: session?.agentType || session?.agent || session?.type || 'session',
328
+ provider: session?.model_provider || session?.provider || '',
329
+ model: session?.model_id || session?.model || '',
330
+ cwd: session?.cwd || '',
331
+ project: session?.project || session?.cwd || '',
332
+ branch: session?.branch || session?.gitBranch || session?.worktreeStatus?.branch || '',
333
+ status,
334
+ lane,
335
+ actionKind,
336
+ actionLabel,
337
+ recommendation,
338
+ confidence,
339
+ evidence,
340
+ attention: attention && attention.severity !== 'none' ? attention : null,
341
+ intent: intentString,
342
+ progress: progressString,
343
+ lastActivity: toIso(lastActivityMs),
344
+ ageMs,
345
+ worktree: session?.worktreeStatus || session?.worktree || null,
346
+ capabilities: session?.agentCapabilities || session?.capabilities || {},
347
+ priority: LANE_ORDER.get(lane) ?? 99,
348
+ };
349
+ }
350
+
351
+ function sortCards(a, b) {
352
+ if (a.priority !== b.priority) return a.priority - b.priority;
353
+ const actionOrder = {
354
+ approval_needed: 0,
355
+ needs_input: 1,
356
+ investigate: 2,
357
+ warning: 3,
358
+ review: 4,
359
+ watch: 5,
360
+ resume: 6,
361
+ archive: 7,
362
+ };
363
+ const ao = actionOrder[a.actionKind] ?? 99;
364
+ const bo = actionOrder[b.actionKind] ?? 99;
365
+ if (ao !== bo) return ao - bo;
366
+ return (toMs(b.lastActivity) || 0) - (toMs(a.lastActivity) || 0);
367
+ }
368
+
369
+ function buildSessionStandupSnapshot({ sessions = [], summaries = [], statuses = [], now = Date.now() } = {}) {
370
+ const summaryIndex = buildIndex(summaries);
371
+ const statusIndex = buildIndex(statuses);
372
+ const cards = sessions
373
+ .map(session => classifySessionStandup(session, {
374
+ summary: lookupForSession(summaryIndex, session),
375
+ status: lookupForSession(statusIndex, session),
376
+ }, now))
377
+ .filter(card => card.id)
378
+ .sort(sortCards);
379
+
380
+ const counts = { total: cards.length };
381
+ for (const lane of LANE_DEFS) counts[lane.id] = 0;
382
+ for (const card of cards) counts[card.lane] = (counts[card.lane] || 0) + 1;
383
+
384
+ const lanes = LANE_DEFS.map(lane => ({
385
+ ...lane,
386
+ count: counts[lane.id] || 0,
387
+ sessions: cards.filter(card => card.lane === lane.id),
388
+ }));
389
+
390
+ return {
391
+ generatedAt: new Date(now).toISOString(),
392
+ counts,
393
+ lanes,
394
+ sessions: cards,
395
+ };
396
+ }
397
+
398
+ module.exports = {
399
+ LANE_DEFS,
400
+ classifySessionStandup,
401
+ buildSessionStandupSnapshot,
402
+ _private: {
403
+ normalizeStatus,
404
+ standupStatusForSession,
405
+ latestActivity,
406
+ truncateText,
407
+ hasFailureSignal,
408
+ },
409
+ };