mixdog 1.0.12 → 1.1.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 (256) hide show
  1. package/README.md +75 -181
  2. package/package.json +4 -2
  3. package/scripts/build-shipped-history.mjs +131 -0
  4. package/scripts/lib/run-node-tests.mjs +235 -13
  5. package/scripts/lib/test-failure-records.mjs +5 -0
  6. package/scripts/lib/test-failure-summary.mjs +9 -3
  7. package/scripts/lib/test-spawn-ledger.mjs +19 -0
  8. package/scripts/prune-embedding-runtime.mjs +9 -5
  9. package/scripts/test-direct.mjs +16 -1
  10. package/src/agents/advisor/AGENT.md +38 -0
  11. package/src/agents/advisor/agent.json +6 -0
  12. package/src/agents/reviewer/AGENT.md +5 -2
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/defaults/shipped-history.json +381 -0
  15. package/src/defaults/skills/goal-management/SKILL.md +11 -5
  16. package/src/defaults/skills/setup/SKILL.md +5 -8
  17. package/src/defaults/skills/setup/references/actions.md +61 -31
  18. package/src/defaults/skills/setup/references/surfaces.md +12 -10
  19. package/src/lib/rules-builder.cjs +1 -1
  20. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  22. package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
  23. package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
  25. package/src/runtime/agent/orchestrator/config.mjs +102 -21
  26. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +77 -26
  27. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +2 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
  30. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -3
  32. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
  33. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
  34. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
  35. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +51 -9
  36. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +2 -1
  37. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +14 -9
  38. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +51 -11
  39. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +6 -1
  40. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
  41. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
  42. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +27 -7
  43. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
  44. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  45. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +2 -1
  46. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
  47. package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -3
  48. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +19 -35
  49. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +5 -0
  50. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
  51. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
  52. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
  53. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
  56. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
  57. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
  58. package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
  59. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +2 -2
  61. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
  62. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
  63. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +28 -3
  64. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +24 -11
  65. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +2 -6
  66. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
  67. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
  68. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
  69. package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
  70. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +18 -8
  71. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
  72. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -11
  73. package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
  74. package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
  75. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
  76. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
  77. package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
  78. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
  79. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
  80. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
  81. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
  82. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
  83. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
  84. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
  85. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
  86. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
  87. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  88. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +4 -1
  89. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
  90. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
  91. package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
  92. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
  93. package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
  94. package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
  95. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
  96. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
  97. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
  98. package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
  99. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
  100. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
  101. package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
  103. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
  104. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
  105. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
  106. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
  107. package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
  108. package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
  109. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
  110. package/src/runtime/channels/lib/config.mjs +3 -8
  111. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
  112. package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
  113. package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
  114. package/src/runtime/computer-bridge/actions.mjs +2 -0
  115. package/src/runtime/effort-judge/judge-client.mjs +12 -3
  116. package/src/runtime/effort-judge/model-manifest.json +7 -7
  117. package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
  118. package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
  119. package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
  120. package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
  121. package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
  122. package/src/runtime/memory/lib/memory-embed.mjs +5 -0
  123. package/src/runtime/shared/agent-route-config.mjs +50 -2
  124. package/src/runtime/shared/bounded-file-read.mjs +166 -0
  125. package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
  126. package/src/runtime/shared/config.mjs +24 -4
  127. package/src/runtime/shared/defaults-separation.mjs +263 -0
  128. package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
  129. package/src/runtime/shared/llm/model-catalog.mjs +23 -0
  130. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
  131. package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
  132. package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
  133. package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
  134. package/src/runtime/shared/orchestration.mjs +14 -6
  135. package/src/runtime/shared/projects.mjs +65 -48
  136. package/src/runtime/shared/provider-accounts.mjs +47 -6
  137. package/src/runtime/shared/quota-resume-store.mjs +255 -0
  138. package/src/runtime/shared/shipped-definitions.mjs +275 -0
  139. package/src/runtime/shared/tool-card-model.golden.json +3 -3
  140. package/src/runtime/shared/tool-result-summary.mjs +86 -15
  141. package/src/runtime/shared/transcript-metadata.mjs +2 -0
  142. package/src/runtime/shared/turn-review-reply.mjs +2 -0
  143. package/src/runtime/shared/turn-snapshot.mjs +65 -6
  144. package/src/runtime/shared/turn-worktree-snapshot.mjs +202 -22
  145. package/src/session-runtime/boot/apis.mjs +19 -0
  146. package/src/session-runtime/boot/config.mjs +4 -0
  147. package/src/session-runtime/boot/facade.mjs +4 -1
  148. package/src/session-runtime/boot/infrastructure.mjs +21 -2
  149. package/src/session-runtime/boot/session.mjs +3 -0
  150. package/src/session-runtime/channel-config-api.mjs +5 -0
  151. package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
  152. package/src/session-runtime/config-lifecycle.mjs +31 -6
  153. package/src/session-runtime/goal-facade-api.mjs +4 -0
  154. package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
  155. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
  156. package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
  157. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +6 -0
  158. package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
  159. package/src/session-runtime/media-api.mjs +32 -22
  160. package/src/session-runtime/model-recency.mjs +14 -0
  161. package/src/session-runtime/model-route/route-tuning.mjs +3 -5
  162. package/src/session-runtime/model-route/set-route.mjs +49 -21
  163. package/src/session-runtime/model-route-api.mjs +2 -1
  164. package/src/session-runtime/notification-bus.mjs +31 -6
  165. package/src/session-runtime/provider-auth/login.mjs +6 -0
  166. package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
  167. package/src/session-runtime/provider-models/row-hydration.mjs +15 -14
  168. package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
  169. package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
  170. package/src/session-runtime/resource-api.mjs +8 -1
  171. package/src/session-runtime/runtime-core.mjs +4 -0
  172. package/src/session-runtime/runtime-facade.mjs +20 -1
  173. package/src/session-runtime/runtime-review-api.mjs +12 -1
  174. package/src/session-runtime/services/agent-tool/helpers.mjs +4 -0
  175. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +10 -15
  176. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +11 -3
  177. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
  178. package/src/session-runtime/services/channel-admin.mjs +87 -13
  179. package/src/session-runtime/services/provider-admin.mjs +256 -63
  180. package/src/session-runtime/services/seeds.mjs +73 -142
  181. package/src/session-runtime/services/usage-dashboard-model.mjs +29 -0
  182. package/src/session-runtime/services/usage-dashboard.mjs +7 -0
  183. package/src/session-runtime/session-address.mjs +125 -0
  184. package/src/session-runtime/session-create.mjs +57 -2
  185. package/src/session-runtime/settings-builtin-tools-api.mjs +8 -0
  186. package/src/session-runtime/settings-compaction-api.mjs +13 -0
  187. package/src/session-runtime/setup-tool/executor.mjs +92 -40
  188. package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
  189. package/src/session-runtime/setup-tool/settings-contract.mjs +44 -1
  190. package/src/session-runtime/setup-tool/tool-defs.mjs +12 -7
  191. package/src/session-runtime/tool-surface.mjs +16 -9
  192. package/src/session-runtime/turn/session-ops.mjs +3 -0
  193. package/src/session-runtime/turn/share-ops.mjs +4 -1
  194. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
  195. package/src/session-runtime/usage-stats-api.mjs +15 -0
  196. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
  197. package/src/session-runtime/workflow-agents-api/agent-route.mjs +4 -0
  198. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
  199. package/src/standalone/daemon-boot-coordinator.mjs +10 -0
  200. package/src/standalone/daemon-drain-policy.mjs +9 -0
  201. package/src/standalone/daemon-stored-session-views.mjs +7 -8
  202. package/src/standalone/daemon.mjs +21 -5
  203. package/src/standalone/local-session-runtime.mjs +11 -0
  204. package/src/standalone/session-client.mjs +52 -7
  205. package/src/standalone/session-protocol.mjs +8 -0
  206. package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
  207. package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
  208. package/src/standalone/session-service/entries/session-loads.mjs +9 -0
  209. package/src/standalone/session-service/projection/revisions.mjs +28 -4
  210. package/src/standalone/session-service/projection.mjs +1 -0
  211. package/src/standalone/session-service/quota-resume.mjs +140 -0
  212. package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
  213. package/src/standalone/session-service/session-calls.mjs +10 -2
  214. package/src/standalone/session-service/stored-reader.mjs +26 -1
  215. package/src/standalone/session-service.mjs +21 -0
  216. package/src/standalone/session-transport/routes.mjs +2 -0
  217. package/src/tui/app/doctor.mjs +7 -11
  218. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
  219. package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
  220. package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
  221. package/src/tui/app/slash-dispatch/route-commands.mjs +3 -2
  222. package/src/tui/components/ContextPanel.jsx +5 -1
  223. package/src/tui/components/Spinner.jsx +34 -319
  224. package/src/tui/components/SpinnerByline.jsx +256 -0
  225. package/src/tui/components/use-spinner-progress.mjs +113 -0
  226. package/src/tui/dist/index.mjs +15540 -14452
  227. package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
  228. package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
  229. package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
  230. package/src/tui/session/agent-job-feed.mjs +4 -0
  231. package/src/tui/session/context-state.mjs +13 -1
  232. package/src/tui/session/initial-state.mjs +6 -0
  233. package/src/tui/session/quota-resume.mjs +263 -0
  234. package/src/tui/session/session-api/intake/abort.mjs +39 -35
  235. package/src/tui/session/session-api/intake/submission.mjs +13 -21
  236. package/src/tui/session/session-api/intake.mjs +3 -3
  237. package/src/tui/session/session-api/integrations.mjs +5 -1
  238. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  239. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
  240. package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
  241. package/src/tui/session/session-api/lifecycle.mjs +71 -2
  242. package/src/tui/session/session-api/media.mjs +2 -2
  243. package/src/tui/session/session-api/quota-resume.mjs +21 -0
  244. package/src/tui/session/session-api/routes.mjs +2 -0
  245. package/src/tui/session/session-api/settings.mjs +1 -0
  246. package/src/tui/session/session-api.mjs +2 -0
  247. package/src/tui/session/session-flow/auto-clear.mjs +14 -6
  248. package/src/tui/session/session-flow/drain.mjs +52 -5
  249. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -3
  250. package/src/tui/session/session-flow/queue.mjs +3 -0
  251. package/src/tui/session/session-flow/steering.mjs +11 -0
  252. package/src/tui/session/session-flow.mjs +1 -0
  253. package/src/tui/session/tool-approval.mjs +7 -2
  254. package/src/tui/session/transcript-intake.mjs +1 -0
  255. package/src/tui/session/turn.mjs +40 -3
  256. package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
