@code-yeongyu/senpi 2026.11.2-1 → 2026.11.2-2
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/CHANGELOG.md +19 -0
- package/dist/bundle/chunks/{chunk-AMCDEC5A.js → chunk-B2LRAXQU.js} +2 -2
- package/dist/bundle/chunks/{chunk-D22YF34L.js → chunk-CQ3G57HE.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZUVGFXCS.js → chunk-FVYWSBX3.js} +3 -3
- package/dist/bundle/chunks/{chunk-665UPAKG.js → chunk-H3BTMIH5.js} +6 -6
- package/dist/bundle/chunks/{chunk-KEJWNCLN.js → chunk-HUSQY3ID.js} +1 -1
- package/dist/bundle/chunks/{chunk-IBV4OVXQ.js → chunk-N6OHYIJR.js} +1 -1
- package/dist/bundle/chunks/{chunk-XZR4J6HM.js → chunk-PP52OWBB.js} +1 -1
- package/dist/bundle/chunks/{chunk-XVQISKHL.js → chunk-QV2IEA3B.js} +4 -4
- package/dist/bundle/chunks/{chunk-2V6BR24J.js → chunk-VEWJSPOZ.js} +1 -1
- package/dist/bundle/chunks/{cli-main-REFQSP6S.js → cli-main-6ZI4QO2Z.js} +1 -1
- package/dist/bundle/chunks/{host-command-Y3ZT6YLD.js → host-command-O3ALG2O3.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-VW73K4WA.js → host-lifecycle-SGTVODHB.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +5 -5
- package/dist/bundle/chunks/{host-session-command-YDEUZBTX.js → host-session-command-4WRR5JSB.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-UZXPNJ4V.js → interactive-mode-TF7R35GK.js} +1 -1
- package/dist/bundle/chunks/{main-O5G75MYS.js → main-XLUYSOXP.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-AZKJYA2X.js → multi-session-host-CADYC2BS.js} +4 -3
- package/dist/bundle/chunks/{package-manager-cli-2YDUGJNR.js → package-manager-cli-I62KMIWK.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-KZD5HZNI.js → rpc-mode-T5GVMUBQ.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-7LSJAINZ.js → schedule-command-YXGRN5CM.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-BG2KQ6MJ.js → session-control-endpoint-PREVS5UO.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +63 -62
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +5 -5
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/session-recovery-identity.d.ts +12 -1
- package/dist/modes/rpc/compact-call-fold.d.ts +3 -1
- package/dist/modes/rpc/compact-call-fold.js +17 -6
- package/dist/modes/rpc/compact-call-identity.d.ts +13 -0
- package/dist/modes/rpc/compact-call-identity.js +30 -0
- package/dist/modes/rpc/compact-call-model.d.ts +14 -0
- package/dist/modes/rpc/compact-call-model.js +4 -0
- package/dist/modes/rpc/compact-call-table.d.ts +9 -0
- package/dist/modes/rpc/compact-call-table.js +20 -0
- package/dist/modes/rpc/compact-catalog-settled.d.ts +5 -0
- package/dist/modes/rpc/compact-catalog-settled.js +9 -2
- package/dist/modes/rpc/compact-lane-current.d.ts +2 -0
- package/dist/modes/rpc/compact-lane-current.js +4 -0
- package/dist/modes/rpc/compact-message-identity.d.ts +74 -0
- package/dist/modes/rpc/compact-message-identity.js +101 -0
- package/dist/modes/rpc/compact-session-calls.d.ts +5 -4
- package/dist/modes/rpc/compact-session-calls.js +13 -8
- package/dist/modes/rpc/compact-session-state.d.ts +2 -0
- package/dist/modes/rpc/compact-session-state.js +4 -0
- package/dist/modes/rpc/compact-settled-messages.d.ts +12 -4
- package/dist/modes/rpc/compact-settled-messages.js +18 -10
- package/dist/modes/rpc/connection-handler.js +1 -1
- package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
- package/dist/modes/rpc/connection-question-bridge.js +3 -1
- package/dist/modes/rpc/host-lifecycle-proxy.d.ts +9 -0
- package/dist/modes/rpc/host-lifecycle-proxy.js +29 -7
- package/dist/modes/rpc/rpc-question-types.d.ts +2 -0
- package/dist/modes/rpc/session-acceptance-compact.d.ts +17 -7
- package/dist/modes/rpc/session-acceptance-compact.js +20 -8
- package/dist/modes/rpc/session-acceptance-lane.d.ts +5 -0
- package/dist/modes/rpc/session-acceptance-lane.js +12 -11
- package/dist/modes/rpc/session-event-writer-destinations.d.ts +2 -1
- package/dist/modes/rpc/session-event-writer-destinations.js +24 -9
- package/dist/utils/version-check.js +9 -3
- package/docs/engine-socket/fixtures/admission-routes/i-live-identity.json +716 -0
- package/docs/engine-socket/fixtures/admission-routes/index.json +3 -1
- package/docs/engine-socket/fixtures/admission-routes/j-live-call-identity.json +1020 -0
- package/docs/rpc.md +66 -0
- package/package.json +6 -6
|
@@ -110,24 +110,23 @@ export class CompactSessionCalls {
|
|
|
110
110
|
raw: call.raw?.kind === "owned" ? call.raw : raw,
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
|
+
/** Each per-id execution record returns the call it changed (RULING-123), undefined when it changed none. */
|
|
113
114
|
executionStart(step, start) {
|
|
114
115
|
const turnId = step.scope.turnId;
|
|
115
116
|
if (turnId === null)
|
|
116
|
-
return;
|
|
117
|
+
return undefined;
|
|
117
118
|
const started = {
|
|
118
119
|
startSequence: step.sequence,
|
|
119
120
|
startedAtMs: Date.now(),
|
|
120
121
|
resolvedName: start.toolName,
|
|
121
122
|
};
|
|
122
123
|
const announced = this.table.oldestById(start.toolCallId, turnId, awaitingStart);
|
|
123
|
-
if (announced)
|
|
124
|
-
this.update(step, announced, {
|
|
124
|
+
if (announced)
|
|
125
|
+
return this.update(step, announced, {
|
|
125
126
|
...started,
|
|
126
127
|
phase: "executing",
|
|
127
128
|
input: { ...announced.input, started: start.args },
|
|
128
129
|
});
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
130
|
const existing = this.table.byTurnId(turnId, start.toolCallId);
|
|
132
131
|
if (existing)
|
|
133
132
|
this.table.counters.reused++;
|
|
@@ -142,6 +141,7 @@ export class CompactSessionCalls {
|
|
|
142
141
|
});
|
|
143
142
|
if (existing)
|
|
144
143
|
this.table.repoint(turnId, start.toolCallId, created.runtimeItemKey);
|
|
144
|
+
return created;
|
|
145
145
|
}
|
|
146
146
|
executionUpdate(step, toolCallId, args, partialResult) {
|
|
147
147
|
const call = this.resolve(step, toolCallId, executing);
|
|
@@ -154,6 +154,7 @@ export class CompactSessionCalls {
|
|
|
154
154
|
if (call.input.prepared === null)
|
|
155
155
|
this.update(step, progressed, { input: { ...call.input, prepared: args } });
|
|
156
156
|
this.sequence = step.sequence;
|
|
157
|
+
return progressed;
|
|
157
158
|
}
|
|
158
159
|
hook(step, toolCallId, record) {
|
|
159
160
|
const call = this.resolve(step, toolCallId, stillOpen);
|
|
@@ -161,8 +162,9 @@ export class CompactSessionCalls {
|
|
|
161
162
|
return this.ignore("unknown");
|
|
162
163
|
if (terminal(call))
|
|
163
164
|
return this.ignore("late");
|
|
164
|
-
this.table.replaceSlot(call, "hook", record);
|
|
165
|
+
const hooked = this.table.replaceSlot(call, "hook", record);
|
|
165
166
|
this.sequence = step.sequence;
|
|
167
|
+
return hooked;
|
|
166
168
|
}
|
|
167
169
|
/** An end closes the oldest call still open under its id (senpi#4278); with none open it may replace an abandoned one. */
|
|
168
170
|
executionEnd(step, toolCallId, result, isError) {
|
|
@@ -172,7 +174,7 @@ export class CompactSessionCalls {
|
|
|
172
174
|
if (terminal(call) && call.outcome?.kind !== "abandoned")
|
|
173
175
|
return this.ignore("late");
|
|
174
176
|
const ended = this.table.replaceSlot(call, "result", result);
|
|
175
|
-
this.update(step, ended, {
|
|
177
|
+
return this.update(step, ended, {
|
|
176
178
|
phase: TERMINAL_PHASE,
|
|
177
179
|
outcome: { kind: "result", isError, source: "execution_end" },
|
|
178
180
|
join: orphaned(this.table, call) || call.join === "carrierless" ? "carrierless" : "pending",
|
|
@@ -226,6 +228,7 @@ export class CompactSessionCalls {
|
|
|
226
228
|
}
|
|
227
229
|
ignore(kind) {
|
|
228
230
|
this.table.counters[kind]++;
|
|
231
|
+
return undefined;
|
|
229
232
|
}
|
|
230
233
|
create(step, fields) {
|
|
231
234
|
if (this.mintedAt !== step.sequence) {
|
|
@@ -245,8 +248,10 @@ export class CompactSessionCalls {
|
|
|
245
248
|
return call;
|
|
246
249
|
}
|
|
247
250
|
update(step, call, patch) {
|
|
248
|
-
|
|
251
|
+
const next = { ...call, ...patch };
|
|
252
|
+
this.table.put(next, creationOf(call));
|
|
249
253
|
this.sequence = step.sequence;
|
|
254
|
+
return next;
|
|
250
255
|
}
|
|
251
256
|
}
|
|
252
257
|
//# sourceMappingURL=compact-session-calls.js.map
|
|
@@ -24,6 +24,8 @@ export declare class CompactSessionState extends CompactActiveMessages {
|
|
|
24
24
|
accept(t: CompactTransition, sequence?: number, turnId?: string | null): number;
|
|
25
25
|
/** A call record at its lane step; `changed` is false when the record is ignored and takes no sequence (Q9). */
|
|
26
26
|
acceptCall(record: object, sequence: number, turnId: string | null): CallFold;
|
|
27
|
+
/** The outstanding call whose block this is: the call a `toolcall_*` update streams (RULING-123), by the table's index. */
|
|
28
|
+
callOwning(messageId: string, contentIndex: number): import("./compact-call-model.ts").OutstandingCall | undefined;
|
|
27
29
|
applyCold(t: CompactColdOperation): Promise<import("./compact-cold-operations.ts").CompactColdOutcome>;
|
|
28
30
|
/** One ordered cold unit per flow: its operations run back to back after acceptance, each with its outcome. */
|
|
29
31
|
applyColdFlow(flow: CompactColdFlow): Promise<readonly import("./compact-cold-operations.ts").CompactColdOutcome[]>;
|
|
@@ -182,6 +182,10 @@ export class CompactSessionState extends CompactActiveMessages {
|
|
|
182
182
|
this.sequence = sequence;
|
|
183
183
|
return fold;
|
|
184
184
|
}
|
|
185
|
+
/** The outstanding call whose block this is: the call a `toolcall_*` update streams (RULING-123), by the table's index. */
|
|
186
|
+
callOwning(messageId, contentIndex) {
|
|
187
|
+
return this.calls.table.byOwner(messageId, contentIndex);
|
|
188
|
+
}
|
|
185
189
|
applyCold(t) {
|
|
186
190
|
return this.cold.run(t, this.sequence);
|
|
187
191
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RecoveryCarrier } from "../../core/session-recovery-identity.ts";
|
|
2
2
|
import type { TranscriptTransition } from "../../core/session-transcript-journal.ts";
|
|
3
3
|
import type { CallProjection } from "./compact-call-model.ts";
|
|
4
|
+
import type { SettledEnd } from "./compact-message-identity.ts";
|
|
4
5
|
import { type FrozenIndex } from "./compact-content-index.ts";
|
|
5
6
|
import { CompactContentStore, type CompactContentStoreOptions, type LeafId } from "./compact-content-store.ts";
|
|
6
7
|
import type { CompactValues, ValueRef } from "./compact-values.ts";
|
|
@@ -69,17 +70,24 @@ export declare class CompactSettledMessages {
|
|
|
69
70
|
reader(root: SettledRoot): SettledReader;
|
|
70
71
|
valid(carrier: RecoveryCarrier | undefined): carrier is RecoveryCarrier;
|
|
71
72
|
/**
|
|
72
|
-
* Returns
|
|
73
|
+
* Returns the accepted end (undefined when refused): whether it joined the message to its durable entry, the only end
|
|
74
|
+
* that settles persistence, and the message's settled ids (RULING-120): its `messageId` and the end's accepted sequence as
|
|
75
|
+
* `terminalSequence`, which a pending or failed terminal pages as `message-<terminalSequence>` and a joined message
|
|
76
|
+
* pages only through its `join-<n>` member's `messageId` (an already-joined end writes no terminal row). `call`:
|
|
73
77
|
* the projection of the outstanding call this toolResult settles, stored with the terminal (Q2). The terminal is
|
|
74
78
|
* serialized from `record`, so a record over the protocol depth limit is refused typed before anything changes
|
|
75
79
|
* (RULING-59 C); the lane runs that check, and counts the refusal, before it calls this.
|
|
76
80
|
*/
|
|
77
|
-
end(record: object, carrier: RecoveryCarrier | undefined, sequence: number, turnId: string | null, call?: CallProjection):
|
|
81
|
+
end(record: object, carrier: RecoveryCarrier | undefined, sequence: number, turnId: string | null, call?: CallProjection): SettledEnd | undefined;
|
|
78
82
|
private reject;
|
|
79
|
-
|
|
83
|
+
/** Marks the message's pending terminal failed; returns the `messageId` that row carries, undefined for none. */
|
|
84
|
+
fail(carrier: RecoveryCarrier | undefined, errorMessage: string): {
|
|
85
|
+
readonly messageId: string;
|
|
86
|
+
} | undefined;
|
|
87
|
+
/** Joins an owned append to its message; returns that message's id, the `messageId` of the join member it made. */
|
|
80
88
|
append(transition: Extract<TranscriptTransition, {
|
|
81
89
|
kind: "append";
|
|
82
|
-
}>):
|
|
90
|
+
}>): string | undefined;
|
|
83
91
|
private join;
|
|
84
92
|
/** Ended and joined: its rows stay paged; its end marker folds into its join, its entry stays claimed. */
|
|
85
93
|
private settle;
|
|
@@ -34,7 +34,10 @@ export class CompactSettledMessages {
|
|
|
34
34
|
return carrier !== undefined && carrier.epoch === this.epoch;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Returns
|
|
37
|
+
* Returns the accepted end (undefined when refused): whether it joined the message to its durable entry, the only end
|
|
38
|
+
* that settles persistence, and the message's settled ids (RULING-120): its `messageId` and the end's accepted sequence as
|
|
39
|
+
* `terminalSequence`, which a pending or failed terminal pages as `message-<terminalSequence>` and a joined message
|
|
40
|
+
* pages only through its `join-<n>` member's `messageId` (an already-joined end writes no terminal row). `call`:
|
|
38
41
|
* the projection of the outstanding call this toolResult settles, stored with the terminal (Q2). The terminal is
|
|
39
42
|
* serialized from `record`, so a record over the protocol depth limit is refused typed before anything changes
|
|
40
43
|
* (RULING-59 C); the lane runs that check, and counts the refusal, before it calls this.
|
|
@@ -43,7 +46,8 @@ export class CompactSettledMessages {
|
|
|
43
46
|
assertProtocolDepth(record);
|
|
44
47
|
if (!this.valid(carrier))
|
|
45
48
|
return this.reject("unknown_message");
|
|
46
|
-
const
|
|
49
|
+
const identity = { messageId: carrier.messageId, terminalSequence: sequence };
|
|
50
|
+
const id = identity.messageId;
|
|
47
51
|
if (this.keys.has(`end:${id}`) || this.keys.has(`settled:${id}`))
|
|
48
52
|
return this.reject("duplicate_end");
|
|
49
53
|
const owned = this.keys.get(`owner:${id}`);
|
|
@@ -58,7 +62,7 @@ export class CompactSettledMessages {
|
|
|
58
62
|
}
|
|
59
63
|
if (this.keys.has(`owner:${id}`)) {
|
|
60
64
|
this.settle(id);
|
|
61
|
-
return joined;
|
|
65
|
+
return { joined, identity };
|
|
62
66
|
}
|
|
63
67
|
// The call projection is owned first: a refusal there leaves no payload leaf and no end marker behind.
|
|
64
68
|
const projection = call ? this.rows.values.own(call) : null;
|
|
@@ -80,7 +84,7 @@ export class CompactSettledMessages {
|
|
|
80
84
|
kind: "terminal",
|
|
81
85
|
messageId: id,
|
|
82
86
|
turnId,
|
|
83
|
-
terminalSequence:
|
|
87
|
+
terminalSequence: identity.terminalSequence,
|
|
84
88
|
role,
|
|
85
89
|
runtimeItemKey: call?.runtimeItemKey ?? null,
|
|
86
90
|
entryId: null,
|
|
@@ -95,26 +99,30 @@ export class CompactSettledMessages {
|
|
|
95
99
|
this.store.adjust(projection.owner, -1, 0);
|
|
96
100
|
if (!["user", "assistant", "toolResult", "custom"].includes(role))
|
|
97
101
|
this.diagnose("unsupported_role");
|
|
98
|
-
return false;
|
|
102
|
+
return { joined: false, identity };
|
|
99
103
|
}
|
|
100
104
|
reject(reason) {
|
|
101
105
|
this.diagnose(reason);
|
|
102
|
-
return
|
|
106
|
+
return undefined;
|
|
103
107
|
}
|
|
108
|
+
/** Marks the message's pending terminal failed; returns the `messageId` that row carries, undefined for none. */
|
|
104
109
|
fail(carrier, errorMessage) {
|
|
105
110
|
if (!this.valid(carrier) || !this.keys.has(`terminal:${carrier.messageId}`))
|
|
106
|
-
return this.
|
|
111
|
+
return this.reject("unknown_message");
|
|
107
112
|
this.rows.patch(`terminal:${carrier.messageId}`, { status: "failed", error: { errorMessage } });
|
|
113
|
+
return { messageId: carrier.messageId };
|
|
108
114
|
}
|
|
115
|
+
/** Joins an owned append to its message; returns that message's id, the `messageId` of the join member it made. */
|
|
109
116
|
append(transition) {
|
|
110
117
|
const id = transition.owningMessageId;
|
|
111
118
|
if (id === undefined)
|
|
112
|
-
return;
|
|
119
|
+
return undefined;
|
|
113
120
|
if (transition.recoveryEpoch !== this.epoch)
|
|
114
|
-
return this.
|
|
121
|
+
return this.reject("retired_owner");
|
|
115
122
|
if (this.keys.has(`owner:${id}`) || this.keys.has(`settled:${id}`) || this.keys.has(`entry:${transition.id}`))
|
|
116
|
-
return this.
|
|
123
|
+
return this.reject("conflicting_owner");
|
|
117
124
|
this.join(id, transition.id);
|
|
125
|
+
return id;
|
|
118
126
|
}
|
|
119
127
|
join(messageId, entryId) {
|
|
120
128
|
const owner = { kind: "owner", messageId, entryId, ordinal: this.joins++ };
|
|
@@ -339,7 +339,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
339
339
|
const pendingExtensionRequests = new SessionExtensionUiRequests(uiRequestChanged);
|
|
340
340
|
const grantRequests = new Map();
|
|
341
341
|
sessionDialogRequests.set(session, pendingExtensionRequests);
|
|
342
|
-
const questions = new ConnectionQuestionBridge(output, uiRequestChanged);
|
|
342
|
+
const questions = new ConnectionQuestionBridge(output, uiRequestChanged, () => options.clientInfo?.connectionId());
|
|
343
343
|
let secretHandshake = false;
|
|
344
344
|
const secretOwners = new Map();
|
|
345
345
|
const secretCapable = (connection) => connectionRoles.has(connection, INTERACTIVE_USER_CAPABILITY) &&
|
|
@@ -8,7 +8,8 @@ export declare class ConnectionQuestionBridge {
|
|
|
8
8
|
private readonly resolved;
|
|
9
9
|
private readonly output;
|
|
10
10
|
private readonly changed;
|
|
11
|
-
|
|
11
|
+
private readonly raiser;
|
|
12
|
+
constructor(output: (record: object) => void, changed?: (change?: UiRequestChange) => void, raiser?: () => string | undefined);
|
|
12
13
|
pendingQuestions(): RpcQuestionUiRequest[];
|
|
13
14
|
ask(request: QuestionRequest, opts?: Options): Promise<QuestionResponse>;
|
|
14
15
|
respond(response: RpcExtensionUIResponse, beforeAnswer?: () => void): boolean | "question_incomplete" | "question_already_resolved";
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { settledQuestionStatus, unansweredQuestionIds } from "./extension-ui-response.js";
|
|
2
2
|
export const sessionQuestionBridges = new WeakMap();
|
|
3
3
|
export class ConnectionQuestionBridge {
|
|
4
|
-
constructor(output, changed) {
|
|
4
|
+
constructor(output, changed, raiser = () => undefined) {
|
|
5
5
|
this.pending = new Map();
|
|
6
6
|
this.resolved = new Set();
|
|
7
7
|
this.output = output;
|
|
8
8
|
this.changed = changed;
|
|
9
|
+
this.raiser = raiser;
|
|
9
10
|
}
|
|
10
11
|
pendingQuestions() {
|
|
11
12
|
return [...this.pending.values()].map(({ frame }) => ({
|
|
@@ -25,6 +26,7 @@ export class ConnectionQuestionBridge {
|
|
|
25
26
|
toolCallId: request.requestId,
|
|
26
27
|
waitForAnswer: request.waitForAnswer,
|
|
27
28
|
questions: request.questions,
|
|
29
|
+
raisedBy: this.raiser() ?? "agent",
|
|
28
30
|
timeout,
|
|
29
31
|
askedAtMs,
|
|
30
32
|
deadlineAtMs: askedAtMs + timeout,
|
|
@@ -42,6 +42,15 @@ export interface PrepareSocketPathHooks {
|
|
|
42
42
|
/** Replaces the listener probe, so a test can pin each verdict. */
|
|
43
43
|
readonly listenerState?: (socketPath: string) => Promise<ListenerState>;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Frees `socketPath` for this process's bind, removing only a dead socket entry and refusing everything else
|
|
47
|
+
* (#5314). No portable fd-based check exists for a socket entry: `open()` fails on one (ENXIO on Linux,
|
|
48
|
+
* EOPNOTSUPP on macOS), `O_PATH` is Linux-only, and there is no unlink-by-fd. So every read is `lstat`, which
|
|
49
|
+
* never follows a link, and `unlink(2)` removes the name, never a link's target. The entry is re-stamped before
|
|
50
|
+
* the second reachability probe (which must not be steered through a swapped-in link) and again as the last step
|
|
51
|
+
* before the unlink; reachability is re-checked after the decision because a socket bound earlier can start
|
|
52
|
+
* listening without changing identity.
|
|
53
|
+
*/
|
|
45
54
|
export declare function prepareSocketPath(socketPath: string, hooks?: PrepareSocketPathHooks): Promise<void>;
|
|
46
55
|
export declare function listen(server: Server, socketPath: string, secret?: Uint8Array): Promise<void>;
|
|
47
56
|
export declare function closeServer(server: Server): Promise<void>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* adoption of the public name, and the readiness probes of the private hop. Split out of
|
|
4
4
|
* `host-lifecycle.ts`, which keeps the supervisor's orchestration (senpi#2566).
|
|
5
5
|
*/
|
|
6
|
-
import { chmod,
|
|
6
|
+
import { chmod, lstat, mkdir, unlink } from "node:fs/promises";
|
|
7
7
|
import { createConnection, createServer } from "node:net";
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
9
|
import { proxyWithAdmission } from "./host-admission-proxy.js";
|
|
@@ -166,7 +166,7 @@ async function canConnect(socketPath, secret, internalKey, probeMs) {
|
|
|
166
166
|
}
|
|
167
167
|
async function stampOf(socketPath) {
|
|
168
168
|
try {
|
|
169
|
-
const entry = await
|
|
169
|
+
const entry = await lstat(socketPath, { bigint: true });
|
|
170
170
|
return { dev: entry.dev, ino: entry.ino, ctimeNs: entry.ctimeNs, socket: entry.isSocket() };
|
|
171
171
|
}
|
|
172
172
|
catch (cause) {
|
|
@@ -176,6 +176,15 @@ async function stampOf(socketPath) {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
const sameStamp = (a, b) => a.dev === b.dev && a.ino === b.ino && a.ctimeNs === b.ctimeNs;
|
|
179
|
+
/**
|
|
180
|
+
* Frees `socketPath` for this process's bind, removing only a dead socket entry and refusing everything else
|
|
181
|
+
* (#5314). No portable fd-based check exists for a socket entry: `open()` fails on one (ENXIO on Linux,
|
|
182
|
+
* EOPNOTSUPP on macOS), `O_PATH` is Linux-only, and there is no unlink-by-fd. So every read is `lstat`, which
|
|
183
|
+
* never follows a link, and `unlink(2)` removes the name, never a link's target. The entry is re-stamped before
|
|
184
|
+
* the second reachability probe (which must not be steered through a swapped-in link) and again as the last step
|
|
185
|
+
* before the unlink; reachability is re-checked after the decision because a socket bound earlier can start
|
|
186
|
+
* listening without changing identity.
|
|
187
|
+
*/
|
|
179
188
|
export async function prepareSocketPath(socketPath, hooks = {}) {
|
|
180
189
|
if (process.platform === "win32" || socketPath.startsWith("\0"))
|
|
181
190
|
return;
|
|
@@ -192,18 +201,31 @@ export async function prepareSocketPath(socketPath, hooks = {}) {
|
|
|
192
201
|
// Linux refuses a connect to a regular file too; never remove what is not a socket.
|
|
193
202
|
if (!probed.socket)
|
|
194
203
|
throw inUse("the entry is not a socket");
|
|
195
|
-
const
|
|
204
|
+
const probe = hooks.listenerState ?? socketEntryListenerState;
|
|
205
|
+
const state = await probe(socketPath);
|
|
196
206
|
if (state.verdict === "gone")
|
|
197
207
|
return;
|
|
198
208
|
if (state.verdict === "live")
|
|
199
209
|
throw inUse(state.evidence);
|
|
200
210
|
await hooks.afterStaleProbe?.();
|
|
201
211
|
// Something may have replaced the entry since the probe; unlink only the entry probed dead.
|
|
202
|
-
const
|
|
203
|
-
|
|
212
|
+
const unchanged = async () => {
|
|
213
|
+
const current = await stampOf(socketPath);
|
|
214
|
+
if (current === undefined)
|
|
215
|
+
return false;
|
|
216
|
+
if (!sameStamp(probed, current))
|
|
217
|
+
throw inUse("the entry changed after it was probed");
|
|
218
|
+
return true;
|
|
219
|
+
};
|
|
220
|
+
if (!(await unchanged()))
|
|
221
|
+
return;
|
|
222
|
+
const again = await probe(socketPath);
|
|
223
|
+
if (again.verdict === "gone")
|
|
224
|
+
return;
|
|
225
|
+
if (again.verdict === "live")
|
|
226
|
+
throw inUse(again.evidence);
|
|
227
|
+
if (!(await unchanged()))
|
|
204
228
|
return;
|
|
205
|
-
if (!sameStamp(probed, current))
|
|
206
|
-
throw inUse("the entry changed after it was probed");
|
|
207
229
|
try {
|
|
208
230
|
await unlink(socketPath);
|
|
209
231
|
}
|
|
@@ -23,6 +23,8 @@ export type RpcQuestionUiRequest = {
|
|
|
23
23
|
toolCallId: string;
|
|
24
24
|
waitForAnswer: boolean;
|
|
25
25
|
questions: RpcQuestionSpec[];
|
|
26
|
+
/** The connection whose command raised the question, or `"agent"` when no connection's command was dispatching. */
|
|
27
|
+
raisedBy?: string;
|
|
26
28
|
timeout: number;
|
|
27
29
|
askedAtMs: number;
|
|
28
30
|
deadlineAtMs: number;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { RecoveryCarrier } from "../../core/session-recovery-identity.ts";
|
|
2
2
|
import type { TranscriptTransition } from "../../core/session-transcript-journal.ts";
|
|
3
|
-
import type {
|
|
3
|
+
import type { CallFold } from "./compact-call-fold.ts";
|
|
4
4
|
import type { CompactLaneCurrent } from "./compact-lane-current.ts";
|
|
5
5
|
import type { CanonicalUnit } from "./compact-lane-transitions.ts";
|
|
6
|
+
import type { LiveIdentity } from "./compact-message-identity.ts";
|
|
6
7
|
import type { CompactSettledMessages } from "./compact-settled-messages.ts";
|
|
7
8
|
/** The compact components one lane epoch feeds: its settled terminals and its current state. */
|
|
8
9
|
export interface CompactComponents {
|
|
@@ -16,14 +17,23 @@ export interface AcceptedRecord {
|
|
|
16
17
|
readonly canonical: CanonicalUnit | undefined;
|
|
17
18
|
readonly sequence: number;
|
|
18
19
|
readonly turnId: string | null;
|
|
19
|
-
/**
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The call fold of a call record: the projection of the outstanding call a toolResult terminal settles (RULING-13 Q2),
|
|
22
|
+
* stored with it, and the call an execution or hook record changed (RULING-123).
|
|
23
|
+
*/
|
|
24
|
+
readonly call?: CallFold | undefined;
|
|
21
25
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
26
|
+
/**
|
|
27
|
+
* One accepted transcript transition: its owned join, then the cold flow and persistence it carries. Returns the
|
|
28
|
+
* message an append joined, the id its `join-<n>` member records.
|
|
29
|
+
*/
|
|
30
|
+
export declare function foldCompactTransition({ settled, current }: CompactComponents, transition: TranscriptTransition, transcriptAvailable: boolean, epoch: string): string | undefined;
|
|
24
31
|
/**
|
|
25
32
|
* The compact side of one accepted record: canonical and component folds, its terminal, its persistence. The call
|
|
26
|
-
* records fold before the lane commits this sequence (RULING-13 Q9), so they are not here.
|
|
33
|
+
* records fold before the lane commits this sequence (RULING-13 Q9), so they are not here. Returns the identity the
|
|
34
|
+
* settled store recorded for this record's message (RULING-120), or the call a live call record belongs to: the one its
|
|
35
|
+
* fold changed, or for a `toolcall_*` update the call owning the streamed block once it folded (RULING-123); undefined
|
|
36
|
+
* when there is none.
|
|
27
37
|
*/
|
|
28
|
-
export declare function foldCompactRecord({ settled, current }: CompactComponents, { record, type, carrier, canonical, sequence, turnId, call }: AcceptedRecord):
|
|
38
|
+
export declare function foldCompactRecord({ settled, current }: CompactComponents, { record, type, carrier, canonical, sequence, turnId, call }: AcceptedRecord): LiveIdentity | undefined;
|
|
29
39
|
//# sourceMappingURL=session-acceptance-compact.d.ts.map
|
|
@@ -1,25 +1,37 @@
|
|
|
1
|
+
import { toolcallIndex } from "./compact-call-identity.js";
|
|
2
|
+
import { callIdentity } from "./compact-call-model.js";
|
|
1
3
|
import { COMPONENT_RECORDS } from "./compact-current-components.js";
|
|
2
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* One accepted transcript transition: its owned join, then the cold flow and persistence it carries. Returns the
|
|
6
|
+
* message an append joined, the id its `join-<n>` member records.
|
|
7
|
+
*/
|
|
3
8
|
export function foldCompactTransition({ settled, current }, transition, transcriptAvailable, epoch) {
|
|
4
|
-
|
|
5
|
-
settled.append(transition);
|
|
9
|
+
const owner = transition.kind === "append" && transcriptAvailable ? settled.append(transition) : undefined;
|
|
6
10
|
current.transcript(transition, settled, epoch);
|
|
11
|
+
return owner;
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
9
14
|
* The compact side of one accepted record: canonical and component folds, its terminal, its persistence. The call
|
|
10
|
-
* records fold before the lane commits this sequence (RULING-13 Q9), so they are not here.
|
|
15
|
+
* records fold before the lane commits this sequence (RULING-13 Q9), so they are not here. Returns the identity the
|
|
16
|
+
* settled store recorded for this record's message (RULING-120), or the call a live call record belongs to: the one its
|
|
17
|
+
* fold changed, or for a `toolcall_*` update the call owning the streamed block once it folded (RULING-123); undefined
|
|
18
|
+
* when there is none.
|
|
11
19
|
*/
|
|
12
20
|
export function foldCompactRecord({ settled, current }, { record, type, carrier, canonical, sequence, turnId, call }) {
|
|
13
21
|
if (canonical)
|
|
14
22
|
current.fold(canonical, sequence, turnId);
|
|
15
23
|
if (COMPONENT_RECORDS.has(type))
|
|
16
24
|
current.foldComponent(record, sequence);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
settled.fail(carrier, String(Reflect.get(record, "errorMessage")))
|
|
25
|
+
const ended = type === "message_end" ? settled.end(record, carrier, sequence, turnId, call?.projection) : undefined;
|
|
26
|
+
const failed = type === "transcript_write_failed"
|
|
27
|
+
? settled.fail(carrier, String(Reflect.get(record, "errorMessage")))
|
|
28
|
+
: undefined;
|
|
20
29
|
// A failed write, or a terminal whose committed entry the lane accepted, settles its compact persistence too.
|
|
21
|
-
const status = type === "transcript_write_failed" ? "failed" : joined ? "appended" : undefined;
|
|
30
|
+
const status = type === "transcript_write_failed" ? "failed" : ended?.joined ? "appended" : undefined;
|
|
22
31
|
if (status && settled.valid(carrier))
|
|
23
32
|
current.status(carrier.messageId, status);
|
|
33
|
+
const block = toolcallIndex(record);
|
|
34
|
+
const streamed = block !== undefined && carrier ? callIdentity(current.callOwning(carrier.messageId, block)) : undefined;
|
|
35
|
+
return ended?.identity ?? failed ?? call?.identity ?? streamed;
|
|
24
36
|
}
|
|
25
37
|
//# sourceMappingURL=session-acceptance-compact.js.map
|
|
@@ -2,6 +2,7 @@ import type { RecoveryCarrier } from "../../core/session-recovery-identity.ts";
|
|
|
2
2
|
import type { TranscriptTransition } from "../../core/session-transcript-journal.ts";
|
|
3
3
|
import { type TranscriptRoot } from "./accepted-transcript.ts";
|
|
4
4
|
import type { CanonicalUnit } from "./compact-lane-transitions.ts";
|
|
5
|
+
import { MessageIdentities } from "./compact-message-identity.ts";
|
|
5
6
|
import { type SettledDiagnostic, type SettledRoot } from "./compact-settled-messages.ts";
|
|
6
7
|
import type { FrozenCompactState } from "./compact-state-view.ts";
|
|
7
8
|
import { type CompactContentStoreOptions } from "./compact-content-store.ts";
|
|
@@ -59,6 +60,8 @@ export declare class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
59
60
|
private turnId;
|
|
60
61
|
private correlation;
|
|
61
62
|
private refusedCorrelations;
|
|
63
|
+
/** RULING-120 (#5331): the settled ids of the record being delivered, read at `acceptedSequence`. */
|
|
64
|
+
readonly identities: MessageIdentities;
|
|
62
65
|
private retiredAs;
|
|
63
66
|
private storageRetired;
|
|
64
67
|
private nonce;
|
|
@@ -119,6 +122,8 @@ export declare class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
119
122
|
allocatedIndexNodes: number;
|
|
120
123
|
residentPages: number;
|
|
121
124
|
peakResidentPages: number;
|
|
125
|
+
ownerJoinsEvicted: number;
|
|
126
|
+
ownerJoinsOverflowed: number;
|
|
122
127
|
residentSettledKeys: number;
|
|
123
128
|
refusedRecords: number;
|
|
124
129
|
transcript: import("./compact-transfer-staging.ts").StagingStatus;
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { AcceptedTranscript } from "./accepted-transcript.js";
|
|
5
|
+
import { MessageIdentities } from "./compact-message-identity.js";
|
|
5
6
|
import { CompactSettledMessages } from "./compact-settled-messages.js";
|
|
6
7
|
import { CompactContentStore } from "./compact-content-store.js";
|
|
7
8
|
import { TransferStaging } from "./compact-transfer-staging.js";
|
|
@@ -26,6 +27,8 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
26
27
|
this.turnId = null;
|
|
27
28
|
this.correlation = null;
|
|
28
29
|
this.refusedCorrelations = 0;
|
|
30
|
+
/** RULING-120 (#5331): the settled ids of the record being delivered, read at `acceptedSequence`. */
|
|
31
|
+
this.identities = new MessageIdentities();
|
|
29
32
|
this.storageRetired = false;
|
|
30
33
|
this.nonce = randomUUID();
|
|
31
34
|
this.cleanup = Promise.resolve();
|
|
@@ -67,6 +70,7 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
67
70
|
return;
|
|
68
71
|
if (this.settled)
|
|
69
72
|
this.retireSettled();
|
|
73
|
+
this.identities.reset();
|
|
70
74
|
this.nonce = nonce;
|
|
71
75
|
if (input.epochSignal)
|
|
72
76
|
Buffer.from(nonce, "ascii").copy(Buffer.from(input.epochSignal));
|
|
@@ -98,7 +102,8 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
98
102
|
for (const transition of transitions) {
|
|
99
103
|
this.sequence += 1;
|
|
100
104
|
this.transcript.apply(transition);
|
|
101
|
-
foldCompactTransition(this.compact(), transition, this.transcript.available, this.nonce);
|
|
105
|
+
const owner = foldCompactTransition(this.compact(), transition, this.transcript.available, this.nonce);
|
|
106
|
+
this.identities.joined(transition, owner);
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
/**
|
|
@@ -109,6 +114,7 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
109
114
|
*/
|
|
110
115
|
accept(record, carrier, canonical, canonicalRefused = false) {
|
|
111
116
|
const type = field(record, "type");
|
|
117
|
+
this.identities.clear();
|
|
112
118
|
if (this.retired || this.epoch === "" || typeof type !== "string" || !SEQUENCED.has(type))
|
|
113
119
|
return undefined;
|
|
114
120
|
this.beforeAccept?.(record);
|
|
@@ -132,16 +138,9 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
132
138
|
const messageId = type.startsWith("message_") && this.settled.valid(carrier) ? carrier.messageId : null;
|
|
133
139
|
if (type === "message_start")
|
|
134
140
|
this.activeMessageId = messageId;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
type,
|
|
139
|
-
carrier,
|
|
140
|
-
canonical,
|
|
141
|
-
sequence: this.sequence,
|
|
142
|
-
turnId,
|
|
143
|
-
call: call?.projection,
|
|
144
|
-
});
|
|
141
|
+
const unit = { record, type, carrier, canonical, sequence: this.sequence, turnId, call };
|
|
142
|
+
// An unfolded record is still delivered, so its `entry_appended` takes its entry's join (RULING-122).
|
|
143
|
+
this.identities.hold(type, this.sequence, record, folds ? foldCompactRecord(this.compact(), unit) : undefined);
|
|
145
144
|
if (type === "message_end" && this.activeMessageId === messageId)
|
|
146
145
|
this.activeMessageId = null;
|
|
147
146
|
return {
|
|
@@ -203,6 +202,7 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
203
202
|
return {
|
|
204
203
|
...this.settled.storageStatus(),
|
|
205
204
|
refusedRecords: this.current.refusedRecords,
|
|
205
|
+
...this.identities.status(),
|
|
206
206
|
transcript: this.staging.status(),
|
|
207
207
|
};
|
|
208
208
|
}
|
|
@@ -227,6 +227,7 @@ export class SessionAcceptanceLane extends AcceptedRootReads {
|
|
|
227
227
|
*/
|
|
228
228
|
retire(reason, keep = new Set()) {
|
|
229
229
|
this.retiredAs ??= reason;
|
|
230
|
+
this.identities.reset();
|
|
230
231
|
this.generations.close();
|
|
231
232
|
this.transcript = new AcceptedTranscript(this.staging);
|
|
232
233
|
for (const root of this.live)
|
|
@@ -20,7 +20,8 @@ export declare abstract class SessionEventWriterDestinations extends SessionEven
|
|
|
20
20
|
* holds this lane-owned session, becomes an `eventLease` envelope at the record's accepted sequence, its content
|
|
21
21
|
* staged from the line another destination built or else streamed from the record (never one legacy line); every
|
|
22
22
|
* other destination gets the legacy line. Past the peer's lease bound, or when the record cannot be staged, the peer
|
|
23
|
-
* gets a typed refusal and its connection closes (fail closed, like a plain peer's overflow).
|
|
23
|
+
* gets a typed refusal and its connection closes (fail closed, like a plain peer's overflow). A stamped copy is
|
|
24
|
+
* built once per delivery (`copies`), so every compact peer leases the same staged text.
|
|
24
25
|
*/
|
|
25
26
|
private eventLine;
|
|
26
27
|
}
|