create-walle 0.9.30 → 0.9.32

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 (112) hide show
  1. package/package.json +1 -1
  2. package/template/claude-task-manager/api-prompts.js +410 -5
  3. package/template/claude-task-manager/api-reviews.js +251 -3
  4. package/template/claude-task-manager/approval-agent.js +322 -24
  5. package/template/claude-task-manager/db.js +77 -13
  6. package/template/claude-task-manager/docs/codex-app-server-approvals.md +35 -1
  7. package/template/claude-task-manager/docs/postgres-concurrency-dgx-spark.html +407 -0
  8. package/template/claude-task-manager/docs/prompt-manager-redesign-proposal.html +667 -0
  9. package/template/claude-task-manager/docs/resume-ux-redesign.html +493 -0
  10. package/template/claude-task-manager/docs/review-redesign-proposal.html +892 -0
  11. package/template/claude-task-manager/docs/unified-auto-approver-proposal.html +520 -0
  12. package/template/claude-task-manager/git-utils.js +164 -26
  13. package/template/claude-task-manager/lib/agent-capabilities.js +26 -5
  14. package/template/claude-task-manager/lib/agent-cli-cache.js +1 -1
  15. package/template/claude-task-manager/lib/agent-presets.js +12 -0
  16. package/template/claude-task-manager/lib/agent-version-service.js +650 -0
  17. package/template/claude-task-manager/lib/approval-drift.js +57 -0
  18. package/template/claude-task-manager/lib/approval-hook.js +13 -0
  19. package/template/claude-task-manager/lib/auth-rules.js +6 -0
  20. package/template/claude-task-manager/lib/broadcast-payload-memo.js +109 -0
  21. package/template/claude-task-manager/lib/coding-agent-models.js +89 -8
  22. package/template/claude-task-manager/lib/db-owner-cooperative-scheduler.js +10 -1
  23. package/template/claude-task-manager/lib/document-review.js +75 -1
  24. package/template/claude-task-manager/lib/escalation-review.js +33 -3
  25. package/template/claude-task-manager/lib/headless-term-service.js +54 -9
  26. package/template/claude-task-manager/lib/mcp-risk.js +128 -0
  27. package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +51 -2
  28. package/template/claude-task-manager/lib/native-agent-model-args.js +149 -0
  29. package/template/claude-task-manager/lib/opencode-conversation-store.js +269 -0
  30. package/template/claude-task-manager/lib/permission-match.js +77 -5
  31. package/template/claude-task-manager/lib/read-pool-client.js +15 -6
  32. package/template/claude-task-manager/lib/restore-backoff.js +82 -0
  33. package/template/claude-task-manager/lib/runtime-registry.js +1 -0
  34. package/template/claude-task-manager/lib/scheduled-wake.js +42 -0
  35. package/template/claude-task-manager/lib/session-capture.js +33 -6
  36. package/template/claude-task-manager/lib/session-history.js +60 -3
  37. package/template/claude-task-manager/lib/session-jobs.js +19 -0
  38. package/template/claude-task-manager/lib/session-restore.js +5 -4
  39. package/template/claude-task-manager/lib/session-standup.js +2 -0
  40. package/template/claude-task-manager/lib/session-stream.js +9 -0
  41. package/template/claude-task-manager/lib/state-sync/cell-diff.js +31 -4
  42. package/template/claude-task-manager/lib/state-sync/restore-frame-hold.js +39 -0
  43. package/template/claude-task-manager/lib/tui-input-modes.js +40 -0
  44. package/template/claude-task-manager/lib/walle-ctm-history.js +103 -36
  45. package/template/claude-task-manager/lib/walle-session-model-catalog.js +7 -1
  46. package/template/claude-task-manager/lib/walle-token-chip.js +65 -0
  47. package/template/claude-task-manager/package.json +1 -1
  48. package/template/claude-task-manager/providers/claude-code.js +20 -1
  49. package/template/claude-task-manager/providers/codex-mcp-extract.js +33 -0
  50. package/template/claude-task-manager/providers/codex-mcp.js +23 -20
  51. package/template/claude-task-manager/providers/codex.js +65 -1
  52. package/template/claude-task-manager/public/css/prompts.css +345 -0
  53. package/template/claude-task-manager/public/css/reviews.css +248 -40
  54. package/template/claude-task-manager/public/css/walle-session.css +90 -26
  55. package/template/claude-task-manager/public/index.html +2031 -428
  56. package/template/claude-task-manager/public/js/document-review-links.js +255 -13
  57. package/template/claude-task-manager/public/js/file-context-menu.js +185 -0
  58. package/template/claude-task-manager/public/js/message-renderer.js +46 -1
  59. package/template/claude-task-manager/public/js/mobile-review-core.js +54 -0
  60. package/template/claude-task-manager/public/js/prompt-diff.js +55 -0
  61. package/template/claude-task-manager/public/js/prompt-editor-chrome.js +108 -0
  62. package/template/claude-task-manager/public/js/prompt-editor-modes.js +87 -0
  63. package/template/claude-task-manager/public/js/prompt-organize.js +179 -0
  64. package/template/claude-task-manager/public/js/prompt-reuse.js +77 -0
  65. package/template/claude-task-manager/public/js/prompts.js +683 -215
  66. package/template/claude-task-manager/public/js/resume-state.js +186 -0
  67. package/template/claude-task-manager/public/js/reviews.js +402 -121
  68. package/template/claude-task-manager/public/js/screenshot-router.js +91 -0
  69. package/template/claude-task-manager/public/js/session-search-utils.js +81 -0
  70. package/template/claude-task-manager/public/js/state-sync-client.js +134 -17
  71. package/template/claude-task-manager/public/js/stream-view.js +93 -20
  72. package/template/claude-task-manager/public/js/walle-session.js +183 -32
  73. package/template/claude-task-manager/public/js/walle.js +21 -9
  74. package/template/claude-task-manager/public/m/app.css +117 -0
  75. package/template/claude-task-manager/public/m/app.js +309 -3
  76. package/template/claude-task-manager/public/m/index.html +56 -1
  77. package/template/claude-task-manager/server.js +724 -305
  78. package/template/claude-task-manager/workers/db-owner-worker.js +8 -0
  79. package/template/claude-task-manager/workers/read-pool-worker.js +4 -0
  80. package/template/claude-task-manager/workers/state-detectors/claude-code.js +10 -1
  81. package/template/docs/proposals/2026-06-23-local-model-right-path.html +340 -0
  82. package/template/docs/proposals/2026-06-24-qlora-done-right.md +255 -0
  83. package/template/package.json +1 -1
  84. package/template/wall-e/api-walle.js +21 -3
  85. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +27 -4
  86. package/template/wall-e/brain.js +50 -5
  87. package/template/wall-e/chat/force-compact.js +73 -0
  88. package/template/wall-e/chat.js +137 -9
  89. package/template/wall-e/coding/action-memory-policy.js +120 -1
  90. package/template/wall-e/coding/compaction-service.js +10 -3
  91. package/template/wall-e/coding/model-router.js +116 -0
  92. package/template/wall-e/coding-orchestrator.js +303 -11
  93. package/template/wall-e/coding-prompts.js +1 -1
  94. package/template/wall-e/docs/skill-self-heal-design.html +429 -0
  95. package/template/wall-e/llm/client.js +115 -6
  96. package/template/wall-e/llm/codex-cli.js +71 -23
  97. package/template/wall-e/llm/codex-cli.plugin.json +1 -0
  98. package/template/wall-e/llm/default-fallback.js +71 -11
  99. package/template/wall-e/llm/mlx-worker.js +0 -0
  100. package/template/wall-e/llm/mlx.js +46 -12
  101. package/template/wall-e/llm/provider-error.js +9 -3
  102. package/template/wall-e/llm/provider-health-state.js +561 -11
  103. package/template/wall-e/llm/registry.js +6 -0
  104. package/template/wall-e/llm/routing-policy.js +255 -25
  105. package/template/wall-e/llm/tool-call-validator.js +156 -0
  106. package/template/wall-e/skills/skill-dispatch-decision.js +82 -2
  107. package/template/wall-e/skills/skill-planner.js +72 -5
  108. package/template/wall-e/tools/command-registry.js +34 -0
  109. package/template/wall-e/training/gemma-e4b-qlora.js +197 -1
  110. package/template/wall-e/training/mlx_lora_launch.py +196 -0
  111. package/template/wall-e/training/training-coexistence.sh +87 -0
  112. package/template/wall-e/training/training-mem-guard.sh +76 -0
