instar 1.3.683 → 1.3.684

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 (78) hide show
  1. package/dist/core/DynamicMcpManager.d.ts +105 -0
  2. package/dist/core/DynamicMcpManager.d.ts.map +1 -0
  3. package/dist/core/DynamicMcpManager.js +119 -0
  4. package/dist/core/DynamicMcpManager.js.map +1 -0
  5. package/dist/core/DynamicMcpService.d.ts +60 -0
  6. package/dist/core/DynamicMcpService.d.ts.map +1 -0
  7. package/dist/core/DynamicMcpService.js +76 -0
  8. package/dist/core/DynamicMcpService.js.map +1 -0
  9. package/dist/core/McpApprovalNonceStore.d.ts +46 -0
  10. package/dist/core/McpApprovalNonceStore.d.ts.map +1 -0
  11. package/dist/core/McpApprovalNonceStore.js +83 -0
  12. package/dist/core/McpApprovalNonceStore.js.map +1 -0
  13. package/dist/core/McpLoadedSetStore.d.ts +39 -0
  14. package/dist/core/McpLoadedSetStore.d.ts.map +1 -0
  15. package/dist/core/McpLoadedSetStore.js +82 -0
  16. package/dist/core/McpLoadedSetStore.js.map +1 -0
  17. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  18. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  19. package/dist/core/PostUpdateMigrator.js +19 -0
  20. package/dist/core/PostUpdateMigrator.js.map +1 -1
  21. package/dist/core/SessionManager.d.ts +20 -0
  22. package/dist/core/SessionManager.d.ts.map +1 -1
  23. package/dist/core/SessionManager.js +89 -0
  24. package/dist/core/SessionManager.js.map +1 -1
  25. package/dist/core/dynamicMcpConfig.d.ts +141 -0
  26. package/dist/core/dynamicMcpConfig.d.ts.map +1 -0
  27. package/dist/core/dynamicMcpConfig.js +125 -0
  28. package/dist/core/dynamicMcpConfig.js.map +1 -0
  29. package/dist/core/frameworkSessionLaunch.d.ts +11 -0
  30. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  31. package/dist/core/frameworkSessionLaunch.js +7 -0
  32. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  33. package/dist/core/mcpPidCapture.d.ts +43 -0
  34. package/dist/core/mcpPidCapture.d.ts.map +1 -0
  35. package/dist/core/mcpPidCapture.js +44 -0
  36. package/dist/core/mcpPidCapture.js.map +1 -0
  37. package/dist/core/types.d.ts +10 -0
  38. package/dist/core/types.d.ts.map +1 -1
  39. package/dist/core/types.js.map +1 -1
  40. package/dist/monitoring/McpIdleOffloadSweep.d.ts +61 -0
  41. package/dist/monitoring/McpIdleOffloadSweep.d.ts.map +1 -0
  42. package/dist/monitoring/McpIdleOffloadSweep.js +89 -0
  43. package/dist/monitoring/McpIdleOffloadSweep.js.map +1 -0
  44. package/dist/monitoring/mcpIdleLiveOffload.d.ts +68 -0
  45. package/dist/monitoring/mcpIdleLiveOffload.d.ts.map +1 -0
  46. package/dist/monitoring/mcpIdleLiveOffload.js +59 -0
  47. package/dist/monitoring/mcpIdleLiveOffload.js.map +1 -0
  48. package/dist/scaffold/templates.d.ts.map +1 -1
  49. package/dist/scaffold/templates.js +2 -1
  50. package/dist/scaffold/templates.js.map +1 -1
  51. package/dist/server/AgentServer.d.ts +2 -0
  52. package/dist/server/AgentServer.d.ts.map +1 -1
  53. package/dist/server/AgentServer.js +165 -0
  54. package/dist/server/AgentServer.js.map +1 -1
  55. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  56. package/dist/server/CapabilityIndex.js +1 -0
  57. package/dist/server/CapabilityIndex.js.map +1 -1
  58. package/dist/server/routes.d.ts +4 -0
  59. package/dist/server/routes.d.ts.map +1 -1
  60. package/dist/server/routes.js +51 -0
  61. package/dist/server/routes.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/data/builtin-manifest.json +65 -65
  64. package/src/scaffold/templates.ts +2 -1
  65. package/upgrades/1.3.684.md +62 -0
  66. package/upgrades/side-effects/dynamic-mcp-awareness.md +36 -0
  67. package/upgrades/side-effects/dynamic-mcp-baseline-at-spawn.md +74 -0
  68. package/upgrades/side-effects/dynamic-mcp-capability-classification.md +36 -0
  69. package/upgrades/side-effects/dynamic-mcp-driver.md +53 -0
  70. package/upgrades/side-effects/dynamic-mcp-idle-sweep.md +40 -0
  71. package/upgrades/side-effects/dynamic-mcp-launch-resolver.md +44 -0
  72. package/upgrades/side-effects/dynamic-mcp-loaded-set-store.md +37 -0
  73. package/upgrades/side-effects/dynamic-mcp-nonce-store.md +38 -0
  74. package/upgrades/side-effects/dynamic-mcp-pure-cores.md +53 -0
  75. package/upgrades/side-effects/dynamic-mcp-real-offload.md +57 -0
  76. package/upgrades/side-effects/dynamic-mcp-routes-wiring.md +59 -0
  77. package/upgrades/side-effects/dynamic-mcp-service.md +41 -0
  78. package/upgrades/side-effects/dynamic-mcp-sweep-wiring.md +46 -0
