create-walle 0.9.28 → 0.9.30

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 (140) 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 +70 -18
  5. package/template/bin/dev.sh +18 -0
  6. package/template/bin/ensure-stable-node.js +11 -0
  7. package/template/bin/node-bin.sh +9 -0
  8. package/template/claude-task-manager/api-prompts.js +214 -23
  9. package/template/claude-task-manager/db.js +884 -50
  10. package/template/claude-task-manager/docs/backfill-incremental-no-main-fallback.md +48 -0
  11. package/template/claude-task-manager/docs/conversation-import-freshness.md +21 -0
  12. package/template/claude-task-manager/docs/conversation-log-redesign.html +587 -0
  13. package/template/claude-task-manager/docs/session-title-authority.md +8 -3
  14. package/template/claude-task-manager/lib/auth-rules.js +13 -0
  15. package/template/claude-task-manager/lib/claude-desktop-sessions.js +63 -0
  16. package/template/claude-task-manager/lib/codex-config-guard.js +124 -0
  17. package/template/claude-task-manager/lib/codex-rollout-snapshot.js +93 -0
  18. package/template/claude-task-manager/lib/coding-agent-models.js +5 -4
  19. package/template/claude-task-manager/lib/db-owner-cooperative-scheduler.js +114 -0
  20. package/template/claude-task-manager/lib/db-owner-task-queue.js +67 -0
  21. package/template/claude-task-manager/lib/db-owner-worker-client.js +5 -1
  22. package/template/claude-task-manager/lib/desktop-fork.js +81 -0
  23. package/template/claude-task-manager/lib/headless-term-service.js +251 -4
  24. package/template/claude-task-manager/lib/message-identity.js +115 -0
  25. package/template/claude-task-manager/lib/mirror-feed-guards.js +25 -0
  26. package/template/claude-task-manager/lib/mirror-feed-sanitize.js +45 -0
  27. package/template/claude-task-manager/lib/path-suggest.js +77 -0
  28. package/template/claude-task-manager/lib/prompt-index-inputs.js +136 -0
  29. package/template/claude-task-manager/lib/real-node.js +36 -4
  30. package/template/claude-task-manager/lib/restore-auto-resume-policy.js +67 -0
  31. package/template/claude-task-manager/lib/restore-resume-batch.js +20 -0
  32. package/template/claude-task-manager/lib/restore-terminal-dims.js +109 -0
  33. package/template/claude-task-manager/lib/resume-cwd.js +124 -3
  34. package/template/claude-task-manager/lib/runtime-approval-recorder.js +152 -0
  35. package/template/claude-task-manager/lib/runtime-context-truth.js +236 -0
  36. package/template/claude-task-manager/lib/runtime-contract.js +195 -0
  37. package/template/claude-task-manager/lib/runtime-history-builder.js +205 -0
  38. package/template/claude-task-manager/lib/runtime-hook-bus.js +98 -0
  39. package/template/claude-task-manager/lib/runtime-input-queue.js +114 -0
  40. package/template/claude-task-manager/lib/runtime-input-recorder.js +156 -0
  41. package/template/claude-task-manager/lib/runtime-lineage.js +189 -0
  42. package/template/claude-task-manager/lib/runtime-registry.js +263 -0
  43. package/template/claude-task-manager/lib/runtime-session-history.js +41 -0
  44. package/template/claude-task-manager/lib/scrollback-snapshot-policy.js +37 -0
  45. package/template/claude-task-manager/lib/server-phase-conditions.js +103 -0
  46. package/template/claude-task-manager/lib/session-content-backfill.js +55 -8
  47. package/template/claude-task-manager/lib/session-db-read-contract.js +67 -0
  48. package/template/claude-task-manager/lib/session-history.js +93 -5
  49. package/template/claude-task-manager/lib/session-host-manager.js +154 -2
  50. package/template/claude-task-manager/lib/session-messages-defer.js +50 -0
  51. package/template/claude-task-manager/lib/session-messages-page.js +13 -0
  52. package/template/claude-task-manager/lib/session-messages-projection.js +48 -29
  53. package/template/claude-task-manager/lib/session-stream.js +80 -17
  54. package/template/claude-task-manager/lib/session-title-signals.js +54 -0
  55. package/template/claude-task-manager/lib/session-token-usage.js +13 -0
  56. package/template/claude-task-manager/lib/state-sync/cell-diff.js +41 -0
  57. package/template/claude-task-manager/lib/state-sync/frame-emitter.js +214 -0
  58. package/template/claude-task-manager/lib/state-sync/frame-rate.js +75 -0
  59. package/template/claude-task-manager/lib/state-sync/row-serializer.js +166 -0
  60. package/template/claude-task-manager/lib/terminal-fingerprint.js +19 -3
  61. package/template/claude-task-manager/lib/transcript-ingest-chunker.js +41 -0
  62. package/template/claude-task-manager/lib/transcript-store.js +99 -7
  63. package/template/claude-task-manager/lib/wal-checkpoint-policy.js +40 -0
  64. package/template/claude-task-manager/lib/walle-session-model-catalog.js +100 -9
  65. package/template/claude-task-manager/lib/worktree-output-binding.js +93 -0
  66. package/template/claude-task-manager/lib/write-coalescer.js +83 -0
  67. package/template/claude-task-manager/public/css/walle-session.css +4 -0
  68. package/template/claude-task-manager/public/css/walle.css +0 -66
  69. package/template/claude-task-manager/public/index.html +1707 -266
  70. package/template/claude-task-manager/public/js/feedback.js +8 -1
  71. package/template/claude-task-manager/public/js/message-renderer.js +72 -2
  72. package/template/claude-task-manager/public/js/session-phase.js +4 -0
  73. package/template/claude-task-manager/public/js/session-status-precedence.js +7 -173
  74. package/template/claude-task-manager/public/js/setup.js +46 -3
  75. package/template/claude-task-manager/public/js/state-sync-client.js +257 -0
  76. package/template/claude-task-manager/public/js/state-sync-predictor.js +41 -0
  77. package/template/claude-task-manager/public/js/stream-view.js +113 -9
  78. package/template/claude-task-manager/public/js/terminal-reconciler.js +24 -4
  79. package/template/claude-task-manager/public/js/walle-session.js +239 -19
  80. package/template/claude-task-manager/public/js/walle.js +32 -119
  81. package/template/claude-task-manager/queue-engine.js +140 -0
  82. package/template/claude-task-manager/server.js +2802 -416
  83. package/template/claude-task-manager/session-integrity.js +16 -1
  84. package/template/claude-task-manager/workers/db-owner-worker.js +23 -6
  85. package/template/claude-task-manager/workers/read-pool-worker.js +55 -1
  86. package/template/claude-task-manager/workers/session-host-pool-process.js +193 -0
  87. package/template/claude-task-manager/workers/session-host-process.js +47 -11
  88. package/template/claude-task-manager/workers/state-detectors/codex.js +33 -0
  89. package/template/package.json +1 -1
  90. package/template/wall-e/agent.js +191 -31
  91. package/template/wall-e/api-walle.js +97 -52
  92. package/template/wall-e/auth/flow-manager.js +78 -1
  93. package/template/wall-e/auth/provider-flows.js +56 -2
  94. package/template/wall-e/bin/walle-mcp-stdio.js +138 -5
  95. package/template/wall-e/brain.js +175 -13
  96. package/template/wall-e/chat.js +46 -1
  97. package/template/wall-e/embeddings.js +70 -0
  98. package/template/wall-e/events/event-bus.js +11 -1
  99. package/template/wall-e/http/auth.js +3 -1
  100. package/template/wall-e/http/model-admin.js +22 -0
  101. package/template/wall-e/lib/brain-owner-worker-client.js +36 -4
  102. package/template/wall-e/lib/diagnostics-flags.js +9 -0
  103. package/template/wall-e/lib/event-loop-monitor.js +84 -5
  104. package/template/wall-e/lib/mcp-scan-lifecycle.js +247 -0
  105. package/template/wall-e/lib/parent-brain-owner-client.js +109 -0
  106. package/template/wall-e/lib/runtime-process-inventory.js +114 -0
  107. package/template/wall-e/lib/runtime-worker-pool.js +214 -23
  108. package/template/wall-e/lib/scheduler-worker-jobs.js +49 -4
  109. package/template/wall-e/lib/scheduler.js +320 -35
  110. package/template/wall-e/lib/slack-identity.js +120 -0
  111. package/template/wall-e/lib/slack-permalink.js +107 -0
  112. package/template/wall-e/lib/slack-web.js +174 -0
  113. package/template/wall-e/lib/worker-thread-pool.js +55 -4
  114. package/template/wall-e/llm/claude-cli.js +21 -3
  115. package/template/wall-e/llm/cli-binary.js +90 -0
  116. package/template/wall-e/llm/codex-cli.js +113 -49
  117. package/template/wall-e/llm/default-fallback.js +10 -4
  118. package/template/wall-e/llm/mlx.js +46 -8
  119. package/template/wall-e/llm/model-catalog.js +129 -17
  120. package/template/wall-e/llm/provider-detector.js +112 -22
  121. package/template/wall-e/loops/backfill.js +32 -16
  122. package/template/wall-e/loops/ingest.js +50 -16
  123. package/template/wall-e/loops/tasks.js +521 -25
  124. package/template/wall-e/mcp-server.js +215 -6
  125. package/template/wall-e/memory/ctm-session-context.js +93 -0
  126. package/template/wall-e/skills/_bundled/google-calendar/run.js +15 -23
  127. package/template/wall-e/skills/_bundled/gws-workspace/gws-router +237 -0
  128. package/template/wall-e/skills/_bundled/gws-workspace/setup.js +112 -1
  129. package/template/wall-e/skills/_bundled/mcp-scan/run.js +265 -41
  130. package/template/wall-e/skills/_bundled/slack-mentions/run.js +434 -93
  131. package/template/wall-e/skills/internal-skill-registry.js +27 -5
  132. package/template/wall-e/skills/mcp-client.js +18 -3
  133. package/template/wall-e/skills/script-skill-runner.js +53 -5
  134. package/template/wall-e/skills/skill-planner.js +5 -26
  135. package/template/wall-e/training/real-trajectory-miner.js +24 -114
  136. package/template/wall-e/utils/dedup.js +165 -66
  137. package/template/wall-e/weather-runtime.js +12 -4
  138. package/template/wall-e/workers/brain-owner-worker.js +68 -0
  139. package/template/wall-e/workers/runtime-worker.js +4 -0
  140. 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.30",
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"
@@ -9,10 +9,35 @@
9
9
  # LaunchAgent plist never needs to change.
