@code-yeongyu/senpi 2026.10.10-4 → 2026.10.10-6
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 +46 -0
- package/dist/bundle/chunks/{anthropic-messages-OE3QBX4P.js → anthropic-messages-DUULMQBF.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-N3L2LNP7.js → app-server-command-GJ5NXQSY.js} +1 -1
- package/dist/bundle/chunks/{chunk-3A3C5VVO.js → chunk-3OGEJMZ7.js} +1 -1
- package/dist/bundle/chunks/{chunk-J5MUMVWB.js → chunk-64ZVBCTL.js} +8 -7
- package/dist/bundle/chunks/{chunk-MP2DMTI7.js → chunk-C7LEZMWS.js} +124 -124
- package/dist/bundle/chunks/{chunk-ZSYRWQWG.js → chunk-JMI5OCIJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-ODYQK5XJ.js → chunk-TALS4X7S.js} +1 -1
- package/dist/bundle/chunks/{chunk-7GVD54RM.js → chunk-U3IL5N76.js} +1 -1
- package/dist/bundle/chunks/{chunk-H67EVDWG.js → chunk-URQSNRHZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-H4UIUFU5.js → chunk-VGYIPYOK.js} +1 -1
- package/dist/bundle/chunks/{chunk-M2IHGL3M.js → chunk-VW74BGIC.js} +1 -1
- package/dist/bundle/chunks/{chunk-4TK7VCZZ.js → chunk-WKMUAG23.js} +1 -1
- package/dist/bundle/chunks/chunk-YG5UO3E4.js +7 -0
- package/dist/bundle/chunks/chunk-YV2XQFXG.js +14 -0
- package/dist/bundle/chunks/{chunk-Z7MMKKIY.js → chunk-ZYHIRFGT.js} +1 -1
- package/dist/bundle/chunks/{cli-main-CHDR33FZ.js → cli-main-SZ54SQLA.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-EHRZEBRO.js → help-fast-path-W5W3FYYM.js} +1 -1
- package/dist/bundle/chunks/{host-command-VATE7HRG.js → host-command-YG2FIDPN.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-GC3GIR2N.js → host-lifecycle-5AV4JXDQ.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-RVEM62WX.js → interactive-mode-SCNDQ4R6.js} +1 -1
- package/dist/bundle/chunks/{list-tips-IIRFXBTO.js → list-tips-BXAA4FG5.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-CFND7CXH.js → multi-session-host-ZDZMPNP5.js} +7 -6
- package/dist/bundle/chunks/{package-manager-cli-NWNECBF7.js → package-manager-cli-4LMYHHKH.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-C5BM7TMM.js → rotation-stream-E3XKSST7.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-JQFUDLNB.js → rpc-mode-ZFNFREN5.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-EQ4PQCRP.js → schedule-command-K6CTAB4T.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-HMB5T5VD.js → session-control-endpoint-MVSYBGM3.js} +1 -1
- package/dist/bundle/chunks/{session-picker-O7OESJWU.js → session-picker-TNXWY5QY.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +224 -222
- package/dist/bundle/cli.js +1 -1
- 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/agent-session-runtime.d.ts +6 -0
- package/dist/core/agent-session-runtime.js +18 -0
- package/dist/core/agent-session.js +5 -2
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/options.js +11 -2
- package/dist/core/extensions/builtin/anthropic-subscription/session-reattach.js +4 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-sync.js +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/settings.d.ts +5 -0
- package/dist/core/extensions/builtin/anthropic-subscription/settings.js +11 -0
- package/dist/core/extensions/builtin/anthropic-subscription/stream.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/stream.js +17 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +20 -12
- package/dist/core/extensions/builtin/compaction/lane-policy.js +39 -34
- package/dist/core/extensions/builtin/html-render/bootstrap.d.ts +15 -0
- package/dist/core/extensions/builtin/html-render/bootstrap.js +123 -0
- package/dist/core/extensions/builtin/html-render/images.d.ts +16 -0
- package/dist/core/extensions/builtin/html-render/images.js +155 -0
- package/dist/core/extensions/builtin/html-render/index.d.ts +3 -0
- package/dist/core/extensions/builtin/html-render/index.js +5 -0
- package/dist/core/extensions/builtin/html-render/tool.d.ts +19 -0
- package/dist/core/extensions/builtin/html-render/tool.js +74 -0
- package/dist/core/extensions/builtin/index.js +4 -0
- package/dist/core/extensions/builtin/mcp/auth/catalog-identity.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +41 -0
- package/dist/core/extensions/builtin/mcp/auth/legacy-lock.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/auth/legacy-lock.js +39 -0
- package/dist/core/extensions/builtin/mcp/auth/legacy-migration.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/auth/legacy-migration.js +91 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +1 -1
- package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/auth/token-store.js +19 -90
- package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +7 -3
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +61 -12
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
- package/dist/core/extensions/builtin/mcp/connection-types.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/expose/call.d.ts +12 -0
- package/dist/core/extensions/builtin/mcp/expose/call.js +60 -0
- package/dist/core/extensions/builtin/mcp/expose/proxy.js +16 -3
- package/dist/core/extensions/builtin/mcp/expose/register.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/expose/register.js +41 -50
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/expose/session.js +5 -1
- package/dist/core/extensions/builtin/mcp/expose/tier-b.js +15 -4
- package/dist/core/extensions/builtin/mcp/index.js +5 -26
- package/dist/core/extensions/builtin/mcp/invocation.d.ts +42 -0
- package/dist/core/extensions/builtin/mcp/invocation.js +118 -0
- package/dist/core/extensions/builtin/mcp/service-connection.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/service-connection.js +6 -0
- package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/service-register.js +45 -1
- package/dist/core/extensions/builtin/mcp/service-tools-changed.js +26 -3
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +6 -0
- package/dist/core/extensions/builtin/mcp/service.js +83 -49
- package/dist/core/extensions/builtin/mcp/shared-connection.js +17 -4
- package/dist/core/extensions/builtin/mcp/sharing-policy.js +1 -0
- package/dist/core/extensions/builtin/mcp/startup-race.js +47 -9
- package/dist/core/extensions/builtin/permission-system/dispatch-metadata.d.ts +8 -0
- package/dist/core/extensions/builtin/permission-system/dispatch-metadata.js +23 -0
- package/dist/core/extensions/builtin/permission-system/dispatch-policy.d.ts +14 -0
- package/dist/core/extensions/builtin/permission-system/dispatch-policy.js +53 -0
- package/dist/core/extensions/builtin/permission-system/dispatch.d.ts +33 -0
- package/dist/core/extensions/builtin/permission-system/dispatch.js +106 -0
- package/dist/core/extensions/builtin/permission-system/index.js +64 -17
- package/dist/core/extensions/builtin/permission-system/prompt.d.ts +1 -1
- package/dist/core/extensions/builtin/permission-system/prompt.js +3 -1
- package/dist/core/extensions/builtin/permission-system/service.d.ts +11 -1
- package/dist/core/extensions/builtin/permission-system/service.js +14 -4
- package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.d.ts +1 -0
- package/dist/core/extensions/builtin/ttsr/detectors/collapse-near-duplicates.js +24 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/modes/interactive/interactive-mode.js +21 -4
- package/dist/modes/rpc/host-reservations.d.ts +1 -1
- package/dist/modes/rpc/host-reservations.js +9 -3
- package/dist/modes/rpc/rpc-client.js +14 -2
- package/dist/modes/rpc/rpc-request-deadline.d.ts +8 -0
- package/dist/modes/rpc/rpc-request-deadline.js +10 -0
- package/dist/modes/rpc/rpc-types.d.ts +7 -4
- package/dist/modes/rpc/session-binding.js +15 -1
- package/dist/modes/rpc/session-command-router.d.ts +1 -0
- package/dist/modes/rpc/session-command-router.js +16 -0
- package/dist/modes/rpc/session-registry-attach.js +8 -2
- package/dist/modes/rpc/session-registry.js +5 -7
- package/dist/modes/rpc/session-teardown.d.ts +6 -1
- package/dist/modes/rpc/session-teardown.js +26 -7
- package/dist/modes/rpc/session-worker-client.d.ts +2 -0
- package/dist/modes/rpc/session-worker-client.js +6 -0
- package/dist/modes/rpc/session-worker-protocol.d.ts +4 -0
- package/dist/modes/rpc/session-worker.js +6 -0
- package/dist/modes/rpc/worker-session-registry.js +6 -1
- package/docs/providers.md +3 -2
- package/docs/rpc.md +26 -10
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-RADHRQ4K.js +0 -7
- package/dist/bundle/chunks/chunk-USRAHAW4.js +0 -14
|
@@ -11,7 +11,7 @@ import { attachToOpenSession } from "./session-registry-attach.js";
|
|
|
11
11
|
import { settleClosingReservation, syncRuntimeMetadata } from "./session-registry-claims.js";
|
|
12
12
|
import { createEntrySwitchSession } from "./session-registry-switch.js";
|
|
13
13
|
import { frozenProfile, RpcSessionRegistryError, sessionIdentity, } from "./session-registry-types.js";
|
|
14
|
-
import { beginSessionClose, closeMarkedSession, closeSession } from "./session-teardown.js";
|
|
14
|
+
import { beginSessionClose, closeMarkedSession, closeSession, releaseWithinGrace, } from "./session-teardown.js";
|
|
15
15
|
export { frozenProfile, RpcSessionRegistryError, sessionIdentity, } from "./session-registry-types.js";
|
|
16
16
|
/** Process-local lifecycle owner for multi-session RPC runtimes. */
|
|
17
17
|
export class RpcSessionRegistry {
|
|
@@ -41,9 +41,9 @@ export class RpcSessionRegistry {
|
|
|
41
41
|
this.options.onSizeChange?.(this.entries.size);
|
|
42
42
|
return deleted;
|
|
43
43
|
},
|
|
44
|
-
releaseReservation: (key) => {
|
|
44
|
+
releaseReservation: async (key) => {
|
|
45
|
+
await this.options.pathReservations?.release(key);
|
|
45
46
|
this.reservations.delete(key);
|
|
46
|
-
this.options.pathReservations?.release(key);
|
|
47
47
|
},
|
|
48
48
|
markDetached: (key) => this.options.pathReservations?.setAttached(key, false),
|
|
49
49
|
now: () => this.now(),
|
|
@@ -174,10 +174,8 @@ export class RpcSessionRegistry {
|
|
|
174
174
|
finally {
|
|
175
175
|
this.entries.delete(handle);
|
|
176
176
|
this.options.onSizeChange?.(this.entries.size);
|
|
177
|
-
if (sessionPath)
|
|
178
|
-
this.
|
|
179
|
-
this.options.pathReservations?.release(sessionPath);
|
|
180
|
-
}
|
|
177
|
+
if (sessionPath)
|
|
178
|
+
await releaseWithinGrace(this.teardownHost, handle, sessionPath);
|
|
181
179
|
}
|
|
182
180
|
}
|
|
183
181
|
if (error instanceof RpcSessionRegistryError)
|
|
@@ -3,7 +3,7 @@ export interface SessionTeardownHost {
|
|
|
3
3
|
readonly closeGraceMs: number;
|
|
4
4
|
get(handle: string): RpcSessionEntry | undefined;
|
|
5
5
|
delete(handle: string): void;
|
|
6
|
-
releaseReservation(key: string): void
|
|
6
|
+
releaseReservation(key: string): Promise<void>;
|
|
7
7
|
/** Publishes that this path is retained with no client attached, for the cross-generation claim. */
|
|
8
8
|
markDetached(key: string): void;
|
|
9
9
|
/** The registry's clock, so the detach stamp shares the sweep's timeline. */
|
|
@@ -13,6 +13,11 @@ export interface SessionTeardownHost {
|
|
|
13
13
|
export declare function beginSessionClose(host: SessionTeardownHost, handle: string, onRole?: (finalizer: boolean) => void, options?: {
|
|
14
14
|
detach?: boolean;
|
|
15
15
|
}): RpcSessionEntry;
|
|
16
|
+
/**
|
|
17
|
+
* Close waits for the claim file to be gone, but a removal that never settles (a wedged mount)
|
|
18
|
+
* must not hold the close past the grace window: the close completes and the failure is reported.
|
|
19
|
+
*/
|
|
20
|
+
export declare function releaseWithinGrace(host: Pick<SessionTeardownHost, "closeGraceMs" | "releaseReservation">, handle: string, key: string): Promise<void>;
|
|
16
21
|
export declare function closeSession(host: SessionTeardownHost, handle: string): Promise<void>;
|
|
17
22
|
export declare function closeMarkedSession(host: SessionTeardownHost, handle: string): Promise<void>;
|
|
18
23
|
//# sourceMappingURL=session-teardown.d.ts.map
|
|
@@ -40,6 +40,25 @@ export function beginSessionClose(host, handle, onRole, options) {
|
|
|
40
40
|
});
|
|
41
41
|
return entry;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Close waits for the claim file to be gone, but a removal that never settles (a wedged mount)
|
|
45
|
+
* must not hold the close past the grace window: the close completes and the failure is reported.
|
|
46
|
+
*/
|
|
47
|
+
export async function releaseWithinGrace(host, handle, key) {
|
|
48
|
+
let timer;
|
|
49
|
+
const late = new Promise((resolve) => {
|
|
50
|
+
timer = setTimeout(() => resolve("late"), host.closeGraceMs);
|
|
51
|
+
});
|
|
52
|
+
const removal = host.releaseReservation(key).then(() => "removed", (cause) => {
|
|
53
|
+
reportDetachedFailure(handle, cause);
|
|
54
|
+
return "failed";
|
|
55
|
+
});
|
|
56
|
+
const outcome = await Promise.race([removal, late]);
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
if (outcome === "late") {
|
|
59
|
+
reportDetachedFailure(handle, new Error(`session path reservation was not removed within ${host.closeGraceMs} ms; closing anyway`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
43
62
|
export function closeSession(host, handle) {
|
|
44
63
|
const entry = beginSessionClose(host, handle);
|
|
45
64
|
if (entry.state !== "closing")
|
|
@@ -80,16 +99,16 @@ export function closeMarkedSession(host, handle) {
|
|
|
80
99
|
await disposeOnce().catch(() => undefined);
|
|
81
100
|
await entry.scope.close?.();
|
|
82
101
|
};
|
|
83
|
-
const release = () => {
|
|
102
|
+
const release = async () => {
|
|
84
103
|
if (released)
|
|
85
104
|
return;
|
|
86
105
|
released = true;
|
|
87
106
|
if (releaseTimer)
|
|
88
107
|
clearTimeout(releaseTimer);
|
|
108
|
+
if (entry.reservationKey)
|
|
109
|
+
await releaseWithinGrace(host, handle, entry.reservationKey);
|
|
89
110
|
entry.state = "closed";
|
|
90
111
|
host.delete(handle);
|
|
91
|
-
if (entry.reservationKey)
|
|
92
|
-
host.releaseReservation(entry.reservationKey);
|
|
93
112
|
};
|
|
94
113
|
const graceful = (async () => {
|
|
95
114
|
await previousLifecycle;
|
|
@@ -119,22 +138,22 @@ export function closeMarkedSession(host, handle) {
|
|
|
119
138
|
}
|
|
120
139
|
})();
|
|
121
140
|
let settled = false;
|
|
122
|
-
const finish = () => {
|
|
141
|
+
const finish = async () => {
|
|
123
142
|
if (settled)
|
|
124
143
|
return;
|
|
125
144
|
settled = true;
|
|
126
|
-
release();
|
|
145
|
+
await release();
|
|
127
146
|
entry.closeResolve?.();
|
|
128
147
|
};
|
|
129
148
|
releaseTimer = setTimeout(() => {
|
|
130
149
|
void disposeOnce().catch((cause) => reportDetachedFailure(handle, cause));
|
|
131
150
|
void closeScopeOnce().catch((cause) => reportDetachedFailure(handle, cause));
|
|
132
151
|
void graceful.catch((cause) => reportDetachedFailure(handle, cause));
|
|
133
|
-
finish();
|
|
152
|
+
void finish();
|
|
134
153
|
}, host.closeGraceMs);
|
|
135
154
|
void graceful.then(finish, (cause) => {
|
|
136
155
|
reportDetachedFailure(handle, cause);
|
|
137
|
-
finish();
|
|
156
|
+
return finish();
|
|
138
157
|
});
|
|
139
158
|
return completion;
|
|
140
159
|
}
|
|
@@ -40,6 +40,8 @@ export declare class SessionWorkerClient {
|
|
|
40
40
|
setPromptSurface(surface: PromptSurface): Promise<void>;
|
|
41
41
|
/** Moves the worker's live session to another browser engine (a later `open_session.browserEngine`). */
|
|
42
42
|
setBrowserEngine(engine: BrowserEngine): Promise<void>;
|
|
43
|
+
/** Moves the worker's live session to another permission preset (a later `open_session.permissionPreset`). */
|
|
44
|
+
setPermissionPreset(preset: string): Promise<void>;
|
|
43
45
|
commit(): Promise<WorkerSnapshot>;
|
|
44
46
|
bind(sessionId: string, writer: SessionEventWriter, requestClose: () => void, options: Pick<RpcConnectionOptions, "capabilities" | "clientInfo">): Promise<RpcSessionBinding>;
|
|
45
47
|
get busy(): boolean;
|
|
@@ -60,6 +60,12 @@ export class SessionWorkerClient {
|
|
|
60
60
|
if (result.type !== "result")
|
|
61
61
|
throw new Error("Invalid worker browser_engine response");
|
|
62
62
|
}
|
|
63
|
+
/** Moves the worker's live session to another permission preset (a later `open_session.permissionPreset`). */
|
|
64
|
+
async setPermissionPreset(preset) {
|
|
65
|
+
const result = await this.request({ type: "permission_preset", preset });
|
|
66
|
+
if (result.type !== "result")
|
|
67
|
+
throw new Error("Invalid worker permission_preset response");
|
|
68
|
+
}
|
|
63
69
|
async commit() {
|
|
64
70
|
const result = await this.request({ type: "commit" });
|
|
65
71
|
if (result.type !== "ready")
|
|
@@ -183,6 +183,12 @@ async function handle(message) {
|
|
|
183
183
|
entry.runtime.setBrowserEngine(message.engine);
|
|
184
184
|
send({ type: "result", request: message.request });
|
|
185
185
|
return;
|
|
186
|
+
case "permission_preset":
|
|
187
|
+
if (!entry?.runtime)
|
|
188
|
+
throw new Error("session_closing");
|
|
189
|
+
entry.runtime.setPermissionPreset(message.preset);
|
|
190
|
+
send({ type: "result", request: message.request });
|
|
191
|
+
return;
|
|
186
192
|
case "cancel_ui":
|
|
187
193
|
binding?.cancelPendingExtensionUiRequests?.();
|
|
188
194
|
send({ type: "control_done", control: "cancel_ui" });
|
|
@@ -233,7 +233,7 @@ export class WorkerSessionRegistry {
|
|
|
233
233
|
return undefined;
|
|
234
234
|
}
|
|
235
235
|
async attach(owner, path, options, requested = { cwd: "" }) {
|
|
236
|
-
const { promptSurface, browserEngine } = requested;
|
|
236
|
+
const { promptSurface, browserEngine, permissionPreset } = requested;
|
|
237
237
|
const entry = this.entries.get(owner);
|
|
238
238
|
if (entry?.state !== "open" || !entry.worker?.bindingReady || entry.worker.snapshot?.sessionPath !== path)
|
|
239
239
|
throw new RpcSessionRegistryError("session_path_in_use");
|
|
@@ -246,6 +246,11 @@ export class WorkerSessionRegistry {
|
|
|
246
246
|
entry.profile = frozenProfile({ ...entry.profile, browserEngine });
|
|
247
247
|
await entry.worker.setBrowserEngine(browserEngine);
|
|
248
248
|
}
|
|
249
|
+
// Sent even when the record already names it: only the worker sees its live session (senpi#2842).
|
|
250
|
+
if (permissionPreset !== undefined) {
|
|
251
|
+
entry.profile = frozenProfile({ ...entry.profile, permissionPreset });
|
|
252
|
+
await entry.worker.setPermissionPreset(permissionPreset);
|
|
253
|
+
}
|
|
249
254
|
const result = this.openResult(owner, entry);
|
|
250
255
|
entry.attachments++;
|
|
251
256
|
// Retention is a property of the live session: any attach may ask for it, and
|
package/docs/providers.md
CHANGED
|
@@ -54,7 +54,7 @@ The `anthropic-subscription` provider routes LLM calls through the official [Cla
|
|
|
54
54
|
- Settings (`anthropicSubscriptionProvider`):
|
|
55
55
|
- `systemPromptMode` — controls how the system prompt is delivered. **`full`** (default) sends senpi's composed system prompt verbatim; the lane no longer rebuilds from the SDK `claude_code` preset, so all prompt regions (project rules, response-language instructions, etc.) reach the model. **`preset-append`** is the previous behaviour (deprecated, kept for one release; emits a one-time warning). **`override`** loads the system prompt from a file (`systemPromptFile`). The legacy `appendSystemPrompt` key still works: `false` → `preset-append`, `true`/unset → `full`; setting both keys makes `systemPromptMode` win and warns.
|
|
56
56
|
- In `full` and `override` modes, `settingSources` defaults to `[]` on every lane because senpi's prompt already carries project context — loading the SDK's own CLAUDE.md would double-inject it. The CLI always prepends its own `"You are a Claude agent, built on Anthropic's Claude Agent SDK."` block, which senpi cannot suppress; `full` means the prompt is delivered intact, not that it is the only system-prompt text.
|
|
57
|
-
- `settingSources` (filesystem settings load only in the ambient lane, so they cannot override your selected account), `strictMcpConfig`, `pinnedAccount`, `tokenInjection` (`oauth-slots` | `config-dir` | `ambient`), `resumeMode` (`auto` default | `off` restores per-turn sessions), `systemPromptFile`, `enabled` (default `false`; gates the ambient lane only).
|
|
57
|
+
- `settingSources` (filesystem settings load only in the ambient lane, so they cannot override your selected account), `strictMcpConfig`, `pinnedAccount`, `tokenInjection` (`oauth-slots` | `config-dir` | `ambient`), `resumeMode` (`auto` default | `off` restores per-turn sessions), `compactionOwner` (`senpi` default | `sdk` hands compaction to Claude Code's native auto-compact), `systemPromptFile`, `enabled` (default `false`; gates the ambient lane only).
|
|
58
58
|
- **Environment overrides** (precedence: `env > project settings > global settings > default`; no new CLI flags):
|
|
59
59
|
|
|
60
60
|
| Variable | Purpose |
|
|
@@ -66,9 +66,10 @@ The `anthropic-subscription` provider routes LLM calls through the official [Cla
|
|
|
66
66
|
| `SENPI_CLAUDE_SDK_OAUTH_TOKEN_INJECTION` | `oauth-slots` \| `config-dir` \| `ambient` |
|
|
67
67
|
| `SENPI_CLAUDE_SDK_OAUTH_SETTING_SOURCES` | Overrides `settingSources` |
|
|
68
68
|
| `SENPI_CLAUDE_SDK_OAUTH_PINNED_ACCOUNT` | Overrides `pinnedAccount` |
|
|
69
|
+
| `SENPI_CLAUDE_SDK_OAUTH_COMPACTION_OWNER` | Overrides `compactionOwner` (`senpi` \| `sdk`) |
|
|
69
70
|
|
|
70
71
|
Every `SENPI_*` variable is stripped from the Claude Code subprocess environment on all three lanes; other inherited variables are preserved.
|
|
71
|
-
- **
|
|
72
|
+
- **Compaction.** senpi owns compaction on this lane by default: speculative and idle compaction, restoration, degradation recovery and per-model thresholds apply, the summary is written by the session's own model unless `compaction.model` is set, and an accepted compaction cold-seeds a fresh resident session from the compacted branch. So that only one side compacts, the lane pins the SDK's session-scoped `autoCompactEnabled: false` (overriding the user's global Claude Code preference) and sets `CLAUDE_CODE_TOTAL_TOKENS_REMINDER=off` for the Claude Code subprocess, whose per-turn token reminder otherwise defeats prompt-cache reuse; at the hard limit Claude Code rejects the turn and senpi's overflow recovery compacts and retries it. If that compaction fails, the turn ends with the error instead of Claude Code compacting natively. Set `compactionOwner: "sdk"` (or `SENPI_CLAUDE_SDK_OAUTH_COMPACTION_OWNER=sdk`) to let the resident Claude Code session compact instead: the lane pins `autoCompactEnabled: true`, does not set `autoCompactWindow`, and senpi stands down its automatic compaction (manual `/compact` stays senpi's). The owner is fixed into the resident Claude Code process when it starts, so changing it mid-session restarts that process on the next turn. Under either owner senpi never rewrites a message the resident session already received, so its per-turn context reduction stays off on this lane. `resumeMode: "off"` restores per-turn sessions, where senpi always owns compaction.
|
|
72
73
|
- **Session reuse.** By default one long-lived SDK query spans the entire senpi session instead of a fresh one per turn, so conversations continue with only the new delta sent. An accepted senpi compaction rewrites the transcript and forks to a fresh resident session; a rejected compaction leaves continuity unchanged. Reuse also fails closed on branch/fork navigation, account failover, an aborted turn, or any configuration change. Idle sessions retire after 30 minutes (max 32 resident); a session with an in-flight turn is never evicted. After a senpi process restart the lane always starts fresh. Set `resumeMode: "off"` (or `SENPI_CLAUDE_SDK_OAUTH_RESUME=off`) to restore the old per-turn behaviour. Accepted values are `"auto"` (default) and `"off"`; any other value is silently ignored.
|
|
73
74
|
- Account state is exposed to desktop/automation clients: RPC `get_provider_accounts`, `account_pin`, `account_remove` and the `auth_accounts_changed` / `account_failover` events, mirrored through the app-server protocol. Token material is never included.
|
|
74
75
|
|
package/docs/rpc.md
CHANGED
|
@@ -870,7 +870,8 @@ session's prompt for the named surface, so one host serves a terminal client and
|
|
|
870
870
|
uses the host process's `SENPI_PROMPT_SURFACE` (`app` or `chat` selects that surface; unset or any other value is `terminal`),
|
|
871
871
|
which remains the only switch for a single-process `--mode rpc` client. A later `open_session` that attaches to a live
|
|
872
872
|
session with another `promptSurface` rebuilds that session's prompt; an attach without the field keeps the current
|
|
873
|
-
surface, and `new_session` / `switch_session` / `fork` inside the session keep it too
|
|
873
|
+
surface, and `new_session` / `switch_session` / `fork` inside the session keep it too, also when the attach lands while
|
|
874
|
+
one of them is still building the replacement. Probe `prompt_surface` in
|
|
874
875
|
`get_protocol_info` before sending the field; an older host ignores it. Probe `prompt_surface_chat` before sending
|
|
875
876
|
`chat`: a host without it refuses `chat` with `invalid_launch_profile`, so a gateway falls back to `app`. Any value other
|
|
876
877
|
than `terminal`, `app` or `chat` is refused with `invalid_launch_profile`.
|
|
@@ -900,9 +901,10 @@ subprocesses (the bash tool and everything it spawns) and its eval kernels, incl
|
|
|
900
901
|
variable, even when the host process itself exports one (a daemon never inherits it from the process that ensured it).
|
|
901
902
|
`BSK_HOME` and `BSK_BIN` are per install, not per session: they pass from the host environment to every session
|
|
902
903
|
unchanged. A later `open_session` that attaches to a live session with another `browserEngine` moves that session to
|
|
903
|
-
it, and an attach without the field keeps the current engine; `new_session` / `switch_session` / `fork` keep it too
|
|
904
|
-
|
|
905
|
-
|
|
904
|
+
it, and an attach without the field keeps the current engine; `new_session` / `switch_session` / `fork` keep it too,
|
|
905
|
+
also when the attach lands while one of them is still building the replacement. Probe `browser_engine` in
|
|
906
|
+
`get_protocol_info` before sending the field; the capability is advertised only because the value reaches every
|
|
907
|
+
consumer above. Any other value is refused with `invalid_launch_profile`.
|
|
906
908
|
|
|
907
909
|
A skill that must tell the client what the browser is doing publishes it as that session's own event: an extension
|
|
908
910
|
registers a tool that calls `pi.rpc.emit(name, data)`, an eval cell reaches it as `tool.<name>(...)`, and the host adds
|
|
@@ -1306,6 +1308,12 @@ REPORT: nothing here aborts a turn, kills a session, or refuses an `open_session
|
|
|
1306
1308
|
- **Event-loop stalls**: a 200 ms timer measures how late it is actually invoked; that lateness is the time the loop
|
|
1307
1309
|
could serve nobody. Drift above `SENPI_RPC_LOOP_LAG_WARN_MS` (default 500) writes one stderr line per 10 seconds,
|
|
1308
1310
|
`senpi rpc host stall: event loop blocked <drift>ms (sessionId=<handle> tool=<tool>)`. Drift above
|
|
1311
|
+
`SENPI_RPC_LOOP_LAG_ERROR_MS` (default 5000) additionally broadcasts a `host_stalled` record
|
|
1312
|
+
(`{ type, driftMs, sessionId?, tool?, processCpuMs?, heapDeltaMb? }`) to every connection, like the other content-free lifecycle records.
|
|
1313
|
+
`processCpuMs` is the process CPU time spent during the stalled window and `heapDeltaMb` the JS heap change across it, so a
|
|
1314
|
+
stall explains itself: CPU close to `driftMs` means the host was busy (a large heap drop in the same window points at a
|
|
1315
|
+
collection), and CPU close to zero means the process did not run at all (the machine starved it, or it sat in a blocking
|
|
1316
|
+
wait). The stderr line carries the same two numbers as `cpu=<ms> heap=<+/-MB>`.
|
|
1309
1317
|
- On accepting an `open_session`, the host sends that connection a `queued` record
|
|
1310
1318
|
(`{ type, for_request, position, in_flight }`) before the open enters the session loop.
|
|
1311
1319
|
`position` is 1-based and `in_flight` counts opens already accepted across the whole host,
|
|
@@ -1315,12 +1323,12 @@ REPORT: nothing here aborts a turn, kills a session, or refuses an `open_session
|
|
|
1315
1323
|
acknowledged open waits up to 10 minutes for its response instead of the 30 s request
|
|
1316
1324
|
deadline (a busy host was measured answering after 57 s), and a timeout after it names the
|
|
1317
1325
|
queue position instead of a bare deadline. A lost transport still rejects at once.
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
`
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1326
|
+
- A multi-session host sends the same `queued` record for a session-routed `prompt` the moment it receives it,
|
|
1327
|
+
before the prompt's preflight runs on the session's loop (`position` counts that session's requests in flight).
|
|
1328
|
+
The bundled `RpcClient` then waits up to 5 minutes for the prompt to be accepted instead of the 30 s request
|
|
1329
|
+
deadline (a compaction's longer wait is never shortened by it), and a timeout after it says the host received the prompt. A prompt the host never acknowledged still
|
|
1330
|
+
fails at the request deadline. A reply written for a session that already closed is dropped, with one stderr
|
|
1331
|
+
line naming the session.
|
|
1324
1332
|
- **Stall attribution**: each routed command is dispatched inside an `AsyncLocalStorage` scope carrying its routing
|
|
1325
1333
|
`sessionId`, and an in-process session's tool executions open a span carrying `{ sessionId, tool }` for as long as
|
|
1326
1334
|
the tool runs. A stall is blamed on the synchronous work that finished inside the measured window, or on the tool
|
|
@@ -1486,6 +1494,14 @@ capability must use `ask`, never `workspace`, for an edit-only approval promise.
|
|
|
1486
1494
|
Hosts that support `permissionPreset: "auto"` advertise `permission_preset_auto`;
|
|
1487
1495
|
clients without it must not send `auto`.
|
|
1488
1496
|
Explicit permission rules and remembered approvals retain their existing precedence, except under `auto`: there settings and CLI rules can only narrow the preset (the more restrictive decision wins), and an "Always" answer (saved in `.senpi/permissions-approved.jsonl`, from this or an earlier session) still allows its pattern.
|
|
1497
|
+
An `open_session` that attaches to a live session with another `permissionPreset` moves that session to it: the next
|
|
1498
|
+
tool call is decided under the new preset, in either direction, and `new_session` / `switch_session` / `fork` keep it,
|
|
1499
|
+
also when the attach lands while one of them or a `reload` (or two overlapping reloads) is still rebuilding the
|
|
1500
|
+
session. An attach that names a preset is applied even when the session was opened or last attached with the same one.
|
|
1501
|
+
An attach without the field keeps the current preset. An attach accepts exactly the values `open_session` accepts and
|
|
1502
|
+
treats them the same way: an unknown preset name is applied like one given to `open_session`, so the session's next
|
|
1503
|
+
tool call is refused with `Permission setup failed: Invalid --permission-preset "<name>". ...` until a later attach
|
|
1504
|
+
names a valid preset.
|
|
1489
1505
|
|
|
1490
1506
|
| Command | Params | Success data | Notes |
|
|
1491
1507
|
| --- | --- | --- | --- |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.10.10-
|
|
3
|
+
"version": "2026.10.10-6",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@earendil-works/chord": "0.99.1",
|
|
60
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.10-
|
|
61
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.10-
|
|
62
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.10-
|
|
60
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.10-6",
|
|
61
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.10-6",
|
|
62
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.10-6",
|
|
63
63
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
64
64
|
"chalk": "6.0.0",
|
|
65
65
|
"cross-spawn": "7.0.6",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"yaml": "2.9.1",
|
|
79
79
|
"@anthropic-ai/claude-agent-sdk": "0.3.289",
|
|
80
80
|
"@anthropic-ai/sdk": "0.127.0",
|
|
81
|
-
"@code-yeongyu/senpi-codemode": "2026.10.10-
|
|
82
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.10-
|
|
81
|
+
"@code-yeongyu/senpi-codemode": "2026.10.10-6",
|
|
82
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.10-6",
|
|
83
83
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
84
84
|
"@mozilla/readability": "0.6.0",
|
|
85
85
|
"linkedom": "0.18.13",
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{getAgentDir}from"./chunk-463OK2Y4.js";import{existsSync,realpathSync}from"node:fs";import{basename,dirname,join,resolve}from"node:path";function canonicalSessionPath(path){let absolutePath=resolve(path),missing=[],existing=absolutePath;for(;!existsSync(existing);){let parent=dirname(existing);if(parent===existing)return absolutePath;missing.unshift(basename(existing)),existing=parent}try{return join(realpathSync(existing),...missing)}catch{return absolutePath}}function sessionDirectoryRemoved(sessionFile){return!existsSync(dirname(sessionFile))}import{createHash,randomUUID}from"node:crypto";import{chmod,link,mkdir,readFile,rename,rm,writeFile}from"node:fs/promises";import{basename as basename2,dirname as dirname2,join as join2,win32}from"node:path";var HOST_DAEMON_LAYOUT=2,HOST_DAEMON_DIR_ENV="SENPI_RPC_HOST_DAEMON_DIR",ENDPOINT_REGISTRY_VERSION=1,DIRECTORY_MODE=448,HOST_STATE_FILE_MODE=384;function canonicalEndpointPath(socket,platform=process.platform){return platform==="win32"?win32.normalize(socket).toLowerCase():socket.startsWith("\0")?socket:join2(canonicalSessionPath(dirname2(socket)),basename2(socket))}function daemonDirectoryName(socket,platform=process.platform){return directoryNameOf(canonicalEndpointPath(socket,platform))}function sameEndpoint(socket,other){return socket===other||canonicalEndpointPath(socket)===canonicalEndpointPath(other)}function socketNamesDirectory(socket,name){return daemonDirectoryName(socket)===name||process.platform!=="win32"&&directoryNameOf(socket)===name}function directoryNameOf(endpoint){return createHash("sha256").update(endpoint,"utf8").digest("hex").slice(0,16)}function shardKey(kind,ownerId){return createHash("sha256").update(`${kind}:${ownerId}`,"utf8").digest("hex").slice(0,16)}function shardSocketPathForKey(root,kind,key){return join2(root,`${kind}-${key}.sock`)}function shardSocketPath(root,kind,ownerId){return shardSocketPathForKey(root,kind,shardKey(kind,ownerId))}var SHARD_SOCKET_NAME=/^(p|i)-([0-9a-f]{16})\.sock$/;function parseShardSocket(socket){let match=SHARD_SOCKET_NAME.exec(basename2(socket)),key=match?.[2];return key===void 0?null:{kind:match?.[1]==="i"?"i":"p",key}}function createHostDaemonPaths(target){let flatDir=join2(target.agentDir??getAgentDir(),"rpc-host-daemon");return{socket:target.socket,flatDir,layoutMarker:join2(flatDir,"layout.json"),legacyPidFile:join2(flatDir,"host.pid"),...hostDaemonDirectoryPaths(join2(flatDir,daemonDirectoryName(target.socket)))}}function hostDaemonDirectoryPaths(dir){return{dir,endpointFile:join2(dir,"endpoint.json"),pointerFile:join2(dir,"host.pid"),lockFile:join2(dir,"daemon.lock"),settingsFile:join2(dir,"settings.json"),stderrLog:join2(dir,"stderr.log"),generationsDir:join2(dir,"generations"),reservationsDir:join2(dir,"reservations")}}function generationPaths(paths,instanceId){let dir=join2(paths.generationsDir,instanceId);return{dir,relativeDir:`generations/${instanceId}`,pidFile:join2(dir,"host.pid"),settingsFile:join2(dir,"settings.json"),scratchDir:join2(dir,"scratch")}}var HostDaemonStateError=class extends Error{constructor(path,cause){super(`RPC daemon state directory ${path} is not usable: ${cause instanceof Error?cause.message:cause}`,{cause}),this.name="HostDaemonStateError",this.path=path}};async function createDaemonDirectories(paths,identity={}){try{await mkdir(paths.flatDir,{recursive:!0,mode:DIRECTORY_MODE}),await makePrivateDirectory(paths.dir),await Promise.all([makePrivateDirectory(paths.generationsDir),makePrivateDirectory(paths.reservationsDir),writeFile(paths.layoutMarker,`${JSON.stringify({layout:HOST_DAEMON_LAYOUT,dir:basename2(paths.dir)})}
|
|
3
|
-
`,{mode:HOST_STATE_FILE_MODE})])}catch(cause){throw new HostDaemonStateError(paths.dir,cause)}await ensureEndpointIdentity(paths,paths.socket,identity)}async function makePrivateDirectory(directory){await mkdir(directory,{recursive:!0,mode:DIRECTORY_MODE})===void 0&&await chmod(directory,DIRECTORY_MODE)}async function ensureEndpointIdentity(paths,socket,options={}){let temporary=`${paths.endpointFile}.${process.pid}-${randomUUID()}.tmp`;try{await mkdir(paths.dir,{recursive:!0,mode:DIRECTORY_MODE});let record=`${JSON.stringify({layout:HOST_DAEMON_LAYOUT,registry_version:ENDPOINT_REGISTRY_VERSION,endpoint_kind:options.kind??"rpc_host",socket,created_at:new Date().toISOString()})}
|
|
4
|
-
`;await writeFile(temporary,record,{mode:HOST_STATE_FILE_MODE,flag:"wx"}),!await link(temporary,paths.endpointFile).then(()=>!0,cause=>isErrorCode(cause,"EEXIST")?!1:createExclusively(paths.endpointFile,record))&&options.repair===!0&&!await namesThisDirectory(paths)&&await rename(temporary,paths.endpointFile)}catch(cause){throw new HostDaemonStateError(paths.endpointFile,cause)}finally{await rm(temporary,{force:!0})}}async function createExclusively(path,content){try{return await writeFile(path,content,{mode:HOST_STATE_FILE_MODE,flag:"wx"}),!0}catch(cause){if(isErrorCode(cause,"EEXIST"))return!1;throw cause}}function isErrorCode(cause,code){return cause instanceof Error&&"code"in cause&&cause.code===code}async function namesThisDirectory(paths){let record;try{record=JSON.parse(await readFile(paths.endpointFile,"utf8"))}catch{return!1}return typeof record!="object"||record===null||!("socket"in record)?!1:typeof record.socket=="string"&&record.socket!==""&&socketNamesDirectory(record.socket,basename2(paths.dir))}async function createGenerationDirectory(generation){try{await mkdir(generation.scratchDir,{recursive:!0,mode:DIRECTORY_MODE}),await chmod(generation.dir,DIRECTORY_MODE)}catch(cause){throw new HostDaemonStateError(generation.dir,cause)}}import{execFile}from"node:child_process";function parseDaemonPidFile(text){let parsed;try{parsed=JSON.parse(text)}catch(error){if(error instanceof SyntaxError)return;throw error}let unguarded=parsed!==null&&isRecord(parsed)&&parsed.processStartTime===null;if(!(!isRecord(parsed)||typeof parsed.pid!="number"||!unguarded&&typeof parsed.processStartTime!="string")&&!(!Number.isInteger(parsed.pid)||parsed.pid<=0)){if(unguarded)return{pid:parsed.pid,processStartTime:null};if(!(typeof parsed.processStartTime!="string"||parsed.processStartTime.trim()===""))return{pid:parsed.pid,processStartTime:parsed.processStartTime}}}var ProcessIdentityUnreadableError=class extends Error{constructor(pid,attempts,cause){super(`process identity for live pid ${pid} stayed unreadable after ${attempts} probe attempt(s): ${cause instanceof Error?cause.message:String(cause)}`),this.name="ProcessIdentityUnreadableError",this.pid=pid,this.attempts=attempts,this.cause=cause}};async function processMatchesPidFile(pidFile,readStartTime=readProcessStartTime,isLive=processIsLive,retry={}){if(pidFile.processStartTime===null){if(!isLive(pidFile.pid))return!1;throw new ProcessIdentityUnreadableError(pidFile.pid,0,new Error("pidfile carries no process identity guard"))}let attempts=Math.max(1,retry.attempts??5),delayMs=retry.delayMs??200,lastError;for(let attempt=1;attempt<=attempts;attempt++)try{let current=await readStartTime(pidFile.pid);if(current!==void 0)return current===pidFile.processStartTime;if(!isLive(pidFile.pid))return!1;lastError=new Error("process identity probe returned no identity for a live process"),attempt<attempts&&await new Promise(resolve2=>setTimeout(resolve2,delayMs))}catch(error){if(!isLive(pidFile.pid))return!1;lastError=error,attempt<attempts&&await new Promise(resolve2=>setTimeout(resolve2,delayMs))}throw new ProcessIdentityUnreadableError(pidFile.pid,attempts,lastError)}async function readProcessIdentity(pid,platform=process.platform,timeoutMs,isLive=processIsLive){let command=platform==="win32"?{executable:"powershell.exe",args:["-NoProfile","-NonInteractive","-Command",`$process = Get-CimInstance Win32_Process -Filter "ProcessId=${String(pid)}" -ErrorAction Stop; if ($null -eq $process) { Write-Output '__SENPI_ABSENT__'; exit 0 }; $process.CreationDate.ToFileTimeUtc().ToString("D", [Globalization.CultureInfo]::InvariantCulture)`]}:{executable:"ps",args:["-o","lstart=","-p",String(pid)]};return new Promise(resolve2=>{let effectiveTimeoutMs=timeoutMs??(platform==="win32"?1e3:void 0);execFile(command.executable,command.args,{windowsHide:!0,...effectiveTimeoutMs===void 0?{}:{timeout:effectiveTimeoutMs}},(error,stdout)=>{if(error){let code="code"in error?error.code:void 0;if(platform!=="win32"&&code===1||!isLive(pid)){resolve2({kind:"absent"});return}resolve2({kind:"error",error});return}let output=stdout.trim();if(output==="__SENPI_ABSENT__")return resolve2({kind:"absent"});if(!output||platform==="win32"&&!/^\d+$/.test(output))return resolve2({kind:"error",error:new Error("invalid process identity output")});resolve2({kind:"present",identity:output})}).once("error",error=>resolve2({kind:"error",error}))})}async function stopValidatedPid(pidFile,signal){if(await processMatchesPidFile(pidFile)){try{process.kill(pidFile.pid,signal)}catch(error){if(!isNodeErrorCode(error,"ESRCH"))throw error}signal==="SIGTERM"&&await waitForGone(pidFile,1e4),signal==="SIGKILL"&&await waitForGone(pidFile,2e3)}}async function waitForGone(pidFile,timeoutMs){let deadline=Date.now()+timeoutMs;for(;Date.now()<=deadline;){if(!await processMatchesPidFile(pidFile))return!0;await delay(100)}return!await processMatchesPidFile(pidFile)}async function readProcessStartTime(pid,platform=process.platform,timeoutMs){return readProcessIdentity(pid,platform,timeoutMs).then(result=>{if(result.kind==="error")throw result.error;return result.kind==="present"?result.identity:void 0})}function processIsLive(pid){try{return process.kill(pid,0),!0}catch(error){if(isNodeErrorCode(error,"ESRCH"))return!1;if(isNodeErrorCode(error,"EPERM"))return!0;throw error}}async function waitForStartTime(pid,timeoutMs,readStartTime=readProcessStartTime,isLive=processIsLive){let deadline=Date.now()+timeoutMs;for(;Date.now()<=deadline;){let startTime;try{startTime=await readStartTime(pid)}catch{}if(startTime)return startTime;await delay(20)}if(!isLive(pid))throw new Error(`spawned daemon pid ${pid} had no process start time`)}function delay(ms){return new Promise(resolveDelay=>setTimeout(resolveDelay,ms))}function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function isNodeErrorCode(error,code){return error instanceof Error&&"code"in error&&error.code===code}import{createHash as createHash2}from"node:crypto";import{mkdir as mkdir2,readdir,readFile as readFile3,rename as rename2,rm as rm2,writeFile as writeFile3}from"node:fs/promises";import{join as join3}from"node:path";import{readFile as readFile2,writeFile as writeFile2}from"node:fs/promises";async function writeHostSettings(paths,settings){let generation=generationPaths(paths,settings.instanceId);await createGenerationDirectory(generation),await writeStateFile(paths.settingsFile,settings),await writeStateFile(generation.settingsFile,settings)}async function readHostSettings(paths){let parsed=parseJson(await readFileOrUndefined(paths.settingsFile));if(parsed)return{...typeof parsed.coldStart=="string"&&{coldStart:parsed.coldStart},...typeof parsed.idleExitMs=="number"&&{idleExitMs:parsed.idleExitMs}}}async function writeStateFile(path,content){try{await writeFile2(path,`${JSON.stringify(content)}
|
|
5
|
-
`,{mode:HOST_STATE_FILE_MODE})}catch(cause){throw new HostDaemonStateError(path,cause)}}async function readFileOrUndefined(path){try{return await readFile2(path,"utf8")}catch(error){if(isNodeErrorCode2(error,"ENOENT")||isNodeErrorCode2(error,"ENOTDIR"))return;throw error}}function parseJson(text){if(text!==void 0)try{let parsed=JSON.parse(text);return isRecord2(parsed)?parsed:void 0}catch{return}}function isRecord2(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function isNodeErrorCode2(error,code){return error instanceof Error&&"code"in error&&error.code===code}var SESSION_PATH_RETRY_AFTER_MS=2e3;function reservationFile(dir,sessionPath){return join3(dir,`${createHash2("sha256").update(sessionPath,"utf8").digest("hex").slice(0,16)}.json`)}function createEndpointReservations(host){let told=process.env[HOST_DAEMON_DIR_ENV],dir=told!==void 0&&told.trim()!==""?told:host.socket===void 0?void 0:createHostDaemonPaths({socket:host.socket,agentDir:host.agentDir}).dir;if(dir!==void 0)return createSessionPathReservations({daemonDir:dir,instanceId:host.instanceId,...host.onFailure?{onFailure:host.onFailure}:{}})}function createSessionPathReservations(options){let paths=hostDaemonDirectoryPaths(options.daemonDir),dir=paths.reservationsDir,pid=options.pid??process.pid,startTime=readProcessStartTime(pid).then(value=>value??null,()=>null),held=new Set,report=message=>options.onFailure?.(message),publish=async(sessionPath,attached,stillHeld)=>{let owner={instanceId:options.instanceId,pid,processStartTime:await startTime,sessionPath,attached},file=reservationFile(dir,sessionPath);await mkdir2(dir,{recursive:!0,mode:448});let staging=`${file}.${pid}.tmp`;if(await writeFile3(staging,`${JSON.stringify(owner)}
|
|
6
|
-
`,{mode:384}),stillHeld?.()===!1){await rm2(staging,{force:!0});return}await rename2(staging,file)};return{async claim(sessionPath,attached=!0){let existing=await readOwner(reservationFile(dir,sessionPath));if(existing&&existing.pid!==pid){let standing=await standingOwner(existing,paths.pointerFile);if(standing)return standing}try{await publish(sessionPath,attached),held.add(sessionPath)}catch(cause){report(`session path reservation for ${sessionPath} could not be written (${errorMessage(cause)})`)}},release(sessionPath){held.delete(sessionPath)&&rm2(reservationFile(dir,sessionPath),{force:!0}).catch(cause=>{report(`session path reservation for ${sessionPath} could not be removed (${errorMessage(cause)})`)})},setAttached(sessionPath,attached){held.has(sessionPath)&&publish(sessionPath,attached,()=>held.has(sessionPath)).catch(cause=>{report(`session path reservation for ${sessionPath} could not be updated (${errorMessage(cause)})`)})}}}async function readSessionPathClaims(dir){let entries=await readdir(dir).catch(()=>[]),claims=[];for(let entry of entries){if(!entry.endsWith(".json"))continue;let file=join3(dir,entry),owner=await readOwner(file);owner&&claims.push({file,owner})}return claims}async function standingOwner(owner,pointerFile){if(!await claimOwnerIsLive(owner))return;let current=await currentInstanceId(pointerFile)===owner.instanceId;return current||owner.attached!==!1?{...owner,current}:void 0}async function currentInstanceId(pointerFile){let pointer=parseJson(await readFileOrUndefined(pointerFile).catch(()=>{}));return typeof pointer?.instance_id=="string"?pointer.instance_id:void 0}async function readOwner(file){let parsed;try{parsed=JSON.parse(await readFile3(file,"utf8"))}catch{return}if(typeof parsed!="object"||parsed===null)return;let{instanceId,pid,processStartTime,sessionPath,attached}=parsed;if(!(typeof instanceId!="string"||typeof pid!="number"||typeof sessionPath!="string"))return{instanceId,pid,processStartTime:typeof processStartTime=="string"?processStartTime:null,sessionPath,...typeof attached=="boolean"?{attached}:{}}}async function claimOwnerIsLive(owner){if(!processIsLive(owner.pid))return!1;if(owner.processStartTime===null)return!0;let current=await readProcessStartTime(owner.pid).catch(()=>{});return current===void 0||current===owner.processStartTime}function errorMessage(cause){return cause instanceof Error?cause.message:String(cause)}import{rename as rename3,rm as rm4}from"node:fs/promises";import{readdir as readdir3,rm as rm3}from"node:fs/promises";import{readdir as readdir2}from"node:fs/promises";import{readdirSync,readFileSync}from"node:fs";function supportedPlatform(platform){return platform==="darwin"||platform==="linux"?platform:void 0}async function loadProcessTableReader(platform=process.platform,options={}){let supported=supportedPlatform(platform);return supported===void 0||typeof globalThis.Bun>"u"?void 0:supported==="darwin"?await darwinReader(options):linuxReader}var KINFO_PROC_STRIDE=648,KINFO_PID_OFFSET=40,KINFO_PPID_OFFSET=560,KINFO_STAT_OFFSET=36,SZOMB=5,PROC_PIDTASKINFO=4,TASKINFO_RESIDENT_OFFSET=8,TABLE_START_BYTES=1<<20,TABLE_MAX_BYTES=32<<20;function parseKernelProcessTable(table,rowCount,residentKb){if(rowCount<=0||table.byteLength<rowCount*KINFO_PROC_STRIDE)return;let view=new DataView(table.buffer,table.byteOffset,table.byteLength),rows=[],selfSeen=!1;for(let index=0;index<rowCount;index++){let base=index*KINFO_PROC_STRIDE,pid=view.getUint32(base+KINFO_PID_OFFSET,!0);if(!Number.isInteger(pid)||pid<=0)continue;let ppid=view.getUint32(base+KINFO_PPID_OFFSET,!0),zombie=table[base+KINFO_STAT_OFFSET]===SZOMB;if(pid===process.pid){if(ppid!==process.ppid||zombie)return;selfSeen=!0}rows.push({pid,ppid,state:zombie?"Z":"U",rssKb:residentKb(pid)})}return selfSeen?rows:void 0}async function darwinReader(options){let{dlopen,FFIType,ptr}=await import("bun:ffi"),library=dlopen("libSystem.B.dylib",{sysctl:{args:[FFIType.ptr,FFIType.u32,FFIType.ptr,FFIType.ptr,FFIType.ptr,FFIType.i32],returns:FFIType.i32},proc_pidinfo:{args:[FFIType.i32,FFIType.i32,FFIType.u32,FFIType.ptr,FFIType.i32],returns:FFIType.i32}}),mib=new Int32Array([1,14,0]),none=new Uint8Array(1),requested=options.initialTableBytes,table=new Uint8Array(Number.isFinite(requested)&&requested!==void 0&&requested>0?Math.min(Math.floor(requested),TABLE_MAX_BYTES):TABLE_START_BYTES),length=new Uint32Array(1),taskInfo=new Uint8Array(256),taskView=new DataView(taskInfo.buffer),residentKb=pid=>(taskInfo.fill(0),library.symbols.proc_pidinfo(pid,PROC_PIDTASKINFO,0,ptr(taskInfo),taskInfo.byteLength)>0?Math.round(Number(taskView.getBigUint64(TASKINFO_RESIDENT_OFFSET,!0))/1024):0);return()=>{for(;;){if(length[0]=table.byteLength,library.symbols.sysctl(ptr(mib),mib.length,ptr(table),ptr(length),ptr(none),0)!==0){if(table.byteLength>=TABLE_MAX_BYTES)return;let reported=length[0]>table.byteLength?length[0]:0;table=new Uint8Array(Math.min(TABLE_MAX_BYTES,Math.max(table.byteLength*2,reported)));continue}return parseKernelProcessTable(table,Math.floor(length[0]/KINFO_PROC_STRIDE),residentKb)}}}function linuxReader(){let rows=[];for(let entry of readdirSync("/proc")){let pid=Number(entry);if(!Number.isInteger(pid)||pid<=0)continue;let stat2;try{stat2=readFileSync(`/proc/${pid}/stat`,"utf8")}catch{continue}let tail=stat2.slice(stat2.lastIndexOf(")")+2).split(" "),state=tail[0],ppid=Number(tail[1]);if(state===void 0||!Number.isInteger(ppid))continue;let rssPages=Number(tail[21]);rows.push({pid,ppid,state,rssKb:Number.isFinite(rssPages)?Math.round(rssPages*4):0})}return rows}var UNOBSERVED={rss_mb:null,host_rss_mb:null,open_fds:null,zombies:null},readers=new Map;function tableReader(platform){let cached=readers.get(platform);if(cached!==void 0)return cached;let loading=loadProcessTableReader(platform);return readers.set(platform,loading),loading}async function readHostProcessMetrics(pid,platform=process.platform){if(platform==="win32")return UNOBSERVED;let read=await tableReader(platform);if(read===void 0)return UNOBSERVED;let rows=read();if(rows===void 0)return UNOBSERVED;let tree=descendants(rows,pid);return tree.length===0?UNOBSERVED:{rss_mb:residentMegabytes(tree),host_rss_mb:residentMegabytes(tree.filter(row=>row.pid===pid||row.ppid===pid)),open_fds:platform==="linux"?await openDescriptors(tree):null,zombies:tree.filter(row=>row.state.startsWith("Z")).length}}function residentMegabytes(rows){return Math.round(rows.reduce((total,row)=>total+row.rssKb,0)/KILOBYTES_PER_MEGABYTE)}function descendants(rows,root){let tree=rows.filter(row=>row.pid===root);if(tree.length===0)return tree;for(let index=0;index<tree.length;index++){let parent=tree[index].pid;for(let row of rows)row.ppid===parent&&!tree.includes(row)&&tree.push(row)}return tree}var KILOBYTES_PER_MEGABYTE=1024;async function openDescriptors(tree){let total=0,counted=!1;for(let row of tree){let entries=await readdir2(`/proc/${row.pid}/fd`).catch(()=>{});entries!==void 0&&(counted=!0,total+=entries.length)}return counted?total:null}async function pruneDeadGenerations(paths){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile).catch(()=>{})),pointedAt=typeof pointer?.instance_id=="string"?pointer.instance_id:void 0,generations=[];for(let instanceId of await readdir3(paths.generationsDir).catch(()=>[])){let record=parseJson(await readFileOrUndefined(generationPaths(paths,instanceId).pidFile));typeof record?.pid!="number"||processIsLive(record.pid)||(await rm3(generationPaths(paths,instanceId).dir,{recursive:!0,force:!0}).catch(()=>{}),generations.push(instanceId))}let pointerRemoved=pointedAt!==void 0&&generations.includes(pointedAt);pointerRemoved&&await rm3(paths.pointerFile,{force:!0}).catch(()=>{});let claims=[];for(let claim of await readSessionPathClaims(paths.reservationsDir))await claimOwnerIsLive(claim.owner)||(await rm3(claim.file,{force:!0}).catch(()=>{}),claims.push(claim.owner.sessionPath));return{generations,claims,pointer:pointerRemoved}}function memoryOf(metrics){return{rss_mb:metrics.rss_mb,host_rss_mb:metrics.host_rss_mb}}async function readGenerationRows(paths,options={}){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile).catch(()=>{})),currentId=typeof pointer?.instance_id=="string"?pointer.instance_id:void 0,claims=await readSessionPathClaims(paths.reservationsDir),rows=[];for(let instanceId of await readdir3(paths.generationsDir).catch(()=>[])){let record=parseJson(await readFileOrUndefined(generationPaths(paths,instanceId).pidFile));if(typeof record?.pid!="number")continue;let alive=processIsLive(record.pid);!alive&&options.includeDead!==!0||rows.push({instanceId,generation:typeof record.generation=="number"?record.generation:0,pid:record.pid,engineVersion:typeof record.engineVersion=="string"?record.engineVersion:null,...alive?memoryOf(await readHostProcessMetrics(record.pid)):{rss_mb:null,host_rss_mb:null},sessions:claims.filter(claim=>claim.owner.instanceId===instanceId).length,current:instanceId===currentId,alive})}return rows.sort((left,right)=>left.generation-right.generation)}async function readHostRegistration(paths){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile));if(!pointer||typeof pointer.instance_id!="string")return;let text=await readFileOrUndefined(generationPaths(paths,pointer.instance_id).pidFile),record=text===void 0?void 0:parseDaemonPidFile(text);if(record===void 0)return;let parsed=parseJson(text)??{},writer=parseWriter(parsed);return{record,...writer&&{writer},...typeof parsed.socket=="string"&&{socket:parsed.socket},instanceId:pointer.instance_id,generation:typeof parsed.generation=="number"?parsed.generation:0}}async function writeHostRegistration(paths,registration,options={}){options.fresh!==!0&&await pruneDeadGenerations(paths);let{generation,writer}=await writeGenerationRecord(paths,registration);await writeStateFile(`${paths.pointerFile}.${process.pid}.tmp`,{layout:HOST_DAEMON_LAYOUT,instance_id:registration.instanceId,generation_dir:generation.relativeDir,writer}),await rename3(`${paths.pointerFile}.${process.pid}.tmp`,paths.pointerFile)}async function writeGenerationRecord(paths,registration){let generation=generationPaths(paths,registration.instanceId),writer={pid:process.pid,startTime:await thisProcessStartTime()},{build}=registration;return await createGenerationDirectory(generation),await writeStateFile(generation.pidFile,{...registration.record,instance_id:registration.instanceId,generation:registration.generation,...build&&{engineVersion:build.text,engineOrdinal:build.ordinal},launchProfileId:registration.launchProfileId,socket:registration.socket,writer}),{generation,writer}}async function clearHostRegistration(paths){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile));typeof pointer?.instance_id=="string"&&await rm4(generationPaths(paths,pointer.instance_id).dir,{recursive:!0,force:!0}),await rm4(paths.settingsFile,{force:!0}),await rm4(paths.pointerFile,{force:!0})}async function releaseGeneration(paths,owner){let generation=generationPaths(paths,owner.instanceId),record=parseDaemonPidFile(await readFileOrUndefined(generation.pidFile)??"");if(record!==void 0&&record.pid!==owner.pid)return;if(owner.superseded===!0){await rm4(generation.dir,{recursive:!0,force:!0});return}let ownsPointer=parseJson(await readFileOrUndefined(paths.pointerFile))?.instance_id===owner.instanceId;ownsPointer&&await rm4(paths.settingsFile,{force:!0}),await rm4(generation.dir,{recursive:!0,force:!0}),ownsPointer&&await rm4(paths.pointerFile,{force:!0})}async function provenOwner(registered,socket){let record=registered?.record;if(!record||record.processStartTime===null||registered?.socket!==void 0&&!sameEndpoint(registered.socket,socket))return;let identity={pid:record.pid,processStartTime:record.processStartTime};return await processMatchesPidFile(identity,readProcessStartTime).catch(()=>!1)?{...identity,instanceId:registered.instanceId}:void 0}async function writtenByThisProcess(writer,readStartTime){if(writer===void 0||writer.pid!==process.pid||writer.startTime===null)return!1;let startTime=readStartTime?await readStartTime(process.pid).then(value=>value??null,()=>null):await thisProcessStartTime();return writer.startTime===startTime}var selfStartTime;function thisProcessStartTime(){return selfStartTime??=readProcessStartTime(process.pid).then(value=>value??null,()=>null),selfStartTime}function parseWriter(parsed){if(!isRecord2(parsed.writer)||typeof parsed.writer.pid!="number")return;let{pid,startTime}=parsed.writer;return{pid,startTime:typeof startTime=="string"?startTime:null}}import{createHash as createHash3,randomBytes,timingSafeEqual}from"node:crypto";import{chmod as chmod2,mkdir as mkdir3,readFile as readFile4,writeFile as writeFile4}from"node:fs/promises";import{dirname as dirname3,win32 as win322}from"node:path";var WINDOWS_PIPE_PREFIX="\\\\.\\pipe\\senpi-rpc-",SOCKET_SECRET_BYTES=32,SOCKET_SECRET_SUFFIX=".secret",SOCKET_SECRET_FILE_ENV="SENPI_RPC_SOCKET_SECRET_FILE",SOCKET_HANDSHAKE_TIMEOUT_MS=2e3;function socketSecretPath(logicalPath){if(logicalPath.toLowerCase().startsWith("\\\\.\\pipe\\"))throw new Error(`Windows RPC named-pipe addresses need a logical filesystem path for their secret: ${logicalPath}`);return`${logicalPath}${SOCKET_SECRET_SUFFIX}`}async function ensureSocketSecret(path){try{let existing=await readFile4(path);if(existing.length===SOCKET_SECRET_BYTES)return existing}catch(error){if(!isNodeErrorCode3(error,"ENOENT"))throw error}return createSocketSecret(path)}async function createSocketSecret(path){await mkdir3(dirname3(path),{recursive:!0});let secret=randomBytes(SOCKET_SECRET_BYTES);return await writeFile4(path,secret,{mode:384}),await chmod2(path,384),secret}async function readSocketSecret(path){let secret=await readFile4(path);if(secret.length!==SOCKET_SECRET_BYTES)throw new Error(`invalid RPC socket secret: ${path}`);return secret}function resolveSocketTransportAddress(socketPath,platform,secret){if(platform!=="win32"||socketPath.toLowerCase().startsWith("\\\\.\\pipe\\"))return socketPath;if(!/^[a-zA-Z]:[\\/]/.test(socketPath)&&!/^\\\\[^/]/.test(socketPath))throw new Error(`Windows RPC socket path must be drive-qualified or UNC: ${socketPath}`);let canonical=win322.normalize(socketPath).toLowerCase(),identity=Buffer.concat([Buffer.from(canonical,"utf8"),Buffer.from(secret??[])]),name=createHash3("sha256").update(identity).digest("hex").slice(0,32);return`${WINDOWS_PIPE_PREFIX}${name}`}function authenticateSocket(socket,secret,onAuthenticated){let received=Buffer.alloc(0),finish=()=>{clearTimeout(timer),socket.off("data",onData),socket.off("error",onError)},onError=()=>{finish(),socket.destroy()},timer=setTimeout(()=>{finish(),socket.destroy()},SOCKET_HANDSHAKE_TIMEOUT_MS),onData=chunk=>{if(received=Buffer.concat([received,chunk]),received.length<secret.length)return;if(finish(),!timingSafeEqual(received.subarray(0,secret.length),Buffer.from(secret))){socket.destroy();return}let remainder=received.subarray(secret.length);remainder.length>0&&socket.unshift(remainder),onAuthenticated()};socket.on("data",onData),socket.once("error",onError),socket.once("close",finish)}function sendSocketHandshake(socket,secret){socket.write(Buffer.from(secret))}function isNodeErrorCode3(error,code){return error instanceof Error&&"code"in error&&error.code===code}import{mkdir as mkdir4,readFile as readFile5,rename as rename4,stat,unlink,writeFile as writeFile5}from"node:fs/promises";import{dirname as dirname4}from"node:path";var PUBLIC_SOCKET_IDENTITY_FILE="public-socket.owner",SOCKET_IDENTITY_WAIT_MS=3e4,MAX_SOCKET_PATH_BYTES=103;function generationBindPath(publicSocket,generation){return`${publicSocket}.next-${generation}`}function sameSocketIdentity(a,b){return a.dev===b.dev&&a.ino===b.ino}async function statSocketIdentity(socketPath){try{let{dev,ino}=await stat(socketPath);return{dev,ino}}catch(cause){if(isNodeErrorCode4(cause,"ENOENT"))return;throw cause}}async function classifyEndpointOwnership(socketPath,identity){if(identity===void 0||process.platform==="win32"||socketPath.startsWith("\0"))return"unknown";let current;try{current=await statSocketIdentity(socketPath)}catch{return"unknown"}return current===void 0?"absent":sameSocketIdentity(current,identity)?"held":"replaced"}async function socketEntryReplaced(socketPath,identity){return await classifyEndpointOwnership(socketPath,identity)==="replaced"}async function writeSocketIdentityFile(path,identity){await mkdir4(dirname4(path),{recursive:!0,mode:448});let tmpPath=`${path}.${process.pid}.tmp`;await writeFile5(tmpPath,`${JSON.stringify(identity)}
|
|
7
|
-
`,{mode:384}),await rename4(tmpPath,path)}async function readSocketIdentityFile(path){let raw;try{raw=await readFile5(path,"utf8")}catch(cause){if(isNodeErrorCode4(cause,"ENOENT"))return;throw cause}let parsed;try{parsed=JSON.parse(raw)}catch{return}if(!(typeof parsed!="object"||parsed===null||typeof parsed.dev!="number"||typeof parsed.ino!="number"))return{dev:parsed.dev,ino:parsed.ino}}async function waitForSocketIdentityFile(path,timeoutMs=SOCKET_IDENTITY_WAIT_MS,intervalMs=25){let deadline=Date.now()+timeoutMs;for(;;){let identity=await readSocketIdentityFile(path);if(identity!==void 0)return identity;if(Date.now()>=deadline)return;await delayUnrefed(intervalMs)}}async function unlinkOwnedSocket(socketPath,identity,log){if(process.platform==="win32"||socketPath.startsWith("\0"))return;let current;try{current=await statSocketIdentity(socketPath)}catch(cause){log(`socket ${socketPath} ownership could not be verified (${errorMessage2(cause)}); leaving it`);return}if(current!==void 0){if(identity===void 0){log(`socket path ${socketPath} ownership unknown; leaving it`);return}if(!sameSocketIdentity(current,identity)){log(`socket path ${socketPath} now owned by another host; leaving it`);return}try{await unlink(socketPath)}catch(cause){isNodeErrorCode4(cause,"ENOENT")||log(`socket ${socketPath} removal failed (${errorMessage2(cause)})`)}}}async function shieldSocketDuringClose(socketPath,close){if(process.platform==="win32"||socketPath.startsWith("\0"))return close();let shieldPath=`${socketPath}.shield-${process.pid}`,shielded=!1;try{await statSocketIdentity(socketPath)!==void 0&&(await rename4(socketPath,shieldPath),shielded=!0)}catch{}try{await close()}finally{if(shielded)try{await rename4(shieldPath,socketPath)}catch{}}}function delayUnrefed(ms){return new Promise(resolve2=>{setTimeout(resolve2,ms).unref?.()})}function errorMessage2(cause){return cause instanceof Error?cause.message:String(cause)}function isNodeErrorCode4(cause,code){return cause instanceof Error&&"code"in cause&&cause.code===code}export{canonicalSessionPath,sessionDirectoryRemoved,HOST_DAEMON_LAYOUT,HOST_DAEMON_DIR_ENV,ENDPOINT_REGISTRY_VERSION,HOST_STATE_FILE_MODE,canonicalEndpointPath,daemonDirectoryName,sameEndpoint,socketNamesDirectory,shardKey,shardSocketPathForKey,shardSocketPath,parseShardSocket,createHostDaemonPaths,hostDaemonDirectoryPaths,generationPaths,createDaemonDirectories,ensureEndpointIdentity,writeHostSettings,readHostSettings,writeStateFile,readFileOrUndefined,parseJson,parseDaemonPidFile,ProcessIdentityUnreadableError,processMatchesPidFile,stopValidatedPid,waitForGone,readProcessStartTime,processIsLive,waitForStartTime,readHostProcessMetrics,SESSION_PATH_RETRY_AFTER_MS,createEndpointReservations,readSessionPathClaims,claimOwnerIsLive,pruneDeadGenerations,readGenerationRows,readHostRegistration,writeHostRegistration,writeGenerationRecord,clearHostRegistration,releaseGeneration,provenOwner,writtenByThisProcess,thisProcessStartTime,SOCKET_SECRET_FILE_ENV,socketSecretPath,ensureSocketSecret,createSocketSecret,readSocketSecret,resolveSocketTransportAddress,authenticateSocket,sendSocketHandshake,PUBLIC_SOCKET_IDENTITY_FILE,MAX_SOCKET_PATH_BYTES,generationBindPath,statSocketIdentity,classifyEndpointOwnership,socketEntryReplaced,writeSocketIdentityFile,readSocketIdentityFile,waitForSocketIdentityFile,unlinkOwnedSocket,shieldSocketDuringClose};
|