@code-yeongyu/senpi 2026.11.6-3 → 2026.11.9
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 +22 -0
- package/dist/bundle/chunks/{chunk-JMQOLOC2.js → chunk-3CRPNPTR.js} +1 -1
- package/dist/bundle/chunks/{chunk-5LKB732P.js → chunk-3TZQXZKH.js} +1 -1
- package/dist/bundle/chunks/{chunk-X53PD4ZC.js → chunk-45MVD7BU.js} +1 -1
- package/dist/bundle/chunks/{chunk-L7ZIS3MC.js → chunk-4JIBEZZE.js} +3 -3
- package/dist/bundle/chunks/{chunk-CF5OGBC4.js → chunk-4NYNMAYF.js} +1 -1
- package/dist/bundle/chunks/{chunk-6CEDL5S2.js → chunk-4UR6UFBH.js} +1 -1
- package/dist/bundle/chunks/{chunk-F5JLDCYD.js → chunk-527Q2UYM.js} +5 -5
- package/dist/bundle/chunks/{chunk-7DRWPZ3N.js → chunk-622VYTRI.js} +1 -1
- package/dist/bundle/chunks/{chunk-GKAYPJ4E.js → chunk-7PROV6BB.js} +1 -1
- package/dist/bundle/chunks/{chunk-4E7GDG4H.js → chunk-7VMEE62L.js} +1 -1
- package/dist/bundle/chunks/{chunk-CFEHJ4VU.js → chunk-HXNXISJD.js} +1 -1
- package/dist/bundle/chunks/{chunk-4BATXPB7.js → chunk-K3UHGDUQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-SB6MXNO5.js → chunk-M7GF73FO.js} +2 -2
- package/dist/bundle/chunks/{chunk-V2QAYGOK.js → chunk-OCMGRBJ5.js} +22 -22
- package/dist/bundle/chunks/{chunk-EO2VPZ2K.js → chunk-SGAFIMAU.js} +1 -1
- package/dist/bundle/chunks/{chunk-BASS47UD.js → chunk-STGOYIXC.js} +1 -1
- package/dist/bundle/chunks/{chunk-DSWDJ7HT.js → chunk-WQ4RBPMS.js} +1 -1
- package/dist/bundle/chunks/{chunk-MJTCZUXS.js → chunk-WX2HB4DD.js} +1 -1
- package/dist/bundle/chunks/chunk-XZDCMDUH.js +2 -0
- package/dist/bundle/chunks/{chunk-LUZVGPQ7.js → chunk-Y5L4UFMI.js} +1 -1
- package/dist/bundle/chunks/{cli-main-RWHDZKTQ.js → cli-main-E4NDWAUD.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-G5LYF62Q.js → hand-entry-5X5Z5Y3D.js} +1 -1
- package/dist/bundle/chunks/{headless-host-ES74ZB6M.js → headless-host-YXUGONR3.js} +1 -1
- package/dist/bundle/chunks/{host-command-I2JZ5JM7.js → host-command-VHX2ILLM.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-Y52NFPP2.js → host-session-command-P3MRW63Z.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-PA7DGVS6.js → interactive-mode-XUSBLLWI.js} +1 -1
- package/dist/bundle/chunks/{main-OBL6375C.js → main-3TWVKTJN.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-NM6I5BWZ.js → multi-session-host-HRGM66PY.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-GNRQ5MVR.js → package-manager-cli-WSH7U4YE.js} +1 -1
- package/dist/bundle/chunks/{role-F56BS2ZQ.js → role-4BUU46VW.js} +3 -3
- package/dist/bundle/chunks/{rotation-stream-BN2YK2MC.js → rotation-stream-TODBIHXJ.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-G4KIP7XS.js → rpc-mode-OAPIV46G.js} +1 -1
- package/dist/bundle/chunks/{run-attached-tui-XTGPAGYM.js → run-attached-tui-MXCC5IZR.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-L4M3TSQ3.js → schedule-command-MX56D6UE.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-V46WMCDF.js → session-control-endpoint-IL6ERD4U.js} +1 -1
- package/dist/bundle/chunks/{session-picker-AG6KSR5A.js → session-picker-6VRC25MJ.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +44 -44
- 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-services.d.ts +2 -0
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session.d.ts +8 -0
- package/dist/core/agent-session.js +51 -4
- package/dist/core/egress/classify.d.ts +8 -3
- package/dist/core/egress/classify.js +177 -158
- package/dist/core/egress/console-redactor.d.ts +7 -0
- package/dist/core/egress/console-redactor.js +74 -0
- package/dist/core/egress/credentials.d.ts +73 -0
- package/dist/core/egress/credentials.js +20 -0
- package/dist/core/egress/device-custody.d.ts +1 -1
- package/dist/core/egress/device-custody.js +18 -15
- package/dist/core/egress/device-proxy-call.d.ts +59 -0
- package/dist/core/egress/device-proxy-call.js +148 -0
- package/dist/core/egress/device-proxy-lost-calls.d.ts +27 -0
- package/dist/core/egress/device-proxy-lost-calls.js +28 -0
- package/dist/core/egress/device-proxy-ports.d.ts +49 -0
- package/dist/core/egress/device-proxy-ports.js +2 -0
- package/dist/core/egress/device-proxy-request.d.ts +34 -0
- package/dist/core/egress/device-proxy-request.js +69 -0
- package/dist/core/egress/device-proxy-waits.d.ts +40 -0
- package/dist/core/egress/device-proxy-waits.js +67 -0
- package/dist/core/egress/device-proxy.d.ts +3 -0
- package/dist/core/egress/device-proxy.js +214 -0
- package/dist/core/egress/endpoint.js +2 -1
- package/dist/core/egress/entry-verify.d.ts +13 -0
- package/dist/core/egress/entry-verify.js +33 -0
- package/dist/core/egress/hpke.js +13 -9
- package/dist/core/egress/key-policy-template.d.ts +7 -1
- package/dist/core/egress/ledger-schema.d.ts +80 -0
- package/dist/core/egress/ledger-schema.js +117 -0
- package/dist/core/egress/ledger.d.ts +55 -0
- package/dist/core/egress/ledger.js +159 -0
- package/dist/core/egress/pinned-infra.d.ts +23 -2
- package/dist/core/egress/pinned-infra.js +9 -2
- package/dist/core/egress/redact.d.ts +10 -0
- package/dist/core/egress/redact.js +134 -0
- package/dist/core/egress/sealed-source.d.ts +35 -0
- package/dist/core/egress/sealed-source.js +140 -0
- package/dist/core/egress/verified-keys.js +1 -1
- package/dist/core/egress/wire.d.ts +5 -3
- package/dist/core/egress/wire.js +5 -3
- package/dist/core/extensions/builtin/monitor-state-event.d.ts +26 -0
- package/dist/core/extensions/builtin/monitor-state-event.js +2 -0
- package/dist/core/extensions/builtin/terminal/background-age.d.ts +25 -0
- package/dist/core/extensions/builtin/terminal/background-age.js +91 -0
- package/dist/core/extensions/builtin/terminal/extension-state.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/extension-state.js +21 -3
- package/dist/core/extensions/builtin/terminal/notify.d.ts +6 -0
- package/dist/core/extensions/builtin/terminal/notify.js +39 -5
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +9 -2
- package/dist/core/extensions/builtin/terminal/session-bundle.js +36 -1
- package/dist/core/extensions/builtin/terminal/settings.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/settings.js +8 -0
- package/dist/core/extensions/builtin/terminal/tools/background-auto-reap.d.ts +15 -0
- package/dist/core/extensions/builtin/terminal/tools/background-auto-reap.js +87 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.js +6 -6
- package/dist/core/extensions/builtin/terminal/tools/child-placement.d.ts +8 -0
- package/dist/core/extensions/builtin/terminal/tools/child-placement.js +43 -6
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +11 -2
- package/dist/core/extensions/session-control-types.d.ts +15 -0
- package/dist/core/extensions/session-control-types.js +11 -0
- package/dist/core/external-admission.d.ts +16 -2
- package/dist/core/external-admission.js +22 -4
- package/dist/core/retry-fallback/controller.d.ts +1 -0
- package/dist/core/retry-fallback/controller.js +3 -0
- package/dist/core/retry-fallback/unusable.d.ts +2 -0
- package/dist/core/retry-fallback/unusable.js +7 -1
- package/dist/core/retry-fallback/usage-limit.d.ts +6 -1
- package/dist/core/retry-fallback/usage-limit.js +9 -4
- package/dist/core/sdk.d.ts +6 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/terminal-settings.d.ts +1 -0
- package/dist/core/turn-origin.d.ts +4 -0
- package/dist/core/turn-origin.js +10 -0
- package/dist/modes/interactive/components/remote-delivery-message.d.ts +2 -1
- package/dist/modes/interactive/components/remote-delivery-message.js +13 -5
- package/dist/modes/rpc/admit-external-message-command.d.ts +37 -0
- package/dist/modes/rpc/admit-external-message-command.js +61 -0
- package/dist/modes/rpc/admit-external-message-wire.d.ts +42 -0
- package/dist/modes/rpc/admit-external-message-wire.js +55 -0
- package/dist/modes/rpc/connection-handler.js +14 -0
- package/dist/modes/rpc/effect-commands.d.ts +1 -1
- package/dist/modes/rpc/host-idle-handover.js +1 -0
- package/dist/modes/rpc/method-classes-schema-delivery.d.ts +32 -0
- package/dist/modes/rpc/method-classes-schema-delivery.js +22 -0
- package/dist/modes/rpc/method-classes-schema-session.d.ts +27 -0
- package/dist/modes/rpc/method-classes-schema-session.js +2 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +27 -0
- package/dist/modes/rpc/method-classes.d.ts +1 -0
- package/dist/modes/rpc/method-classes.js +2 -0
- package/dist/modes/rpc/rpc-input-validation.js +4 -3
- package/dist/modes/rpc/rpc-response-types.d.ts +2 -1
- package/dist/modes/rpc/rpc-session-command-types.d.ts +2 -1
- package/dist/modes/rpc/tool-child/terminal-jobs.d.ts +1 -0
- package/dist/modes/rpc/tool-child/terminal-jobs.js +17 -1
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +19 -2
- package/docs/rpc.md +57 -0
- package/docs/terminal-tools.md +25 -1
- 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-UODSYQ2D.js +0 -2
|
@@ -25,6 +25,9 @@ function childGenerationOf(id) {
|
|
|
25
25
|
const generation = CHILD_SESSION_ID.exec(id)?.[1];
|
|
26
26
|
return generation === undefined ? undefined : Number(generation);
|
|
27
27
|
}
|
|
28
|
+
function optionalMinutes(value) {
|
|
29
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? Math.trunc(value) : undefined;
|
|
30
|
+
}
|
|
28
31
|
function parseBackgroundWake(value) {
|
|
29
32
|
if (typeof value !== "object" || value === null)
|
|
30
33
|
return undefined;
|
|
@@ -37,19 +40,46 @@ function parseBackgroundWake(value) {
|
|
|
37
40
|
const startedAtMs = Reflect.get(value, "startedAtMs");
|
|
38
41
|
if (typeof description !== "string" || typeof startedAtMs !== "number")
|
|
39
42
|
return undefined;
|
|
40
|
-
|
|
43
|
+
const command = Reflect.get(value, "command");
|
|
44
|
+
const autoReapMinutes = optionalMinutes(Reflect.get(value, "autoReapMinutes"));
|
|
45
|
+
return {
|
|
46
|
+
event,
|
|
47
|
+
id,
|
|
48
|
+
description,
|
|
49
|
+
startedAtMs,
|
|
50
|
+
...(typeof command === "string" ? { command } : {}),
|
|
51
|
+
...(autoReapMinutes === undefined ? {} : { autoReapMinutes }),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (event === "background_reap_failed") {
|
|
55
|
+
const autoReapMinutes = optionalMinutes(Reflect.get(value, "autoReapMinutes"));
|
|
56
|
+
const reason = Reflect.get(value, "reason");
|
|
57
|
+
if (autoReapMinutes === undefined || typeof reason !== "string")
|
|
58
|
+
return undefined;
|
|
59
|
+
return { event, id, autoReapMinutes, reason };
|
|
41
60
|
}
|
|
42
61
|
if (event === "background_exit") {
|
|
43
62
|
const output = Reflect.get(value, "output");
|
|
44
63
|
const exit = Reflect.get(value, "exit");
|
|
45
64
|
if (typeof output !== "string" || (exit !== null && typeof exit !== "object"))
|
|
46
65
|
return undefined;
|
|
47
|
-
|
|
66
|
+
const autoReapedAfterMinutes = optionalMinutes(Reflect.get(value, "autoReapedAfterMinutes"));
|
|
67
|
+
return {
|
|
68
|
+
event,
|
|
69
|
+
id,
|
|
70
|
+
exit: exit,
|
|
71
|
+
output,
|
|
72
|
+
...(autoReapedAfterMinutes === undefined ? {} : { autoReapedAfterMinutes }),
|
|
73
|
+
};
|
|
48
74
|
}
|
|
49
75
|
return undefined;
|
|
50
76
|
}
|
|
51
|
-
function exitView(exit, output) {
|
|
52
|
-
return {
|
|
77
|
+
function exitView(exit, output, autoReapedAfterMinutes) {
|
|
78
|
+
return {
|
|
79
|
+
exitResult: exit,
|
|
80
|
+
fullOutput: () => output,
|
|
81
|
+
...(autoReapedAfterMinutes === undefined ? {} : { autoReapedAfterMinutes }),
|
|
82
|
+
};
|
|
53
83
|
}
|
|
54
84
|
function bashIdOf(value) {
|
|
55
85
|
if (typeof value !== "object" || value === null)
|
|
@@ -106,6 +136,9 @@ export function terminalChildJob(ctx, kind) {
|
|
|
106
136
|
defaultRows: ctx.defaultRows,
|
|
107
137
|
redactSecretsInOutput: ctx.redactSecretsInOutput === true,
|
|
108
138
|
...(ctx.timeoutAction === undefined ? {} : { timeoutAction: ctx.timeoutAction }),
|
|
139
|
+
...(ctx.backgroundAutoReapMinutes === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { backgroundAutoReapMinutes: ctx.backgroundAutoReapMinutes }),
|
|
109
142
|
};
|
|
110
143
|
},
|
|
111
144
|
};
|
|
@@ -124,11 +157,15 @@ function backgroundRelay(ctx) {
|
|
|
124
157
|
return;
|
|
125
158
|
if (wake.event === "background_start") {
|
|
126
159
|
running.set(wake.id, generation);
|
|
127
|
-
ctx.onBackgroundStart?.(wake.id, wake.description, wake.startedAtMs);
|
|
160
|
+
ctx.onBackgroundStart?.(wake.id, wake.description, wake.startedAtMs, wake.command, wake.autoReapMinutes);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (wake.event === "background_reap_failed") {
|
|
164
|
+
ctx.onBackgroundReapFailed?.(wake.id, wake.autoReapMinutes, wake.reason);
|
|
128
165
|
return;
|
|
129
166
|
}
|
|
130
167
|
running.delete(wake.id);
|
|
131
|
-
ctx.onBackgroundExit?.(wake.id, exitView(wake.exit, wake.output));
|
|
168
|
+
ctx.onBackgroundExit?.(wake.id, exitView(wake.exit, wake.output, wake.autoReapedAfterMinutes));
|
|
132
169
|
},
|
|
133
170
|
childExited: (generation) => {
|
|
134
171
|
for (const [id, startedOn] of running) {
|
|
@@ -17,6 +17,8 @@ export interface TerminalToolContext {
|
|
|
17
17
|
readonly timeoutAction?: TimeoutAction;
|
|
18
18
|
/** Resolved `terminal.maxDurableMonitors`; omitted contexts admit any number of persistent monitors. */
|
|
19
19
|
readonly maxDurableMonitors?: number | "unlimited";
|
|
20
|
+
/** Resolved `terminal.backgroundAutoReapMinutes`: background sessions are stopped at this age; unset means never. */
|
|
21
|
+
readonly backgroundAutoReapMinutes?: number | undefined;
|
|
20
22
|
/** Resolve the environment for spawned sessions (mirrors core bash `getShellEnv`). */
|
|
21
23
|
readonly getEnv: () => NodeJS.ProcessEnv;
|
|
22
24
|
/**
|
|
@@ -24,12 +26,17 @@ export interface TerminalToolContext {
|
|
|
24
26
|
* expose PI_* session metadata to spawned commands like the core bash tool does.
|
|
25
27
|
*/
|
|
26
28
|
readonly getSessionContext?: () => import("../../../types.ts").ExtensionContext | undefined;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Registers a session when it transitions to background liveness; `command` is what it runs, when known, and
|
|
31
|
+
* `autoReapMinutes` the age at which the opt-in auto-reap stops it.
|
|
32
|
+
*/
|
|
33
|
+
readonly onBackgroundStart?: (id: string, description: string, startedAtMs: number, command?: string, autoReapMinutes?: number) => void;
|
|
29
34
|
/** A person or the model wrote to a session's stdin (`bash_input`): it is interactive from now on. */
|
|
30
35
|
readonly onSessionInput?: (id: string) => void;
|
|
31
36
|
/** Notified when a background session exits, so the notify layer can wake the agent. */
|
|
32
37
|
readonly onBackgroundExit?: (id: string, runtime: TerminalExitView) => void;
|
|
38
|
+
/** The opt-in auto-reap's one stop at `autoReapMinutes` did not end the session, for `reason`; it keeps running. */
|
|
39
|
+
readonly onBackgroundReapFailed?: (id: string, autoReapMinutes: number, reason: string) => void;
|
|
33
40
|
/** Session-scoped monitor state, sharing the terminal manager's bash-id namespace. */
|
|
34
41
|
readonly monitorRegistry?: MonitorRegistry;
|
|
35
42
|
/** Receives filtered monitor line and terminal-summary events. */
|
|
@@ -55,6 +62,8 @@ export interface TerminalToolContext {
|
|
|
55
62
|
/** What a background exit notice reads: on the tool child it is a summary the child sent, not the live session. */
|
|
56
63
|
export interface TerminalExitView {
|
|
57
64
|
readonly exitResult: Pick<TerminalSessionExit, "exitCode" | "signal" | "cancelled" | "timedOut"> | null;
|
|
65
|
+
/** Set when the opt-in auto-reap stopped the session at this age (senpi#6201). */
|
|
66
|
+
readonly autoReapedAfterMinutes?: number;
|
|
58
67
|
fullOutput(): string;
|
|
59
68
|
}
|
|
60
69
|
export interface TerminalLaunchBarrier {
|
|
@@ -45,6 +45,15 @@ export type SessionControlSender = {
|
|
|
45
45
|
readonly platform: string;
|
|
46
46
|
readonly author?: string;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* The remote device a delivery's RPC connection speaks for, stamped by the engine from the connection it arrived on
|
|
50
|
+
* (senpi#6233). It is never taken from a caller's record or an extension's input, so it is the one provenance on a
|
|
51
|
+
* delivery that a remote caller cannot write.
|
|
52
|
+
*/
|
|
53
|
+
export interface SessionControlRemoteOrigin {
|
|
54
|
+
readonly deviceId: string;
|
|
55
|
+
readonly deviceName?: string;
|
|
56
|
+
}
|
|
48
57
|
export interface AdmitExternalMessageInput {
|
|
49
58
|
readonly delivery_id: string;
|
|
50
59
|
readonly text: string;
|
|
@@ -154,7 +163,13 @@ export interface SessionControlDeliveryDetails {
|
|
|
154
163
|
readonly deliverAs: ExternalDeliverAs;
|
|
155
164
|
readonly sender?: SessionControlSender;
|
|
156
165
|
readonly display_text?: string;
|
|
166
|
+
/** Engine-stamped: the remote device the admitting connection speaks for. */
|
|
167
|
+
readonly remote_origin?: SessionControlRemoteOrigin;
|
|
168
|
+
/** Engine-stamped: the thread-chain hop count the delivery was admitted at. */
|
|
169
|
+
readonly chain_count?: number;
|
|
157
170
|
}
|
|
171
|
+
/** The engine-stamped remote device a delivery's `details` names, when it names a well-formed one. */
|
|
172
|
+
export declare function sessionControlRemoteOriginOf(details: unknown): SessionControlRemoteOrigin | undefined;
|
|
158
173
|
/** The sender a delivery's `details` names, when it names a well-formed one. */
|
|
159
174
|
export declare function sessionControlSenderOf(details: unknown): SessionControlSender | undefined;
|
|
160
175
|
/** Whether a custom-message `details` value proves it came through session-control admission. */
|
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
export { RELOCATION_TOOL_NAMES } from "../session-relocation.js";
|
|
12
12
|
/** `customType` of the transcript entry an admitted delivery becomes. */
|
|
13
13
|
export const SESSION_CONTROL_DELIVERY_TYPE = "session_control_delivery";
|
|
14
|
+
/** The engine-stamped remote device a delivery's `details` names, when it names a well-formed one. */
|
|
15
|
+
export function sessionControlRemoteOriginOf(details) {
|
|
16
|
+
if (!isSessionControlDeliveryDetails(details) || typeof details.remote_origin !== "object")
|
|
17
|
+
return undefined;
|
|
18
|
+
const origin = { ...details.remote_origin };
|
|
19
|
+
if (typeof origin.deviceId !== "string" || origin.deviceId === "")
|
|
20
|
+
return undefined;
|
|
21
|
+
return typeof origin.deviceName === "string" && origin.deviceName !== ""
|
|
22
|
+
? { deviceId: origin.deviceId, deviceName: origin.deviceName }
|
|
23
|
+
: { deviceId: origin.deviceId };
|
|
24
|
+
}
|
|
14
25
|
/** The sender a delivery's `details` names, when it names a well-formed one. */
|
|
15
26
|
export function sessionControlSenderOf(details) {
|
|
16
27
|
if (!isSessionControlDeliveryDetails(details) || typeof details.sender !== "object" || details.sender === null)
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* a file that still refuses writes.
|
|
14
14
|
*/
|
|
15
15
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
16
|
-
import { type AdmissionHoldReason, type AdmitExternalMessageInput, type AdmittedDeliveries, type ExternalAdmissionResult, type ExternalDeliverAs, type SessionAdmissionGate, type SessionControlDeliveryDetails } from "./extensions/session-control-types.ts";
|
|
16
|
+
import { type AdmissionHoldReason, type AdmitExternalMessageInput, type AdmittedDeliveries, type ExternalAdmissionResult, type ExternalDeliverAs, type SessionAdmissionGate, type SessionControlDeliveryDetails, type SessionControlRemoteOrigin } from "./extensions/session-control-types.ts";
|
|
17
17
|
import type { CustomMessage } from "./messages.ts";
|
|
18
|
+
import type { HistoryReducer } from "./session-history-reduce.ts";
|
|
19
|
+
import { type InputTurnMark } from "./turn-origin.ts";
|
|
18
20
|
export type SessionControlDeliveryMessage = CustomMessage<SessionControlDeliveryDetails>;
|
|
19
21
|
export interface ExternalAdmissionPort {
|
|
20
22
|
/** A run is active, or a prompt has claimed its start: new input queues behind it. */
|
|
@@ -28,6 +30,16 @@ export interface EditorHoldState {
|
|
|
28
30
|
readonly hold_reason?: AdmissionHoldReason;
|
|
29
31
|
readonly revision: number;
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* What the engine itself adds to a delivery an RPC connection admits (senpi#6233): the remote device that connection
|
|
35
|
+
* speaks for, the thread-chain hop it was admitted at, and the origin mark its turn takes. `pi.session` never passes
|
|
36
|
+
* one, so neither an extension nor a caller's record can write these.
|
|
37
|
+
*/
|
|
38
|
+
export interface DeliveryStamp {
|
|
39
|
+
readonly remoteOrigin?: SessionControlRemoteOrigin;
|
|
40
|
+
readonly chainCount?: number;
|
|
41
|
+
readonly turnMark?: InputTurnMark;
|
|
42
|
+
}
|
|
31
43
|
export declare class ExternalAdmission {
|
|
32
44
|
private turnEpochValue;
|
|
33
45
|
private readonly pending;
|
|
@@ -70,7 +82,7 @@ export declare class ExternalAdmission {
|
|
|
70
82
|
/** Undoes `close` when the hand-over it was closed for did not happen. */
|
|
71
83
|
reopen(): void;
|
|
72
84
|
gate(): SessionAdmissionGate;
|
|
73
|
-
admit(input: AdmitExternalMessageInput): ExternalAdmissionResult;
|
|
85
|
+
admit(input: AdmitExternalMessageInput, stamp?: DeliveryStamp): ExternalAdmissionResult;
|
|
74
86
|
list(): AdmittedDeliveries;
|
|
75
87
|
/** A message's transcript entry was written: the file takes writes; a delivery's own entry makes it emitted. */
|
|
76
88
|
observePersisted(message: AgentMessage): void;
|
|
@@ -96,5 +108,7 @@ export interface InputHold {
|
|
|
96
108
|
/** The submission ended without being accepted (it threw, was cancelled, or never submitted). */
|
|
97
109
|
end(): void;
|
|
98
110
|
}
|
|
111
|
+
/** Every delivery id the transcript holds an entry for: what answers for the process-lifetime ledger after a restart. */
|
|
112
|
+
export declare const writtenDeliveryIds: HistoryReducer<Set<string>>;
|
|
99
113
|
export declare function deliveryIdOf(message: AgentMessage): string | undefined;
|
|
100
114
|
//# sourceMappingURL=external-admission.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSessionControlDeliveryDetails, SESSION_CONTROL_DELIVERY_TYPE, } from "./extensions/session-control-types.js";
|
|
2
|
+
import { markInputTurnOrigin } from "./turn-origin.js";
|
|
2
3
|
export class ExternalAdmission {
|
|
3
4
|
constructor(port) {
|
|
4
5
|
this.turnEpochValue = 0;
|
|
@@ -81,7 +82,7 @@ export class ExternalAdmission {
|
|
|
81
82
|
? { can_admit: true, ...base }
|
|
82
83
|
: { can_admit: false, hold_reason: editor.hold_reason, ...base };
|
|
83
84
|
}
|
|
84
|
-
admit(input) {
|
|
85
|
+
admit(input, stamp = {}) {
|
|
85
86
|
if (this.closedReason !== undefined)
|
|
86
87
|
throw new Error(this.closedReason);
|
|
87
88
|
const turn_epoch = this.turnEpochValue;
|
|
@@ -95,7 +96,7 @@ export class ExternalAdmission {
|
|
|
95
96
|
if (input.expected_turn_id !== undefined && input.expected_turn_id !== turn_epoch) {
|
|
96
97
|
return { kind: "turn_conflict", turn_epoch };
|
|
97
98
|
}
|
|
98
|
-
const message = deliveryMessage(input);
|
|
99
|
+
const message = deliveryMessage(input, stamp);
|
|
99
100
|
if (!this.isBusy() && input.whenIdle !== "queue") {
|
|
100
101
|
this.pending.set(id, "start");
|
|
101
102
|
// A start that settles without writing the entry left it queued for later (the runtime's
|
|
@@ -188,8 +189,8 @@ export class ExternalAdmission {
|
|
|
188
189
|
return false;
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
|
-
function deliveryMessage(input) {
|
|
192
|
-
|
|
192
|
+
function deliveryMessage(input, stamp) {
|
|
193
|
+
const message = {
|
|
193
194
|
role: "custom",
|
|
194
195
|
customType: SESSION_CONTROL_DELIVERY_TYPE,
|
|
195
196
|
content: input.text,
|
|
@@ -200,10 +201,27 @@ function deliveryMessage(input) {
|
|
|
200
201
|
deliverAs: input.deliverAs,
|
|
201
202
|
...(input.sender === undefined ? {} : { sender: input.sender }),
|
|
202
203
|
...(input.display_text === undefined ? {} : { display_text: input.display_text }),
|
|
204
|
+
...(stamp.remoteOrigin === undefined ? {} : { remote_origin: stamp.remoteOrigin }),
|
|
205
|
+
...(stamp.chainCount === undefined ? {} : { chain_count: stamp.chainCount }),
|
|
203
206
|
},
|
|
204
207
|
timestamp: Date.now(),
|
|
205
208
|
};
|
|
209
|
+
// The mark rides the message into the run, where the turn that consumes it takes its origin (turn-origin.ts).
|
|
210
|
+
if (stamp.turnMark !== undefined)
|
|
211
|
+
markInputTurnOrigin(message, stamp.turnMark);
|
|
212
|
+
return message;
|
|
206
213
|
}
|
|
214
|
+
/** Every delivery id the transcript holds an entry for: what answers for the process-lifetime ledger after a restart. */
|
|
215
|
+
export const writtenDeliveryIds = {
|
|
216
|
+
initial: () => new Set(),
|
|
217
|
+
add: (ids, entry) => {
|
|
218
|
+
if (entry.type === "custom_message" &&
|
|
219
|
+
entry.customType === SESSION_CONTROL_DELIVERY_TYPE &&
|
|
220
|
+
isSessionControlDeliveryDetails(entry.details))
|
|
221
|
+
ids.add(entry.details.delivery_id);
|
|
222
|
+
return ids;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
207
225
|
export function deliveryIdOf(message) {
|
|
208
226
|
if (message.role !== "custom" || message.customType !== SESSION_CONTROL_DELIVERY_TYPE)
|
|
209
227
|
return undefined;
|
|
@@ -31,6 +31,7 @@ export declare class RetryFallbackController {
|
|
|
31
31
|
accountError(provider: string): string | undefined;
|
|
32
32
|
accountResetAt(provider: string): number | undefined;
|
|
33
33
|
releaseGuessedAccountHold(provider: string): boolean;
|
|
34
|
+
releaseAccountHold(provider: string): boolean;
|
|
34
35
|
resetTurn(): void;
|
|
35
36
|
clear(): void;
|
|
36
37
|
private canonicalChains;
|
|
@@ -55,6 +55,9 @@ export class RetryFallbackController {
|
|
|
55
55
|
releaseGuessedAccountHold(provider) {
|
|
56
56
|
return this.unusable.releaseGuessedAccountHold(provider);
|
|
57
57
|
}
|
|
58
|
+
releaseAccountHold(provider) {
|
|
59
|
+
return this.unusable.releaseAccountHold(provider);
|
|
60
|
+
}
|
|
58
61
|
resetTurn() {
|
|
59
62
|
this.triedSelectors.clear();
|
|
60
63
|
this.refusedProviders.clear();
|
|
@@ -42,6 +42,8 @@ export declare class UnusableEntries {
|
|
|
42
42
|
* (senpi#5568). A hold with a reset the provider stated is kept. Returns whether a hold was dropped.
|
|
43
43
|
*/
|
|
44
44
|
releaseGuessedAccountHold(provider: string): boolean;
|
|
45
|
+
/** Drops the provider's account hold, whatever its length; returns whether one was held (senpi#5590). */
|
|
46
|
+
releaseAccountHold(provider: string): boolean;
|
|
45
47
|
isUnusable(model: Model<Api>): boolean;
|
|
46
48
|
clear(model: Model<Api>): void;
|
|
47
49
|
/** The provider's accounts changed (a login, an added or re-authorized account): every hold it earned is stale. */
|
|
@@ -97,7 +97,13 @@ export class UnusableEntries {
|
|
|
97
97
|
* (senpi#5568). A hold with a reset the provider stated is kept. Returns whether a hold was dropped.
|
|
98
98
|
*/
|
|
99
99
|
releaseGuessedAccountHold(provider) {
|
|
100
|
-
if (
|
|
100
|
+
if (this.accountResetAt(provider) !== undefined)
|
|
101
|
+
return false;
|
|
102
|
+
return this.releaseAccountHold(provider);
|
|
103
|
+
}
|
|
104
|
+
/** Drops the provider's account hold, whatever its length; returns whether one was held (senpi#5590). */
|
|
105
|
+
releaseAccountHold(provider) {
|
|
106
|
+
if (!this.isProviderSpent(provider))
|
|
101
107
|
return false;
|
|
102
108
|
this.cooldowns.clear(accountKey(provider));
|
|
103
109
|
this.accountErrors.delete(provider);
|
|
@@ -10,8 +10,13 @@ import type { SessionEntry } from "../session-manager.ts";
|
|
|
10
10
|
*/
|
|
11
11
|
export type UsageLimitScope = "model" | "account";
|
|
12
12
|
export declare class AccountUsageLimitError extends Error {
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* `families`: a no-family weekly limit confirmed on these families before it held the provider (senpi#5596).
|
|
15
|
+
* `accountsOut`: a pooled provider's accounts, each with why it cannot serve (senpi#5590).
|
|
16
|
+
*/
|
|
17
|
+
constructor(provider: string, raw?: string, resetsAt?: number, families?: readonly string[], accountsOut?: string);
|
|
14
18
|
}
|
|
19
|
+
export declare function formatResetTime(epochMs: number): string;
|
|
15
20
|
export declare function usageLimitScope(failure: string | undefined | Pick<AssistantMessage, "errorMessage" | "errorCause">): UsageLimitScope | undefined;
|
|
16
21
|
/** Project advice without mutating the diagnostic-bearing engine message. */
|
|
17
22
|
export declare function describeUsageLimitMessage(message: AssistantMessage): AssistantMessage;
|
|
@@ -2,20 +2,25 @@ import { describeProviderFailureForUser, isQuotaExhaustionMessage } from "@earen
|
|
|
2
2
|
import { rateLimitModelFamily } from "../credential-pool/model-scope.js";
|
|
3
3
|
import { isBillingErrorMessage } from "./billing.js";
|
|
4
4
|
export class AccountUsageLimitError extends Error {
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* `families`: a no-family weekly limit confirmed on these families before it held the provider (senpi#5596).
|
|
7
|
+
* `accountsOut`: a pooled provider's accounts, each with why it cannot serve (senpi#5590).
|
|
8
|
+
*/
|
|
9
|
+
constructor(provider, raw = "usage limit reached", resetsAt, families, accountsOut) {
|
|
6
10
|
const message = describeProviderFailureForUser(raw, {
|
|
7
11
|
model: provider,
|
|
8
12
|
usageLimit: "account",
|
|
9
13
|
...(resetsAt === undefined ? {} : { resetsAt: formatResetTime(resetsAt) }),
|
|
10
14
|
});
|
|
11
|
-
|
|
15
|
+
const withFamilies = families === undefined || families.length === 0
|
|
12
16
|
? message
|
|
13
|
-
: `${message} The limit named no model family; it was confirmed on ${families.join(" and ")}
|
|
17
|
+
: `${message} The limit named no model family; it was confirmed on ${families.join(" and ")}.`;
|
|
18
|
+
super(accountsOut === undefined ? withFamilies : `${withFamilies} No account can serve: ${accountsOut}.`);
|
|
14
19
|
this.name = "AccountUsageLimitError";
|
|
15
20
|
}
|
|
16
21
|
}
|
|
17
22
|
// The same reader-local time plus timezone the credential pool's blocked message uses.
|
|
18
|
-
function formatResetTime(epochMs) {
|
|
23
|
+
export function formatResetTime(epochMs) {
|
|
19
24
|
return new Date(epochMs).toLocaleString("en-US", {
|
|
20
25
|
month: "short",
|
|
21
26
|
day: "numeric",
|
package/dist/core/sdk.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
1
|
+
import { type AgentOptions, type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { ThinkingSelection } from "@earendil-works/pi-ai";
|
|
3
3
|
import { type Model } from "@earendil-works/pi-ai/compat";
|
|
4
4
|
import { AgentSession } from "./agent-session.ts";
|
|
@@ -17,6 +17,11 @@ import { createBashTool, createCodingTools, createEditTool, createFindTool, crea
|
|
|
17
17
|
import type { ToolRuntimeKind } from "../cli/tool-runtime.ts";
|
|
18
18
|
import { type ExecutionPipelineWitnessSink } from "./execution/pipeline-witness-sink.ts";
|
|
19
19
|
export interface CreateAgentSessionOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Makes each finished model step durable before the loop acts on it (hosted custody, C6 C.P3). Passed to the Agent
|
|
22
|
+
* this function builds; fused and split-local sessions leave it unset.
|
|
23
|
+
*/
|
|
24
|
+
commitStep?: AgentOptions["commitStep"];
|
|
20
25
|
/**
|
|
21
26
|
* Where this session's execution-pipeline receipts go (S18b part 3): a sink outside the session, never a session entry.
|
|
22
27
|
* Unset: the file `SENPI_EXECUTION_PIPELINE_WITNESS` names (one JSON line per call), else no receipts.
|
package/dist/core/sdk.js
CHANGED
|
@@ -292,6 +292,7 @@ export async function createAgentSession(options = {}) {
|
|
|
292
292
|
agent = new Agent({
|
|
293
293
|
internalChannel,
|
|
294
294
|
onExecutionPipeline: resolveWitnessSink(options.executionPipelineWitness),
|
|
295
|
+
...(options.commitStep ? { commitStep: options.commitStep } : {}),
|
|
295
296
|
initialState: {
|
|
296
297
|
systemPrompt: "",
|
|
297
298
|
model,
|
|
@@ -25,6 +25,7 @@ export interface TerminalSettings {
|
|
|
25
25
|
monitorRunawayLinesPerMinute?: number;
|
|
26
26
|
monitorRunawayMinutes?: number;
|
|
27
27
|
maxDurableMonitors?: number | "unlimited";
|
|
28
|
+
backgroundAutoReapMinutes?: number;
|
|
28
29
|
killGuard?: "on" | "off";
|
|
29
30
|
}
|
|
30
31
|
export interface BranchSummarySettings {
|
|
@@ -17,6 +17,10 @@ export declare function inputTurnMark(input: {
|
|
|
17
17
|
readonly source?: string;
|
|
18
18
|
}): InputTurnMark;
|
|
19
19
|
export declare function markInputTurnOrigin(message: object, mark: InputTurnMark): void;
|
|
20
|
+
/** Carries `from`'s origin mark onto `to`, a copy of the same input: a copy without it would open or join a turn unmarked. */
|
|
21
|
+
export declare function copyInputTurnMark(from: object, to: object): void;
|
|
22
|
+
/** Whether a message carries an origin mark: a user message always opens a turn's origin, any other only when marked. */
|
|
23
|
+
export declare function hasInputTurnMark(message: object): boolean;
|
|
20
24
|
/** A run starts as engine work; the user message that opens a turn sets its origin, a steer keeps it. */
|
|
21
25
|
export declare function noteRunStart(sessionManager: object): void;
|
|
22
26
|
export declare function noteUserMessageStart(sessionManager: object, message: object): void;
|
package/dist/core/turn-origin.js
CHANGED
|
@@ -13,6 +13,16 @@ export function inputTurnMark(input) {
|
|
|
13
13
|
export function markInputTurnOrigin(message, mark) {
|
|
14
14
|
inputMarks.set(message, mark);
|
|
15
15
|
}
|
|
16
|
+
/** Carries `from`'s origin mark onto `to`, a copy of the same input: a copy without it would open or join a turn unmarked. */
|
|
17
|
+
export function copyInputTurnMark(from, to) {
|
|
18
|
+
const mark = inputMarks.get(from);
|
|
19
|
+
if (mark !== undefined)
|
|
20
|
+
inputMarks.set(to, mark);
|
|
21
|
+
}
|
|
22
|
+
/** Whether a message carries an origin mark: a user message always opens a turn's origin, any other only when marked. */
|
|
23
|
+
export function hasInputTurnMark(message) {
|
|
24
|
+
return inputMarks.has(message);
|
|
25
|
+
}
|
|
16
26
|
/** A run starts as engine work; the user message that opens a turn sets its origin, a steer keeps it. */
|
|
17
27
|
export function noteRunStart(sessionManager) {
|
|
18
28
|
currentOrigins.set(sessionManager, "engine");
|
|
@@ -3,7 +3,8 @@ import { type MessageRenderer, type SessionControlSender } from "../../../core/e
|
|
|
3
3
|
* A message another session delivered renders as its own block - never as the user's own input, so
|
|
4
4
|
* a reader always sees which text came from outside this terminal. A delivery that names its sender
|
|
5
5
|
* shows one label line ("Sent by another agent · <name>") over the message as it was written; one
|
|
6
|
-
* that does not keeps the generic heading over the text the model read.
|
|
6
|
+
* that does not keeps the generic heading over the text the model read. A delivery an RPC connection admitted for a
|
|
7
|
+
* remote device leads with the device the engine stamped, before the sender that device's caller claimed.
|
|
7
8
|
*/
|
|
8
9
|
export declare const renderRemoteDelivery: MessageRenderer;
|
|
9
10
|
export declare function senderLabel(sender: SessionControlSender): string;
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
|
-
import { isSessionControlDeliveryDetails, SESSION_CONTROL_DELIVERY_TYPE, sessionControlSenderOf, } from "../../../core/extensions/types.js";
|
|
2
|
+
import { isSessionControlDeliveryDetails, SESSION_CONTROL_DELIVERY_TYPE, sessionControlRemoteOriginOf, sessionControlSenderOf, } from "../../../core/extensions/types.js";
|
|
3
3
|
import { sharedMarkdownTheme } from "../theme/theme.js";
|
|
4
4
|
/**
|
|
5
5
|
* A message another session delivered renders as its own block - never as the user's own input, so
|
|
6
6
|
* a reader always sees which text came from outside this terminal. A delivery that names its sender
|
|
7
7
|
* shows one label line ("Sent by another agent · <name>") over the message as it was written; one
|
|
8
|
-
* that does not keeps the generic heading over the text the model read.
|
|
8
|
+
* that does not keeps the generic heading over the text the model read. A delivery an RPC connection admitted for a
|
|
9
|
+
* remote device leads with the device the engine stamped, before the sender that device's caller claimed.
|
|
9
10
|
*/
|
|
10
11
|
export const renderRemoteDelivery = (message, _options, theme) => {
|
|
11
12
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
12
13
|
const sender = sessionControlSenderOf(message.details);
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const origin = sessionControlRemoteOriginOf(message.details);
|
|
15
|
+
const shown = sender === undefined && origin === undefined ? undefined : displayTextOf(message.details);
|
|
16
|
+
if (shown !== undefined) {
|
|
17
|
+
if (origin !== undefined)
|
|
18
|
+
box.addChild(new Text(theme.fg("dim", originLabel(origin)), 0, 0));
|
|
19
|
+
if (sender !== undefined)
|
|
20
|
+
box.addChild(new Text(theme.fg("dim", senderLabel(sender)), 0, 0));
|
|
16
21
|
box.addChild(new Spacer(1));
|
|
17
22
|
box.addChild(new Markdown(shown, 0, 0, sharedMarkdownTheme(), { color: (line) => theme.fg("customMessageText", line) }));
|
|
18
23
|
return box;
|
|
@@ -43,6 +48,9 @@ export function senderLabel(sender) {
|
|
|
43
48
|
: `Sent from ${sender.platform} · ${sender.author}`;
|
|
44
49
|
}
|
|
45
50
|
}
|
|
51
|
+
function originLabel(origin) {
|
|
52
|
+
return `Relayed from device ${origin.deviceName ?? origin.deviceId}`;
|
|
53
|
+
}
|
|
46
54
|
export function builtInMessageRenderer(customType) {
|
|
47
55
|
return customType === SESSION_CONTROL_DELIVERY_TYPE ? renderRemoteDelivery : undefined;
|
|
48
56
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `admit_external_message` (senpi#6233): the session's own `ExternalAdmission.admit`, the call behind
|
|
3
|
+
* `pi.session.admitExternalMessage`. The engine writes its fixed `session_control_delivery` entry: never a user message,
|
|
4
|
+
* never the extension `input` event, never a slash command or prompt template, whatever the text says.
|
|
5
|
+
*
|
|
6
|
+
* What the engine adds comes from the connection the record arrived on (`DeliveryPeer`), never from the record (the
|
|
7
|
+
* security design gate's binding conditions): the device a remote connection speaks for is stamped into the entry's
|
|
8
|
+
* metadata (`details.remote_origin`) while the model reads exactly the text a same-machine delivery carries (senpi#6250:
|
|
9
|
+
* an agent need not know a message crossed devices), the ledger keys the delivery under that device, a `command_line` sender is refused
|
|
10
|
+
* from a remote device, the turn that consumes the delivery takes the connection's origin, the thread-chain guard
|
|
11
|
+
* runs first, and an id the transcript already holds is answered `already_admitted` after a restart too.
|
|
12
|
+
*/
|
|
13
|
+
import type { AgentSession } from "../../core/agent-session.ts";
|
|
14
|
+
import type { ConnectionOrigin } from "../../core/turn-origin.ts";
|
|
15
|
+
import type { RpcAdmitExternalMessageCommand, RpcAdmitExternalMessageReply } from "./admit-external-message-wire.ts";
|
|
16
|
+
import { type ChainRefusal } from "./chain-admission.ts";
|
|
17
|
+
import type { RpcRemoteOrigin } from "./remote-origin.ts";
|
|
18
|
+
/** The connection a delivery arrived on, as the host knows it. */
|
|
19
|
+
export interface DeliveryPeer {
|
|
20
|
+
/** The remote device the connection speaks for (`ConnectionRoles.originOf`); absent on a plain local connection. */
|
|
21
|
+
readonly origin: RpcRemoteOrigin | undefined;
|
|
22
|
+
readonly connectionOrigin: ConnectionOrigin;
|
|
23
|
+
}
|
|
24
|
+
export type AdmitExternalMessageAnswer = {
|
|
25
|
+
readonly ok: true;
|
|
26
|
+
readonly data: RpcAdmitExternalMessageReply;
|
|
27
|
+
} | {
|
|
28
|
+
readonly ok: false;
|
|
29
|
+
readonly refusal: ChainRefusal | {
|
|
30
|
+
readonly message: string;
|
|
31
|
+
readonly errorCode: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
type AdmittingSession = Pick<AgentSession, "externalAdmission" | "sessionManager" | "settingsManager">;
|
|
35
|
+
export declare function admitExternalMessage(session: AdmittingSession, command: RpcAdmitExternalMessageCommand, peer: DeliveryPeer): AdmitExternalMessageAnswer;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=admit-external-message-command.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { writtenDeliveryIds } from "../../core/external-admission.js";
|
|
2
|
+
import { admitInputChain } from "./chain-admission.js";
|
|
3
|
+
const SENDER_NOT_ALLOWED = "sender_not_allowed";
|
|
4
|
+
const DELEGATED_ORIGIN_UNKNOWN = "delegated_origin_unknown";
|
|
5
|
+
const CANONICAL_TURN_ID = /^(0|[1-9]\d{0,15})$/;
|
|
6
|
+
/** The ledger key: a remote device's ids live under that device, a local connection's under its own prefix. */
|
|
7
|
+
function ledgerId(deliveryId, origin) {
|
|
8
|
+
return origin === undefined
|
|
9
|
+
? `rpc:local/${deliveryId}`
|
|
10
|
+
: `device:${encodeURIComponent(origin.deviceId)}/${deliveryId}`;
|
|
11
|
+
}
|
|
12
|
+
export function admitExternalMessage(session, command, peer) {
|
|
13
|
+
const chain = admitInputChain(command.chainCount, session.settingsManager);
|
|
14
|
+
if (chain.kind === "refused")
|
|
15
|
+
return { ok: false, refusal: chain.refusal };
|
|
16
|
+
const { origin } = peer;
|
|
17
|
+
// A delegated connection that names no device is never treated as this machine's own: it is refused (fail closed).
|
|
18
|
+
if (peer.connectionOrigin === "delegated" && origin === undefined)
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
refusal: {
|
|
22
|
+
message: `${DELEGATED_ORIGIN_UNKNOWN}: a delegated connection that names no remote device cannot admit a delivery`,
|
|
23
|
+
errorCode: DELEGATED_ORIGIN_UNKNOWN,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
if (origin !== undefined && command.sender?.kind === "command_line")
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
refusal: {
|
|
30
|
+
message: `${SENDER_NOT_ALLOWED}: a remote device cannot send as this machine's command line`,
|
|
31
|
+
errorCode: SENDER_NOT_ALLOWED,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const admission = session.externalAdmission;
|
|
35
|
+
const answer = (kind, epoch) => ({
|
|
36
|
+
ok: true,
|
|
37
|
+
data: { kind, turnId: String(epoch) },
|
|
38
|
+
});
|
|
39
|
+
const id = ledgerId(command.deliveryId, origin);
|
|
40
|
+
if (session.sessionManager.reduceHistory(writtenDeliveryIds).has(id))
|
|
41
|
+
return answer("already_admitted", admission.turnEpoch);
|
|
42
|
+
const expected = command.expectedTurnId;
|
|
43
|
+
if (expected !== undefined && !CANONICAL_TURN_ID.test(expected))
|
|
44
|
+
return answer("turn_conflict", admission.turnEpoch);
|
|
45
|
+
const displayText = command.displayText ?? (origin === undefined ? undefined : command.text);
|
|
46
|
+
const result = admission.admit({
|
|
47
|
+
delivery_id: id,
|
|
48
|
+
text: command.text,
|
|
49
|
+
deliverAs: command.deliverAs,
|
|
50
|
+
...(command.sender === undefined ? {} : { sender: command.sender }),
|
|
51
|
+
...(displayText === undefined ? {} : { display_text: displayText }),
|
|
52
|
+
...(expected === undefined ? {} : { expected_turn_id: Number(expected) }),
|
|
53
|
+
...(command.whenIdle === undefined ? {} : { whenIdle: command.whenIdle }),
|
|
54
|
+
}, {
|
|
55
|
+
...(origin === undefined ? {} : { remoteOrigin: origin }),
|
|
56
|
+
...(chain.chainCount === undefined ? {} : { chainCount: chain.chainCount }),
|
|
57
|
+
turnMark: command.deliverAs === "steer" ? "join" : peer.connectionOrigin,
|
|
58
|
+
});
|
|
59
|
+
return answer(result.kind, result.turn_epoch);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=admit-external-message-command.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire of `admit_external_message` (senpi#6233): a message another agent sent this session, relayed over RPC and
|
|
3
|
+
* admitted the way `pi.session.admitExternalMessage` admits it in process. The fields mirror
|
|
4
|
+
* `AdmitExternalMessageInput` in the wire's camelCase, plus a thread call's `chainCount`; turn ids are strings, as
|
|
5
|
+
* `get_state.currentTurnId`. The field checks run on the local connection and inside `validateRpcInboundRecord`, so
|
|
6
|
+
* both paths refuse the same records.
|
|
7
|
+
*/
|
|
8
|
+
import type { ExternalAdmissionKind, ExternalDeliverAs, SessionControlSender } from "../../core/extensions/session-control-types.ts";
|
|
9
|
+
export interface RpcAdmitExternalMessageCommand {
|
|
10
|
+
id?: string;
|
|
11
|
+
type: "admit_external_message";
|
|
12
|
+
/** The delivery's id: admitting an id this runtime already holds or wrote answers `already_admitted`. */
|
|
13
|
+
deliveryId: string;
|
|
14
|
+
/** What the model reads, provenance included. */
|
|
15
|
+
text: string;
|
|
16
|
+
/** Who the caller says sent it, for the surfaces a human reads; a claim, shown after the engine-stamped device. */
|
|
17
|
+
sender?: SessionControlSender;
|
|
18
|
+
/** The message as its sender wrote it, for human surfaces. */
|
|
19
|
+
displayText?: string;
|
|
20
|
+
deliverAs: ExternalDeliverAs;
|
|
21
|
+
/** The turn the sender observed (`get_state.currentTurnId`); a steer is admitted only while it is current. */
|
|
22
|
+
expectedTurnId?: string;
|
|
23
|
+
/** What an idle session does with it: `start` (the default) starts a turn, `queue` holds it for the next one. */
|
|
24
|
+
whenIdle?: "start" | "queue";
|
|
25
|
+
/** A thread call's hop count (1 for a chain's first hop), checked against the user's chain limit first (K8). */
|
|
26
|
+
chainCount?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface RpcAdmitExternalMessageReply {
|
|
29
|
+
kind: ExternalAdmissionKind;
|
|
30
|
+
/** The session's turn when the delivery was admitted, in `get_state.currentTurnId`'s form. */
|
|
31
|
+
turnId: string;
|
|
32
|
+
}
|
|
33
|
+
export interface RpcAdmitExternalMessageResponse {
|
|
34
|
+
id?: string;
|
|
35
|
+
type: "response";
|
|
36
|
+
command: "admit_external_message";
|
|
37
|
+
success: true;
|
|
38
|
+
data: RpcAdmitExternalMessageReply;
|
|
39
|
+
}
|
|
40
|
+
/** Why an `admit_external_message` record is refused, prefixed with its type; `undefined` for any other record. */
|
|
41
|
+
export declare function admitExternalMessageFieldError(value: Record<string, unknown>, maxTextCharacters: number): string | undefined;
|
|
42
|
+
//# sourceMappingURL=admit-external-message-wire.d.ts.map
|