10
10
  set -euo pipefail
11
11
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
12
- NODE_BIN="$(bash "$ROOT/bin/node-bin.sh")"
12
+ # CTM_LAUNCH_NODE_BIN lets a test inject the pinned node without running node-bin.sh.
13
+ NODE_BIN="${CTM_LAUNCH_NODE_BIN:-"$(bash "$ROOT/bin/node-bin.sh")"}"
13
14
  SERVER="$ROOT/claude-task-manager/server.js"
14
15
  PINNED_V="$("$NODE_BIN" -v 2>/dev/null)"
15
16
 
17
+ # Exec the chosen runtime — or, under CTM_LAUNCH_PRINT_CHOICE (tests), print the path it WOULD exec
18
+ # and exit 0 without launching the server. Keeps the candidate ladder below assertable end-to-end.
19
+ _exec_choice() {
20
+ local bin="$1"; shift
21
+ if [ -n "${CTM_LAUNCH_PRINT_CHOICE:-}" ]; then printf '%s\n' "$bin"; exit 0; fi
22
+ exec "$bin" "$SERVER" "$@"
23
+ }
24
+
25
+ # Self-heal the native SQLite driver BEFORE exec'ing the server. A hard power-off can leave
26
+ # better_sqlite3.node torn on disk, and a Homebrew node upgrade can leave it built for the wrong
27
+ # ABI. The server DETECTS this but cannot hot-reload a native module mid-process, so it wedges
28
+ # the boot: DB-owner worker dies -> "Database not initialized" everywhere -> no port bound ->
29
+ # launchd respawns into the SAME broken state every ~5s (a respawn spiral that never converges).
30
+ # Running the preflight here rebuilds the driver if needed, so the fresh `exec` below loads a good
31
+ # binary. It is cheap on the happy path (a require + smoke-test that early-returns when the ABI
32
+ # already matches) and only does the heavy `npm rebuild` when the driver is genuinely broken.
33
+ # Best-effort: a failure here never blocks the boot — the server's own in-process repair still runs.
34
+ # Skipped under CTM_LAUNCH_PRINT_CHOICE: it is irrelevant to runtime selection and needs a real DB.
35
+ CHECK_DRIVER="$ROOT/claude-task-manager/bin/check-sqlite-driver.js"
36
+ if [ -z "${CTM_LAUNCH_PRINT_CHOICE:-}" ] && [ -f "$CHECK_DRIVER" ]; then
37
+ ( cd "$ROOT/claude-task-manager" && "$NODE_BIN" "$CHECK_DRIVER" ) >/tmp/ctm-boot-sqlite-check.log 2>&1 \
38
+ || echo "[ctm-launch] sqlite driver preflight reported a problem; see /tmp/ctm-boot-sqlite-check.log (boot continues; server self-repair still applies)" >&2
39
+ fi
40
+
16
41
  # Prefer a STABLE-IDENTITY node so macOS TCC grants (e.g. the "Coding Task Manager would like to