@@ -1,7 +1,17 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { pluginSkillsRoots } from '../../runtime/shared/plugin-manifest.mjs';
4
- import { readMarkdownDocument } from '../../runtime/shared/markdown-frontmatter.mjs';
4
+ import {
5
+ EMPTY_HISTORY,
6
+ HISTORY_MARKER_FILE,
7
+ fileHash,
8
+ historyStamp,
9
+ holdsOnlyEditorFiles,
10
+ loadShippedHistory,
11
+ makeBackup,
12
+ packHashFromDir,
13
+ treeHashFromDir,
14
+ } from '../../runtime/shared/shipped-definitions.mjs';
5
15
 
6
16
  export function ensureStandaloneEnvironment({ rootDir, dataDir }) {
7
17
  if (!rootDir) throw new Error('standalone rootDir is required');
@@ -17,8 +27,27 @@ export function ensureStandaloneEnvironment({ rootDir, dataDir }) {
17
27
  process.env.MIXDOG_PATCH_NATIVE_PREWARM ??= '0';
18
28
 
19
29
  mkdirSync(dataDir, { recursive: true });
20
- retireSeededSkillCopies({ rootDir, dataDir });
21
- retireSeededPackCopies({ rootDir, dataDir });
30
+ // Copies equal to the CURRENT shipped version are retired on every start; the
31
+ // (larger) historical comparison runs once per separation/history version.
32
+ const backup = makeBackup(dataDir);
33
+ const markerFile = join(dataDir, HISTORY_MARKER_FILE);
34
+ const stamp = historyStamp(rootDir);
35
+ let recorded = '';
36
+ try {
37
+ recorded = readFileSync(markerFile, 'utf8').trim();
38
+ } catch {
39
+ recorded = '';
40
+ }
41
+ const includeHistory = recorded !== stamp;
42
+ retireSeededSkillCopies({ rootDir, dataDir, backup, includeHistory });
43
+ retireSeededPackCopies({ rootDir, dataDir, backup, includeHistory });
44
+ if (includeHistory && !backup.failures) {
45
+ try {
46
+ writeFileSync(markerFile, `${stamp}\n`);
47
+ } catch {
48
+ /* marker only; the sweep simply repeats */
49
+ }
50
+ }
22
51
  cleanupRetiredChannelSecrets(dataDir);
23
52
  }
24
53
 
@@ -52,16 +81,21 @@ function cleanupRetiredChannelSecrets(dataDir) {
52
81
  });
