create-walle 0.9.28 → 0.9.29

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 (105) hide show
  1. package/README.md +2 -2
  2. package/bin/create-walle.js +166 -6
  3. package/package.json +1 -1
  4. package/template/bin/ctm-launch.sh +7 -0
  5. package/template/bin/dev.sh +5 -0
  6. package/template/claude-task-manager/api-prompts.js +32 -15
  7. package/template/claude-task-manager/db.js +717 -38
  8. package/template/claude-task-manager/docs/backfill-incremental-no-main-fallback.md +48 -0
  9. package/template/claude-task-manager/docs/conversation-import-freshness.md +21 -0
  10. package/template/claude-task-manager/docs/conversation-log-redesign.html +587 -0
  11. package/template/claude-task-manager/lib/auth-rules.js +13 -0
  12. package/template/claude-task-manager/lib/codex-rollout-snapshot.js +53 -0
  13. package/template/claude-task-manager/lib/headless-term-service.js +246 -4
  14. package/template/claude-task-manager/lib/message-identity.js +115 -0
  15. package/template/claude-task-manager/lib/mirror-feed-guards.js +25 -0
  16. package/template/claude-task-manager/lib/path-suggest.js +77 -0
  17. package/template/claude-task-manager/lib/prompt-index-inputs.js +136 -0
  18. package/template/claude-task-manager/lib/real-node.js +36 -4
  19. package/template/claude-task-manager/lib/restore-auto-resume-policy.js +67 -0
  20. package/template/claude-task-manager/lib/restore-resume-batch.js +20 -0
  21. package/template/claude-task-manager/lib/restore-terminal-dims.js +109 -0
  22. package/template/claude-task-manager/lib/resume-cwd.js +124 -3
  23. package/template/claude-task-manager/lib/runtime-approval-recorder.js +152 -0
  24. package/template/claude-task-manager/lib/runtime-context-truth.js +226 -0
  25. package/template/claude-task-manager/lib/runtime-contract.js +195 -0
  26. package/template/claude-task-manager/lib/runtime-history-builder.js +205 -0
  27. package/template/claude-task-manager/lib/runtime-hook-bus.js +98 -0
  28. package/template/claude-task-manager/lib/runtime-input-queue.js +114 -0
  29. package/template/claude-task-manager/lib/runtime-input-recorder.js +156 -0
  30. package/template/claude-task-manager/lib/runtime-lineage.js +189 -0
  31. package/template/claude-task-manager/lib/runtime-registry.js +263 -0
  32. package/template/claude-task-manager/lib/runtime-session-history.js +41 -0
  33. package/template/claude-task-manager/lib/server-phase-conditions.js +103 -0
  34. package/template/claude-task-manager/lib/session-content-backfill.js +55 -8
  35. package/template/claude-task-manager/lib/session-db-read-contract.js +67 -0
  36. package/template/claude-task-manager/lib/session-history.js +5 -1
  37. package/template/claude-task-manager/lib/session-host-manager.js +154 -2
  38. package/template/claude-task-manager/lib/session-messages-defer.js +50 -0
  39. package/template/claude-task-manager/lib/session-messages-projection.js +37 -2
  40. package/template/claude-task-manager/lib/session-stream.js +19 -1
  41. package/template/claude-task-manager/lib/state-sync/cell-diff.js +41 -0
  42. package/template/claude-task-manager/lib/state-sync/frame-emitter.js +173 -0
  43. package/template/claude-task-manager/lib/state-sync/frame-rate.js +75 -0
  44. package/template/claude-task-manager/lib/state-sync/row-serializer.js +166 -0
  45. package/template/claude-task-manager/lib/terminal-fingerprint.js +19 -3
  46. package/template/claude-task-manager/lib/transcript-store.js +87 -6
  47. package/template/claude-task-manager/lib/wal-checkpoint-policy.js +40 -0
  48. package/template/claude-task-manager/lib/worktree-output-binding.js +93 -0
  49. package/template/claude-task-manager/lib/write-coalescer.js +83 -0
  50. package/template/claude-task-manager/public/index.html +952 -188
  51. package/template/claude-task-manager/public/js/feedback.js +8 -1
  52. package/template/claude-task-manager/public/js/message-renderer.js +72 -2
  53. package/template/claude-task-manager/public/js/session-phase.js +4 -0
  54. package/template/claude-task-manager/public/js/session-status-precedence.js +7 -173
  55. package/template/claude-task-manager/public/js/setup.js +46 -3
  56. package/template/claude-task-manager/public/js/state-sync-client.js +218 -0
  57. package/template/claude-task-manager/public/js/state-sync-predictor.js +41 -0
  58. package/template/claude-task-manager/public/js/stream-view.js +113 -9
  59. package/template/claude-task-manager/public/js/terminal-reconciler.js +24 -4
  60. package/template/claude-task-manager/public/js/walle-session.js +28 -0
  61. package/template/claude-task-manager/public/js/walle.js +26 -9
  62. package/template/claude-task-manager/queue-engine.js +140 -0
  63. package/template/claude-task-manager/server.js +2263 -351
  64. package/template/claude-task-manager/session-integrity.js +16 -1
  65. package/template/claude-task-manager/workers/db-owner-worker.js +8 -0
  66. package/template/claude-task-manager/workers/read-pool-worker.js +18 -1
  67. package/template/claude-task-manager/workers/session-host-pool-process.js +188 -0
  68. package/template/claude-task-manager/workers/session-host-process.js +42 -11
  69. package/template/package.json +1 -1
  70. package/template/wall-e/agent.js +113 -15
  71. package/template/wall-e/api-walle.js +73 -9
  72. package/template/wall-e/auth/flow-manager.js +78 -1
  73. package/template/wall-e/auth/provider-flows.js +56 -2
  74. package/template/wall-e/brain.js +53 -12
  75. package/template/wall-e/embeddings.js +70 -0
  76. package/template/wall-e/events/event-bus.js +11 -1
  77. package/template/wall-e/http/auth.js +3 -1
  78. package/template/wall-e/lib/brain-owner-worker-client.js +16 -4
  79. package/template/wall-e/lib/diagnostics-flags.js +9 -0
  80. package/template/wall-e/lib/event-loop-monitor.js +84 -5
  81. package/template/wall-e/lib/mcp-scan-lifecycle.js +247 -0
  82. package/template/wall-e/lib/runtime-process-inventory.js +114 -0
  83. package/template/wall-e/lib/runtime-worker-pool.js +200 -23
  84. package/template/wall-e/lib/scheduler-worker-jobs.js +19 -3
  85. package/template/wall-e/lib/scheduler.js +250 -34
  86. package/template/wall-e/lib/worker-thread-pool.js +6 -4
  87. package/template/wall-e/llm/claude-cli.js +21 -3
  88. package/template/wall-e/llm/cli-binary.js +77 -0
  89. package/template/wall-e/llm/codex-cli.js +22 -3
  90. package/template/wall-e/llm/default-fallback.js +10 -4
  91. package/template/wall-e/llm/mlx.js +46 -8
  92. package/template/wall-e/llm/provider-detector.js +112 -22
  93. package/template/wall-e/loops/tasks.js +521 -25
  94. package/template/wall-e/memory/ctm-session-context.js +93 -0
  95. package/template/wall-e/skills/_bundled/google-calendar/run.js +15 -23
  96. package/template/wall-e/skills/_bundled/gws-workspace/gws-router +180 -0
  97. package/template/wall-e/skills/_bundled/gws-workspace/setup.js +112 -1
  98. package/template/wall-e/skills/_bundled/mcp-scan/run.js +265 -41
  99. package/template/wall-e/skills/_bundled/slack-mentions/run.js +267 -41
  100. package/template/wall-e/skills/internal-skill-registry.js +27 -5
  101. package/template/wall-e/skills/mcp-client.js +18 -3
  102. package/template/wall-e/skills/script-skill-runner.js +53 -5
  103. package/template/wall-e/training/real-trajectory-miner.js +24 -114
  104. package/template/wall-e/workers/brain-owner-worker.js +8 -0
  105. package/template/website/index.html +3 -0
