@caupulican/pi-adaptative 0.80.99 → 0.80.102

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 (129) hide show
  1. package/CHANGELOG.md +203 -0
  2. package/dist/core/agent-session.d.ts +72 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +437 -46
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/autonomy/envelope-enforcement.d.ts +5 -0
  7. package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
  8. package/dist/core/autonomy/envelope-enforcement.js +28 -4
  9. package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
  10. package/dist/core/autonomy/path-scope.d.ts.map +1 -1
  11. package/dist/core/autonomy/path-scope.js +33 -2
  12. package/dist/core/autonomy/path-scope.js.map +1 -1
  13. package/dist/core/bash-executor.d.ts.map +1 -1
  14. package/dist/core/bash-executor.js +15 -7
  15. package/dist/core/bash-executor.js.map +1 -1
  16. package/dist/core/context/brain-curator.d.ts +10 -0
  17. package/dist/core/context/brain-curator.d.ts.map +1 -1
  18. package/dist/core/context/brain-curator.js +19 -2
  19. package/dist/core/context/brain-curator.js.map +1 -1
  20. package/dist/core/context-gc.d.ts +8 -3
  21. package/dist/core/context-gc.d.ts.map +1 -1
  22. package/dist/core/context-gc.js +5 -0
  23. package/dist/core/context-gc.js.map +1 -1
  24. package/dist/core/delegation/worker-actions.d.ts +0 -10
  25. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  26. package/dist/core/delegation/worker-actions.js +5 -7
  27. package/dist/core/delegation/worker-actions.js.map +1 -1
  28. package/dist/core/delegation/worker-result.d.ts +6 -0
  29. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  30. package/dist/core/delegation/worker-result.js +10 -4
  31. package/dist/core/delegation/worker-result.js.map +1 -1
  32. package/dist/core/delegation/worker-runner.d.ts +3 -0
  33. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  34. package/dist/core/delegation/worker-runner.js +6 -1
  35. package/dist/core/delegation/worker-runner.js.map +1 -1
  36. package/dist/core/doctor.d.ts +66 -0
  37. package/dist/core/doctor.d.ts.map +1 -0
  38. package/dist/core/doctor.js +144 -0
  39. package/dist/core/doctor.js.map +1 -0
  40. package/dist/core/learning/learning-audit.d.ts +13 -1
  41. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  42. package/dist/core/learning/learning-audit.js +22 -1
  43. package/dist/core/learning/learning-audit.js.map +1 -1
  44. package/dist/core/learning/learning-gate.d.ts +3 -0
  45. package/dist/core/learning/learning-gate.d.ts.map +1 -1
  46. package/dist/core/learning/learning-gate.js +13 -4
  47. package/dist/core/learning/learning-gate.js.map +1 -1
  48. package/dist/core/models/default-model-suggestions.d.ts +34 -0
  49. package/dist/core/models/default-model-suggestions.d.ts.map +1 -0
  50. package/dist/core/models/default-model-suggestions.js +58 -0
  51. package/dist/core/models/default-model-suggestions.js.map +1 -0
  52. package/dist/core/models/local-registration.d.ts +2 -0
  53. package/dist/core/models/local-registration.d.ts.map +1 -1
  54. package/dist/core/models/local-registration.js +2 -1
  55. package/dist/core/models/local-registration.js.map +1 -1
  56. package/dist/core/models/local-runtime.d.ts +76 -2
  57. package/dist/core/models/local-runtime.d.ts.map +1 -1
  58. package/dist/core/models/local-runtime.js +233 -21
  59. package/dist/core/models/local-runtime.js.map +1 -1
  60. package/dist/core/profile-resource-selection.d.ts +15 -3
  61. package/dist/core/profile-resource-selection.d.ts.map +1 -1
  62. package/dist/core/profile-resource-selection.js +19 -9
  63. package/dist/core/profile-resource-selection.js.map +1 -1
  64. package/dist/core/resource-loader.d.ts +20 -0
  65. package/dist/core/resource-loader.d.ts.map +1 -1
  66. package/dist/core/resource-loader.js +42 -16
  67. package/dist/core/resource-loader.js.map +1 -1
  68. package/dist/core/settings-manager.d.ts +30 -0
  69. package/dist/core/settings-manager.d.ts.map +1 -1
  70. package/dist/core/settings-manager.js +65 -3
  71. package/dist/core/settings-manager.js.map +1 -1
  72. package/dist/core/skills.d.ts +14 -0
  73. package/dist/core/skills.d.ts.map +1 -1
  74. package/dist/core/skills.js +8 -0
  75. package/dist/core/skills.js.map +1 -1
  76. package/dist/core/slash-commands.d.ts.map +1 -1
  77. package/dist/core/slash-commands.js +1 -1
  78. package/dist/core/slash-commands.js.map +1 -1
  79. package/dist/core/tools/fff-search-backend.d.ts +34 -1
  80. package/dist/core/tools/fff-search-backend.d.ts.map +1 -1
  81. package/dist/core/tools/fff-search-backend.js +43 -4
  82. package/dist/core/tools/fff-search-backend.js.map +1 -1
  83. package/dist/core/tools/find.d.ts +16 -0
  84. package/dist/core/tools/find.d.ts.map +1 -1
  85. package/dist/core/tools/find.js +17 -3
  86. package/dist/core/tools/find.js.map +1 -1
  87. package/dist/core/tools/grep.d.ts +20 -0
  88. package/dist/core/tools/grep.d.ts.map +1 -1
  89. package/dist/core/tools/grep.js +15 -3
  90. package/dist/core/tools/grep.js.map +1 -1
  91. package/dist/main.d.ts.map +1 -1
  92. package/dist/main.js +17 -0
  93. package/dist/main.js.map +1 -1
  94. package/dist/modes/interactive/components/fitness-role-selector.d.ts +6 -2
  95. package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/fitness-role-selector.js +55 -45
  97. package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -1
  98. package/dist/modes/interactive/components/model-suggestion-selector.d.ts +13 -0
  99. package/dist/modes/interactive/components/model-suggestion-selector.d.ts.map +1 -0
  100. package/dist/modes/interactive/components/model-suggestion-selector.js +40 -0
  101. package/dist/modes/interactive/components/model-suggestion-selector.js.map +1 -0
  102. package/dist/modes/interactive/components/profile-resource-editor.d.ts +3 -0
  103. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/profile-resource-editor.js +16 -2
  105. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  106. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/settings-selector.js +98 -0
  108. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.d.ts +8 -1
  110. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.js +57 -10
  112. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/dist/utils/safe-write-stream.d.ts +8 -0
  114. package/dist/utils/safe-write-stream.d.ts.map +1 -1
  115. package/dist/utils/safe-write-stream.js +32 -0
  116. package/dist/utils/safe-write-stream.js.map +1 -1
  117. package/dist/utils/tools-manager.d.ts +92 -2
  118. package/dist/utils/tools-manager.d.ts.map +1 -1
  119. package/dist/utils/tools-manager.js +172 -32
  120. package/dist/utils/tools-manager.js.map +1 -1
  121. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  122. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  123. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  124. package/examples/extensions/sandbox/package-lock.json +2 -2
  125. package/examples/extensions/sandbox/package.json +1 -1
  126. package/examples/extensions/with-deps/package-lock.json +2 -2
  127. package/examples/extensions/with-deps/package.json +1 -1
  128. package/npm-shrinkwrap.json +12 -36
  129. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,3 +1,206 @@