17
42
  # access data from other apps" prompt) PERSIST across restarts. macOS keys a TCC grant to the
18
43
  # binary's code-signing Designated Requirement: a notarized / Developer-ID-signed node keeps its
@@ -23,39 +48,66 @@ PINNED_V="$("$NODE_BIN" -v 2>/dev/null)"
23
48
  # bump can never select a mismatched-ABI runtime (preserves the "upgrade = edit .node-version"
24
49
  # guarantee, and the daemon's native modules stay ABI-correct).
25
50
  _ver_match() { [ -x "$1" ] && [ "$("$1" -v 2>/dev/null)" = "$PINNED_V" ]; }
51
+ # Does this binary carry a stable code-signing Team Identifier (Developer-ID signed)? Signal that a
52
+ # TCC/Full-Disk-Access grant will PERSIST and that prompts show a branded name. A fast local read.
53
+ # NB this alone CANNOT distinguish "our branded CTM bundle" from the bare notarized node — the
54
+ # notarized node is also Developer-ID signed (Team HX7739G8FX, Node.js Foundation). The discriminator
55
+ # is the code-signing IDENTIFIER (see _is_ctm_bundle_identity).
56
+ # NB capture codesign's output FIRST, then match the string — do NOT pipe `codesign | grep -q`.
57
+ # `grep -q` exits on the first match and closes the pipe; codesign then dies with SIGPIPE (141), and
58
+ # under `set -o pipefail` (above) the pipeline is reported as FAILED even though the pattern matched.
59
+ # That false-negative is exactly what made the daemon keep running the wrong node. Capturing to a
60
+ # var runs codesign to completion (exit 0), then a here-string grep has no upstream to kill.
61
+ _has_team_id() { local o; o="$(codesign -dv "$1" 2>&1)" || true; grep -q '^TeamIdentifier=[A-Z0-9]' <<<"$o"; }
62
+ # Is this binary OUR branded CTM bundle (Identifier=com.walle.ctm)? That is the EXACT identity the
63
+ # Full Disk Access banner tells the user to grant ("Coding Task Manager.app"). The notarized node's
64
+ # Identifier is "node", so this cleanly separates the two.
65
+ _is_ctm_bundle_identity() { local o; o="$(codesign -dv "$1" 2>&1)" || true; grep -q '^Identifier=com\.walle\.ctm$' <<<"$o"; }
26
66
 
