@inixiative/agent-session 0.12.0 → 0.12.1

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/README.md CHANGED
@@ -51,7 +51,8 @@ session.kill();
51
51
  blocks in the same user message, Codex `image` inputs with a `data:` URL. Anything else is
52
52
  refused before a byte is written.
53
53
  - **fork / resume / interrupt** and pre-send hooks (rewrite the outgoing
54
- message, for example to add context for that turn).
54
+ message, for example to add context for that turn). Resume is configuration, not a
55
+ method: build the session with `externalSessionId`.
55
56
  - **A resume whose native conversation is not found is typed.** Claude finding no transcript
56
57
  ("No conversation found with session ID": cleaned up, deleted, or under another project
57
58
  directory or config home) and Codex finding no rollout ("no rollout found for thread id") both
@@ -96,12 +97,13 @@ Codex runs only over `codex app-server --listen stdio://` (`CodexAppServerSessio
96
97
  Codex CLI 0.155 has no `mcp-server` subcommand.
97
98
 
98
99
  Muse Code runs over `muse serve` through Meta's `@muse-code/sdk` (`MuseSession`), pinned to
99
- the CLI's version (1.4.14): a host serving another version is refused at start. It bills the
100
+ the CLI's version (1.4.20): a host serving another version is refused at start. It bills the
100
101
  saved Muse login: metered keys and endpoints never reach it (nor Muse's own tool runs, which
101
102
  inherit its environment: a repo command needing `OPENAI_API_KEY` will not see it under Muse),
102
103
  and a resumed session must be on provider `meta`. Per-login isolation is `env: { XDG_CONFIG_HOME, XDG_DATA_HOME }` (login and
103
- sessions). `musePermissions(mode)` maps the host modes to an explicit permission profile, because
104
- the host default lets Muse's AI reviewer answer approvals instead of the gates. Questions arrive
104
+ sessions). The session's `permissionMode` (default `default`, gated) goes through `musePermissions(mode)`
105
+ to an explicit permission profile, because the host default lets Muse's AI reviewer answer
106
+ approvals instead of the gates. Questions arrive
105
107
  through `onUserInput()`; unanswered ones are cancelled with a reason. An approval offering
106
108
  no approve-once or reject choice is reported as an `error` event and left open (the turn ends
107
109
  at its timeout). Work Muse starts itself
@@ -233,9 +235,11 @@ const result = await decisions.decide(spec, { input, standby });
233
235
  main thread, which wakes the main model (Claude Code 2.1.300). Members of a
234
236
  dispatcher that ended unasked are therefore never retired or stopped, and an
235
237
  orphan's `answer` call is held for the session's life. Past `maxOrphans`
236
- (40) the host stops serving and its caller falls back.
238
+ (40) `serving()` turns false and `setup` reports `degraded`; `decide()` does
239
+ not refuse on its own, so the caller checks `serving()` and falls back.
237
240
  - **Limits.** Claude Code runs 20 subagents at once by default; the launch
238
- raises `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS` (`maxSubagents`, 96). The
241
+ raises `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS` to the user's own cap (20 when
242
+ unset) plus `maxSubagents` (76), so 96 by default. The
239
243
  session declares `perTaskStopAffordance`, so the user's Stop spares
240
244
  background agents.
241
245
 
@@ -251,9 +255,12 @@ const pool = new SubscriptionPool({ instances: [
251
255
  const { session, lease } = await pool.open({ runtime: "claude", model: "sonnet", organizationId: "org", preferredInstanceId: "work" });
252
256
  ```
253
257
 
254
- Routing is deterministic (quartile-balanced, owner-first or pinned) over
255
- observed limits, leases and health; stale or unknown limits exclude an instance
256
- and every exclusion has a reason. `continueOn` moves a thread only between
258
+ Routing is deterministic over observed limits, priority, leases and health, by a
259
+ named strategy (`quartile-balanced` by default, `owner-first`, `priority-balanced`,
260
+ `priority-strict`, `least-used`) or an explicit term list; `pinned` restricts it to
261
+ the preferred instance. By default stale or unknown limits exclude an instance
262
+ (`unknownUtilization: 'rank-last'` ranks it last instead), and every exclusion has a
263
+ reason. `continueOn` moves a thread only between
257
264
  instances that share native history, and says why when it cannot.
258
265
 
259
266
  ## Usage telemetry
@@ -271,6 +278,33 @@ model and tags) in `raw`; they may repeat across content blocks. Do not sum
271
278
  request snapshots together with result totals. Interrupted turns retain request
