@sagentlab/navarch-runtime 0.1.116 → 0.1.118
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 +43 -17
- package/bin/muse-mcp-bridge.cjs +3 -0
- package/bin/worktree-guard-hook.cjs +74 -2
- package/dist/adapters/index.cjs +6 -1
- package/dist/adapters/muse.cjs +433 -0
- package/dist/agent-auth.cjs +2 -1
- package/dist/config.cjs +8 -1
- package/dist/conversation/capability.cjs +1 -1
- package/dist/exit-conditions.cjs +1 -1
- package/dist/muse-mcp-bridge.cjs +167 -0
- package/dist/session.cjs +30 -8
- package/node_modules/@muse-code/sdk/LICENSE +21 -0
- package/node_modules/@muse-code/sdk/README.md +88 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/connection.d.ts +149 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/connection.js +581 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/spawn.d.ts +188 -0
- package/node_modules/@muse-code/sdk/dist/src/connection/spawn.js +496 -0
- package/node_modules/@muse-code/sdk/dist/src/errors.d.ts +71 -0
- package/node_modules/@muse-code/sdk/dist/src/errors.js +76 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/approval.d.ts +96 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/approval.js +293 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/client.d.ts +171 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/client.js +442 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/discarded.d.ts +36 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/discarded.js +36 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/gap-fill.d.ts +80 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/gap-fill.js +388 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-death.d.ts +139 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-death.js +96 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-start.d.ts +62 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/host-start.js +81 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/session.d.ts +309 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/session.js +654 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-handle.d.ts +156 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-handle.js +271 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-submit.d.ts +72 -0
- package/node_modules/@muse-code/sdk/dist/src/facade/turn-submit.js +183 -0
- package/node_modules/@muse-code/sdk/dist/src/fingerprint.d.ts +37 -0
- package/node_modules/@muse-code/sdk/dist/src/fingerprint.js +54 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/item-store.d.ts +110 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/item-store.js +169 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/session-fold.d.ts +536 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/session-fold.js +427 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/state-store.d.ts +68 -0
- package/node_modules/@muse-code/sdk/dist/src/fold/state-store.js +89 -0
- package/node_modules/@muse-code/sdk/dist/src/index.d.ts +65 -0
- package/node_modules/@muse-code/sdk/dist/src/index.js +57 -0
- package/node_modules/@muse-code/sdk/dist/src/msp.d.ts +3380 -0
- package/node_modules/@muse-code/sdk/dist/src/pending/pending-command-set.d.ts +361 -0
- package/node_modules/@muse-code/sdk/dist/src/pending/pending-command-set.js +468 -0
- package/node_modules/@muse-code/sdk/package.json +47 -0
- 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
|
+
}
|