package/README.md CHANGED
@@ -40,9 +40,9 @@ This copies the project, installs dependencies, auto-detects your name and timez
40
40
 
41
41
  ### Download for Mac (no terminal)
42
42
 
43
- Prefer a click-to-install app? Download the notarized **Wall-E.app** (Apple Silicon):
43
+ Prefer a click-to-install app? Download the notarized **Wall-E.app** (Apple Silicon & Intel):
44
44
 
45
- **⬇ [Download for Mac](https://github.com/ShanniLi/walle-dist/releases/latest)** → `Wall-E-…-arm64.dmg`
45
+ **⬇ [Download for Mac](https://github.com/ShanniLi/walle-dist/releases/latest)** → `Wall-E-…-arm64.dmg` (Apple Silicon) or `Wall-E-…-x64.dmg` (Intel)
46
46
 
47
47
  Open the DMG, drag **Wall-E** to Applications, and launch it. First run installs everything into `~/.walle` and opens the dashboard — Developer-ID notarized + stapled, so there's no Gatekeeper warning. Under the hood it runs the same `create-walle` setup.
48
48
 
@@ -38,6 +38,21 @@ const NATIVE_DEPENDENCIES = new Set([
38
38
  // create-walle/macos/build-macos-app.sh (NODE_VERSION). Set WALLE_NO_NOTARIZED_NODE=1 to opt out.
39
39
  const NOTARIZED_NODE_VERSION = '25.2.1';
40
40
 
41
+ // ── Branded notarized macOS app (the real Screen Recording fix for no-Dev-ID users) ──
42
+ // A bare notarized node is stable but anonymous — macOS shows "node" in the Screen Recording
43
+ // prompt. The downloadable Developer-ID-notarized Wall-E.app runs the daemon under its OWN vendored
44
+ // node, whose signature is part of the branded bundle (Team HQSJ8SS8Q6), so the grant shows
45
+ // "Wall-E", auto-lists, and persists. `npx create-walle` users never get that app — so we fetch the
46
+ // SAME prebuilt notarized app once and adopt its vendored node as the daemon identity (parity with
47
+ // the downloadable app's `WALLE_NOTARIZED_NODE`). All best-effort: a download/verify failure leaves
48
+ // the daemon on the bare notarized node (no regression). Set WALLE_NO_BRANDED_APP=1 to opt out.
49
+ // The runtime-shell app is versioned independently of the npm package (it changes only when the
50
+ // vendored Node / entitlements / signing change) — keep in sync with build-macos-app.sh VERSION.
51
+ const NOTARIZED_APP_VERSION = '1.0.2';
52
+ const NOTARIZED_APP_TEAM_ID = 'HQSJ8SS8Q6';
53
+ const WALLE_DIST_RELEASE_BASE = process.env.WALLE_DIST_RELEASE_BASE
54
+ || 'https://github.com/ShanniLi/walle-dist/releases/download';
55
+
41
56
  // Files to preserve during update (user config, not code)
42
57
  const PRESERVE_ON_UPDATE = ['.env', 'wall-e/wall-e-config.json'];
43
58
 
@@ -88,10 +103,11 @@ function execTeamIdentifier(binaryPath) {
88
103
  // app's own branded launcher identity, so it shows "Wall-E").
89
104
  // 2. The CTM .app bundle exec WHEN it carries a stable Team Identifier (Developer-ID-signed by
90
105
  // bin/ensure-stable-node.js) — branded AND grant-persisting.
91
- // 3. The official notarized node we downloaded ourselves — stable but anonymous ("node"); the
92
- // fallback for machines without a Developer ID, where a branded-stable bundle isn't possible.
93
- // 4. The self-signed CTM bundle execbranded but re-prompts (no stable Team ID).
94
- // 5. The running node.
106
+ // 3. The adopted Developer-ID-notarized Wall-E.app's vendored node (ensureNotarizedBrandedApp)
107
+ // branded ("Wall-E") AND grant-persisting, for no-Dev-ID machines (the npx common case).
108
+ // 4. The official notarized node we downloaded ourselves stable but anonymous ("node").
109
+ // 5. The self-signed CTM bundle exec — branded but re-prompts (no stable Team ID).
110
+ // 6. The running node.
95
111
  function daemonExec() {
96
112
  const notarized = process.env.WALLE_NOTARIZED_NODE;
97
113
  if (notarized && process.platform === 'darwin') {
@@ -104,6 +120,11 @@ function daemonExec() {
104
120
  // Prefer the branded bundle only when it is Developer-ID-signed (has a Team ID): branded AND
105
121
  // its grants persist. Otherwise prefer the notarized bare node (stable but "node").
106
122
  if (bundleExists && execTeamIdentifier(bundle)) return bundle;
123
+ // Adopted Developer-ID-notarized Wall-E.app vendored node — branded ("Wall-E") AND
124
+ // grant-persisting, for no-Dev-ID npx users where the bundle above has no Team ID. Beats the
125
+ // bare notarized node (anonymous "node").
126
+ const brandedApp = validatedNotarizedApp();
127
+ if (brandedApp) return brandedApp;
107
128
  const own = validatedNotarizedNode();
108
129
  if (own) return own;
109
130
  if (bundleExists) return bundle; // self-signed branded bundle — branded name, but re-prompts
@@ -140,10 +161,13 @@ function validatedNotarizedNode() {
140
161
  return null;
141
162
  }
142
163
 
143
- // The node whose ABI native modules must target = whatever the daemon will run under.
164
+ // The node whose ABI native modules must target = whatever the daemon will run under. The bare
165
+ // notarized node and the adopted branded app's vendored node are BOTH pinned to NOTARIZED_NODE_VERSION
166
+ // (same ABI), so order is belt-and-suspenders; the app fallback only matters in the edge where the
167
+ // node fetch failed but the app fetch succeeded (the daemon then runs under the app's vendored node).
144
168
  function daemonNodeForBuild() {
145
169
  if (process.platform !== 'darwin') return process.execPath;
146
- return validatedNotarizedNode() || process.execPath;
170
+ return validatedNotarizedNode() || validatedNotarizedApp() || process.execPath;
147
171
  }
148
172
 
149
173
  function nodeReportsVersion(bin, version) {
@@ -238,6 +262,124 @@ function disableNotarizedNode() {
238
262
  try { fs.rmSync(notarizedNodePath(), { force: true }); } catch {}
239
263
  }
240
264
 
265
+ // ── Branded notarized app adoption (mirrors the notarized-node fetch above) ──
266
+
267
+ function notarizedAppDir() {
268
+ return process.env.WALLE_NOTARIZED_APP_DIR || path.join(process.env.HOME, '.walle', 'notarized-app');
269
+ }
270
+
271
+ // The vendored node inside the adopted Wall-E.app — the exact path the downloadable app uses as
272
+ // WALLE_NOTARIZED_NODE. It lives in Contents/Resources (NOT Contents/MacOS), so isBundleExec() is
273
+ // false for it and the screenshot bridge routes capture through it under the branded identity.
274
+ // A SEPARATE dir from ~/.walle/bundles (which buildAppBundles re-clones every update and would
275
+ // clobber the notarized app).
276
+ function notarizedAppVendoredNode() {
277
+ return path.join(notarizedAppDir(), 'Wall-E.app', 'Contents', 'Resources', 'node');
278
+ }
279
+
280
+ // Returns the adopted app's vendored node ONLY when the marker matches the pinned app version (the
281
+ // marker is written last, so a partial/failed download is never trusted). Cheap — no codesign here;
282
+ // signature verification happens once at adopt time in ensureNotarizedBrandedApp.
283
+ function validatedNotarizedApp() {
284
+ if (process.platform !== 'darwin') return null;
285
+ if (process.env.WALLE_NO_BRANDED_APP === '1') return null;
286
+ const node = notarizedAppVendoredNode();
287
+ const marker = path.join(notarizedAppDir(), 'version');
288
+ try {
289
+ if (fs.existsSync(node) && fs.existsSync(marker) &&
290
+ fs.readFileSync(marker, 'utf8').trim() === NOTARIZED_APP_VERSION) {
291
+ return node;
292
+ }
293
+ } catch {}
294
+ return null;
295
+ }
296
+
297
+ // codesign --verify --strict gates adoption (catches a tampered/altered bundle); the vendored node
298
+ // must carry the expected Developer-ID Team Identifier (else it's not the branded notarized app and
299
+ // would prompt as "node" anyway). spctl is informational only.
300
+ function verifyNotarizedApp(appDir, { log } = {}) {
301
+ try {
302
+ execFileSync('codesign', ['--verify', '--strict', appDir], { stdio: 'pipe', timeout: 60000 });
303
+ } catch {
304
+ return false;
305
+ }
306
+ const vendored = path.join(appDir, 'Contents', 'Resources', 'node');
307
+ if (execTeamIdentifier(vendored) !== NOTARIZED_APP_TEAM_ID) return false;
308
+ if (log) {
309
+ let assessment;
310
+ try {
311
+ execFileSync('spctl', ['-a', '-vv', '--type', 'exec', vendored], { stdio: 'pipe', timeout: 30000 });
312
+ assessment = 'accepted (notarized)';
313
+ } catch { assessment = 'signature valid (spctl unconfirmed)'; }
314
+ log(` ${DIM}app signature: ${assessment}${RESET}`);
315
+ }
316
+ return true;
317
+ }
318
+
319
+ // Download (once) the prebuilt Developer-ID-notarized Wall-E.app and adopt its vendored node as the
320
+ // daemon identity. Returns the vendored-node path on success, else null (daemon keeps its current
321
+ // node). Best-effort: any failure is non-blocking. Mirrors ensureNotarizedDaemonNode's
322
+ // download→verify→marker-last discipline.
323
+ function ensureNotarizedBrandedApp({ log = console.log } = {}) {
324
+ if (process.platform !== 'darwin') return null;
325
+ if (process.env.WALLE_NO_BRANDED_APP === '1') return null;
326
+ const arch = process.arch === 'arm64' ? 'arm64' : process.arch === 'x64' ? 'x64' : null;
327
+ if (!arch) return null;
328
+
329
+ const dir = notarizedAppDir();
330
+ const appDir = path.join(dir, 'Wall-E.app');
331
+ const vendored = notarizedAppVendoredNode();
332
+ const marker = path.join(dir, 'version');
333
+
334
+ // Cached and still valid (marker + signature) → reuse (the "download once").
335
+ if (validatedNotarizedApp() && verifyNotarizedApp(appDir)) {
336
+ return vendored;
337
+ }
338
+
339
+ let tmp;
340
+ try {
341
+ fs.mkdirSync(dir, { recursive: true });
342
+ const asset = `Wall-E-${NOTARIZED_APP_VERSION}-${arch}.zip`;
343
+ const url = `${WALLE_DIST_RELEASE_BASE}/v${NOTARIZED_APP_VERSION}/${asset}`;
344
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'walle-napp-'));
345
+ const zip = path.join(tmp, asset);
346
+ log(` Downloading notarized Wall-E.app ${NOTARIZED_APP_VERSION} (${arch})…`);
347
+ execFileSync('curl', ['-fsSL', '-o', zip, url], { timeout: 300000, stdio: 'pipe' });
348
+ // ditto -x -k preserves the code signature + stapled notarization ticket through extraction.
349
+ const exDir = path.join(tmp, 'x');
350
+ fs.mkdirSync(exDir, { recursive: true });
351
+ execFileSync('ditto', ['-x', '-k', zip, exDir], { timeout: 120000, stdio: 'pipe' });
352
+ const extractedApp = path.join(exDir, 'Wall-E.app');
353
+ if (!fs.existsSync(extractedApp)) throw new Error('archive missing Wall-E.app');
354
+ // Verify BEFORE adopting — never install an unsigned/wrong-team bundle.
355
+ if (!verifyNotarizedApp(extractedApp)) throw new Error('signature/team verification failed');
356
+ const exVendored = path.join(extractedApp, 'Contents', 'Resources', 'node');
357
+ if (!nodeReportsVersion(exVendored, NOTARIZED_NODE_VERSION)) throw new Error('vendored node ABI mismatch');
358
+
359
+ fs.rmSync(appDir, { recursive: true, force: true });
360
+ fs.rmSync(marker, { force: true });
361
+ fs.cpSync(extractedApp, appDir, { recursive: true });
362
+ fs.chmodSync(vendored, 0o755);
363
+
364
+ if (!verifyNotarizedApp(appDir, { log })) throw new Error('post-install verification failed');
365
+ fs.writeFileSync(marker, `${NOTARIZED_APP_VERSION}\n`);
366
+ log(` ${GREEN}Notarized Wall-E.app ready${RESET} ${DIM}(${appDir})${RESET}`);
367
+ return vendored;
368
+ } catch (e) {
369
+ log(` ${DIM}Notarized branded app unavailable (${e && e.message ? e.message : e}) — daemon keeps its current node${RESET}`);
370
+ disableNotarizedBrandedApp();
371
+ return null;
372
+ } finally {
373
+ if (tmp) { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
374
+ }
375
+ }
376
+
377
+ // Drop the marker (and app) so daemonExec stops trusting it. Used when the download/verify fails.
378
+ function disableNotarizedBrandedApp() {
379
+ try { fs.rmSync(path.join(notarizedAppDir(), 'version'), { force: true }); } catch {}
380
+ try { fs.rmSync(path.join(notarizedAppDir(), 'Wall-E.app'), { recursive: true, force: true }); } catch {}
381
+ }
382
+
241
383
  function writeCliLifecycleEvent(event, meta = {}) {
242
384
  if (process.env.WALLE_TELEMETRY === '0' || process.env.WALLE_TELEMETRY === 'false') return;
243
385
  try {
@@ -389,6 +531,9 @@ function install(targetDir) {
389
531
  // Fetch the notarized daemon node BEFORE installing deps, so native modules build against
390
532
  // its ABI (the daemon and its forks will all run under it). Best-effort / macOS-only.
391
533
  ensureNotarizedDaemonNode();
534
+ // Adopt the branded notarized Wall-E.app so the Screen Recording grant shows "Wall-E" (not the
535
+ // anonymous "node"). Best-effort; daemonExec prefers its vendored node when present.
536
+ ensureNotarizedBrandedApp();
392
537
 
393
538
  console.log(` Installing dependencies...\n`);
394
539
  npmInstall(targetDir);
@@ -412,6 +557,10 @@ function install(targetDir) {
412
557
  `CTM_PORT=${port}`,
413
558
  `WALL_E_PORT=${wallePort}`,
414
559
  '',
560
+ '# Terminal rendering: Option B (mosh-style server-authoritative state-sync) is on by default.',
561
+ '# Uncomment to fall back to the raw byte-stream render path.',
562
+ '# CTM_STATE_SYNC=0',
563
+ '',
415
564
  '# SLACK_TOKEN=',
416
565
  '# SLACK_OWNER_USER_ID=',
417
566
  '# SLACK_OWNER_HANDLE=',
@@ -509,6 +658,9 @@ function update() {
509
658
  // 6. Reinstall deps (in case package.json changed). Refresh the notarized daemon node first
510
659
  // so native modules rebuild against its ABI (idempotent: a valid cache is reused, not re-DLed).
511
660
  ensureNotarizedDaemonNode();
661
+ // Refresh the branded notarized Wall-E.app (idempotent) so the in-web upgrade migrates a user
662
+ // off the anonymous "node" Screen Recording identity onto the branded "Wall-E" one.
663
+ ensureNotarizedBrandedApp();
512
664
  console.log(` Installing dependencies...\n`);
513
665
  npmInstall(dir);
514
666
 
@@ -1537,4 +1689,12 @@ module.exports = {
1537
1689
  notarizedNodeDir,
1538
1690
  notarizedNodePath,
1539
1691
  NOTARIZED_NODE_VERSION,
1692
+ ensureNotarizedBrandedApp,
1693
+ validatedNotarizedApp,
1694
+ verifyNotarizedApp,
1695
+ disableNotarizedBrandedApp,
1696
+ notarizedAppDir,
1697
+ notarizedAppVendoredNode,
1698
+ NOTARIZED_APP_VERSION,
1699
+ NOTARIZED_APP_TEAM_ID,
1540
1700
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-walle",
3
- "version": "0.9.28",
3
+ "version": "0.9.29",
4
4
  "description": "CTM + Wall-E — AI coding dashboard and personal digital twin agent. Multi-agent terminal for Claude Code, Codex, Gemini, Aider, OpenCode, and more, plus prompt editor, task queue, remote phone and tablet access, code/doc review, and an agent that learns from Slack, email & calendar.",
5
5
  "bin": {
6
6
  "create-walle": "bin/create-walle.js"
@@ -47,6 +47,13 @@ CTM_BUNDLE="$HOME/.walle/bundles/Coding Task Manager.app/Contents/MacOS/Coding T
47
47
  if _ver_match "$CTM_BUNDLE" && codesign -dv "$CTM_BUNDLE" 2>&1 | grep -q '^TeamIdentifier=[A-Z0-9]'; then
48
48
  exec "$CTM_BUNDLE" "$SERVER" "$@"
49
49
  fi
50
+ # 2.5) The adopted Developer-ID-notarized Wall-E.app's vendored node (create-walle
51
+ # ensureNotarizedBrandedApp) — branded "Wall-E" AND grant-persisting, for no-Dev-ID machines.
52
+ # Gated on the version match + a stable Team Identifier (skips a self-signed/wrong bundle).
53
+ BRANDED_APP_NODE="$HOME/.walle/notarized-app/Wall-E.app/Contents/Resources/node"
54
+ if _ver_match "$BRANDED_APP_NODE" && codesign -dv "$BRANDED_APP_NODE" 2>&1 | grep -q '^TeamIdentifier=[A-Z0-9]'; then
55
+ exec "$BRANDED_APP_NODE" "$SERVER" "$@"
56
+ fi
50
57
  # 3) Notarized node we provisioned ourselves (~/.walle/notarized-node) — stable but anonymous
51
58
  # ("node"); the fallback for machines without a Developer ID.
52
59
  NOTARIZED_NODE="$HOME/.walle/notarized-node/bin/node"
@@ -221,6 +221,11 @@ elif [[ "$MODE" == "refresh" ]]; then
221
221
  elif [[ "$COPY_IMAGES" != "1" ]]; then
222
222
  echo " Images: skipped (--no-images)"
223
223
  fi
224
+ rm -rf "$DEV_DIR/scrollback"
225
+ if [[ -d "$PROD_CTM_DIR/scrollback" ]]; then
226
+ echo " Scrollback: syncing $PROD_CTM_DIR/scrollback -> $DEV_DIR/scrollback"
227
+ "$NODE_BIN" "$ROOT/bin/sync-images.js" "$PROD_CTM_DIR/scrollback" "$DEV_DIR/scrollback"
228
+ fi
224
229
  # Ensure the dev instance owns its WAL files from first open.
225
230
  rm -f "$DEV_DIR"/*.db-wal "$DEV_DIR"/*.db-shm
226
231
  elif [[ "$MODE" == "reuse" ]]; then
@@ -1053,7 +1053,6 @@ const {
1053
1053
  createCodexUserDeduper,
1054
1054
  parseCodexJsonlFileIntoMessagesAsync,
1055
1055
  parseCodexJsonlFileIntoMessages,
1056
- parseCodexJsonlIntoMessages,
1057
1056
  readCodexRolloutMetadata,
1058
1057
  } = require('./lib/session-history');
1059
1058
  const fsp = require('fs').promises;
@@ -1592,17 +1591,6 @@ function _codexUserDeduperFromMessages(messages) {
1592
1591
  return createCodexUserDeduper((Array.isArray(messages) ? messages : []).filter(msg => msg && msg.role === 'user'));
1593
1592
  }
1594
1593
 
1595
- async function _readFileRange(filePath, start, length) {
1596
- const fh = await fsp.open(filePath, 'r');
1597
- try {
1598
- const buf = Buffer.alloc(length);
1599
- const { bytesRead } = await fh.read(buf, 0, length, start);
1600
- return buf.subarray(0, bytesRead).toString('utf8');
1601
- } finally {
1602
- await fh.close();
1603
- }
1604
- }
1605
-
1606
1594
  function _conversationImportIndexRows() {
1607
1595
  // Attribution: two full-table index scans built on every conversation-import
1608
1596
  // tick. They run as the sync prefix of _conversationImportCandidates, which is
@@ -1794,8 +1782,15 @@ async function _importCodexSessionFile(parsed, filePath, options = {}) {
1794
1782
  const newMessages = [];
1795
1783
  let parsedTail;
1796
1784
  if (prevFileSize > 0 && parsed.fileSize > prevFileSize) {
1797
- const content = await _readFileRange(filePath, prevFileSize, parsed.fileSize - prevFileSize);
1798
- parsedTail = parseCodexJsonlIntoMessages(content, newMessages, { codexUserDeduper });
1785
+ // Stream the new tail [prevFileSize, EOF) in bounded 1 MiB chunks, line-by-line. Reading the
1786
+ // whole delta in one fs.read (the old _readFileRange path) passed a >2 GiB `length` for a
1787
+ // session that grew >2 GiB since the last import, tripping V8's Int32 assertion in node::fs::Read
1788
+ // and ABORTING the process (uncatchable) → launchd respawn loop → all Codex sessions unusable.
1789
+ parsedTail = await parseCodexJsonlFileIntoMessagesAsync(filePath, newMessages, {
1790
+ codexUserDeduper,
1791
+ startOffset: prevFileSize,
1792
+ yieldAfterMs: options.yieldAfterMs || 25,
1793
+ });
1799
1794
  } else if (options.cooperative) {
1800
1795
  parsedTail = await parseCodexJsonlFileIntoMessagesAsync(filePath, newMessages, {
1801
1796
  codexUserDeduper,
@@ -2407,6 +2402,16 @@ function handleGetConversation(req, res, url) {
2407
2402
  const sessionId = url.pathname.split('/').pop();
2408
2403
  const conv = db.getSessionConversation(sessionId);
2409
2404
  if (!conv) return jsonResponse(res, 404, { error: 'Not found' });
2405
+ // The stored `messages` blob is retired ('[]') by default — the conversation lives in the
2406
+ // faithful session_message_rows. Hydrate the blob from rows for this cold API read so callers
2407
+ // still receive the turns. O(N) reconstruction, paid only on this rare endpoint (the hot UI
2408
+ // paths page rows directly and never hit this).
2409
+ if ((!conv.messages || conv.messages === '[]') && typeof db.getSessionMessagesArray === 'function') {
2410
+ try {
2411
+ const rows = db.getSessionMessagesArray(sessionId, { fallbackToBlob: false });
2412
+ if (Array.isArray(rows) && rows.length) conv.messages = JSON.stringify(rows);
2413
+ } catch { /* fall through with the empty blob */ }
2414
+ }
2410
2415
  jsonResponse(res, 200, conv);
2411
2416
  }
2412
2417
 
@@ -2926,10 +2931,22 @@ function screenshotResponsibleContext() {
2926
2931
  try { nodeIsBundle = require('./lib/real-node').isBundleExec(node); } catch {}
2927
2932
  const useBridge = disclaimOk && !!node && !nodeIsBundle;
2928
2933
  const responsiblePath = useBridge ? node : process.execPath;
2929
- const responsibleName = responsiblePath ? path.basename(responsiblePath) : 'CTM';
2934
+ const responsibleName = screenshotResponsibleDisplayName(responsiblePath);
2930
2935
  return { realNode: node, disclaim, useBridge, responsiblePath, responsibleName, granted };
2931
2936
  }
2932
2937
 
2938
+ // macOS attributes the Screen Recording grant to the responsible binary's code SIGNATURE, and shows
2939
+ // the enclosing .app's CFBundleName. The adopted Developer-ID-notarized app's vendored node
2940
+ // (~/.walle/notarized-app/Wall-E.app/Contents/Resources/node, create-walle ensureNotarizedBrandedApp)
2941
+ // and the branded daemon bundles list as "Wall-E"/"Coding Task Manager", not the basename "node" —
2942
+ // so the guidance must name the entry the user will actually see, else they grant the wrong thing.
2943
+ function screenshotResponsibleDisplayName(responsiblePath) {
2944
+ if (!responsiblePath) return 'CTM';
2945
+ if (/(?:^|\/)Wall-E\.app\//.test(responsiblePath) || responsiblePath.includes('/.walle/notarized-app/')) return 'Wall-E';
2946
+ if (/(?:^|\/)Coding Task Manager\.app\//.test(responsiblePath)) return 'Coding Task Manager';
2947
+ return path.basename(responsiblePath);
2948
+ }
2949
+
2933
2950
  function screenshotCaptureCommand(tmpFile, context = {}) {
2934
2951
  if (context.useBridge) {
2935
2952
  return {