@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.
Files changed (165) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/bundle/chunks/{bun-extension-importer-RCGPQTI2.js → bun-extension-importer-GQ6IKEXD.js} +2 -2
  3. package/dist/bundle/chunks/{chunk-QN2BHP3M.js → chunk-44BJPS5I.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-C7OKUC7L.js → chunk-4BATXPB7.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-RQ6CHXQ2.js → chunk-5LKB732P.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-X5ZVYNOL.js → chunk-6CEDL5S2.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-HLM46XEB.js → chunk-7DRWPZ3N.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-7PSBD5KS.js → chunk-BASS47UD.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-ECW7VBMB.js → chunk-CFEHJ4VU.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-TIPKAMSS.js → chunk-EO2VPZ2K.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-LNPAT22L.js → chunk-F5JLDCYD.js} +8 -7
  12. package/dist/bundle/chunks/{chunk-GSKCWXXG.js → chunk-JMQOLOC2.js} +1 -1
  13. package/dist/bundle/chunks/chunk-L7ZIS3MC.js +106 -0
  14. package/dist/bundle/chunks/{chunk-47PPZKPO.js → chunk-LM6DOWQH.js} +4 -4
  15. package/dist/bundle/chunks/chunk-LPYBKMPT.js +2 -0
  16. package/dist/bundle/chunks/{chunk-GNSMNSL7.js → chunk-LUZVGPQ7.js} +2 -2
  17. package/dist/bundle/chunks/{chunk-7III44PV.js → chunk-MJTCZUXS.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-2FDW2LQL.js → chunk-SB6MXNO5.js} +1 -1
  19. package/dist/bundle/chunks/chunk-UODSYQ2D.js +2 -0
  20. package/dist/bundle/chunks/{chunk-D5MRJWUK.js → chunk-V2QAYGOK.js} +216 -224
  21. package/dist/bundle/chunks/{chunk-WGRKM3N6.js → chunk-X53PD4ZC.js} +1 -1
  22. package/dist/bundle/chunks/{cli-main-T74YJGIF.js → cli-main-RWHDZKTQ.js} +1 -1
  23. package/dist/bundle/chunks/{dispatch-BAK2YZKD.js → dispatch-VDIYSXQN.js} +1 -1
  24. package/dist/bundle/chunks/{hand-entry-AYRUGE2A.js → hand-entry-G5LYF62Q.js} +1 -1
  25. package/dist/bundle/chunks/{host-command-YRXZE4TV.js → host-command-I2JZ5JM7.js} +1 -1
  26. package/dist/bundle/chunks/{host-session-command-N7IN7ZDV.js → host-session-command-Y52NFPP2.js} +1 -1
  27. package/dist/bundle/chunks/interactive-mode-PA7DGVS6.js +2 -0
  28. package/dist/bundle/chunks/main-OBL6375C.js +2 -0
  29. package/dist/bundle/chunks/{multi-session-host-XSUJDHFZ.js → multi-session-host-NM6I5BWZ.js} +20 -17
  30. package/dist/bundle/chunks/{package-manager-cli-GLUNPQJU.js → package-manager-cli-GNRQ5MVR.js} +1 -1
  31. package/dist/bundle/chunks/{role-RRFFHZF5.js → role-F56BS2ZQ.js} +1 -1
  32. package/dist/bundle/chunks/rpc-mode-G4KIP7XS.js +2 -0
  33. package/dist/bundle/chunks/{run-attached-tui-W236SEOR.js → run-attached-tui-XTGPAGYM.js} +1 -1
  34. package/dist/bundle/chunks/{runtime-session-OBXYK4PI.js → runtime-session-VO3X4OE3.js} +1 -1
  35. package/dist/bundle/chunks/{schedule-command-B2EJTBOE.js → schedule-command-L4M3TSQ3.js} +1 -1
  36. package/dist/bundle/chunks/{session-control-endpoint-YIMLHGLG.js → session-control-endpoint-V46WMCDF.js} +1 -1
  37. package/dist/bundle/chunks/{session-picker-E6ZONUJB.js → session-picker-AG6KSR5A.js} +1 -1
  38. package/dist/bundle/chunks/session-worker.js +87 -83
  39. package/dist/bundle/cli.js +2 -2
  40. package/dist/bundle/index.js +1 -1
  41. package/dist/bundle/rpc-entry.js +1 -1
  42. package/dist/bundle/runtime-manifest.json +1 -1
  43. package/dist/core/agent-session-runtime.d.ts +2 -0
  44. package/dist/core/agent-session-runtime.js +9 -2
  45. package/dist/core/agent-session.d.ts +24 -3
  46. package/dist/core/agent-session.js +94 -28
  47. package/dist/core/credential-pool/rotation-slots.d.ts +3 -3
  48. package/dist/core/credential-pool/state-store.d.ts +1 -1
  49. package/dist/core/egress/hpke.d.ts +63 -0
  50. package/dist/core/egress/hpke.js +188 -0
  51. package/dist/core/egress/placement-claim.d.ts +69 -0
  52. package/dist/core/egress/placement-claim.js +115 -0
  53. package/dist/core/egress/pool-state.d.ts +18 -0
  54. package/dist/core/egress/pool-state.js +5 -0
  55. package/dist/core/egress/sealed-format.d.ts +101 -0
  56. package/dist/core/egress/sealed-format.js +174 -0
  57. package/dist/core/egress/wire.d.ts +226 -0
  58. package/dist/core/egress/wire.js +47 -0
  59. package/dist/core/extensions/builtin/ask-user/escape-residue.d.ts +50 -0
  60. package/dist/core/extensions/builtin/ask-user/escape-residue.js +113 -0
  61. package/dist/core/extensions/builtin/ask-user/extension.js +7 -1
  62. package/dist/core/extensions/builtin/ask-user/resume.js +2 -1
  63. package/dist/core/extensions/builtin/ask-user/tool.d.ts +3 -0
  64. package/dist/core/extensions/builtin/ask-user/tool.js +8 -0
  65. package/dist/core/extensions/builtin/goal/index.js +46 -0
  66. package/dist/core/extensions/builtin/goal/stop-pause.d.ts +1 -0
  67. package/dist/core/extensions/builtin/goal/stop-pause.js +4 -2
  68. package/dist/core/extensions/builtin/permission-system/index.js +10 -4
  69. package/dist/core/extensions/builtin/permission-system/preset-registry.d.ts +2 -0
  70. package/dist/core/extensions/builtin/permission-system/preset-registry.js +11 -0
  71. package/dist/core/extensions/builtin/permission-system/settings.d.ts +2 -0
  72. package/dist/core/extensions/builtin/permission-system/settings.js +10 -1
  73. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +1 -1
  74. package/dist/core/extensions/builtin/terminal/tools/bash.js +38 -9
  75. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +3 -0
  76. package/dist/core/extensions/bun-extension-importer.js +4 -3
  77. package/dist/core/extensions/bun-extension-load-turns.d.ts +4 -0
  78. package/dist/core/extensions/bun-extension-load-turns.js +45 -0
  79. package/dist/core/extensions/bun-extension-registry.d.ts +1 -0
  80. package/dist/core/extensions/bun-extension-registry.js +19 -1
  81. package/dist/core/extensions/loader.js +5 -0
  82. package/dist/core/extensions/session-dialog-api.d.ts +5 -0
  83. package/dist/core/extensions/session-dialog-api.js +13 -0
  84. package/dist/core/extensions/types.d.ts +14 -1
  85. package/dist/core/permission-decision.d.ts +22 -0
  86. package/dist/core/permission-decision.js +53 -0
  87. package/dist/core/permission-mode.d.ts +56 -0
  88. package/dist/core/permission-mode.js +133 -0
  89. package/dist/core/retry-fallback/controller-types.d.ts +6 -0
  90. package/dist/core/retry-fallback/controller.d.ts +8 -0
  91. package/dist/core/retry-fallback/controller.js +31 -3
  92. package/dist/core/retry-fallback/unusable.d.ts +23 -1
  93. package/dist/core/retry-fallback/unusable.js +80 -1
  94. package/dist/core/retry-fallback/usage-limit.d.ts +1 -1
  95. package/dist/core/retry-fallback/usage-limit.js +6 -3
  96. package/dist/core/sdk.js +2 -0
  97. package/dist/core/session-log.js +1 -1
  98. package/dist/core/stall-warning.d.ts +41 -0
  99. package/dist/core/stall-warning.js +72 -0
  100. package/dist/core/turn-origin.d.ts +27 -0
  101. package/dist/core/turn-origin.js +54 -0
  102. package/dist/modes/rpc/client-input-handler.d.ts +2 -0
  103. package/dist/modes/rpc/client-input-handler.js +2 -0
  104. package/dist/modes/rpc/connection-handler.js +16 -5
  105. package/dist/modes/rpc/connection-roles.d.ts +6 -0
  106. package/dist/modes/rpc/connection-roles.js +13 -0
  107. package/dist/modes/rpc/delegated-drive-origin.d.ts +9 -0
  108. package/dist/modes/rpc/delegated-drive-origin.js +16 -0
  109. package/dist/modes/rpc/effect-commands.d.ts +6 -0
  110. package/dist/modes/rpc/host-bare-endpoint-registration.d.ts +7 -0
  111. package/dist/modes/rpc/host-bare-endpoint-registration.js +35 -0
  112. package/dist/modes/rpc/host-capabilities.d.ts +2 -0
  113. package/dist/modes/rpc/host-capabilities.js +3 -0
  114. package/dist/modes/rpc/host-handover-parked.d.ts +11 -0
  115. package/dist/modes/rpc/host-handover-parked.js +17 -0
  116. package/dist/modes/rpc/host-handover-reopen.js +18 -6
  117. package/dist/modes/rpc/host-handover-resident-writer.d.ts +16 -0
  118. package/dist/modes/rpc/host-handover-resident-writer.js +57 -0
  119. package/dist/modes/rpc/host-warm.d.ts +16 -1
  120. package/dist/modes/rpc/host-warm.js +33 -6
  121. package/dist/modes/rpc/method-classes-schema-session.d.ts +9 -0
  122. package/dist/modes/rpc/method-classes-schema-session.js +1 -0
  123. package/dist/modes/rpc/method-classes-schema.d.ts +9 -0
  124. package/dist/modes/rpc/method-classes.d.ts +1 -0
  125. package/dist/modes/rpc/method-classes.js +2 -0
  126. package/dist/modes/rpc/multi-session-host.js +6 -1
  127. package/dist/modes/rpc/permission-preset-command.d.ts +6 -0
  128. package/dist/modes/rpc/permission-preset-command.js +27 -0
  129. package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
  130. package/dist/modes/rpc/rpc-session-command-types.d.ts +4 -0
  131. package/dist/modes/rpc/rpc-session-event-types.d.ts +5 -2
  132. package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -1
  133. package/dist/modes/rpc/rpc-session-state.js +2 -0
  134. package/dist/modes/rpc/session-attachment-commands.js +3 -3
  135. package/dist/modes/rpc/session-event-writer-connections.js +1 -0
  136. package/dist/modes/rpc/session-event-writer.d.ts +1 -1
  137. package/dist/modes/rpc/session-registry-attach.d.ts +0 -9
  138. package/dist/modes/rpc/session-registry-attach.js +16 -3
  139. package/dist/modes/rpc/session-registry-types.d.ts +2 -0
  140. package/dist/modes/rpc/session-registry.d.ts +2 -0
  141. package/dist/modes/rpc/session-registry.js +16 -3
  142. package/dist/modes/rpc/session-router-close.js +43 -6
  143. package/dist/modes/rpc/session-router-context.js +2 -0
  144. package/dist/modes/rpc/session-router-occupancy.js +2 -11
  145. package/dist/modes/rpc/session-router-open-profile.js +4 -0
  146. package/dist/modes/rpc/session-router-open.d.ts +1 -1
  147. package/dist/modes/rpc/session-router-open.js +6 -17
  148. package/dist/modes/rpc/session-router-residency.d.ts +24 -0
  149. package/dist/modes/rpc/session-router-residency.js +90 -0
  150. package/dist/modes/rpc/session-router-routing.js +7 -1
  151. package/dist/modes/rpc/session-status.js +4 -1
  152. package/dist/modes/rpc/session-worker.js +4 -0
  153. package/dist/modes/rpc/worker-session-registry.js +5 -2
  154. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  155. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +12 -2
  156. package/docs/extensions.md +12 -0
  157. package/docs/rpc.md +72 -12
  158. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  159. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  160. package/package.json +6 -6
  161. package/dist/bundle/chunks/chunk-C7HEEBFH.js +0 -2
  162. package/dist/bundle/chunks/chunk-XCMII2VQ.js +0 -98
  163. package/dist/bundle/chunks/interactive-mode-B5SQDLH7.js +0 -2
  164. package/dist/bundle/chunks/main-UQFVFCNY.js +0 -2
  165. 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.getPauseState?.().holds.some((hold) => hold.kind === "session_hold" && hold.key !== "interrupted-run") ===
5
- true);
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 = pi.getFlag("permission-preset");
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-preset") &&
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 ?? projectPreset ?? globalPreset ?? DEFAULT_PERMISSION_PRESET;
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(_toolCallId: string, input: PtyBashInput, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, execCtx?: SessionEnvSource): Promise<TerminalToolResult>;
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 spawnCommandSession(sessionSpawnContext(ctx), {
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 spawnCommandSession(sessionSpawnContext(ctx), {
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(_toolCallId, input, signal, onUpdate, execCtx) {
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