@@ -0,0 +1,36 @@
1
+ # Side-effects review — dynamic-MCP agent awareness + migration
2
+
3
+ **Change:** A "Dynamic MCP Lifecycle" CLAUDE.md section (honestly experimental/dark),
4
+ delivered to new agents (generateClaudeMd) and existing agents (migrateClaudeMd) via
5
+ one shared `DYNAMIC_MCP_CLAUDEMD_SECTION(port)` helper. 3 unit tests.
6
+
7
+ ## 1. Blast radius
8
+ Documentation only. No runtime behavior, no gate, no route. New agents get the
9
+ section at init; existing agents get it appended once on the next update.
10
+
11
+ ## 2. Reversibility
12
+ Reversible — remove the helper + call sites + migration block. The migration is
13
+ add-if-missing; it never rewrites or deletes existing CLAUDE.md content.
14
+
15
+ ## 3. State / data touched
16
+ Appends one section to the agent's own CLAUDE.md on update (idempotent, content-
17
+ sniffed on the heading). Nothing else.
18
+
19
+ ## 4. Failure modes
20
+ The migration inherits migrateClaudeMd's guards (missing CLAUDE.md ⇒ skipped; read
21
+ error ⇒ recorded, no crash). Idempotent: a second run is a no-op (test-verified
22
+ byte-identical).
23
+
24
+ ## 5. Security / authority
25
+ None. The section TEXT states the Know-Your-Principal authorization rule (the agent
26
+ cannot self-approve), which is enforced in code elsewhere; this is awareness, not
27
+ enforcement.
28
+
29
+ ## 6. Framework generality
30
+ Documentation; the capability it describes is claude-code-scoped (stated in the
31
+ text). No framework-launch surface touched.
32
+
33
+ ## 7. Tests
34
+ 3 unit tests: the section lands on an existing agent (dark tag + /mcp/session + the
35
+ needs-approval rule); idempotent second run (byte-identical); generateClaudeMd
36
+ parity. Plus the existing migrator tests still green. tsc clean.
@@ -0,0 +1,74 @@
1
+ # Side-effects review — dynamic-MCP baseline-at-spawn wiring
2
+
3
+ **Change:** Wire the dynamic-MCP launch-set resolver into the real interactive
4
+ spawn path so a claude-code session can launch with a lean/explicit MCP subset
5
+ instead of the full `.mcp.json`. Three edits:
6
+ - `src/core/frameworkSessionLaunch.ts` — add `mcpFlags?: string[]` to
7
+ `InteractiveLaunchOptions`; `claudeCodeBuilder` pushes them into argv (right
8
+ before returning). Empty/absent ⇒ no flags ⇒ full `.mcp.json` (the default).
9
+ - `src/core/SessionManager.ts` — new fail-safe private `buildSessionMcpFlags(topicId,
10
+ framework)` (reads `.mcp.json` + the committed loaded-set state file,
11
+ `resolveSessionMcpServers` → `filterMcpConfig` → writes a unique filtered config,
12
+ seeds the committed state file from baseline when absent); wired into the
13
+ `buildInteractiveLaunch` call via a spread.
14
+ - `src/core/types.ts` — `SessionManagerConfig.dynamicMcp?` config field (type-only,
15
+ NOT in ConfigDefaults).
16
+
17
+ ## 1. Blast radius
18
+ **Zero while dark** (the default). `buildSessionMcpFlags` returns `[]` on the FIRST
19
+ line when `dynamicMcp.enabled !== true`, so no `mcpFlags` are passed, so
20
+ `claudeCodeBuilder` pushes nothing → the launch argv is byte-identical to today.
21
+ A dedicated test asserts this no-op. Behavior changes ONLY when an operator sets
22
+ `dynamicMcp.enabled:true` + a `keepWarm` trim.
23
+
24
+ ## 2. Reversibility
25
+ Fully reversible. Removing the field/flag/method returns launch to its prior
26
+ behavior. The filtered config + state files live under `.instar/state/` and are
27
+ inert once the feature is disabled (the resolver enabled-gates FIRST, so a
28
+ disabled feature ignores any state file — verified by the C3 test).
29
+
30
+ ## 3. State / data touched
31
+ Writes two best-effort files under `<projectDir>/.instar/state/` ONLY when enabled
32
+ + trimming: a per-topic committed loaded-set (`mcp-loaded/<topic>.json`, atomic
33
+ temp+rename) and a unique filtered `--mcp-config` (`session-mcp-config/mcp-<topic>-<ts>.json`).
34
+ Never mutates `.mcp.json` (read-only; `filterMcpConfig` returns a new object).
35
+
36
+ ## 4. Failure modes
37
+ Fail-safe to the FULL `.mcp.json` (`[]`) on EVERY error: unreadable/absent
38
+ `.mcp.json`, write failure, undefined topicId, any thrown error (outer try/catch).
39
+ A seed-write failure is swallowed and the launch still proceeds with the resolved
40
+ set. The worst case is "the full tool set", never "a session stranded without its
41
+ tools". State-file-unreadable-but-config-readable falls back to the LEAN baseline,
42
+ not full config (M6) — so a transient state error cannot relaunch every heavy
43
+ server warm and re-create the resource-panic condition.
44
+
45
+ ## 5. Security / authority
46
+ None exercised here. This is launch-time config selection only — no restart of a
47
+ live session, no operator-approval flow (those land in the driver/routes commits
48
+ with the server-minted-nonce gate). No authority is taken; the agent cannot self-
49
+ authorize anything via this change.
50
+
51
+ ## 6. Framework generality
52
+ The MCP `--mcp-config` / `--strict-mcp-config` mechanism is **Claude-Code
53
+ specific**. `buildSessionMcpFlags` returns `[]` for any framework other than
54
+ `claude-code` (explicit gate, tested), and `claudeCodeBuilder` is the only builder
55
+ that consumes `mcpFlags` — the **codex-cli**, **gemini-cli**, and **pi-cli**
56
+ builders ignore the option entirely. codex-cli configures MCP via `mcp_servers` in
57
+ its config.toml and gemini-cli differs again, so a per-framework MCP-trim is NOT
58
+ attempted for them here; this is a deliberate Claude-Code-scoped capability, not a
59
+ Claude-only assumption baked into a framework-general path. The `framework` is
60
+ resolved per-spawn (the topic's framework), so a codex session on a mixed-framework
61
+ agent is correctly excluded.
62
+
63
+ ## 7. Tests
64
+ 8 wiring tests (`session-manager-dynamic-mcp-flags.test.ts`) against a real temp
65
+ projectDir: dark ⇒ [] (no-op); non-claude-code ⇒ []; enabled+no-keepWarm ⇒ [];
66
+ enabled+lean ⇒ trimmed config + seeded committed state; committed state file wins;
67
+ un-committed state file ignored; unreadable `.mcp.json` ⇒ [] (fail-safe); undefined
68
+ topic ⇒ []. Plus the 26 + 10 pure-core tests. tsc clean across the project.
69
+
70
+ ## Scope note
71
+ Ships behind the EXPLICIT `dynamicMcp.enabled` flag (not the dev-agent gate) on
72
+ purpose: baseline-trim must NOT go live before the on-demand load/offload driver
73
+ exists, or a trimmed dev session could be stranded without Playwright and no way to
74
+ load it. The flip to the dev-agent gate is the final step once the full path lands.
@@ -0,0 +1,36 @@
1
+ # Side-effects review — dynamic-MCP capability classification + silent-fallback ratchet
2
+
3
+ **Change:** Two CI-guardrail fixes for the dynamic-MCP PR (#1293):
4
+ - `src/server/CapabilityIndex.ts`: add `{ prefix: 'mcp', reason: ... }` to
5
+ INTERNAL_PREFIXES — the `/mcp/*` routes are dark/experimental (503 when off), so they
6
+ are agent-invisible in /capabilities until matured (the honest classification; the
7
+ agent learns the feature via the CLAUDE.md awareness section).
8
+ - `tests/unit/no-silent-fallbacks.test.ts`: raise the ratchet baseline 476→488 with a
9
+ justification — the +12 are the feature's INTENTIONAL fail-safes (fail toward full
10
+ .mcp.json / abort, never a wrong action), documented in the spec + side-effects.
11
+
12
+ ## 1. Blast radius
13
+ Zero behavior change. INTERNAL_PREFIXES classification only affects whether /mcp shows
14
+ in the /capabilities discovery list (it doesn't — correct for a dark feature). The
15
+ ratchet baseline is a test threshold, not runtime code.
16
+
17
+ ## 2. Reversibility
18
+ Fully reversible — remove the prefix entry / restore the baseline number.
19
+
20
+ ## 3. State / data touched
21
+ None.
22
+
23
+ ## 4. Failure modes
24
+ None introduced. The classification is the deliberate "skip discovery" choice the lint
25
+ offers; the ratchet still prevents net regressions beyond 488.
26
+
27
+ ## 5. Security / authority
28
+ None. Hiding a dark feature from discovery is the conservative choice (does not surface
29
+ an unavailable capability as available).
30
+
31
+ ## 6. Framework generality
32
+ N/A (classification + a test threshold).
33
+
34
+ ## 7. Tests
35
+ 144 tests pass (capabilities-discoverability 139 + no-silent-fallbacks 5). tsc clean.
36
+ These were the only two failing tests across the #1293 CI shards (1/4 + 3/4).
@@ -0,0 +1,53 @@
1
+ # Side-effects review — dynamic-MCP load/offload driver
2
+
3
+ **Change:** New `src/core/DynamicMcpManager.ts` — the load/offload orchestration
4
+ for the dynamic-MCP lifecycle. All IO is INJECTED (state write, restart, pid
5
+ capture/reap, authorization, mid-tool-use probe); the class touches no
6
+ fs/process/network directly. 16 unit tests via fakes.
7
+
8
+ ## 1. Blast radius
9
+ Zero at runtime. The class has NO importer yet (verified by grep) — nothing
10
+ instantiates it. It becomes live only when the routes/sweep wire it with real
11
+ deps (a later commit, behind the dark flag).
12
+
13
+ ## 2. Reversibility
14
+ Fully reversible — deleting the file + tests removes it. No persisted state, no
15
+ migration, no config consumed.
16
+
17
+ ## 3. State / data touched
18
+ None directly. The class CALLS injected `writeLoadedSet` / `reapPids` / etc., but
19
+ in this commit those are only ever the test fakes.
20
+
21
+ ## 4. Failure modes
22
+ The orchestration is designed fail-safe: a non-`ok` restart rolls the committed
23
+ state back to the prior set (no phantom unapproved change); an offload whose
24
+ restart fails does NOT reap the captured pids (the old session is still alive); a
25
+ mid-tool-use `true` OR `null` aborts an offload (fail-closed); audit is best-effort
26
+ (a throwing audit sink never breaks a change). Authorization fails closed: an
27
+ unverified request returns needs-approval, never proceeds.
28
+
29
+ ## 5. Security / authority
30
+ This is the authority-bearing core, so the gate is built to the "Agent Proposes,
31
+ Operator Approves" + Know-Your-Principal standards: a change proceeds ONLY when
32
+ the topic is LIVE-preapproved (re-checked via the injected `isPreapproved`, not
33
+ trusted from the caller) OR an operator-supplied single-use nonce is consumed
34
+ successfully. An `agent`-actor request that is not preapproved can NEVER proceed —
35
+ it returns needs-approval with a server-minted nonce. The approval prompt text is
36
+ server-authored (a fixed template), never agent free-text. No authority is
37
+ exercised in this commit (nothing wires it live).
38
+
39
+ ## 6. Framework generality
40
+ Not applicable here — the manager is framework-neutral orchestration over injected
41
+ deps. The Claude-Code-specific surface (the actual `--mcp-config` restart) lives in
42
+ the deps' implementations, which the wiring commit will scope to claude-code (the
43
+ restart dep returns `unsupported-unbound`/`not_telegram_bound` for sessions the
44
+ restart mechanism doesn't support).
45
+
46
+ ## 7. Tests
47
+ 16 unit tests (`dynamic-mcp-manager.test.ts`): no-op short-circuits (already-loaded
48
+ / not-loaded / unknown-server, no restart); the auth gate both sides incl. valid vs
49
+ invalid nonce; two-phase write-then-commit + rollback on a failed restart;
50
+ not_telegram_bound ⇒ unsupported-unbound; offload capture→restart→reap ordering;
51
+ no-reap on a failed offload restart; mid-tool-use true/null abort; load skips the
52
+ mid-tool-use check; per-topic serialization (no two restarts in flight for one
53
+ topic). tsc clean.
@@ -0,0 +1,40 @@
1
+ # Side-effects review — dynamic-MCP idle-offload sweep (orchestration)
2
+
3
+ **Change:** New `src/monitoring/McpIdleOffloadSweep.ts` — the automatic
4
+ "offload a heavy MCP server once it's been idle a while" trigger. Stateful
5
+ orchestration over INJECTED deps (proc listing, session→topic, signature→server,
6
+ mid-tool-use, keep-warm, requestOffload). 9 unit tests.
7
+
8
+ ## 1. Blast radius
9
+ Zero at runtime. No importer yet — nothing ticks it until the background-interval
10
+ wiring lands (a thin follow-up). The whole feature is also behind the dark
11
+ `sessions.dynamicMcp.enabled` flag.
12
+
13
+ ## 2. Reversibility
14
+ Fully reversible — delete the file + tests. No persisted state (the idle clocks are
15
+ in-memory and pruned).
16
+
17
+ ## 3. State / data touched
18
+ None on disk. An in-memory Map of per-proc idle clocks, pruned each tick for procs
19
+ that vanished (cannot grow unbounded).
20
+
21
+ ## 4. Failure modes
22
+ Fail-closed by construction: a busy OR unknown(null) mid-tool-use resets the idle
23
+ clock (never ages an ambiguous session toward an offload); an unmapped server or a
24
+ non-topic-bound session is skipped; a thrown requestOffload never breaks the sweep.
25
+ It delegates the actual offload to the authorization-gated driver, so the same
26
+ "agent can't self-approve" + "abort if mid-tool-use" + capture-then-reap guarantees
27
+ apply — the sweep adds no new destructive path of its own.
28
+
29
+ ## 5. Security / authority
30
+ No new authority. An idle-offload it triggers goes through the SAME
31
+ authorization-gated requestOffload as an explicit request — on a non-preapproved
32
+ session it returns needs-approval (no silent restart).
33
+
34
+ ## 6. Framework generality
35
+ Framework-neutral orchestration; the mid-tool-use dep is framework-aware upstream.
36
+
37
+ ## 7. Tests
38
+ 9 unit tests: disabled no-op; below-window no-offload; offload once the window is
39
+ crossed; mid-tool-use reset (busy + unknown); keep-warm exclusion; dry-run logs-only;
40
+ non-topic-bound skip; clock pruning. tsc clean.
@@ -0,0 +1,44 @@
1
+ # Side-effects review — dynamic-MCP launch-set resolver
2
+
3
+ **Change:** Add one pure function `resolveSessionMcpServers` (+ its input type) to
4
+ `src/core/dynamicMcpConfig.ts`, with 8 unit tests. It encodes the load-bearing
5
+ launch-set resolution ORDER from the converged spec (DYNAMIC-MCP-LIFECYCLE-SPEC.md),
6
+ folding convergence findings C3 (enabled-gate first ⇒ clean rollback) and M6
7
+ (state-unreadable ⇒ lean baseline, not full config). No wiring — pure, computes a
8
+ list only.
9
+
10
+ ## 1. Blast radius
11
+ Zero at runtime. The function is not yet called by any live path; it is a pure
12
+ addition to an already-dark module. Behavior change lands only when the IO caller
13
+ (SessionManager.buildSessionMcpFlags) wires it in a later commit, behind a flag.
14
+
15
+ ## 2. Reversibility
16
+ Fully reversible — removing the function + tests returns the module to the prior
17
+ committed state (1fbf13bbb). No persisted state, no migration, no config consumed.
18
+
19
+ ## 3. State / data touched
20
+ None. Reads nothing, writes nothing. Returns a fresh array or null.
21
+
22
+ ## 4. Failure modes
23
+ None reachable here (no IO). The function is total over its typed inputs. Its
24
+ DESIGN intent is fail-safe: the caller passes `stateFileUnreadable:true` on a read
25
+ error and the resolver returns the lean baseline (M6), never full config; and the
26
+ enabled-gate returns null (full) first so a disabled feature is a clean no-op (C3).
27
+
28
+ ## 5. Security / authority
29
+ None. No authority exercised; the live-restart authority + the operator-approval
30
+ nonce flow land in the driver/routes commits with their own review.
31
+
32
+ ## 6. Framework generality
33
+ The resolver itself is framework-data-driven: it takes `framework` as input and
34
+ returns null (full .mcp.json) for any non-`claude-code` framework, because the MCP
35
+ `--mcp-config` mechanism is Claude-Code-specific (codex-cli uses `mcp_servers` in
36
+ config.toml; gemini differs). The eventual spawn caller will resolve THIS topic's
37
+ framework and pass it in. No Claude-only assumption is baked in beyond that
38
+ explicit, tested gate.
39
+
40
+ ## 7. Tests
41
+ 8 new unit tests (26 total in the file): disabled-with-committed-state ⇒ null (C3);
42
+ non-claude-code ⇒ null; committed state wins; empty committed ⇒ []; unreadable ⇒
43
+ lean baseline (M6); unreadable + no baseline ⇒ null; baseline present ⇒ baseline;
44
+ no state + no baseline ⇒ null. tsc clean.
@@ -0,0 +1,37 @@
1
+ # Side-effects review — dynamic-MCP loaded-set state store
2
+
3
+ **Change:** New `src/core/McpLoadedSetStore.ts` — durable per-topic "which MCP
4
+ servers is this session running with" state, with two-phase commit + atomic writes.
5
+ 7 unit tests.
6
+
7
+ ## 1. Blast radius
8
+ Zero at runtime. No importer yet — the spawn builder and driver wire it in a later
9
+ commit (behind the dark flag). `buildSessionMcpFlags` currently inlines an
10
+ equivalent read; the composition commit will route both through this store.
11
+
12
+ ## 2. Reversibility
13
+ Fully reversible — delete the file + tests. No migration; the on-disk JSON it would
14
+ write is inert when the feature is off.
15
+
16
+ ## 3. State / data touched
17
+ Writes `<dir>/<topicId>.json` ONLY when its `write` is called (by the driver/spawn
18
+ builder in a later commit). Atomic temp+rename. Holds no secrets — just server
19
+ NAMES + a committed flag + timestamp.
20
+
21
+ ## 4. Failure modes
22
+ `read`/`readCommitted` return null on absent/unreadable/torn files (never throw);
23
+ `exists` lets the caller distinguish absent from unreadable (drives the M6 fail-to-
24
+ lean decision). `write` throws on a genuine fs failure so the driver can roll back —
25
+ the driver is responsible for fail-safety. The two-phase contract (un-committed is
26
+ invisible to the reader) is the M1/M3 fix.
27
+
28
+ ## 5. Security / authority
29
+ None. No authority; just state IO of server names.
30
+
31
+ ## 6. Framework generality
32
+ Not applicable — framework-neutral state file; no launch/inject surface.
33
+
34
+ ## 7. Tests
35
+ 7 unit tests: absent ⇒ nulls; committed write readable; un-committed exists-but-
36
+ readCommitted-null (M1/M3); commit-after-in-flight; de-dup; torn file ⇒ read-null-
37
+ exists-true; no leftover .tmp (atomic). tsc clean.
@@ -0,0 +1,38 @@
1
+ # Side-effects review — dynamic-MCP approval nonce store
2
+
3
+ **Change:** New `src/core/McpApprovalNonceStore.ts` — the single-use, change-bound,
4
+ TTL approval nonce substrate for the dynamic-MCP authorization gate (fold C4). 8
5
+ unit tests.
6
+
7
+ ## 1. Blast radius
8
+ Zero at runtime. No importer yet — nothing mints/consumes from it until the
9
+ approval routes wire it (a later commit, behind the dark flag).
10
+
11
+ ## 2. Reversibility
12
+ Fully reversible — delete the file + tests. In-memory only; no persisted state, no
13
+ migration, no config.
14
+
15
+ ## 3. State / data touched
16
+ None on disk. An in-memory Map of (topicId|kind|server) → {nonce, expiresAt},
17
+ TTL-bounded, pruned on read. Holds no secrets.
18
+
19
+ ## 4. Failure modes
20
+ Fails CLOSED: consume returns false for a never-minted change, a wrong nonce, an
21
+ expired nonce, or a binding mismatch. A wrong attempt does NOT burn the real nonce
22
+ (the genuine operator can retry). Single-use on success.
23
+
24
+ ## 5. Security / authority
25
+ This IS the authority substrate. The nonce is the proof that an operator-
26
+ authenticated approval happened: it is server-minted, random (crypto.randomBytes),
27
+ bound to the exact (topicId, kind, server), single-use, and TTL-expiring. An agent
28
+ cannot forge, reuse, or cross-replay one — closing the "agent self-certifies
29
+ approved:true over the shared Bearer" hole (C4). No authority is exercised in this
30
+ commit (nothing wires it live).
31
+
32
+ ## 6. Framework generality
33
+ Not applicable — framework-neutral in-memory store; no launch/inject surface.
34
+
35
+ ## 7. Tests
36
+ 8 unit tests: consume-once; single-use (second fails); wrong value fails without
37
+ burning the real one; bound (cross server/kind/topic replay rejected); never-minted
38
+ fails; TTL expiry; re-mint replaces prior; size()/prune. tsc clean.
@@ -0,0 +1,53 @@
1
+ # Side-effects review — dynamic-MCP pure cores
2
+
3
+ **Change:** Two pure, dark-by-default decision modules for the on-demand MCP
4
+ lifecycle: `src/core/dynamicMcpConfig.ts` (baseline trim + load/offload mutation)
5
+ and `src/monitoring/mcpIdleLiveOffload.ts` (idle-offload eligibility). 28 unit
6
+ tests. No wiring — these compute answers only; nothing spawns, kills, or writes.
7
+
8
+ ## 1. Blast radius
9
+ Zero at runtime. Neither module is imported by any live code path in this commit;
10
+ they are pure functions with no side effects. The eventual wiring (next commit)
11
+ is where behavior changes, and it ships behind a disabled flag.
12
+
13
+ ## 2. Reversibility
14
+ Fully reversible — deleting the two files and their tests returns the tree to
15
+ its prior state. Nothing persisted, no migrations, no config consumed yet.
16
+
17
+ ## 3. State / data touched
18
+ None. No files written, no DB, no network. `mutateLoadedServers` and
19
+ `resolveBaselineServers` return fresh arrays; `filterMcpConfig` returns a new
20
+ object and never mutates its input (explicitly tested).
21
+
22
+ ## 4. Failure modes
23
+ Fail-closed by construction. `decideIdleLiveOffload` keeps the server on ANY
24
+ uncertainty: feature off, owner not live, light signature, keep-warm, mid-tool-use
25
+ true OR unknown (null), or idle clock not yet crossed. `mutateLoadedServers`
26
+ rejects loading an undefined server and no-ops a redundant load/offload. There is
27
+ no path that can drop a tool out from under a session in this commit (no actuation).
28
+
29
+ ## 5. Security / authority
30
+ None exercised here. The actuation layer (restart a live session) carries real
31
+ authority and lands in the wiring commit with the full Tier-2 spec ceremony —
32
+ including the now-decided authorization model: an autonomous/preapproved session
33
+ loads + restarts autonomously; a non-preapproved session must ASK the operator
34
+ ("ready for a quick restart?") before acting. That gate is NOT in this commit.
35
+
36
+ ## 6. Framework generality
37
+ Not applicable to these two pure modules (they touch no framework launch/inject
38
+ surface). NOTE for the wiring commit: injecting the lean baseline at spawn WILL
39
+ touch the Claude-code launch path, and MCP config is a Claude-Code-specific
40
+ mechanism (`--mcp-config`/`--strict-mcp-config`); codex-cli and gemini-cli handle
41
+ MCP differently. The wiring will scope the baseline-trim + restart to the
42
+ claude-code framework branch and state that reasoning in its own artifact.
43
+
44
+ ## 7. Tests
45
+ 28 unit tests covering both sides of every decision boundary (baseline
46
+ on/off/trim, load known/unknown/redundant, offload present/absent/to-empty,
47
+ idle-offload each gate both ways, fail-closed on unknown mid-tool-use). tsc clean.
48
+
49
+ ## Decision context
50
+ Supersedes the static per-topic profile model (#1292, held as draft) per Justin's
51
+ 2026-06-27 correction: MCP needs are not knowable at launch and must be mutable
52
+ mid-session via restart. Design now fully decided: genuinely-lean baseline +
53
+ load-on-demand gated by preapproval (autonomous = preapproved; else ask).
@@ -0,0 +1,57 @@
1
+ # Side-effects review — dynamic-MCP real offload primitives (capture-then-reap)
2
+
3
+ **Change:** Complete the offload path (elevated from follow-up to required by the
4
+ operator's full-lifecycle directive). Two edits:
5
+ - `src/core/mcpPidCapture.ts` (new, pure): `captureHeavyMcpPidsForSession` — given the
6
+ live MCP process scan + process tree + tmux pane map, returns the heavy MCP child
7
+ PIDs belonging to a specific session AND server. Conservative: unknown/non-heavy
8
+ server or unresolvable session ⇒ no pid.
9
+ - `src/server/AgentServer.ts`: replace the three conservative offload stubs in the
10
+ DynamicMcpService construction with REAL primitives — `captureHeavyPids` (reaper
11
+ ps-listing + the pure capture), `reapPids` (the reaper's `killProcess`), and
12
+ `isMidToolUse` (live pane frame + `looksActivelyWorking`).
13
+
14
+ ## 1. Blast radius
15
+ Dark by default (`sessions.dynamicMcp.enabled`); when off, the service is built but
16
+ its methods are never invoked (the routes 503). When ON, the offload path can now
17
+ actually drop a server + reap its orphaned heavy process. Service construction stays
18
+ fail-safe (a throw ⇒ null ⇒ routes 503), and the e2e boot is unaffected (verified).
19
+
20
+ ## 2. Reversibility
21
+ Reversible — revert the stubs. No persisted state beyond the existing loaded-set file.
22
+
23
+ ## 3. State / data touched
24
+ `captureHeavyPids` READS the process table (ps) + tmux pane map (read-only).
25
+ `reapPids` sends SIGTERM/SIGKILL to the captured PIDs (the reaper's audited
26
+ `killProcess`). No new files; the reaper audit path is reused.
27
+
28
+ ## 4. Failure modes — THE destructive-authority review
29
+ `reapPids` is the only destructive op. It is tightly bounded: it kills ONLY the PIDs
30
+ `captureHeavyPids` returned, which are ONLY procs that (a) match a HEAVY MCP signature
31
+ mapped from the offloaded server name, AND (b) resolve via the ppid-walk to the EXACT
32
+ target session. An unknown/non-heavy server captures nothing; an orphan or
33
+ wrong-session proc captures nothing — so the worst case is "reap nothing" (a missed
34
+ reclaim the generic reaper still backstops), never "kill the wrong process". The kill
35
+ only runs AFTER an authorized offload's restart is CONFIRMED (the old session is gone),
36
+ and the offload aborts entirely if the session is — or might be — mid-tool-use. Every
37
+ step is fail-safe (capture ⇒ [] on any error; reap ⇒ best-effort per-pid try/catch;
38
+ mid-tool-use ⇒ null ⇒ abort on any uncertainty).
39
+
40
+ ## 5. Security / authority
41
+ No new authority surface — the change only makes the already-gated offload actually
42
+ reclaim. The kill is gated behind: feature enabled + an authorized offload (live
43
+ preapproval or operator nonce) + a confirmed restart + not-mid-tool-use. The agent
44
+ cannot trigger an unauthorized kill.
45
+
46
+ ## 6. Framework generality
47
+ The MCP-server processes are framework-agnostic (a Chromium is a Chromium); the
48
+ mid-tool-use probe passes the session's framework to `looksActivelyWorking` (which is
49
+ framework-aware). The capture/reap is claude-code-relevant in practice (only
50
+ claude-code sessions get a trimmed/dynamic MCP set), but the pid-capture itself is not
51
+ Claude-specific.
52
+
53
+ ## 7. Tests
54
+ 7 unit tests for the pure capture (target-session-only, unknown/light server ⇒ none,
55
+ wrong-session ⇒ none, orphan ⇒ none, multiple pids, the conservative server→signature
56
+ map). The e2e AgentServer boot + the routes integration remain green with the new
57
+ wiring. tsc clean.
@@ -0,0 +1,59 @@
1
+ # Side-effects review — dynamic-MCP routes + AgentServer wiring (live-authority)
2
+
3
+ **Change:** Make the dynamic-MCP feature reachable. Three edits:
4
+ - `src/server/routes.ts` — `RouteContext.dynamicMcpService?` field + three routes:
5
+ `GET /mcp/session/:topicId`, `POST /mcp/load`, `POST /mcp/offload`. Bearer-gated
6
+ (global middleware); 503 when the service is absent/disabled (dark by default).
7
+ - `src/server/AgentServer.ts` — build `DynamicMcpService` with the host's REAL
8
+ primitives (restart→SessionRefresh via `getSessionForTopic`, isPreapproved→
9
+ `activeAutonomousJobs`) and thread it into `routeCtx`. Construction is fail-safe
10
+ (a throw ⇒ null ⇒ routes 503).
11
+ - Integration test (9 cases) over the real createRoutes pipeline.
12
+
13
+ ## 1. Blast radius
14
+ Reachable but DARK by default: every `/mcp/*` route 503s unless
15
+ `sessions.dynamicMcp.enabled === true` (explicit flag, not the dev-gate yet). The
16
+ service construction is wrapped in try/catch → null on any error, so a wiring fault
17
+ can NEVER break server boot — it just disables the feature. When dark, `routeCtx`
18
+ gains one extra (unused) field; nothing else changes.
19
+
20
+ ## 2. Reversibility
21
+ Fully reversible — remove the field/routes/construction. No migration; the on-disk
22
+ state the service would write only happens when an enabled change is requested.
23
+
24
+ ## 3. State / data touched
25
+ None unless enabled + a change is requested (then McpLoadedSetStore writes under
26
+ `.instar/state/mcp-loaded/`). Reads `.mcp.json`. Never mutates `.mcp.json`.
27
+
28
+ ## 4. Failure modes
29
+ Service construction fail-safe (→ null → 503). The restart primitive maps a null
30
+ SessionRefresh / unbound session to `not_telegram_bound` ⇒ `unsupported-unbound`
31
+ (no crash). isPreapproved fails CLOSED on any error. v1 OFFLOAD is conservatively
32
+ inert (isMidToolUse ⇒ null ⇒ the driver aborts every offload; captureHeavyPids ⇒ [])
33
+ so there is NO leak and NO bad restart until the paneTail probe + reaper pid-capture
34
+ land in a follow-up.
35
+
36
+ ## 5. Security / authority — THE load-bearing review
37
+ The agent-facing routes are Bearer-gated and ALWAYS act as `{kind:'agent'}` — they
38
+ NEVER honor a caller-supplied `nonce` from the request body. This is the C4 fix made
39
+ concrete: the agent RECEIVES the nonce in a needs-approval response, so if the route
40
+ honored a body nonce the agent could self-approve over the shared Bearer. An
41
+ integration test asserts that a body `nonce` does NOT authorize a not-preapproved
42
+ change. The only paths that complete a change are (a) a live-preapproved topic
43
+ (active autonomous run — the operator's own standing grant) or (b) — FOLLOW-UP — an
44
+ operator-authenticated approval route (dashboard PIN / sentinel-bound yes) that
45
+ consumes the server-minted nonce. Until that route lands, a non-preapproved change
46
+ returns needs-approval and performs no restart. No authority can be self-granted.
47
+
48
+ ## 6. Framework generality
49
+ The routes/service are framework-neutral; the Claude-Code-specific surface is the
50
+ restart primitive (SessionRefresh, which returns not_telegram_bound for sessions it
51
+ can't restart ⇒ unsupported-unbound). The baseline-trim that the restart re-applies
52
+ is already claude-code-scoped in buildSessionMcpFlags (prior commit). codex-cli /
53
+ gemini-cli sessions are not trimmed and a restart of them carries no mcpFlags.
54
+
55
+ ## 7. Tests
56
+ 9 integration tests (`dynamic-mcp-routes.test.ts`): 503 absent / 503 disabled / GET
57
+ state shape / GET 400 / POST load applied + restart / POST load needs-approval (202,
58
+ no restart) / C4 body-nonce-rejected / POST 400 missing server / offload conservative
59
+ 409 abort. Plus the ~105 unit tests for the underlying blocks. tsc clean.
@@ -0,0 +1,41 @@
1
+ # Side-effects review — dynamic-MCP composition service
2
+
3
+ **Change:** New `src/core/DynamicMcpService.ts` — the composition root that
4
+ assembles DynamicMcpManager + McpLoadedSetStore + McpApprovalNonceStore and wires
5
+ them to INJECTED host primitives (restart, preapproval, pid capture/reap,
6
+ mid-tool-use). Exposes getSessionState / requestLoad / requestOffload. 7 unit tests.
7
+
8
+ ## 1. Blast radius
9
+ Zero at runtime. No importer yet — AgentServer wires it with real primitives in the
10
+ final commit (behind the dark flag).
11
+
12
+ ## 2. Reversibility
13
+ Fully reversible — delete the file + tests. No migration; consumes the dynamicMcp
14
+ config block only when instantiated (which nothing does yet).
15
+
16
+ ## 3. State / data touched
17
+ Indirectly via McpLoadedSetStore (writes `.instar/state/mcp-loaded/<topic>.json`)
18
+ and reads `.mcp.json` — but ONLY when a caller invokes requestLoad/requestOffload,
19
+ which only the tests do in this commit. Never mutates `.mcp.json`.
20
+
21
+ ## 4. Failure modes
22
+ Inherits the manager's fail-safety (verified-auth, two-phase rollback, capture-then-
23
+ reap, mid-tool-use abort). `.mcp.json` unreadable ⇒ [] names ⇒ a load is
24
+ unknown-server no-op; currentServers falls back baseline→full safely.
25
+
26
+ ## 5. Security / authority
27
+ The authority gate lives in the injected `isPreapproved` + the nonce store: a
28
+ non-preapproved request returns needs-approval with a server-minted nonce and
29
+ performs no restart. The service never trusts a caller-supplied approval; only a
30
+ consumed nonce or a live preapproval proceeds.
31
+
32
+ ## 6. Framework generality
33
+ Framework-neutral composition. The Claude-Code-specific surface lives in the
34
+ primitives AgentServer injects (the restart maps SessionRefresh, which returns
35
+ not_telegram_bound ⇒ unsupported-unbound for sessions it can't restart).
36
+
37
+ ## 7. Tests
38
+ 7 unit tests vs a real temp projectDir: lean-baseline state read; full load+restart+
39
+ commit; not-preapproved ⇒ needs-approval then nonce authorizes; forged nonce
40
+ rejected; offload drops+reaps; offload aborts on unknown mid-tool-use; failed
41
+ restart leaves committed set unchanged (rollback). tsc clean.
@@ -0,0 +1,46 @@
1
+ # Side-effects review — dynamic-MCP idle-offload sweep wiring
2
+
3
+ **Change:** Wire McpIdleOffloadSweep into AgentServer on a timer so the automatic
4
+ idle-offload actually runs. Two edits:
5
+ - `src/server/AgentServer.ts`: build the sweep with real deps (heavy-live-proc
6
+ listing via the reaper deps + resolveOwningSession; session→topic via
7
+ getTopicForSession; signature→server via the reverse map; mid-tool-use via the
8
+ live pane frame; requestOffload via the gated driver) and tick it on an
9
+ `.unref()`'d interval; clear it in `stop()`.
10
+ - `src/core/dynamicMcpConfig.ts`: add the `sweep` config block type.
11
+
12
+ ## 1. Blast radius
13
+ Dark + dryRun-first. The sweep timer is created ONLY when BOTH
14
+ `sessions.dynamicMcp.enabled` AND `sessions.dynamicMcp.sweep.enabled` are true; and
15
+ `sweep.dryRun` defaults true, so even then it only LOGS "would offload". Construction
16
+ is wrapped (a fault ⇒ no sweep, never a broken boot). The e2e boot is unaffected
17
+ (verified — no sweep config ⇒ no timer).
18
+
19
+ ## 2. Reversibility
20
+ Fully reversible — remove the block + the config type. The timer is `.unref()`'d and
21
+ cleared on stop, so it never holds the event loop or leaks across a restart.
22
+
23
+ ## 3. State / data touched
24
+ Read-only at the host level (ps + tmux pane map + pane capture). When NOT dryRun and
25
+ a server is eligible, it calls the gated `requestOffload`, which is the SAME path an
26
+ explicit request uses (loaded-set state write + restart + capture-then-reap).
27
+
28
+ ## 4. Failure modes
29
+ Fail-closed throughout: a busy/unknown mid-tool-use resets the idle clock; an
30
+ unmapped server or non-topic session is skipped; a thrown tick is swallowed; the
31
+ timer never keeps the process alive. The sweep adds NO new destructive path — it only
32
+ DECIDES and delegates to the authorization-gated driver (which itself aborts on
33
+ mid-tool-use, requires authorization, and reaps only captured pids).
34
+
35
+ ## 5. Security / authority
36
+ No new authority. An idle-offload it triggers on a non-preapproved session returns
37
+ needs-approval (no silent restart) exactly like an explicit request. dryRun (default)
38
+ takes no action at all.
39
+
40
+ ## 6. Framework generality
41
+ The timer/sweep are framework-neutral; the mid-tool-use probe is framework-aware.
42
+
43
+ ## 7. Tests
44
+ The sweep orchestration has 9 unit tests (committed prior). This wiring is verified
45
+ by the e2e AgentServer boot remaining green (no sweep config ⇒ no timer ⇒ unchanged),
46
+ and tsc clean. A live run is part of the operator live-channel proof (the gate).