272
279
  snapshots in the artifact but do not fabricate a completed-turn total.
273
280
 
281
+ ## Secret scratch pad
282
+
283
+ A person can hand an agent a secret as `$$word`. `SecretScratchPad(dir)` lifts it out
284
+ of the message before the agent sees it (`lift`), into a private pad per thread
285
+ (`<thread>.pad`, 0600 in a 0700 directory), and leaves a reference in its place:
286
+ `†<message>.<n>` inside its thread, `†<thread>/<message>.<n>` from anywhere else.
287
+ `lift(text, at, { patterns })` also lifts matches of the caller's global regexes, for
288
+ tokens the person forgot to mark, and counts them as `recognised`.
289
+
290
+ The agent uses a value without reading it. `env` sets `KEY=value` in an env file,
291
+ `write` puts it in a file, and `pipe` runs a command with the value on its stdin,
292
+ from a reference or a key already in an env file; only the exit code comes back, and
293
+ a timeout kills the whole process group. `consume` (or `consume: true` on a use)
294
+ removes a block, `list` shows what is pending without values, `scrub` swaps held
295
+ values in text back to references, and `clear` deletes a pad.
296
+
297
+ Files `env` and `write` touched are recorded (`heldFiles()`). `privatePathGate`
298
+ (or `privatePathDenial` inside your own gate) denies a tool call naming one of them,
299
+ a `.private` path, a `privatePaths` glob or the pad directory; `isOwnTool` exempts the
300
+ scratch tool's own calls. A `privatePathGate` keeps the `heldFiles` list it was built
301
+ with: rebuild it after `env` or `write`, or call
302
+ `privatePathDenial(request, { scratchDir: pad.dir, heldFiles: pad.heldFiles() })` in your
303
+ own gate on each request. `SECRET_SCRATCH_GUIDANCE` is the text to give the agent.
304
+ The gate reads names, not contents, and a pipe command can still copy a value out:
305
+ it keeps honest agents from reading secrets, it is not a sandbox. Design and
306
+ limits: [tickets/AS-009](tickets/AS-009-secret-scratch-pad.md).
307
+
274
308
  ## Roadmap
275
309
 
276
310
  | Runtime | Status |
@@ -299,4 +333,4 @@ MIT
299
333
 
300
334
  ## Runtime
301
335
 
302
- Development and new continuation captures use Bun 1.4.2, pinned in `.bun-version` and `package.json`. Historical S0 recordings retain their original Bun 1.3.14 evidence; they are not new 1.4.2 captures.
336
+ Development and new continuation captures use Bun 1.4.4, pinned in `.bun-version` and `package.json`. Historical S0 recordings retain their original Bun 1.3.14 evidence; they are not new 1.4.4 captures.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/agent-session",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Drive coding-agent CLIs as persistent, streaming, event-captured sessions over interchangeable transports, with primed decision sessions, subscription pools, routing, limit polling and continuity.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -18,6 +18,7 @@
18
18
  "check": "bun run typecheck && bun run lint && bun run test",
19
19
  "prepublishOnly": "bun run check",
20
20
  "measure:primed": "bun scripts/measure-primed.ts",
21
+ "record:muse": "bun scripts/record-muse.ts",
21
22
  "lint": "biome check .",
22
23
  "prepare": "if git rev-parse --git-dir >/dev/null 2>&1; then lefthook install; fi"
23
24
  },
@@ -36,12 +37,12 @@
36
37
  ],
37
38
  "license": "MIT",