27
- # 0) The stable daemon node chosen by bin/ensure-stable-node.js (run off the boot path from
28
- # restart-ctm.sh). On a machine WITH a Developer ID this is the branded, Dev-ID-signed CTM
29
- # bundle exec both grant-persisting AND shown as "Coding Task Manager" (not "node") in TCC
30
- # prompts; without a Developer ID it is the bare notarized node. Reading the marker keeps
31
- # codesign OFF this launchd boot path; the version check rejects a stale marker.
67
+ CTM_BUNDLE="$HOME/.walle/bundles/Coding Task Manager.app/Contents/MacOS/Coding Task Manager"
68
+
69
+ # 0) The branded CTM bundle (Identifier=com.walle.ctm) the EXACT identity the FDA banner asks the
70
+ # user to grant Full Disk Access to. Prefer it ABOVE the stable-node marker. WHY first: the marker
71
+ # written by ensure-stable-node.js can point at the bare notarized node ("node", Team HX7739G8FX);
72
+ # because that node IS Developer-ID signed it would satisfy a naive "has a Team Identifier" check
73
+ # and win — the daemon then runs as "node", the user's grant to "Coding Task Manager.app" never
74
+ # applies, and the macOS "network volume"/FDA prompt recurs on EVERY restart. Checking THIS bundle
75
+ # path + its com.walle.ctm identity makes the grant-matching runtime authoritative. Gated on the
76
+ # version (ABI) match too. Costs one fast `codesign -dv` read per boot (boots are rare); on a
77
+ # no-Dev-ID machine the bundle is absent/self-signed so this falls through with no behavior change.
78
+ if _ver_match "$CTM_BUNDLE" && _is_ctm_bundle_identity "$CTM_BUNDLE" && _has_team_id "$CTM_BUNDLE"; then
79
+ _exec_choice "$CTM_BUNDLE" "$@"
80
+ fi
81
+ # 1) The stable daemon node chosen by bin/ensure-stable-node.js (run off the boot path from
82
+ # restart-ctm.sh). On a machine WITHOUT a Developer ID this is the bare notarized node — the best
83
+ # stable identity available there. Version-gated; the version check rejects a stale marker.
32
84
  MARKER="$HOME/.walle/.stable-daemon-node"
