@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,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PendingCommand`: the client-side fold over pending commands.
|
|
3
|
+
*
|
|
4
|
+
* Normative source: tdd SS4.13 (owned by decision record D-032). This module
|
|
5
|
+
* implements that subsection and does not re-derive it; every rule below
|
|
6
|
+
* quotes or cites the clause it realizes. It is **client state only** — no
|
|
7
|
+
* wire event, method, snapshot member, or durable record (INV-007), and it
|
|
8
|
+
* never crosses the wire.
|
|
9
|
+
*
|
|
10
|
+
* The full fold is `S' = f(S, server events, pending client events)`: server
|
|
11
|
+
* events remain the only source of durable truth, and a `PendingCommand`
|
|
12
|
+
* entry is a rendering of the client's own intent.
|
|
13
|
+
*
|
|
14
|
+
* The entry itself is client-local by ruling, and its join keys (`commandId`,
|
|
15
|
+
* `turnId`, `itemId`) are opaque strings by protocol rule (SS3.1.4, SS4.1).
|
|
16
|
+
* The wire vocabulary it touches is fully generated: spec 206 Phase 11
|
|
17
|
+
* (#22772) enrolled the `turn/start` ack and the `-32030` `commandRejected`
|
|
18
|
+
* registry row, so INV-001's one recorded interim exception is discharged
|
|
19
|
+
* here — nothing below restates a wire shape.
|
|
20
|
+
*
|
|
21
|
+
* Every comparison against a wire value goes through a constant ANNOTATED
|
|
22
|
+
* with the generated vocabulary (`Extract<...>`), which is what makes a
|
|
23
|
+
* misspelling a compile error. Comparing a bare literal against the open
|
|
24
|
+
* `ErrorKind`/`TurnStartDisposition` unions typechecks either way — that
|
|
25
|
+
* invisibility is precisely what the exception cost.
|
|
26
|
+
*/
|
|
27
|
+
import { MuseSessionDiscardedError } from "../errors.js";
|
|
28
|
+
/**
|
|
29
|
+
* The JSON-RPC code for a durable command rejection (tdd SS3.1.2, Appendix B).
|
|
30
|
+
*
|
|
31
|
+
* `@muse-code/msp` is a types-only package, so the registry's CODE cannot be
|
|
32
|
+
* imported the way its types can. It is named exactly once, here, and
|
|
33
|
+
* `pending-command-wire-binding.test.ts` pins it to the `commandRejected` row
|
|
34
|
+
* in `schema/msp/stable/msp.schema.json` — the same pin shape
|
|
35
|
+
* `EXPECTED_SCHEMA_FINGERPRINT` uses, so a schema advance that moves the code
|
|
36
|
+
* reds this lane instead of silently leaving the SDK settling on a dead code.
|
|
37
|
+
*/
|
|
38
|
+
export const COMMAND_REJECTED_CODE = -32030;
|
|
39
|
+
/**
|
|
40
|
+
* The registry row's `kind`, bound to the CLOSED generated vocabulary.
|
|
41
|
+
*
|
|
42
|
+
* `ErrorKind` is open (`| (string & {})`), so a typo compared against it still
|
|
43
|
+
* typechecks. `MspErrorDataKind` is the closed registry vocabulary, so this
|
|
44
|
+
* annotation makes a misspelling fail `tsc`.
|
|
45
|
+
*/
|
|
46
|
+
export const COMMAND_REJECTED_KIND = "commandRejected";
|
|
47
|
+
/** The `"queued"` disposition, bound so a misspelling fails `tsc` (SS3.2). */
|
|
48
|
+
const QUEUED_DISPOSITION = "queued";
|
|
49
|
+
/**
|
|
50
|
+
* Tracks the commands you have submitted that the server has not yet woven
|
|
51
|
+
* into the view: their optimistic display order, acknowledgements, replay
|
|
52
|
+
* answers at reconnect, and retirements. `Session` owns and drives one per
|
|
53
|
+
* conversation; read it through the session's pending view.
|
|
54
|
+
*/
|
|
55
|
+
export class PendingCommandSet {
|
|
56
|
+
#entries = new Map();
|
|
57
|
+
#ephemeralDiscardedCommandIds;
|
|
58
|
+
#discarded = false;
|
|
59
|
+
#nextSubmissionIndex = 0;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
this.#ephemeralDiscardedCommandIds = options?.discardedCommandIds ?? new Set();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Record a submission. `anchorAfterItemId` is the last item present in the
|
|
65
|
+
* client's fold right now (SS4.13 insertion point).
|
|
66
|
+
*/
|
|
67
|
+
submitted(params) {
|
|
68
|
+
// Replay check FIRST: for an id this set actually retired, "cannot be
|
|
69
|
+
// replayed against a new host" is the precise diagnosis, and the generic
|
|
70
|
+
// set-closed message below would bury it.
|
|
71
|
+
this.#assertNotEphemeralReplay(params.commandId);
|
|
72
|
+
// Then the whole SET, not just the ids it retired. A brand-new submit
|
|
73
|
+
// after an ephemeral host died would otherwise be accepted and retired
|
|
74
|
+
// `terminalUnknown` by the next discharge — "we don't know whether this
|
|
75
|
+
// ran" about input provably never sent to any host, which is the
|
|
76
|
+
// fabricated annotation SS2.13.3b exists to forbid.
|
|
77
|
+
this.#assertSessionActive();
|
|
78
|
+
if (this.#entries.has(params.commandId)) {
|
|
79
|
+
// A resubmit of the same commandId is the sanctioned exactly-once
|
|
80
|
+
// retry (SS3.1.1); it does not create a second entry and MUST NOT
|
|
81
|
+
// move the original's anchor.
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const entry = {
|
|
85
|
+
commandId: params.commandId,
|
|
86
|
+
input: params.input,
|
|
87
|
+
anchorAfterItemId: params.anchorAfterItemId ?? null,
|
|
88
|
+
submissionIndex: this.#nextSubmissionIndex++,
|
|
89
|
+
};
|
|
90
|
+
if (params.displayText !== undefined)
|
|
91
|
+
entry.displayText = params.displayText;
|
|
92
|
+
this.#entries.set(params.commandId, entry);
|
|
93
|
+
}
|
|
94
|
+
/** The submit (or a replay) was acked. */
|
|
95
|
+
acked(commandId, ack) {
|
|
96
|
+
const entry = this.#entries.get(commandId);
|
|
97
|
+
if (entry === undefined)
|
|
98
|
+
return;
|
|
99
|
+
entry.ack = ack;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The submit (or a replay) answered with an error.
|
|
103
|
+
*
|
|
104
|
+
* Only a durable `commandRejected` (-32030) is a settlement. Every other
|
|
105
|
+
* error admits nothing — the other SS3.1.2 admission errors and SS1's
|
|
106
|
+
* envelope errors, `inputTooLarge` among them — so the entry HOLDS while
|
|
107
|
+
* the client retries with the same `commandId` (SS4.13 "Nothing-admitted
|
|
108
|
+
* errors are not settlements").
|
|
109
|
+
*/
|
|
110
|
+
ackErrored(commandId, error) {
|
|
111
|
+
const entry = this.#entries.get(commandId);
|
|
112
|
+
if (entry === undefined)
|
|
113
|
+
return "held";
|
|
114
|
+
if (!PendingCommandSet.isSettlement(error))
|
|
115
|
+
return "held";
|
|
116
|
+
return this.#retire(entry, this.#rejectionRetirement(entry, error));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The client has decided to stop retrying a nothing-admitted error. The
|
|
120
|
+
* entry must not linger as a durable-looking echo (SS4.13).
|
|
121
|
+
*/
|
|
122
|
+
stopRetrying(commandId) {
|
|
123
|
+
const entry = this.#entries.get(commandId);
|
|
124
|
+
if (entry === undefined)
|
|
125
|
+
return undefined;
|
|
126
|
+
return this.#retire(entry, {
|
|
127
|
+
kind: "retryAbandonedByClient",
|
|
128
|
+
commandId,
|
|
129
|
+
input: entry.input,
|
|
130
|
+
restoreToComposer: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A `userMessage` item carrying this `commandId` folded in: the entry
|
|
135
|
+
* materialized and the item replaces it at the item's own transcript
|
|
136
|
+
* position (SS4.13 "Materialized").
|
|
137
|
+
*
|
|
138
|
+
* Multi-client echo de-duplication falls out of the same join: a
|
|
139
|
+
* `userMessage` with no matching local entry is another client's
|
|
140
|
+
* submission — this returns `undefined` and the caller renders it plainly.
|
|
141
|
+
*/
|
|
142
|
+
observedUserMessage(commandId, itemId) {
|
|
143
|
+
const entry = this.#entries.get(commandId);
|
|
144
|
+
if (entry === undefined)
|
|
145
|
+
return undefined;
|
|
146
|
+
return this.#retire(entry, { kind: "materialized", commandId, matchedBy: "userMessage", itemId });
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A reclaim this client actually observed (its own reclaim ack, or any live
|
|
150
|
+
* fold D-024's lane charters). Retire immediately — same composer restore,
|
|
151
|
+
* without waiting for a snapshot join (SS4.13 "Reclaimed").
|
|
152
|
+
*/
|
|
153
|
+
observedReclaim(commandId) {
|
|
154
|
+
const entry = this.#entries.get(commandId);
|
|
155
|
+
if (entry === undefined)
|
|
156
|
+
return undefined;
|
|
157
|
+
return this.#retire(entry, {
|
|
158
|
+
kind: "reclaimed",
|
|
159
|
+
commandId,
|
|
160
|
+
input: entry.input,
|
|
161
|
+
restoreToComposer: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Queue movement: a `turn/started` or `turn/completed` for a turn that is
|
|
166
|
+
* NOT the entry's own.
|
|
167
|
+
*
|
|
168
|
+
* SS4.3 carries no view event for a command-intake settlement, so a
|
|
169
|
+
* post-ack rejection reaches a live client only by replay: on each such
|
|
170
|
+
* event a client holding an acked-QUEUED entry MUST re-verify it by
|
|
171
|
+
* replaying its `commandId`. Queue movement is the trigger precisely
|
|
172
|
+
* because a queued turn's fate is decided at its launch boundary — no
|
|
173
|
+
* polling loop and no invented timeout (SS3.1.3 stays intact).
|
|
174
|
+
*
|
|
175
|
+
* @returns the `commandId`s the caller must replay.
|
|
176
|
+
*/
|
|
177
|
+
observedQueueMovement(turnId) {
|
|
178
|
+
const out = [];
|
|
179
|
+
for (const entry of this.#entries.values()) {
|
|
180
|
+
if (entry.ack === undefined)
|
|
181
|
+
continue;
|
|
182
|
+
if (entry.ack.disposition !== QUEUED_DISPOSITION)
|
|
183
|
+
continue;
|
|
184
|
+
if (entry.ack.turnId === turnId)
|
|
185
|
+
continue; // the entry's own turn
|
|
186
|
+
out.push(entry.commandId);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Feed back a `commandId` replay answer (SS3.1.1 — always safe).
|
|
192
|
+
*
|
|
193
|
+
* Three shapes, per SS4.13's rejected/abandoned arms:
|
|
194
|
+
* - a durable rejection: retire (reason `"abandoned"` → Abandoned arm,
|
|
195
|
+
* any other reason → Rejected arm);
|
|
196
|
+
* - the original ack, still pending: keep waiting;
|
|
197
|
+
* - an ack whose staleness only a snapshot can prove: keep waiting — the
|
|
198
|
+
* reclaimed signature needs `queuedTurns` in hand (`joinSnapshot`).
|
|
199
|
+
*/
|
|
200
|
+
replayAnswered(commandId, answer) {
|
|
201
|
+
this.#assertNotEphemeralReplay(commandId);
|
|
202
|
+
const entry = this.#entries.get(commandId);
|
|
203
|
+
if (entry === undefined)
|
|
204
|
+
return "held";
|
|
205
|
+
if (answer.kind === "ack") {
|
|
206
|
+
entry.ack = answer.ack;
|
|
207
|
+
return "held";
|
|
208
|
+
}
|
|
209
|
+
if (!PendingCommandSet.isSettlement(answer.error))
|
|
210
|
+
return "held";
|
|
211
|
+
return this.#retire(entry, this.#rejectionRetirement(entry, answer.error));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Reconnect with NO snapshot (`history.mode: "none"`, the default
|
|
215
|
+
* cursor-resume path). There is no join to run, so reconnect itself is the
|
|
216
|
+
* trigger: replay each ACKED entry's `commandId` once; resubmit each
|
|
217
|
+
* UNACKED entry's same `commandId` once before any retire-to-composer.
|
|
218
|
+
*
|
|
219
|
+
* Without `queuedTurns` in hand the reclaimed signature is undecidable, so
|
|
220
|
+
* a stale `"queued"` ack waits for the next snapshot join or queue
|
|
221
|
+
* movement — never a locally invented terminal.
|
|
222
|
+
*/
|
|
223
|
+
reconnectedWithoutSnapshot() {
|
|
224
|
+
// Once-per-plan comes from the single `#ordered()` pass (each entry is
|
|
225
|
+
// visited exactly once); a NEW reconnect demands again, which SS3.1.1
|
|
226
|
+
// same-`commandId` idempotency makes safe — a once-per-lifetime latch
|
|
227
|
+
// would strand an entry whose demand was lost to a second disconnect
|
|
228
|
+
// (SS4.13 forbids).
|
|
229
|
+
const mustReplay = [];
|
|
230
|
+
const mustResubmit = [];
|
|
231
|
+
for (const entry of this.#ordered()) {
|
|
232
|
+
if (entry.ack === undefined) {
|
|
233
|
+
mustResubmit.push(entry.commandId);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
mustReplay.push(entry.commandId);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
retirements: [],
|
|
241
|
+
mustReplay,
|
|
242
|
+
mustResubmit,
|
|
243
|
+
kept: this.#ordered().map((e) => e.commandId),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Join local entries against an authoritative snapshot (SS4.9), by
|
|
248
|
+
* `commandId`. The five arms, in the order SS4.13 states them.
|
|
249
|
+
*/
|
|
250
|
+
joinSnapshot(facts) {
|
|
251
|
+
// Once-per-plan comes from the single ordered pass below, exactly as in
|
|
252
|
+
// `reconnectedWithoutSnapshot`; a NEW join demands again, which SS3.1.1
|
|
253
|
+
// same-`commandId` idempotency makes safe (SS4.13 forbids stranding an
|
|
254
|
+
// entry whose demand was lost to a second disconnect).
|
|
255
|
+
const retirements = [];
|
|
256
|
+
const mustReplay = [];
|
|
257
|
+
const mustResubmit = [];
|
|
258
|
+
const userMessageItemByCommandId = new Map();
|
|
259
|
+
for (const pair of facts.userMessageCommandIds) {
|
|
260
|
+
userMessageItemByCommandId.set(pair.commandId, pair.itemId);
|
|
261
|
+
}
|
|
262
|
+
const queuedIndexByCommandId = new Map();
|
|
263
|
+
facts.queuedTurns.forEach((q, index) => queuedIndexByCommandId.set(q.commandId, index));
|
|
264
|
+
for (const entry of this.#ordered()) {
|
|
265
|
+
const { commandId } = entry;
|
|
266
|
+
// Arm 1 — materialized: a userMessage item (its own item id is in the
|
|
267
|
+
// join facts), or the activeTurn's commandId (no item id in hand — the
|
|
268
|
+
// retirement says so via matchedBy, never aliasing the command id).
|
|
269
|
+
const userMessageItemId = userMessageItemByCommandId.get(commandId);
|
|
270
|
+
if (userMessageItemId !== undefined) {
|
|
271
|
+
retirements.push(this.#retire(entry, {
|
|
272
|
+
kind: "materialized",
|
|
273
|
+
commandId,
|
|
274
|
+
matchedBy: "userMessage",
|
|
275
|
+
itemId: userMessageItemId,
|
|
276
|
+
}));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (facts.activeTurn?.commandId === commandId) {
|
|
280
|
+
retirements.push(this.#retire(entry, { kind: "materialized", commandId, matchedBy: "activeTurn" }));
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
// Arm 2 — server-confirmed queued: keep, reordered to queuedTurns order.
|
|
284
|
+
const queuedIndex = queuedIndexByCommandId.get(commandId);
|
|
285
|
+
if (queuedIndex !== undefined) {
|
|
286
|
+
entry.queuedOrder = queuedIndex;
|
|
287
|
+
// A kept entry re-anchors after the last item of the reconciled fold.
|
|
288
|
+
entry.anchorAfterItemId = facts.lastItemId;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
// Arm 3 — an acked STEER matches neither activeTurn.commandId nor
|
|
292
|
+
// queuedTurns by design: it is pending on the running turn's
|
|
293
|
+
// PendingSteerQueue (SS3.3), not settled. Keep it as server-confirmed
|
|
294
|
+
// pending; retiring here shows "never ran" and then double-sends when
|
|
295
|
+
// the steer delivers. A steer keeps its submission anchor — it has no
|
|
296
|
+
// launch position.
|
|
297
|
+
if (entry.ack !== undefined &&
|
|
298
|
+
facts.activeTurn !== null &&
|
|
299
|
+
entry.ack.turnId === facts.activeTurn.turnId) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
// Arm 4 — acked, matching nothing: resolve by replaying the commandId.
|
|
303
|
+
if (entry.ack !== undefined) {
|
|
304
|
+
mustReplay.push(commandId);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
// Arm 5 — unacked, and the join did not match it. The intake is durable
|
|
308
|
+
// BEFORE the ack (SS3.1.3), and a join miss does not prove the intake
|
|
309
|
+
// was never written (a parked steer is in none of the join surfaces),
|
|
310
|
+
// so demand a SAME-commandId resubmit before any retire-to-composer.
|
|
311
|
+
mustResubmit.push(commandId);
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
retirements,
|
|
315
|
+
mustReplay,
|
|
316
|
+
mustResubmit,
|
|
317
|
+
kept: this.#ordered().map((e) => e.commandId),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The reclaimed signature, decidable only with a snapshot in hand: an
|
|
322
|
+
* acked-queued entry ABSENT from the snapshot's `queuedTurns` whose replay
|
|
323
|
+
* still answers the stale `"queued"` ack retires as *reclaimed* at the
|
|
324
|
+
* snapshot join (SS4.13 "Reclaimed").
|
|
325
|
+
*
|
|
326
|
+
* Call this with the replay answer for a `mustReplay` entry produced by
|
|
327
|
+
* `joinSnapshot`, passing the same snapshot's `queuedTurns` commandIds.
|
|
328
|
+
*/
|
|
329
|
+
resolveReplayAtJoin(commandId, answer, snapshotQueuedCommandIds) {
|
|
330
|
+
this.#assertNotEphemeralReplay(commandId);
|
|
331
|
+
const entry = this.#entries.get(commandId);
|
|
332
|
+
if (entry === undefined)
|
|
333
|
+
return "held";
|
|
334
|
+
if (answer.kind === "error") {
|
|
335
|
+
if (!PendingCommandSet.isSettlement(answer.error))
|
|
336
|
+
return "held";
|
|
337
|
+
return this.#retire(entry, this.#rejectionRetirement(entry, answer.error));
|
|
338
|
+
}
|
|
339
|
+
const stillQueuedByServer = new Set(snapshotQueuedCommandIds).has(commandId);
|
|
340
|
+
if (answer.ack.disposition === QUEUED_DISPOSITION && !stillQueuedByServer) {
|
|
341
|
+
// The snapshot just proved this ack stale.
|
|
342
|
+
return this.#retire(entry, {
|
|
343
|
+
kind: "reclaimed",
|
|
344
|
+
commandId,
|
|
345
|
+
input: entry.input,
|
|
346
|
+
restoreToComposer: true,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
// A still-pending "steered" or "started" ack: the entry stays pending and
|
|
350
|
+
// materializes or settles later. Never promote it locally.
|
|
351
|
+
entry.ack = answer.ack;
|
|
352
|
+
return "held";
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Abnormal death of an EPHEMERAL-profile host (SS2.13, SS4.4.3 carve-out):
|
|
356
|
+
* nothing is ever settled, replaying a `commandId` at a new host is
|
|
357
|
+
* forbidden, and every entry falls under the discard obligation as
|
|
358
|
+
* terminal-unknown — a client-side annotation, never a wire fact.
|
|
359
|
+
*/
|
|
360
|
+
discardEphemeral() {
|
|
361
|
+
const out = [];
|
|
362
|
+
for (const entry of this.#ordered()) {
|
|
363
|
+
this.#ephemeralDiscardedCommandIds.add(entry.commandId);
|
|
364
|
+
out.push(this.#retire(entry, {
|
|
365
|
+
kind: "terminalUnknown",
|
|
366
|
+
commandId: entry.commandId,
|
|
367
|
+
input: entry.input,
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
this.#discarded = true;
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
/** Has the ephemeral discard closed this set? */
|
|
374
|
+
get discarded() {
|
|
375
|
+
return this.#discarded;
|
|
376
|
+
}
|
|
377
|
+
#assertSessionActive() {
|
|
378
|
+
if (this.#discarded) {
|
|
379
|
+
throw new MuseSessionDiscardedError("ephemeral session was discarded after abnormal host death; it accepts no new submissions");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
#assertNotEphemeralReplay(commandId) {
|
|
383
|
+
if (this.#ephemeralDiscardedCommandIds.has(commandId)) {
|
|
384
|
+
throw new MuseSessionDiscardedError(`ephemeral host died; commandId \`${commandId}\` cannot be replayed against a new host`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Entries in render order.
|
|
389
|
+
*
|
|
390
|
+
* Before any join this is submission order. After a join, entries the
|
|
391
|
+
* server confirmed queued are re-anchored to the end of the reconciled
|
|
392
|
+
* fold and ordered among themselves by `queuedTurns` — so they sort after
|
|
393
|
+
* the submission-anchored entries (steers and unacked), which keep their
|
|
394
|
+
* own anchors. That is SS4.13's "entries render in submission order, and
|
|
395
|
+
* for queued submits the server-confirmed order wins whenever it is
|
|
396
|
+
* observed".
|
|
397
|
+
*/
|
|
398
|
+
list() {
|
|
399
|
+
return this.#ordered().map((entry) => {
|
|
400
|
+
const out = {
|
|
401
|
+
commandId: entry.commandId,
|
|
402
|
+
input: entry.input,
|
|
403
|
+
anchorAfterItemId: entry.anchorAfterItemId,
|
|
404
|
+
submissionIndex: entry.submissionIndex,
|
|
405
|
+
...(entry.displayText !== undefined ? { displayText: entry.displayText } : {}),
|
|
406
|
+
...(entry.ack !== undefined ? { ack: entry.ack } : {}),
|
|
407
|
+
...(entry.queuedOrder !== undefined ? { queuedOrder: entry.queuedOrder } : {}),
|
|
408
|
+
};
|
|
409
|
+
return out;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
get(commandId) {
|
|
413
|
+
return this.list().find((e) => e.commandId === commandId);
|
|
414
|
+
}
|
|
415
|
+
has(commandId) {
|
|
416
|
+
return this.#entries.has(commandId);
|
|
417
|
+
}
|
|
418
|
+
get size() {
|
|
419
|
+
return this.#entries.size;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Only a durable `commandRejected` (-32030) settles a command. Everything
|
|
423
|
+
* else admitted nothing (SS4.13).
|
|
424
|
+
*
|
|
425
|
+
* The registry binds `-32030` <-> `commandRejected` one-to-one (SS3.1.2
|
|
426
|
+
* Appendix B), so a response where the two fields DISAGREE is a server
|
|
427
|
+
* fault in which one field still asserts the durable rejection. Either
|
|
428
|
+
* signal settles (OR): SS3.1.2's stated bias for unfamiliar vocabulary is
|
|
429
|
+
* toward terminal ("treat unknown reasons as terminal rejections"), and
|
|
430
|
+
* holding forever on a half-asserted rejection strands the entry as the
|
|
431
|
+
* durable-looking echo SS4.13 forbids.
|
|
432
|
+
*/
|
|
433
|
+
static isSettlement(error) {
|
|
434
|
+
return error.code === COMMAND_REJECTED_CODE || error.kind === COMMAND_REJECTED_KIND;
|
|
435
|
+
}
|
|
436
|
+
#rejectionRetirement(entry, error) {
|
|
437
|
+
const reason = error.reason ?? "";
|
|
438
|
+
if (reason === "abandoned") {
|
|
439
|
+
return {
|
|
440
|
+
kind: "abandoned",
|
|
441
|
+
commandId: entry.commandId,
|
|
442
|
+
input: entry.input,
|
|
443
|
+
restoreToComposer: true,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
kind: "rejected",
|
|
448
|
+
commandId: entry.commandId,
|
|
449
|
+
reason,
|
|
450
|
+
input: entry.input,
|
|
451
|
+
restoreToComposer: true,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
#retire(entry, retirement) {
|
|
455
|
+
this.#entries.delete(entry.commandId);
|
|
456
|
+
return retirement;
|
|
457
|
+
}
|
|
458
|
+
#ordered() {
|
|
459
|
+
const submissionAnchored = [];
|
|
460
|
+
const launchOrdered = [];
|
|
461
|
+
for (const entry of this.#entries.values()) {
|
|
462
|
+
(entry.queuedOrder === undefined ? submissionAnchored : launchOrdered).push(entry);
|
|
463
|
+
}
|
|
464
|
+
submissionAnchored.sort((a, b) => a.submissionIndex - b.submissionIndex);
|
|
465
|
+
launchOrdered.sort((a, b) => (a.queuedOrder ?? 0) - (b.queuedOrder ?? 0));
|
|
466
|
+
return [...submissionAnchored, ...launchOrdered];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@muse-code/sdk",
|
|
3
|
+
"version": "1.4.6",
|
|
4
|
+
"description": "TypeScript SDK for the Muse Session Protocol (MSP): connect to a Muse Code session, drive turns, and fold the event stream. Zero runtime dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/meta-models/muse-code-sdk",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/meta-models/muse-code-sdk.git",
|
|
10
|
+
"directory": "clients/sdk-ts"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/meta-models/muse-code-sdk/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "dist/src/index.js",
|
|
17
|
+
"types": "dist/src/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/src"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc --build",
|
|
29
|
+
"check": "tsc --build",
|
|
30
|
+
"prepack": "npm run build && node scripts/bundle-msp-types.mjs",
|
|
31
|
+
"test": "npm run build && node --test --test-timeout=300000 \"dist/test/**/*.test.js\"",
|
|
32
|
+
"qa:sdk": "npm run build && node dist/qa/bin.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@types/node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@types/node": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@muse-code/msp": "*",
|
|
45
|
+
"@types/node": "26.2.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sagentlab/navarch-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.118",
|
|
4
4
|
"description": "Navarch machine-side session manager: claims delivery tasks and runs them through local coding agents.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,10 +19,12 @@
|
|
|
19
19
|
"opencode",
|
|
20
20
|
"agent-client-protocol",
|
|
21
21
|
"deepseek-harness",
|
|
22
|
-
"task-runner"
|
|
22
|
+
"task-runner",
|
|
23
|
+
"muse-code"
|
|
23
24
|
],
|
|
24
25
|
"bin": {
|
|
25
|
-
"navarch-runtime": "bin/navarch.cjs"
|
|
26
|
+
"navarch-runtime": "bin/navarch.cjs",
|
|
27
|
+
"navarch-muse-mcp-bridge": "bin/muse-mcp-bridge.cjs"
|
|
26
28
|
},
|
|
27
29
|
"files": [
|
|
28
30
|
"dist",
|
|
@@ -56,11 +58,13 @@
|
|
|
56
58
|
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
57
59
|
"playwright": "1.63.0",
|
|
58
60
|
"typescript": "^5.7.2",
|
|
59
|
-
"zod": "^3.25.76"
|
|
61
|
+
"zod": "^3.25.76",
|
|
62
|
+
"@muse-code/sdk": "1.4.6"
|
|
60
63
|
},
|
|
61
64
|
"bundleDependencies": [
|
|
62
65
|
"@agentclientprotocol/sdk",
|
|
63
66
|
"@modelcontextprotocol/sdk",
|
|
67
|
+
"@muse-code/sdk",
|
|
64
68
|
"playwright",
|
|
65
69
|
"typescript",
|
|
66
70
|
"zod"
|