1
+ ## [0.80.102] - 2026-07-03
2
+
3
+ ### Fixed
4
+
5
+ - Made the FFF lazy-install test suite environment-independent. Three tests asserted that
6
+ `@ff-labs/fff-node` was not resolvable — which held locally (the package resolves only via the
7
+ managed install) but failed in CI, where the declared dependency is installed by `npm ci`,
8
+ blocking v0.80.101 from publishing. `loadAvailableFffNodePackage`/`ensureFffNodePackage` now take
9
+ an optional injectable resolver-candidate list, and those tests pass an empty list to force the
10
+ "not available" precondition deterministically regardless of environment. Test-only; no runtime
11
+ behavior change. 0.80.102 republishes the 0.80.101 changes with this fix.
12
+
13
+ ## [0.80.101] - 2026-07-03
14
+
15
+ ### Added
16
+
17
+ - Skills may declare a `thinking` level in their frontmatter (parsed and validated on load, invalid
18
+ values ignored), so a task-specific skill can carry its intended reasoning depth. The parsed value
19
+ is available wherever `Skill` objects flow; turn-scoped application of an active skill's thinking
20
+ is a follow-up (an extension can already apply it via the existing `ctx.setThinkingLevel()` hook).
21
+ Complements profile-level thinking, which is applied to the session across CLI startup, SDK
22
+ creation, `/profile` switch, and `/reload` (explicit `--thinking` still wins) — now covered by
23
+ tests, including its composition with per-tier router thinking (the router override wins for the
24
+ routed turn and restores the profile level after).
25
+ - Per-tier thinking level for the model router: `modelRouter` now accepts `cheapThinking`,
26
+ `mediumThinking`, `expensiveThinking`, `executorThinking`, and `judgeThinking`. A routed turn
27
+ applies the configured thinking level for its tier (clamped to the routed model), so a
28
+ cheap/local tier can run at `low`/`off` while the expensive tier runs at `high` — thinking level
29
+ is now a cost/speed lever independent of the model choice. Unset per-tier thinking reproduces
30
+ today's inherit-and-clamp behavior exactly; the routing judge's own bounded completion uses
31
+ `judgeThinking` (default `off`). Executor-direct routes are matched before the cheap tier so they
32
+ read `executorThinking`, not `cheapThinking`. Each tier's thinking level is settable in the Model
33
+ Router settings submenu (an "(inherit)" option clears it to the default behavior).
34
+ - `/models suggest` now opens an interactive picker over the validated local-model roster instead of
35
+ printing a list to retype: choosing a model installs it, probes its six fitness surfaces on the
36
+ actual hardware, and lands the role it was shaped for already pre-selected in the assignment step —
37
+ so a suggested model goes from pick to installed-and-set in one flow. The suggestion's shaped
38
+ `assignRole` (previously unused metadata) drives the pre-selection; non-tool-calling models carry
39
+ curator/judge/lane roles and never pre-select the executor role.
40
+ - The model router now auto-manages the local ollama server: when a turn routes to a local (ollama)
41
+ model, pi ensures the server is up first — reusing an already-running server and the user's own
42
+ `~/.ollama` models (never pi's owned storage; a reuse-mode start omits the `OLLAMA_MODELS` override
43
+ that `/models add` still uses) or booting one if needed — so a local-routed turn never fails just
44
+ because the server wasn't started yet. A per-session "confirmed up" cache skips the health check on
45
+ later turns (re-checked only after a connection error from the same model). If the local model
46
+ can't be made ready (binary missing, or the server won't boot) the turn does not dead-end: a
47
+ visible warning states why (install steps inline when the binary is missing) and which tier is now
48
+ handling the turn, then it escalates to the next configured tier. The runtime is owned by
49
+ `AgentSession`, so headless/SDK turns get the same behavior and `/models` shares the one instance.
50
+ - When a routed local model is missing only because the ollama binary itself isn't installed (not
51
+ because its server is down), an interactive session now asks first: "Install Ollama?", noting it's
52
+ a large one-time download, before doing anything. A "yes" downloads the pinned ollama release for
53
+ the current platform straight into pi's own `runtimes/ollama` directory (never `curl | sh`, never
54
+ outside pi's own directory) and extracts it — `.tar.zst` archives decompress via Node's own
55
+ built-in zstd support when available, falling back to a system `zstd` binary, with an honest error
56
+ if neither exists — then the turn proceeds on the original local model with no fallback needed. A
57
+ "no", a timeout, an install that itself fails, or a headless/SDK session with no interactive prompt
58
+ to ask through all fall straight through to the existing graceful tier-escalation warning above,
59
+ worded to say so honestly (an install attempt that failed is reported as a failed install attempt,
60
+ never re-labeled as if nothing was tried). The confirm dialog pauses the routing working-indicator
61
+ while it's on screen and hands it back right after, so the spinner and the dialog never compete for
62
+ the terminal at the same time.
63
+ - New `pi-adaptative doctor` command plus an update/startup preflight that verify required tooling and
64
+ provision what pi owns. It checks FFF native search (managed — actually installs it if missing, via
65
+ the same path as lazy first-use), ripgrep, ollama (binary + server status), and python, reporting
66
+ each as present/missing with version/detail and, for tools pi doesn't own, guide-mode install steps
67
+ (printed, never executed — no `curl | sh`, no `sudo`). The preflight runs automatically after a
68
+ successful `pi-adaptative update` (best-effort, never fails the update), so an update actually
69
+ provisions the managed tooling instead of leaving it to lazy first-use — closing the "ran update on
70
+ another machine and FFF never got installed" gap. The interactive `doctor` shows install progress;
71
+ the background preflight stays quiet.
72
+
73
+ ### Fixed
74
+
75
+ - FFF native search now provisions itself even for ordinary searches. `find`/`grep` decide
76
+ FFF-vs-fallback using their default result limit (1000/100), which exceeds the router's top-N
77
+ threshold, so the decision is "fallback" and the tool returned *before* ever calling `getFinder()`
78
+ — the call that triggers the lazy managed install of `@ff-labs/fff-node`. A machine that only ran
79
+ default-limit searches therefore never installed FFF at all (rg/fd silently handled everything, so
80
+ its speed was never available). Provisioning is now decoupled from the per-call routing outcome:
81
+ `find`/`grep` kick off the finder unconditionally via a new `safeGetFinder` that can never reject
82
+ (even if a custom backend's `getFinder` throws synchronously), reusing the same in-flight promise
83
+ when the call does route to FFF. A genuine install failure is now retryable instead of permanently
84
+ gating FFF out — the finder is always evicted so the next search re-enters — while a 30s cooldown
85
+ throttles the npm spawn itself, so repeated searches during an outage don't respawn npm each time.
86
+ An explicit `--offline`/`PI_OFFLINE` directive and unsupported platforms remain a stable no-retry
87
+ fallback. The last install outcome is recorded (`getLastFffInstallOutcome`) for a future
88
+ environment `doctor` check.
89
+ - The user's prompt now appears on screen immediately when submitted, instead of appearing to hang
90
+ until the model router's routing judge finishes. The judge is a bounded LLM completion (seconds),
91
+ and the turn previously built and emitted the user message only *after* awaiting it, so the prompt
92
+ looked frozen right after Enter. `_promptUnserialized` now builds the user message and paints it (a
93
+ synthetic `message_start`) before the judge await, then reuses that same object for the turn; the
94
+ authoritative `message_start` emitted later is suppressed exactly once (reference-keyed via a Set),
95
+ so the message is still shown and persisted a single time, and extensions still observe it at the
96
+ real turn start (their visible behavior is unchanged).
97
+ - While the model router's routing/prep phase runs — the gap after the prompt paints and before the
98
+ turn starts streaming, during which the routing judge makes a bounded LLM call — the interactive UI
99
+ now shows the existing "working" indicator as general processing feedback, so that gap no longer
100
+ looks frozen. It shows independently of the thinking level (this is routing feedback, not
101
+ model-thinking) and respects the user's working-indicator toggle. New UI-only `routing_start` /
102
+ `routing_end` session events bracket the phase and are emitted exactly once each — `routing_end`
103
+ fires on both the success and failure paths, so the indicator never spins on after a turn that
104
+ fails before it starts — and it hands off seamlessly into the normal streaming indicator.
105
+ - Hardened the hardening sweep after a max-effort review of the working diff found nine regressions
106
+ and latent holes the first pass introduced or left open:
107
+ - `endWriteStream` (newly awaited by the bash executor) hung forever when the stream had already
108
+ errored or closed before the call — an errored stream never emits `finish`, so the `once`
109
+ listeners attached after the events fired and never resolved, hanging the bash tool on any spill
110
+ write error. It now resolves immediately for an already-finished/destroyed/closed/errored stream.
111
+ - The git-filter spill branch created its temp-file stream with no error handler, so a failed spill
112
+ write still advertised a "Full output:" path pointing at a partial or missing file. It now drops
113
+ the path on write error, matching the main capture path.
114
+ - The execution-time envelope path check resolved a target through `existsSync`, which reports
115
+ false for a DANGLING symlink — so a dangling symlink placed inside an allowed root resolved
116
+ lexically and escaped the envelope (the subsequent write follows the link). `safeRealpathSync`
117
+ now dereferences a dangling leaf via `lstat`/`readlink` with a hop cap, closing the escape.
118
+ - `applyWorkerActions` routed writes through an injectable fs seam while the scope check used the
119
+ real filesystem, so the two could disagree. The test-only seam (its sole injector) is removed;
120
+ scope check and write now share one filesystem of record.
121
+ - The context-GC auto-digest was fenced at RENDER time, regenerating a random boundary nonce on
122
+ every provider request (the GC transform re-runs per request) — a byte-unstable prompt prefix
123
+ that broke prefix caching, and double-wrapped/mangled an already-fenced digest. Fencing now
124
+ happens once at digest store time; GC renders it verbatim.
125
+ - A reflection write the gate marked "apply" recorded an `action:"apply"` audit with a rollback
126
+ plan even when the memory tool silently refused it (budget/drift/threat return `success:false`
127
+ without throwing) — a phantom rollback target that then failed not-found. A refused apply now
128
+ records an `apply_failed` audit with no rollback plan and is not rollback-eligible.
129
+ - The `/context` "withheld by the active resource profile" line fired even with NO active profile,
130
+ blaming a nonexistent profile for a plain user disable (and double-reporting alongside the
131
+ disable-wins warning). The count is now profile-only and empty when no profile is active.
132
+ - A durable `memory_replace`/`memory_remove` could never auto-apply even under a fully permissive
133
+ policy, because the contradiction branch short-circuited before every eligibility check — an
134
+ append-only incentive that degraded memory hygiene. A new `learningPolicy.autoApplySupersessions`
135
+ (default false, TUI-toggleable) lets a supersession fall through to the normal eligibility bars.
136
+ - Router-swapped turns restored the pre-turn tools/system-prompt unconditionally in `finally`,
137
+ silently clobbering a mid-turn extension change (e.g. `setActiveToolsByName`) that legitimately
138
+ replaced them. Restore now happens only when the live values are still exactly what the swap
139
+ assigned.
140
+ - The editor grant-widening guard was applied only to allow-framing: the block-framing all-enabled
141
+ branch still widened a closed grant to `{allow:["*"]}` for a kind the profile omitted, and the
142
+ editor let the literal `"*"` wildcard marker enter the working set as a fake resource id (so
143
+ unchecking one item under a `{allow:["*"]}` grant persisted `{allow:[…,"*"]}`, and the disabled
144
+ item kept loading). Both paths now gate on the same original-wildcard/grant-all check, and the
145
+ marker never enters the item set.
146
+ - Fixed `executeBash` returning a full-output temp-file path before the file's write was flushed:
147
+ the artifact write went through an async stream that was `.end()`-ed but never awaited, so a caller
148
+ reading the path immediately could see partial or empty content (a load-dependent flake). The stream
149
+ flush is now awaited across the normal, git-filter, cancel, and error paths, so the returned path
150
+ always points at a complete file.
151
+ - Fixed the context-GC auto-digest (a machine paraphrase of possibly attacker-influenced tool output)
152
+ being inlined into the prompt as bare prose: it is now wrapped in the standard untrusted-content
153
+ fence, the same boundary memory recall pages use, so an injection payload in the digest is framed as
154
+ data, not instructions.
155
+ - Fixed profile-denied context files (AGENTS.md/CLAUDE.md/GEMINI.md) being read and processed as
156
+ content before the agents-kind profile filter: the loader now reads each candidate file's raw bytes
157
+ once purely to discover any embedded `<resource-profile>` blocks (unavoidable — a profile can be
158
+ defined in a file it then denies), and only sanitizes, threat-scans, and exposes files the profile
159
+ allows. A denied file's instructional content is never loaded into the session, and the redundant
160
+ double read of every context file is gone.
161
+ - Fixed the profile resource editor silently widening a closed grant to a wildcard: when every id in a
162
+ kind's (possibly collapsed) universe was enabled, a no-change save re-encoded an enumerated grant
163
+ like `{allow:[alpha,beta]}` as `{allow:["*"]}`, auto-granting every future resource of that kind. The
164
+ save now preserves the enumerated list and only keeps `["*"]` when the grant was already a wildcard
165
+ (or grant-all was chosen explicitly).
166
+ - Fixed skills, prompts, and extensions denied by the active resource profile being silently absent
167
+ from `/context`: it now reports "N skill(s)/prompt(s)/extension(s) withheld by the active resource
168
+ profile — grant the <kind> kind to restore them", the same visibility the withheld-AGENTS.md
169
+ warning already gave context files.
170
+ - Fixed a silently-swallowed executor miss: when an executor-routed turn ran no toolkit command and
171
+ the reflex brain could not refine the request into an explicit instruction, the turn ended with no
172
+ retry and no explanation. It now surfaces a warning that the command did not run and no automatic
173
+ escalation happened (the no-frontier-fallback policy is unchanged).
174
+ - Fixed router-swapped turns (G4) leaving the system prompt at the full tool surface: when a turn is
175
+ routed to a smaller model its tool set is filtered, but the system prompt still carried guidelines
176
+ and schemas for tools the routed model couldn't call — billed every request and confusing to a
177
+ cheap/local model. The prompt is now rebuilt for the routed model's filtered surface and restored
178
+ afterwards, including when the routed run throws.
179
+ - Fixed the learning gate's contradiction branch being unreachable dead code: the reflection call site
180
+ hardcoded `contradictions: 0`, so a durable `memory_replace`/`memory_remove` — which overwrites or
181
+ deletes an existing memory fact (the reflection engine's confront-before-write conflict signal) —
182
+ could auto-apply and silently destroy prior knowledge. Such supersessions now route through the
183
+ contradiction branch (approval-gated proposal, audited); purely additive writes are unaffected.
184
+ - Fixed a learning-policy cliff where enabling the policy with stock settings silently disabled
185
+ learning entirely: a below-confidence-threshold cue with no evidence returned a silent no-op
186
+ (reflectionSourceConfidence 50 < confidenceThreshold 90, reflection writes carry no evidence), so
187
+ every durable write was dropped with no apply and no audit trail. Below-threshold cues now degrade
188
+ to an approval-gated proposal that is audited and visible in `/autonomy diagnostics`, while staying
189
+ fail-closed — nothing auto-applies below the threshold.
190
+
191
+ ## [0.80.100] - 2026-07-02
192
+
193
+ ### Added
194
+
195
+ - Added `/models suggest`: a curated roster of local models validated during pi's own small-model
196
+ research, each with the role it was shaped for — qwen3:1.7b as the toolkit executor/reflex
197
+ muscle, qwen3:0.6b as a minimal fast executor, and the Ternary-Bonsai family (1.7B search scout,
198
+ 4B context-curator/reflex-brain, 8B routing-judge for larger machines) marked as non-tool-calling
199
+ lane/brain models. Each suggestion is a ready `/models add` ref; the roster is surfaced when no
200
+ local models are installed. No per-model score is baked in — fitness is host-dependent, so
201
+ `/models add` still auto-probes on the actual machine and the roster only encodes each model's
202
+ validated role.
203
+
1
204
  ## [0.80.99] - 2026-07-02
2
205
 
3
206
  ### Added
@@ -29,6 +29,7 @@ import { type CustomMessage } from "./messages.ts";
29
29
  import { type ModelCapabilityProfile } from "./model-capability.ts";
30
30
  import type { ModelRegistry } from "./model-registry.ts";
31
31
  import { type StoredFitnessReport } from "./models/fitness-store.ts";
32
+ import { type LocalRuntimeDeps, OllamaRuntime } from "./models/local-runtime.ts";
32
33
  import { type PromptTemplate } from "./prompt-templates.ts";
33
34
  import { type ModelFitnessReport } from "./research/model-fitness.ts";
34
35
  import { type ResearchRunResult } from "./research/research-runner.ts";
@@ -91,6 +92,10 @@ export type AgentSessionEvent = Exclude<AgentEvent, {
91
92
  success: boolean;
92
93
  attempt: number;
93
94
  finalError?: string;
95
+ } | {
96
+ type: "routing_start";
97
+ } | {
98
+ type: "routing_end";
94
99
  };
95
100
  /** Listener function for agent session events */
96
101
  export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
@@ -148,6 +153,12 @@ export interface AgentSessionConfig {
148
153
  * defaults to the real, best-effort collector.
149
154
  */
150
155
  collectWorkspaceSources?: typeof collectWorkspaceSources;
156
+ /**
157
+ * Injected fetch/spawn/exists for the local (Ollama) runtime health-check + boot used by the
158
+ * model router before a turn routed to a local model (see _ensureLocalModelReady). Unit tests
159
+ * inject fakes so they never hit a real network/process; production defaults to the real ones.
160
+ */
161
+ localRuntimeDeps?: LocalRuntimeDeps;
151
162
  }
152
163
  export interface ExtensionBindings {
153
164
  uiContext?: ExtensionUIContext;
@@ -345,6 +356,8 @@ export declare class AgentSession {
345
356
  private readonly _brainCurator;
346
357
  private _lastCurationSkipReason;
347
358
  private _inertExtensionWarnings;
359
+ /** Extensions the active resource profile removed from the runtime set (surfaced in /context). */
360
+ private _profileDeniedExtensionCount;
348
361
  private _lastPreDigestSkipReason;
349
362
  private _unboundToolGrantWarnings;
350
363
  private _toolArtifactStore;
@@ -370,6 +383,12 @@ export declare class AgentSession {
370
383
  private _cwd;
371
384
  private _agentDir;
372
385
  private _collectWorkspaceSources;
386
+ private _localRuntimeDeps?;
387
+ /** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */
388
+ private _localRuntimes;
389
+ /** Server URLs confirmed reachable THIS session — skips the health-check round trip on every
390
+ * local-routed turn once warm. Keyed the same way as _localRuntimes. */
391
+ private _localRuntimeConfirmedUp;
373
392
  private _extensionRunnerRef?;
374
393
  private _initialActiveToolNames?;
375
394
  private _allowedToolNames?;
@@ -673,6 +692,16 @@ export declare class AgentSession {
673
692
  private _emit;
674
693
  private _emitQueueUpdate;
675
694
  private _lastAssistantMessage;
695
+ /**
696
+ * User messages already painted to the UI by an early, synthetic `message_start` fired from
697
+ * `_promptUnserialized` — before the model-router judge's bounded LLM call — so the prompt
698
+ * appears immediately instead of hanging until routing finishes. The real agent-loop run emits
699
+ * its own authoritative `message_start` for the SAME message object once the turn actually
700
+ * starts; `_handleAgentEvent` consumes (deletes) it from this set to suppress that one duplicate
701
+ * listener notification. Persistence is untouched: it stays keyed off `message_end`, which is
702
+ * never added here and never suppressed.
703
+ */
704
+ private _earlyDisplayedUserMessages;
676
705
  /** Internal handler for agent events - shared by subscribe and reconnect */
677
706
  private _handleAgentEvent;
678
707
  private _willRetryAfterAgentEnd;
@@ -797,13 +826,41 @@ export declare class AgentSession {
797
826
  private _buildSituationSoulPrompt;
798
827
  private _buildSelfModificationPrompt;
799
828
  private _buildAutonomyPrompt;
829
+ private _buildSystemPromptOptionsForToolNames;
800
830
  private _rebuildSystemPrompt;
831
+ /**
832
+ * Build a system prompt for a specific tool surface WITHOUT touching the session's base prompt
833
+ * state. Used for a router-swapped turn (G4): the routed model runs against a filtered tool set,
834
+ * so it must also receive a system prompt whose tool guidelines/snippets match that filtered
835
+ * surface — but the change is per-turn, so it must not mutate `_baseSystemPromptOptions` (which
836
+ * later turns and extension events read).
837
+ */
838
+ private _buildSystemPromptForToolNames;
801
839
  private _runAgentPrompt;
802
840
  private _isModelAvailableAndAuthed;
803
841
  private _resolveExecutorRoute;
804
842
  /** True if a run_toolkit_script tool result since `fromIndex` actually EXECUTED (not error/ambiguous). */
805
843
  private _executorTurnExecutedScript;
806
844
  private _buildExecutorRefinedPrompt;
845
+ /**
846
+ * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every
847
+ * caller — the router's readiness gate below and any host UI's own model-lifecycle commands
848
+ * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its
849
+ * own untracked child.
850
+ */
851
+ getLocalRuntime(baseUrl?: string): OllamaRuntime;
852
+ /** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's
853
+ * own health/boot endpoints are on the Ollama-native server root. */
854
+ private _deriveOllamaServerUrl;
855
+ /**
856
+ * If the last assistant message in this session was an error from THIS exact local server, a
857
+ * cached "confirmed up" flag would be stale (the server may have died mid-session) — drop it so
858
+ * the next ensure-check is a real one instead of trusting stale state.
859
+ */
860
+ private _invalidateLocalRuntimeIfLastCallFailed;
861
+ private _ensureLocalModelReady;
862
+ private _maybeInstallOllamaOnConsent;
863
+ private _ensureRouteModelReady;
807
864
  private _resolveModelRouterTurnRoute;
808
865
  private _resolveModelRouterModelForIntent;
809
866
  private _resolveConfiguredTierModel;
@@ -1021,6 +1078,21 @@ export declare class AgentSession {
1021
1078
  private _isToolOrCommandAllowedByProfile;
1022
1079
  private _hasToolOrCommandProfileGate;
1023
1080
  private _filterExtensionsForRuntime;
1081
+ /**
1082
+ * /context observations for skills/prompts/extensions the active resource profile removed from
1083
+ * listings — the analog of the withheld-AGENTS.md warning. Strict UAC makes these silently absent,
1084
+ * so a lean profile's effect on the resource surface stays visible. Counts are profile-scoped
1085
+ * (skills/prompts via the profile-independent discovery universe filtered by the live profile
1086
+ * filter; extensions via the runtime filter's denied tally). Empty when nothing is withheld.
1087
+ *
1088
+ * Uses `isResourceDeniedByActiveProfile` (profile-only), not `isResourceAllowedByProfile` (which
1089
+ * also folds in the user's own legacy `disabledResources` list): a plain user-disabled resource
1090
+ * must never be misattributed to "the active resource profile" — that case is already surfaced by
1091
+ * the G14 disable-wins warning. With no active profile at all, the helper always reports nothing
1092
+ * denied, so this naturally stays silent (extensions keep their own runtime-filter-derived count,
1093
+ * which is already correctly zero absent a profile).
1094
+ */
1095
+ private _profileDeniedResourceObservations;
1024
1096
  private _reapplyActiveProfileModelSettings;
1025
1097
  private _initializeMemory;
1026
1098
  /** Register a memory provider contributed by an extension; applied on the next memory (re)init. */