@@ -1814,6 +1814,13 @@ function runMigrations() {
1814
1814
  if (!scCols2.find(c => c.name === 'extracted_source_len')) {
1815
1815
  getDb().prepare("ALTER TABLE session_conversations ADD COLUMN extracted_source_len INTEGER DEFAULT -1").run();
1816
1816
  }
1817
+ // Byte size of the .jsonl.bak at last full compact-pair stitch. The .bak is byte-stable between
1818
+ // compactions, so a later .jsonl-only growth can append just the new tail (priorJsonlSize =
1819
+ // file_size - compact_bak_size) instead of re-reading+re-stitching the whole (100MB+) pair — the
1820
+ // giant compacted-session import freeze. 0 = not a compact-pair / unknown → take the full path.
1821
+ if (!scCols2.find(c => c.name === 'compact_bak_size')) {
1822
+ getDb().prepare("ALTER TABLE session_conversations ADD COLUMN compact_bak_size INTEGER DEFAULT 0").run();
1823
+ }
1817
1824
  if (!scCols2.find(c => c.name === 'last_message_at')) {
1818
1825
  getDb().prepare("ALTER TABLE session_conversations ADD COLUMN last_message_at TEXT").run();
1819
1826
  try {
@@ -4164,7 +4171,7 @@ function getPrompt(id) {
4164
4171
  }
4165
4172
 
4166
4173
  function listPrompts({ folder_id, context_type, search, starred, limit, offset, include_children, lifecycle_status } = {}) {
4167
- let sql = `SELECT p.*, (SELECT COUNT(*) FROM prompts c WHERE c.parent_id = p.id) as child_count FROM prompts p WHERE 1=1`;
4174
+ let sql = `SELECT p.*, (SELECT COUNT(*) FROM prompts c WHERE c.parent_id = p.id) as child_count, (SELECT COUNT(*) FROM prompt_usage u WHERE u.prompt_id = p.id) as use_count, (SELECT MAX(v.version) FROM prompt_versions v WHERE v.prompt_id = p.id) as version FROM prompts p WHERE 1=1`;
4168
4175
  const params = [];
4169
4176
 
4170
4177
  // By default, only show top-level prompts (parent_id IS NULL)
@@ -5030,6 +5037,19 @@ const _importTokenEstimateLen = new Map();
5030
5037
  function _blobRetirementActive() {
5031
5038
  return process.env.CTM_DUAL_WRITE_BLOB !== '1' && process.env.CTM_SESSION_ROWS !== '0';
5032
5039
  }
5040
+ // Belt-and-suspenders: the over-cap guard above should already skip the whole-array stringify for a
5041
+ // multi-GB conversation, but if any caller ever under-reports the size again, a >512MB JSON.stringify
5042
+ // would throw a RangeError and abort the import (and the loop). Degrade to an empty blob instead — the
5043
+ // session then renders from rows / the streaming transcript-store, exactly like the over-cap path.
5044
+ function _safeBlobStringify(messages) {
5045
+ try {
5046
+ return JSON.stringify(messages || []);
5047
+ } catch (e) {
5048
+ console.error(`[db] blob stringify failed (${e.message}) — storing empty blob, reads fall back to rows.`);
5049
+ return '[]';
5050
+ }
5051
+ }
5052
+
5033
5053
  function importSessionConversation({
5034
5054
  session_id, project_path, messages, user_msg_count, assistant_msg_count,
5035
5055
  search_messages,
@@ -5047,6 +5067,16 @@ function importSessionConversation({
5047
5067
  // while content-rows-backfill fills rows across slices (chunked/cold-import route). Over-cap still
5048
5068
  // wins — a multi-GB stringify is never safe regardless of this flag.
5049
5069
  forceBlobWrite,
5070
+ // Byte size of the .jsonl.bak at this compact-pair stitch (0 / omitted for non-compact imports).
5071
+ // Persisted so a later .jsonl-only growth can append just the new tail instead of re-stitching the
5072
+ // whole pair. Always written so a session that stops being a compact-pair resets to 0.
5073
+ compact_bak_size,
5074
+ // Truthful on-disk transcript size, used ONLY for the over-cap decision below. Some callers pass a
5075
+ // `file_size` that under-reports the real conversation size (codex `importedFileSize` is a
5076
+ // consumed-bytes high-water-mark), which would let a multi-GB rollout slip under the parse cap and
5077
+ // then throw at JSON.stringify (>512MB). Decision-only: the stored `file_size` column is unchanged.
5078
+ // Defaults to `file_size` when omitted (back-compat for every existing caller).
5079
+ parse_size_bytes,
5050
5080
  }) {
5051
5081
  // Attribution: the whole import is one synchronous span — a JSON.stringify of
5052
5082
  // the full message array (multi-MB for 2000+ prompt sessions) + an upsert + a
@@ -5064,7 +5094,12 @@ function importSessionConversation({
5064
5094
  // heavy work; the streaming transcript-store rows remain the source of truth.
5065
5095
  let _isOverParseCap = null;
5066
5096
  try { _isOverParseCap = require('./lib/size-cap').isOverParseCap; } catch { /* optional */ }
5067
- const _overParseCap = typeof _isOverParseCap === 'function' ? _isOverParseCap(file_size) : false;
5097
+ // Prefer the truthful on-disk size when the caller provides it; fall back to file_size. isOverParseCap
5098
+ // already fails open on a non-finite/zero value, so the fallback is robust.
5099
+ const _parseSizeBytes = Number.isFinite(Number(parse_size_bytes)) && Number(parse_size_bytes) > 0
5100
+ ? Number(parse_size_bytes)
5101
+ : file_size;
5102
+ const _overParseCap = typeof _isOverParseCap === 'function' ? _isOverParseCap(_parseSizeBytes) : false;
5068
5103
  // Phase 7 (blob retirement): reads are served from session_message_rows (the
5069
5104
  // faithful per-message store), so the monolithic `messages` blob is dead weight
5070
5105
  // and its full-array JSON.stringify on every append is the O(N²) write cost we
@@ -5080,7 +5115,7 @@ function importSessionConversation({
5080
5115
  const _skipBlobWrite = _overParseCap || (_retireBlob && !forceBlobWrite);
5081
5116
  if (_overParseCap && !_overCapImportLogged.has(session_id)) {
5082
5117
  _overCapImportLogged.add(session_id);
5083
- console.warn(`[db] session_conversations import over parse cap (file_size=${file_size}) for ${String(session_id || '').slice(0, 8)} — storing empty blob, skipping full stringify/row-rewrite.`);
5118
+ console.warn(`[db] session_conversations import over parse cap (parse_size=${_parseSizeBytes}, file_size=${file_size}) for ${String(session_id || '').slice(0, 8)} — storing empty blob, skipping full stringify/row-rewrite.`);
5084
5119
  }
5085
5120
  // Estimate-tier token fallback, FOLDED INTO the upsert below (no extra write /
5086
5121
  // WAL flush). Stored so providers without an authoritative transcript
@@ -5115,8 +5150,8 @@ function importSessionConversation({
5115
5150
 
5116
5151
  const _doImport = () => {
5117
5152
  getDb().prepare(
5118
- `INSERT INTO session_conversations (ctm_session_id, project_path, messages, user_msg_count, assistant_msg_count, title, first_message, git_branch, file_size, session_created_at, last_message_at, hostname, model_provider, model_id, import_parser_version, last_user_content, first_assistant_text, rename_name, tokens_total, tokens_ctx, tokens_window, tokens_exact, tokens_breakdown, tokens_updated_at)
5119
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
5153
+ `INSERT INTO session_conversations (ctm_session_id, project_path, messages, user_msg_count, assistant_msg_count, title, first_message, git_branch, file_size, session_created_at, last_message_at, hostname, model_provider, model_id, import_parser_version, last_user_content, first_assistant_text, rename_name, tokens_total, tokens_ctx, tokens_window, tokens_exact, tokens_breakdown, compact_bak_size, tokens_updated_at)
5154
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
5120
5155
  ON CONFLICT(ctm_session_id) DO UPDATE SET
5121
5156
  project_path=excluded.project_path,
5122
5157
  messages=excluded.messages, user_msg_count=excluded.user_msg_count,
@@ -5143,16 +5178,18 @@ function importSessionConversation({
5143
5178
  tokens_exact=CASE WHEN excluded.tokens_total IS NOT NULL THEN excluded.tokens_exact ELSE session_conversations.tokens_exact END,
5144
5179
  tokens_breakdown=COALESCE(excluded.tokens_breakdown, session_conversations.tokens_breakdown),
5145
5180
  tokens_updated_at=CASE WHEN excluded.tokens_total IS NOT NULL THEN excluded.tokens_updated_at ELSE session_conversations.tokens_updated_at END,
5181
+ compact_bak_size=excluded.compact_bak_size,
5146
5182
  imported_at=datetime('now')`
5147
5183
  ).run(
5148
- session_id, project_path || '', _skipBlobWrite ? '[]' : JSON.stringify(messages || []),
5184
+ session_id, project_path || '', _skipBlobWrite ? '[]' : _safeBlobStringify(messages),
5149
5185
  user_msg_count || 0, assistant_msg_count || 0, title || '',
5150
5186
  first_message || '', git_branch || '', file_size || 0, session_created_at || '',
5151
5187
  _lastMessageAt,
5152
5188
  hostname || require('os').hostname(), model_provider || '', model_id || '',
5153
5189
  Number(import_parser_version || 0),
5154
5190
  last_user_content || '', first_assistant_text || '', rename_name || '',
5155
- _tokTotal, _tokCtx, _tokWindow, _tokExact, _tokBreakdown
5191
+ _tokTotal, _tokCtx, _tokWindow, _tokExact, _tokBreakdown,
5192
+ Number(compact_bak_size) || 0
5156
5193
  );
5157
5194
 
5158
5195
  // Chunked-import route: rows are written later by content-rows-backfill (in windows). Stamp the
@@ -8190,9 +8227,16 @@ function appendSessionMessageRows(sessionId, newMessages, expectedBaseCount) {
8190
8227
  const count = Number(d.prepare('SELECT COUNT(*) AS n FROM session_message_rows WHERE ctm_session_id = ?').get(sessionId).n);
8191
8228
  // Dense 0..count-1 block — no gaps, starts at 0 — else appending would misorder.
8192
8229
  if (count === 0 || minRow == null || minRow.idx !== 0 || count !== Number(maxRow.idx) + 1) return null;
8193
- // The row count must equal the base length the delta was parsed against. When the caller can't
8194
- // prove it (HWM unset / -1), refuse and let the full path re-establish the invariant.
8195
- if (!Number.isFinite(expectedBaseCount) || expectedBaseCount < 0 || count !== expectedBaseCount) return null;
8230
+ // The dense row base must COVER the base length the delta was parsed against (count >= HWM).
8231
+ // A base SHORTER than the HWM (count < expectedBaseCount) is still backfilling / incomplete
8232
+ // refuse so the full path re-establishes the invariant. A base AHEAD of the HWM (count > HWM) is
8233
+ // the giant compact-pair drift: a chunked full re-stitch stamps extracted_source_len = parseCount
8234
+ // but never rewrites the rows, so a piecewise-append row count sits a few ahead of the HWM. The
8235
+ // delta's byte boundary (file_size - bakSize) is independent of this count drift, so the delta is
8236
+ // still strictly-new bytes — appending onto the (dense, gap-free, 0-based) base at `count` is
8237
+ // correct and re-aligns the HWM (newLen = count + add.length). Refusing here instead forced a
8238
+ // perpetual full re-stitch of the 100MB+ pair every growth = the multi-second main-loop freeze.
8239
+ if (!Number.isFinite(expectedBaseCount) || expectedBaseCount < 0 || count < expectedBaseCount) return null;
8196
8240
  const insertRow = d.prepare(
8197
8241
  'INSERT OR IGNORE INTO session_message_rows (ctm_session_id, message_index, role, text, timestamp, meta) VALUES (?, ?, ?, ?, ?, ?)'
8198
8242
  );
@@ -8354,6 +8398,22 @@ function sessionContentRowsAvailable(sessionId) {
8354
8398
  } catch { return false; }
8355
8399
  }
8356
8400
 
8401
+ // True when a chunked-route import deferred this session's per-message rows (extracted_source_len
8402
+ // stamped as the HWM) but the windowed content-rows-backfill hasn't filled them yet. This is the
8403
+ // exact window where the O(Δ) appendSessionConversation fast-path can't prove its row base and
8404
+ // refuses ('rows-not-appendable') — without this signal the caller falls through to a multi-second
8405
+ // full-rebuild blob re-parse (the giant-session restart freeze). A session with no deferred-rows HWM
8406
+ // (expected <= 0) is NOT pending: it's either a normal inline import or simply has no rows to wait on.
8407
+ function sessionRowsBackfillPending(sessionId) {
8408
+ try {
8409
+ const d = getDb();
8410
+ const conv = d.prepare('SELECT extracted_source_len FROM session_conversations WHERE ctm_session_id = ?').get(sessionId);
8411
+ const expected = conv ? Number(conv.extracted_source_len) : 0;
8412
+ if (!(expected > 0)) return false; // no deferred-rows HWM → nothing to wait on
8413
+ return !sessionContentRowsAvailable(sessionId); // HWM set but rows < HWM → backfill still pending
8414
+ } catch { return false; }
8415
+ }
8416
+
8357
8417
  // Newest-first offset page (matches lib/message-pagination.js semantics), O(limit) rows.
8358
8418
  // Delegates to the shared lib/session-messages-page module so the SAME logic runs on the main
8359
8419
  // thread (here) and on the read-pool worker (off-thread, read-only handle) — parity by construction.
@@ -8473,8 +8533,12 @@ function runContentRowsBackfillSweep(options = {}) {
8473
8533
  const budgetMs = Math.max(0, Number(o.budgetMs ?? process.env.CTM_ROWS_BACKFILL_BUDGET_MS ?? 250));
8474
8534
  const maxRows = Math.max(0, Number(o.maxRows ?? process.env.CTM_ROWS_BACKFILL_MAX_ROWS ?? 3000));
8475
8535
  // Per-session row cap: migrate a giant session in bounded slices across ticks (resumable) so one
8476
- // session can't hold the write lock for its whole length. 0 = whole-session (legacy/tests).
8477
- const maxRowsPerSession = Math.max(0, Number(o.maxRowsPerSession ?? process.env.CTM_ROWS_BACKFILL_MAX_ROWS_PER_SESSION ?? 2000));
8536
+ // session can't hold the write lock for its whole length. 0 = whole-session (legacy/tests). Default
8537
+ // 500: appendChunk holds the cross-process write lock for the whole row batch, and at the measured
8538
+ // ~6-7ms/row under Dropbox-WAL contention a 2000-row batch ran ~13.6s — over the coop 5s ceiling,
8539
+ // starving interactive writers during a giant session's first backfill. 500 keeps each batch's
8540
+ // lock-hold ~3-4s (under the ceiling, with margin); tune via CTM_ROWS_BACKFILL_MAX_ROWS_PER_SESSION.
8541
+ const maxRowsPerSession = Math.max(0, Number(o.maxRowsPerSession ?? process.env.CTM_ROWS_BACKFILL_MAX_ROWS_PER_SESSION ?? 500));
8478
8542
  const bf = require('./lib/session-content-backfill');
8479
8543
  return bf.runContentRowsBackfillSweep(getDb(), replaceSessionMessageRows, {
8480
8544
  limit, budgetMs, maxRows, maxRowsPerSession,
@@ -10421,7 +10485,7 @@ module.exports = {
10421
10485
  listPermissionLog, addPermissionLog,
10422
10486
  getAlwaysAskTools, setAlwaysAsk,
10423
10487
  importSessionConversation, appendSessionConversation, appendSessionMessageRows, listSessionConversations, getSessionConversation, getSessionConversationMeta, getSessionConversationMessages, updateSessionModel,
10424
- replaceSessionMessageRows, appendSessionMessageRowsChunk, getSessionMessagesArray, sessionContentRowsAvailable, getSessionMessagesPage, getSessionMessagesTurnPage, countSessionMessageRows,
10488
+ replaceSessionMessageRows, appendSessionMessageRowsChunk, getSessionMessagesArray, sessionContentRowsAvailable, sessionRowsBackfillPending, getSessionMessagesPage, getSessionMessagesTurnPage, countSessionMessageRows,
10425
10489
  runContentRowsBackfillSweep, getSessionRowsStatus, retireLegacyStores, runVacuum, _blobRetirementActive,
10426
10490
  updateSessionTokens, getSessionTokens,
10427
10491
  pickDisplayTitle,
@@ -64,9 +64,43 @@ The engine is also the right substrate for any **programmatic** Codex consumer (
64
64
  Wall-E coding backend), where there is no TUI and driven mode is the *only* mode — there
65
65
  the remaining UI item (1) does not even apply.
66
66
 
67
+ ## Spike re-validation — codex-cli 0.142.3 (2026-06-27)
68
+
69
+ Triggered by the live `search_graph` strand: the scrape approver missed Codex's new MCP
70
+ approval UI (fixed separately — verb-anchored detection + a drift alarm). That prompted a
71
+ re-validation of the structured channel against the current binary. Findings:
72
+
73
+ 1. **`--remote` removes the old blocker.** 0.142.3 ships `codex --remote <ws://|unix://>`,
74
+ `codex remote-control`, and `codex app-server proxy` — **a TUI can attach to a running
75
+ app-server**. So structured approvals no longer cost the interactive terminal, which was
76
+ the entire "larger product decision" above. The follow-on plan's **step 1** is to stand
77
+ up an app-server (or `app-server daemon`) and confirm the `--remote` attach UX end to end
78
+ (CTM hosts; the user's terminal attaches).
79
+ 2. **Protocol method names held.** The v2 `item/commandExecution|fileChange|permissions/
80
+ requestApproval` + legacy `execCommandApproval`/`applyPatchApproval` the bridge maps are
81
+ still present in the 0.142.3 schema. The existing bridge mapping does not need to change
82
+ for those.
83
+ 3. **GAP — the MCP path the screenshot represents is unhandled.** The schema now has
84
+ `McpServerElicitationRequest` (params + response) — the `Allow the <server> MCP server to
85
+ run tool …?` form *is* an MCP elicitation (`mode: form|openai/form|url`, `message` +
86
+ `requestedSchema` = the rendered `file_pattern`/`limit` fields). The bridge has **no
87
+ elicitation handler**, and its response is a **form accept**, not a yes/no. Also new and
88
+ unhandled: `item/tool/requestUserInput` (`ToolRequestUserInputParams`). These are the
89
+ concrete adds for driven-mode MCP auto-approval.
90
+ 4. **Codex now self-reviews.** It emits `item/autoApprovalReview/started|completed`
91
+ notifications and exposes `approval_policy` / per-server `codex mcp` trust. The follow-on
92
+ must define how CTM's `decideApproval` **composes** with Codex's native auto-approval
93
+ (don't double-decide), or push the safe class into Codex config so it never asks.
94
+
95
+ Net: the structured channel is now the clearly-preferred durable fix (no TUI sacrifice), and
96
+ the remaining work is bounded — wire the driven/remote session type, add the elicitation +
97
+ requestUserInput handlers, and settle the autoApprovalReview composition. This stays a spike
98
+ branch until its own plan is approved.
99
+
67
100
  ## Reference
68
101
 
69
102
  Exact wire contract (regenerate from the installed binary):
70
103
  `codex app-server generate-json-schema --out <dir>` → `ServerRequest.json`,
71
- `v2/ThreadStartParams.json`, `CommandExecutionRequestApprovalParams.json`, etc.
104
+ `v2/ThreadStartParams.json`, `CommandExecutionRequestApprovalParams.json`,
105
+ `McpServerElicitationRequestParams.json`, etc.
72
106
  See memory `ctm-approver-protocol-hook-prototype-validated` for the full protocol notes.