@code-yeongyu/senpi 2026.11.6-2 → 2026.11.6-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/bundle/chunks/{bun-extension-importer-RCGPQTI2.js → bun-extension-importer-GQ6IKEXD.js} +2 -2
- package/dist/bundle/chunks/{chunk-QN2BHP3M.js → chunk-44BJPS5I.js} +1 -1
- package/dist/bundle/chunks/{chunk-C7OKUC7L.js → chunk-4BATXPB7.js} +1 -1
- package/dist/bundle/chunks/{chunk-RQ6CHXQ2.js → chunk-5LKB732P.js} +1 -1
- package/dist/bundle/chunks/{chunk-X5ZVYNOL.js → chunk-6CEDL5S2.js} +1 -1
- package/dist/bundle/chunks/{chunk-HLM46XEB.js → chunk-7DRWPZ3N.js} +1 -1
- package/dist/bundle/chunks/{chunk-7PSBD5KS.js → chunk-BASS47UD.js} +1 -1
- package/dist/bundle/chunks/{chunk-ECW7VBMB.js → chunk-CFEHJ4VU.js} +1 -1
- package/dist/bundle/chunks/{chunk-TIPKAMSS.js → chunk-EO2VPZ2K.js} +1 -1
- package/dist/bundle/chunks/{chunk-LNPAT22L.js → chunk-F5JLDCYD.js} +8 -7
- package/dist/bundle/chunks/{chunk-GSKCWXXG.js → chunk-JMQOLOC2.js} +1 -1
- package/dist/bundle/chunks/chunk-L7ZIS3MC.js +106 -0
- package/dist/bundle/chunks/{chunk-47PPZKPO.js → chunk-LM6DOWQH.js} +4 -4
- package/dist/bundle/chunks/chunk-LPYBKMPT.js +2 -0
- package/dist/bundle/chunks/{chunk-GNSMNSL7.js → chunk-LUZVGPQ7.js} +2 -2
- package/dist/bundle/chunks/{chunk-7III44PV.js → chunk-MJTCZUXS.js} +1 -1
- package/dist/bundle/chunks/{chunk-2FDW2LQL.js → chunk-SB6MXNO5.js} +1 -1
- package/dist/bundle/chunks/chunk-UODSYQ2D.js +2 -0
- package/dist/bundle/chunks/{chunk-D5MRJWUK.js → chunk-V2QAYGOK.js} +216 -224
- package/dist/bundle/chunks/{chunk-WGRKM3N6.js → chunk-X53PD4ZC.js} +1 -1
- package/dist/bundle/chunks/{cli-main-T74YJGIF.js → cli-main-RWHDZKTQ.js} +1 -1
- package/dist/bundle/chunks/{dispatch-BAK2YZKD.js → dispatch-VDIYSXQN.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-AYRUGE2A.js → hand-entry-G5LYF62Q.js} +1 -1
- package/dist/bundle/chunks/{host-command-YRXZE4TV.js → host-command-I2JZ5JM7.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-N7IN7ZDV.js → host-session-command-Y52NFPP2.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-PA7DGVS6.js +2 -0
- package/dist/bundle/chunks/main-OBL6375C.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-XSUJDHFZ.js → multi-session-host-NM6I5BWZ.js} +20 -17
- package/dist/bundle/chunks/{package-manager-cli-GLUNPQJU.js → package-manager-cli-GNRQ5MVR.js} +1 -1
- package/dist/bundle/chunks/{role-RRFFHZF5.js → role-F56BS2ZQ.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-G4KIP7XS.js +2 -0
- package/dist/bundle/chunks/{run-attached-tui-W236SEOR.js → run-attached-tui-XTGPAGYM.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-OBXYK4PI.js → runtime-session-VO3X4OE3.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-B2EJTBOE.js → schedule-command-L4M3TSQ3.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-YIMLHGLG.js → session-control-endpoint-V46WMCDF.js} +1 -1
- package/dist/bundle/chunks/{session-picker-E6ZONUJB.js → session-picker-AG6KSR5A.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +87 -83
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session-runtime.d.ts +2 -0
- package/dist/core/agent-session-runtime.js +9 -2
- package/dist/core/agent-session.d.ts +24 -3
- package/dist/core/agent-session.js +94 -28
- package/dist/core/credential-pool/rotation-slots.d.ts +3 -3
- package/dist/core/credential-pool/state-store.d.ts +1 -1
- package/dist/core/egress/hpke.d.ts +63 -0
- package/dist/core/egress/hpke.js +188 -0
- package/dist/core/egress/placement-claim.d.ts +69 -0
- package/dist/core/egress/placement-claim.js +115 -0
- package/dist/core/egress/pool-state.d.ts +18 -0
- package/dist/core/egress/pool-state.js +5 -0
- package/dist/core/egress/sealed-format.d.ts +101 -0
- package/dist/core/egress/sealed-format.js +174 -0
- package/dist/core/egress/wire.d.ts +226 -0
- package/dist/core/egress/wire.js +47 -0
- package/dist/core/extensions/builtin/ask-user/escape-residue.d.ts +50 -0
- package/dist/core/extensions/builtin/ask-user/escape-residue.js +113 -0
- package/dist/core/extensions/builtin/ask-user/extension.js +7 -1
- package/dist/core/extensions/builtin/ask-user/resume.js +2 -1
- package/dist/core/extensions/builtin/ask-user/tool.d.ts +3 -0
- package/dist/core/extensions/builtin/ask-user/tool.js +8 -0
- package/dist/core/extensions/builtin/goal/index.js +46 -0
- package/dist/core/extensions/builtin/goal/stop-pause.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/stop-pause.js +4 -2
- package/dist/core/extensions/builtin/permission-system/index.js +10 -4
- package/dist/core/extensions/builtin/permission-system/preset-registry.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/preset-registry.js +11 -0
- package/dist/core/extensions/builtin/permission-system/settings.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/settings.js +10 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.js +38 -9
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +3 -0
- package/dist/core/extensions/bun-extension-importer.js +4 -3
- package/dist/core/extensions/bun-extension-load-turns.d.ts +4 -0
- package/dist/core/extensions/bun-extension-load-turns.js +45 -0
- package/dist/core/extensions/bun-extension-registry.d.ts +1 -0
- package/dist/core/extensions/bun-extension-registry.js +19 -1
- package/dist/core/extensions/loader.js +5 -0
- package/dist/core/extensions/session-dialog-api.d.ts +5 -0
- package/dist/core/extensions/session-dialog-api.js +13 -0
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/permission-decision.d.ts +22 -0
- package/dist/core/permission-decision.js +53 -0
- package/dist/core/permission-mode.d.ts +56 -0
- package/dist/core/permission-mode.js +133 -0
- package/dist/core/retry-fallback/controller-types.d.ts +6 -0
- package/dist/core/retry-fallback/controller.d.ts +8 -0
- package/dist/core/retry-fallback/controller.js +31 -3
- package/dist/core/retry-fallback/unusable.d.ts +23 -1
- package/dist/core/retry-fallback/unusable.js +80 -1
- package/dist/core/retry-fallback/usage-limit.d.ts +1 -1
- package/dist/core/retry-fallback/usage-limit.js +6 -3
- package/dist/core/sdk.js +2 -0
- package/dist/core/session-log.js +1 -1
- package/dist/core/stall-warning.d.ts +41 -0
- package/dist/core/stall-warning.js +72 -0
- package/dist/core/turn-origin.d.ts +27 -0
- package/dist/core/turn-origin.js +54 -0
- package/dist/modes/rpc/client-input-handler.d.ts +2 -0
- package/dist/modes/rpc/client-input-handler.js +2 -0
- package/dist/modes/rpc/connection-handler.js +16 -5
- package/dist/modes/rpc/connection-roles.d.ts +6 -0
- package/dist/modes/rpc/connection-roles.js +13 -0
- package/dist/modes/rpc/delegated-drive-origin.d.ts +9 -0
- package/dist/modes/rpc/delegated-drive-origin.js +16 -0
- package/dist/modes/rpc/effect-commands.d.ts +6 -0
- package/dist/modes/rpc/host-bare-endpoint-registration.d.ts +7 -0
- package/dist/modes/rpc/host-bare-endpoint-registration.js +35 -0
- package/dist/modes/rpc/host-capabilities.d.ts +2 -0
- package/dist/modes/rpc/host-capabilities.js +3 -0
- package/dist/modes/rpc/host-handover-parked.d.ts +11 -0
- package/dist/modes/rpc/host-handover-parked.js +17 -0
- package/dist/modes/rpc/host-handover-reopen.js +18 -6
- package/dist/modes/rpc/host-handover-resident-writer.d.ts +16 -0
- package/dist/modes/rpc/host-handover-resident-writer.js +57 -0
- package/dist/modes/rpc/host-warm.d.ts +16 -1
- package/dist/modes/rpc/host-warm.js +33 -6
- package/dist/modes/rpc/method-classes-schema-session.d.ts +9 -0
- package/dist/modes/rpc/method-classes-schema-session.js +1 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +9 -0
- package/dist/modes/rpc/method-classes.d.ts +1 -0
- package/dist/modes/rpc/method-classes.js +2 -0
- package/dist/modes/rpc/multi-session-host.js +6 -1
- package/dist/modes/rpc/permission-preset-command.d.ts +6 -0
- package/dist/modes/rpc/permission-preset-command.js +27 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +4 -0
- package/dist/modes/rpc/rpc-session-event-types.d.ts +5 -2
- package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -1
- package/dist/modes/rpc/rpc-session-state.js +2 -0
- package/dist/modes/rpc/session-attachment-commands.js +3 -3
- package/dist/modes/rpc/session-event-writer-connections.js +1 -0
- package/dist/modes/rpc/session-event-writer.d.ts +1 -1
- package/dist/modes/rpc/session-registry-attach.d.ts +0 -9
- package/dist/modes/rpc/session-registry-attach.js +16 -3
- package/dist/modes/rpc/session-registry-types.d.ts +2 -0
- package/dist/modes/rpc/session-registry.d.ts +2 -0
- package/dist/modes/rpc/session-registry.js +16 -3
- package/dist/modes/rpc/session-router-close.js +43 -6
- package/dist/modes/rpc/session-router-context.js +2 -0
- package/dist/modes/rpc/session-router-occupancy.js +2 -11
- package/dist/modes/rpc/session-router-open-profile.js +4 -0
- package/dist/modes/rpc/session-router-open.d.ts +1 -1
- package/dist/modes/rpc/session-router-open.js +6 -17
- package/dist/modes/rpc/session-router-residency.d.ts +24 -0
- package/dist/modes/rpc/session-router-residency.js +90 -0
- package/dist/modes/rpc/session-router-routing.js +7 -1
- package/dist/modes/rpc/session-status.js +4 -1
- package/dist/modes/rpc/session-worker.js +4 -0
- package/dist/modes/rpc/worker-session-registry.js +5 -2
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +12 -2
- package/docs/extensions.md +12 -0
- package/docs/rpc.md +72 -12
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-C7HEEBFH.js +0 -2
- package/dist/bundle/chunks/chunk-XCMII2VQ.js +0 -98
- package/dist/bundle/chunks/interactive-mode-B5SQDLH7.js +0 -2
- package/dist/bundle/chunks/main-UQFVFCNY.js +0 -2
- package/dist/bundle/chunks/rpc-mode-JYN3E7P7.js +0 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { releaseQuestionInput } from "../../../question-input.js";
|
|
2
2
|
import { WAKE_SOURCE_STATE_EVENT } from "../monitor-state-event.js";
|
|
3
|
+
import { AskUserEscapeResidueError, escapeResidueNote, findEscapeResidue, refuseEscapeResidue, } from "./escape-residue.js";
|
|
3
4
|
import { TOOL_NAMES } from "./family.js";
|
|
4
5
|
import { formatGatedCancellation, formatModelAnswer, formatResultDetails, formatUserMessage, showsPartialAnswers, } from "./format.js";
|
|
5
6
|
import { ASK_USER_ASKED_EVENT, ASK_USER_QUESTION_ENTRY, ASK_USER_SETTLEMENT_ENTRY, emitAskUserClosed, emitAskUserNotification, } from "./notify.js";
|
|
@@ -329,6 +330,13 @@ export function createAskUserTool(variant, pi, state) {
|
|
|
329
330
|
emitAskUserClosed(pi, request.requestId, unavailable);
|
|
330
331
|
return result(variant, unavailable, request);
|
|
331
332
|
}
|
|
333
|
+
const residue = findEscapeResidue(request.questions);
|
|
334
|
+
if (refuseEscapeResidue(state.escapeResidue, residue.length > 0)) {
|
|
335
|
+
emitAskUserClosed(pi, request.requestId, unavailable);
|
|
336
|
+
throw new AskUserEscapeResidueError(residue);
|
|
337
|
+
}
|
|
338
|
+
if (residue.length > 0)
|
|
339
|
+
ctx.ui.notify(escapeResidueNote(residue), "warning");
|
|
332
340
|
const completion = startQuestion(pi, ctx, request, signal, state, variant);
|
|
333
341
|
if (!request.waitForAnswer)
|
|
334
342
|
return {
|
|
@@ -175,6 +175,13 @@ export default function goalExtension(pi) {
|
|
|
175
175
|
clearAgentGoalAccounting();
|
|
176
176
|
}
|
|
177
177
|
refreshGoalUi(ctx, goal);
|
|
178
|
+
// A session reopened under a live Stop keeps its goal paused until the Stop is released.
|
|
179
|
+
if (await pauseGoalWithHeldSession(ctx))
|
|
180
|
+
return;
|
|
181
|
+
// A goal a live Stop paused (a crashed run included) resumes with the session once the Stop is released, so the
|
|
182
|
+
// resume prompt would only restart its counter under the hold.
|
|
183
|
+
if (goal?.status === "paused" && stopHoldLive(ctx) && pausedByStop(ctx, goal.id))
|
|
184
|
+
return;
|
|
178
185
|
// Opening the session earns one reminder; renders and extension reloads do not.
|
|
179
186
|
// Stale stops retain active status, unlike user-paused or blocking stops.
|
|
180
187
|
if ((event.reason === "startup" || event.reason === "resume") &&
|
|
@@ -218,6 +225,13 @@ export default function goalExtension(pi) {
|
|
|
218
225
|
}
|
|
219
226
|
}
|
|
220
227
|
});
|
|
228
|
+
// A Stop that lands while no turn runs emits no `session_abort`, so the goal pauses and resumes with the session here.
|
|
229
|
+
pi.on("session_hold_changed", async (event, ctx) => {
|
|
230
|
+
if (event.change === "paused")
|
|
231
|
+
await pauseGoalWithHeldSession(ctx);
|
|
232
|
+
else
|
|
233
|
+
await resumeGoalWithReleasedSession(ctx);
|
|
234
|
+
});
|
|
221
235
|
pi.on("held_wakes_released", async (event, ctx) => {
|
|
222
236
|
if (!event.sources.goal)
|
|
223
237
|
return;
|
|
@@ -303,6 +317,11 @@ export default function goalExtension(pi) {
|
|
|
303
317
|
if (event.aborted === true && event.abortSource === "user" && goal?.status === "active") {
|
|
304
318
|
goal = await pauseOrBlockOnUserAbort(ctx, goal.id);
|
|
305
319
|
}
|
|
320
|
+
else if (goal?.status === "active" && goal.continuationStoppedAt === undefined && stopHoldLive(ctx)) {
|
|
321
|
+
// A Stop that joined this turn's agent_end boundary leaves the turn un-aborted; the goal still pauses with it.
|
|
322
|
+
markStopPaused(pi, goal.id);
|
|
323
|
+
goal = await updateGoal(goalStoreRef(ctx), { status: "paused" }, "user");
|
|
324
|
+
}
|
|
306
325
|
if (goal?.status === "active") {
|
|
307
326
|
beginAgentGoalAccounting(goal);
|
|
308
327
|
}
|
|
@@ -404,6 +423,33 @@ export default function goalExtension(pi) {
|
|
|
404
423
|
return;
|
|
405
424
|
syncContinuationGoal(ctx, continuedGoal);
|
|
406
425
|
}
|
|
426
|
+
/** Close the accounting window at the Stop and pause an idle active goal; a running turn pauses at its `agent_end`. */
|
|
427
|
+
async function pauseGoalWithHeldSession(ctx) {
|
|
428
|
+
if (agentTurnInProgress || !stopHoldLive(ctx))
|
|
429
|
+
return false;
|
|
430
|
+
const goal = await readGoal(goalStoreRef(ctx));
|
|
431
|
+
if (goal?.status !== "active" || goal.continuationStoppedAt !== undefined)
|
|
432
|
+
return false;
|
|
433
|
+
const accounted = await accountCurrentAgentTurn(ctx, "active");
|
|
434
|
+
if (accounted?.status !== "active")
|
|
435
|
+
return false;
|
|
436
|
+
markStopPaused(pi, accounted.id);
|
|
437
|
+
const paused = await updateGoal(goalStoreRef(ctx), { status: "paused" }, "user");
|
|
438
|
+
clearAgentGoalAccounting();
|
|
439
|
+
refreshGoalUiBestEffort(ctx, paused);
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
async function resumeGoalWithReleasedSession(ctx) {
|
|
443
|
+
if (stopHoldLive(ctx))
|
|
444
|
+
return;
|
|
445
|
+
const goal = await readGoal(goalStoreRef(ctx));
|
|
446
|
+
if (goal?.status !== "paused" || !pausedByStop(ctx, goal.id))
|
|
447
|
+
return;
|
|
448
|
+
markStopResumed(pi, goal.id);
|
|
449
|
+
const resumed = await updateGoal(goalStoreRef(ctx), { status: "active" }, "user");
|
|
450
|
+
beginAgentGoalAccounting(resumed);
|
|
451
|
+
refreshGoalUiBestEffort(ctx, resumed);
|
|
452
|
+
}
|
|
407
453
|
function beginAgentGoalAccounting(goal) {
|
|
408
454
|
// Goal usage accrues only while its accounting window is open. A stale stop
|
|
409
455
|
// excludes extension-driven turns, like other inactive goals, until input or resume.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "../../types.ts";
|
|
2
2
|
export declare const GOAL_STOP_PAUSED_ENTRY = "goal-stop-paused";
|
|
3
3
|
export declare const GOAL_STOP_RESUMED_ENTRY = "goal-stop-resumed";
|
|
4
|
+
/** A user Stop, or a parent's Stop inherited by this child, holds the session paused; an interrupted run does not. */
|
|
4
5
|
export declare function stopHoldLive(ctx: ExtensionContext): boolean;
|
|
5
6
|
/** The session reopened over a run that never finished: its process died mid-turn (senpi#4683, senpi#5755). */
|
|
6
7
|
export declare function interruptedRunHeld(ctx: ExtensionContext): boolean;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export const GOAL_STOP_PAUSED_ENTRY = "goal-stop-paused";
|
|
2
2
|
export const GOAL_STOP_RESUMED_ENTRY = "goal-stop-resumed";
|
|
3
|
+
/** A user Stop, or a parent's Stop inherited by this child, holds the session paused; an interrupted run does not. */
|
|
3
4
|
export function stopHoldLive(ctx) {
|
|
4
|
-
return (ctx
|
|
5
|
-
|
|
5
|
+
return (ctx
|
|
6
|
+
.getPauseState?.()
|
|
7
|
+
.holds.some((hold) => (hold.kind === "session_hold" && hold.key !== "interrupted-run") || hold.kind === "inherited_hold") === true);
|
|
6
8
|
}
|
|
7
9
|
/** The session reopened over a run that never finished: its process died mid-turn (senpi#4683, senpi#5755). */
|
|
8
10
|
export function interruptedRunHeld(ctx) {
|
|
@@ -13,7 +13,7 @@ import { INTERNAL_PERMISSION_TOOLS } from "./internal-tools.js";
|
|
|
13
13
|
import { handleNoUI } from "./non-interactive.js";
|
|
14
14
|
import { createBuiltinParserRegistry, permissionRequestsFor, toolOwnedPermissionRequests, } from "./parsers.js";
|
|
15
15
|
import { sharedPathIdentityResolver } from "./path-identity-host.js";
|
|
16
|
-
import { registerPermissionPresetReader } from "./preset-registry.js";
|
|
16
|
+
import { readDecisionPresetOverride, registerPermissionPresetReader } from "./preset-registry.js";
|
|
17
17
|
import { showPermissionPrompt } from "./prompt.js";
|
|
18
18
|
import { PermissionService } from "./service.js";
|
|
19
19
|
import { loadPermissionSettings } from "./settings.js";
|
|
@@ -184,7 +184,7 @@ export default function permissionSystemExtension(pi) {
|
|
|
184
184
|
activeSettingsManager = settingsManager;
|
|
185
185
|
const permissionFlag = pi.getFlag("permission");
|
|
186
186
|
loadedPermissionFlag = permissionFlag;
|
|
187
|
-
const permissionPresetFlag =
|
|
187
|
+
const permissionPresetFlag = decisionPresetFlag();
|
|
188
188
|
loadedPresetFlag = { value: permissionPresetFlag };
|
|
189
189
|
cliRuleset = typeof permissionFlag === "string" ? parsePermissionFlag(permissionFlag) : [];
|
|
190
190
|
const cliPreset = typeof permissionPresetFlag === "string" ? parsePermissionPresetFlag(permissionPresetFlag) : undefined;
|
|
@@ -202,6 +202,13 @@ export default function permissionSystemExtension(pi) {
|
|
|
202
202
|
if (cellSession)
|
|
203
203
|
updateCellEnforcement(cellSession, staticRuleset, activePreset);
|
|
204
204
|
};
|
|
205
|
+
// The core's delegated-default override wins over a valid or absent preset flag; an invalid one keeps refusing.
|
|
206
|
+
const decisionPresetFlag = () => {
|
|
207
|
+
const configured = pi.getFlag("permission-preset");
|
|
208
|
+
if (typeof configured === "string" && !parsePermissionPresetFlag(configured))
|
|
209
|
+
return configured;
|
|
210
|
+
return (cellSession === undefined ? undefined : readDecisionPresetOverride(cellSession)) ?? configured;
|
|
211
|
+
};
|
|
205
212
|
const applyToolDenials = (onlyWhenNarrowed = false) => {
|
|
206
213
|
const allTools = pi.getAllTools().map((tool) => tool.name);
|
|
207
214
|
const disabledTools = disabled(allTools, staticRuleset);
|
|
@@ -215,8 +222,7 @@ export default function permissionSystemExtension(pi) {
|
|
|
215
222
|
// the rules follow from the next tool call on, and a failed reload refuses calls like session_start.
|
|
216
223
|
const followPresetFlag = (cwd, agentDir) => {
|
|
217
224
|
if (loadedPresetFlag === undefined ||
|
|
218
|
-
(loadedPresetFlag.value === pi.getFlag("permission
|
|
219
|
-
loadedPermissionFlag === pi.getFlag("permission"))) {
|
|
225
|
+
(loadedPresetFlag.value === decisionPresetFlag() && loadedPermissionFlag === pi.getFlag("permission"))) {
|
|
220
226
|
return;
|
|
221
227
|
}
|
|
222
228
|
const sessionApproved = service ? service.getSessionApproved() : [];
|
|
@@ -8,5 +8,7 @@ type PresetReader = () => PermissionPresetName | undefined;
|
|
|
8
8
|
export declare function registerPermissionPresetReader(session: object, read: PresetReader): () => void;
|
|
9
9
|
/** `undefined` when no permission system runs for the session, or its setup failed. */
|
|
10
10
|
export declare function readPermissionPreset(session: object): PermissionPresetName | undefined;
|
|
11
|
+
export declare function registerDecisionPresetOverride(session: object, read: PresetReader): void;
|
|
12
|
+
export declare function readDecisionPresetOverride(session: object): PermissionPresetName | undefined;
|
|
11
13
|
export {};
|
|
12
14
|
//# sourceMappingURL=preset-registry.d.ts.map
|
|
@@ -10,4 +10,15 @@ export function registerPermissionPresetReader(session, read) {
|
|
|
10
10
|
export function readPermissionPreset(session) {
|
|
11
11
|
return readers.get(session)?.();
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The preset a gated decision must use instead of the configured one, read at EACH decision (the core's
|
|
15
|
+
* delegated-default rule: `ask` for delegated-origin work on a session no client named a mode for).
|
|
16
|
+
*/
|
|
17
|
+
const decisionOverrides = new WeakMap();
|
|
18
|
+
export function registerDecisionPresetOverride(session, read) {
|
|
19
|
+
decisionOverrides.set(session, read);
|
|
20
|
+
}
|
|
21
|
+
export function readDecisionPresetOverride(session) {
|
|
22
|
+
return decisionOverrides.get(session)?.();
|
|
23
|
+
}
|
|
13
24
|
//# sourceMappingURL=preset-registry.js.map
|
|
@@ -17,4 +17,6 @@ export declare function loadPermissionSettings(settingsManager: SettingsManager,
|
|
|
17
17
|
approved: Ruleset;
|
|
18
18
|
preset: PermissionPresetName;
|
|
19
19
|
};
|
|
20
|
+
/** The preset `loadPermissionSettings` would enforce, without loading rules; throws on an invalid settings preset. */
|
|
21
|
+
export declare function resolvePermissionPreset(settingsManager: SettingsManager, cliPresetOverride?: PermissionPresetName): PermissionPresetName;
|
|
20
22
|
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -27,9 +27,18 @@ export function loadPermissionSettings(settingsManager, cliOverride, projectDir,
|
|
|
27
27
|
const cliPresetRuleset = cliPresetOverride && cliPresetOverride !== DEFAULT_PERMISSION_PRESET ? rulesForPreset(cliPresetOverride) : [];
|
|
28
28
|
const staticRuleset = merge(rulesForPreset(DEFAULT_PERMISSION_PRESET), globalPresetRuleset, globalRuleset, projectPresetRuleset, projectRuleset, cliPresetRuleset, cliOverride);
|
|
29
29
|
const approved = agentDir === undefined ? [] : loadApproved(agentDir, projectDir);
|
|
30
|
-
const preset = cliPresetOverride
|
|
30
|
+
const preset = pickPermissionPreset(cliPresetOverride, projectPreset, globalPreset);
|
|
31
31
|
return { staticRuleset, approved, preset };
|
|
32
32
|
}
|
|
33
|
+
function pickPermissionPreset(cliPreset, projectPreset, globalPreset) {
|
|
34
|
+
return cliPreset ?? projectPreset ?? globalPreset ?? DEFAULT_PERMISSION_PRESET;
|
|
35
|
+
}
|
|
36
|
+
/** The preset `loadPermissionSettings` would enforce, without loading rules; throws on an invalid settings preset. */
|
|
37
|
+
export function resolvePermissionPreset(settingsManager, cliPresetOverride) {
|
|
38
|
+
const globalSettings = settingsManager.getGlobalSettings();
|
|
39
|
+
const projectSettings = settingsManager.getProjectSettings();
|
|
40
|
+
return pickPermissionPreset(cliPresetOverride, parseSettingsPreset(projectSettings.permissionPreset, "project"), parseSettingsPreset(globalSettings.permissionPreset, "global"));
|
|
41
|
+
}
|
|
33
42
|
function parseSettingsPreset(value, scope) {
|
|
34
43
|
if (value === undefined) {
|
|
35
44
|
return undefined;
|
|
@@ -53,7 +53,7 @@ export declare function createPtyBashTool(ctx: TerminalToolContext): {
|
|
|
53
53
|
cols: Type.TOptional<Type.TNumber>;
|
|
54
54
|
rows: Type.TOptional<Type.TNumber>;
|
|
55
55
|
}>;
|
|
56
|
-
execute(
|
|
56
|
+
execute(toolCallId: string, input: PtyBashInput, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, execCtx?: SessionEnvSource): Promise<TerminalToolResult>;
|
|
57
57
|
};
|
|
58
58
|
export {};
|
|
59
59
|
//# sourceMappingURL=bash.d.ts.map
|
|
@@ -1,5 +1,8 @@
|
|
|
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";
|
|
@@ -76,6 +79,32 @@ export function createThrottledEmitter(emit, throttleMs = BASH_UPDATE_THROTTLE_M
|
|
|
76
79
|
},
|
|
77
80
|
};
|
|
78
81
|
}
|
|
82
|
+
let defaultSessionLogger;
|
|
83
|
+
/**
|
|
84
|
+
* Spawn, logging once if the spawn outlives the call's own timeout (capped at the stall threshold). The call's
|
|
85
|
+
* kill timer and detach gate are armed only after spawn returns, so a spawn that never returns is otherwise
|
|
86
|
+
* silent (senpi#6189). The spawn itself is awaited exactly as before.
|
|
87
|
+
*/
|
|
88
|
+
async function spawnReportingStall(ctx, request, toolCallId, execCtx) {
|
|
89
|
+
const thresholdMs = stallThresholdMs(request.timeoutMs === undefined ? undefined : request.timeoutMs / 1000);
|
|
90
|
+
const watch = watchForStall(thresholdMs, (elapsedMs) => {
|
|
91
|
+
const logger = ctx.sessionLogger ?? (defaultSessionLogger ??= createSessionLogger(getAgentDir()));
|
|
92
|
+
const sessionId = (execCtx ?? ctx.getSessionContext?.())?.sessionManager?.getSessionId();
|
|
93
|
+
logger.warn("bash_spawn_stalled", {
|
|
94
|
+
toolCallId,
|
|
95
|
+
toolName: TERMINAL_BASH_TOOL,
|
|
96
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
97
|
+
thresholdMs,
|
|
98
|
+
elapsedMs,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
return await spawnCommandSession(sessionSpawnContext(ctx), request);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
watch.stop();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
79
108
|
function sessionSpawnContext(ctx) {
|
|
80
109
|
return {
|
|
81
110
|
...ctx,
|
|
@@ -198,18 +227,18 @@ function scheduleDetachedSweep(ctx, id, runtime, timeoutMs, startedAt) {
|
|
|
198
227
|
void ctx.manager.stop(id).catch(() => { });
|
|
199
228
|
}, remainingMs);
|
|
200
229
|
}
|
|
201
|
-
async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, execCtx) {
|
|
230
|
+
async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, toolCallId, execCtx) {
|
|
202
231
|
if (signal?.aborted)
|
|
203
232
|
return errorResult("Command aborted");
|
|
204
233
|
const timeoutMs = input.timeout !== undefined ? Math.trunc(input.timeout * 1000) : undefined;
|
|
205
|
-
const { id, runtime } = await
|
|
234
|
+
const { id, runtime } = await spawnReportingStall(ctx, {
|
|
206
235
|
command: input.command,
|
|
207
236
|
cols,
|
|
208
237
|
rows,
|
|
209
238
|
timeoutMs,
|
|
210
239
|
cwd,
|
|
211
240
|
envOverrides: { ...sessionEnvOverrides(ctx, execCtx), ...FOREGROUND_ENV_OVERRIDES },
|
|
212
|
-
});
|
|
241
|
+
}, toolCallId, execCtx);
|
|
213
242
|
const startedAt = Date.now();
|
|
214
243
|
const activity = `running ${input.command.slice(0, 80)}`;
|
|
215
244
|
const emitOutputUpdate = () => {
|
|
@@ -334,16 +363,16 @@ async function runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, exec
|
|
|
334
363
|
},
|
|
335
364
|
});
|
|
336
365
|
}
|
|
337
|
-
async function runBackground(ctx, input, cols, rows, cwd, execCtx) {
|
|
366
|
+
async function runBackground(ctx, input, cols, rows, cwd, toolCallId, execCtx) {
|
|
338
367
|
// Background sessions are NEVER killed by `timeout` (bash-timeout injects a default into
|
|
339
368
|
// every bash call); they live until exit or kill_bash, so no timeoutMs is passed.
|
|
340
|
-
const { id, runtime } = await
|
|
369
|
+
const { id, runtime } = await spawnReportingStall(ctx, {
|
|
341
370
|
command: input.command,
|
|
342
371
|
cols,
|
|
343
372
|
rows,
|
|
344
373
|
cwd,
|
|
345
374
|
envOverrides: sessionEnvOverrides(ctx, execCtx),
|
|
346
|
-
});
|
|
375
|
+
}, toolCallId, execCtx);
|
|
347
376
|
ctx.onBackgroundStart?.(id, input.description ?? input.command, Date.now(), input.command);
|
|
348
377
|
if (ctx.onBackgroundExit)
|
|
349
378
|
runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));
|
|
@@ -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
|
}
|
|
@@ -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 {
|
|
@@ -48,6 +49,8 @@ export interface TerminalToolContext {
|
|
|
48
49
|
* the server and only then lets the command run. Absent on the in-process path, which spawns exactly as before.
|
|
49
50
|
*/
|
|
50
51
|
readonly launchBarrier?: TerminalLaunchBarrier;
|
|
52
|
+
/** Where a stalled spawn is reported (senpi#6189); defaults to the agent directory's session log. */
|
|
53
|
+
readonly sessionLogger?: Pick<SessionLogger, "warn">;
|
|
51
54
|
}
|
|
52
55
|
/** What a background exit notice reads: on the tool child it is a summary the child sent, not the live session. */
|
|
53
56
|
export interface TerminalExitView {
|
|
@@ -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();
|
|
@@ -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.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SessionManager } from "./session-manager.ts";
|
|
2
|
+
type DecisionSessionManager = Pick<SessionManager, "getEntries" | "getHeader" | "getSessionId">;
|
|
3
|
+
export interface DecisionSession {
|
|
4
|
+
readonly sessionManager: DecisionSessionManager;
|
|
5
|
+
readonly namedPreset: () => string | undefined;
|
|
6
|
+
readonly launchFlagNamed: () => boolean;
|
|
7
|
+
readonly recordedPreset: () => string | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Binds the remote approval-mode rule to one live session, evaluated at EACH gated decision:
|
|
11
|
+
* 1. a preset a client named for the session wins (no override);
|
|
12
|
+
* 2. delegated-origin work asks, or uses the mode a client recorded in the session file when the operator named none.
|
|
13
|
+
* It is decided before any `spawnedBy` inheritance, because `spawnedBy` is a client-supplied claim: naming a
|
|
14
|
+
* parent, real or made up, can never take a remote device's turn off this rule;
|
|
15
|
+
* 3. a child (`spawnedBy`) follows its live parent's mode for the parent's current turn; a child whose parent is not
|
|
16
|
+
* live in this process asks, unless the operator named a preset for it with the launch flag;
|
|
17
|
+
* 4. anything else keeps the host's own default (no override).
|
|
18
|
+
*/
|
|
19
|
+
export declare function bindDecisionPreset(session: DecisionSession): void;
|
|
20
|
+
export declare function releaseDecisionPreset(sessionManager: DecisionSessionManager): void;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=permission-decision.d.ts.map
|