@code-yeongyu/senpi 2026.11.6-2 → 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 +52 -0
- package/dist/bundle/chunks/{bun-extension-importer-RCGPQTI2.js → bun-extension-importer-GQ6IKEXD.js} +2 -2
- package/dist/bundle/chunks/{chunk-GSKCWXXG.js → chunk-3CRPNPTR.js} +1 -1
- package/dist/bundle/chunks/{chunk-RQ6CHXQ2.js → chunk-3TZQXZKH.js} +1 -1
- package/dist/bundle/chunks/{chunk-QN2BHP3M.js → chunk-44BJPS5I.js} +1 -1
- package/dist/bundle/chunks/{chunk-WGRKM3N6.js → chunk-45MVD7BU.js} +1 -1
- package/dist/bundle/chunks/chunk-4JIBEZZE.js +106 -0
- package/dist/bundle/chunks/{chunk-CF5OGBC4.js → chunk-4NYNMAYF.js} +1 -1
- package/dist/bundle/chunks/{chunk-X5ZVYNOL.js → chunk-4UR6UFBH.js} +1 -1
- package/dist/bundle/chunks/{chunk-LNPAT22L.js → chunk-527Q2UYM.js} +9 -8
- package/dist/bundle/chunks/{chunk-HLM46XEB.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-ECW7VBMB.js → chunk-HXNXISJD.js} +1 -1
- package/dist/bundle/chunks/{chunk-C7OKUC7L.js → chunk-K3UHGDUQ.js} +1 -1
- 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-2FDW2LQL.js → chunk-M7GF73FO.js} +2 -2
- package/dist/bundle/chunks/{chunk-D5MRJWUK.js → chunk-OCMGRBJ5.js} +221 -229
- package/dist/bundle/chunks/{chunk-TIPKAMSS.js → chunk-SGAFIMAU.js} +1 -1
- package/dist/bundle/chunks/{chunk-7PSBD5KS.js → chunk-STGOYIXC.js} +1 -1
- package/dist/bundle/chunks/{chunk-DSWDJ7HT.js → chunk-WQ4RBPMS.js} +1 -1
- package/dist/bundle/chunks/{chunk-7III44PV.js → chunk-WX2HB4DD.js} +1 -1
- package/dist/bundle/chunks/chunk-XZDCMDUH.js +2 -0
- package/dist/bundle/chunks/{chunk-GNSMNSL7.js → chunk-Y5L4UFMI.js} +2 -2
- package/dist/bundle/chunks/{cli-main-T74YJGIF.js → cli-main-E4NDWAUD.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-5X5Z5Y3D.js} +1 -1
- package/dist/bundle/chunks/{headless-host-ES74ZB6M.js → headless-host-YXUGONR3.js} +1 -1
- package/dist/bundle/chunks/{host-command-YRXZE4TV.js → host-command-VHX2ILLM.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-N7IN7ZDV.js → host-session-command-P3MRW63Z.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-XUSBLLWI.js +2 -0
- package/dist/bundle/chunks/main-3TWVKTJN.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-XSUJDHFZ.js → multi-session-host-HRGM66PY.js} +20 -17
- package/dist/bundle/chunks/{package-manager-cli-GLUNPQJU.js → package-manager-cli-WSH7U4YE.js} +1 -1
- package/dist/bundle/chunks/{role-RRFFHZF5.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-OAPIV46G.js +2 -0
- package/dist/bundle/chunks/{run-attached-tui-W236SEOR.js → run-attached-tui-MXCC5IZR.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-MX56D6UE.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-YIMLHGLG.js → session-control-endpoint-IL6ERD4U.js} +1 -1
- package/dist/bundle/chunks/{session-picker-E6ZONUJB.js → session-picker-6VRC25MJ.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +114 -110
- 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-services.d.ts +2 -0
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session.d.ts +32 -3
- package/dist/core/agent-session.js +142 -29
- 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/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.d.ts +63 -0
- package/dist/core/egress/hpke.js +192 -0
- 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/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/redact.d.ts +10 -0
- package/dist/core/egress/redact.js +134 -0
- package/dist/core/egress/sealed-format.d.ts +101 -0
- package/dist/core/egress/sealed-format.js +174 -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 +228 -0
- package/dist/core/egress/wire.js +49 -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/monitor-state-event.d.ts +26 -0
- package/dist/core/extensions/builtin/monitor-state-event.js +2 -0
- 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/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 +3 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.js +44 -15
- 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 +14 -2
- 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-control-types.d.ts +15 -0
- package/dist/core/extensions/session-control-types.js +11 -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/external-admission.d.ts +16 -2
- package/dist/core/external-admission.js +22 -4
- 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 +9 -0
- package/dist/core/retry-fallback/controller.js +34 -3
- package/dist/core/retry-fallback/unusable.d.ts +25 -1
- package/dist/core/retry-fallback/unusable.js +87 -2
- package/dist/core/retry-fallback/usage-limit.d.ts +6 -1
- package/dist/core/retry-fallback/usage-limit.js +12 -4
- package/dist/core/sdk.d.ts +6 -1
- package/dist/core/sdk.js +3 -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/terminal-settings.d.ts +1 -0
- package/dist/core/turn-origin.d.ts +31 -0
- package/dist/core/turn-origin.js +64 -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/client-input-handler.d.ts +2 -0
- package/dist/modes/rpc/client-input-handler.js +2 -0
- package/dist/modes/rpc/connection-handler.js +30 -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 +7 -1
- 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-idle-handover.js +1 -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-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 +36 -0
- package/dist/modes/rpc/method-classes-schema-session.js +3 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +36 -0
- package/dist/modes/rpc/method-classes.d.ts +2 -0
- package/dist/modes/rpc/method-classes.js +4 -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-input-validation.js +4 -3
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-response-types.d.ts +2 -1
- package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -1
- 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/tool-child/terminal-jobs.d.ts +1 -0
- package/dist/modes/rpc/tool-child/terminal-jobs.js +17 -1
- 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 +29 -2
- package/docs/extensions.md +12 -0
- package/docs/rpc.md +129 -12
- 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-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
|
@@ -6,6 +6,7 @@ export declare const ptyBashSchema: Type.TObject<{
|
|
|
6
6
|
timeout: Type.TOptional<Type.TNumber>;
|
|
7
7
|
description: Type.TOptional<Type.TString>;
|
|
8
8
|
run_in_background: Type.TOptional<Type.TBoolean>;
|
|
9
|
+
auto_reap_minutes: Type.TOptional<Type.TNumber>;
|
|
9
10
|
cols: Type.TOptional<Type.TNumber>;
|
|
10
11
|
rows: Type.TOptional<Type.TNumber>;
|
|
11
12
|
}>;
|
|
@@ -50,10 +51,11 @@ export declare function createPtyBashTool(ctx: TerminalToolContext): {
|
|
|
50
51
|
timeout: Type.TOptional<Type.TNumber>;
|
|
51
52
|
description: Type.TOptional<Type.TString>;
|
|
52
53
|
run_in_background: Type.TOptional<Type.TBoolean>;
|
|
54
|
+
auto_reap_minutes: Type.TOptional<Type.TNumber>;
|
|
53
55
|
cols: Type.TOptional<Type.TNumber>;
|
|
54
56
|
rows: Type.TOptional<Type.TNumber>;
|
|
55
57
|
}>;
|
|
56
|
-
execute(
|
|
58
|
+
execute(toolCallId: string, input: PtyBashInput, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, execCtx?: SessionEnvSource): Promise<TerminalToolResult>;
|
|
57
59
|
};
|
|
58
60
|
export {};
|
|
59
61
|
//# sourceMappingURL=bash.d.ts.map
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { getAgentDir } from "../../../../../config.js";
|
|
2
3
|
import { annotateShellTrust } from "../../../../../engine-trust/shell-trust.js";
|
|
4
|
+
import { createSessionLogger } from "../../../../session-log.js";
|
|
5
|
+
import { stallThresholdMs, watchForStall } from "../../../../stall-warning.js";
|
|
3
6
|
import { formatTerminalToolOutput } from "../output-format.js";
|
|
4
7
|
import { BACKGROUND_START_GRACE_MS, DEFAULT_COLS, DEFAULT_ROWS, FOREGROUND_ENV_OVERRIDES, KILLED_SESSION_EXIT_GRACE_MS, TERMINAL_BASH_TOOL, } from "../shared.js";
|
|
5
8
|
import { errorResult, noticedResult, textResult, } from "./context.js";
|
|
9
|
+
import { backgroundStart, trackBackgroundSession } from "./background-auto-reap.js";
|
|
6
10
|
import { createForegroundDetachGate } from "./foreground-detach.js";
|
|
7
11
|
import { resolveForegroundWindowSeconds, SLEEP_WAIT_WINDOW_SECONDS } from "./foreground-window.js";
|
|
8
12
|
import { classifySleepWait } from "./sleep-wait.js";
|
|
@@ -16,6 +20,9 @@ export const ptyBashSchema = Type.Object({
|
|
|
16
20
|
run_in_background: Type.Optional(Type.Boolean({
|
|
17
21
|
description: "Run as a persistent background session: returns a bash_id immediately (read with bash_output, steer with bash_input, stop with kill_bash). A plain & job dies with the call's PTY hangup (native backend), and nohup at best leaves an untracked orphan (no bash_id, no log) that the call's timeout still group-kills. This flag is the only reliable way a job outlives the call.",
|
|
18
22
|
})),
|
|
23
|
+
auto_reap_minutes: Type.Optional(Type.Number({
|
|
24
|
+
description: "Background sessions only: stop this session (TERM, then KILL to its process group) once it has run this many minutes. Defaults to the terminal.backgroundAutoReapMinutes setting, which is off; 0 turns it off for this call.",
|
|
25
|
+
})),
|
|
19
26
|
cols: Type.Optional(Type.Number({ description: "PTY width in columns (default 120)." })),
|
|
20
27
|
rows: Type.Optional(Type.Number({ description: "PTY height in rows (default 40)." })),
|
|
21
28
|
});
|
|
@@ -76,6 +83,32 @@ export function createThrottledEmitter(emit, throttleMs = BASH_UPDATE_THROTTLE_M
|
|
|
76
83
|
},
|
|
77
84
|
};
|
|
78
85
|
}
|
|
86
|
+
let defaultSessionLogger;
|
|
87
|
+
/**
|
|
88
|
+
* Spawn, logging once if the spawn outlives the call's own timeout (capped at the stall threshold). The call's
|
|
89
|
+
* kill timer and detach gate are armed only after spawn returns, so a spawn that never returns is otherwise
|
|
90
|
+
* silent (senpi#6189). The spawn itself is awaited exactly as before.
|
|
91
|
+
*/
|
|
92
|
+
async function spawnReportingStall(ctx, request, toolCallId, execCtx) {
|
|
93
|
+
const thresholdMs = stallThresholdMs(request.timeoutMs === undefined ? undefined : request.timeoutMs / 1000);
|
|
94
|
+
const watch = watchForStall(thresholdMs, (elapsedMs) => {
|
|
95
|
+
const logger = ctx.sessionLogger ?? (defaultSessionLogger ??= createSessionLogger(getAgentDir()));
|
|
96
|
+
const sessionId = (execCtx ?? ctx.getSessionContext?.())?.sessionManager?.getSessionId();
|
|
97
|
+
logger.warn("bash_spawn_stalled", {
|
|
98
|
+
toolCallId,
|
|
99
|
+
toolName: TERMINAL_BASH_TOOL,
|
|
100
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
101
|
+
thresholdMs,
|
|
102
|
+
elapsedMs,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
return await spawnCommandSession(sessionSpawnContext(ctx), request);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
watch.stop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
79
112
|
function sessionSpawnContext(ctx) {
|
|
80
113
|
return {
|
|
81
114
|
...ctx,
|
|
@@ -198,18 +231,18 @@ function scheduleDetachedSweep(ctx, id, runtime, timeoutMs, startedAt) {
|
|
|
198
231
|
void ctx.manager.stop(id).catch(() => { });
|
|
199
232
|
}, remainingMs);
|
|
200
233
|
}
|
|
201
|
-
async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, execCtx) {
|
|
234
|
+
async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, toolCallId, execCtx) {
|
|
202
235
|
if (signal?.aborted)
|
|
203
236
|
return errorResult("Command aborted");
|
|
204
237
|
const timeoutMs = input.timeout !== undefined ? Math.trunc(input.timeout * 1000) : undefined;
|
|
205
|
-
const { id, runtime } = await
|
|
238
|
+
const { id, runtime } = await spawnReportingStall(ctx, {
|
|
206
239
|
command: input.command,
|
|
207
240
|
cols,
|
|
208
241
|
rows,
|
|
209
242
|
timeoutMs,
|
|
210
243
|
cwd,
|
|
211
244
|
envOverrides: { ...sessionEnvOverrides(ctx, execCtx), ...FOREGROUND_ENV_OVERRIDES },
|
|
212
|
-
});
|
|
245
|
+
}, toolCallId, execCtx);
|
|
213
246
|
const startedAt = Date.now();
|
|
214
247
|
const activity = `running ${input.command.slice(0, 80)}`;
|
|
215
248
|
const emitOutputUpdate = () => {
|
|
@@ -277,9 +310,7 @@ async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, exec
|
|
|
277
310
|
}
|
|
278
311
|
if (outcome === "detached") {
|
|
279
312
|
scheduleDetachedSweep(ctx, id, runtime, timeoutMs, startedAt);
|
|
280
|
-
ctx
|
|
281
|
-
if (ctx.onBackgroundExit)
|
|
282
|
-
runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));
|
|
313
|
+
trackBackgroundSession(ctx, id, runtime, backgroundStart(input, ctx.backgroundAutoReapMinutes, startedAt));
|
|
283
314
|
const delta = runtime.readDelta();
|
|
284
315
|
const partialOutput = formatTerminalToolOutput(delta.text).text || "(no output yet)";
|
|
285
316
|
const timeoutNote = input.timeout !== undefined && Number.isFinite(input.timeout)
|
|
@@ -334,19 +365,17 @@ async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, exec
|
|
|
334
365
|
},
|
|
335
366
|
});
|
|
336
367
|
}
|
|
337
|
-
async function runBackground(ctx, input, cols, rows, cwd, execCtx) {
|
|
368
|
+
async function runBackground(ctx, input, cols, rows, cwd, toolCallId, execCtx) {
|
|
338
369
|
// Background sessions are NEVER killed by `timeout` (bash-timeout injects a default into
|
|
339
370
|
// every bash call); they live until exit or kill_bash, so no timeoutMs is passed.
|
|
340
|
-
const { id, runtime } = await
|
|
371
|
+
const { id, runtime } = await spawnReportingStall(ctx, {
|
|
341
372
|
command: input.command,
|
|
342
373
|
cols,
|
|
343
374
|
rows,
|
|
344
375
|
cwd,
|
|
345
376
|
envOverrides: sessionEnvOverrides(ctx, execCtx),
|
|
346
|
-
});
|
|
347
|
-
ctx
|
|
348
|
-
if (ctx.onBackgroundExit)
|
|
349
|
-
runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));
|
|
377
|
+
}, toolCallId, execCtx);
|
|
378
|
+
trackBackgroundSession(ctx, id, runtime, backgroundStart(input, ctx.backgroundAutoReapMinutes, Date.now()));
|
|
350
379
|
// Capture any output the command emits within a short grace window (or its exit).
|
|
351
380
|
await Promise.race([delay(BACKGROUND_START_GRACE_MS), runtime.session.waitExit()]);
|
|
352
381
|
const early = formatTerminalToolOutput(runtime.readDelta().text).text;
|
|
@@ -368,13 +397,13 @@ export function createPtyBashTool(ctx) {
|
|
|
368
397
|
promptGuidelines: ["PI_* env vars = current model + session details."],
|
|
369
398
|
fullPromptGuidelines: ["You can inspect PI_* environment variables for current model and session details."],
|
|
370
399
|
parameters: ptyBashSchema,
|
|
371
|
-
async execute(
|
|
400
|
+
async execute(toolCallId, input, signal, onUpdate, execCtx) {
|
|
372
401
|
const cols = resolveDimension(input.cols, ctx.defaultCols || DEFAULT_COLS);
|
|
373
402
|
const rows = resolveDimension(input.rows, ctx.defaultRows || DEFAULT_ROWS);
|
|
374
403
|
const cwd = execCtx?.cwd;
|
|
375
404
|
if (input.run_in_background)
|
|
376
|
-
return runBackground(ctx, input, cols, rows, cwd, execCtx);
|
|
377
|
-
return runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, execCtx);
|
|
405
|
+
return runBackground(ctx, input, cols, rows, cwd, toolCallId, execCtx);
|
|
406
|
+
return runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, toolCallId, execCtx);
|
|
378
407
|
},
|
|
379
408
|
};
|
|
380
409
|
}
|
|
@@ -27,11 +27,19 @@ export type TerminalBackgroundWake = {
|
|
|
27
27
|
readonly id: string;
|
|
28
28
|
readonly description: string;
|
|
29
29
|
readonly startedAtMs: number;
|
|
30
|
+
readonly command?: string;
|
|
31
|
+
readonly autoReapMinutes?: number;
|
|
30
32
|
} | {
|
|
31
33
|
readonly event: "background_exit";
|
|
32
34
|
readonly id: string;
|
|
33
35
|
readonly exit: TerminalExitView["exitResult"];
|
|
34
36
|
readonly output: string;
|
|
37
|
+
readonly autoReapedAfterMinutes?: number;
|
|
38
|
+
} | {
|
|
39
|
+
readonly event: "background_reap_failed";
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly autoReapMinutes: number;
|
|
42
|
+
readonly reason: string;
|
|
35
43
|
};
|
|
36
44
|
export declare function lostSessionResult(id: string, generation: number): AgentToolResult<unknown>;
|
|
37
45
|
export declare function terminalChildJob(ctx: TerminalToolContext, kind: (typeof TERMINAL_CHILD_JOB_KINDS)[keyof typeof TERMINAL_CHILD_JOB_KINDS]): ToolChildJob;
|
|
@@ -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) {
|
|
@@ -2,6 +2,7 @@ import type { TextContent } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import type { TerminalSessionExit } from "@earendil-works/pi-pty";
|
|
3
3
|
import type { TerminalManager } from "../manager.ts";
|
|
4
4
|
import type { MonitorEvent, MonitorRegistry } from "../monitor-registry.ts";
|
|
5
|
+
import type { SessionLogger } from "../../../../session-log.ts";
|
|
5
6
|
import type { TimeoutAction } from "../settings.ts";
|
|
6
7
|
/** Shared dependencies handed to every terminal tool factory. */
|
|
7
8
|
export interface TerminalToolContext {
|
|
@@ -16,6 +17,8 @@ export interface TerminalToolContext {
|
|
|
16
17
|
readonly timeoutAction?: TimeoutAction;
|
|
17
18
|
/** Resolved `terminal.maxDurableMonitors`; omitted contexts admit any number of persistent monitors. */
|
|
18
19
|
readonly maxDurableMonitors?: number | "unlimited";
|
|
20
|
+
/** Resolved `terminal.backgroundAutoReapMinutes`: background sessions are stopped at this age; unset means never. */
|
|
21
|
+
readonly backgroundAutoReapMinutes?: number | undefined;
|
|
19
22
|
/** Resolve the environment for spawned sessions (mirrors core bash `getShellEnv`). */
|
|
20
23
|
readonly getEnv: () => NodeJS.ProcessEnv;
|
|
21
24
|
/**
|
|
@@ -23,12 +26,17 @@ export interface TerminalToolContext {
|
|
|
23
26
|
* expose PI_* session metadata to spawned commands like the core bash tool does.
|
|
24
27
|
*/
|
|
25
28
|
readonly getSessionContext?: () => import("../../../types.ts").ExtensionContext | undefined;
|
|
26
|
-
/**
|
|
27
|
-
|
|
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;
|
|
28
34
|
/** A person or the model wrote to a session's stdin (`bash_input`): it is interactive from now on. */
|
|
29
35
|
readonly onSessionInput?: (id: string) => void;
|
|
30
36
|
/** Notified when a background session exits, so the notify layer can wake the agent. */
|
|
31
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;
|
|
32
40
|
/** Session-scoped monitor state, sharing the terminal manager's bash-id namespace. */
|
|
33
41
|
readonly monitorRegistry?: MonitorRegistry;
|
|
34
42
|
/** Receives filtered monitor line and terminal-summary events. */
|
|
@@ -48,10 +56,14 @@ export interface TerminalToolContext {
|
|
|
48
56
|
* the server and only then lets the command run. Absent on the in-process path, which spawns exactly as before.
|
|
49
57
|
*/
|
|
50
58
|
readonly launchBarrier?: TerminalLaunchBarrier;
|
|
59
|
+
/** Where a stalled spawn is reported (senpi#6189); defaults to the agent directory's session log. */
|
|
60
|
+
readonly sessionLogger?: Pick<SessionLogger, "warn">;
|
|
51
61
|
}
|
|
52
62
|
/** What a background exit notice reads: on the tool child it is a summary the child sent, not the live session. */
|
|
53
63
|
export interface TerminalExitView {
|
|
54
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;
|
|
55
67
|
fullOutput(): string;
|
|
56
68
|
}
|
|
57
69
|
export interface TerminalLaunchBarrier {
|
|
@@ -14,6 +14,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
14
14
|
import { parse } from "es-module-lexer/js";
|
|
15
15
|
import { isCommonJsFile, isEsmByPackageType, parseImportClause, rewriteCommonJsImport, } from "./bun-extension-commonjs.js";
|
|
16
16
|
import { ExtensionSourceError } from "./bun-extension-error.js";
|
|
17
|
+
import { loadSynchronously } from "./bun-extension-load-turns.js";
|
|
17
18
|
import { createPreparedSourceCache } from "./bun-extension-prepared-cache.js";
|
|
18
19
|
import { ExtensionGenerationDisposedError, extensionNamespace, registerExtensionGraph, runtimeModulePath, } from "./bun-extension-registry.js";
|
|
19
20
|
import { hasUseStrictDirective, strictDirectiveModuleSource } from "./bun-extension-strict-directive.js";
|
|
@@ -181,7 +182,7 @@ export function createBunExtensionImporter(virtualModules, builtinRedirects = {}
|
|
|
181
182
|
const evaluating = commonJsModules.get(id);
|
|
182
183
|
if (evaluating !== undefined)
|
|
183
184
|
return evaluating.exports;
|
|
184
|
-
const result = nativeRequire(id);
|
|
185
|
+
const result = loadSynchronously(() => nativeRequire(id));
|
|
185
186
|
return commonJs.has(id) ? result.default : result;
|
|
186
187
|
},
|
|
187
188
|
evaluateCommonJs(filename, body) {
|
|
@@ -240,7 +241,7 @@ export function createBunExtensionImporter(virtualModules, builtinRedirects = {}
|
|
|
240
241
|
// A package directory loads through its main or index file, as a native import does.
|
|
241
242
|
const entry = statSync(absolute).isDirectory() ? realpathSync(Bun.resolveSync(absolute, absolute)) : absolute;
|
|
242
243
|
const { id } = fileTarget(entry);
|
|
243
|
-
const module = await import(__rewriteRelativeImportExtension(id));
|
|
244
|
+
const module = await registration.holdDuring(import(__rewriteRelativeImportExtension(id)));
|
|
244
245
|
const factory = module.default;
|
|
245
246
|
if (typeof factory !== "function")
|
|
246
247
|
return factory;
|
|
@@ -255,7 +256,7 @@ export function createBunExtensionImporter(virtualModules, builtinRedirects = {}
|
|
|
255
256
|
async importNamespace(path) {
|
|
256
257
|
assertActive();
|
|
257
258
|
const { id } = fileTarget(realpathSync(resolve(path)));
|
|
258
|
-
return import(__rewriteRelativeImportExtension(id));
|
|
259
|
+
return registration.holdDuring(import(__rewriteRelativeImportExtension(id)));
|
|
259
260
|
},
|
|
260
261
|
// Capture identity from the transpiler input, never from a later disk snapshot.
|
|
261
262
|
compiledSources() {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function loadInTurn<T>(load: () => T): T | Promise<T>;
|
|
2
|
+
/** Runs `require` so every module it loads is served synchronously, as a CommonJS require needs. */
|
|
3
|
+
export declare function loadSynchronously<T>(require: () => T): T;
|
|
4
|
+
//# sourceMappingURL=bun-extension-load-turns.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One extension module per event-loop turn (senpi#6199). Bun fetches the whole static graph of an `import()` inside
|
|
3
|
+
* one microtask drain, so a synchronous `onLoad` holds the loop for every module of the graph: a host compiling an
|
|
4
|
+
* extension (its boot load, a warm, an open) answered no socket until the last one. During an import each load waits
|
|
5
|
+
* for its own turn instead; a synchronous `require()` cannot take a promise, so its loads still run at once.
|
|
6
|
+
*/
|
|
7
|
+
const queued = [];
|
|
8
|
+
let draining = false;
|
|
9
|
+
let synchronousDepth = 0;
|
|
10
|
+
function drainOne() {
|
|
11
|
+
queued.shift()?.();
|
|
12
|
+
if (queued.length > 0)
|
|
13
|
+
setImmediate(drainOne);
|
|
14
|
+
else
|
|
15
|
+
draining = false;
|
|
16
|
+
}
|
|
17
|
+
export function loadInTurn(load) {
|
|
18
|
+
if (synchronousDepth > 0)
|
|
19
|
+
return load();
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
queued.push(() => {
|
|
22
|
+
try {
|
|
23
|
+
resolve(load());
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
reject(error);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (draining)
|
|
30
|
+
return;
|
|
31
|
+
draining = true;
|
|
32
|
+
setImmediate(drainOne);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Runs `require` so every module it loads is served synchronously, as a CommonJS require needs. */
|
|
36
|
+
export function loadSynchronously(require) {
|
|
37
|
+
synchronousDepth++;
|
|
38
|
+
try {
|
|
39
|
+
return require();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
synchronousDepth--;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=bun-extension-load-turns.js.map
|
|
@@ -29,6 +29,7 @@ export declare class ExtensionGenerationDisposedError extends Error {
|
|
|
29
29
|
}
|
|
30
30
|
export declare function registerExtensionGraph(graph: ExtensionGraph, virtualModules: Readonly<Record<string, Readonly<Record<string, unknown>>>>): {
|
|
31
31
|
generation: string;
|
|
32
|
+
holdDuring<T>(pending: Promise<T>): Promise<T>;
|
|
32
33
|
dispose(): void;
|
|
33
34
|
};
|
|
34
35
|
/** Internal lifecycle diagnostics, shared by regression and compiled probes. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { loadInTurn } from "./bun-extension-load-turns.js";
|
|
4
5
|
import { publishRuntimeMetadata } from "./extension-runtime-module.js";
|
|
5
6
|
// esbuild reads an import() only when its second argument is fully static, and these
|
|
6
7
|
// attributes are whatever the extension wrote. The call stays dynamic either way, so
|
|
@@ -33,6 +34,10 @@ function splitModuleId(id) {
|
|
|
33
34
|
}
|
|
34
35
|
const graphs = new Map();
|
|
35
36
|
const collected = new FinalizationRegistry((generation) => graphs.delete(generation));
|
|
37
|
+
// Bun serves an import()'s modules one loop turn at a time (senpi#6199) and its loader keeps no reference to the graph
|
|
38
|
+
// serving them: a graph whose importer was already dropped was collected between two loads and failed the import as
|
|
39
|
+
// disposed (senpi#6208). A graph is held strongly while any import it serves is in flight.
|
|
40
|
+
const importing = new Map();
|
|
36
41
|
let nextGeneration = 0;
|
|
37
42
|
let installed = false;
|
|
38
43
|
let pluginRegistrations = 0;
|
|
@@ -129,7 +134,7 @@ function installRegistry(virtualModules) {
|
|
|
129
134
|
const parsed = splitModuleId(path);
|
|
130
135
|
if (parsed === undefined)
|
|
131
136
|
throw new ExtensionModuleIdError(path);
|
|
132
|
-
return graphFor(parsed.generation).load(parsed.filename);
|
|
137
|
+
return loadInTurn(() => graphFor(parsed.generation).load(parsed.filename));
|
|
133
138
|
});
|
|
134
139
|
installed = true;
|
|
135
140
|
},
|
|
@@ -143,6 +148,19 @@ export function registerExtensionGraph(graph, virtualModules) {
|
|
|
143
148
|
collected.register(graph, generation, graph);
|
|
144
149
|
return {
|
|
145
150
|
generation,
|
|
151
|
+
async holdDuring(pending) {
|
|
152
|
+
importing.set(graph, (importing.get(graph) ?? 0) + 1);
|
|
153
|
+
try {
|
|
154
|
+
return await pending;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
const remaining = (importing.get(graph) ?? 1) - 1;
|
|
158
|
+
if (remaining === 0)
|
|
159
|
+
importing.delete(graph);
|
|
160
|
+
else
|
|
161
|
+
importing.set(graph, remaining);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
146
164
|
dispose() {
|
|
147
165
|
graphs.delete(generation);
|
|
148
166
|
collected.unregister(graph);
|
|
@@ -53,6 +53,7 @@ import { cachedExtensionFactory, extensionModuleImporter, rememberExtensionFacto
|
|
|
53
53
|
import { kernelToolInvokerStorage } from "./kernel-tools-context.js";
|
|
54
54
|
import { createLifecycleApi } from "./lifecycle-api.js";
|
|
55
55
|
import { withProviderLoginStart } from "./provider-login-start.js";
|
|
56
|
+
import { createSessionDialogApi } from "./session-dialog-api.js";
|
|
56
57
|
import { StaleExtensionContextError } from "./stale-context-error.js";
|
|
57
58
|
import { DEFAULT_EXTENSION_SESSION_PROFILE, EMPTY_SESSION_CONTEXT } from "./types.js";
|
|
58
59
|
function sessionProfile(options) {
|
|
@@ -573,6 +574,10 @@ function createExtensionAPI(extension, runtime, cwd, eventBus, session) {
|
|
|
573
574
|
}
|
|
574
575
|
return readPermissionPreset(sessionManager);
|
|
575
576
|
},
|
|
577
|
+
sessionDialog: createSessionDialogApi(() => {
|
|
578
|
+
assertActive();
|
|
579
|
+
return runtime.createContext().ui;
|
|
580
|
+
}),
|
|
576
581
|
getSubscriptionLimits() {
|
|
577
582
|
assertActive();
|
|
578
583
|
return subscriptionLimits.snapshot();
|
|
@@ -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)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExtensionUIContext } from "./types.ts";
|
|
2
|
+
/** Dialogs addressed to every client attached to the session (`pi.sessionDialog`). */
|
|
3
|
+
export type SessionDialogApi = Pick<ExtensionUIContext, "select" | "confirm" | "input" | "editor">;
|
|
4
|
+
export declare function createSessionDialogApi(currentUI: () => ExtensionUIContext): SessionDialogApi;
|
|
5
|
+
//# sourceMappingURL=session-dialog-api.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const toSession = (opts) => ({
|
|
2
|
+
...opts,
|
|
3
|
+
audience: "session",
|
|
4
|
+
});
|
|
5
|
+
export function createSessionDialogApi(currentUI) {
|
|
6
|
+
return {
|
|
7
|
+
select: (title, options, opts) => currentUI().select(title, options, toSession(opts)),
|
|
8
|
+
confirm: (title, message, opts) => currentUI().confirm(title, message, toSession(opts)),
|
|
9
|
+
input: (title, placeholder, opts) => currentUI().input(title, placeholder, toSession(opts)),
|
|
10
|
+
editor: (title, prefill, opts) => currentUI().editor(title, prefill, toSession(opts)),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=session-dialog-api.js.map
|
|
@@ -51,6 +51,7 @@ import type { PermissionPresetName } from "./builtin/permission-system/types.ts"
|
|
|
51
51
|
import type { EvalHandleHost } from "./eval-handle-host.ts";
|
|
52
52
|
import type { ExtensionKernelTools } from "./kernel-tools-context.ts";
|
|
53
53
|
import type { SessionControlActions, SessionControlWakeEvent } from "./session-control-types.ts";
|
|
54
|
+
import type { SessionDialogApi } from "./session-dialog-api.ts";
|
|
54
55
|
import type { UserActivityKind } from "./user-activity.ts";
|
|
55
56
|
export type { ExecOptions, ExecResult } from "../exec.ts";
|
|
56
57
|
export type { AppKeybinding, KeybindingsManager } from "../keybindings.ts";
|
|
@@ -72,6 +73,11 @@ export interface ExtensionUIDialogOptions {
|
|
|
72
73
|
toolCallId?: string;
|
|
73
74
|
/** Set with `toolCallId` when another tool (for example a codemode script) issued that call. */
|
|
74
75
|
parentToolCallId?: string;
|
|
76
|
+
/**
|
|
77
|
+
* `session`: address the dialog to every client attached to the session instead of the connection whose command
|
|
78
|
+
* is running, so a prompt raised after that command returned still reaches someone who can answer. RPC field.
|
|
79
|
+
*/
|
|
80
|
+
audience?: "session";
|
|
75
81
|
}
|
|
76
82
|
/** A guarded private single-line input; callers on older hosts must check capabilities first. */
|
|
77
83
|
export interface ExtensionUIInputOptions extends ExtensionUIDialogOptions {
|
|
@@ -247,7 +253,7 @@ export interface ExtensionUIContext {
|
|
|
247
253
|
/** Get the current text from the core input editor. */
|
|
248
254
|
getEditorText(): string;
|
|
249
255
|
/** Show a multi-line editor for text editing. */
|
|
250
|
-
editor(title: string, prefill?: string): Promise<string | undefined>;
|
|
256
|
+
editor(title: string, prefill?: string, opts?: ExtensionUIDialogOptions): Promise<string | undefined>;
|
|
251
257
|
/** Stack additional autocomplete behavior on top of the built-in provider. */
|
|
252
258
|
addAutocompleteProvider(factory: AutocompleteProviderFactory): void;
|
|
253
259
|
/**
|
|
@@ -2245,6 +2251,13 @@ export interface ExtensionAPI {
|
|
|
2245
2251
|
* permission system is not running for the session, its setup failed, or the session is not bound yet.
|
|
2246
2252
|
*/
|
|
2247
2253
|
getPermissionPreset(): PermissionPresetName | undefined;
|
|
2254
|
+
/**
|
|
2255
|
+
* Dialogs addressed to every client attached to this session, not only to the connection whose command is
|
|
2256
|
+
* running: a prompt raised after that command returned (a background child's permission request a task runner
|
|
2257
|
+
* forwards) still reaches someone who can answer. The first answer wins. Secret inputs stay with the raising
|
|
2258
|
+
* connection. In the terminal UI they show like any dialog.
|
|
2259
|
+
*/
|
|
2260
|
+
sessionDialog: SessionDialogApi;
|
|
2248
2261
|
/**
|
|
2249
2262
|
* Every subscription account's latest limit windows: the records `get_subscription_limits` serves, fed by
|
|
2250
2263
|
* the engine's usage poll and by turns (senpi#3684). An account nothing has reported for is absent.
|
|
@@ -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
|