53
82
  }
54
83
 
55
- // Built-in skills (src/defaults/skills/<name>/) used to be copied into the
56
- // user data skills dir once and never refreshed, so every install kept the
57
- // version it first saw. They are now read in place by the skill collector and
58
- // a user-global skill of the same name shadows them. A leftover seeded copy
59
- // would therefore pin the old text forever: remove it when it is still
60
- // byte-identical to some bundled skill (the user never touched it) and keep
61
- // it when it differs, because then it is the user's own override.
62
- export function retireSeededSkillCopies({ rootDir, dataDir }) {
84
+ // Shipped definitions live in code (read in place from the package); the data
85
+ // dir should hold only copies the user changed. Older releases copied, or the
86
+ // editors saved, full copies that then pin the text of the day they were made.
87
+ // A copy is "unchanged" when it equals the current shipped version or ANY past
88
+ // shipped version (hashes in defaults/shipped-history.json, generated from git
89
+ // by scripts/build-shipped-history.mjs, normalised for CRLF/trailing space).
90
+ // Unchanged copies are moved — never deleted — into
91
+ // <dataDir>/backups/defaults-separation-<timestamp>/, so the shipped version
92
+ // applies again; anything else is the user's own and stays.
93
+
94
+ // Built-in skills (<root>/defaults/skills/<name>/): compared as a whole tree.
95
+ export function retireSeededSkillCopies({ rootDir, dataDir, backup = makeBackup(dataDir), includeHistory = true }) {
63
96
  const targetRoot = join(dataDir, 'skills');
64
97
  if (!existsSync(targetRoot)) return [];
98
+ const history = (includeHistory ? loadShippedHistory(rootDir) : EMPTY_HISTORY).skills;
65
99
  const retired = [];
66
100
  // The bundle is a plugin root; walk the same skill roots the collector reads.
67
101
  for (const bundledDir of pluginSkillsRoots(join(rootDir, 'defaults'))) {
@@ -75,79 +109,53 @@ export function retireSeededSkillCopies({ rootDir, dataDir }) {
75
109
  if (!entry.isDirectory()) continue;
76
110
  const copy = join(targetRoot, entry.name);
77
111
  if (!existsSync(copy)) continue;
78
- if (!sameTree(join(bundledDir, entry.name), copy)) continue;
79
- if (removeEntry(copy)) retired.push(entry.name);
112
+ const copyHash = treeHashFromDir(copy);
113
+ if (!copyHash) continue;
114
+ const known = [treeHashFromDir(join(bundledDir, entry.name)), ...(history[entry.name] || [])];
115
+ if (!known.includes(copyHash)) continue;
116
+ if (backup.move(copy)) retired.push(entry.name);
80
117
  }
81
118
  }
82
119
  return retired;
83
120
  }
84
121
 
85
- function sameTree(left, right) {
86
- let leftEntries;
87
- let rightEntries;
88
- try {
89
- leftEntries = readdirSync(left, { withFileTypes: true });
90
- rightEntries = readdirSync(right, { withFileTypes: true });
91
- } catch {
92
- return false;
93
- }
94
- const names = (entries) => entries.map((entry) => entry.name).sort();
95
- if (names(leftEntries).join('\0') !== names(rightEntries).join('\0')) return false;
96
- for (const entry of leftEntries) {
97
- const a = join(left, entry.name);
98
- const b = join(right, entry.name);
99
- if (entry.isDirectory()) {
100
- if (!sameTree(a, b)) return false;
101
- continue;
102
- }
103
- if (!sameFile(a, b)) return false;
104
- }
105
- return true;
106
- }
107
-
108
- // A seed copied from a CRLF checkout and a package shipped with LF are the
109
- // same text; only carriage returns are ignored, nothing else.
110
- function sameBytes(left, right) {
111
- const strip = (buffer) => Buffer.from(buffer.toString('utf8').replace(/\r\n/g, '\n'), 'utf8');
112
- return strip(left).equals(strip(right));
113
- }
114
-
115
122
  // Shipped workflows (<root>/workflows/<id>/WORKFLOW.md), agents
116
123
  // (<root>/agents/<id>/AGENT.md + agent.json) and output styles
117
- // (<root>/output-styles/<name>.md) are read in place, and a data-dir entry of
118
- // the same id shadows them. The editors save a full copy, so saving a built-in
119
- // pack without changing anything pins the text that shipped that day and no
120
- // later app update ever reaches that user. Such a copy holds nothing of the
121
- // user's: drop it so the current built-in applies again.
124
+ // (<root>/output-styles/<name>.md).
122
125
  //
123
- // "Unchanged" is the editable surface — name, description, body — because the
124
- // editors cannot round-trip the rest (frontmatter flags, manifest id/entry),
125
- // so a byte compare would read every no-op save as an override. A copy holding
126
- // any other file (added references, a `.deleted` tombstone) is the user's own
127
- // and is never touched.
126
+ // "Unchanged" for a pack means every loader-visible field matches: name,
127
+ // description, body and all other frontmatter/manifest fields (permission,
128
+ // hidden, model/tool fields, entry...). Only normalisation (CRLF, trailing
129
+ // space, quoting, an id equal to the folder name, the default entry) is
130
+ // ignored. A copy holding any file beyond the editor's own (added references,
131
+ // a `.deleted` tombstone) is the user's and is never touched; stray backups
132
+ // such as AGENT.md.bak do not count and travel to the backup with the copy.
128
133
  const PACK_KINDS = [
129
- { dir: 'workflows', entry: 'WORKFLOW.md', files: ['WORKFLOW.md'] },
130
- { dir: 'agents', entry: 'AGENT.md', files: ['AGENT.md', 'agent.json'] },
134
+ { dir: 'workflows', historyKey: 'workflows', entry: 'WORKFLOW.md', files: ['WORKFLOW.md'] },
135
+ { dir: 'agents', historyKey: 'agents', entry: 'AGENT.md', files: ['AGENT.md', 'agent.json'] },
131
136
  ];
132
137
 
133
- export function retireSeededPackCopies({ rootDir, dataDir }) {
138
+ export function retireSeededPackCopies({ rootDir, dataDir, backup = makeBackup(dataDir), includeHistory = true }) {
139
+ const history = includeHistory ? loadShippedHistory(rootDir) : EMPTY_HISTORY;
134
140
  const retired = [];
135
141
  for (const kind of PACK_KINDS) {
136
142
  for (const name of childNames(join(dataDir, kind.dir), 'dir')) {
137
143
  const bundled = join(rootDir, kind.dir, name);
138
144
  const copy = join(dataDir, kind.dir, name);
139
- if (!existsSync(join(bundled, kind.entry))) continue;
140
- if (!holdsOnly(copy, kind.files)) continue;
141
- if (!samePackDefinition(bundled, copy, kind.entry)) continue;
142
- if (removeEntry(copy)) retired.push(`${kind.dir}/${name}`);
145
+ const current = packHashFromDir(bundled, kind.entry, name);
146
+ if (!current) continue;
147
+ if (!holdsOnlyEditorFiles(copy, kind.files, kind.entry)) continue;
148
+ const copyHash = packHashFromDir(copy, kind.entry, name);
149
+ if (![current, ...(history[kind.historyKey][name] || [])].includes(copyHash)) continue;
150
+ if (backup.move(copy)) retired.push(`${kind.dir}/${name}`);
143
151
  }
144
152
  }
145
153
  for (const name of childNames(join(dataDir, 'output-styles'), 'md')) {
146
- const bundled = join(rootDir, 'output-styles', name);
154
+ const bundled = fileHash(join(rootDir, 'output-styles', name));
155
+ if (!bundled) continue;
147
156
  const copy = join(dataDir, 'output-styles', name);
148
- if (!existsSync(bundled)) continue;
149
- if (!sameFile(bundled, copy)) continue;
150
- if (removeEntry(copy)) retired.push(`output-styles/${name}`);
157
+ if (![bundled, ...(history.outputStyles[name] || [])].includes(fileHash(copy))) continue;
158
+ if (backup.move(copy)) retired.push(`output-styles/${name}`);
151
159
  }
152
160
  return retired;
153
161
  }
@@ -163,80 +171,3 @@ function childNames(root, kind) {
163
171
  kind === 'dir' ? entry.isDirectory() : entry.isFile() && entry.name.toLowerCase().endsWith('.md');
164
172
  return entries.filter(wanted).map((entry) => entry.name);
165
173
  }
166
-
167
- // The copy may hold only the files an editor save writes; anything else in
168
- // there was put there by the user and outranks the shipped pack.
169
- function holdsOnly(dir, allowed) {
170
- let entries;
171
- try {
172
- entries = readdirSync(dir, { withFileTypes: true });
173
- } catch {
174
- return false;
175
- }
176
- return entries.length > 0 && entries.every((entry) => entry.isFile() && allowed.includes(entry.name));
177
- }
178
-
179
- function samePackDefinition(bundledDir, copyDir, entry) {
180
- const bundled = packDefinition(bundledDir, entry);
181
- const copy = packDefinition(copyDir, entry);
182
- if (!bundled || !copy) return false;
183
- return bundled.name === copy.name && bundled.description === copy.description && bundled.body === copy.body;
184
- }
185
-
186
- // Same resolution the pack loaders use: agent.json wins over the markdown
187
- // frontmatter for name/description, and the body is the markdown minus it.
188
- function packDefinition(dir, entry) {
189
- const manifest = readJsonFile(join(dir, 'agent.json'));
190
- const raw = readTextFile(join(dir, oneLine(manifest.entry) || entry));
191
- const doc = readMarkdownDocument(raw);
192
- const body = String(doc.body || '')
193
- .replace(/\r\n/g, '\n')
194
- .trim();
195
- if (!body) return null;
196
- return {
197
- name: oneLine(manifest.name || doc.frontmatter.name),
198
- description: oneLine(manifest.description || doc.frontmatter.description),
199
- body,
200
- };
201
- }
202
-
203
- function readJsonFile(file) {
204
- try {
205
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
206
- return parsed && typeof parsed === 'object' ? parsed : {};
207
- } catch {
208
- return {};
209
- }
210
- }
211
-
212
- function readTextFile(file) {
213
- try {
214
- return readFileSync(file, 'utf8');
215
- } catch {
216
- return '';
217
- }
218
- }
219
-
220
- function oneLine(value) {
221
- return String(value || '')
222
- .replace(/\s+/g, ' ')
223
- .trim();
224
- }
225
-
226
- function sameFile(left, right) {
227
- try {
228
- return sameBytes(readFileSync(left), readFileSync(right));
229
- } catch {
230
- return false;
231
- }
232
- }
233
-
234
- function removeEntry(target) {
235
- try {
236
- rmSync(target, { recursive: true, force: true });
237
- return true;
238
- } catch {
239
- // best-effort; a stuck copy simply keeps shadowing until removed by hand
240
- return false;
241
- }
242
- }
@@ -445,9 +445,38 @@ function usageTotal(rows) {
445
445
  return total;
446
446
  }
447
447
 
448
+ // Credit feature of a subscription provider; null unless the account has it
449
+ // enabled. Codex counts credits, Claude's extra usage is USD.
450
+ export function creditsFromSnapshot(providerId, snapshot) {
451
+ const balance = snapshot?.balance;
452
+ if (providerId === 'openai-oauth') {
453
+ if (balance?.unit !== 'credits') return null;
454
+ const amount = num(balance.remainingCredits, null);
455
+ const unlimited = balance.unlimited === true;
456
+ if (!(balance.hasCredits === true || unlimited || (amount !== null && amount > 0))) return null;
457
+ return { unit: 'credits', balance: amount, ...(unlimited ? { unlimited: true } : {}) };
458
+ }
459
+ if (providerId === 'anthropic-oauth') {
460
+ const extra = (snapshot?.quotaWindows || []).find((w) => w?.label === 'EXTRA');
461
+ const spend = balance && num(balance.remainingUsd, null) !== null ? balance : null;
462
+ if (!extra && !spend) return null;
463
+ const remaining = num(spend?.remainingUsd ?? extra?.remainingUsd, null);
464
+ const monthlyLimit = num(spend?.budgetUsd ?? extra?.limitUsd, null);
465
+ const spent = num(spend?.spentUsd ?? extra?.usedUsd, null);
466
+ return {
467
+ unit: 'usd',
468
+ balance: remaining ?? (monthlyLimit !== null && spent !== null ? Math.max(0, monthlyLimit - spent) : null),
469
+ ...(monthlyLimit !== null ? { monthlyLimit } : {}),
470
+ ...(spent !== null ? { spent } : {}),
471
+ };
472
+ }
473
+ return null;
474
+ }
475
+
448
476
  function snapshotRow(row) {
449
477
  return {
450
478
  ...row,
479
+ ...(row.credits ? { credits: { ...row.credits } } : {}),
451
480
  windows: row.windows.map((window) => ({ ...window })),
452
481
  ...(row.resetCredits
453
482
  ? {
@@ -12,7 +12,9 @@ import {
12
12
  readCachedApiUsageSnapshot,
13
13
  } from '../../runtime/agent/orchestrator/providers/api-usage.mjs';
14
14
  import { num } from '../../runtime/agent/orchestrator/providers/lib/usage-primitives.mjs';
15
+ import { readProviderAccountPool } from '../../runtime/shared/provider-accounts.mjs';
15
16
  import {
17
+ creditsFromSnapshot,
16
18
  applyApiUnavailable,
17
19
  applyKnownRemaining,
18
20
  applyKnownUsage,
@@ -31,6 +33,8 @@ import {
31
33
  usageDashboardSnapshot,
32
34
  } from './usage-dashboard-model.mjs';
33
35
 
36
+ const CREDIT_PROVIDERS = new Set(['openai-oauth', 'anthropic-oauth']);
37
+
34
38
  async function oauthSnapshot(providerId, { refresh, getProvider, log }) {
35
39
  const cached = readCachedOAuthUsageSnapshot({ provider: providerId, model: '' });
36
40
  if (!refresh && cached) return cached;
@@ -206,6 +210,7 @@ async function oauthProviderRow(item, { providers, preview, snapshotOptions, row
206
210
  row.primary = row.authenticated ? '' : 'not signed in';
207
211
  row.detail = row.authenticated ? 'Checking provider usage' : item.detail || 'OAuth credentials missing';
208
212
  row.tone = rowTone(row);
213
+ if (CREDIT_PROVIDERS.has(item.id)) row.useCredits = readProviderAccountPool(item.id).useCredits === true;
209
214
  rows.push(row);
210
215
  emit(true);
211
216
 
@@ -222,6 +227,8 @@ async function oauthProviderRow(item, { providers, preview, snapshotOptions, row
222
227
  const resetCredits = normaliseResetCredits(snapshot?.resetCredits);
223
228
  row.windows = windows;
224
229
  if (resetCredits) row.resetCredits = resetCredits;
230
+ const credits = creditsFromSnapshot(item.id, snapshot);
231
+ if (credits) row.credits = credits;
225
232
  row.updatedAt = num(snapshot?.cachedAt, null);
226
233
  if (known) {
227
234
  applyKnownRemaining(row, known, { estimated: false });
@@ -0,0 +1,125 @@
1
+ // session-address.mjs — the durable address of an addressed runtime.
2
+ //
3
+ // A daemon-addressed runtime (reserved or loaded by the session service) owns
4
+ // exactly ONE conversation id for its whole life. `reservedSessionId` is the
5
+ // one-shot "materialize me" token and is cleared on first adoption; the
6
+ // address outlives it. The address is bound at reservation and at
7
+ // resume/load-on-demand, and thereafter:
8
+ // - "no live session object" never permits creating another conversation;
9
+ // - an OPEN durable record under the address is resumed (same id);
10
+ // - a CLOSED record makes the runtime terminal (never recreated/overwritten);
11
+ // - a missing/unreadable record is an explicit failure (never minted).
12
+ // Admission (open → closing → terminal) is the canonical-close fence.
13
+ import { SessionClosedError } from '../runtime/agent/orchestrator/session/manager/session-errors.mjs';
14
+
15
+ export class SessionAddressError extends Error {
16
+ constructor(message, sessionId = null) {
17
+ super(message);
18
+ this.name = 'SessionAddressError';
19
+ this.sessionId = sessionId;
20
+ }
21
+ }
22
+
23
+ export function initSessionAddress(rt) {
24
+ rt.addressedSessionId = null;
25
+ rt.admission = 'open';
26
+ rt.lifecycleGeneration = 0;
27
+ }
28
+
29
+ /** Bind (or re-confirm) the durable address. A second, different id is a
30
+ * re-key attempt and is refused. */
31
+ export function bindSessionAddress(rt, id) {
32
+ const next = String(id || '').trim();
33
+ if (!next) throw new SessionAddressError('session address is required');
34
+ if (rt.addressedSessionId && rt.addressedSessionId !== next) {
35
+ throw new SessionAddressError(
36
+ `runtime is addressed as ${rt.addressedSessionId}; it cannot be re-keyed to ${next} (use a new session entry)`,
37
+ rt.addressedSessionId
38
+ );
39
+ }
40
+ rt.addressedSessionId = next;
41
+ return next;
42
+ }
43
+
44
+ export const isAddressedTo = (rt, id) => !!rt.addressedSessionId && rt.addressedSessionId === String(id || '');
45
+
46
+ /** Refuse an explicit transition that would move an addressed runtime to
47
+ * another conversation. */
48
+ export function assertNotAddressed(rt, operation) {
49
+ if (rt.addressedSessionId) {
50
+ throw new SessionAddressError(
51
+ `${operation} would re-key addressed session ${rt.addressedSessionId}; create a new session entry instead`,
52
+ rt.addressedSessionId
53
+ );
54
+ }
55
+ }
56
+
57
+ export const isAdmissionOpen = (rt) => !rt.admission || rt.admission === 'open';
58
+
59
+ export function assertAdmissionOpen(rt) {
60
+ if (isAdmissionOpen(rt)) return;
61
+ throw new SessionClosedError(rt.addressedSessionId || 'unknown', 'session is closed to new work', 'canonical-close');
62
+ }
63
+
64
+ /** Resume may only reopen the conversation this runtime is addressed as. */
65
+ export function assertResumeTarget(rt, id) {
66
+ assertAdmissionOpen(rt);
67
+ if (rt.addressedSessionId && rt.addressedSessionId !== String(id || '')) {
68
+ throw new SessionAddressError(
69
+ `runtime is addressed as ${rt.addressedSessionId}; it cannot resume ${id} (use a new session entry)`,
70
+ rt.addressedSessionId
71
+ );
72
+ }
73
+ }
74
+
75
+ /** Fence admission before the manager close can cancel owned shells. */
76
+ export function beginCanonicalClose(rt, sessionId) {
77
+ const previous = rt.admission || 'open';
78
+ if (!rt.addressedSessionId) rt.addressedSessionId = String(sessionId);
79
+ rt.admission = 'closing';
80
+ return previous;
81
+ }
82
+
83
+ /** Settle the fence: terminal after a durable close, restored otherwise. */
84
+ export function finishCanonicalClose(rt, previous, closed) {
85
+ if (closed) markTerminal(rt);
86
+ else rt.admission = previous;
87
+ }
88
+
89
+ export function markTerminal(rt) {
90
+ if (rt.admission !== 'terminal') {
91
+ rt.admission = 'terminal';
92
+ rt.lifecycleGeneration = (rt.lifecycleGeneration || 0) + 1;
93
+ }
94
+ }
95
+
96
+ /** Lifecycle view used by the notification bus; null = not an owner here. */
97
+ export function ownerLifecycle(rt, sessionId) {
98
+ if (!isAddressedTo(rt, sessionId)) return null;
99
+ return { state: rt.admission || 'open', generation: rt.lifecycleGeneration || 0 };
100
+ }
101
+
102
+ /** The durable record under the address: open | closed | missing. */
103
+ export function addressedRecord(rt, mgr) {
104
+ let record = null;
105
+ try {
106
+ record = mgr.getSession(rt.addressedSessionId);
107
+ } catch {
108
+ record = null;
109
+ }
110
+ if (!record) return { state: 'missing', record: null };
111
+ if (record.closed === true || record.status === 'closed') return { state: 'closed', record };
112
+ return { state: 'open', record };
113
+ }
114
+
115
+ export function closedAddressError(rt) {
116
+ markTerminal(rt);
117
+ return new SessionClosedError(rt.addressedSessionId, 'durable record is closed', 'canonical-close');
118
+ }
119
+
120
+ export function missingAddressError(rt) {
121
+ return new SessionAddressError(
122
+ `session ${rt.addressedSessionId} record is missing or unreadable; refusing to create another conversation`,
123
+ rt.addressedSessionId
124
+ );
125
+ }
@@ -8,11 +8,22 @@ import { runAbortable, throwIfAborted } from '../runtime/shared/abort-race.mjs';
8
8
  import { elapsedMs, prepareRoute } from './session-create/route-prep.mjs';
9
9
  import { materializeSession, prewarmWsTransport } from './session-create/materialize.mjs';
10
10
  import { dispatchSessionStart } from './session-create/session-start-hook.mjs';
11
+ import { SessionClosedError } from '../runtime/agent/orchestrator/session/manager/session-errors.mjs';
12
+ import {
13
+ SessionAddressError,
14
+ addressedRecord,
15
+ assertAdmissionOpen,
16
+ closedAddressError,
17
+ isAddressedTo,
18
+ missingAddressError,
19
+ } from './session-address.mjs';
11
20
 
12
21
  export function createSessionCreator(deps, routes) {
13
22
  const { rt, mgr } = deps;
14
23
 
15
- /** A still-open live session short-circuits creation. */
24
+ /** A still-open live session short-circuits creation. For an addressed
25
+ * runtime a closed/missing record is terminal/explicit failure — never
26
+ * "no session, so create one". */
16
27
  function liveSession() {
17
28
  if (!rt.session?.id) return null;
18
29
  const live = mgr.getSession(rt.session.id);
@@ -20,14 +31,56 @@ export function createSessionCreator(deps, routes) {
20
31
  rt.session = live;
21
32
  return live;
22
33
  }
34
+ if (isAddressedTo(rt, rt.session.id)) {
35
+ if (live) throw closedAddressError(rt);
36
+ throw missingAddressError(rt);
37
+ }
23
38
  rt.session = null;
24
39
  return null;
25
40
  }
26
41
 
42
+ /** Same-id recovery of an OPEN durable conversation (idle eviction, manager
43
+ * unload, load-on-demand, crash restore) through the lifecycle resume. */
44
+ async function resumeAddressed(signal) {
45
+ if (typeof deps.resumeAddressedSession !== 'function') {
46
+ throw new SessionAddressError('addressed session resume is unavailable', rt.addressedSessionId);
47
+ }
48
+ const id = rt.addressedSessionId;
49
+ await runAbortable(signal, () => deps.resumeAddressedSession(id), 'Session creation aborted');
50
+ assertAdmissionOpen(rt);
51
+ if (rt.session?.id === id) return rt.session;
52
+ const { state } = addressedRecord(rt, mgr);
53
+ if (state === 'closed') throw closedAddressError(rt);
54
+ throw new SessionAddressError(`session ${id} could not be resumed; refusing to create another`, id);
55
+ }
56
+
57
+ /** Decide, from the durable record under the address, whether this runtime
58
+ * may materialize (first time, reserved) or must resume. Synchronous so no
59
+ * await sits between the decision and the adoption. */
60
+ function addressDecision() {
61
+ if (!rt.addressedSessionId) return 'create';
62
+ const { state } = addressedRecord(rt, mgr);
63
+ if (state === 'closed') throw closedAddressError(rt);
64
+ if (state === 'open') return 'resume';
65
+ if (rt.reservedSessionId === rt.addressedSessionId) return 'create';
66
+ throw missingAddressError(rt);
67
+ }
68
+
27
69
  async function buildSession(reason, signal, startedAt) {
28
70
  const { coreMemoryContext, providerImpl } = await prepareRoute(deps, routes, signal, startedAt);
71
+ // The fence/record may have changed during the awaits above (close during
72
+ // reservation prewarm): re-validate synchronously right before adoption.
73
+ assertAdmissionOpen(rt);
74
+ if (addressDecision() === 'resume') return await resumeAddressed(signal);
29
75
  materializeSession(deps, reason, coreMemoryContext);
30
76
  await dispatchSessionStart(deps, reason, signal);
77
+ assertAdmissionOpen(rt);
78
+ if (!rt.session)
79
+ throw new SessionClosedError(
80
+ rt.addressedSessionId || 'unknown',
81
+ 'session closed during creation',
82
+ 'canonical-close'
83
+ );
31
84
  prewarmWsTransport(rt, providerImpl);
32
85
  throwIfAborted(signal);
33
86
  bootProfile('session:create:ready', {
@@ -42,11 +95,13 @@ export function createSessionCreator(deps, routes) {
42
95
  return async function createCurrentSession(reason = 'demand', options = {}) {
43
96
  const signal = options?.signal || null;
44
97
  throwIfAborted(signal);
98
+ assertAdmissionOpen(rt);
45
99
  if (rt.sessionCreatePromise) {
46
100
  return await runAbortable(signal, () => rt.sessionCreatePromise, 'Session creation aborted');
47
101
  }
48
102
  const live = liveSession();
49
103
  if (live) return live;
104
+ const decision = addressDecision();
50
105
 
51
106
  const startedAt = performance.now();
52
107
  bootProfile('session:create:start', { mode: rt.mode, reason });
@@ -58,7 +113,7 @@ export function createSessionCreator(deps, routes) {
58
113
  bootProfile('agent-loop:prewarm-failed', { error: error?.message || String(error) });
59
114
  });
60
115
  }
61
- const promise = buildSession(reason, signal, startedAt);
116
+ const promise = decision === 'resume' ? resumeAddressed(signal) : buildSession(reason, signal, startedAt);
62
117
  rt.sessionCreatePromise = promise;
63
118
  try {
64
119
  return await promise;
@@ -9,7 +9,9 @@ import {
9
9
  } from '../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
10
10
  import { LOCAL_PROVIDER_ID } from '../runtime/local-provider/managed-runtime.mjs';
11
11
  import { getEmbeddingInfo } from '../runtime/memory/lib/embedding-provider.mjs';
12
+ import { onnxRuntimeSupported } from '../runtime/shared/onnx-runtime-support.mjs';
12
13
  import {
14
+ EFFORT_JUDGE_UNSUPPORTED,
13
15
  effortJudgeAvailable,
14
16
  effortJudgeInfo,
15
17
  effortJudgeInstalling,
@@ -82,6 +84,7 @@ export function createBuiltinToolSettings(
82
84
  enabled: memoryToolsEnabledFn(),
83
85
  installed: builtinInstalled(config, 'memory'),
84
86
  info: getEmbeddingInfo(),
87
+ semanticSearchSupported: onnxRuntimeSupported(),
85
88
  },
86
89
  git: { enabled: gitToolsEnabledFn(), installed: builtinInstalled(config, 'git') },
87
90
  office: { enabled: officeToolsEnabledFn(), installed: builtinInstalled(config, 'office') },
@@ -96,6 +99,7 @@ export function createBuiltinToolSettings(
96
99
  installed: builtinInstalled(config, 'localProvider') && localProviderRuntimeInstalled,
97
100
  },
98
101
  autoEffort: {
102
+ supported: onnxRuntimeSupported(),
99
103
  enabled: builtinFeatureActive(config, 'autoEffort'),
100
104
  // On by default: "installed" means the judge model is on disk;
101
105
  // "installing" covers the boot download the settings did not start.
@@ -135,6 +139,9 @@ export function createBuiltinToolSettings(
135
139
  if (!TOGGLEABLE_BUILTINS.includes(name)) {
136
140
  throw new TypeError('Built-in tool must be git, office, tidy, localProvider, or autoEffort.');
137
141
  }
142
+ if (name === 'autoEffort' && enabled !== false && !onnxRuntimeSupported()) {
143
+ throw new Error(EFFORT_JUDGE_UNSUPPORTED);
144
+ }
138
145
  if (name === 'localProvider' && enabled !== false && getLocalProviderStatus?.()?.runtime?.installed !== true) {
139
146
  await prepareBuiltinFeature?.(name);
140
147
  }
@@ -165,6 +172,7 @@ export function createBuiltinToolSettings(
165
172
  if (!INSTALLABLE_BUILTIN_IDS.includes(name)) {
166
173
  throw new TypeError('Built-in feature must be git, memory, office, tidy, localProvider, or autoEffort.');
167
174
  }
175
+ if (name === 'autoEffort' && !onnxRuntimeSupported()) throw new Error(EFFORT_JUDGE_UNSUPPORTED);
168
176
  await prepareBuiltinFeature?.(name);
169
177
  const config = getConfig();
170
178
  let nextConfig = setBuiltinInstalledInConfig({ ...config }, name, true);
@@ -1,4 +1,8 @@
1
1
  // Context lifecycle settings: compaction budgets and the idle auto-clear.
2
+ import {
3
+ refreshLiveSessionsProviderCacheOpts,
4
+ refreshSessionProviderCacheOpts,
5
+ } from '../runtime/agent/orchestrator/session/manager/session-lifecycle.mjs';
2
6
 
3
7
  // Every spelling of the main-context buffer budget; a new representation
4
8
  // replaces the previous one.
@@ -46,6 +50,13 @@ export function createCompactionSettings({
46
50
  formatDurationMs,
47
51
  invalidateContextStatusCache,
48
52
  }) {
53
+ // The Lead messages-tail TTL derives from the auto-clear idle window.
54
+ const refreshLeadCacheOpts = () => {
55
+ const config = getConfig();
56
+ refreshSessionProviderCacheOpts(getSession(), config);
57
+ refreshLiveSessionsProviderCacheOpts(config);
58
+ };
59
+
49
60
  /** Apply a per-provider idle override (or its reset) and return the resolved view. */
50
61
  const saveProviderIdle = (config, next, providerKey, input) => {
51
62
  const providerIdleMs = { ...(next.providerIdleMs || {}) };
@@ -57,6 +68,7 @@ export function createCompactionSettings({
57
68
  providerIdleMs[providerKey] = Math.max(60_000, Math.round(idleMs));
58
69
  }
59
70
  saveConfigAndAdopt({ ...config, autoClear: { ...next, providerIdleMs } });
71
+ refreshLeadCacheOpts();
60
72
  };
61
73
 
62
74
  return {
@@ -157,6 +169,7 @@ export function createCompactionSettings({
157
169
  if (!hasOwn(input, 'enabled')) next.enabled = true;
158
170
  }
159
171
  saveConfigAndAdopt({ ...config, autoClear: next });
172
+ refreshLeadCacheOpts();
160
173
  }
161
174
  const resolved = this.getAutoClear();
162
175
  return { ...resolved, label: formatDurationMs(resolved.idleMs) };