33
85
  if [ -f "$MARKER" ]; then
34
86
  STABLE_NODE="$(head -n1 "$MARKER" 2>/dev/null)"
35
87
  if [ -n "$STABLE_NODE" ] && _ver_match "$STABLE_NODE"; then
36
- exec "$STABLE_NODE" "$SERVER" "$@"
88
+ _exec_choice "$STABLE_NODE" "$@"
37
89
  fi
38
90
  fi
39
- # 1) Notarized node handed in by the downloadable Developer-ID Wall-E.app.
91
+ # 2) Notarized node handed in by the downloadable Developer-ID Wall-E.app.
40
92
  if [ -n "${WALLE_NOTARIZED_NODE:-}" ] && _ver_match "$WALLE_NOTARIZED_NODE"; then
41
- exec "$WALLE_NOTARIZED_NODE" "$SERVER" "$@"
93
+ _exec_choice "$WALLE_NOTARIZED_NODE" "$@"
42
94
  fi
43
- # 2) The branded .app bundle node when it carries a stable Team Identifier (Developer-ID-signed by
44
- # ensure-stable-node.js) — branded AND grant-persisting. `codesign -dv` is a fast local read; it
45
- # runs only on a cold boot where the marker is absent/stale.
46
- CTM_BUNDLE="$HOME/.walle/bundles/Coding Task Manager.app/Contents/MacOS/Coding Task Manager"
47
- if _ver_match "$CTM_BUNDLE" && codesign -dv "$CTM_BUNDLE" 2>&1 | grep -q '^TeamIdentifier=[A-Z0-9]'; then
48
- exec "$CTM_BUNDLE" "$SERVER" "$@"
95
+ # 2.5) The adopted Developer-ID-notarized Wall-E.app's vendored node (create-walle
96
+ # ensureNotarizedBrandedApp) — branded "Wall-E" AND grant-persisting, for no-Dev-ID machines.
97
+ # Gated on the version match + a stable Team Identifier (skips a self-signed/wrong bundle).
98
+ BRANDED_APP_NODE="$HOME/.walle/notarized-app/Wall-E.app/Contents/Resources/node"
99
+ if _ver_match "$BRANDED_APP_NODE" && _has_team_id "$BRANDED_APP_NODE"; then
100
+ _exec_choice "$BRANDED_APP_NODE" "$@"
49
101
  fi
