@code-yeongyu/senpi 2026.11.6-1 → 2026.11.6-2

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 (167) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/bundle/chunks/{chunk-5MWHZH2O.js → chunk-2FDW2LQL.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-GG2JO3CL.js → chunk-4E7GDG4H.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-K5JLYACP.js → chunk-4GKU755E.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-437VLZQZ.js → chunk-7III44PV.js} +1 -1
  6. package/dist/bundle/chunks/chunk-7PSBD5KS.js +9 -0
  7. package/dist/bundle/chunks/{chunk-KFWHLVVH.js → chunk-C7OKUC7L.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-CX32K5KD.js → chunk-CF5OGBC4.js} +7 -7
  9. package/dist/bundle/chunks/{chunk-7TGZJH6Z.js → chunk-D5MRJWUK.js} +40 -39
  10. package/dist/bundle/chunks/{chunk-E6L5VZSM.js → chunk-DSWDJ7HT.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-6NMTFURP.js → chunk-ECW7VBMB.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-2CSLJLM6.js → chunk-GKAYPJ4E.js} +2 -2
  13. package/dist/bundle/chunks/{chunk-KCGUUIUS.js → chunk-GNSMNSL7.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-V3JSQ3PG.js → chunk-GSKCWXXG.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-USKWGPA6.js → chunk-GTJJ73DE.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-HHSYTDKO.js → chunk-HLM46XEB.js} +2 -2
  17. package/dist/bundle/chunks/{chunk-A4F4J2KH.js → chunk-I3PHVJAS.js} +2 -2
  18. package/dist/bundle/chunks/{chunk-LAMRXFHZ.js → chunk-LNPAT22L.js} +1 -1
  19. package/dist/bundle/chunks/chunk-NH7SYIGN.js +2 -0
  20. package/dist/bundle/chunks/{chunk-ZIIDIVTG.js → chunk-R2Z7CX77.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-32I42CEY.js → chunk-RQ6CHXQ2.js} +1 -1
  22. package/dist/bundle/chunks/chunk-SJI6UB23.js +2 -0
  23. package/dist/bundle/chunks/{chunk-KUIEHAPR.js → chunk-T2SMQPRE.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-IPG434JT.js → chunk-TIPKAMSS.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-RI5ABX7A.js → chunk-TMXRR3N7.js} +2 -2
  26. package/dist/bundle/chunks/chunk-VD7AKTGP.js +3 -0
  27. package/dist/bundle/chunks/{chunk-22JICYKS.js → chunk-WAOGBBZR.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-6MHCFJDB.js → chunk-WGRKM3N6.js} +2 -2
  29. package/dist/bundle/chunks/{chunk-WF5R2VSP.js → chunk-X5ZVYNOL.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-H2FBGZNP.js → chunk-XCMII2VQ.js} +2 -2
  31. package/dist/bundle/chunks/{chunk-5JUCTKYS.js → chunk-YK3ZZCHI.js} +1 -1
  32. package/dist/bundle/chunks/{cli-main-ZQBSX5H5.js → cli-main-T74YJGIF.js} +1 -1
  33. package/dist/bundle/chunks/{hand-entry-7U7354GG.js → hand-entry-AYRUGE2A.js} +2 -2
  34. package/dist/bundle/chunks/{headless-host-7LKTG76U.js → headless-host-ES74ZB6M.js} +1 -1
  35. package/dist/bundle/chunks/{help-fast-path-NLSWH2CA.js → help-fast-path-TMTGLOBG.js} +1 -1
  36. package/dist/bundle/chunks/{host-command-3YRFN35E.js → host-command-YRXZE4TV.js} +1 -1
  37. package/dist/bundle/chunks/{host-lifecycle-6XLVIECJ.js → host-lifecycle-6VCXMPDW.js} +1 -1
  38. package/dist/bundle/chunks/host-lifecycle.js +1 -1
  39. package/dist/bundle/chunks/{host-session-command-F7USXUZV.js → host-session-command-N7IN7ZDV.js} +1 -1
  40. package/dist/bundle/chunks/{interactive-mode-4RF3PUQ2.js → interactive-mode-B5SQDLH7.js} +1 -1
  41. package/dist/bundle/chunks/{list-tips-H6VGDFTS.js → list-tips-DU57HHKA.js} +1 -1
  42. package/dist/bundle/chunks/{main-G2SRZYQN.js → main-UQFVFCNY.js} +1 -1
  43. package/dist/bundle/chunks/{multi-session-host-RPN54GUC.js → multi-session-host-XSUJDHFZ.js} +5 -5
  44. package/dist/bundle/chunks/{openai-codex-responses-MJZKUS6I.js → openai-codex-responses-7V6LTDRU.js} +2 -2
  45. package/dist/bundle/chunks/{package-manager-cli-D2VFD463.js → package-manager-cli-GLUNPQJU.js} +1 -1
  46. package/dist/bundle/chunks/{role-37WVG42T.js → role-RRFFHZF5.js} +1 -1
  47. package/dist/bundle/chunks/{rotation-stream-QELPMM4N.js → rotation-stream-BN2YK2MC.js} +1 -1
  48. package/dist/bundle/chunks/{rpc-mode-P4D3DVYD.js → rpc-mode-JYN3E7P7.js} +1 -1
  49. package/dist/bundle/chunks/rpc-monitor-source-C6VQ3BC4.js +3 -0
  50. package/dist/bundle/chunks/{run-attached-tui-CGA6O5FR.js → run-attached-tui-W236SEOR.js} +1 -1
  51. package/dist/bundle/chunks/{schedule-command-XWSNOAGG.js → schedule-command-B2EJTBOE.js} +1 -1
  52. package/dist/bundle/chunks/session-control-endpoint-YIMLHGLG.js +3 -0
  53. package/dist/bundle/chunks/{session-picker-OA2AUVZG.js → session-picker-E6ZONUJB.js} +1 -1
  54. package/dist/bundle/chunks/session-worker.js +294 -292
  55. package/dist/bundle/cli.js +2 -2
  56. package/dist/bundle/host-supervisor.js +1 -1
  57. package/dist/bundle/index.js +1 -1
  58. package/dist/bundle/rpc-entry.js +1 -1
  59. package/dist/bundle/runtime-manifest.json +1 -1
  60. package/dist/cli/project-trust-headless.d.ts +13 -0
  61. package/dist/cli/project-trust-headless.js +35 -0
  62. package/dist/cli/project-trust.js +13 -50
  63. package/dist/cli/schedule-delivery.d.ts +4 -0
  64. package/dist/cli/schedule-delivery.js +6 -0
  65. package/dist/cli/schedule-runner.js +8 -0
  66. package/dist/cli/session-opening.d.ts +19 -0
  67. package/dist/cli/session-opening.js +47 -1
  68. package/dist/core/agent-session-runtime.d.ts +12 -0
  69. package/dist/core/agent-session-runtime.js +41 -0
  70. package/dist/core/agent-session.js +13 -22
  71. package/dist/core/egress/api-table.d.ts +121 -0
  72. package/dist/core/egress/api-table.js +41 -0
  73. package/dist/core/egress/classify.d.ts +19 -0
  74. package/dist/core/egress/classify.js +197 -0
  75. package/dist/core/egress/credential-fields.d.ts +18 -0
  76. package/dist/core/egress/credential-fields.js +83 -0
  77. package/dist/core/egress/custody-sidecar.d.ts +44 -0
  78. package/dist/core/egress/custody-sidecar.js +91 -0
  79. package/dist/core/egress/device-custody.d.ts +12 -0
  80. package/dist/core/egress/device-custody.js +82 -0
  81. package/dist/core/egress/endpoint.d.ts +33 -0
  82. package/dist/core/egress/endpoint.js +115 -0
  83. package/dist/core/execute-tool-on-host.d.ts +26 -0
  84. package/dist/core/execute-tool-on-host.js +96 -0
  85. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +15 -0
  86. package/dist/core/extensions/builtin/goal/index.js +15 -3
  87. package/dist/core/extensions/builtin/goal/stop-pause.d.ts +2 -0
  88. package/dist/core/extensions/builtin/goal/stop-pause.js +5 -0
  89. package/dist/core/extensions/builtin/herdr/herdr-state.d.ts +10 -0
  90. package/dist/core/extensions/builtin/herdr/herdr-state.js +34 -6
  91. package/dist/core/extensions/builtin/herdr/index.js +6 -2
  92. package/dist/core/extensions/builtin/mcp/service.d.ts +9 -1
  93. package/dist/core/extensions/builtin/mcp/service.js +32 -1
  94. package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.d.ts +43 -0
  95. package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.js +158 -0
  96. package/dist/core/extensions/builtin/terminal/rpc-monitor-render.d.ts +2 -0
  97. package/dist/core/extensions/builtin/terminal/rpc-monitor-render.js +4 -0
  98. package/dist/core/extensions/builtin/terminal/rpc-monitor-source.d.ts +5 -2
  99. package/dist/core/extensions/builtin/terminal/rpc-monitor-source.js +19 -4
  100. package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.d.ts +4 -0
  101. package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.js +15 -0
  102. package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.d.ts +10 -0
  103. package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.js +28 -2
  104. package/dist/core/extensions/builtin/terminal/rpc-remote-source.d.ts +59 -0
  105. package/dist/core/extensions/builtin/terminal/rpc-remote-source.js +15 -0
  106. package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +5 -0
  107. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +14 -1
  108. package/dist/core/extensions/builtin/terminal/tools/monitor-rpc.js +8 -1
  109. package/dist/core/extensions/loader.js +8 -0
  110. package/dist/core/extensions/types.d.ts +6 -0
  111. package/dist/core/foreign-session-holders.d.ts +1 -1
  112. package/dist/core/foreign-session-holders.js +3 -3
  113. package/dist/core/session-import/jsonl-import.d.ts +8 -12
  114. package/dist/core/session-import/jsonl-import.js +99 -40
  115. package/dist/core/session-manager.d.ts +2 -0
  116. package/dist/core/session-manager.js +7 -2
  117. package/dist/core/session-store/durable-binding.d.ts +2 -0
  118. package/dist/core/session-store/durable-binding.js +26 -4
  119. package/dist/core/settings-manager.d.ts +20 -0
  120. package/dist/core/settings-manager.js +51 -0
  121. package/dist/core/trust-manager.d.ts +1 -18
  122. package/dist/core/trust-manager.js +3 -124
  123. package/dist/core/trust-requiring-resources.d.ts +21 -0
  124. package/dist/core/trust-requiring-resources.js +189 -0
  125. package/dist/index.d.ts +1 -0
  126. package/dist/index.js +1 -0
  127. package/dist/main.js +42 -12
  128. package/dist/modes/interactive/interactive-mode.js +2 -2
  129. package/dist/modes/interactive/session-control-commands.js +2 -0
  130. package/dist/modes/rpc/connection-handler.js +17 -0
  131. package/dist/modes/rpc/connection-question-bridge.js +7 -4
  132. package/dist/modes/rpc/effect-commands.d.ts +24 -0
  133. package/dist/modes/rpc/host-boot-extensions.d.ts +4 -0
  134. package/dist/modes/rpc/host-boot-extensions.js +17 -0
  135. package/dist/modes/rpc/host-lifecycle-shutdown.js +11 -2
  136. package/dist/modes/rpc/loopless-runtime-settings.js +5 -1
  137. package/dist/modes/rpc/method-classes-schema-session.d.ts +33 -0
  138. package/dist/modes/rpc/method-classes-schema-session.js +4 -0
  139. package/dist/modes/rpc/method-classes-schema.d.ts +33 -0
  140. package/dist/modes/rpc/method-classes.d.ts +4 -0
  141. package/dist/modes/rpc/method-classes.js +7 -0
  142. package/dist/modes/rpc/multi-session-host.d.ts +2 -0
  143. package/dist/modes/rpc/multi-session-host.js +7 -2
  144. package/dist/modes/rpc/project-workspace-commands.d.ts +60 -0
  145. package/dist/modes/rpc/project-workspace-commands.js +145 -0
  146. package/dist/modes/rpc/rpc-error-types.d.ts +11 -1
  147. package/dist/modes/rpc/rpc-error-types.js +10 -0
  148. package/dist/modes/rpc/rpc-input-validation.js +4 -0
  149. package/dist/modes/rpc/rpc-mutation-response-types.d.ts +13 -0
  150. package/dist/modes/rpc/rpc-read-response-types.d.ts +13 -0
  151. package/dist/modes/rpc/rpc-session-command-types.d.ts +15 -0
  152. package/dist/modes/rpc/session-attachment-gate.js +2 -0
  153. package/dist/modes/rpc/session-command-activity.js +2 -0
  154. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  155. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +41 -2
  156. package/docs/extensions.md +17 -1
  157. package/docs/rpc.md +121 -4
  158. package/docs/terminal-tools.md +8 -0
  159. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  160. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  161. package/package.json +6 -6
  162. package/dist/bundle/chunks/chunk-FUQJK2TU.js +0 -2
  163. package/dist/bundle/chunks/chunk-POZMRM6V.js +0 -2
  164. package/dist/bundle/chunks/chunk-XBG63MJZ.js +0 -8
  165. package/dist/bundle/chunks/chunk-YP47G4YP.js +0 -3
  166. package/dist/bundle/chunks/rpc-monitor-source-KKN2UP4T.js +0 -3
  167. package/dist/bundle/chunks/session-control-endpoint-OST7WAKP.js +0 -3
