@code-yeongyu/senpi 2026.11.6-2 → 2026.11.6-3
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 +30 -0
- package/dist/bundle/chunks/{bun-extension-importer-RCGPQTI2.js → bun-extension-importer-GQ6IKEXD.js} +2 -2
- package/dist/bundle/chunks/{chunk-QN2BHP3M.js → chunk-44BJPS5I.js} +1 -1
- package/dist/bundle/chunks/{chunk-C7OKUC7L.js → chunk-4BATXPB7.js} +1 -1
- package/dist/bundle/chunks/{chunk-RQ6CHXQ2.js → chunk-5LKB732P.js} +1 -1
- package/dist/bundle/chunks/{chunk-X5ZVYNOL.js → chunk-6CEDL5S2.js} +1 -1
- package/dist/bundle/chunks/{chunk-HLM46XEB.js → chunk-7DRWPZ3N.js} +1 -1
- package/dist/bundle/chunks/{chunk-7PSBD5KS.js → chunk-BASS47UD.js} +1 -1
- package/dist/bundle/chunks/{chunk-ECW7VBMB.js → chunk-CFEHJ4VU.js} +1 -1
- package/dist/bundle/chunks/{chunk-TIPKAMSS.js → chunk-EO2VPZ2K.js} +1 -1
- package/dist/bundle/chunks/{chunk-LNPAT22L.js → chunk-F5JLDCYD.js} +8 -7
- package/dist/bundle/chunks/{chunk-GSKCWXXG.js → chunk-JMQOLOC2.js} +1 -1
- package/dist/bundle/chunks/chunk-L7ZIS3MC.js +106 -0
- package/dist/bundle/chunks/{chunk-47PPZKPO.js → chunk-LM6DOWQH.js} +4 -4
- package/dist/bundle/chunks/chunk-LPYBKMPT.js +2 -0
- package/dist/bundle/chunks/{chunk-GNSMNSL7.js → chunk-LUZVGPQ7.js} +2 -2
- package/dist/bundle/chunks/{chunk-7III44PV.js → chunk-MJTCZUXS.js} +1 -1
- package/dist/bundle/chunks/{chunk-2FDW2LQL.js → chunk-SB6MXNO5.js} +1 -1
- package/dist/bundle/chunks/chunk-UODSYQ2D.js +2 -0
- package/dist/bundle/chunks/{chunk-D5MRJWUK.js → chunk-V2QAYGOK.js} +216 -224
- package/dist/bundle/chunks/{chunk-WGRKM3N6.js → chunk-X53PD4ZC.js} +1 -1
- package/dist/bundle/chunks/{cli-main-T74YJGIF.js → cli-main-RWHDZKTQ.js} +1 -1
- package/dist/bundle/chunks/{dispatch-BAK2YZKD.js → dispatch-VDIYSXQN.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-AYRUGE2A.js → hand-entry-G5LYF62Q.js} +1 -1
- package/dist/bundle/chunks/{host-command-YRXZE4TV.js → host-command-I2JZ5JM7.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-N7IN7ZDV.js → host-session-command-Y52NFPP2.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-PA7DGVS6.js +2 -0
- package/dist/bundle/chunks/main-OBL6375C.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-XSUJDHFZ.js → multi-session-host-NM6I5BWZ.js} +20 -17
- package/dist/bundle/chunks/{package-manager-cli-GLUNPQJU.js → package-manager-cli-GNRQ5MVR.js} +1 -1
- package/dist/bundle/chunks/{role-RRFFHZF5.js → role-F56BS2ZQ.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-G4KIP7XS.js +2 -0
- package/dist/bundle/chunks/{run-attached-tui-W236SEOR.js → run-attached-tui-XTGPAGYM.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-OBXYK4PI.js → runtime-session-VO3X4OE3.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-B2EJTBOE.js → schedule-command-L4M3TSQ3.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-YIMLHGLG.js → session-control-endpoint-V46WMCDF.js} +1 -1
- package/dist/bundle/chunks/{session-picker-E6ZONUJB.js → session-picker-AG6KSR5A.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +87 -83
- package/dist/bundle/cli.js +2 -2
- 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 +2 -0
- package/dist/core/agent-session-runtime.js +9 -2
- package/dist/core/agent-session.d.ts +24 -3
- package/dist/core/agent-session.js +94 -28
- package/dist/core/credential-pool/rotation-slots.d.ts +3 -3
- package/dist/core/credential-pool/state-store.d.ts +1 -1
- package/dist/core/egress/hpke.d.ts +63 -0
- package/dist/core/egress/hpke.js +188 -0
- package/dist/core/egress/placement-claim.d.ts +69 -0
- package/dist/core/egress/placement-claim.js +115 -0
- package/dist/core/egress/pool-state.d.ts +18 -0
- package/dist/core/egress/pool-state.js +5 -0
- package/dist/core/egress/sealed-format.d.ts +101 -0
- package/dist/core/egress/sealed-format.js +174 -0
- package/dist/core/egress/wire.d.ts +226 -0
- package/dist/core/egress/wire.js +47 -0
- package/dist/core/extensions/builtin/ask-user/escape-residue.d.ts +50 -0
- package/dist/core/extensions/builtin/ask-user/escape-residue.js +113 -0
- package/dist/core/extensions/builtin/ask-user/extension.js +7 -1
- package/dist/core/extensions/builtin/ask-user/resume.js +2 -1
- package/dist/core/extensions/builtin/ask-user/tool.d.ts +3 -0
- package/dist/core/extensions/builtin/ask-user/tool.js +8 -0
- package/dist/core/extensions/builtin/goal/index.js +46 -0
- package/dist/core/extensions/builtin/goal/stop-pause.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/stop-pause.js +4 -2
- package/dist/core/extensions/builtin/permission-system/index.js +10 -4
- package/dist/core/extensions/builtin/permission-system/preset-registry.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/preset-registry.js +11 -0
- package/dist/core/extensions/builtin/permission-system/settings.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/settings.js +10 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.js +38 -9
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +3 -0
- package/dist/core/extensions/bun-extension-importer.js +4 -3
- package/dist/core/extensions/bun-extension-load-turns.d.ts +4 -0
- package/dist/core/extensions/bun-extension-load-turns.js +45 -0
- package/dist/core/extensions/bun-extension-registry.d.ts +1 -0
- package/dist/core/extensions/bun-extension-registry.js +19 -1
- package/dist/core/extensions/loader.js +5 -0
- package/dist/core/extensions/session-dialog-api.d.ts +5 -0
- package/dist/core/extensions/session-dialog-api.js +13 -0
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/permission-decision.d.ts +22 -0
- package/dist/core/permission-decision.js +53 -0
- package/dist/core/permission-mode.d.ts +56 -0
- package/dist/core/permission-mode.js +133 -0
- package/dist/core/retry-fallback/controller-types.d.ts +6 -0
- package/dist/core/retry-fallback/controller.d.ts +8 -0
- package/dist/core/retry-fallback/controller.js +31 -3
- package/dist/core/retry-fallback/unusable.d.ts +23 -1
- package/dist/core/retry-fallback/unusable.js +80 -1
- package/dist/core/retry-fallback/usage-limit.d.ts +1 -1
- package/dist/core/retry-fallback/usage-limit.js +6 -3
- package/dist/core/sdk.js +2 -0
- package/dist/core/session-log.js +1 -1
- package/dist/core/stall-warning.d.ts +41 -0
- package/dist/core/stall-warning.js +72 -0
- package/dist/core/turn-origin.d.ts +27 -0
- package/dist/core/turn-origin.js +54 -0
- package/dist/modes/rpc/client-input-handler.d.ts +2 -0
- package/dist/modes/rpc/client-input-handler.js +2 -0
- package/dist/modes/rpc/connection-handler.js +16 -5
- package/dist/modes/rpc/connection-roles.d.ts +6 -0
- package/dist/modes/rpc/connection-roles.js +13 -0
- package/dist/modes/rpc/delegated-drive-origin.d.ts +9 -0
- package/dist/modes/rpc/delegated-drive-origin.js +16 -0
- package/dist/modes/rpc/effect-commands.d.ts +6 -0
- package/dist/modes/rpc/host-bare-endpoint-registration.d.ts +7 -0
- package/dist/modes/rpc/host-bare-endpoint-registration.js +35 -0
- package/dist/modes/rpc/host-capabilities.d.ts +2 -0
- package/dist/modes/rpc/host-capabilities.js +3 -0
- package/dist/modes/rpc/host-handover-parked.d.ts +11 -0
- package/dist/modes/rpc/host-handover-parked.js +17 -0
- package/dist/modes/rpc/host-handover-reopen.js +18 -6
- package/dist/modes/rpc/host-handover-resident-writer.d.ts +16 -0
- package/dist/modes/rpc/host-handover-resident-writer.js +57 -0
- package/dist/modes/rpc/host-warm.d.ts +16 -1
- package/dist/modes/rpc/host-warm.js +33 -6
- package/dist/modes/rpc/method-classes-schema-session.d.ts +9 -0
- package/dist/modes/rpc/method-classes-schema-session.js +1 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +9 -0
- package/dist/modes/rpc/method-classes.d.ts +1 -0
- package/dist/modes/rpc/method-classes.js +2 -0
- package/dist/modes/rpc/multi-session-host.js +6 -1
- package/dist/modes/rpc/permission-preset-command.d.ts +6 -0
- package/dist/modes/rpc/permission-preset-command.js +27 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +4 -0
- package/dist/modes/rpc/rpc-session-event-types.d.ts +5 -2
- package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -1
- package/dist/modes/rpc/rpc-session-state.js +2 -0
- package/dist/modes/rpc/session-attachment-commands.js +3 -3
- package/dist/modes/rpc/session-event-writer-connections.js +1 -0
- package/dist/modes/rpc/session-event-writer.d.ts +1 -1
- package/dist/modes/rpc/session-registry-attach.d.ts +0 -9
- package/dist/modes/rpc/session-registry-attach.js +16 -3
- package/dist/modes/rpc/session-registry-types.d.ts +2 -0
- package/dist/modes/rpc/session-registry.d.ts +2 -0
- package/dist/modes/rpc/session-registry.js +16 -3
- package/dist/modes/rpc/session-router-close.js +43 -6
- package/dist/modes/rpc/session-router-context.js +2 -0
- package/dist/modes/rpc/session-router-occupancy.js +2 -11
- package/dist/modes/rpc/session-router-open-profile.js +4 -0
- package/dist/modes/rpc/session-router-open.d.ts +1 -1
- package/dist/modes/rpc/session-router-open.js +6 -17
- package/dist/modes/rpc/session-router-residency.d.ts +24 -0
- package/dist/modes/rpc/session-router-residency.js +90 -0
- package/dist/modes/rpc/session-router-routing.js +7 -1
- package/dist/modes/rpc/session-status.js +4 -1
- package/dist/modes/rpc/session-worker.js +4 -0
- package/dist/modes/rpc/worker-session-registry.js +5 -2
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +12 -2
- package/docs/extensions.md +12 -0
- package/docs/rpc.md +72 -12
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-C7HEEBFH.js +0 -2
- package/dist/bundle/chunks/chunk-XCMII2VQ.js +0 -98
- package/dist/bundle/chunks/interactive-mode-B5SQDLH7.js +0 -2
- package/dist/bundle/chunks/main-UQFVFCNY.js +0 -2
- package/dist/bundle/chunks/rpc-mode-JYN3E7P7.js +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type OpenAcceptance } from "./compact-open-admission.ts";
|
|
2
|
-
import {
|
|
2
|
+
import type { HandoverParkedEntry } from "./host-handover-parked.ts";
|
|
3
3
|
import type { RpcCommand, RpcResponse } from "./rpc-types.ts";
|
|
4
4
|
import type { RouterContext } from "./session-router-context.ts";
|
|
5
5
|
export declare function openWithBarrier(host: RouterContext, command: Extract<RpcCommand, {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { buildRpcSessionState } from "./connection-handler.js";
|
|
2
2
|
import { compactUpdatesEnabled } from "../../core/engine-socket-flag.js";
|
|
3
3
|
import { startOpenTimer } from "../../core/open-timing.js";
|
|
4
|
+
import { openReplyPermissionMode } from "../../core/permission-mode.js";
|
|
4
5
|
import { compactOpen } from "./compact-open-admission.js";
|
|
5
6
|
import { CompactAdmissionError } from "./compact-admission-error.js";
|
|
6
7
|
import { runAdmissionProducer } from "./compact-admission-producer.js";
|
|
7
|
-
import { clearHandoverParkedSession } from "./host-handover-parked.js";
|
|
8
8
|
import { parkedOpenCommand } from "./host-handover-parked-open.js";
|
|
9
9
|
import { sessionHeldCheck } from "./session-held.js";
|
|
10
10
|
import { forgetSessionOwnership, releaseOwnedSession, releaseOwnerAttachment } from "./session-router-attachments.js";
|
|
@@ -13,6 +13,7 @@ import { code, detail, error } from "./session-router-errors.js";
|
|
|
13
13
|
import { sweepDrain } from "./session-router-occupancy.js";
|
|
14
14
|
import { invalidSpawnedBy, validatedOpenProfile } from "./session-router-open-profile.js";
|
|
15
15
|
import { relocationConnection } from "./session-router-relocation.js";
|
|
16
|
+
import { syncRetainedRecord } from "./session-router-residency.js";
|
|
16
17
|
import { liveToolMediaScope, toolMediaPersister } from "./tool-media-store.js";
|
|
17
18
|
export function openWithBarrier(host, command) {
|
|
18
19
|
if (compactUpdatesEnabled() && command.admissionId !== undefined)
|
|
@@ -48,22 +49,6 @@ function openPhysicalWithBarrier(host, command, accept, signal) {
|
|
|
48
49
|
// A late open's physical session is released by its admission (the accept/rollback path, 4272-admission-
|
|
49
50
|
// abandonment), so the late response itself owns nothing to release here.
|
|
50
51
|
const opened = signal === undefined ? start() : runAdmissionProducer(signal, start, async () => { });
|
|
51
|
-
// A manual reopen-by-path supersedes the handover auto-reopen for that session: clear its
|
|
52
|
-
// record entry, or a later generation would reopen a second copy (MEDIUM-1, senpi#3282 review).
|
|
53
|
-
// Cleared only after the open SUCCEEDS (MEDIUM-A): if the open fails - a session_path_in_use
|
|
54
|
-
// during a handover, or a bad profile - the entry must stay so a later generation can still
|
|
55
|
-
// reopen the session. open() resolves `undefined` on success (the response is written through
|
|
56
|
-
// the writer) and an RpcResponse only on failure, so success is `response === undefined`.
|
|
57
|
-
if (command.sessionPath !== undefined && host.handoverParkedDir !== undefined) {
|
|
58
|
-
const dir = host.handoverParkedDir;
|
|
59
|
-
const sessionPath = command.sessionPath;
|
|
60
|
-
void opened.then((response) => {
|
|
61
|
-
if (response === undefined)
|
|
62
|
-
void clearHandoverParkedSession(dir, sessionPath).catch(() => { });
|
|
63
|
-
}, () => {
|
|
64
|
-
// A rejection means the open failed before producing a response; the entry stays.
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
52
|
return opened.finally(() => {
|
|
68
53
|
resolveBarrier();
|
|
69
54
|
opens.delete(barrier);
|
|
@@ -214,6 +199,9 @@ export async function open(host, command, owner, options) {
|
|
|
214
199
|
releaseOwnerAttachment(host, owner, openedSession.sessionId);
|
|
215
200
|
await releaseOwnedSession(host, openedSession.sessionId);
|
|
216
201
|
}
|
|
202
|
+
// After the open SUCCEEDS (MEDIUM-A): a failed open keeps any record entry, so a later start
|
|
203
|
+
// still reopens that session.
|
|
204
|
+
await syncRetainedRecord(host, openedSession.sessionId, command.sessionPath);
|
|
217
205
|
if (options?.accept) {
|
|
218
206
|
return undefined;
|
|
219
207
|
}
|
|
@@ -230,6 +218,7 @@ export async function open(host, command, owner, options) {
|
|
|
230
218
|
data: {
|
|
231
219
|
sessionId: opened.sessionId,
|
|
232
220
|
state: replyState,
|
|
221
|
+
...openReplyPermissionMode(replyState),
|
|
233
222
|
...(opened.attached ? { attached: true } : {}),
|
|
234
223
|
...(host.defaults.sessionPresence
|
|
235
224
|
? { presence: host.defaults.sessionPresence.view(replyState.sessionId) }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RouterContext } from "./session-router-context.ts";
|
|
2
|
+
import type { RpcSessionEntry } from "./session-registry-types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Writes the record entry of a retained session under `sessionPath`, stamped with this host instance, and drops
|
|
5
|
+
* the entry it was recorded under before when the session has since moved to another transcript (senpi#6190).
|
|
6
|
+
* `resident` stamps the writing process: the session is open here. A park writes it unstamped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function recordRetainedSession(host: RouterContext, retained: RpcSessionEntry, sessionPath: string, resident: boolean): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Brings the record in line with a session this host just opened or attached: a retained session is
|
|
11
|
+
* recorded as resident here, so a restart reopens it; a non-retained open by path supersedes any
|
|
12
|
+
* earlier park of that path, whose entry is cleared so no later start reopens a second copy
|
|
13
|
+
* (MEDIUM-1, senpi#3282 review). Runs only after the open succeeded, so a failed open keeps the entry.
|
|
14
|
+
*/
|
|
15
|
+
export declare function syncRetainedRecord(host: RouterContext, sessionId: string, requestedPath: string | undefined): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Called after every record a session publishes: a retained session whose transcript was switched (`new_session`,
|
|
18
|
+
* `switch_session`, a fork) moves its record entry to the new file at once, so a crash restores the transcript the
|
|
19
|
+
* session is on, never the one it left. A string compare until the path actually moves.
|
|
20
|
+
*/
|
|
21
|
+
export declare function followRetainedRecord(host: RouterContext, sessionId: string): void;
|
|
22
|
+
/** An explicit close or reclaim ends the session for good: drop every key its record was written under. */
|
|
23
|
+
export declare function clearRetainedRecord(host: RouterContext, entry: RpcSessionEntry | undefined): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=session-router-residency.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The retained-session record a host keeps for the NEXT start of its endpoint: one per-session
|
|
3
|
+
* entry in the handover-parked record (`host-handover-parked.ts`), written when a session becomes
|
|
4
|
+
* retained-and-resident here, moved when the session switches to another transcript, and rewritten
|
|
5
|
+
* when it parks. A restart on the same socket and agent dir - after a handoff, a crash, or a kill
|
|
6
|
+
* mid-turn - reopens every entry a previous process wrote by its session file, the way a TUI resumes
|
|
7
|
+
* from its session file, so `list_sessions` (and the relay that mirrors it) lists the session again
|
|
8
|
+
* without anyone re-opening it by hand.
|
|
9
|
+
*/
|
|
10
|
+
import { clearHandoverParkedSession, recordHandoverParkedSession } from "./host-handover-parked.js";
|
|
11
|
+
import { parkedEntryFromProfile } from "./host-handover-parked-open.js";
|
|
12
|
+
import { currentResidentWriter } from "./host-handover-resident-writer.js";
|
|
13
|
+
import { hostInstanceId } from "./protocol-identity.js";
|
|
14
|
+
const reportRecordFailure = (sessionId) => (cause) => {
|
|
15
|
+
process.stderr.write(`senpi rpc retained session record ${sessionId} failed: ${String(cause)}\n`);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Writes the record entry of a retained session under `sessionPath`, stamped with this host instance, and drops
|
|
19
|
+
* the entry it was recorded under before when the session has since moved to another transcript (senpi#6190).
|
|
20
|
+
* `resident` stamps the writing process: the session is open here. A park writes it unstamped.
|
|
21
|
+
*/
|
|
22
|
+
export async function recordRetainedSession(host, retained, sessionPath, resident) {
|
|
23
|
+
const dir = host.handoverParkedDir;
|
|
24
|
+
if (dir === undefined)
|
|
25
|
+
return;
|
|
26
|
+
const previous = retained.recordedPath;
|
|
27
|
+
retained.recordedPath = sessionPath;
|
|
28
|
+
if (previous !== undefined && previous !== sessionPath)
|
|
29
|
+
await clearHandoverParkedSession(dir, previous);
|
|
30
|
+
const durableSessionId = retained.runtime?.session.sessionId ?? retained.durableSessionId;
|
|
31
|
+
await recordHandoverParkedSession(dir, {
|
|
32
|
+
...parkedEntryFromProfile({
|
|
33
|
+
sessionPath,
|
|
34
|
+
contextOverlay: retained.contextOverlay,
|
|
35
|
+
...(durableSessionId !== undefined ? { durableSessionId } : {}),
|
|
36
|
+
}, retained.profile, Object.keys(host.defaults.hostContext ?? {})),
|
|
37
|
+
parkedBy: host.defaults.hostContext?.host_instance ?? hostInstanceId(),
|
|
38
|
+
...(resident ? { residentWriter: currentResidentWriter() } : {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** The session path a live entry writes now; a replacement moves it before the registry's metadata catches up. */
|
|
42
|
+
const currentPath = (entry) => entry.runtime?.session.sessionFile ?? entry.sessionPath;
|
|
43
|
+
/**
|
|
44
|
+
* Brings the record in line with a session this host just opened or attached: a retained session is
|
|
45
|
+
* recorded as resident here, so a restart reopens it; a non-retained open by path supersedes any
|
|
46
|
+
* earlier park of that path, whose entry is cleared so no later start reopens a second copy
|
|
47
|
+
* (MEDIUM-1, senpi#3282 review). Runs only after the open succeeded, so a failed open keeps the entry.
|
|
48
|
+
*/
|
|
49
|
+
export async function syncRetainedRecord(host, sessionId, requestedPath) {
|
|
50
|
+
const dir = host.handoverParkedDir;
|
|
51
|
+
if (dir === undefined)
|
|
52
|
+
return;
|
|
53
|
+
const entry = host.registry.peek(sessionId);
|
|
54
|
+
const sessionPath = entry === undefined ? undefined : currentPath(entry);
|
|
55
|
+
if (entry?.retainOnDisconnect === true && sessionPath !== undefined) {
|
|
56
|
+
await recordRetainedSession(host, entry, sessionPath, true).catch(reportRecordFailure(sessionId));
|
|
57
|
+
if (requestedPath === undefined || requestedPath === sessionPath)
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (requestedPath !== undefined)
|
|
61
|
+
await clearHandoverParkedSession(dir, requestedPath).catch(() => { });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Called after every record a session publishes: a retained session whose transcript was switched (`new_session`,
|
|
65
|
+
* `switch_session`, a fork) moves its record entry to the new file at once, so a crash restores the transcript the
|
|
66
|
+
* session is on, never the one it left. A string compare until the path actually moves.
|
|
67
|
+
*/
|
|
68
|
+
export function followRetainedRecord(host, sessionId) {
|
|
69
|
+
if (host.handoverParkedDir === undefined)
|
|
70
|
+
return;
|
|
71
|
+
const entry = host.registry.peek(sessionId);
|
|
72
|
+
if (entry?.retainOnDisconnect !== true || entry.recordedPath === undefined || entry.state !== "open")
|
|
73
|
+
return;
|
|
74
|
+
const sessionPath = currentPath(entry);
|
|
75
|
+
if (sessionPath === undefined || sessionPath === entry.recordedPath)
|
|
76
|
+
return;
|
|
77
|
+
void recordRetainedSession(host, entry, sessionPath, true).catch(reportRecordFailure(sessionId));
|
|
78
|
+
}
|
|
79
|
+
/** An explicit close or reclaim ends the session for good: drop every key its record was written under. */
|
|
80
|
+
export async function clearRetainedRecord(host, entry) {
|
|
81
|
+
const dir = host.handoverParkedDir;
|
|
82
|
+
if (dir === undefined || entry === undefined)
|
|
83
|
+
return;
|
|
84
|
+
const keys = new Set([entry.recordedPath, entry.sessionPath, entry.runtime?.session.sessionFile]);
|
|
85
|
+
entry.recordedPath = undefined;
|
|
86
|
+
for (const key of keys)
|
|
87
|
+
if (key !== undefined)
|
|
88
|
+
await clearHandoverParkedSession(dir, key).catch(() => { });
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=session-router-residency.js.map
|
|
@@ -24,6 +24,7 @@ import { answerListSessions, answerProtocolInfo, answerSubscriptionLimits } from
|
|
|
24
24
|
import { acknowledgePrompt, otherRequestsSettled } from "./session-router-observation.js";
|
|
25
25
|
import { sweepDrain } from "./session-router-occupancy.js";
|
|
26
26
|
import { openWithBarrier } from "./session-router-open.js";
|
|
27
|
+
import { syncRetainedRecord } from "./session-router-residency.js";
|
|
27
28
|
import { answerSetSessionContext, answerSubscribeSessions, answerUnsubscribeSessions, notAttachedRefusal, } from "./session-status-commands.js";
|
|
28
29
|
import { unloadSession } from "./session-unload.js";
|
|
29
30
|
/**
|
|
@@ -245,7 +246,12 @@ export function setSessionContext(host, command) {
|
|
|
245
246
|
return notAttachedRefusal(command);
|
|
246
247
|
try {
|
|
247
248
|
const entry = host.registry.getForCommand(command.sessionId, command.type);
|
|
248
|
-
|
|
249
|
+
const answered = answerSetSessionContext(command, entry, host.writer.status);
|
|
250
|
+
// The record carries the overlay a restart reopens the session with (a terminal task_status
|
|
251
|
+
// is not reopened at all), so it follows each accepted patch.
|
|
252
|
+
if (answered.success)
|
|
253
|
+
void syncRetainedRecord(host, command.sessionId, undefined);
|
|
254
|
+
return answered;
|
|
249
255
|
}
|
|
250
256
|
catch (cause) {
|
|
251
257
|
return error(command.id, command.type, code(cause), detail(cause));
|
|
@@ -57,7 +57,10 @@ export function deriveSessionStatus(entry, runtime, fanout, terminal, cause) {
|
|
|
57
57
|
export function terminalOfLifecycle(record) {
|
|
58
58
|
const reason = typeof record.reason === "string" ? record.reason : undefined;
|
|
59
59
|
if (record.type === "session_parked")
|
|
60
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
status: "unloaded",
|
|
62
|
+
reason: reason === "unloaded" ? "unload_session" : reason === "host_shutdown" ? "host_shutdown" : "idle",
|
|
63
|
+
};
|
|
61
64
|
if (record.type !== "session_closed")
|
|
62
65
|
return undefined;
|
|
63
66
|
if (reason === "handoff_parked")
|
|
@@ -278,6 +278,8 @@ async function handle(message) {
|
|
|
278
278
|
const bound = roleConnection(message.connection);
|
|
279
279
|
if (message.delegation !== undefined && connectionRoles.retain(bound, message.delegation).kind === "conflict")
|
|
280
280
|
throw new Error(DELEGATION_AUTHORITY_CONFLICT);
|
|
281
|
+
if (message.delegation !== undefined)
|
|
282
|
+
connectionRoles.noteCarriedDelegation(bound);
|
|
281
283
|
if (message.roles !== undefined)
|
|
282
284
|
connectionRoles.adopt(bound, message.roles, message.delegation?.incarnation);
|
|
283
285
|
const bindingEntry = entry;
|
|
@@ -336,6 +338,8 @@ async function handle(message) {
|
|
|
336
338
|
const resolved = message.delegation === undefined ? undefined : connectionRoles.retain(connection, message.delegation);
|
|
337
339
|
if (resolved?.kind !== "conflict" && message.roles !== undefined)
|
|
338
340
|
connectionRoles.adopt(connection, message.roles, message.delegation?.incarnation);
|
|
341
|
+
if (message.delegation !== undefined)
|
|
342
|
+
connectionRoles.noteCarriedDelegation(connection);
|
|
339
343
|
if (message.origin !== undefined)
|
|
340
344
|
connectionRoles.declareOrigin(connection, message.origin);
|
|
341
345
|
const activeBinding = binding;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clientNamedPreset, withClientPreset } from "../../core/permission-mode.js";
|
|
1
2
|
import { outsideCommand } from "./command-authority.js";
|
|
2
3
|
import { refreshesSessionActivity } from "./session-command-activity.js";
|
|
3
4
|
import { publishedSessionContext } from "./session-context-patch.js";
|
|
@@ -163,7 +164,9 @@ export class WorkerSessionRegistry {
|
|
|
163
164
|
return undefined;
|
|
164
165
|
}
|
|
165
166
|
async attach(owner, path, options, requested = { cwd: "" }) {
|
|
166
|
-
const { promptSurface, browserEngine
|
|
167
|
+
const { promptSurface, browserEngine } = requested;
|
|
168
|
+
// A host-default preset folded into a preset-less attach is not a client choice (see session-registry-attach.ts).
|
|
169
|
+
const permissionPreset = clientNamedPreset(requested);
|
|
167
170
|
const entry = this.entries.get(owner);
|
|
168
171
|
if (entry?.state !== "open" || !entry.worker?.bindingReady || entry.worker.snapshot?.sessionPath !== path)
|
|
169
172
|
throw new RpcSessionRegistryError("session_path_in_use");
|
|
@@ -178,7 +181,7 @@ export class WorkerSessionRegistry {
|
|
|
178
181
|
}
|
|
179
182
|
// Sent even when the record already names it: only the worker sees its live session (senpi#2842).
|
|
180
183
|
if (permissionPreset !== undefined) {
|
|
181
|
-
entry.profile = frozenProfile(
|
|
184
|
+
entry.profile = frozenProfile(withClientPreset(entry.profile, permissionPreset));
|
|
182
185
|
await entry.worker.setPermissionPreset(permissionPreset);
|
|
183
186
|
}
|
|
184
187
|
const result = this.openResult(owner, entry);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"rpcClientRequests": "{\"type\":\"get_protocol_info\",\"id\":\"rpc-auth-protocol\",\"observe\":true,\"auth\":{\"client_nonce\":\"<public-nonce>\"}}\n{\"type\":\"set_client_info\",\"width\":100,\"capabilities\":[],\"id\":\"req_1\"}\n{\"type\":\"open_session\",\"cwd\":\"<root>/cwd\",\"sessionPath\":\"<root>/cwd/session.jsonl\",\"id\":\"req_2\"}\n{\"type\":\"get_entries\",\"sessionId\":\"rpc-1\",\"id\":\"req_3\"}\n{\"type\":\"close_session\",\"sessionId\":\"rpc-1\",\"id\":\"req_4\"}\n",
|
|
3
|
-
"rpcClientFrames": "{\"id\":\"rpc-auth-protocol\",\"type\":\"response\",\"command\":\"get_protocol_info\",\"success\":true,\"data\":{\"protocolVersion\":1,\"serverVersion\":\"<server-version>\",\"capabilities\":[\"<capabilities>\"],\"mode\":\"multi\",\"instanceId\":\"<uuid-1>\",\"generation\":0,\"engineVersion\":\"<server-version>\",\"engineOrdinal\":\"<engine-ordinal>\",\"launch_profile\":{\"profile_id\":\"ca7a49435f02009fdda5dac49d29408dcfeb1ab5374353784c5a9def467f5be1\",\"core\":{\"extensions\":[],\"multi_session\":false,\"session_runtime\":\"worker\"}},\"agentDir\":\"<root>/agent\",\"method_classes\":{\"version\":1,\"digest\":\"3860136b2a1b07c20373ed361df07c045bd1e84fce001917e271ca0e4b903e63\",\"classes\":{\"get_protocol_info\":\"observe\",\"get_subscription_limits\":\"observe\",\"list_sessions\":\"observe\",\"list_hands\":\"observe\",\"get_state\":\"observe\",\"get_snapshot\":\"observe\",\"get_entries\":\"observe\",\"get_messages\":\"observe\",\"get_tree\":\"observe\",\"get_fork_messages\":\"observe\",\"get_last_assistant_text\":\"observe\",\"get_session_stats\":\"observe\",\"get_effect_status\":\"dynamic\",\"get_pending_inputs\":\"observe\",\"get_follow_up_messages\":\"observe\",\"get_steering_messages\":\"observe\",\"get_commands\":\"observe\",\"get_loaded_surfaces\":\"observe\",\"get_project_trust\":\"observe\",\"get_available_models\":\"observe\",\"get_available_thinking_levels\":\"observe\",\"get_model_thinking_level\":\"observe\",\"get_fast_mode\":\"observe\",\"get_media\":\"observe\",\"memory_report\":\"observe\",\"eval_cells.list\":\"observe\",\"eval_cells.peek\":\"observe\",\"prompt\":\"dynamic\",\"steer\":\"dynamic\",\"follow_up\":\"dynamic\",\"abort\":\"drive\",\"interrupt\":\"drive\",\"abort_retry\":\"drive\",\"abort_compaction\":\"drive\",\"abort_branch_summary\":\"drive\",\"compact\":\"drive\",\"clear_queue\":\"drive\",\"dequeue_message\":\"drive\",\"edit_queued_message\":\"drive\",\"hold_input\":\"drive\",\"withdraw_held_input\":\"drive\",\"append_user_message\":\"drive\",\"edit_user_message\":\"drive\",\"continue_from_leaf\":\"drive\",\"navigate_tree\":\"drive\",\"set_label\":\"drive\",\"set_session_name\":\"drive\",\"set_thinking_level\":\"dynamic\",\"set_model\":\"dynamic\",\"set_auto_compaction\":\"drive\",\"set_retry_fallback\":\"drive\",\"pause_for_run\":\"drive\",\"resume_for_run\":\"drive\",\"wake\":\"drive\",\"release_tool_barrier\":\"drive\",\"request_self_settle\":\"drive\",\"eval_cells.stop\":\"drive\",\"append_session_entry\":\"dynamic\",\"extension_ui_response\":\"answer\",\"extension_ui_progress\":\"answer\",\"new_session\":\"dynamic\",\"open_session\":\"dynamic\",\"resume_session\":\"thread\",\"fork\":\"thread\",\"clone\":\"thread\",\"close_session\":\"thread\",\"unload_session\":\"thread\",\"set_session_context\":\"thread\",\"attach_session\":\"thread\",\"select_hand\":\"device_admin\",\"bash\":\"device_admin\",\"abort_bash\":\"device_admin\",\"record_bash_result\":\"device_admin\",\"cleanup_bash_output\":\"device_admin\",\"extension_request\":\"device_admin\",\"reload\":\"device_admin\",\"check_reload_veto\":\"device_admin\",\"resolve_path\":\"device_admin\",\"login_start\":\"device_admin\",\"login_cancel\":\"device_admin\",\"login_api_key\":\"device_admin\",\"logout\":\"device_admin\",\"verify_api_key\":\"device_admin\",\"get_auth_providers\":\"device_admin\",\"get_provider_accounts\":\"device_admin\",\"account_order\":\"device_admin\",\"account_pin\":\"device_admin\",\"account_remove\":\"device_admin\",\"account_reset\":\"device_admin\",\"test_provider_account\":\"device_admin\",\"set_favorite_models\":\"device_admin\",\"set_scoped_models\":\"device_admin\",\"cycle_model\":\"device_admin\",\"cycle_thinking_level\":\"device_admin\",\"set_fast_mode\":\"device_admin\",\"set_steering_mode\":\"device_admin\",\"set_follow_up_mode\":\"device_admin\",\"set_auto_retry\":\"device_admin\",\"set_project_trust\":\"device_admin\",\"get_project_settings\":\"device_admin\",\"set_project_setting\":\"device_admin\",\"edit_assistant_message\":\"device_admin\",\"send_custom_message\":\"device_admin\",\"switch_session\":\"device_admin\",\"import_jsonl\":\"device_admin\",\"export_html\":\"device_admin\",\"export_jsonl\":\"device_admin\",\"set_client_info\":\"connection_local\",\"subscribe_sessions\":\"connection_local\",\"unsubscribe_sessions\":\"connection_local\",\"record_user_activity\":\"connection_local\",\"detach_connection\":\"connection_local\",\"release_session\":\"connection_local\",\"warm\":\"connection_local\",\"host_response\":\"connection_local\",\"release_attachment\":\"connection_local\",\"reclaim_session\":\"connection_local\",\"move_session\":\"connection_local\",\"get_recovery_page\":\"connection_local\",\"release_recovery\":\"connection_local\",\"cancel_attempt\":\"connection_local\",\"reset_stream\":\"connection_local\"},\"dynamic_versions\":{\"get_effect_status\":1,\"prompt\":1,\"steer\":1,\"follow_up\":1,\"set_thinking_level\":1,\"set_model\":1,\"append_session_entry\":1,\"new_session\":1,\"open_session\":2}},\"memory_pressure\":false,\"path_identity_resolver\":{\"state\":\"not_started\",\"detail\":null}}}\n{\"id\":\"req_1\",\"type\":\"response\",\"command\":\"set_client_info\",\"success\":true}\n{\"type\":\"queued\",\"for_request\":\"req_2\",\"position\":1,\"in_flight\":0}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-2>\",\"method\":\"setWidget\",\"widgetKey\":\"todo-sidebar\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-3>\",\"method\":\"setStatus\",\"statusKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-4>\",\"method\":\"setWidget\",\"widgetKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-5>\",\"method\":\"setStatus\",\"statusKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-6>\",\"method\":\"setWidget\",\"widgetKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-7>\",\"method\":\"setStatus\",\"statusKey\":\"monitors\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-8>\",\"method\":\"setStatus\",\"statusKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-9>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-10>\",\"method\":\"setStatus\",\"statusKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-11>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"entry_appended\",\"entry\":{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"},\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-12>\",\"method\":\"setStatus\",\"statusKey\":\"todo-waiting\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-13>\",\"method\":\"setStatus\",\"statusKey\":\"goal\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"session_opened\",\"sessionId\":\"rpc-1\",\"durableSessionId\":\"<uuid-14>\",\"sessionPath\":\"<root>/cwd/session.jsonl\",\"kind\":\"interactive\",\"cwd\":\"<root>/cwd\"}\n{\"id\":\"req_2\",\"type\":\"response\",\"command\":\"open_session\",\"success\":true,\"data\":{\"sessionId\":\"rpc-1\",\"state\":{\"answeredUiRequests\":[],\"paused\":false,\"pausedNoticeCount\":0,\"holds\":[],\"lifecycle\":{\"state\":\"done\",\"seq\":0,\"epoch\":0,\"epochOrigin\":\"automatic\",\"humanInputRevision\":0,\"selfSettleEligible\":true,\"restoring\":false,\"persisted\":false,\"runPaused\":false,\"since\":\"2026-10-29T00:00:00.000Z\",\"role\":\"root\",\"rootSessionId\":\"<uuid-14>\",\"activityRevision\":{\"instanceId\":\"<uuid-15>\",\"revision\":0},\"waitingOn\":{\"children\":0,\"background\":[],\"human\":[]},\"holds\":[],\"observedAt\":\"2026-10-29T00:00:00.000Z\"},\"pendingQuestions\":[],\"pendingDialogs\":[],\"model\":{\"id\":\"faux-a\",\"name\":\"faux-a\",\"api\":\"faux:fixture\",\"provider\":\"faux\",\"baseUrl\":\"http://localhost:0\",\"reasoning\":false,\"input\":[\"text\",\"image\"],\"cost\":{\"input\":0,\"output\":0,\"cacheRead\":0,\"cacheWrite\":0},\"contextWindow\":128000,\"maxTokens\":16384},\"pendingModelSwitch\":null,\"thinkingLevel\":\"off\",\"thinkingLevelSource\":\"default\",\"sessionModelThinkingLevels\":{\"faux/faux-a\":\"off\"},\"fastMode\":false,\"isStreaming\":false,\"pausedHeldInputCount\":0,\"runPaused\":false,\"pause\":{\"pauseSeq\":0,\"records\":[]},\"isCompacting\":false,\"retryAttempt\":0,\"isBashRunning\":false,\"steeringMode\":\"all\",\"followUpMode\":\"one-at-a-time\",\"sessionFile\":\"<root>/cwd/session.jsonl\",\"sessionId\":\"<uuid-14>\",\"cwd\":\"<root>/cwd\",\"projectTrusted\":false,\"entries\":[{\"type\":\"model_change\",\"id\":\"<entry-3>\",\"parentId\":null,\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"thinking_level_change\",\"id\":\"<entry-2>\",\"parentId\":\"<entry-3>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"thinkingLevel\":\"off\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"}],\"steering\":[],\"followUp\":[],\"ordered\":[],\"autoCompactionEnabled\":true,\"messageCount\":0,\"pendingMessageCount\":0,\"usageTotals\":{\"input\":0,\"output\":0,\"cacheRead\":0,\"cacheWrite\":0,\"cost\":0},\"contextUsage\":{\"tokens\":0,\"contextWindow\":128000,\"percent\":0},\"favoriteModels\":[],\"scopedModels\":[]}},\"sessionId\":\"rpc-1\"}\n{\"id\":\"req_3\",\"type\":\"response\",\"command\":\"get_entries\",\"success\":true,\"data\":{\"entries\":[{\"type\":\"model_change\",\"id\":\"<entry-3>\",\"parentId\":null,\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"thinking_level_change\",\"id\":\"<entry-2>\",\"parentId\":\"<entry-3>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"thinkingLevel\":\"off\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"}],\"leafId\":\"<entry-1>\"},\"sessionId\":\"rpc-1\"}\n{\"type\":\"session_closed\",\"sessionId\":\"rpc-1\",\"reason\":\"client_close\"}\n{\"id\":\"req_4\",\"type\":\"response\",\"command\":\"close_session\",\"success\":true,\"data\":{},\"sessionId\":\"rpc-1\"}\n",
|
|
3
|
+
"rpcClientFrames": "{\"id\":\"rpc-auth-protocol\",\"type\":\"response\",\"command\":\"get_protocol_info\",\"success\":true,\"data\":{\"protocolVersion\":1,\"serverVersion\":\"<server-version>\",\"capabilities\":[\"<capabilities>\"],\"mode\":\"multi\",\"instanceId\":\"<uuid-1>\",\"generation\":0,\"engineVersion\":\"<server-version>\",\"engineOrdinal\":\"<engine-ordinal>\",\"launch_profile\":{\"profile_id\":\"ca7a49435f02009fdda5dac49d29408dcfeb1ab5374353784c5a9def467f5be1\",\"core\":{\"extensions\":[],\"multi_session\":false,\"session_runtime\":\"worker\"}},\"agentDir\":\"<root>/agent\",\"method_classes\":{\"version\":1,\"digest\":\"67e7dac5622360f9e5fc471764f5a69c43c3abcc22bed55859761cbb53ede37c\",\"classes\":{\"get_protocol_info\":\"observe\",\"get_subscription_limits\":\"observe\",\"list_sessions\":\"observe\",\"list_hands\":\"observe\",\"get_state\":\"observe\",\"get_snapshot\":\"observe\",\"get_entries\":\"observe\",\"get_messages\":\"observe\",\"get_tree\":\"observe\",\"get_fork_messages\":\"observe\",\"get_last_assistant_text\":\"observe\",\"get_session_stats\":\"observe\",\"get_effect_status\":\"dynamic\",\"get_pending_inputs\":\"observe\",\"get_follow_up_messages\":\"observe\",\"get_steering_messages\":\"observe\",\"get_commands\":\"observe\",\"get_loaded_surfaces\":\"observe\",\"get_project_trust\":\"observe\",\"get_available_models\":\"observe\",\"get_available_thinking_levels\":\"observe\",\"get_model_thinking_level\":\"observe\",\"get_fast_mode\":\"observe\",\"get_media\":\"observe\",\"memory_report\":\"observe\",\"eval_cells.list\":\"observe\",\"eval_cells.peek\":\"observe\",\"prompt\":\"dynamic\",\"steer\":\"dynamic\",\"follow_up\":\"dynamic\",\"abort\":\"drive\",\"interrupt\":\"drive\",\"abort_retry\":\"drive\",\"abort_compaction\":\"drive\",\"abort_branch_summary\":\"drive\",\"compact\":\"drive\",\"clear_queue\":\"drive\",\"dequeue_message\":\"drive\",\"edit_queued_message\":\"drive\",\"hold_input\":\"drive\",\"withdraw_held_input\":\"drive\",\"append_user_message\":\"drive\",\"edit_user_message\":\"drive\",\"continue_from_leaf\":\"drive\",\"navigate_tree\":\"drive\",\"set_label\":\"drive\",\"set_session_name\":\"drive\",\"set_thinking_level\":\"dynamic\",\"set_model\":\"dynamic\",\"set_auto_compaction\":\"drive\",\"set_permission_preset\":\"device_admin\",\"set_retry_fallback\":\"drive\",\"pause_for_run\":\"drive\",\"resume_for_run\":\"drive\",\"wake\":\"drive\",\"release_tool_barrier\":\"drive\",\"request_self_settle\":\"drive\",\"eval_cells.stop\":\"drive\",\"append_session_entry\":\"dynamic\",\"extension_ui_response\":\"answer\",\"extension_ui_progress\":\"answer\",\"new_session\":\"dynamic\",\"open_session\":\"dynamic\",\"resume_session\":\"thread\",\"fork\":\"thread\",\"clone\":\"thread\",\"close_session\":\"thread\",\"unload_session\":\"thread\",\"set_session_context\":\"thread\",\"attach_session\":\"thread\",\"select_hand\":\"device_admin\",\"bash\":\"device_admin\",\"abort_bash\":\"device_admin\",\"record_bash_result\":\"device_admin\",\"cleanup_bash_output\":\"device_admin\",\"extension_request\":\"device_admin\",\"reload\":\"device_admin\",\"check_reload_veto\":\"device_admin\",\"resolve_path\":\"device_admin\",\"login_start\":\"device_admin\",\"login_cancel\":\"device_admin\",\"login_api_key\":\"device_admin\",\"logout\":\"device_admin\",\"verify_api_key\":\"device_admin\",\"get_auth_providers\":\"device_admin\",\"get_provider_accounts\":\"device_admin\",\"account_order\":\"device_admin\",\"account_pin\":\"device_admin\",\"account_remove\":\"device_admin\",\"account_reset\":\"device_admin\",\"test_provider_account\":\"device_admin\",\"set_favorite_models\":\"device_admin\",\"set_scoped_models\":\"device_admin\",\"cycle_model\":\"device_admin\",\"cycle_thinking_level\":\"device_admin\",\"set_fast_mode\":\"device_admin\",\"set_steering_mode\":\"device_admin\",\"set_follow_up_mode\":\"device_admin\",\"set_auto_retry\":\"device_admin\",\"set_project_trust\":\"device_admin\",\"get_project_settings\":\"device_admin\",\"set_project_setting\":\"device_admin\",\"edit_assistant_message\":\"device_admin\",\"send_custom_message\":\"device_admin\",\"switch_session\":\"device_admin\",\"import_jsonl\":\"device_admin\",\"export_html\":\"device_admin\",\"export_jsonl\":\"device_admin\",\"set_client_info\":\"connection_local\",\"subscribe_sessions\":\"connection_local\",\"unsubscribe_sessions\":\"connection_local\",\"record_user_activity\":\"connection_local\",\"detach_connection\":\"connection_local\",\"release_session\":\"connection_local\",\"warm\":\"connection_local\",\"host_response\":\"connection_local\",\"release_attachment\":\"connection_local\",\"reclaim_session\":\"connection_local\",\"move_session\":\"connection_local\",\"get_recovery_page\":\"connection_local\",\"release_recovery\":\"connection_local\",\"cancel_attempt\":\"connection_local\",\"reset_stream\":\"connection_local\"},\"dynamic_versions\":{\"get_effect_status\":1,\"prompt\":1,\"steer\":1,\"follow_up\":1,\"set_thinking_level\":1,\"set_model\":1,\"append_session_entry\":1,\"new_session\":1,\"open_session\":2}},\"memory_pressure\":false,\"path_identity_resolver\":{\"state\":\"not_started\",\"detail\":null}}}\n{\"id\":\"req_1\",\"type\":\"response\",\"command\":\"set_client_info\",\"success\":true}\n{\"type\":\"queued\",\"for_request\":\"req_2\",\"position\":1,\"in_flight\":0}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-2>\",\"method\":\"setWidget\",\"widgetKey\":\"todo-sidebar\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-3>\",\"method\":\"setStatus\",\"statusKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-4>\",\"method\":\"setWidget\",\"widgetKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-5>\",\"method\":\"setStatus\",\"statusKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-6>\",\"method\":\"setWidget\",\"widgetKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-7>\",\"method\":\"setStatus\",\"statusKey\":\"monitors\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-8>\",\"method\":\"setStatus\",\"statusKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-9>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-10>\",\"method\":\"setStatus\",\"statusKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-11>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"entry_appended\",\"entry\":{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"},\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-12>\",\"method\":\"setStatus\",\"statusKey\":\"todo-waiting\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-13>\",\"method\":\"setStatus\",\"statusKey\":\"goal\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"session_opened\",\"sessionId\":\"rpc-1\",\"durableSessionId\":\"<uuid-14>\",\"sessionPath\":\"<root>/cwd/session.jsonl\",\"kind\":\"interactive\",\"cwd\":\"<root>/cwd\"}\n{\"id\":\"req_2\",\"type\":\"response\",\"command\":\"open_session\",\"success\":true,\"data\":{\"sessionId\":\"rpc-1\",\"state\":{\"answeredUiRequests\":[],\"paused\":false,\"pausedNoticeCount\":0,\"holds\":[],\"lifecycle\":{\"state\":\"done\",\"seq\":0,\"epoch\":0,\"epochOrigin\":\"automatic\",\"humanInputRevision\":0,\"selfSettleEligible\":true,\"restoring\":false,\"persisted\":false,\"runPaused\":false,\"since\":\"2026-10-29T00:00:00.000Z\",\"role\":\"root\",\"rootSessionId\":\"<uuid-14>\",\"activityRevision\":{\"instanceId\":\"<uuid-15>\",\"revision\":0},\"waitingOn\":{\"children\":0,\"background\":[],\"human\":[]},\"holds\":[],\"observedAt\":\"2026-10-29T00:00:00.000Z\"},\"pendingQuestions\":[],\"pendingDialogs\":[],\"permissionPreset\":\"full-access\",\"permissionPresetSource\":\"host_default\",\"permissionPresetStatus\":\"applied\",\"effectivePermissionPreset\":\"full-access\",\"model\":{\"id\":\"faux-a\",\"name\":\"faux-a\",\"api\":\"faux:fixture\",\"provider\":\"faux\",\"baseUrl\":\"http://localhost:0\",\"reasoning\":false,\"input\":[\"text\",\"image\"],\"cost\":{\"input\":0,\"output\":0,\"cacheRead\":0,\"cacheWrite\":0},\"contextWindow\":128000,\"maxTokens\":16384},\"pendingModelSwitch\":null,\"thinkingLevel\":\"off\",\"thinkingLevelSource\":\"default\",\"sessionModelThinkingLevels\":{\"faux/faux-a\":\"off\"},\"fastMode\":false,\"isStreaming\":false,\"pausedHeldInputCount\":0,\"runPaused\":false,\"pause\":{\"pauseSeq\":0,\"records\":[]},\"isCompacting\":false,\"retryAttempt\":0,\"isBashRunning\":false,\"steeringMode\":\"all\",\"followUpMode\":\"one-at-a-time\",\"sessionFile\":\"<root>/cwd/session.jsonl\",\"sessionId\":\"<uuid-14>\",\"cwd\":\"<root>/cwd\",\"projectTrusted\":false,\"entries\":[{\"type\":\"model_change\",\"id\":\"<entry-3>\",\"parentId\":null,\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"thinking_level_change\",\"id\":\"<entry-2>\",\"parentId\":\"<entry-3>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"thinkingLevel\":\"off\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"}],\"steering\":[],\"followUp\":[],\"ordered\":[],\"autoCompactionEnabled\":true,\"messageCount\":0,\"pendingMessageCount\":0,\"usageTotals\":{\"input\":0,\"output\":0,\"cacheRead\":0,\"cacheWrite\":0,\"cost\":0},\"contextUsage\":{\"tokens\":0,\"contextWindow\":128000,\"percent\":0},\"favoriteModels\":[],\"scopedModels\":[]},\"permissionPreset\":\"full-access\",\"permissionPresetSource\":\"host_default\",\"permissionPresetStatus\":\"applied\"},\"sessionId\":\"rpc-1\"}\n{\"id\":\"req_3\",\"type\":\"response\",\"command\":\"get_entries\",\"success\":true,\"data\":{\"entries\":[{\"type\":\"model_change\",\"id\":\"<entry-3>\",\"parentId\":null,\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"thinking_level_change\",\"id\":\"<entry-2>\",\"parentId\":\"<entry-3>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\",\"thinkingLevel\":\"off\",\"provider\":\"faux\",\"modelId\":\"faux-a\"},{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"}],\"leafId\":\"<entry-1>\"},\"sessionId\":\"rpc-1\"}\n{\"type\":\"session_closed\",\"sessionId\":\"rpc-1\",\"reason\":\"client_close\"}\n{\"id\":\"req_4\",\"type\":\"response\",\"command\":\"close_session\",\"success\":true,\"data\":{},\"sessionId\":\"rpc-1\"}\n",
|
|
4
4
|
"subscribeRequests": "{\"type\":\"subscribe_sessions\",\"mirror\":\"all\",\"id\":\"subscriber-1\"}\n{\"type\":\"unsubscribe_sessions\",\"subscriptionId\":\"sub-1\",\"id\":\"subscriber-2\"}\n",
|
|
5
5
|
"subscribeFrames": "{\"id\":\"subscriber-1\",\"type\":\"response\",\"command\":\"subscribe_sessions\",\"success\":true,\"data\":{\"subscriptionId\":\"sub-1\",\"snapshot\":[],\"seq\":0,\"hostGeneration\":\"0\",\"hostInstanceId\":\"<uuid-1>\"}}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-2>\",\"method\":\"setWidget\",\"widgetKey\":\"todo-sidebar\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-3>\",\"method\":\"setStatus\",\"statusKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-4>\",\"method\":\"setWidget\",\"widgetKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-5>\",\"method\":\"setStatus\",\"statusKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-6>\",\"method\":\"setWidget\",\"widgetKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-7>\",\"method\":\"setStatus\",\"statusKey\":\"monitors\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-8>\",\"method\":\"setStatus\",\"statusKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-9>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-10>\",\"method\":\"setStatus\",\"statusKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-11>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"entry_appended\",\"entry\":{\"type\":\"custom\",\"customType\":\"pi-rules.scan\",\"data\":{\"cwd\":\"<root>/cwd\",\"reason\":\"startup\"},\"startupBookkeeping\":true,\"id\":\"<entry-1>\",\"parentId\":\"<entry-2>\",\"timestamp\":\"2026-10-29T00:00:00.000Z\"},\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-12>\",\"method\":\"setStatus\",\"statusKey\":\"todo-waiting\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-13>\",\"method\":\"setStatus\",\"statusKey\":\"goal\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"session_opened\",\"sessionId\":\"rpc-1\",\"durableSessionId\":\"<uuid-14>\",\"sessionPath\":\"<root>/cwd/session.jsonl\",\"kind\":\"interactive\",\"cwd\":\"<root>/cwd\"}\n{\"type\":\"session_status\",\"subscriptionId\":\"sub-1\",\"seq\":1,\"sessionId\":\"rpc-1\",\"status\":\"idle\",\"kind\":\"interactive\",\"cwd\":\"<root>/cwd\",\"attachments\":1,\"durableSessionId\":\"<uuid-14>\",\"sessionPath\":\"<root>/cwd/session.jsonl\",\"model\":{\"provider\":\"faux\",\"id\":\"faux-a\"},\"thinkingLevel\":\"off\",\"pendingQueue\":{\"steering\":0,\"followUp\":0},\"lastChange\":{\"at\":\"2026-10-29T00:00:00.000Z\",\"cause\":\"open\",\"by\":\"client\"}}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-15>\",\"method\":\"setStatus\",\"statusKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-16>\",\"method\":\"setWidget\",\"widgetKey\":\"anthropic-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-17>\",\"method\":\"setStatus\",\"statusKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-18>\",\"method\":\"setWidget\",\"widgetKey\":\"openai-web-search\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-19>\",\"method\":\"setStatus\",\"statusKey\":\"monitors\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-20>\",\"method\":\"setStatus\",\"statusKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-21>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-websearch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-22>\",\"method\":\"setStatus\",\"statusKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-23>\",\"method\":\"setWidget\",\"widgetKey\":\"pi-webfetch\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"extension_ui_request\",\"id\":\"<uuid-24>\",\"method\":\"setStatus\",\"statusKey\":\"loop\",\"sessionId\":\"rpc-1\"}\n{\"type\":\"session_closed\",\"sessionId\":\"rpc-1\",\"reason\":\"client_close\"}\n{\"type\":\"session_status\",\"subscriptionId\":\"sub-1\",\"seq\":2,\"sessionId\":\"rpc-1\",\"status\":\"closed\",\"reason\":\"client_close\",\"kind\":\"interactive\",\"cwd\":\"<root>/cwd\",\"attachments\":1,\"durableSessionId\":\"<uuid-14>\",\"sessionPath\":\"<root>/cwd/session.jsonl\",\"model\":{\"provider\":\"faux\",\"id\":\"faux-a\"},\"thinkingLevel\":\"off\",\"pendingQueue\":{\"steering\":0,\"followUp\":0},\"lastChange\":{\"at\":\"2026-10-29T00:00:00.000Z\",\"cause\":\"close\",\"by\":\"client\"}}\n{\"id\":\"subscriber-2\",\"type\":\"response\",\"command\":\"unsubscribe_sessions\",\"success\":true}\n"
|
|
6
6
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"methodClassesVersion": 1,
|
|
3
|
-
"methodClassesDigest": "
|
|
3
|
+
"methodClassesDigest": "67e7dac5622360f9e5fc471764f5a69c43c3abcc22bed55859761cbb53ede37c",
|
|
4
4
|
"sampleId": "00000000-0000-7000-8000-000000000000",
|
|
5
5
|
"samplePathRoot": "/nonexistent/senpi-rpc-inbound-sample",
|
|
6
|
-
"methodCount":
|
|
6
|
+
"methodCount": 123,
|
|
7
7
|
"methods": [
|
|
8
8
|
{
|
|
9
9
|
"method": "abort",
|
|
@@ -1065,6 +1065,16 @@
|
|
|
1065
1065
|
"modelId": "rpc-sample-model"
|
|
1066
1066
|
}
|
|
1067
1067
|
},
|
|
1068
|
+
{
|
|
1069
|
+
"method": "set_permission_preset",
|
|
1070
|
+
"class": "device_admin",
|
|
1071
|
+
"sampleClass": "device_admin",
|
|
1072
|
+
"connectionLocal": false,
|
|
1073
|
+
"sample": {
|
|
1074
|
+
"type": "set_permission_preset",
|
|
1075
|
+
"permissionPreset": "ask"
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1068
1078
|
{
|
|
1069
1079
|
"method": "set_project_setting",
|
|
1070
1080
|
"class": "device_admin",
|
package/docs/extensions.md
CHANGED
|
@@ -885,6 +885,18 @@ pi.on("ui_prompt_end", async (event, ctx) => {
|
|
|
885
885
|
});
|
|
886
886
|
```
|
|
887
887
|
|
|
888
|
+
#### Permission mode and session-wide dialogs
|
|
889
|
+
|
|
890
|
+
`pi.getPermissionPreset()` returns the preset the permission system enforces for this session right now, or `undefined` when no permission system runs for it or its setup failed.
|
|
891
|
+
|
|
892
|
+
When a client moves a live session to another preset (`set_permission_preset`, or an attach naming one), the session announces it on the `pi.events` bus. A session's initial preset is not announced; read it with `pi.getPermissionPreset()`.
|
|
893
|
+
|
|
894
|
+
| Channel | Payload | Meaning |
|
|
895
|
+
|---------|---------|---------|
|
|
896
|
+
| `permission_preset_changed` | `{ permissionPreset, permissionPresetSource, permissionPresetStatus, effectivePermissionPreset }` | The session's approval mode changed; the payload is the same mode `get_state` reports. |
|
|
897
|
+
|
|
898
|
+
`pi.sessionDialog.select / confirm / input / editor` raise a dialog addressed to every client attached to the session, not only to the connection whose command is running. Use it for a prompt raised after that command returned, such as a background child's permission request a task runner forwards. The first answer wins; a later answer to the same dialog is refused with `already_answered`. In the terminal UI it shows like any other dialog. The same addressing is available on `ctx.ui` dialogs with the `audience: "session"` option.
|
|
899
|
+
|
|
888
900
|
#### Pending-question bus events
|
|
889
901
|
|
|
890
902
|
These additive events use `pi.events.on(...)`, not `pi.on(...)`:
|
package/docs/rpc.md
CHANGED
|
@@ -1286,7 +1286,7 @@ from them that the host itself went away (shutdown) or handed its endpoint to a
|
|
|
1286
1286
|
| `event_queue_failed` | `true` | The socket's event queue failed; follows its `overflow` record when it has one | `overflow`, `stall` or `transport_error` |
|
|
1287
1287
|
| `event_lease_refused` | `true` | An event cannot be leased; follows its `event_lease_refused` record | `lease_limit` or `storage_unavailable` |
|
|
1288
1288
|
| `compact_endpoint_unsupported` | `false` | A compact mirror of a session served only on the legacy wire; follows its `compact_refused` record | none |
|
|
1289
|
-
| `host_shutdown` | `true` | The host is shutting down; follows its sessions' `session_closed` records (`reason: "host_shutdown"`) | none |
|
|
1289
|
+
| `host_shutdown` | `true` | The host is shutting down; follows its sessions' `session_closed` records (`reason: "host_shutdown"`), or `session_parked { reason: "host_shutdown" }` for a retained session on a socket host | none |
|
|
1290
1290
|
| `host_handoff` | `true` | The host drains for a newer generation; follows its sessions' handoff park records | none |
|
|
1291
1291
|
|
|
1292
1292
|
#### interactive_user
|
|
@@ -2514,7 +2514,8 @@ releases the caller's own attachment and closes the session only when no other c
|
|
|
2514
2514
|
|
|
2515
2515
|
Retention limits (what `open`'s `retain_on_disconnect: true` does NOT survive): an explicit `close`, the host's own
|
|
2516
2516
|
idle-eviction window (`SENPI_RPC_SESSION_IDLE_EVICTION_MS`, default 30 minutes, after which the session is parked -
|
|
2517
|
-
any `senpi host session` verb given its path or durable id resumes it)
|
|
2517
|
+
any `senpi host session` verb given its path or durable id resumes it). The host process exiting does not end it: the next
|
|
2518
|
+
host on the same endpoint reopens it (see "Retained sessions across a host restart"). A session that has not
|
|
2518
2519
|
received a message yet may have no JSONL file on disk even though `open` already reported a `sessionPath`.
|
|
2519
2520
|
|
|
2520
2521
|
A minimal orchestration run:
|
|
@@ -2801,8 +2802,8 @@ viewer opened to the next one (senpi#5808). So every `open_session` on a delegat
|
|
|
2801
2802
|
stays `open` with `attachments: 0` and attachable from any connection, exactly like a retained session (see "Retained
|
|
2802
2803
|
sessions"). The host decides this when it reads the line, so a connection that ends while its open is still running
|
|
2803
2804
|
leaves that session retained too. Like any retained session it is still closed by an explicit `close_session`, parked
|
|
2804
|
-
by the idle-eviction window, and recorded at park for the next host
|
|
2805
|
-
|
|
2805
|
+
by the idle-eviction window, and recorded (at open, and again at park) for the next host on the endpoint, which reopens
|
|
2806
|
+
it on startup, so it survives into the next generation and across a host restart. `attach_session` never turns retention on: attaching over a delegated
|
|
2806
2807
|
connection to a session that is not retained leaves it closing with its last attachment. One consequence to know: a
|
|
2807
2808
|
delegated `open_session` by `sessionPath` (which needs `device_admin`) that lands on a session already live and opened by
|
|
2808
2809
|
someone else joins it as an attach that asks for retention, so it turns retention on for that live session and every
|
|
@@ -3297,8 +3298,9 @@ What the host does enforce are lifecycle windows, and they only ever return memo
|
|
|
3297
3298
|
command's `unknown_session`. A GENERATION HANDOFF also parks attached sessions, using the distinct activity
|
|
3298
3299
|
predicate below, and emits `session_closed { sessionId, reason: "handoff_parked", sessionPath }`, so a client can tell "the host handed
|
|
3299
3300
|
over, reopen by path" from "this session ended". An explicit `close_session` is `reason: "client_close"`; the host
|
|
3300
|
-
process exiting is `reason: "host_shutdown"
|
|
3301
|
-
|
|
3301
|
+
process exiting is `reason: "host_shutdown"`, except for a retained session on a socket host, which PARKS instead
|
|
3302
|
+
(`session_parked { reason: "host_shutdown" }`) because the next host on the endpoint reopens it (see "Retained sessions
|
|
3303
|
+
across a host restart"). `reason` is optional on the wire; a client that does not know a
|
|
3302
3304
|
value, or receives a record with no `reason` field, treats the record exactly as a reason-less one. "Session-owned
|
|
3303
3305
|
work" is the complete activity contract, not just a streaming turn: an agent run, a running bash command,
|
|
3304
3306
|
background terminal jobs and any other published wake source (terminal monitors, loop-guard holds), compaction,
|
|
@@ -3334,17 +3336,45 @@ What the host does enforce are lifecycle windows, and they only ever return memo
|
|
|
3334
3336
|
in-flight turn to settlement, keeps its path reservation and worker slot, and stays in `list_sessions`. A later
|
|
3335
3337
|
`open_session` with that `sessionPath` — from any connection — attaches to the same routing handle and returns
|
|
3336
3338
|
`attached: true`. Retention never outranks an explicit teardown: a `close_session` from an attached connection still
|
|
3337
|
-
reaches zero attachments and closes the session, host shutdown
|
|
3339
|
+
reaches zero attachments and closes the session, a socket host's shutdown parks it (see below), and the idle-eviction
|
|
3340
|
+
window above still
|
|
3338
3341
|
parks it (or the completed-detached-worker rule above parks it sooner) — announced as
|
|
3339
3342
|
`session_parked { sessionId, sessionPath }`, after which the file reopens by path like any
|
|
3340
3343
|
evicted session, and the parked session counts as gone for the empty-host exit below. The park is also recorded
|
|
3341
3344
|
for the NEXT generation, which reopens it on startup; the host that parked it never reopens it on its own, so a
|
|
3342
|
-
client reopening the session right after `session_parked` is the only open of that path (senpi#3485).
|
|
3343
|
-
|
|
3345
|
+
client reopening the session right after `session_parked` is the only open of that path (senpi#3485). Retention survives
|
|
3346
|
+
the host process too (next item). Any attach may turn retention
|
|
3344
3347
|
on for a live session; no attach turns it off for clients that already rely on it. Omitting the flag is byte-identical
|
|
3345
3348
|
to the previous behavior — the session is closed with its last connection — except on a delegated connection
|
|
3346
3349
|
(`declare_delegation`), where every `open_session` is retained (see "Delegated connections"; senpi#5808). Probe `retain_on_disconnect` in
|
|
3347
3350
|
`get_protocol_info` capabilities before relying on it: an older host silently ignores the field.
|
|
3351
|
+
- **Retained sessions across a host restart**: every retained session with a session path is recorded in the
|
|
3352
|
+
endpoint's daemon directory (`handover-parked/`, one entry per session path, the same record a handoff park writes)
|
|
3353
|
+
as soon as its open succeeds; the entry follows the session's `set_session_context` patches and its park, and a
|
|
3354
|
+
transcript switch (`new_session`, `switch_session`, a fork) moves it to the new file, so only the transcript the
|
|
3355
|
+
session is on comes back. An explicit `close_session` or a reclaim removes every key the session was recorded
|
|
3356
|
+
under. While the session is open the entry names the process that holds it; a park drops that, and a host never
|
|
3357
|
+
reopens an entry whose holding process is still alive. So during a generation handoff the successor does not open
|
|
3358
|
+
a session its draining predecessor still has loaded, idle and detached ones included: it reopens the predecessor's
|
|
3359
|
+
park once the drain writes it (or the entry, if the predecessor dies without parking). A host
|
|
3360
|
+
that starts on the same socket and agent dir - after a handoff, a clean stop, a crash, or a kill in the middle of a
|
|
3361
|
+
turn - reopens, in the background right after it binds, every entry an earlier process wrote, replaying the
|
|
3362
|
+
recorded launch profile as `open_session { sessionPath, durableSessionId, retain_on_disconnect: true, ... }`: the same
|
|
3363
|
+
open-by-path a TUI resume runs on its session file. The session is `open` in `list_sessions` again with the same
|
|
3364
|
+
`durableSessionId`, `sessionPath` and transcript, announced by a `session_opened` record, and no client has to
|
|
3365
|
+
re-open it. A clean stop (SIGTERM, idle or empty exit) parks each retained session first: attached clients receive
|
|
3366
|
+
`session_parked { sessionId, sessionPath, reason: "host_shutdown" }`, never `session_closed`, and a turn running at
|
|
3367
|
+
the stop ends with `agent_end { aborted: true, abortSource: "system", abortCause: { kind: "host_shutdown" } }`, never
|
|
3368
|
+
a user abort. A killed host sends nothing. Either way the turn the stopped process was running is not resumed: the
|
|
3369
|
+
transcript holds what was persisted before the stop, as after a TUI crash. The routing handle (`sessionId`, `rpc-N`) is new, since handles are unique per process;
|
|
3370
|
+
a client holding the old one gets `session_not_found` and re-resolves the session from `list_sessions` (or
|
|
3371
|
+
`session_opened`) by `durableSessionId`. A relay that mirrors `list_sessions` (relayd keeps no session store of its
|
|
3372
|
+
own) therefore sees the session again unchanged. Not reopened: an entry whose session file is gone (a session that
|
|
3373
|
+
never received a message has none), an explicitly closed or reclaimed session (its entry is removed), and a session
|
|
3374
|
+
whose context `task_status` is terminal (`completed`, `cancelled`, `error`, `failed`, `lost`, `interrupted`). There is
|
|
3375
|
+
no separate resume-by-durable-id request: `open_session { sessionPath }` already resumes a known transcript, but a
|
|
3376
|
+
remote client learns sessions only from `list_sessions`, so a session the restarted host did not list could not be
|
|
3377
|
+
resumed by one at all.
|
|
3348
3378
|
- **Empty-host exit**: when the registry holds zero sessions AND no client is connected, continuously for
|
|
3349
3379
|
`SENPI_RPC_HOST_EMPTY_EXIT_MS` (default 15 minutes), the host exits through its clean shutdown path (flush, socket
|
|
3350
3380
|
removal), for stdio and `--listen` hosts alike. A connected client counts as occupancy even with no session open,
|
|
@@ -4740,11 +4770,26 @@ Response:
|
|
|
4740
4770
|
"autoCompactionEnabled": true,
|
|
4741
4771
|
"messageCount": 5,
|
|
4742
4772
|
"pendingMessageCount": 0,
|
|
4743
|
-
"pendingDialogs": []
|
|
4773
|
+
"pendingDialogs": [],
|
|
4774
|
+
"permissionPreset": "ask",
|
|
4775
|
+
"permissionPresetSource": "client",
|
|
4776
|
+
"permissionPresetStatus": "applied",
|
|
4777
|
+
"effectivePermissionPreset": "ask"
|
|
4744
4778
|
}
|
|
4745
4779
|
}
|
|
4746
4780
|
```
|
|
4747
4781
|
|
|
4782
|
+
`permissionPreset`, `permissionPresetSource`, `permissionPresetStatus` and `effectivePermissionPreset` report the approval mode the session enforces. `permissionPresetSource` is `client` when a client named a valid preset for this session (`open_session.permissionPreset`, an attach naming one, or `set_permission_preset`) and `host_default` when the host's own default applies (its `--permission-preset` flag, then project and global settings, then `full-access`). `permissionPresetStatus` is `invalid` when the named preset or the host's setting cannot be parsed: the open is still accepted, but every gated tool call is refused, and an invalid preset never counts as one the client set. `effectivePermissionPreset` is the preset the next permission decision uses, `null` while the status is `invalid`. A client confirms a mode it asked for only when `permissionPreset` equals it and `permissionPresetStatus` is `applied`. The `open_session` reply carries the first three fields in `data` as well as in `data.state`.
|
|
4783
|
+
|
|
4784
|
+
A preset a client named is recorded in the session file as a `permission_mode` entry. The entry is a record only: a later open that names no preset still uses the host's own default, and the launch flag always wins.
|
|
4785
|
+
|
|
4786
|
+
Each gated decision picks its preset in this order:
|
|
4787
|
+
|
|
4788
|
+
1. A preset a client named for the session (`open_session.permissionPreset`, an attach naming one, or `set_permission_preset`) wins.
|
|
4789
|
+
2. Work a remote device started asks: a turn started by a `prompt`, `follow_up` or `steer` (`whenIdle: "start"`) from a delegated connection, and engine-started work (extension messages, goal and loop turns, `continue_from_leaf`, `wake`, restored input) on a session a remote device has driven. A session counts as remote-driven once a delegated connection sends it any record whose method class is `drive` or `device_admin`; the engine's method-class table decides, so every run starter is covered. That includes records that start no turn, such as `abort`, `set_session_name` or `clear_queue`, and the mark lasts for the session's lifetime on purpose: telling which drive records can shape later engine-started work would need a second hand-kept list beside the table, and a wrong guess there would run remote-influenced work without asking, so the engine errs toward asking. When the operator named no `--permission-preset`, it uses the preset a client recorded in the session file instead. An embedder's host-level preset is the host default, not a launch flag. This rule is decided before `spawnedBy`: a parent named in `open_session`, real or not, never takes a remote device's work off it.
|
|
4790
|
+
3. A child session (`spawnedBy`) follows its live parent's current mode, including later changes to it. A child whose parent is not live in this process asks, unless the operator named a preset for it with `--permission-preset`.
|
|
4791
|
+
4. Everything else uses the host's own default. A local connection's turn keeps the host default even on a session a remote device has driven, and a `steer` never changes the origin of the turn it joins.
|
|
4792
|
+
|
|
4748
4793
|
`currentTurnId` is the id of the running turn, present only while one runs; a steer or follow-up names it as `expectedTurnId` (on `prompt`, `steer` or `follow_up`).
|
|
4749
4794
|
|
|
4750
4795
|
The `model` field is a full [Model](#model) object or `null`. `pendingModelSwitch` is always present: `null` means no held switch, while an object with `provider` and `id` identifies a switch that applies after the next compaction. The `sessionName` field is the display name set via `set_session_name`, or omitted if not set.
|
|
@@ -5198,6 +5243,21 @@ Response:
|
|
|
5198
5243
|
{"type": "response", "command": "set_auto_compaction", "success": true}
|
|
5199
5244
|
```
|
|
5200
5245
|
|
|
5246
|
+
#### set_permission_preset
|
|
5247
|
+
|
|
5248
|
+
Move this session to another approval mode. It works on a classic `--mode rpc` connection, which refuses `open_session`, and on a multi-session host with the `sessionId` envelope, so a client on another device can change a live session's mode without knowing its session file. It is classed `device_admin`: it changes what the agent may do without asking. Hosts that take it advertise `set_permission_preset_v1`.
|
|
5249
|
+
|
|
5250
|
+
```json
|
|
5251
|
+
{"type": "set_permission_preset", "permissionPreset": "ask"}
|
|
5252
|
+
```
|
|
5253
|
+
|
|
5254
|
+
Response:
|
|
5255
|
+
```json
|
|
5256
|
+
{"type": "response", "command": "set_permission_preset", "success": true, "data": {"permissionPreset": "ask", "permissionPresetSource": "client", "permissionPresetStatus": "applied"}}
|
|
5257
|
+
```
|
|
5258
|
+
|
|
5259
|
+
The new preset applies from the next permission decision, and the session file records it like a preset named at open. An unknown preset is refused with `errorCode` `invalid_launch_profile` and changes nothing, so the session keeps the mode it had. Extensions hear a change on the `permission_preset_changed` event and read the enforced preset with `pi.getPermissionPreset()`.
|
|
5260
|
+
|
|
5201
5261
|
### Retry
|
|
5202
5262
|
|
|
5203
5263
|
#### set_auto_retry
|
|
@@ -6259,7 +6319,7 @@ When a multi-session host ends a routing handle it may name why:
|
|
|
6259
6319
|
| `client_close` | An attached client sent `close_session` |
|
|
6260
6320
|
| `idle_evicted` | The idle sweep ended a session that was not retained |
|
|
6261
6321
|
| `session_dir_removed` | The session's transcript directory was deleted and nothing can reopen it: the idle sweep ended it because no client held it (whatever the idle window), or a generation handoff drain ended it instead of parking it. No `sessionPath` |
|
|
6262
|
-
| `host_shutdown` | The host process is exiting (SIGTERM, idle-exit, empty-host). A retained session
|
|
6322
|
+
| `host_shutdown` | The host process is exiting (SIGTERM, idle-exit, empty-host). A retained session on a socket host is parked instead (`session_parked { reason: "host_shutdown" }`) |
|
|
6263
6323
|
| `replaced` | The routing handle ended because the live session behind it was replaced |
|
|
6264
6324
|
| `handoff_parked` | A generation handoff drained this host; reopen with `open_session { sessionPath }` |
|
|
6265
6325
|
| `released` | `release_session` handed the session to a runtime outside this host. Carries `sessionPath`, the file that runtime now writes: do NOT reopen it here |
|
|
@@ -6450,7 +6510,7 @@ cannot replace the ID on a queued start event. Older engines omit this additive
|
|
|
6450
6510
|
|
|
6451
6511
|
### agent_end
|
|
6452
6512
|
|
|
6453
|
-
Emitted when one low-level agent run completes. Contains all messages generated during this run. If `willRetry` is true, an automatic retry will follow. A run stopped because its session was replaced also carries `abortCause: { kind: "session_replaced", reason, targetSessionFile?, targetSessionId? }` (see `session_replaced`); extensions' own `agent_end` does not carry it, they learn of the replacement from `session_shutdown`.
|
|
6513
|
+
Emitted when one low-level agent run completes. Contains all messages generated during this run. If `willRetry` is true, an automatic retry will follow. A run stopped because its session was replaced also carries `abortCause: { kind: "session_replaced", reason, targetSessionFile?, targetSessionId? }` (see `session_replaced`); a run the host's own stop cut short ends with `aborted: true`, `abortSource: "system"` and `abortCause: { kind: "host_shutdown" }`, never `abortSource: "user"` (senpi#6190); extensions' own `agent_end` does not carry it, they learn of the replacement from `session_shutdown`.
|
|
6454
6514
|
|
|
6455
6515
|
```json
|
|
6456
6516
|
{
|
|
Binary file
|