@ashlr/hub 3.8.0 → 3.9.0

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 (147) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/dist/build-identity.json +2 -2
  3. package/dist/cli/resource-profile.js +95 -21
  4. package/dist/cli/resource-profile.js.map +1 -1
  5. package/dist/core/resources/native-profile.d.ts +56 -0
  6. package/dist/core/resources/native-profile.js +368 -6
  7. package/dist/core/resources/native-profile.js.map +1 -1
  8. package/dist/core/run/model-catalog.js +8 -1
  9. package/dist/core/run/model-catalog.js.map +1 -1
  10. package/dist/core/universe/builtins/preparation/manifest.json +1 -1
  11. package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +9 -2
  12. package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +8 -1
  13. package/dist/core/verse/adapters/claude.d.ts +49 -2
  14. package/dist/core/verse/adapters/claude.js +317 -56
  15. package/dist/core/verse/adapters/claude.js.map +1 -1
  16. package/dist/core/verse/adapters/codex.d.ts +81 -5
  17. package/dist/core/verse/adapters/codex.js +409 -47
  18. package/dist/core/verse/adapters/codex.js.map +1 -1
  19. package/dist/core/verse/adapters/grok.d.ts +13 -1
  20. package/dist/core/verse/adapters/grok.js +28 -2
  21. package/dist/core/verse/adapters/grok.js.map +1 -1
  22. package/dist/core/verse/adapters/index.d.ts +32 -0
  23. package/dist/core/verse/adapters/index.js +2 -0
  24. package/dist/core/verse/adapters/index.js.map +1 -1
  25. package/dist/core/verse/codex-rollout.d.ts +284 -0
  26. package/dist/core/verse/codex-rollout.js +786 -0
  27. package/dist/core/verse/codex-rollout.js.map +1 -0
  28. package/dist/core/verse/context-fit.d.ts +63 -0
  29. package/dist/core/verse/context-fit.js +355 -0
  30. package/dist/core/verse/context-fit.js.map +1 -0
  31. package/dist/core/verse/context-math.d.ts +215 -0
  32. package/dist/core/verse/context-math.js +365 -0
  33. package/dist/core/verse/context-math.js.map +1 -0
  34. package/dist/core/verse/local-models.d.ts +137 -1
  35. package/dist/core/verse/local-models.js +209 -5
  36. package/dist/core/verse/local-models.js.map +1 -1
  37. package/dist/core/verse/model-windows.d.ts +217 -0
  38. package/dist/core/verse/model-windows.js +616 -0
  39. package/dist/core/verse/model-windows.js.map +1 -0
  40. package/dist/core/verse/preferences.d.ts +123 -0
  41. package/dist/core/verse/preferences.js +403 -0
  42. package/dist/core/verse/preferences.js.map +1 -0
  43. package/dist/core/verse/project-memory.d.ts +112 -0
  44. package/dist/core/verse/project-memory.js +295 -0
  45. package/dist/core/verse/project-memory.js.map +1 -0
  46. package/dist/core/verse/seats.d.ts +106 -4
  47. package/dist/core/verse/seats.js +291 -89
  48. package/dist/core/verse/seats.js.map +1 -1
  49. package/dist/core/verse/session-engine.d.ts +76 -2
  50. package/dist/core/verse/session-engine.js +684 -63
  51. package/dist/core/verse/session-engine.js.map +1 -1
  52. package/dist/core/verse/session-handoff.d.ts +94 -0
  53. package/dist/core/verse/session-handoff.js +551 -0
  54. package/dist/core/verse/session-handoff.js.map +1 -0
  55. package/dist/core/verse/session-search.d.ts +63 -0
  56. package/dist/core/verse/session-search.js +213 -0
  57. package/dist/core/verse/session-search.js.map +1 -0
  58. package/dist/core/verse/session-store.d.ts +16 -2
  59. package/dist/core/verse/session-store.js +80 -6
  60. package/dist/core/verse/session-store.js.map +1 -1
  61. package/dist/core/verse/types.d.ts +333 -1
  62. package/dist/core/verse/types.js +54 -2
  63. package/dist/core/verse/types.js.map +1 -1
  64. package/dist/core/verse/verse-api.d.ts +40 -5
  65. package/dist/core/verse/verse-api.js +724 -25
  66. package/dist/core/verse/verse-api.js.map +1 -1
  67. package/dist/core/web/api.d.ts +14 -4
  68. package/dist/core/web/api.js +26 -11
  69. package/dist/core/web/api.js.map +1 -1
  70. package/dist/core/web/public/next/assets/{App-BsbJFA2j.js → App-XURmiAAp.js} +5 -5
  71. package/dist/core/web/public/next/assets/{App-BsbJFA2j.js.map → App-XURmiAAp.js.map} +1 -1
  72. package/dist/core/web/public/next/assets/{ApprovalsSection-2CCSoBTJ.js → ApprovalsSection-BriUzoZc.js} +2 -2
  73. package/dist/core/web/public/next/assets/{ApprovalsSection-2CCSoBTJ.js.map → ApprovalsSection-BriUzoZc.js.map} +1 -1
  74. package/dist/core/web/public/next/assets/AutonomySection-C4GBsQVc.js +2 -0
  75. package/dist/core/web/public/next/assets/{AutonomySection-yeNNUPur.js.map → AutonomySection-C4GBsQVc.js.map} +1 -1
  76. package/dist/core/web/public/next/assets/ChatSection-BpOBOUvg.css +1 -0
  77. package/dist/core/web/public/next/assets/ChatSection-hlgPBRco.js +84 -0
  78. package/dist/core/web/public/next/assets/ChatSection-hlgPBRco.js.map +1 -0
  79. package/dist/core/web/public/next/assets/ConfirmDialog-BdQYI-Ng.js +2 -0
  80. package/dist/core/web/public/next/assets/ConfirmDialog-BdQYI-Ng.js.map +1 -0
  81. package/dist/core/web/public/next/assets/{Dialog-nK15L1M0.js → Dialog-BxdIfmFH.js} +2 -2
  82. package/dist/core/web/public/next/assets/{Dialog-nK15L1M0.js.map → Dialog-BxdIfmFH.js.map} +1 -1
  83. package/dist/core/web/public/next/assets/{DiffViewer-D369eZe8.js → DiffViewer-DmeRWuc4.js} +2 -2
  84. package/dist/core/web/public/next/assets/{DiffViewer-D369eZe8.js.map → DiffViewer-DmeRWuc4.js.map} +1 -1
  85. package/dist/core/web/public/next/assets/{McpSection-DVgiSYG1.js → McpSection-q34T7Tho.js} +2 -2
  86. package/dist/core/web/public/next/assets/{McpSection-DVgiSYG1.js.map → McpSection-q34T7Tho.js.map} +1 -1
  87. package/dist/core/web/public/next/assets/{MutationTokenDialog-B9I4PAcp.js → MutationTokenDialog-CFAkwOEt.js} +2 -2
  88. package/dist/core/web/public/next/assets/{MutationTokenDialog-B9I4PAcp.js.map → MutationTokenDialog-CFAkwOEt.js.map} +1 -1
  89. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-z6tJj3s9.js → ResourcePoolConsoleApp-DHc8GFOY.js} +4 -4
  90. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-z6tJj3s9.js.map → ResourcePoolConsoleApp-DHc8GFOY.js.map} +1 -1
  91. package/dist/core/web/public/next/assets/{RouteErrorBoundary-OSajL45F.js → RouteErrorBoundary-ZL9F4qUn.js} +2 -2
  92. package/dist/core/web/public/next/assets/{RouteErrorBoundary-OSajL45F.js.map → RouteErrorBoundary-ZL9F4qUn.js.map} +1 -1
  93. package/dist/core/web/public/next/assets/{Meter-CLcr-4wS.css → Segmented-8cf0Fypn.css} +1 -1
  94. package/dist/core/web/public/next/assets/{Meter-Cmo1pLbk.js → Segmented-DbRjVye3.js} +2 -2
  95. package/dist/core/web/public/next/assets/Segmented-DbRjVye3.js.map +1 -0
  96. package/dist/core/web/public/next/assets/SettingsSection-nJGGGYqv.js +2 -0
  97. package/dist/core/web/public/next/assets/{SettingsSection-B3YH2Npi.js.map → SettingsSection-nJGGGYqv.js.map} +1 -1
  98. package/dist/core/web/public/next/assets/UniverseConsoleApp-6GJydgo4.js +2 -0
  99. package/dist/core/web/public/next/assets/{UniverseConsoleApp-wEKIzl1M.js.map → UniverseConsoleApp-6GJydgo4.js.map} +1 -1
  100. package/dist/core/web/public/next/assets/{UniverseView-CQVpJqqI.js → UniverseView-DLDtNYL8.js} +2 -2
  101. package/dist/core/web/public/next/assets/{UniverseView-CQVpJqqI.js.map → UniverseView-DLDtNYL8.js.map} +1 -1
  102. package/dist/core/web/public/next/assets/UsageSection-Cf9d9iFH.js +2 -0
  103. package/dist/core/web/public/next/assets/UsageSection-Cf9d9iFH.js.map +1 -0
  104. package/dist/core/web/public/next/assets/VerseConsoleApp-DsdKQoTH.css +1 -0
  105. package/dist/core/web/public/next/assets/VerseConsoleApp-y0uOHixd.js +3 -0
  106. package/dist/core/web/public/next/assets/VerseConsoleApp-y0uOHixd.js.map +1 -0
  107. package/dist/core/web/public/next/assets/{charts-BopViIha.js → charts-CVooqJWN.js} +2 -2
  108. package/dist/core/web/public/next/assets/{charts-BopViIha.js.map → charts-CVooqJWN.js.map} +1 -1
  109. package/dist/core/web/public/next/assets/context-model-Bqv8DVuH.js +2 -0
  110. package/dist/core/web/public/next/assets/context-model-Bqv8DVuH.js.map +1 -0
  111. package/dist/core/web/public/next/assets/global-eaY9GyX3.js +2 -0
  112. package/dist/core/web/public/next/assets/global-eaY9GyX3.js.map +1 -0
  113. package/dist/core/web/public/next/assets/{icons-L06pQH6j.js → icons-vex8rclC.js} +2 -2
  114. package/dist/core/web/public/next/assets/{icons-L06pQH6j.js.map → icons-vex8rclC.js.map} +1 -1
  115. package/dist/core/web/public/next/assets/{index-CXgVQHek.js → index-D2Eik-zt.js} +3 -3
  116. package/dist/core/web/public/next/assets/{index-CXgVQHek.js.map → index-D2Eik-zt.js.map} +1 -1
  117. package/dist/core/web/public/next/assets/{queries-D3sSlan1.js → queries-bAYU0VNy.js} +2 -2
  118. package/dist/core/web/public/next/assets/{queries-D3sSlan1.js.map → queries-bAYU0VNy.js.map} +1 -1
  119. package/dist/core/web/public/next/assets/use-guarded-action-C7QrP7Yp.js +2 -0
  120. package/dist/core/web/public/next/assets/{use-guarded-action-BxfJ9E-n.js.map → use-guarded-action-C7QrP7Yp.js.map} +1 -1
  121. package/dist/core/web/public/next/assets/verse-model-TfDk9cky.js +2 -0
  122. package/dist/core/web/public/next/assets/verse-model-TfDk9cky.js.map +1 -0
  123. package/dist/core/web/public/next/assets/{workspace-model-v0a1gl-C.js → workspace-model-CHol5RVp.js} +2 -2
  124. package/dist/core/web/public/next/assets/{workspace-model-v0a1gl-C.js.map → workspace-model-CHol5RVp.js.map} +1 -1
  125. package/dist/core/web/public/next/index.html +1 -1
  126. package/dist/release-dependency-inventory.json +1 -1
  127. package/docs/README.md +1 -0
  128. package/package.json +1 -1
  129. package/dist/core/web/public/next/assets/AutonomySection-yeNNUPur.js +0 -2
  130. package/dist/core/web/public/next/assets/ChatSection-BfnLe5dT.css +0 -1
  131. package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js +0 -80
  132. package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js.map +0 -1
  133. package/dist/core/web/public/next/assets/ConfirmDialog-C363ty_c.js +0 -2
  134. package/dist/core/web/public/next/assets/ConfirmDialog-C363ty_c.js.map +0 -1
  135. package/dist/core/web/public/next/assets/Meter-Cmo1pLbk.js.map +0 -1
  136. package/dist/core/web/public/next/assets/SettingsSection-B3YH2Npi.js +0 -2
  137. package/dist/core/web/public/next/assets/UniverseConsoleApp-wEKIzl1M.js +0 -2
  138. package/dist/core/web/public/next/assets/UsageSection-dToBThXe.js +0 -2
  139. package/dist/core/web/public/next/assets/UsageSection-dToBThXe.js.map +0 -1
  140. package/dist/core/web/public/next/assets/VerseConsoleApp-CMkCizvL.css +0 -1
  141. package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js +0 -3
  142. package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js.map +0 -1
  143. package/dist/core/web/public/next/assets/global-DMWakEhP.js +0 -2
  144. package/dist/core/web/public/next/assets/global-DMWakEhP.js.map +0 -1
  145. package/dist/core/web/public/next/assets/use-guarded-action-BxfJ9E-n.js +0 -2
  146. package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js +0 -2
  147. package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -339,6 +339,207 @@ superseded, not a setup procedure. Use [runtime activation authority](docs/RUNTI
339
339
  and the [current architecture boundary](docs/ARCHITECTURE.md#legacy-fleet-activation-boundary).
340
340
  Neither the historical record nor a successful test activates a resident fleet.
341
341
 
342
+ ## [3.9.0] — 2026-09-24 UTC — the context meter tells the truth, and long sessions have somewhere to go
343
+
344
+ The Verse context meter was wrong on every engine, in a different way each time,
345
+ and a session that was filling up had nowhere to go but a cold new chat. The
346
+ numbers below were read from the pinned CLI binaries, each seat's own model
347
+ catalog and every September Codex rollout on this machine; no paid model was
348
+ prompted to produce them. `docs/VERSE-CONTEXT.md` is the new authority for all of it.
349
+
350
+ ### Behaviour change: new Claude chats compact at ≈367k, existing ones keep ≈967k
351
+
352
+ - **New Claude chats on a 1M model start in Standard** and compact at ≈367k
353
+ (`--autocompact 400000`), not near 967k as every Claude chat did before.
354
+ Expansive — the old full window — is one click away, and can be a seat's default.
355
+ - **Chats created before 3.9 keep the window they ran with.** A Claude chat with
356
+ no recorded mode is marked **Expansive** the first time 3.9 loads it and runs
357
+ with `--autocompact auto`, exactly as before, so upgrading compacts nothing and
358
+ spends nothing. Codex, Grok, local and 200k-Claude chats from before 3.9 stay
359
+ Standard, which is what they already ran at.
360
+ - **Switching a chat to Standard above ≈367k compacts it on the next turn.** The
361
+ CLI then summarizes the whole context — on a paid seat that spends usage — and
362
+ early turns survive only as that summary. The same holds for a Codex chat
363
+ switched back from Expansive above 244.8k. Switching to Expansive is always
364
+ free. The mode menu warns before you switch.
365
+
366
+ ### Windows read from the CLIs, not assumed
367
+
368
+ - **Claude.** Six of the eight models run a **1M** window and compact near
369
+ 967k; Verse showed 200k for all of them, so a Fable session went red at 180k
370
+ and then sat at 100 % while it grew to 967k — hiding exactly the range where
371
+ every turn is most expensive. Each model now carries its real window, and the
372
+ window the CLI reports at runtime (`result.modelUsage`) wins, which also
373
+ catches the CLI dropping a session to 200k when long-context credit runs out.
374
+ - **"Opus 5.5" was running Opus 5.** Verse offered `claude-opus-5.5`; Claude
375
+ Code's id parser rejects the dotted suffix and falls back to a substring match
376
+ on `claude-opus-5`. The real id is `claude-opus-5-5`, which needs Claude Code
377
+ 2.1.280 — and the claude-a seat is pinned to 2.1.257. Opus 5.5 is now listed on
378
+ that seat **disabled, with the reason** and the fix
379
+ (`ashlr resources profile repin`, new in this release — below), rather than
380
+ silently running a different model. Old sessions keep their recorded id, but
381
+ their turns now ask for the real model, so on a seat that cannot run it the
382
+ turn is refused up front (409 `VERSE_MODEL_UNAVAILABLE`, with the reason)
383
+ instead of starting a CLI that would reject the id. A client that still sends
384
+ `claude-opus-5.5` when creating a chat gets `claude-opus-5-5`.
385
+ - **Codex.** The CLI measures against 95 % of the window — 258,400, reported by
386
+ every one of 151,715 rollout readings — and compacts near 244,800. Occupancy was
387
+ the turn's *summed* input — a median 28.8× the real last-call prompt across 260
388
+ recent turns — so the meter sat red on almost every turn. It now comes from the
389
+ seat's own rollout — the last call's `last_token_usage.total_tokens`, the
390
+ figure Codex itself compacts against, and the window the rollout reports —
391
+ read every 2 s during a turn; until that is readable the figure is marked `≤`. The rollout also fixes per-turn usage: on `exec resume`,
392
+ Codex's reported usage is the whole thread's running total, which Verse would
393
+ have added again every turn (no Verse codex chat had run yet, so no stored
394
+ total is wrong). Models come from each seat's own catalog, so hidden slugs are
395
+ no longer offered.
396
+ - **Grok** is 500k compacting at 400k, not 256k — the meter read twice the real
397
+ fill. Labels come from the catalog ("Grok 4.7 Fast"), and only its `.info`
398
+ entries are read: the same file carries a per-model `api_key` field. Like
399
+ Claude, the window Grok reports at runtime wins.
400
+ - **Local.** Claude Code assumes 200k for a model it does not know, so a local
401
+ seat never compacted before a 64k runner overflowed. Verse now tells it the
402
+ real window (`CLAUDE_CODE_MAX_CONTEXT_TOKENS`), resolves that window from what
403
+ the runner actually serves (llama-server's per-slot window on that lane,
404
+ Ollama's own server default for unpinned tags — preferred over whatever
405
+ context a resident runner was loaded with, since another app may have loaded
406
+ it at 8k), and parses `-ctx64k` tags, which the old suffix pattern missed.
407
+ Before every local turn the chat's stored window is refreshed from live
408
+ discovery, so the window the CLI is told and the one the meter shows are the
409
+ same, current number. Tags under a 56k window are listed disabled with the
410
+ reason: after Claude Code's 33k reserve and its ~15k base prompt they would
411
+ compact on every turn. The fleet's model catalog no longer
412
+ tags the 64k local coder `long-context` (a capability defined as ≥ 100k), which
413
+ had routed long-context work to a model that would overflow.
414
+ - Readings are stored **unclamped**: an overflow is information, and the old
415
+ clamp erased it on disk.
416
+
417
+ ### Compaction you can see
418
+
419
+ Native compactions become persisted `compaction` events — Claude and Grok from
420
+ `compact_boundary`, Codex from its rollout, sized from the readings just before
421
+ and after it — with a transcript divider ("Auto-compacted 967k → 19k in 1m 58s")
422
+ and a per-session count. The meter draws the whole window with a tick at the
423
+ compaction point and warns at 80 % / 95 % of **that point**. The old fixed
424
+ 70 / 90 % of the window turned red only after Grok and Codex had already
425
+ compacted.
426
+
427
+ **Compact now** — in the chip beside the meter on every Claude and local session
428
+ (a "Context" chip where the model has no second mode), and in the handoff banner — sends `/compact` as an ordinary turn,
429
+ through the same gate as every turn, and the divider reads "Compacted on
430
+ request". It was verified headless on a local seat, where it is free but slow
431
+ (about 2½ minutes for a 15k context on a 27B model). **On a paid seat it spends
432
+ usage**: the CLI reads the whole context to write its summary.
433
+
434
+ ### Standard and expansive context
435
+
436
+ A new chat on any Claude 1M model now compacts at ≈367k by default
437
+ (`--autocompact 400000`) instead of ≈967k (chats from before 3.9 keep ≈967k —
438
+ above). Each turn re-sends the whole conversation, and long-context
439
+ recall degrades with length on every published benchmark, so a session held
440
+ near 900k re-reads about 2.3× what one held under 400k does. **Expansive** mode
441
+ restores the full window — and on GPT-6 / GPT-5.6 raises Codex to its 872k
442
+ catalog maximum, passing `model_context_window` and
443
+ `model_auto_compact_token_limit` together (one without the other breaks Codex's
444
+ compaction). The mode is per session, applies from the next turn, can be a
445
+ seat's default, and exists only where it is real. Verse may suggest it; it never
446
+ switches it on. The menu's cost note is the ratio of the two compaction points
447
+ (≈2.6× on Claude, ≈3.2× on Codex). On Codex every Expansive surface also says
448
+ that GPT-5.6 requests above 272k reportedly count about 2× against plan limits
449
+ (one secondary source; GPT-6 Astra reportedly exempt) — if that holds, a turn
450
+ near the expansive limit counts nearer 6× a standard one.
451
+
452
+ ### Continue in a fresh chat
453
+
454
+ A banner appears when a session nears its compaction point, has compacted twice,
455
+ or has sat idle past the prompt cache's lifetime with a large context. It builds
456
+ a handoff note from the session's own log — goal, latest asks, state, files,
457
+ commands, errors, `git diff --stat` per root — with no model call, lets you edit
458
+ it and pick any seat (a Claude chat can continue on Codex), creates the new
459
+ session for free and leaves the note in its composer. Nothing is sent until you
460
+ press send.
461
+
462
+ ### Shared memory, fit, search, efficiency
463
+
464
+ - **Project memory:** one private directory per project under
465
+ `~/.ashlr/verse/memory/`, shared by every seat, with a fixed instruction block
466
+ that stays byte-identical across turns so the prompt cache survives. On by
467
+ default, per-project opt-out, editable from the Resources panel. Claude and
468
+ local seats get the directory with `--add-dir` and the block with
469
+ `--append-system-prompt`; Codex through `-c` overrides (a writable root and
470
+ `developer_instructions`), since `exec resume` takes no `--add-dir`. Grok, whose
471
+ CLI can be granted nothing beyond its working directory, gets a read-only
472
+ snapshot through `--rules`. **It is not free on a paid seat:** the block (up
473
+ to 6 KB) rides in the system prompt of every turn of every new session —
474
+ cached after the first turn, but cached tokens still count — and it asks the
475
+ agent to read and update `MEMORY.md`, which is extra tool calls and output.
476
+ Turn it off per project, or for every project, in the Resources panel.
477
+ The editor shows the sanitized copy the API serves and says so; a save that
478
+ would write `[REDACTED]` placeholders over real values is refused.
479
+ - **Context fit:** per-model verdicts — fits, tight, needs expansive, or split —
480
+ for a folder or workspace, from `git ls-files` sizes, plus each engine's base
481
+ prompt (local 15k, measured; Claude 25k, Codex 15k, Grok 20k, estimated), so
482
+ a handoff note fits the 64k local seat while a whole repository shows split.
483
+ - **Session search** across past chats from the sidebar: a bounded keyword scan
484
+ of Verse's own session store (newest 200 sessions, 20 MB of message text), with
485
+ no index and no embeddings.
486
+ - **Efficiency:** cache-hit ratio, context per turn, compactions and an
487
+ idle-cache warning, computed in the browser.
488
+
489
+ Memory aside, all of it is deterministic and spends nothing: the handoff
490
+ preview, context fit, search and the efficiency stats make no model call. The
491
+ only model calls remain the turns you send, through the same local-only-gated
492
+ chokepoint as before.
493
+
494
+ ### Re-pin a seat to a newer CLI
495
+
496
+ `ashlr resources profile repin --directory <profile> --executable <binary>`
497
+ points an existing prepared native profile at another CLI binary — the fix the
498
+ claude-a seat's note names for Opus 5.5. It changes only the pinned executable
499
+ (the launcher's one `const profile=` line and `profile.json`) and refuses a
500
+ profile that is not unmodified `prepare` output. `--dry-run` checks everything
501
+ and writes nothing. A real repin first saves `launcher.mjs`, `profile.json` and
502
+ `command.json` as `.prev` files — one rollback set; restoring all three restores
503
+ the old pin. Re-running an interrupted repin finishes it, repinning to the
504
+ current binary reports `unchanged`, and failures say whether the profile
505
+ changed. It does not check sign-in: run `ashlr resources launcher check`
506
+ afterwards. The command ships in the built CLI, so it needs `npm run build` in a
507
+ source checkout.
508
+
509
+ ### For API clients
510
+
511
+ - New routes under `/api/verse`: `POST /sessions/:id/context-mode`,
512
+ `POST /sessions/:id/handoff-preview`, `GET`/`POST /preferences`,
513
+ `GET /context-fit`, `GET /search`, `GET`/`POST /memory`. None starts a model
514
+ call. `handoff-preview` is a POST because it runs `git`, so like every POST it
515
+ answers 404 on a read-only server.
516
+ - `POST /sessions` is now strict: an unknown key is a 400, and `workspaceName` is
517
+ refused (the server fills it from `workspaceId`). New sessions always carry
518
+ `memoryEnabled`, `false` when memory is off for the project.
519
+ - The new GETs reject unknown or duplicated query parameters; `context-fit`
520
+ takes `extraRoots` repeated once per root, because a comma is legal in a path.
521
+ - `POST /memory` content is capped at 64 KiB; that route alone accepts a body
522
+ of up to 2 × 64 KiB + 8 KiB so a full-size file survives JSON escaping. It
523
+ answers 409 `VERSE_MEMORY_REDACTED` when the content holds more `[REDACTED]`
524
+ placeholders than the file on disk, and `GET`/`POST /memory` responses carry
525
+ `contentSanitized: true` whenever the content sent is not the file's bytes.
526
+ - New sessions always record `contextMode` (`'standard'` included); an absent
527
+ key marks a pre-3.9 record (resolved as above).
528
+ - New persisted events `compaction` and `context`; `context` may carry
529
+ `contextWindowSource` (absent = `runtime`), so a catalog budget written after
530
+ a mode switch is not shown as a measurement. `VERSE_HANDOFF_SUMMARY_REQUEST`
531
+ (`types.ts`) is the fixed "summarize first" turn. The full additive contract
532
+ is in `docs/VERSE-CONTRACT-V1.md`.
533
+
534
+ ### Hygiene
535
+
536
+ - `eslint.config.js` ignores `.claude/**`. Agent worktrees there produced about
537
+ 98k lint errors, which made `npm run lint` — and so `prepublishOnly` — fail on
538
+ this machine.
539
+ - Two release-truth test pins that asserted superseded facts (npm provenance,
540
+ removed on purpose in `e24a7dae`, and the pre-rewrite desktop README) now
541
+ assert the current policy.
542
+
342
543
  ## [3.8.0] — 2026-09-23 UTC — local-only actually prevents spend
343
544
 
344
545
  Two gates that make a promise the UI was already making. Both landed after the
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "packageVersion": "3.8.0",
4
- "revision": "a3a44cbbc5599bdb369c3cba1d7fcd53a45fa854",
3
+ "packageVersion": "3.9.0",
4
+ "revision": "4be748c6b2176684892bebb3acdb488ab5522326",
5
5
  "dirty": false,
6
6
  "provenance": "git"
7
7
  }
@@ -1,13 +1,27 @@
1
1
  import { isAbsolute, parse as parsePath, resolve } from 'node:path';
2
2
  const USAGE = `usage: ashlr resources profile prepare --provider codex|claude|grok
3
3
  --directory NEW_ABS --executable ABS [--json]
4
+ ashlr resources profile repin --directory EXISTING_ABS --executable ABS
5
+ [--dry-run] [--json]
4
6
 
5
- Creates one new private profile under an existing owned mode-0700 parent.
7
+ prepare creates one new private profile under an existing owned mode-0700 parent.
6
8
  Writes a standalone launcher, command.json, preparation manifest and empty native
7
9
  state directories. Existing targets are never overwritten, repaired or reused.
8
10
  On failure, a partially prepared directory may remain; inspect it rather than
9
11
  deleting account state automatically. No parent directories are created.
10
12
 
13
+ repin points one existing prepared profile at a different native executable,
14
+ for example a newer CLI version that a model requires. It rewrites only the
15
+ executable locator in launcher.mjs and profile.json (command.json names node and
16
+ the launcher, never the native executable, so its bytes do not change), keeps
17
+ the provider, native state and sign-in untouched, and first copies the three
18
+ files to launcher.mjs.prev, profile.json.prev and command.json.prev. Every file
19
+ must still be prepare's unmodified output; a hand-edited profile is refused.
20
+ Rerunning the same repin after an interruption finishes it; repinning to the
21
+ current executable writes nothing. Sessions already running keep the binary
22
+ they started with; the next launch through the profile uses the new one.
23
+ --dry-run validates everything and reports the change without writing.
24
+
11
25
  No executable is launched, credentials read/copied, account signed in, provider
12
26
  contacted, pool enrolled or resident service installed. The returned login argv
13
27
  is a separate interactive native action, not something this command executes.
@@ -16,12 +30,17 @@ environment; they are operator-owned code, not a sandbox or billing attestation.
16
30
  Node with process.execve support and a supported POSIX platform are required.
17
31
  --executable must name a canonical regular executable file, not an install symlink.
18
32
  Output includes private local paths, never authentication material.
19
- Exit codes: 0 prepared (authentication not checked), 1 preparation unavailable,
20
- 2 invalid arguments. Grok preparation supports separate native login/metadata;
21
- it does not enable a Grok task-generation adapter.
33
+ Exit codes: 0 prepared, repinned or already pinned (authentication not checked),
34
+ 1 preparation or repin unavailable, 2 invalid arguments. Grok preparation
35
+ supports separate native login/metadata; it does not enable a Grok
36
+ task-generation adapter.
22
37
  `;
23
38
  class UsageError extends Error {
24
39
  }
40
+ /** A repin failure whose message is fixed, path-free text saying whether the profile changed. */
41
+ class RepinFailure extends Error {
42
+ }
43
+ const REPIN_UNAVAILABLE = 'Native profile repin unavailable; inspect the profile before retrying';
25
44
  function parse(args) {
26
45
  if (args.length > 12 || args.some((arg) => typeof arg !== 'string' || Buffer.byteLength(arg) > 4096 ||
27
46
  [...arg].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) >= 127 && char.charCodeAt(0) <= 159)) ||
@@ -29,10 +48,14 @@ function parse(args) {
29
48
  throw new UsageError('Invalid profile arguments');
30
49
  if (args.length === 1 && ['--help', '-h'].includes(args[0]))
31
50
  return { help: true };
32
- if (args[0] !== 'prepare')
33
- throw new UsageError('Expected prepare subcommand');
51
+ const subcommand = args[0];
52
+ if (subcommand !== 'prepare' && subcommand !== 'repin')
53
+ throw new UsageError('Expected prepare or repin subcommand');
54
+ // repin reads the provider from the existing profile and never changes it.
55
+ const valued = subcommand === 'prepare' ? ['--provider', '--directory', '--executable'] : ['--directory', '--executable'];
34
56
  const values = new Map();
35
57
  let json = false;
58
+ let dryRun = false;
36
59
  for (let i = 1; i < args.length; i++) {
37
60
  const flag = args[i];
38
61
  if (flag === '--json') {
@@ -41,16 +64,21 @@ function parse(args) {
41
64
  json = true;
42
65
  continue;
43
66
  }
44
- if (!['--provider', '--directory', '--executable'].includes(flag) || values.has(flag))
67
+ if (flag === '--dry-run' && subcommand === 'repin') {
68
+ if (dryRun)
69
+ throw new UsageError('Duplicate profile option');
70
+ dryRun = true;
71
+ continue;
72
+ }
73
+ if (flag === '--provider' && subcommand === 'repin')
74
+ throw new UsageError('repin keeps the profile provider; --provider is not accepted');
75
+ if (!valued.includes(flag) || values.has(flag))
45
76
  throw new UsageError('Unknown or duplicate profile option');
46
77
  const value = args[++i];
47
78
  if (!value || value.startsWith('-'))
48
79
  throw new UsageError('Profile option requires a value');
49
80
  values.set(flag, value);
50
81
  }
51
- const provider = values.get('--provider');
52
- if (provider !== 'codex' && provider !== 'claude' && provider !== 'grok')
53
- throw new UsageError('Expected codex, claude or grok provider');
54
82
  const path = (flag) => {
55
83
  const value = values.get(flag);
56
84
  if (!value || !isAbsolute(value) || resolve(value) !== value || parsePath(value).root === value) {
@@ -58,28 +86,74 @@ function parse(args) {
58
86
  }
59
87
  return value;
60
88
  };
61
- return { help: false, json, provider, directory: path('--directory'), executable: path('--executable') };
89
+ if (subcommand === 'repin')
90
+ return { help: false, subcommand, json, dryRun, directory: path('--directory'), executable: path('--executable') };
91
+ const provider = values.get('--provider');
92
+ if (provider !== 'codex' && provider !== 'claude' && provider !== 'grok')
93
+ throw new UsageError('Expected codex, claude or grok provider');
94
+ return { help: false, subcommand, json, provider, directory: path('--directory'), executable: path('--executable') };
95
+ }
96
+ const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
97
+ async function prepare(options) {
98
+ const { prepareResourceNativeProfile } = await import('../core/resources/native-profile.js');
99
+ const report = prepareResourceNativeProfile({ provider: options.provider, directory: options.directory, executable: options.executable });
100
+ console.log(options.json ? JSON.stringify(report, null, 2) : [
101
+ `Native profile · ${report.provider} · prepared · authentication not checked`,
102
+ `Directory: ${report.directory}`, `Command file: ${report.commandPath}`,
103
+ `Separate interactive sign-in: ${report.loginCommand.map(quote).join(' ')}`,
104
+ 'No login was executed. Verify native account, billing route and quota before enrolling capacity.',
105
+ ].join('\n'));
106
+ }
107
+ async function repin(options) {
108
+ const { repinResourceNativeProfile, ResourceNativeProfileRepinError } = await import('../core/resources/native-profile.js');
109
+ let report;
110
+ try {
111
+ report = repinResourceNativeProfile({ directory: options.directory, executable: options.executable, dryRun: options.dryRun });
112
+ }
113
+ catch (error) {
114
+ throw new RepinFailure(error instanceof ResourceNativeProfileRepinError ? error.message : REPIN_UNAVAILABLE);
115
+ }
116
+ if (options.json) {
117
+ console.log(JSON.stringify(report, null, 2));
118
+ return;
119
+ }
120
+ const heading = report.status === 'repinned' ? (report.resumed ? 'repinned (finished an interrupted repin)' : 'repinned')
121
+ : report.status === 'would-repin' ? (report.resumed ? 'dry run · would finish an interrupted repin' : 'dry run · would repin')
122
+ : 'already pinned · nothing written';
123
+ const lines = [`Native profile · ${report.provider} · ${heading} · authentication not checked`, `Directory: ${report.directory}`];
124
+ if (report.status === 'unchanged')
125
+ lines.push(`Executable: ${report.executable}`);
126
+ else
127
+ lines.push(`Executable: ${report.previousExecutable} -> ${report.executable}`);
128
+ if (report.backups) {
129
+ lines.push(`Previous files kept: ${[report.backups.launcherPath, report.backups.manifestPath, report.backups.commandPath].map(quote).join(' ')}`);
130
+ lines.push('Sessions already running keep the binary they started with; the next launch through this profile uses the new one.');
131
+ }
132
+ if (report.status === 'would-repin')
133
+ lines.push('Nothing was written.');
134
+ lines.push(`Nothing was executed. Check the new binary with: ashlr resources launcher check --provider ${report.provider} --command ${quote(report.commandPath)} --cwd <private dir>`);
135
+ console.log(lines.join('\n'));
62
136
  }
63
137
  export async function cmdResourceProfile(args) {
138
+ let subcommand = null;
64
139
  try {
65
140
  const options = parse(args);
66
141
  if (options.help) {
67
142
  console.log(USAGE);
68
143
  return 0;
69
144
  }
70
- const { prepareResourceNativeProfile } = await import('../core/resources/native-profile.js');
71
- const report = prepareResourceNativeProfile({ provider: options.provider, directory: options.directory, executable: options.executable });
72
- const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
73
- console.log(options.json ? JSON.stringify(report, null, 2) : [
74
- `Native profile · ${report.provider} · prepared · authentication not checked`,
75
- `Directory: ${report.directory}`, `Command file: ${report.commandPath}`,
76
- `Separate interactive sign-in: ${report.loginCommand.map(quote).join(' ')}`,
77
- 'No login was executed. Verify native account, billing route and quota before enrolling capacity.',
78
- ].join('\n'));
145
+ subcommand = options.subcommand;
146
+ if (options.subcommand === 'repin')
147
+ await repin(options);
148
+ else
149
+ await prepare(options);
79
150
  return 0;
80
151
  }
81
152
  catch (error) {
82
- const message = error instanceof UsageError ? error.message : 'Native profile preparation unavailable; inspect the selected target for partial files';
153
+ // Repin failures carry fixed, path-free text that says whether the profile
154
+ // changed; anything else (and every prepare failure) stays generic.
155
+ const message = error instanceof UsageError || error instanceof RepinFailure ? error.message
156
+ : subcommand === 'repin' ? REPIN_UNAVAILABLE : 'Native profile preparation unavailable; inspect the selected target for partial files';
83
157
  if (args.includes('--json'))
84
158
  console.log(JSON.stringify({ error: message }));
85
159
  else
@@ -1 +1 @@
1
- {"version":3,"file":"resource-profile.js","sourceRoot":"","sources":["../../src/cli/resource-profile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resource-profile.js","sourceRoot":"","sources":["../../src/cli/resource-profile.ts"],"names":[],"mappings":"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"}
@@ -21,9 +21,65 @@ export interface ResourceNativeProfile {
21
21
  command: string[];
22
22
  loginCommand: string[];
23
23
  }
24
+ /**
25
+ * Key order of profile.json, exactly as `prepareResourceNativeProfile` builds it.
26
+ * Exported so a test can pin prepare's output to it: repin refuses any other
27
+ * shape, so the two must never drift apart silently.
28
+ */
29
+ export declare const RESOURCE_NATIVE_PROFILE_MANIFEST_KEYS: readonly ["schemaVersion", "scope", "status", "authentication", "provider", "directory", "executable", "nodeExecutable", "commandPath", "launcherPath", "nativeStatePath", "anthropicStatePath", "manifestPath", "command", "loginCommand"];
24
30
  /**
25
31
  * Exclusive private leaf only. Existing paths are never reused, removed, repaired
26
32
  * or overwritten. A failed write may leave a partial directory for inspection.
27
33
  * Native login remains a separate operation through the generated command.
28
34
  */
29
35
  export declare function prepareResourceNativeProfile(options: ResourceNativeProfileOptions): ResourceNativeProfile;
36
+ export interface ResourceNativeProfileRepinOptions {
37
+ directory: string;
38
+ executable: string;
39
+ /** Validate everything and report what would change; write nothing. */
40
+ dryRun?: boolean;
41
+ }
42
+ export interface ResourceNativeProfileRepin {
43
+ schemaVersion: 1;
44
+ scope: 'native-profile-repin';
45
+ /**
46
+ * `repinned` — launcher.mjs and profile.json now name `executable`.
47
+ * `unchanged` — both already named `executable`; nothing was written.
48
+ * `would-repin` — dry run: every check passed; nothing was written.
49
+ */
50
+ status: 'repinned' | 'unchanged' | 'would-repin';
51
+ authentication: 'not-checked';
52
+ provider: ResourceNativeProfileProvider;
53
+ directory: string;
54
+ previousExecutable: string;
55
+ executable: string;
56
+ launcherPath: string;
57
+ manifestPath: string;
58
+ commandPath: string;
59
+ /** The `*.prev` copies of the three files as they stood before this repin; null when nothing was repinned. */
60
+ backups: {
61
+ launcherPath: string;
62
+ manifestPath: string;
63
+ commandPath: string;
64
+ } | null;
65
+ /** True when this call finished (or, dry, would finish) a repin interrupted between its launcher and profile.json publishes. */
66
+ resumed: boolean;
67
+ }
68
+ /**
69
+ * - `invalid` — options, executable, runtime, private directories or file modes failed validation. Nothing written.
70
+ * - `inconsistent` — the files are not an unmodified prepared profile, or disagree with each other. Nothing written.
71
+ * - `failed-unchanged` — a write failed before launcher.mjs changed; the profile still runs its previous executable.
72
+ * - `failed-partial` — launcher.mjs changed but the rest did not complete; rerun the same repin, or restore `*.prev`.
73
+ */
74
+ export type ResourceNativeProfileRepinFailure = 'invalid' | 'inconsistent' | 'failed-unchanged' | 'failed-partial';
75
+ export declare class ResourceNativeProfileRepinError extends Error {
76
+ readonly failure: ResourceNativeProfileRepinFailure;
77
+ constructor(failure: ResourceNativeProfileRepinFailure);
78
+ }
79
+ /**
80
+ * Re-pin one existing prepared profile to a different native executable.
81
+ * Rewrites only the executable locator (see the section comment above);
82
+ * idempotent when already pinned; resumes its own interrupted run; never
83
+ * executes anything and never reads native state or credentials.
84
+ */
85
+ export declare function repinResourceNativeProfile(options: ResourceNativeProfileRepinOptions): ResourceNativeProfileRepin;