package/dist/main.js CHANGED
@@ -29,7 +29,7 @@ import { printPromptSizes } from "./cli/print-prompt-sizes.js";
29
29
  import { pipedStdinOptions } from "./cli/piped-stdin-options.js";
30
30
  import { exitAfterOutput, printThenExit } from "./cli/print-then-exit.js";
31
31
  import { createProjectTrustContext } from "./cli/project-trust.js";
32
- import { prepareSessionOpening } from "./cli/session-opening.js";
32
+ import { admitStartupSession, prepareSessionOpening, SESSION_HELD_EXIT_CODE } from "./cli/session-opening.js";
33
33
  import { createStartupLoadingIndicator, pauseIndicatorDuringPrompts, shouldShowStartupLoadingIndicator, } from "./cli/startup-loading-indicator.js";
34
34
  import { shouldRunFirstTimeSetup, showFirstTimeSetup, showStartupSelector } from "./cli/startup-ui.js";
35
35
  import { resolveToolRuntime } from "./cli/tool-runtime.js";
@@ -363,7 +363,21 @@ function validateSessionIdFlags(parsed) {
363
363
  process.exit(1);
364
364
  }
365
365
  }
366
- function openSessionOrExit(path, sessionDir) {
366
+ /**
367
+ * Admission of an existing session file before startup loads, migrates, rebinds or runs it: a session another live
368
+ * process runs must not get a second writer (senpi#5756). Forking only reads its source and is not admitted.
369
+ */
370
+ async function admitOrExit(sessionFile, appMode) {
371
+ const admission = await admitStartupSession(sessionFile, {
372
+ canAsk: appMode === "interactive" && process.stdin.isTTY === true,
373
+ confirm: promptConfirm,
374
+ err: (line) => console.error(line),
375
+ });
376
+ if (admission !== "open")
377
+ process.exit(admission === "cancelled" ? 0 : SESSION_HELD_EXIT_CODE);
378
+ }
379
+ async function openSessionOrExit(path, appMode, sessionDir) {
380
+ await admitOrExit(path, appMode);
367
381
  try {
368
382
  return SessionManager.open(path, sessionDir);
369
383
  }
@@ -383,7 +397,8 @@ function forkSessionOrExit(sourcePath, cwd, sessionDir, sessionId) {
383
397
  process.exit(1);
384
398
  }
385
399
  }
386
- async function rebindSessionOrExit(sourcePath, cwd, sessionDir) {
400
+ async function rebindSessionOrExit(sourcePath, cwd, appMode, sessionDir) {
401
+ await admitOrExit(sourcePath, appMode);
387
402
  try {
388
403
  const reboundPath = await rebindSessionFile(sourcePath, cwd, sessionDir);
389
404
  console.log(chalk.dim(`Session moved to ${cwd}`));
@@ -437,24 +452,24 @@ export async function createSessionManager(parsed, cwd, sessionDir, settingsMana
437
452
  }
438
453
  const sessionCwd = resolved.type === "global" ? resolved.cwd : await sessionCwdOrUndefined(resolved.path);
439
454
  if (resolved.type === "local" || sessionCwd === undefined || resolvePath(sessionCwd) === resolvePath(cwd)) {
440
- return openSessionOrExit(resolved.path, sessionDir);
455
+ return openSessionOrExit(resolved.path, appMode, sessionDir);
441
456
  }
442
457
  if ((await classifySessionRepository(resolved.path, sessionCwd, cwd)) === "different") {
443
458
  console.error(chalk.red(`Refusing to rebind: ${cwd} is a different git repository than ${sessionCwd}. Use --fork '${parsed.rebind}' to copy the session into this directory instead.`));
444
459
  process.exit(1);
445
460
  }
446
- return rebindSessionOrExit(resolved.path, cwd, sessionDir);
461
+ return rebindSessionOrExit(resolved.path, cwd, appMode, sessionDir);
447
462
  }
448
463
  if (parsed.session) {
449
464
  const resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);
450
465
  switch (resolved.type) {
451
466
  case "path":
452
467
  case "local":
453
- return openSessionOrExit(resolved.path, sessionDir);
468
+ return openSessionOrExit(resolved.path, appMode, sessionDir);
454
469
  case "global": {
455
470
  // Recorded under a folder the OmO desktop moved here: this folder's own session (senpi#2990).
456
471
  if (resolved.cwd !== "" && resolvePath(resolveMovedPath(resolved.cwd)) === resolvePath(cwd))
457
- return openSessionOrExit(resolved.path, sessionDir);
472
+ return openSessionOrExit(resolved.path, appMode, sessionDir);
458
473
  // The confirmation blocks on readline, which only an interactive session can
459
474
  // answer. Print, JSON, and RPC runs reach here with a TTY attached
460
475
  // too (`-p` from a terminal), where the question hangs the process or resolves
@@ -470,7 +485,7 @@ export async function createSessionManager(parsed, cwd, sessionDir, settingsMana
470
485
  err: (line) => console.error(line),
471
486
  });
472
487
  if (action === "rebind")
473
- return rebindSessionOrExit(resolved.path, cwd, sessionDir);
488
+ return rebindSessionOrExit(resolved.path, cwd, appMode, sessionDir);
474
489
  if (action === "fork")
475
490
  return forkSessionOrExit(resolved.path, cwd, sessionDir);
476
491
  if (action === "abort")
@@ -491,6 +506,8 @@ export async function createSessionManager(parsed, cwd, sessionDir, settingsMana
491
506
  console.log(chalk.dim("No session selected"));
492
507
  process.exit(0);
493
508
  }
509
+ // The resume target may be rebound (moved and rewritten) below, so the picked file is admitted first.
510
+ await admitOrExit(selectedPath, appMode);
494
511
  const target = await resolveResumeTarget({
495
512
  sessionPath: selectedPath,
496
513
  cwd,
@@ -520,8 +537,14 @@ export async function createSessionManager(parsed, cwd, sessionDir, settingsMana
520
537
  // A shared session dir matches sessions the OmO desktop moved here in continueRecent. In the default per-folder
521
538
  // layout they sit in the old folder's dir: continue the newest of those and the folder's own, in place (senpi#2990).
522
539
  const movedHere = sessionDir === undefined ? await movedHereSessionToContinue(cwd) : undefined;
523
- if (movedHere !== undefined)
540
+ if (movedHere !== undefined) {
541
+ await admitOrExit(movedHere, appMode);
524
542
  return SessionManager.open(movedHere, sessionDir);
543
+ }
544
+ // Loading the recent session can migrate and rewrite it, so its file is admitted before it is loaded.
545
+ const recentPath = SessionManager.recentSessionFile(cwd, sessionDir);
546
+ if (recentPath !== null && existsSync(recentPath))
547
+ await admitOrExit(recentPath, appMode);
525
548
  const recent = SessionManager.continueRecent(cwd, sessionDir);
526
549
  const recentFile = recent.getSessionFile();
527
550
  if (recentFile !== undefined && existsSync(recentFile))
@@ -534,13 +557,14 @@ export async function createSessionManager(parsed, cwd, sessionDir, settingsMana
534
557
  out: (line) => console.log(line),
535
558
  err: (line) => console.error(line),
536
559
  });
537
- return movedChoice === undefined ? recent : rebindSessionOrExit(movedChoice, cwd, sessionDir);
560
+ return movedChoice === undefined ? recent : rebindSessionOrExit(movedChoice, cwd, appMode, sessionDir);
538
561
  }
539
562
  if (parsed.sessionId) {
540
563
  const existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
541
564
  if (existingSession) {
542
565
  if (parsed.spawnedBy)
543
566
  console.error(chalk.yellow("Warning: --spawned-by is ignored when resuming an existing session."));
567
+ await admitOrExit(existingSession.path, appMode);
544
568
  return SessionManager.open(existingSession.path, sessionDir);
545
569
  }
546
570
  console.error(chalk.yellow(`Warning: No project session found with id '${parsed.sessionId}'; creating a new session with that id.`));
@@ -692,10 +716,12 @@ export function createCliRuntimeFactory(configuration, local = {}) {
692
716
  const createServices = async ({ cwd, agentDir, projectTrustContext, launchProfile, mcpRegistry, isInitialRuntime, }) => {
693
717
  const timer = startOpenTimer("runtime-services");
694
718
  const projectTrustDiagnostics = [];
695
- const cachedProjectTrust = projectTrustByCwd.get(cwd);
696
719
  const hasTrustRequiringResources = hasTrustRequiringProjectResources(cwd);
697
720
  // The saved decision, including the one a linked git worktree inherits from its main checkout (senpi#4388).
698
721
  const savedProjectTrust = await getSavedProjectTrust(trustStore, cwd);
722
+ // The memo stands in for an unsaved answer only: a decision saved since (an RPC `set_project_trust`, senpi#6176)
723
+ // is what a fresh process would read, so it outranks what an earlier session of this process resolved.
724
+ const cachedProjectTrust = savedProjectTrust === null ? projectTrustByCwd.get(cwd) : undefined;
699
725
  timer.mark("project_trust");
700
726
  // A full-access session trusts its project without asking, for that session only: nothing is
701
727
  // cached here or saved to the trust store, and a saved "do not trust" still wins (#2465).
@@ -716,7 +742,11 @@ export function createCliRuntimeFactory(configuration, local = {}) {
716
742
  cachedProjectTrust === undefined &&
717
743
  parsed.projectTrustOverride === undefined &&
718
744
  savedProjectTrust !== true;
719
- const runtimeSettingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted, trustFromFullAccess });
745
+ const runtimeSettingsManager = SettingsManager.create(cwd, agentDir, {
746
+ projectTrusted,
747
+ trustFromFullAccess,
748
+ projectTrustLocked: parsed.projectTrustOverride !== undefined,
749
+ });
720
750
  // The opener's per-session fallback policy is an in-memory override: never saved, never shared.
721
751
  if (launchProfile?.retryFallback)
722
752
  applyRetryFallbackProfile(runtimeSettingsManager, launchProfile.retryFallback);
@@ -5672,7 +5672,7 @@ export class InteractiveMode {
5672
5672
  // which the stdout/stderr error handler turns into emergencyTerminalExit;
5673
5673
  // the render loop is already idle, so this cannot hot-spin (see #4144).
5674
5674
  await this.sessionControlHost?.disposeActive();
5675
- await this.runtimeHost.dispose({ processExit: true });
5675
+ await this.runtimeHost.dispose({ processExit: true, endActiveRun: true });
5676
5676
  this.themeController.disableAutoSync();
5677
5677
  await this.ui.terminal.drainInput(1000);
5678
5678
  this.stop();
@@ -5688,7 +5688,7 @@ export class InteractiveMode {
5688
5688
  this.stop({ restoreStderr: false });
5689
5689
  try {
5690
5690
  await this.sessionControlHost?.disposeActive();
5691
- await this.runtimeHost.dispose({ processExit: true });
5691
+ await this.runtimeHost.dispose({ processExit: true, endActiveRun: true });
5692
5692
  }
5693
5693
  finally {
5694
5694
  restoreInteractiveStderr();
@@ -12,6 +12,7 @@ import { engineBuildIdentity } from "../../core/engine-build-identity.js";
12
12
  import { ENTRIES_TAIL_CAPABILITY, OBSERVE_READS_CAPABILITY, SESSION_STATUS_CAPABILITY, TUI_OBSERVER_CAPABILITY, } from "../rpc/custom-capability.js";
13
13
  import { answerGetEntries } from "../rpc/get-entries-command.js";
14
14
  import { answeredUiRequestId, settledQuestionStatus, unansweredQuestionIds } from "../rpc/extension-ui-response.js";
15
+ import { describeRpcMethodClasses } from "../rpc/method-classes.js";
15
16
  import { buildRpcSessionState } from "../rpc/rpc-session-state.js";
16
17
  import { sessionUiAnswerLedger } from "../rpc/ui-answer-ledger.js";
17
18
  import { runTuiRpcCommand, TUI_RPC_COMMANDS } from "./session-control-rpc-commands.js";
@@ -56,6 +57,7 @@ export async function runControlCommand(context, connection, command) {
56
57
  engineVersion: build.text,
57
58
  engineOrdinal: build.ordinal,
58
59
  commands: TUI_CONTROL_COMMANDS,
60
+ method_classes: describeRpcMethodClasses(),
59
61
  });
60
62
  }
61
63
  case "list_sessions":
@@ -67,6 +67,7 @@ import { runHoldInput } from "./held-input-commands.js";
67
67
  import { HostSessionControl } from "./host-session-control.js";
68
68
  import { createRpcLoginPromptCallbacks } from "./login-prompts.js";
69
69
  import { answerMemoryReport } from "./memory-report-command.js";
70
+ import { readProjectSettings, readProjectTrust, writeProjectSetting, writeProjectTrust, } from "./project-workspace-commands.js";
70
71
  import { protocolIdentity } from "./protocol-identity.js";
71
72
  import { buildRpcCommandsForSession, createCommandsChangedEvent, rpcCommandListDigest } from "./rpc-command-surface.js";
72
73
  import { rpcCommandPayloadError, rpcCommandShapeError, rpcMessageLengthError, sessionRetryFallbackError, } from "./rpc-input-validation.js";
@@ -1708,6 +1709,22 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1708
1709
  await requestMcpWireStatus();
1709
1710
  return success(id, "get_loaded_surfaces", currentLoadedSurfaces().data);
1710
1711
  }
1712
+ case "get_project_trust":
1713
+ return success(id, "get_project_trust", await readProjectTrust(session));
1714
+ case "set_project_trust": {
1715
+ const result = await writeProjectTrust(session, command.choice, (task) => runtimeHost.runInProjectTrustChangeOrder(task));
1716
+ return result.ok
1717
+ ? success(id, "set_project_trust", result.data)
1718
+ : error(id, "set_project_trust", result.message, result.errorCode);
1719
+ }
1720
+ case "get_project_settings":
1721
+ return success(id, "get_project_settings", readProjectSettings(session));
1722
+ case "set_project_setting": {
1723
+ const result = await writeProjectSetting(session, command.key, command.value);
1724
+ return result.ok
1725
+ ? success(id, "set_project_setting", result.data)
1726
+ : error(id, "set_project_setting", result.message, result.errorCode);
1727
+ }
1711
1728
  case "extension_request":
1712
1729
  case "eval_cells.list":
1713
1730
  case "eval_cells.peek":
@@ -95,6 +95,10 @@ export class ConnectionQuestionBridge {
95
95
  frame,
96
96
  cancel,
97
97
  progress: (draft) => {
98
+ // A claimed question already has its winner: a draft that lands while that answer is being admitted
99
+ // must not overwrite the selections it settles with (senpi#5960).
100
+ if (claimed)
101
+ return "question_already_resolved";
98
102
  if (draft.answers !== undefined)
99
103
  answers = draft.answers;
100
104
  if (draft.comment !== undefined)
@@ -108,6 +112,7 @@ export class ConnectionQuestionBridge {
108
112
  deadlineAtMs: frame.deadlineAtMs,
109
113
  remainingMs: Math.max(0, frame.deadlineAtMs - Date.now()),
110
114
  });
115
+ return "applied";
111
116
  },
112
117
  respond: (response, beforeAnswer) => {
113
118
  // The first answer wins: one that arrives while the winner's `beforeAnswer` runs settles nothing.
@@ -162,10 +167,8 @@ export class ConnectionQuestionBridge {
162
167
  /** Hands a draft to its pending question; reports whether one took it, or why none did (senpi#5761). */
163
168
  progress(draft) {
164
169
  const pending = this.pending.get(draft.id);
165
- if (pending) {
166
- pending.progress(draft);
167
- return "applied";
168
- }
170
+ if (pending)
171
+ return pending.progress(draft);
169
172
  return this.resolved.has(draft.id) ? "question_already_resolved" : "unknown_extension_ui_request";
170
173
  }
171
174
  cancelAll() {
@@ -402,6 +402,18 @@ export declare function runSetThinkingLevel(ctx: EffectCommandContext, command:
402
402
  type: "response";
403
403
  command: "set_steering_mode";
404
404
  success: true;
405
+ } | {
406
+ id?: string;
407
+ type: "response";
408
+ command: "set_project_trust";
409
+ success: true;
410
+ data: import("./project-workspace-commands.ts").RpcProjectTrust;
411
+ } | {
412
+ id?: string;
413
+ type: "response";
414
+ command: "set_project_setting";
415
+ success: true;
416
+ data: import("./project-workspace-commands.ts").RpcProjectSettings;
405
417
  } | {
406
418
  id?: string;
407
419
  type: "response";
@@ -841,6 +853,18 @@ export declare function runSetThinkingLevel(ctx: EffectCommandContext, command:
841
853
  extensions: import("./rpc-session-state-types.ts").RpcLoadedExtension[];
842
854
  mcpServers: import("./rpc-session-state-types.ts").RpcLoadedMcpServer[];
843
855
  };
856
+ } | {
857
+ id?: string;
858
+ type: "response";
859
+ command: "get_project_trust";
860
+ success: true;
861
+ data: import("./project-workspace-commands.ts").RpcProjectTrust;
862
+ } | {
863
+ id?: string;
864
+ type: "response";
865
+ command: "get_project_settings";
866
+ success: true;
867
+ data: import("./project-workspace-commands.ts").RpcProjectSettings;
844
868
  } | {
845
869
  id?: string;
846
870
  type: "response";
@@ -0,0 +1,4 @@
1
+ import type { HostWarm, HostWarmProfile } from "./host-warm.ts";
2
+ /** Starts the boot load and returns at once: it never delays the listener, and a failure is only logged. */
3
+ export declare function warmAtBootForPrivateFds(warm: HostWarm | undefined, profile: HostWarmProfile, log: (message: string) => void): void;
4
+ //# sourceMappingURL=host-boot-extensions.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A multi-session host loads its extensions per `open_session` or `warm`. A launch that hands it a
3
+ * private descriptor table expects an extension to claim its channel with no session open (the omo
4
+ * desktop waits for that channel's first frame before it opens one), so such a host runs the warm of
5
+ * its default profile once at boot (senpi#6147). It is that exact warm: a client's `warm` of the same
6
+ * profile joins it or answers `already_warm`, so the factories never run twice for it. Without a table nothing changes.
7
+ */
8
+ import { privateTablePresent } from "../../private-fds.js";
9
+ /** Starts the boot load and returns at once: it never delays the listener, and a failure is only logged. */
10
+ export function warmAtBootForPrivateFds(warm, profile, log) {
11
+ if (warm === undefined || !privateTablePresent())
12
+ return;
13
+ warm(profile).catch((cause) => {
14
+ log(`boot extension load failed: ${cause instanceof Error ? cause.message : String(cause)}`);
15
+ });
16
+ }
17
+ //# sourceMappingURL=host-boot-extensions.js.map
@@ -131,8 +131,17 @@ async function stopChild(context, reason) {
131
131
  catch (cause) {
132
132
  // Metadata/watch failures must not leave a child behind. A fired exit breaker is already
133
133
  // the final bound: never restart it or enlarge the normal caller's shutdown budget.
134
- if (!killAttempted)
135
- await reapChildAfterFailure(child, termAttempted);
134
+ if (!killAttempted) {
135
+ try {
136
+ await reapChildAfterFailure(child, termAttempted);
137
+ }
138
+ catch (reapCause) {
139
+ // Log only the recovery error here: performShutdown logs the original
140
+ // failure once when this rethrow reaches it, so logging it here too
141
+ // would print the same line twice (#6144 review).
142
+ supervisorLog(`shutdown recovery failed for host child pid ${pid ?? "unknown"}: ${errorMessage(reapCause)}`);
143
+ }
144
+ }
136
145
  throw cause;
137
146
  }
138
147
  }
@@ -27,7 +27,11 @@ export async function resolveLooplessSettings(configuration, startupSettingsMana
27
27
  hasTrustRequiringResources &&
28
28
  parsed.projectTrustOverride === undefined &&
29
29
  savedProjectTrust !== true;
30
- const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted, trustFromFullAccess });
30
+ const settingsManager = SettingsManager.create(cwd, agentDir, {
31
+ projectTrusted,
32
+ trustFromFullAccess,
33
+ projectTrustLocked: parsed.projectTrustOverride !== undefined,
34
+ });
31
35
  const reloadOptions = shouldResolveProjectTrust
32
36
  ? {
33
37
  resolveProjectTrust: ({ extensionsResult }) => resolveProjectTrusted({
@@ -781,6 +781,39 @@ export declare const SESSION_RECORD_SCHEMAS: {
781
781
  } & {
782
782
  readonly sessionId: Type.TOptional<Type.TString>;
783
783
  }>;
784
+ get_project_trust: Type.TObject<{
785
+ id: Type.TOptional<Type.TString>;
786
+ admissionId: Type.TOptional<Type.TString>;
787
+ type: Type.TLiteral<"get_project_trust">;
788
+ } & {
789
+ readonly sessionId: Type.TOptional<Type.TString>;
790
+ }>;
791
+ set_project_trust: Type.TObject<{
792
+ id: Type.TOptional<Type.TString>;
793
+ admissionId: Type.TOptional<Type.TString>;
794
+ type: Type.TLiteral<"set_project_trust">;
795
+ } & {
796
+ readonly sessionId: Type.TOptional<Type.TString>;
797
+ } & {
798
+ readonly choice: Type.TUnion<[Type.TLiteral<string>, ...Type.TLiteral<string>[]]>;
799
+ }>;
800
+ get_project_settings: Type.TObject<{
801
+ id: Type.TOptional<Type.TString>;
802
+ admissionId: Type.TOptional<Type.TString>;
803
+ type: Type.TLiteral<"get_project_settings">;
804
+ } & {
805
+ readonly sessionId: Type.TOptional<Type.TString>;
806
+ }>;
807
+ set_project_setting: Type.TObject<{
808
+ id: Type.TOptional<Type.TString>;
809
+ admissionId: Type.TOptional<Type.TString>;
810
+ type: Type.TLiteral<"set_project_setting">;
811
+ } & {
812
+ readonly sessionId: Type.TOptional<Type.TString>;
813
+ } & {
814
+ readonly key: Type.TString;
815
+ readonly value: Type.TUnknown;
816
+ }>;
784
817
  extension_request: Type.TObject<{
785
818
  id: Type.TOptional<Type.TString>;
786
819
  admissionId: Type.TOptional<Type.TString>;
@@ -191,6 +191,10 @@ const BASH_AND_TREE_SCHEMAS = {
191
191
  const SURFACE_AND_ACCOUNT_SCHEMAS = {
192
192
  get_commands: session("get_commands", {}),
193
193
  get_loaded_surfaces: session("get_loaded_surfaces", {}),
194
+ get_project_trust: session("get_project_trust", {}),
195
+ set_project_trust: session("set_project_trust", { choice: literals("trust", "trust_parent", "do_not_trust") }),
196
+ get_project_settings: session("get_project_settings", {}),
197
+ set_project_setting: session("set_project_setting", { key: Id, value: Type.Unknown() }),
194
198
  extension_request: session("extension_request", { name: Id, data: Opt(Type.Unknown()) }),
195
199
  "eval_cells.list": session("eval_cells.list", {}),
196
200
  "eval_cells.peek": session("eval_cells.peek", { cellId: Id, tailBytes: OptNum }),
@@ -1155,6 +1155,39 @@ export declare const RPC_INBOUND_SCHEMAS: {
1155
1155
  } & {
1156
1156
  readonly sessionId: import("typebox").TOptional<import("typebox").TString>;
1157
1157
  }>;
1158
+ readonly get_project_trust: import("typebox").TObject<{
1159
+ id: import("typebox").TOptional<import("typebox").TString>;
1160
+ admissionId: import("typebox").TOptional<import("typebox").TString>;
1161
+ type: import("typebox").TLiteral<"get_project_trust">;
1162
+ } & {
1163
+ readonly sessionId: import("typebox").TOptional<import("typebox").TString>;
1164
+ }>;
1165
+ readonly set_project_trust: import("typebox").TObject<{
1166
+ id: import("typebox").TOptional<import("typebox").TString>;
1167
+ admissionId: import("typebox").TOptional<import("typebox").TString>;
1168
+ type: import("typebox").TLiteral<"set_project_trust">;
1169
+ } & {
1170
+ readonly sessionId: import("typebox").TOptional<import("typebox").TString>;
1171
+ } & {
1172
+ readonly choice: import("typebox").TUnion<[import("typebox").TLiteral<string>, ...import("typebox").TLiteral<string>[]]>;
1173
+ }>;
1174
+ readonly get_project_settings: import("typebox").TObject<{
1175
+ id: import("typebox").TOptional<import("typebox").TString>;
1176
+ admissionId: import("typebox").TOptional<import("typebox").TString>;
1177
+ type: import("typebox").TLiteral<"get_project_settings">;
1178
+ } & {
1179
+ readonly sessionId: import("typebox").TOptional<import("typebox").TString>;
1180
+ }>;
1181
+ readonly set_project_setting: import("typebox").TObject<{
1182
+ id: import("typebox").TOptional<import("typebox").TString>;
1183
+ admissionId: import("typebox").TOptional<import("typebox").TString>;
1184
+ type: import("typebox").TLiteral<"set_project_setting">;
1185
+ } & {
1186
+ readonly sessionId: import("typebox").TOptional<import("typebox").TString>;
1187
+ } & {
1188
+ readonly key: import("typebox").TString;
1189
+ readonly value: import("typebox").TUnknown;
1190
+ }>;
1158
1191
  readonly extension_request: import("typebox").TObject<{
1159
1192
  id: import("typebox").TOptional<import("typebox").TString>;
1160
1193
  admissionId: import("typebox").TOptional<import("typebox").TString>;
@@ -93,6 +93,7 @@ export declare const RPC_METHOD_CLASS: {
93
93
  readonly get_steering_messages: "observe";
94
94
  readonly get_commands: "observe";
95
95
  readonly get_loaded_surfaces: "observe";
96
+ readonly get_project_trust: "observe";
96
97
  readonly get_available_models: "observe";
97
98
  readonly get_available_thinking_levels: "observe";
98
99
  readonly get_model_thinking_level: "observe";
@@ -196,6 +197,9 @@ export declare const RPC_METHOD_CLASS: {
196
197
  readonly set_steering_mode: "device_admin";
197
198
  readonly set_follow_up_mode: "device_admin";
198
199
  readonly set_auto_retry: "device_admin";
200
+ readonly set_project_trust: "device_admin";
201
+ readonly get_project_settings: "device_admin";
202
+ readonly set_project_setting: "device_admin";
199
203
  readonly edit_assistant_message: "device_admin";
200
204
  readonly send_custom_message: "device_admin";
201
205
  readonly switch_session: "device_admin";
@@ -148,6 +148,8 @@ export const RPC_METHOD_CLASS = {
148
148
  get_steering_messages: "observe",
149
149
  get_commands: "observe",
150
150
  get_loaded_surfaces: "observe",
151
+ // the session's own effective trust and saved decision, as get_state already shows projectTrusted
152
+ get_project_trust: "observe",
151
153
  get_available_models: "observe",
152
154
  get_available_thinking_levels: "observe",
153
155
  get_model_thinking_level: "observe",
@@ -243,6 +245,11 @@ export const RPC_METHOD_CLASS = {
243
245
  set_steering_mode: "device_admin",
244
246
  set_follow_up_mode: "device_admin",
245
247
  set_auto_retry: "device_admin",
248
+ // a consent decision saved in the device's trust store: it decides whether project code runs on the device
249
+ set_project_trust: "device_admin",
250
+ // reads a file from the session's cwd; writes one into it
251
+ get_project_settings: "device_admin",
252
+ set_project_setting: "device_admin",
246
253
  // switch_session opens (and appends to) the named session file; import_jsonl reads any host file into a session
247
254
  // both write what the model reads as engine work: an assistant reply it never gave, a custom context message
248
255
  // (append_session_entry classes the same entries appended directly as device_admin)
@@ -64,6 +64,8 @@ export declare function createHostCore(options: MultiSessionHostOptions, writer:
64
64
  handle: (line: string) => Promise<void>;
65
65
  handover: import("./host-idle-handover.ts").HostIdleHandover | undefined;
66
66
  handoverAnswered: () => Promise<void>;
67
+ /** The in-process registry's `warm`; the worker registry has none. */
68
+ warm: import("./host-warm.ts").HostWarm | undefined;
67
69
  /**
68
70
  * A session's live registry entry by its routing id. Only in-process test fixtures read it today, in place of
69
71
  * spying on the registry prototype (senpi#5802); the host itself never calls it.
@@ -7,6 +7,7 @@ import { HostMcpRegistry } from "../../core/extensions/builtin/mcp/host-registry
7
7
  import { flushRawStdout, takeOverStdout, waitForRawStdoutBackpressure, writeRawStdout, } from "../../core/output-guard.js";
8
8
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
9
9
  import { startHostChildReaper } from "./child-reaper.js";
10
+ import { warmAtBootForPrivateFds } from "./host-boot-extensions.js";
10
11
  import { CommandAuthority, commandAuthority, takeRelayExec } from "./command-authority.js";
11
12
  import { answerDelegation, relayExecRefusal } from "./delegated-answer.js";
12
13
  import { parseClientCapabilities } from "./custom-capability.js";
@@ -190,6 +191,8 @@ export function createHostCore(options, writer, capabilities = parseClientCapabi
190
191
  handle,
191
192
  handover: gate.handover,
192
193
  handoverAnswered: () => gate.answered(),
194
+ /** The in-process registry's `warm`; the worker registry has none. */
195
+ warm: "warm" in registry ? registry.warm : undefined,
193
196
  /**
194
197
  * A session's live registry entry by its routing id. Only in-process test fixtures read it today, in place of
195
198
  * spying on the registry prototype (senpi#5802); the host itself never calls it.
@@ -206,7 +209,7 @@ async function runStdioHost(options) {
206
209
  // forever: exit through the normal shutdown path once the window elapses.
207
210
  const runtimeBuildId = await startupRuntimeBuildId();
208
211
  const trim = createZeroSessionTrim(writer, () => router.sessionCount);
209
- const { router, handle } = createHostCore(options, writer, undefined, { onEmptyExit: () => void shutdown(0) }, undefined, {
212
+ const { router, handle, warm } = createHostCore(options, writer, undefined, { onEmptyExit: () => void shutdown(0) }, undefined, {
210
213
  ...(runtimeBuildId === undefined ? {} : { runtimeBuildId }),
211
214
  onSessionCountChange: (size) => trim.observe(size),
212
215
  });
@@ -237,6 +240,7 @@ async function runStdioHost(options) {
237
240
  process.stdin.off("end", onEnd);
238
241
  };
239
242
  registerShutdownSignals(shutdown);
243
+ warmAtBootForPrivateFds(warm, { cwd: options.cwd }, hostLog);
240
244
  return new Promise(() => { });
241
245
  }
242
246
  async function runSocketHost(options, socketPath) {
@@ -260,7 +264,7 @@ async function runSocketHost(options, socketPath) {
260
264
  }
261
265
  const canHandOver = runtimeBuildId !== undefined && process.platform !== "win32";
262
266
  const trim = createZeroSessionTrim(writer, () => router.sessionCount);
263
- const { router, handle, handover, handoverAnswered } = createHostCore(options, writer, [
267
+ const { router, handle, handover, handoverAnswered, warm } = createHostCore(options, writer, [
264
268
  ...parseClientCapabilities(envValue("RPC_CLIENT_CAPABILITIES")),
265
269
  // A socket host installs the SIGUSR1 drain below, so it can be handed off to a newer
266
270
  // generation instead of being killed. A host that does not advertise this is never
@@ -555,6 +559,7 @@ async function runSocketHost(options, socketPath) {
555
559
  supervisorPublicIdentity = await waitForSocketIdentityFile(supervisorPublicOwnerFile);
556
560
  }
557
561
  process.stderr.write(`senpi rpc listening on ${formatSocketAddress(socketPath)}\n`);
562
+ warmAtBootForPrivateFds(warm, { cwd: options.cwd, sessionContext: hostContext }, hostLog);
558
563
  // Opt-in only: set by the lifecycle supervisor so this host can never outlive
559
564
  // it, including when the supervisor is SIGKILLed and runs no handler at all.
560
565
  return new Promise(() => { });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The session workspace's project trust and project-scope settings over RPC (senpi#6176).
3
+ *
4
+ * Trust: the choices are exactly the ones `/trust` offers (`getProjectTrustOptions` without the session-only
5
+ * entries), and choosing one saves exactly what that choice saves in `trust.json`. It is never implied: only an
6
+ * explicit `set_project_trust` writes, and a launch that named the trust (`--approve`/`--no-approve`) refuses it, because
7
+ * that flag outranks every saved decision. Then, in the order `resolveProjectTrusted` decides a session's trust, a folder
8
+ * with nothing trust gates stays trusted, and a `project_trust` handler of the extensions loaded before trust (never a
9
+ * project extension) decides before any saved choice: a choice it decides against is refused, with nothing saved and
10
+ * nothing applied. Otherwise the session takes the decision the way a Full access change does
11
+ * (`applyProjectTrustChange`), in line with the runtime's own trust changes: project settings, skills, prompts,
12
+ * extensions and MCP servers load or unload at once.
13
+ *
14
+ * Settings: the project settings file is read as stored (reading runs nothing, so trust does not gate it) and written
15
+ * one top-level key at a time through `SettingsManager`'s own project write path, which refuses an untrusted project.
16
+ */
17
+ import type { AgentSession } from "../../core/agent-session.ts";
18
+ import { type ProjectSettingsFile } from "../../core/settings-manager.ts";
19
+ /** Runs a trust change after every trust change the session's runtime already started (Full access syncs). */
20
+ export type InTrustChangeOrder = <T>(task: () => Promise<T>) => Promise<T>;
21
+ export type ProjectTrustChoice = "trust" | "trust_parent" | "do_not_trust";
22
+ export interface RpcProjectTrustChoice {
23
+ choice: ProjectTrustChoice;
24
+ label: string;
25
+ trusted: boolean;
26
+ /** The folder the choice saves its decision for. */
27
+ path: string;
28
+ }
29
+ export interface RpcProjectTrust {
30
+ cwd: string;
31
+ /** Whether this session runs with the project trusted now. */
32
+ projectTrusted: boolean;
33
+ /** Whether the folder has anything trust gates; without it the project counts as trusted. */
34
+ requiresTrust: boolean;
35
+ /** The nearest saved decision at or above the folder, as `/trust` shows it. */
36
+ saved: {
37
+ path: string;
38
+ trusted: boolean;
39
+ } | null;
40
+ /** The launch named the trust (`--approve`/`--no-approve`); `set_project_trust` is refused. */
41
+ locked: boolean;
42
+ choices: RpcProjectTrustChoice[];
43
+ }
44
+ export type ProjectCommandResult<T> = {
45
+ ok: true;
46
+ data: T;
47
+ } | {
48
+ ok: false;
49
+ message: string;
50
+ errorCode: string;
51
+ };
52
+ export declare function readProjectTrust(session: AgentSession): Promise<RpcProjectTrust>;
53
+ export declare function writeProjectTrust(session: AgentSession, choice: unknown, inTrustChangeOrder: InTrustChangeOrder): Promise<ProjectCommandResult<RpcProjectTrust>>;
54
+ export interface RpcProjectSettings extends ProjectSettingsFile {
55
+ cwd: string;
56
+ projectTrusted: boolean;
57
+ }
58
+ export declare function readProjectSettings(session: AgentSession): RpcProjectSettings;
59
+ export declare function writeProjectSetting(session: AgentSession, key: unknown, value: unknown): Promise<ProjectCommandResult<RpcProjectSettings>>;
60
+ //# sourceMappingURL=project-workspace-commands.d.ts.map