50
102
  # 3) Notarized node we provisioned ourselves (~/.walle/notarized-node) — stable but anonymous
51
103
  # ("node"); the fallback for machines without a Developer ID.
52
104
  NOTARIZED_NODE="$HOME/.walle/notarized-node/bin/node"
53
105
  if _ver_match "$NOTARIZED_NODE"; then
54
- exec "$NOTARIZED_NODE" "$SERVER" "$@"
106
+ _exec_choice "$NOTARIZED_NODE" "$@"
55
107
  fi
56
108
  # 4) The branded bundle node even if only self-signed (branded name, but may re-prompt).
57
109
  if _ver_match "$CTM_BUNDLE"; then
58
- exec "$CTM_BUNDLE" "$SERVER" "$@"
110
+ _exec_choice "$CTM_BUNDLE" "$@"
59
111
  fi
60
112
  # 5) Last resort: the pinned node (ABI-correct, but no stable TCC identity → may re-prompt).
61
- exec "$NODE_BIN" "$SERVER" "$@"
113
+ _exec_choice "$NODE_BIN" "$@"
@@ -164,6 +164,19 @@ mkdir -p "$DEV_DIR"
164
164
  # (corruption + contention). A throwaway CODEX_HOME keeps dev codex writes — and
165
165
  # CTM's own rollout reads, which resolve via the same CODEX_HOME — inside DEV_DIR.
166
166
  mkdir -p "$DEV_DIR/codex/sessions"
167
+ # Seed the throwaway CODEX_HOME with the user's existing Codex login. The isolation above
168
+ # relocates CODEX_HOME, which ALSO relocates ~/.codex/auth.json — so without this, dev codex
169
+ # sessions are logged out and prompt for `codex login` (the "codex login issue in staging").
170
+ # Copy only auth.json from the real ~/.codex so dev codex is authenticated while rollouts/sessions
171
+ # stay isolated in DEV_DIR. We deliberately do NOT copy config.toml — it can carry MCP entries that
172
+ # point at the user's primary (e.g. the node_repl missing-binary noise). Idempotent: never clobber
173
+ # an existing dev login.
174
+ _REAL_CODEX_HOME="$HOME/.codex"
175
+ if [[ -f "$_REAL_CODEX_HOME/auth.json" && ! -f "$DEV_DIR/codex/auth.json" ]]; then
176
+ cp "$_REAL_CODEX_HOME/auth.json" "$DEV_DIR/codex/auth.json" 2>/dev/null \
177
+ && chmod 600 "$DEV_DIR/codex/auth.json" 2>/dev/null \
178
+ && echo " seeded dev CODEX_HOME auth from $_REAL_CODEX_HOME/auth.json"
179
+ fi
167
180
 
