@sagentlab/navarch-runtime 0.1.112 → 0.1.117

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 (56) hide show
  1. package/README.md +56 -18
  2. package/bin/muse-mcp-bridge.cjs +3 -0
  3. package/dist/adapters/index.cjs +6 -1
  4. package/dist/adapters/muse.cjs +433 -0
  5. package/dist/agent-auth.cjs +2 -1
  6. package/dist/cli.cjs +4 -1
  7. package/dist/config.cjs +9 -1
  8. package/dist/conversation/capability.cjs +1 -1
  9. package/dist/conversation/loop.cjs +16 -0
  10. package/dist/conversation/mirror.cjs +225 -0
  11. package/dist/conversation/workspace.cjs +11 -4
  12. package/dist/exit-conditions.cjs +1 -1
  13. package/dist/muse-mcp-bridge.cjs +167 -0
  14. package/dist/session.cjs +30 -8
  15. package/dist/worktree-guard.cjs +137 -3
  16. package/node_modules/@muse-code/sdk/LICENSE +21 -0
  17. package/node_modules/@muse-code/sdk/README.md +88 -0
  18. package/node_modules/@muse-code/sdk/dist/src/connection/connection.d.ts +149 -0
  19. package/node_modules/@muse-code/sdk/dist/src/connection/connection.js +581 -0
  20. package/node_modules/@muse-code/sdk/dist/src/connection/spawn.d.ts +188 -0
  21. package/node_modules/@muse-code/sdk/dist/src/connection/spawn.js +496 -0
  22. package/node_modules/@muse-code/sdk/dist/src/errors.d.ts +71 -0
  23. package/node_modules/@muse-code/sdk/dist/src/errors.js +76 -0
  24. package/node_modules/@muse-code/sdk/dist/src/facade/approval.d.ts +96 -0
  25. package/node_modules/@muse-code/sdk/dist/src/facade/approval.js +293 -0
  26. package/node_modules/@muse-code/sdk/dist/src/facade/client.d.ts +171 -0
  27. package/node_modules/@muse-code/sdk/dist/src/facade/client.js +442 -0
  28. package/node_modules/@muse-code/sdk/dist/src/facade/discarded.d.ts +36 -0
  29. package/node_modules/@muse-code/sdk/dist/src/facade/discarded.js +36 -0
  30. package/node_modules/@muse-code/sdk/dist/src/facade/gap-fill.d.ts +80 -0
  31. package/node_modules/@muse-code/sdk/dist/src/facade/gap-fill.js +388 -0
  32. package/node_modules/@muse-code/sdk/dist/src/facade/host-death.d.ts +139 -0
  33. package/node_modules/@muse-code/sdk/dist/src/facade/host-death.js +96 -0
  34. package/node_modules/@muse-code/sdk/dist/src/facade/host-start.d.ts +62 -0
  35. package/node_modules/@muse-code/sdk/dist/src/facade/host-start.js +81 -0
  36. package/node_modules/@muse-code/sdk/dist/src/facade/session.d.ts +309 -0
  37. package/node_modules/@muse-code/sdk/dist/src/facade/session.js +654 -0
  38. package/node_modules/@muse-code/sdk/dist/src/facade/turn-handle.d.ts +156 -0
  39. package/node_modules/@muse-code/sdk/dist/src/facade/turn-handle.js +271 -0
  40. package/node_modules/@muse-code/sdk/dist/src/facade/turn-submit.d.ts +72 -0
  41. package/node_modules/@muse-code/sdk/dist/src/facade/turn-submit.js +183 -0
  42. package/node_modules/@muse-code/sdk/dist/src/fingerprint.d.ts +37 -0
  43. package/node_modules/@muse-code/sdk/dist/src/fingerprint.js +54 -0
  44. package/node_modules/@muse-code/sdk/dist/src/fold/item-store.d.ts +110 -0
  45. package/node_modules/@muse-code/sdk/dist/src/fold/item-store.js +169 -0
  46. package/node_modules/@muse-code/sdk/dist/src/fold/session-fold.d.ts +536 -0
  47. package/node_modules/@muse-code/sdk/dist/src/fold/session-fold.js +427 -0
  48. package/node_modules/@muse-code/sdk/dist/src/fold/state-store.d.ts +68 -0
  49. package/node_modules/@muse-code/sdk/dist/src/fold/state-store.js +89 -0
  50. package/node_modules/@muse-code/sdk/dist/src/index.d.ts +65 -0
  51. package/node_modules/@muse-code/sdk/dist/src/index.js +57 -0
  52. package/node_modules/@muse-code/sdk/dist/src/msp.d.ts +3380 -0
  53. package/node_modules/@muse-code/sdk/dist/src/pending/pending-command-set.d.ts +361 -0
  54. package/node_modules/@muse-code/sdk/dist/src/pending/pending-command-set.js +468 -0
  55. package/node_modules/@muse-code/sdk/package.json +47 -0
  56. package/package.json +8 -4
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The ephemeral-profile host-death discard obligation, client side
3
+ * (spec 14990 FM-002 / Scenario 4.3, tasks.md T030b; tdd SS2.13, SS4.4.3).
4
+ *
5
+ * Slice 2b landed the two STORE-LEVEL primitives with no production callers:
6
+ * `PendingCommandSet.discardEphemeral()` and
7
+ * `ItemStore.markEphemeralHostDeath()`. This module holds the two facts the
8
+ * stores deliberately do not know — which durability profile the handshake
9
+ * declared, and whether a given process exit was abnormal — and `Session`
10
+ * composes them into the discharge.
11
+ *
12
+ * The stores stay wire-shape-blind (INV-001): the `Item` probe lives here,
13
+ * where it is checked against the GENERATED type.
14
+ */
15
+ /**
16
+ * Bound to the CLOSED extraction of the generated vocabularies so a
17
+ * misspelling fails `tsc`. Comparing a bare literal against the open
18
+ * `SessionDurability`/`ItemStatus` unions typechecks either way — the same
19
+ * invisibility that cost INV-001 its interim exception in the pending set.
20
+ */
21
+ const DURABLE = "durable";
22
+ const EPHEMERAL = "ephemeral";
23
+ const IN_PROGRESS = "inProgress";
24
+ /**
25
+ * Read `sessionDurability` off the handshake result (SS2.13.1).
26
+ *
27
+ * Two readings are easy to conflate and must not be:
28
+ *
29
+ * - ABSENT is `"durable"`, and that is decidable rather than fabricated: the
30
+ * member is optional only so the addition was additive (SS1.5.4), and no
31
+ * server that omits it has the ephemeral profile.
32
+ * - An UNRECOGNIZED value is NOT durable. SS2.13.1: a client that does not
33
+ * know the value "MUST NOT infer any durability guarantee from it, and MUST
34
+ * NOT fall through to the absent-means-durable rule — that rule keys on the
35
+ * member being missing, not on its value being unfamiliar." The enum is open
36
+ * for a reason (#14401's degraded state is a candidate third value), so the
37
+ * conservative read is "assume nothing survives this host".
38
+ */
39
+ export function readSessionDurability(result) {
40
+ const declared = result.sessionDurability;
41
+ if (declared === undefined || declared === DURABLE)
42
+ return { kind: "durable" };
43
+ if (declared === EPHEMERAL)
44
+ return { kind: "ephemeral" };
45
+ return { kind: "unrecognized", value: declared };
46
+ }
47
+ /** Does a session on this profile survive its host? Only a durable one does. */
48
+ export function survivesHostDeath(profile) {
49
+ return profile.kind === DURABLE;
50
+ }
51
+ /**
52
+ * Was this notification an abnormal death (SS2.13.3b)?
53
+ *
54
+ * Exit 0 is the ONLY SS2.11 row where the run was cancelled, the drain
55
+ * completed, and the durable `SessionEnd` records were written. Every other
56
+ * row — including the ones with a tidy-looking name like `configError` —
57
+ * left no `SessionEnd`, which is exactly what "abnormal" means here. A
58
+ * transport EOF the client did not cause left no `SessionEnd` either, so it
59
+ * joins the same predicate rather than getting a parallel one that could drift.
60
+ */
61
+ export function isAbnormalHostDeath(notification) {
62
+ return notification.kind !== "cleanShutdown";
63
+ }
64
+ /**
65
+ * The in-progress probe the store asks its caller for. `Item.status` is an
66
+ * open enum whose terminal rule is "anything other than `inProgress`", so this
67
+ * is a positive test on the one non-terminal value rather than a negative test
68
+ * against a list that a schema advance would silently widen.
69
+ */
70
+ export function isItemInProgress(item) {
71
+ return item.status === IN_PROGRESS;
72
+ }
73
+ /** A durable host died abnormally: its terminals arrive on resume (FM-001). */
74
+ export class MuseHostDiedError extends Error {
75
+ exit;
76
+ // The parameter is narrowed rather than checked: `cleanShutdown` is not a
77
+ // death, so the type refuses to build this error for one instead of a
78
+ // ternary papering over a state no caller can reach.
79
+ constructor(exit) {
80
+ super(`MSP host died: ${exit.kind} (${MuseHostDiedError.#cause(exit)})`);
81
+ this.name = "MuseHostDiedError";
82
+ this.exit = exit;
83
+ }
84
+ static #cause(exit) {
85
+ // EOF has no exit code to render, and "exit code undefined" would read as
86
+ // a missing datum rather than the fact that the process has not reported.
87
+ if (exit.kind === "transportEof")
88
+ return "transport EOF with no orderly close";
89
+ // A signal kill has a null exit code, and the signal is its ONLY
90
+ // diagnostic — rendering "exit code null" throws that away.
91
+ if (exit.kind === "crash" && exit.exitCode === null) {
92
+ return `signal ${String(exit.exitSignal)}`;
93
+ }
94
+ return `exit code ${String(exit.exitCode)}`;
95
+ }
96
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The typed failure of `MuseClient.spawn` when the host is lost before the
3
+ * SS1.4 handshake completes (#49791).
4
+ *
5
+ * Without it the rejection is whatever the transport saw first — `write
6
+ * EPIPE` on the first frame, or `connection reached EOF` — while the evidence
7
+ * that explains it (Node's spawn error, or the SS2.11 exit row with its
8
+ * bounded stderr tail) sits unread on the child the facade already owns.
9
+ * Spec 14990 Scenario 4 attaches that evidence to every process-exit error;
10
+ * this module is the handshake-failure arm of that rule.
11
+ */
12
+ import type { ExitClassification, MuseServeChild } from "../connection/spawn.js";
13
+ /**
14
+ * How the host was lost. Two arms because the repair differs: `spawnFailed`
15
+ * means no process ever ran (`error` is Node's own spawn error — `code`
16
+ * `ENOENT`, `EACCES`, … — so fix the path or its permissions); `hostExited`
17
+ * means a process ran and ended before the handshake completed, and `exit` is
18
+ * its SS2.11 row — any row, the clean one included: a host that wrote its
19
+ * complaint to stderr and exited 0 before its first frame is still a lost
20
+ * host, and the row says only how it ended. `answeredInitialize` says whether
21
+ * it got as far as a usable `initialize` answer before dying (then the loss
22
+ * came while the SDK was completing the handshake, not at startup).
23
+ */
24
+ export type HostStartFailure = {
25
+ readonly kind: "spawnFailed";
26
+ readonly error: NodeJS.ErrnoException;
27
+ } | {
28
+ readonly kind: "hostExited";
29
+ readonly exit: ExitClassification;
30
+ readonly answeredInitialize: boolean;
31
+ };
32
+ /**
33
+ * `MuseClient.spawn` lost its host before the handshake completed.
34
+ *
35
+ * Typed rather than a bare `Error` because the two arms of `failure` name
36
+ * STATES an embedder branches on (retry after fixing the path, versus act on
37
+ * an SS2.11 retry posture). The transport error that first surfaced the loss
38
+ * rides as `cause`, so nothing the old rejection carried is dropped.
39
+ */
40
+ export declare class MuseHostStartError extends Error {
41
+ readonly failure: HostStartFailure;
42
+ /**
43
+ * The SDK's bounded stderr tail when the host was lost, one accessor
44
+ * regardless of arm: empty when no process ran, and the only place the
45
+ * evidence lives when the row is `cleanShutdown` (SS2.11 keeps no tail on
46
+ * the clean row; a logger wants the lines either way).
47
+ */
48
+ readonly stderrTail: readonly string[];
49
+ constructor(failure: HostStartFailure, stderrTail: readonly string[], cause: unknown);
50
+ }
51
+ /**
52
+ * Read a lost host's evidence into the typed error. "Lost" is decided by
53
+ * whether the host ANSWERED — whether a response frame came back — never by
54
+ * how it ended: the caller keeps an answer's own error (`MspError`, or a
55
+ * `ProtocolError` carrying the refused frame) and routes only frameless
56
+ * failures here, where the row says how the unanswering host ended.
57
+ *
58
+ * Call only after the handshake's `close()` has settled: the close ladder
59
+ * ends at the child's observed exit (or at its spawn failure), so
60
+ * `child.exit` is already settled here and this never waits on a live host.
61
+ */
62
+ export declare function hostStartFailure(child: MuseServeChild, cause: unknown, answeredInitialize: boolean): Promise<MuseHostStartError>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The typed failure of `MuseClient.spawn` when the host is lost before the
3
+ * SS1.4 handshake completes (#49791).
4
+ *
5
+ * Without it the rejection is whatever the transport saw first — `write
6
+ * EPIPE` on the first frame, or `connection reached EOF` — while the evidence
7
+ * that explains it (Node's spawn error, or the SS2.11 exit row with its
8
+ * bounded stderr tail) sits unread on the child the facade already owns.
9
+ * Spec 14990 Scenario 4 attaches that evidence to every process-exit error;
10
+ * this module is the handshake-failure arm of that rule.
11
+ */
12
+ /** How many tail lines the message quotes; `stderrTail` carries them all. */
13
+ const MESSAGE_TAIL_LINES = 5;
14
+ /**
15
+ * `MuseClient.spawn` lost its host before the handshake completed.
16
+ *
17
+ * Typed rather than a bare `Error` because the two arms of `failure` name
18
+ * STATES an embedder branches on (retry after fixing the path, versus act on
19
+ * an SS2.11 retry posture). The transport error that first surfaced the loss
20
+ * rides as `cause`, so nothing the old rejection carried is dropped.
21
+ */
22
+ export class MuseHostStartError extends Error {
23
+ failure;
24
+ /**
25
+ * The SDK's bounded stderr tail when the host was lost, one accessor
26
+ * regardless of arm: empty when no process ran, and the only place the
27
+ * evidence lives when the row is `cleanShutdown` (SS2.11 keeps no tail on
28
+ * the clean row; a logger wants the lines either way).
29
+ */
30
+ stderrTail;
31
+ constructor(failure, stderrTail, cause) {
32
+ super(describeHostStartFailure(failure, stderrTail), { cause });
33
+ this.name = "MuseHostStartError";
34
+ this.failure = failure;
35
+ this.stderrTail = stderrTail;
36
+ }
37
+ }
38
+ function describeHostStartFailure(failure, tail) {
39
+ if (failure.kind === "spawnFailed") {
40
+ return `muse host could not be spawned: ${failure.error.message}`;
41
+ }
42
+ const exit = failure.exit;
43
+ const how = exit.kind === "cleanShutdown"
44
+ ? "exit code 0"
45
+ : exit.kind === "crash"
46
+ ? exit.exitSignal !== null
47
+ ? `signal ${exit.exitSignal}`
48
+ : `exit code ${String(exit.exitCode)}`
49
+ : `exit code ${String(exit.exitCode)}${"retry" in exit ? `, retry: ${exit.retry}` : ""}`;
50
+ const quoted = tail.slice(-MESSAGE_TAIL_LINES);
51
+ const evidence = tail.length === 0
52
+ ? "stderr tail: (empty)"
53
+ : `stderr tail${quoted.length < tail.length ? ` (last ${quoted.length} of ${tail.length} lines)` : ""}:\n ${quoted.join("\n ")}`;
54
+ const when = failure.answeredInitialize
55
+ ? "after answering initialize, before the handshake completed"
56
+ : "before answering initialize";
57
+ return `muse host exited ${when}: ${exit.kind} (${how}); ${evidence}`;
58
+ }
59
+ /**
60
+ * Read a lost host's evidence into the typed error. "Lost" is decided by
61
+ * whether the host ANSWERED — whether a response frame came back — never by
62
+ * how it ended: the caller keeps an answer's own error (`MspError`, or a
63
+ * `ProtocolError` carrying the refused frame) and routes only frameless
64
+ * failures here, where the row says how the unanswering host ended.
65
+ *
66
+ * Call only after the handshake's `close()` has settled: the close ladder
67
+ * ends at the child's observed exit (or at its spawn failure), so
68
+ * `child.exit` is already settled here and this never waits on a live host.
69
+ */
70
+ export async function hostStartFailure(child, cause, answeredInitialize) {
71
+ let exit;
72
+ try {
73
+ exit = await child.exit;
74
+ }
75
+ catch (spawnError) {
76
+ // `exit` rejects only with the ChildProcess 'error' payload: Node's own
77
+ // spawn errno (`code` ENOENT, EACCES, …). No other producer reaches it.
78
+ return new MuseHostStartError({ kind: "spawnFailed", error: spawnError }, child.stderrTail, cause);
79
+ }
80
+ return new MuseHostStartError({ kind: "hostExited", exit, answeredInitialize }, child.stderrTail, cause);
81
+ }
@@ -0,0 +1,309 @@
1
+ /**
2
+ * `Session` — the SS7.1 facade's composition of the transport-less core
3
+ * (spec 14990 FR-018 / T030, FM-002 / T030b).
4
+ *
5
+ * It owns a `SessionFold` and a `PendingCommandSet`, routes folded view events
6
+ * to per-turn handles, and discharges the ephemeral host-death obligation.
7
+ * Like everything else in this SDK it holds no durable state (spec Lifecycle &
8
+ * Composition): every fact it reports came from a server event or from the two
9
+ * stores it composes.
10
+ *
11
+ * The SS4.8 splice-fill (FR-020, T032) is here too, in `gap-fill.ts`: this
12
+ * layer owns it because the recipe needs client→server I/O (`view/page`) and
13
+ * because the buffer it splices is the same routing path the iterators read.
14
+ *
15
+ * Everything else FR-018/FR-019 name IS here as of #23980: `sendUserTurn` over
16
+ * the enrolled `turn/start` row, `onApproval`'s idempotent `approval/decide`
17
+ * submission, and the SS4.13 retirement drivers that need client->server I/O.
18
+ * The connection is an OPTIONAL seam: a fold-only `Session` — the transport-less
19
+ * composition every earlier arm builds — stays valid and refuses the submit
20
+ * verbs loudly rather than dropping a caller's input on the floor.
21
+ */
22
+ import { SessionFold } from "../fold/session-fold.js";
23
+ import type { FoldOutcome, ViewEvent } from "../fold/session-fold.js";
24
+ import { PendingCommandSet } from "../pending/pending-command-set.js";
25
+ import type { PendingRetirement, ReplayAnswer, SnapshotJoinFacts } from "../pending/pending-command-set.js";
26
+ import type { Connection } from "../connection/connection.js";
27
+ import type { ApprovalFailureHandler, ApprovalHandler } from "./approval.js";
28
+ import type { SendUserTurnOptions } from "./turn-submit.js";
29
+ import type { GapFillFailureHandler } from "./gap-fill.js";
30
+ import type { DiscardedSessions } from "./discarded.js";
31
+ import type { HostDeathDischarge, HostDeathNotification, SessionDurabilityProfile } from "./host-death.js";
32
+ import type { Turn } from "./turn-handle.js";
33
+ import type { SessionResumeResult, SessionStartResult } from "../msp.js";
34
+ /**
35
+ * Which verb opened this session, and what it answered.
36
+ *
37
+ * `MuseClient.startSession`/`resumeSession` resolve to a `Session` (FR-018), so
38
+ * without this the typed wire result — `viewCursor`, the resume history, the
39
+ * server's own `sessionId` — would be swallowed by the wrapper. Discriminated
40
+ * by the verb rather than a bare union, so a consumer that only handles resume
41
+ * cannot silently read a start result's absent members.
42
+ */
43
+ export type SessionOpening = {
44
+ readonly verb: "session/start";
45
+ readonly result: SessionStartResult;
46
+ } | {
47
+ readonly verb: "session/resume";
48
+ readonly result: SessionResumeResult;
49
+ };
50
+ /**
51
+ * Options for constructing a `Session` directly. Sessions opened through
52
+ * `MuseClient` are configured for you; build one yourself when you manage
53
+ * the connection — or omit the connection to fold recorded events with no
54
+ * host at all.
55
+ */
56
+ export interface SessionOptions {
57
+ readonly sessionId: string;
58
+ /**
59
+ * Required, not defaulted. Whether this session survives its host decides
60
+ * what happens to every in-flight item and command when the host dies
61
+ * (SS2.13.3b), and a default would let a caller skip reading the handshake
62
+ * and silently inherit the wrong obligation. `readSessionDurability` turns
63
+ * an `InitializeResult` into this value.
64
+ */
65
+ readonly durability: SessionDurabilityProfile;
66
+ /**
67
+ * The wire seam the submit verbs author through. OPTIONAL because fold-only
68
+ * construction is a real current use, not a hypothetical one: every
69
+ * transport-less arm in this package builds a `Session` with no host at all.
70
+ */
71
+ readonly connection?: Connection;
72
+ /**
73
+ * What an ephemeral host death already discarded, shared across the sessions
74
+ * ONE client opened (SS2.13.3b, T030 obligation (c)). Omitted, this session
75
+ * remembers only its own discards — the pre-#23980 behaviour.
76
+ */
77
+ readonly discarded?: DiscardedSessions;
78
+ /** Set by `MuseClient`; see {@link SessionOpening}. */
79
+ readonly opening?: SessionOpening;
80
+ }
81
+ /**
82
+ * The fold as a consumer may READ it.
83
+ *
84
+ * An ALLOWLIST (`Pick`), not `Omit` of the mutator names. `Omit` never checks
85
+ * its keys against the type, so every future public `SessionFold` method would
86
+ * land on this barrel-exported "read-only" view silently — and the contract
87
+ * already plans one: `seedFromSnapshot` (FR-008), a mutator that would feed the
88
+ * fold past `apply`'s turn routing and discard latch. `Pick` constrains its
89
+ * keys to `keyof SessionFold`, so both drift directions are compile errors.
90
+ * This is the same conversion `FoldItems` made one layer down (PR #23087).
91
+ */
92
+ export type SessionFoldView = Pick<SessionFold, "activeTurnId" | "current" | "pendingGap" | "items" | "pendingApprovals" | "pendingUserInputs" | "resolvedApprovals" | "sessionState" | "settledUserInputs" | "turn" | "turns">;
93
+ /**
94
+ * The pending set as a consumer may READ and DRIVE it.
95
+ *
96
+ * `discardEphemeral`, `observedReclaim` and `observedUserMessage` are
97
+ * `Session`'s to call: it drives them from folded events and reports what they
98
+ * returned. A consumer calling `discardEphemeral()` directly drains the set and
99
+ * latches it discarded, so the later real `hostExited` reports
100
+ * `retiredCommands: []` — silently losing the very inputs the discharge exists
101
+ * to hand back. Same allowlist discipline as the fold view.
102
+ *
103
+ * `stopRetrying` and `replayAnswered` are hidden for the same bookkeeping
104
+ * reason, re-exposed as `Session` methods: both can retire an entry the
105
+ * submit verb authored, and a retirement that bypasses
106
+ * `TurnSubmitter.forgetRetired` leaves that command's `turn/start` params in
107
+ * the replay memory forever — unbounded over the sanctioned SS3.1.1
108
+ * submit-then-abandon flow.
109
+ */
110
+ export type PendingCommandView<I> = Pick<PendingCommandSet<I>, "ackErrored" | "acked" | "discarded" | "get" | "has" | "joinSnapshot" | "list" | "observedQueueMovement" | "reconnectedWithoutSnapshot" | "resolveReplayAtJoin" | "size" | "submitted">;
111
+ /**
112
+ * `Session.apply`'s own refusal. NOT a `FoldOutcome`: the fold can never emit
113
+ * it, and putting it there promised a direct `SessionFold` consumer a refusal
114
+ * that seam does not produce.
115
+ */
116
+ export interface SessionApplyRefusal {
117
+ readonly kind: "refusedSessionDiscarded";
118
+ }
119
+ /**
120
+ * `Session.apply`'s other own verdict, for the same reason: a frame held while
121
+ * an SS4.8 fill is in flight (FR-020 / T032).
122
+ *
123
+ * NOT a `FoldOutcome` — the fold has not seen this frame yet and reporting one
124
+ * would state a fold move that has not happened. The frame is not lost: it is
125
+ * re-fed through this same path, in cursor order behind the paged prefix, when
126
+ * the fill splices. The pause a consumer sees is exactly this.
127
+ */
128
+ export interface SessionGapBuffered {
129
+ readonly kind: "bufferedDuringGap";
130
+ readonly method: string;
131
+ }
132
+ /**
133
+ * A live frame the SS4.8 fill already applied from a page — the other half of
134
+ * the recipe's overlap discard (PR #24930 review round 1).
135
+ *
136
+ * The wire promises no order between a `view/gap` and the frame at its `next`,
137
+ * so the live twin of an event the page served can arrive after the fill
138
+ * finished, when the buffer is gone. Refused once, by cursor equality, because
139
+ * folding it again would re-run its routing — a duplicate SS4.13 queue-movement
140
+ * replay on the wire, which is exactly what the discard exists to prevent.
141
+ */
142
+ export interface SessionGapOverlap {
143
+ readonly kind: "ignoredGapOverlap";
144
+ readonly method: string;
145
+ readonly viewCursor: string;
146
+ }
147
+ /**
148
+ * What one `Session.apply` did: the fold's own outcome, plus any SS4.13
149
+ * pending-command retirements the event triggered.
150
+ *
151
+ * Retirements ride the return value rather than a stream, matching the pull
152
+ * model the pending set already uses: every
153
+ * retirement is returned synchronously by the mutator that produced it — with
154
+ * ONE stated exception.
155
+ *
156
+ * THE EXCEPTION (FR-020 / T032). A frame HELD during an SS4.8 fill reports
157
+ * `bufferedDuringGap` with `retirements: []`, because at that moment nothing
158
+ * has folded. Its real effects — an SS4.13 reclaim, an approval round trip —
159
+ * happen when the fill splices, and they surface on the `io` of the EARLIER
160
+ * `view/gap` apply that started the fill, never on the buffered frame's own
161
+ * return. A consumer reconciling retirements frame by frame must therefore keep
162
+ * the gap frame's outcome and await its `io`, or it will read `[]` for a
163
+ * reclaim that really happened.
164
+ *
165
+ * A SECOND `view/gap` that lands while a fill is running rides the same
166
+ * channel for the same reason: it extends the running walk instead of starting
167
+ * its own, so its apply reports `deliveryGap` with an already-settled empty
168
+ * `io`, and the work it caused settles on the FIRST gap frame's.
169
+ */
170
+ export interface SessionApplyOutcome<I> {
171
+ readonly fold: FoldOutcome | SessionApplyRefusal | SessionGapBuffered | SessionGapOverlap;
172
+ readonly retirements: readonly PendingRetirement<I>[];
173
+ /**
174
+ * The client->server I/O this event triggered, settled — the SS4.13 replays
175
+ * queue movement demands, and FR-019's `approval/decide` submission. Resolves
176
+ * to the retirements that I/O produced.
177
+ *
178
+ * A SEPARATE channel from `retirements` because `apply` is synchronous by
179
+ * contract: a consumer's notification pump must not be made to await a round
180
+ * trip before it can fold the next frame. An event that triggers no I/O gets
181
+ * a shared already-resolved empty, so awaiting this is always safe and never
182
+ * allocates.
183
+ *
184
+ * IT NEVER REJECTS. A replay whose transport failed proves nothing about the
185
+ * intake, so its entry holds and the authoritative report is the host-death
186
+ * notification that is already on its way; an approval failure is reported to
187
+ * `onApprovalError`. Rejecting here would surface as an unhandled rejection
188
+ * in every consumer that does not await it — which is most of them, since the
189
+ * value exists for tests and for consumers that want a barrier.
190
+ */
191
+ readonly io: Promise<readonly PendingRetirement<I>[]>;
192
+ }
193
+ /**
194
+ * One conversation with the agent.
195
+ *
196
+ * A session folds every server event into readable state — items, turns,
197
+ * session facts, pending approvals — and submits your side of the exchange.
198
+ * Get one from `MuseClient.startSession` or `resumeSession`, then
199
+ * `sendUserTurn` to talk, `turn` to follow a turn's items to its outcome,
200
+ * and `onApproval` to answer permission requests.
201
+ */
202
+ export declare class Session<I = unknown> {
203
+ #private;
204
+ readonly sessionId: string;
205
+ /** See {@link SessionOpening}. Absent when the caller built this directly. */
206
+ readonly opening: SessionOpening | undefined;
207
+ constructor(options: SessionOptions);
208
+ get durability(): SessionDurabilityProfile;
209
+ /** Read-only; events enter through `apply` so turn routing cannot be skipped. */
210
+ get fold(): SessionFoldView;
211
+ /** The SS4.13 set, minus the three mutators `Session` drives itself. */
212
+ get pending(): PendingCommandView<I>;
213
+ /**
214
+ * Fold one view event, then fan it out to the turn handles that want it.
215
+ *
216
+ * Throws on an event for another session. Every `ViewEvent` params type
217
+ * carries a required `sessionId`, and with no `MuseClient` router yet an
218
+ * embedder feeds this straight off a connection — so on a multiplexed feed
219
+ * one misrouted frame would silently corrupt this transcript. The
220
+ * constructor takes a `sessionId`; this is what makes taking it mean
221
+ * something.
222
+ */
223
+ apply(event: ViewEvent): SessionApplyOutcome<I>;
224
+ apply(event: {
225
+ readonly method: string;
226
+ readonly params?: unknown;
227
+ }): SessionApplyOutcome<I>;
228
+ /** Observe SS4.8 fills that did not complete. See `MuseGapFillError`. */
229
+ onGapError(handler: GapFillFailureHandler): void;
230
+ /**
231
+ * The handle for a turn, created on first mention from either side. A turn
232
+ * the fold has already settled returns its settled handle, so a wait
233
+ * registered late resolves instead of hanging on an event that has passed.
234
+ */
235
+ turn(turnId: string): Turn;
236
+ /**
237
+ * A host process exited. Classify it, and discharge SS2.13.3b if the profile
238
+ * demands it (tasks.md T030b, FM-002).
239
+ *
240
+ * REPORT EVERY NOTIFICATION OF THE DEATH — the exit promise AND transport
241
+ * EOF. A repeat report of an already-latched death is not a no-op: it marks
242
+ * END OF DRAIN and settles waits the drain minted after the first report.
243
+ * A consumer that dedupes exit notifications and reports the death once
244
+ * leaves those waits pending forever (FM-001).
245
+ *
246
+ * Three outcomes, and the middle one is the one an implementation drops:
247
+ * - an ORDERLY exit (SS2.11 row 0) is not a death: nothing is discharged
248
+ * and the session stays usable;
249
+ * - a DURABLE session's abnormal death reconciles on resume (FM-001), so
250
+ * the stores are left exactly as observed and only the waiters are told;
251
+ * - an EPHEMERAL — or unrecognized (SS2.13.1) — session's abnormal death
252
+ * discharges the obligation in full.
253
+ */
254
+ hostExited(exit: HostDeathNotification): HostDeathDischarge<I>;
255
+ /**
256
+ * Submit a user turn (tdd SS3.2) and hand back THIS session's handle for the
257
+ * turn the ack named.
258
+ *
259
+ * The returned handle is the one `apply` already routes events to — not a
260
+ * second one minted beside it, which would compare equal on every field and
261
+ * then never receive an item, a delta, or a terminal.
262
+ *
263
+ * The wire shaping, the optimistic SS4.13 entry, and the settle-on-rejection
264
+ * rule all live in `TurnSubmitter`; what is decided HERE is the one thing that
265
+ * needs the fold: the entry's insertion point, which is the last item this
266
+ * session holds at submission time.
267
+ */
268
+ sendUserTurn(options: SendUserTurnOptions<I>): Promise<Turn>;
269
+ /** Answer approvals with `handler` (FR-019). See `ApprovalRouter`. */
270
+ onApproval(handler: ApprovalHandler): void;
271
+ /** Observe round trips that did not complete. See `ApprovalFailure`. */
272
+ onApprovalError(handler: ApprovalFailureHandler): void;
273
+ /**
274
+ * Stop the SS3.1.1 retry loop for one entry and take its input back
275
+ * (SS4.13 "Abandoned").
276
+ *
277
+ * A `Session` method rather than a `pending` view member: the retirement
278
+ * must also prune the submitter's replay memory, or the abandoned command's
279
+ * `turn/start` params live for the session's lifetime — unbounded over the
280
+ * sanctioned submit-then-abandon flow.
281
+ */
282
+ stopRetrying(commandId: string): PendingRetirement<I> | undefined;
283
+ /**
284
+ * Feed a consumer-driven replay's answer through the live SS4.13 settlement
285
+ * rules. Wrapped for the same replay-memory bookkeeping as `stopRetrying`;
286
+ * `"held"` means the answer settled nothing and the entry stays.
287
+ */
288
+ replayAnswered(commandId: string, answer: ReplayAnswer): PendingRetirement<I> | "held";
289
+ /**
290
+ * Resolve a snapshot join by performing the I/O its plan demands (SS4.13,
291
+ * SS4.9): same-`commandId` resubmits FIRST, then the replays, then the
292
+ * reclaimed-signature verdict.
293
+ *
294
+ * The order is normative, not incidental: SS4.13 requires an unacked entry be
295
+ * resubmitted "before any retire-to-composer", because a join miss does not
296
+ * prove the intake was never written.
297
+ *
298
+ * `PendingCommandSet.joinSnapshot` stays available on `pending` for a
299
+ * fold-only consumer that performs its own I/O; this verb is for the wired
300
+ * case and therefore requires a connection even when the plan turns out empty.
301
+ */
302
+ resolveSnapshotJoin(facts: SnapshotJoinFacts): Promise<readonly PendingRetirement<I>[]>;
303
+ /**
304
+ * Resolve a reconnect with NO snapshot (`history.mode: "none"`, the default
305
+ * cursor-resume path). Reconnect itself is the trigger: replay each acked
306
+ * entry once, resubmit each unacked entry's SAME `commandId` once.
307
+ */
308
+ resolveReconnect(): Promise<readonly PendingRetirement<I>[]>;
309
+ }