@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.
- package/CHANGELOG.md +35 -0
- package/dist/bundle/chunks/{chunk-5MWHZH2O.js → chunk-2FDW2LQL.js} +1 -1
- package/dist/bundle/chunks/{chunk-GG2JO3CL.js → chunk-4E7GDG4H.js} +1 -1
- package/dist/bundle/chunks/{chunk-K5JLYACP.js → chunk-4GKU755E.js} +1 -1
- package/dist/bundle/chunks/{chunk-437VLZQZ.js → chunk-7III44PV.js} +1 -1
- package/dist/bundle/chunks/chunk-7PSBD5KS.js +9 -0
- package/dist/bundle/chunks/{chunk-KFWHLVVH.js → chunk-C7OKUC7L.js} +1 -1
- package/dist/bundle/chunks/{chunk-CX32K5KD.js → chunk-CF5OGBC4.js} +7 -7
- package/dist/bundle/chunks/{chunk-7TGZJH6Z.js → chunk-D5MRJWUK.js} +40 -39
- package/dist/bundle/chunks/{chunk-E6L5VZSM.js → chunk-DSWDJ7HT.js} +1 -1
- package/dist/bundle/chunks/{chunk-6NMTFURP.js → chunk-ECW7VBMB.js} +1 -1
- package/dist/bundle/chunks/{chunk-2CSLJLM6.js → chunk-GKAYPJ4E.js} +2 -2
- package/dist/bundle/chunks/{chunk-KCGUUIUS.js → chunk-GNSMNSL7.js} +1 -1
- package/dist/bundle/chunks/{chunk-V3JSQ3PG.js → chunk-GSKCWXXG.js} +1 -1
- package/dist/bundle/chunks/{chunk-USKWGPA6.js → chunk-GTJJ73DE.js} +1 -1
- package/dist/bundle/chunks/{chunk-HHSYTDKO.js → chunk-HLM46XEB.js} +2 -2
- package/dist/bundle/chunks/{chunk-A4F4J2KH.js → chunk-I3PHVJAS.js} +2 -2
- package/dist/bundle/chunks/{chunk-LAMRXFHZ.js → chunk-LNPAT22L.js} +1 -1
- package/dist/bundle/chunks/chunk-NH7SYIGN.js +2 -0
- package/dist/bundle/chunks/{chunk-ZIIDIVTG.js → chunk-R2Z7CX77.js} +1 -1
- package/dist/bundle/chunks/{chunk-32I42CEY.js → chunk-RQ6CHXQ2.js} +1 -1
- package/dist/bundle/chunks/chunk-SJI6UB23.js +2 -0
- package/dist/bundle/chunks/{chunk-KUIEHAPR.js → chunk-T2SMQPRE.js} +1 -1
- package/dist/bundle/chunks/{chunk-IPG434JT.js → chunk-TIPKAMSS.js} +1 -1
- package/dist/bundle/chunks/{chunk-RI5ABX7A.js → chunk-TMXRR3N7.js} +2 -2
- package/dist/bundle/chunks/chunk-VD7AKTGP.js +3 -0
- package/dist/bundle/chunks/{chunk-22JICYKS.js → chunk-WAOGBBZR.js} +1 -1
- package/dist/bundle/chunks/{chunk-6MHCFJDB.js → chunk-WGRKM3N6.js} +2 -2
- package/dist/bundle/chunks/{chunk-WF5R2VSP.js → chunk-X5ZVYNOL.js} +1 -1
- package/dist/bundle/chunks/{chunk-H2FBGZNP.js → chunk-XCMII2VQ.js} +2 -2
- package/dist/bundle/chunks/{chunk-5JUCTKYS.js → chunk-YK3ZZCHI.js} +1 -1
- package/dist/bundle/chunks/{cli-main-ZQBSX5H5.js → cli-main-T74YJGIF.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-7U7354GG.js → hand-entry-AYRUGE2A.js} +2 -2
- package/dist/bundle/chunks/{headless-host-7LKTG76U.js → headless-host-ES74ZB6M.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-NLSWH2CA.js → help-fast-path-TMTGLOBG.js} +1 -1
- package/dist/bundle/chunks/{host-command-3YRFN35E.js → host-command-YRXZE4TV.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-6XLVIECJ.js → host-lifecycle-6VCXMPDW.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +1 -1
- package/dist/bundle/chunks/{host-session-command-F7USXUZV.js → host-session-command-N7IN7ZDV.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-4RF3PUQ2.js → interactive-mode-B5SQDLH7.js} +1 -1
- package/dist/bundle/chunks/{list-tips-H6VGDFTS.js → list-tips-DU57HHKA.js} +1 -1
- package/dist/bundle/chunks/{main-G2SRZYQN.js → main-UQFVFCNY.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-RPN54GUC.js → multi-session-host-XSUJDHFZ.js} +5 -5
- package/dist/bundle/chunks/{openai-codex-responses-MJZKUS6I.js → openai-codex-responses-7V6LTDRU.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-D2VFD463.js → package-manager-cli-GLUNPQJU.js} +1 -1
- package/dist/bundle/chunks/{role-37WVG42T.js → role-RRFFHZF5.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-QELPMM4N.js → rotation-stream-BN2YK2MC.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-P4D3DVYD.js → rpc-mode-JYN3E7P7.js} +1 -1
- package/dist/bundle/chunks/rpc-monitor-source-C6VQ3BC4.js +3 -0
- package/dist/bundle/chunks/{run-attached-tui-CGA6O5FR.js → run-attached-tui-W236SEOR.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-XWSNOAGG.js → schedule-command-B2EJTBOE.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-YIMLHGLG.js +3 -0
- package/dist/bundle/chunks/{session-picker-OA2AUVZG.js → session-picker-E6ZONUJB.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +294 -292
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +1 -1
- 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/cli/project-trust-headless.d.ts +13 -0
- package/dist/cli/project-trust-headless.js +35 -0
- package/dist/cli/project-trust.js +13 -50
- package/dist/cli/schedule-delivery.d.ts +4 -0
- package/dist/cli/schedule-delivery.js +6 -0
- package/dist/cli/schedule-runner.js +8 -0
- package/dist/cli/session-opening.d.ts +19 -0
- package/dist/cli/session-opening.js +47 -1
- package/dist/core/agent-session-runtime.d.ts +12 -0
- package/dist/core/agent-session-runtime.js +41 -0
- package/dist/core/agent-session.js +13 -22
- package/dist/core/egress/api-table.d.ts +121 -0
- package/dist/core/egress/api-table.js +41 -0
- package/dist/core/egress/classify.d.ts +19 -0
- package/dist/core/egress/classify.js +197 -0
- package/dist/core/egress/credential-fields.d.ts +18 -0
- package/dist/core/egress/credential-fields.js +83 -0
- package/dist/core/egress/custody-sidecar.d.ts +44 -0
- package/dist/core/egress/custody-sidecar.js +91 -0
- package/dist/core/egress/device-custody.d.ts +12 -0
- package/dist/core/egress/device-custody.js +82 -0
- package/dist/core/egress/endpoint.d.ts +33 -0
- package/dist/core/egress/endpoint.js +115 -0
- package/dist/core/execute-tool-on-host.d.ts +26 -0
- package/dist/core/execute-tool-on-host.js +96 -0
- package/dist/core/extensions/builtin/compaction/retained-message-safety.js +15 -0
- package/dist/core/extensions/builtin/goal/index.js +15 -3
- package/dist/core/extensions/builtin/goal/stop-pause.d.ts +2 -0
- package/dist/core/extensions/builtin/goal/stop-pause.js +5 -0
- package/dist/core/extensions/builtin/herdr/herdr-state.d.ts +10 -0
- package/dist/core/extensions/builtin/herdr/herdr-state.js +34 -6
- package/dist/core/extensions/builtin/herdr/index.js +6 -2
- package/dist/core/extensions/builtin/mcp/service.d.ts +9 -1
- package/dist/core/extensions/builtin/mcp/service.js +32 -1
- package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.d.ts +43 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-remote.js +158 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-render.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-render.js +4 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-source.d.ts +5 -2
- package/dist/core/extensions/builtin/terminal/rpc-monitor-source.js +19 -4
- package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.d.ts +4 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-targets.js +15 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.d.ts +10 -0
- package/dist/core/extensions/builtin/terminal/rpc-monitor-watch.js +28 -2
- package/dist/core/extensions/builtin/terminal/rpc-remote-source.d.ts +59 -0
- package/dist/core/extensions/builtin/terminal/rpc-remote-source.js +15 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +14 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor-rpc.js +8 -1
- package/dist/core/extensions/loader.js +8 -0
- package/dist/core/extensions/types.d.ts +6 -0
- package/dist/core/foreign-session-holders.d.ts +1 -1
- package/dist/core/foreign-session-holders.js +3 -3
- package/dist/core/session-import/jsonl-import.d.ts +8 -12
- package/dist/core/session-import/jsonl-import.js +99 -40
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.js +7 -2
- package/dist/core/session-store/durable-binding.d.ts +2 -0
- package/dist/core/session-store/durable-binding.js +26 -4
- package/dist/core/settings-manager.d.ts +20 -0
- package/dist/core/settings-manager.js +51 -0
- package/dist/core/trust-manager.d.ts +1 -18
- package/dist/core/trust-manager.js +3 -124
- package/dist/core/trust-requiring-resources.d.ts +21 -0
- package/dist/core/trust-requiring-resources.js +189 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/main.js +42 -12
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/session-control-commands.js +2 -0
- package/dist/modes/rpc/connection-handler.js +17 -0
- package/dist/modes/rpc/connection-question-bridge.js +7 -4
- package/dist/modes/rpc/effect-commands.d.ts +24 -0
- package/dist/modes/rpc/host-boot-extensions.d.ts +4 -0
- package/dist/modes/rpc/host-boot-extensions.js +17 -0
- package/dist/modes/rpc/host-lifecycle-shutdown.js +11 -2
- package/dist/modes/rpc/loopless-runtime-settings.js +5 -1
- package/dist/modes/rpc/method-classes-schema-session.d.ts +33 -0
- package/dist/modes/rpc/method-classes-schema-session.js +4 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +33 -0
- package/dist/modes/rpc/method-classes.d.ts +4 -0
- package/dist/modes/rpc/method-classes.js +7 -0
- package/dist/modes/rpc/multi-session-host.d.ts +2 -0
- package/dist/modes/rpc/multi-session-host.js +7 -2
- package/dist/modes/rpc/project-workspace-commands.d.ts +60 -0
- package/dist/modes/rpc/project-workspace-commands.js +145 -0
- package/dist/modes/rpc/rpc-error-types.d.ts +11 -1
- package/dist/modes/rpc/rpc-error-types.js +10 -0
- package/dist/modes/rpc/rpc-input-validation.js +4 -0
- package/dist/modes/rpc/rpc-mutation-response-types.d.ts +13 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +13 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +15 -0
- package/dist/modes/rpc/session-attachment-gate.js +2 -0
- package/dist/modes/rpc/session-command-activity.js +2 -0
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +41 -2
- package/docs/extensions.md +17 -1
- package/docs/rpc.md +121 -4
- package/docs/terminal-tools.md +8 -0
- 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-FUQJK2TU.js +0 -2
- package/dist/bundle/chunks/chunk-POZMRM6V.js +0 -2
- package/dist/bundle/chunks/chunk-XBG63MJZ.js +0 -8
- package/dist/bundle/chunks/chunk-YP47G4YP.js +0 -3
- package/dist/bundle/chunks/rpc-monitor-source-KKN2UP4T.js +0 -3
- 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
|
-
|
|
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, {
|
|
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
|
-
|
|
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, {
|
|
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
|