168
181
  cleanup_processes() {
169
182
  local stale_args=()
@@ -221,6 +234,11 @@ elif [[ "$MODE" == "refresh" ]]; then
221
234
  elif [[ "$COPY_IMAGES" != "1" ]]; then
222
235
  echo " Images: skipped (--no-images)"
223
236
  fi
237
+ rm -rf "$DEV_DIR/scrollback"
238
+ if [[ -d "$PROD_CTM_DIR/scrollback" ]]; then
239
+ echo " Scrollback: syncing $PROD_CTM_DIR/scrollback -> $DEV_DIR/scrollback"
240
+ "$NODE_BIN" "$ROOT/bin/sync-images.js" "$PROD_CTM_DIR/scrollback" "$DEV_DIR/scrollback"
241
+ fi
224
242
  # Ensure the dev instance owns its WAL files from first open.
225
243
  rm -f "$DEV_DIR"/*.db-wal "$DEV_DIR"/*.db-shm
226
244
  elif [[ "$MODE" == "reuse" ]]; then
@@ -65,6 +65,17 @@ function resolveStableNode(deps) {
65
65
  if (t.exec === ctmExec && team) ctmTeam = team;
66
66
  }
67
67
  if (ctmTeam) return ctmExec;
68
+ // Re-signing did not land a Team Identifier THIS run (e.g. a transient codesign failure), but
69
+ // the CTM bundle may ALREADY be Developer-ID-signed on disk from a prior run — that signature
70
+ // is self-contained and stays valid regardless. Prefer it over the anonymous notarized node so
71
+ // the marker records the BRANDED identity and the daemon keeps matching the user's Full Disk
72
+ // Access grant to "Coding Task Manager.app" (otherwise it runs as "node" → the FDA banner
73
+ // re-prompts every restart). Gated on existing + version-matching the pin (ABI safety).
74
+ if (ctmExec && existsSync(ctmExec) && (!pin || cw.nodeReportsVersion(ctmExec, pin))
75
+ && codesign.localCodeSignTeamId && codesign.localCodeSignTeamId(ctmExec)) {
76
+ log('CTM bundle already Developer-ID-signed on disk → using it (skip notarized fallback)');
77
+ return ctmExec;
78
+ }
68
79
  } catch (e) {
69
80
  log(`bundle signing failed: ${e && e.message ? e.message : e}`);
70
81
  }
@@ -44,6 +44,15 @@ if [[ -z "$PIN" ]]; then
44
44
  fi
45
45
 
46
46
  candidates=(
47
+ # Immutable, self-managed runtimes FIRST. A `brew upgrade node` (often dragged in by an
48
+ # unrelated `brew install`) repoints or deletes the Cellar dir below, which would otherwise
49
+ # leave this resolver unable to find the pin -> daemon won't start. These vendored nodes are
50
+ # provisioned by bin/ensure-stable-node.js / create-walle and are not touched by Homebrew, so
51
+ # resolution survives that churn. Every candidate is STILL gated on an exact version match with
52
+ # the pin in the loop below, so a stale vendored node is skipped (it can never select a wrong ABI).
53
+ "$HOME/.walle/notarized-node/bin/node"
54
+ "$HOME/.walle/notarized-app/Wall-E.app/Contents/Resources/node"
55
+ "$HOME/.walle/bundles/Coding Task Manager.app/Contents/MacOS/Coding Task Manager"
47
56
  "/opt/homebrew/Cellar/node/$PIN/bin/node"
48
57
  "/usr/local/Cellar/node/$PIN/bin/node"
49
58
  "$HOME/.fnm/node-versions/v$PIN/installation/bin/node"