38
39
  "dependencies": {
39
- "@inixiative/json-rules": "^3.16.0",
40
- "@muse-code/sdk": "1.4.14"
40
+ "@inixiative/json-rules": "^3.22.0",
41
+ "@muse-code/sdk": "1.4.20"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@biomejs/biome": "2.5.2",
44
- "@inixiative/config": "^0.5.187",
45
+ "@inixiative/config": "^0.5.212",
45
46
  "@types/bun": "1.4.4",
46
47
  "lefthook": "2.1.9",
47
48
  "typescript": "6.0.3"
@@ -40,17 +40,18 @@ import { TurnState } from './turn-state';
40
40
  // ---------------------------------------------------------------------------
41
41
  // MuseSession — one `muse serve` host per session, driven through Meta's
42
42
  // `@muse-code/sdk` MuseClient (Muse Session Protocol). The facade opens the
43
- // session, submits turns, answers approvals and folds the view; the calls it
44
- // lacks (turn/interrupt, turn/steer, session/fork, session/setApprovalMode,
45
- // usage/read, userInput/cancel) go through the same connection's `command`.
43
+ // session, submits turns, answers approvals and folds the view. turn/interrupt,
44
+ // turn/steer, session/fork, session/setApprovalMode, usage/read and
45
+ // userInput/cancel go through the same connection's `command`, written before
46
+ // the facade had them (1.4.14) and kept on the raw wire, whose types 1.4.20 keeps.
46
47
  // Every view notification is classified into the shared SessionEvent taxonomy.
47
48
  // The Muse session id is the externalSessionId used for cold resume.
48
49
  //
49
- // Wire contract: Muse Code 1.4.14 / @muse-code/sdk 1.4.14 (lockstep).
50
+ // Wire contract: Muse Code 1.4.20 / @muse-code/sdk 1.4.20 (lockstep).
50
51
  // ---------------------------------------------------------------------------
51
52
 
52
53
  /** The SDK version this session is built against; the served CLI must report the same. */
53
- export const MUSE_SDK_VERSION = '1.4.14';
54
+ export const MUSE_SDK_VERSION = '1.4.20';
54
55
 
55
56
  export type MuseApprovalMode = 'allowAll' | 'promptUnmatched';
56
57
  export type MusePermissionProfile = ':ask-me' | ':read-only' | ':unrestricted';
@@ -226,13 +227,15 @@ const CLIENT_INFO = { name: 'agent_session', title: 'agent-session', version: '1
226
227
 
227
228
  /**
228
229
  * Inherited variables that would bill, route or loosen Muse away from its saved subscription login
229
- * and the gates, by the names the 1.4.14 binary reads: metered keys and endpoints (META_, LLM_,
230
+ * and the gates, by the names the 1.4.20 binary reads: metered keys and endpoints (META_, LLM_,
230
231
  * MODEL_, LLAMA_, OPENROUTER_, ANTHROPIC_, OPENAI_), Meta's auth and key-mint endpoints, and MUSE_
231
- * switches for the model, experiments, contributor mode, the approval judge and a fail-open
232
- * profile. Muse's own tool runs inherit this environment, so they do not see these either.
232
+ * switches for the model, experiments, contributor mode, the approval judge, a fail-open profile,
233
+ * request routing and custom headers, the computer-use dev policy and identity skip, the launcher's
234
+ * channel and install switches, the credential backend, and the eval and test hooks (prompt injection,
235
+ * policy and grant overrides). The CA and TLS roots stay: a machine behind a proxy needs them. Muse's own tool runs inherit this environment, so they do not see these either.
233
236
  */
234
237
  const COMPETING_ENV =
235
- /^(META_|LLM_|MODEL_|LLAMA_|OPENROUTER_|ANTHROPIC_|OPENAI_|TBH_(AUTH|MINT)_BASE_URL$|MUSE_EXPERIMENTAL_|MUSE_(MODEL|CONTRIBUTOR_MODE|DISABLE_APPROVAL_JUDGE|PERMISSION_PROFILE_FAIL_OPEN)$)/;
238
+ /^(META_|LLM_|MODEL_|LLAMA_|OPENROUTER_|ANTHROPIC_|OPENAI_|TBH_(AUTH|MINT)_BASE_URL$|MUSE_EXPERIMENTAL_|TBH_(CREDENTIAL_BACKEND$|EVAL_|TEST_)|MUSE_CUA_DEV_|MUSE_(MODEL|CHANNEL|LAUNCHER_INSTALL|CONTRIBUTOR_MODE|DISABLE_APPROVAL_JUDGE|PERMISSION_PROFILE_FAIL_OPEN|WWW_ROUTING|CUSTOM_HEADERS)$)/;
236
239
 
237
240
  export class MuseSession implements HarnessSession {
238
241
  get transport() {
@@ -1914,10 +1917,10 @@ export class MuseSession implements HarnessSession {
1914
1917
  }
1915
1918
 
1916
1919
  // ---------------------------------------------------------------------------
1917
- // Default host: `muse serve` through the SDK's owned-process handshake
1920
+ // Default host: `muse serve` through the SDK's owned-process handshake; exported for scripts/record-muse.ts
1918
1921
  // ---------------------------------------------------------------------------
1919
1922
 
1920
- async function spawnMuseHost(launch: MuseLaunch): Promise<MuseHost> {
1923
+ export async function spawnMuseHost(launch: MuseLaunch): Promise<MuseHost> {
1921
1924
  const handshake = spawnMspConnection({
1922
1925
  command: launch.museBin,
1923
1926
  args: launch.args,
package/src/transport.ts CHANGED
@@ -143,12 +143,12 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
143
143
  },
144
144
  notes: [
145
145
  "`muse serve --trust-workspace` (plus `--disable-sandbox` under bypassPermissions) driven through Meta's `@muse-code/sdk` MuseClient, pinned to the installed CLI's version.",
146
- 'Billing is the saved Muse Code login: metered keys and endpoints (META_, LLM_, MODEL_, LLAMA_, OPENROUTER_, ANTHROPIC_, OPENAI_), Meta auth endpoints (TBH_) and the MUSE_ model, experiment, contributor, approval-judge and fail-open switches are removed from the child environment; a resumed or forked session must report provider `meta`.',
146
+ 'Billing is the saved Muse Code login: metered keys and endpoints (META_, LLM_, MODEL_, LLAMA_, OPENROUTER_, ANTHROPIC_, OPENAI_), Meta auth endpoints (TBH_) and the MUSE_ model, experiment, contributor, approval-judge, fail-open, routing, custom-header, computer-use dev, channel and install switches, and the TBH_ credential backend and eval/test hooks are removed from the child environment; a resumed or forked session must report provider `meta`.',
147
147
  'Approvals use an explicit permission profile (`:ask-me` supervised, `:read-only` plan, `:unrestricted` bypass), never the host default `:auto-review`, whose AI reviewer would decide instead of the gates; the committed profile, approval mode and (gated) a human reviewer are read back before any turn.',
148
148
  "Work Muse starts itself (cron firings, monitor and background-terminal wakes, goals, reminder agents, workflows, another client's messages) is folded like a turn's and emitted with `unattributedReason: 'task'`, plus a `task` lifecycle typed `muse-cron`, `muse-monitor`, `muse-goal`, `muse-reminder`, `muse-workflow` or `muse-turn`; it is seen only while this host has the session loaded.",
149
149
  "interruptNative() sends turn/interrupt; push() sends turn/steer; fork() runs session/fork on the new session's own host; readLimits() sends usage/read.",
150
150
  'userInput/request questions go to onUserInput(); without an answer they are cancelled with a reason, so a turn never waits on them.',
151
- 'Live-verified (muse 1.4.14): start, profile read-back, multi-stage shell and network approvals, text and tool events, token usage, usage/changed and usage/read (recorded in fixtures/muse); fork, resume with setApprovalMode and sessionNotFound were checked live without a model turn (not recorded). A steer (`turn/steer` into a running shell turn, answered in that turn) was checked live 2026-11-02 (not recorded). Images, interrupt, questions and Muse-initiated turns follow the SDK wire types without a live run.',
151
+ 'Live-verified (muse 1.4.20, 2026-11-15): start, profile read-back, multi-stage shell and network approvals, text and tool events, token usage, usage/changed and usage/read (recorded in fixtures/muse); fork, resume with setApprovalMode and sessionNotFound were checked live on 1.4.14 without a model turn (not recorded). A steer (`turn/steer` into a running shell turn, answered in that turn) and an interrupt (`turn/interrupt`, terminal `cancelled`) were checked live on 1.4.20, 2026-11-15 (not recorded). Images, questions and Muse-initiated turns follow the SDK wire types without a live run.',
152
152
  ],
153
153
  },
154
154
  'claude-agent-sdk': {