instar 1.2.45 → 1.2.47

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.
@@ -0,0 +1,99 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+ <!-- patch = bug fix, no new capability, no breaking change -->
5
+
6
+ ## What Changed
7
+
8
+ **fix(framework): Telegram spawn path now honors the agent's configured framework (codex-cli agents stop spawning Claude sessions).**
9
+
10
+ A follow-on to the framework-spawn-portability fix (v1.2.31). That fix
11
+ made the fresh-spawn path (`spawnInteractiveSession`'s internal
12
+ resolution) read the agent's resolved `config.sessions.framework`. But
13
+ there are TWO paths that pick a framework, and the **Telegram message
14
+ path** was missed.
15
+
16
+ When a Telegram message arrives, `spawnSessionForTopic` resolves the
17
+ framework via `resolveTopicFramework(topicId)`, which returns the
18
+ module-level `_defaultFramework` for any topic without an explicit
19
+ per-topic override (i.e. every fresh topic). `_defaultFramework` was
20
+ initialized in `startServer` as:
21
+
22
+ ```ts
23
+ const framework = frameworkFromEnv() ?? 'claude-code';
24
+ ```
25
+
26
+ — sourced ONLY from the `INSTAR_FRAMEWORK` environment variable. The
27
+ setup wizard sets `enabledFrameworks: ['codex-cli']` in config, NOT the
28
+ env var, so for a codex-cli-only agent that didn't export
29
+ `INSTAR_FRAMEWORK` (the common case), `frameworkFromEnv()` returned
30
+ undefined and `_defaultFramework` fell back to `'claude-code'`. Worse,
31
+ `spawnSessionForTopic` passes its resolved framework as the per-call
32
+ `options.framework` to `spawnInteractiveSession` — and per-call wins
33
+ over `config.framework`. So even though v1.2.31 correctly resolved
34
+ `config.framework` to `codex-cli`, the Telegram path's wrong default
35
+ overrode it and spawned a Claude session on every message.
36
+
37
+ The fix: derive `_defaultFramework` from the agent's resolved runtime
38
+ framework first, falling back to the env var only when that's unset:
39
+
40
+ ```ts
41
+ const framework = config.sessions?.framework ?? frameworkFromEnv() ?? 'claude-code';
42
+ ```
43
+
44
+ `config.sessions.framework` is the single resolved source
45
+ (`resolveConfiguredFramework(sessions.framework, INSTAR_FRAMEWORK,
46
+ enabledFrameworks)` from `Config.load`), so both the fresh-spawn path
47
+ and the Telegram path now agree. A codex-cli-only agent spawns Codex
48
+ sessions whether the session is created directly via the API or
49
+ triggered by a Telegram message.
50
+
51
+ No migration needed — the fix is a server-startup derivation. Deployed
52
+ codex-cli agents get the correct behavior the moment they update and
53
+ restart cleanly. (Note: a clean restart IS required — an agent running
54
+ an older server process keeps the old `_defaultFramework` in memory
55
+ until restarted, even after its files update.)
56
+
57
+ ## Evidence
58
+
59
+ The original failure: a codex-cli-only agent ("codey") with
60
+ `enabledFrameworks: ['codex-cli']` and no `INSTAR_FRAMEWORK` env spawned
61
+ a Claude Code (Opus 4.7) session every time it answered a Telegram
62
+ message — including in brand-new topics. A direct `/sessions/create`
63
+ API spawn correctly produced Codex, which masked the bug (the API path
64
+ doesn't pass the Telegram default).
65
+
66
+ Regression coverage in `tests/unit/framework-spawn-portability.test.ts`
67
+ (2 new assertions):
68
+
69
+ 1. `server.ts` derives `_defaultFramework` from `config.sessions?.framework`
70
+ ahead of the `frameworkFromEnv()` fallback.
71
+ 2. The pre-fix env-only derivation (`const framework = frameworkFromEnv()
72
+ ?? 'claude-code';`) no longer appears in `server.ts`.
73
+
74
+ These join the existing 10 framework-spawn-portability tests (precedence
75
+ of `resolveConfiguredFramework`, the spawn-path source assertions, and
76
+ the `Config.load` wiring) — 12 tests total, all passing. `tsc --noEmit`
77
+ clean, lint clean.
78
+
79
+ Empirical confirmation against a live codex-cli agent (message it, watch
80
+ the spawned session run `codex --model gpt-5.3-codex` instead of
81
+ `claude`) is performed as part of the rollout, per the bug-fix evidence
82
+ bar — no "fixed" claim without reproducing the corrected behavior.
83
+
84
+ ## What to Tell Your User
85
+
86
+ If you set up an agent to run on Codex, it will now correctly run on
87
+ Codex when it answers your messages — not just when a session is
88
+ created some other way. Before this fix, a Codex-only agent could still
89
+ quietly start up on Claude whenever you messaged it, because the
90
+ message-handling path was reading the wrong setting. You don't need to
91
+ do anything beyond letting your agent update and restart; the Codex
92
+ choice you made at setup is now respected on every path.
93
+
94
+ ## Summary of New Capabilities
95
+
96
+ | Capability | How to Use |
97
+ |-----------|-----------|
98
+ | Telegram message path honors configured framework | Automatic — codex-cli agents spawn Codex on incoming messages |
99
+ | Single framework source of truth across both spawn paths | Automatic — derived from your install-time framework choice |
@@ -0,0 +1,36 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ PR 7 of the tunnel-failure-resilience chain. This is the security cleanup that makes the backup-tunnel feature safe to use: whenever a backup relay episode ends, the agent now rotates your dashboard PIN and access token.
8
+
9
+ **Why it matters.** While a backup relay is active, your dashboard traffic — including the PIN and any private view links — briefly passes through a third-party operator's machines. PIN rotation alone is not enough: the private view links are signed with your access token, so a copied link would keep working forever unless the token itself changes. This release rotates both. The moment the backup ends, the old PIN stops working and every previously-shared private view link becomes invalid.
10
+
11
+ **Where it triggers.** Every terminal exit from a backup relay episode rotates: an operator stop/shutdown while the relay was up, and — most importantly — boot-recovery. If the agent died mid-relay-episode, a flag persisted to disk causes rotation to run on the next boot **before the server accepts any API traffic**, so a relay operator who saw the old credentials can't use them against the freshly-started server.
12
+
13
+ **How it takes effect immediately.** The auth layer now reads the token live on every request instead of capturing it once at startup. So rotation invalidates the old token and old signed URLs instantly, with no restart. Existing behavior is unchanged when no rotation happens.
14
+
15
+ ## What to Tell Your User
16
+
17
+ - If a backup tunnel was ever used, you'll get a private message with a fresh dashboard PIN once it's no longer needed. Any open dashboard tab will ask you to sign in again with the new PIN.
18
+ - Any private view link you shared while the backup was active will stop working — that's deliberate, so the backup operator can't reuse it.
19
+ - If nothing ever falls back to a backup, you'll never see any of this; normal operation is unchanged.
20
+
21
+ ## Summary of New Capabilities
22
+
23
+ | Capability | How to Use |
24
+ |-----------|-----------|
25
+ | Credential rotation on relay-episode end | Automatic — fires on stop/shutdown and boot-recovery whenever a backup relay was in use; new PIN arrives in the owner DM |
26
+ | Live auth-token resolution | Automatic — the server now honors a rotated token immediately, no restart |
27
+
28
+ ## Evidence
29
+
30
+ - Spec: `specs/dev-infrastructure/tunnel-failure-resilience.md` Part 6. Side-effects: `upgrades/side-effects/tunnel-credential-rotation.md`.
31
+ - Tests: `tests/unit/auth-middleware-live-token.test.ts` (5) asserts both sides of the rotation boundary — the old bearer token AND an old HMAC-signed view URL are rejected the instant the live token changes, and a URL signed with the new token works. `tests/unit/tunnel-credential-rotation.test.ts` (6) asserts the lifecycle: relay-active→idle rotates, a non-relay stop does not, a thrown rotator leaves the pending flag set for retry, and a fresh manager restores the flag and rotates on boot-recovery.
32
+ - No regression: 75 auth + tunnel tests pass, including the existing string-form auth-middleware suites (the refactor is backward compatible).
33
+
34
+ ## Rollback
35
+
36
+ The middleware change is backward compatible (it still accepts a plain string). Revert = restore the string argument at the AgentServer auth callsite, drop the manager rotation methods and the `stop()` trigger, and remove the server.ts rotator closure + boot-recovery call. No config schema or persistent-state migration — a rotated PIN/token is just a new value.
@@ -0,0 +1,106 @@
1
+ # Side-effects review — Telegram default-framework fix (codex agents)
2
+
3
+ **Scope**: Follow-on to framework-spawn-portability (v1.2.31). The
4
+ original fix corrected the fresh-spawn path
5
+ (`spawnInteractiveSession`'s internal framework resolution) but missed
6
+ the Telegram message path (`spawnSessionForTopic` →
7
+ `resolveTopicFramework` → `_defaultFramework`). `_defaultFramework` was
8
+ sourced from `frameworkFromEnv()` only, so a codex-cli-only agent
9
+ without the `INSTAR_FRAMEWORK` env var defaulted to claude-code and
10
+ spawned Claude on every Telegram message — and because the Telegram
11
+ path passes its framework as the per-call `options.framework`
12
+ (highest precedence), it overrode the correct `config.framework`.
13
+ Governed by the existing converged + approved spec
14
+ `specs/dev-infrastructure/framework-spawn-portability.md`.
15
+
16
+ **Files touched**:
17
+ - `src/commands/server.ts` — one line in `startServer`:
18
+ `const framework = frameworkFromEnv() ?? 'claude-code';`
19
+ →
20
+ `const framework = config.sessions?.framework ?? frameworkFromEnv() ?? 'claude-code';`
21
+ plus an explanatory comment. `config.sessions.framework` is the
22
+ resolved runtime framework (`resolveConfiguredFramework` output from
23
+ `Config.load`, line 635), so the Telegram default now matches the
24
+ fresh-spawn path.
25
+ - `tests/unit/framework-spawn-portability.test.ts` — 2 new source-grep
26
+ assertions (derivation reads `config.sessions?.framework`; the
27
+ env-only derivation is gone).
28
+
29
+ **Under-block**: None. The change makes a previously-ignored config
30
+ field authoritative for the Telegram default. There is no path where a
31
+ framework that *should* spawn is now suppressed: per-topic overrides
32
+ (`_topicFrameworksStore` / `_topicFrameworks`) still win over the
33
+ default (they're checked first in `resolveTopicFramework`), and the
34
+ `INSTAR_FRAMEWORK` env still applies as a fallback when
35
+ `config.sessions.framework` is somehow unset.
36
+
37
+ **Over-block**: None. A claude-code agent (config.sessions.framework =
38
+ 'claude-code') still resolves to claude-code. A codex-cli agent now
39
+ correctly resolves to codex-cli instead of silently falling back to
40
+ claude-code. The only behavior change is the bug being fixed.
41
+
42
+ **Level-of-abstraction fit**: The Telegram default now reads the same
43
+ single resolved value (`config.sessions.framework`) that the
44
+ SessionManager's internal resolution reads. One source of truth, both
45
+ spawn paths. `resolveTopicFramework`'s precedence (per-topic store >
46
+ per-topic config map > default) is unchanged — only the *default's*
47
+ source is corrected.
48
+
49
+ **Signal vs authority**: Compliant. `config.sessions.framework`,
50
+ `INSTAR_FRAMEWORK`, and per-topic overrides are all SIGNALS;
51
+ `resolveConfiguredFramework` (in Config.load) is the single AUTHORITY
52
+ that resolves the agent-level framework, and `resolveTopicFramework`
53
+ layers per-topic overrides on top. No new authority introduced.
54
+
55
+ **Interactions**:
56
+ - `resolveTopicFramework` callers (the Telegram spawn path) now get the
57
+ correct default. The per-topic `/route` override path is unaffected
58
+ (it sets `_topicFrameworksStore`, checked before the default).
59
+ - `spawnSessionForTopic` passes `framework` as `options.framework` to
60
+ `spawnInteractiveSession`. With the fix, that value is now correct
61
+ for codex-cli agents, so the per-call override and the internal
62
+ `config.framework` resolution agree (no conflict).
63
+ - `resolvedFramework` (used a few lines later to pick the
64
+ IntelligenceProvider for relationships/shared intelligence) now also
65
+ reflects the configured framework — correct: a codex-cli agent's
66
+ shared intelligence provider should match its framework.
67
+
68
+ **External surfaces**: None. No new API endpoint, no new config field
69
+ (the `INSTAR_FRAMEWORK` env and `enabledFrameworks` config already
70
+ existed), no CLI change.
71
+
72
+ **Migration parity**: No agent-installed file change. The fix is a
73
+ server-startup derivation; deployed codex-cli agents get correct
74
+ behavior on their next update + clean restart. IMPORTANT operational
75
+ note (documented in NEXT.md): an agent running an older server process
76
+ keeps the stale `_defaultFramework` in memory until restarted, even
77
+ after its files update. This is the version-skew pattern — "updated"
78
+ ≠ "running the update". The codey incident that surfaced this bug was
79
+ partly this: the fix-bearing files were present but a long-running
80
+ process still served the old default.
81
+
82
+ **Rollback cost**: Trivial. Revert one line in server.ts + the 2 test
83
+ assertions.
84
+
85
+ **Tests**:
86
+ - 12/12 `framework-spawn-portability.test.ts` (10 existing + 2 new).
87
+ - `tsc --noEmit` clean. `npm run lint` clean.
88
+ - Empirical end-to-end confirmation (live codex-cli agent spawns
89
+ `codex --model gpt-5.3-codex` on a Telegram message, not `claude`)
90
+ is performed during rollout per the bug-fix evidence bar.
91
+
92
+ **Decision-point inventory**:
93
+ 1. Derive `_defaultFramework` from `config.sessions.framework` (vs.
94
+ adding yet another env read or a new config field) — reuses the
95
+ single resolved value the rest of the system already trusts. No new
96
+ knob.
97
+ 2. Keep `frameworkFromEnv()` + `'claude-code'` as fallbacks (vs.
98
+ removing them) — defense-in-depth: if `config.sessions.framework`
99
+ is ever unset (older Config.load, or a hand-edited config), the env
100
+ var and historical default still apply. The fix only re-orders
101
+ precedence to put the resolved config value first.
102
+ 3. Regression test via source-grep (vs. spinning up a full server) —
103
+ `_defaultFramework` is module-private state set inside the large
104
+ `startServer`; a source-grep assertion that the derivation reads
105
+ `config.sessions?.framework` is the proportionate guard, matching
106
+ the existing framework-spawn-portability spawn-path assertions.
@@ -0,0 +1,88 @@
1
+ # Side-effects review — credential rotation (PR 7 of tunnel-failure-resilience chain)
2
+
3
+ **Scope (PR 7 of the chain):** Make `authToken` + `dashboardPin`
4
+ rotation real on every terminal exit from `relay-active`, plus
5
+ boot-recovery. The `rotationPending` flag (set on relay-active entry in
6
+ PR 5, persisted to `tunnel.json`) is now consumed: rotating the
7
+ authToken invalidates every previously-signed view URL and the dashboard
8
+ session — the documented UX cost of having briefly routed private
9
+ traffic through a third-party relay. Spec Part 6.
10
+
11
+ **Files touched:**
12
+ - `src/server/middleware.ts` — `authMiddleware` accepts `string | (() =>
13
+ string | undefined)` and resolves the token **per request**, so a
14
+ runtime rotation takes effect immediately (no restart). Backward
15
+ compatible: the string form is byte-identical to before.
16
+ - `src/server/AgentServer.ts` — wires the live getter
17
+ `authMiddleware(() => this.config.authToken, …)`. `this.config` is set
18
+ in the constructor and never reassigned, so the getter always reads the
19
+ shared, mutable config object.
20
+ - `src/tunnel/TunnelManager.ts` — `setCredentialRotator(fn)`,
21
+ `runCredentialRotation(reason)` (gated on `rotationPending`,
22
+ clears-only-after-success), `recoverPendingRotation()` (boot path), and
23
+ a rotation trigger at the end of `stop()`.
24
+ - `src/commands/server.ts` — the rotator closure (regen PIN + authToken →
25
+ `liveConfig.set` + mutate the in-memory `config` → owner DM with the new
26
+ PIN), wired right after tunnel construction; boot-recovery awaited
27
+ **before** `server.start()`.
28
+ - Tests: `tests/unit/tunnel-credential-rotation.test.ts` (6),
29
+ `tests/unit/auth-middleware-live-token.test.ts` (5).
30
+
31
+ **Over-block:** None. The middleware resolves `string → itself`, so all
32
+ existing string callers and the 75 auth/tunnel tests are unchanged.
33
+
34
+ **Under-block:** The getter returning `undefined` means "open" (the
35
+ documented test/dev passthrough), same as the old `authToken === undefined`
36
+ behavior. In production `authToken` is always set (init generates it;
37
+ rotation always assigns a fresh non-empty UUID). The rotator does
38
+ `config.authToken = newToken` as a single synchronous assignment after
39
+ the `liveConfig.set` calls — there is no window where the live token is
40
+ `undefined`; a concurrent in-flight request reads either the old or the
41
+ new token, never empty. The instant the assignment lands, the old bearer
42
+ token and old signed URLs are rejected (asserted by the rotation tests).
43
+
44
+ **Level-of-abstraction fit:** The manager owns the WHEN (lifecycle
45
+ triggers + the `rotationPending` invariant); the injected closure owns
46
+ the WHAT (regenerate/persist/notify). The manager never imports config or
47
+ messaging. Mirrors the PR 6 `attachTelegram` seam.
48
+
49
+ **Signal vs authority:** `rotationPending` is the single-writer
50
+ authoritative marker owned by `TunnelLifecycle`. `runCredentialRotation`
51
+ clears it **only after** the rotator resolves; a thrown rotator leaves it
52
+ set so the next `stop()` or next boot retries (tested). No abstraction
53
+ treats a low-context signal as authority.
54
+
55
+ **Interactions:**
56
+ - Boot-recovery runs before `server.start()` (verified ordering:
57
+ rotator wired at tunnel-construction ~L4846 → boot-recovery at L7305 →
58
+ listen inside `server.start()`). The owner DM uses `sendToOwnerDM`
59
+ (private chat), so it does not depend on the Dashboard topic being
60
+ ensured yet.
61
+ - The startup `dashboardPin` auto-gen (`if (!config.dashboardPin)`) runs
62
+ after boot-recovery, so a boot-recovery rotation is not clobbered.
63
+ - `signViewPath` (routes.ts) and `verifyViewSignature` (middleware) both
64
+ read the live token, so post-rotation: new view URLs are signed with
65
+ the new token and old shared URLs fail verification — the intended
66
+ invalidation (tested).
67
+ - The 6-digit PIN keeps the existing `Math.random` generator (a
68
+ convenience factor, not the primary secret); `authToken` is
69
+ `randomUUID()` and is the HMAC key. No change to the secret model.
70
+
71
+ **Migration parity:** No agent-installed files change. The rotation is
72
+ runtime behavior in `server.ts` (every agent runs the new server after
73
+ update) and an internal middleware refactor; no new config field, no
74
+ CLAUDE.md/hook change (those land in PR 9). `rotationPending` already
75
+ existed in the persisted schema since PR 5.
76
+
77
+ **Tier-2/Tier-3 note:** PR 7 adds no HTTP route — per spec Part 8 the
78
+ HTTP-assertable "feature is alive" surface is the `/tunnel` route in
79
+ PR 9. PR 7's security guarantee is covered by the live-token middleware
80
+ unit tests (both sides of the boundary + view-URL invalidation) and the
81
+ manager rotation-lifecycle tests; the boot-recovery wiring is verified at
82
+ the `server.ts` call site.
83
+
84
+ **Rollback cost:** Low. The middleware change is backward compatible;
85
+ revert = restore the string arg at the AgentServer callsite, drop the
86
+ manager rotation methods + `stop()` trigger, and remove the server.ts
87
+ rotator closure + boot-recovery call. No schema or persistent-state
88
+ migration (rotated PIN/token are just new values).