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.
- package/dist/core/DynamicMcpManager.d.ts +105 -0
- package/dist/core/DynamicMcpManager.d.ts.map +1 -0
- package/dist/core/DynamicMcpManager.js +119 -0
- package/dist/core/DynamicMcpManager.js.map +1 -0
- package/dist/core/DynamicMcpService.d.ts +60 -0
- package/dist/core/DynamicMcpService.d.ts.map +1 -0
- package/dist/core/DynamicMcpService.js +76 -0
- package/dist/core/DynamicMcpService.js.map +1 -0
- package/dist/core/McpApprovalNonceStore.d.ts +46 -0
- package/dist/core/McpApprovalNonceStore.d.ts.map +1 -0
- package/dist/core/McpApprovalNonceStore.js +83 -0
- package/dist/core/McpApprovalNonceStore.js.map +1 -0
- package/dist/core/McpLoadedSetStore.d.ts +39 -0
- package/dist/core/McpLoadedSetStore.d.ts.map +1 -0
- package/dist/core/McpLoadedSetStore.js +82 -0
- package/dist/core/McpLoadedSetStore.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +19 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +20 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +89 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/dynamicMcpConfig.d.ts +141 -0
- package/dist/core/dynamicMcpConfig.d.ts.map +1 -0
- package/dist/core/dynamicMcpConfig.js +125 -0
- package/dist/core/dynamicMcpConfig.js.map +1 -0
- package/dist/core/frameworkSessionLaunch.d.ts +11 -0
- package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
- package/dist/core/frameworkSessionLaunch.js +7 -0
- package/dist/core/frameworkSessionLaunch.js.map +1 -1
- package/dist/core/mcpPidCapture.d.ts +43 -0
- package/dist/core/mcpPidCapture.d.ts.map +1 -0
- package/dist/core/mcpPidCapture.js +44 -0
- package/dist/core/mcpPidCapture.js.map +1 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/McpIdleOffloadSweep.d.ts +61 -0
- package/dist/monitoring/McpIdleOffloadSweep.d.ts.map +1 -0
- package/dist/monitoring/McpIdleOffloadSweep.js +89 -0
- package/dist/monitoring/McpIdleOffloadSweep.js.map +1 -0
- package/dist/monitoring/mcpIdleLiveOffload.d.ts +68 -0
- package/dist/monitoring/mcpIdleLiveOffload.d.ts.map +1 -0
- package/dist/monitoring/mcpIdleLiveOffload.js +59 -0
- package/dist/monitoring/mcpIdleLiveOffload.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +165 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +4 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +51 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +2 -1
- package/upgrades/1.3.684.md +62 -0
- package/upgrades/side-effects/dynamic-mcp-awareness.md +36 -0
- package/upgrades/side-effects/dynamic-mcp-baseline-at-spawn.md +74 -0
- package/upgrades/side-effects/dynamic-mcp-capability-classification.md +36 -0
- package/upgrades/side-effects/dynamic-mcp-driver.md +53 -0
- package/upgrades/side-effects/dynamic-mcp-idle-sweep.md +40 -0
- package/upgrades/side-effects/dynamic-mcp-launch-resolver.md +44 -0
- package/upgrades/side-effects/dynamic-mcp-loaded-set-store.md +37 -0
- package/upgrades/side-effects/dynamic-mcp-nonce-store.md +38 -0
- package/upgrades/side-effects/dynamic-mcp-pure-cores.md +53 -0
- package/upgrades/side-effects/dynamic-mcp-real-offload.md +57 -0
- package/upgrades/side-effects/dynamic-mcp-routes-wiring.md +59 -0
- package/upgrades/side-effects/dynamic-mcp-service.md +41 -0
- 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).
|