@code-yeongyu/senpi 2026.10.25-4 → 2026.10.26-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 +47 -0
- package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-ODSFJCXR.js} +3 -3
- package/dist/bundle/chunks/{args-B444OQBK.js → args-SWKJAYQD.js} +1 -1
- package/dist/bundle/chunks/azure-openai-responses-REGXS4XO.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
- package/dist/bundle/chunks/chunk-2XF6W6F6.js +410 -0
- package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-4QVORFQ2.js} +1 -1
- package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-5W3DTM2J.js} +2 -2
- package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-6GAMRAVM.js} +9 -7
- package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-7IZH2KQX.js} +9 -7
- package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-ARSENVDY.js} +1 -1
- package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-CNDPCYNT.js} +1 -1
- package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-CYOHFREV.js} +1 -1
- package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-DNDZPKIQ.js} +2 -2
- package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-EQNUL2WB.js} +4 -4
- package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-ESNSRUW4.js} +1 -1
- package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-FQDARFHT.js} +1 -1
- package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-GPPOPK76.js} +3 -3
- package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
- package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
- package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
- package/dist/bundle/chunks/chunk-I2WFLKZ2.js +5 -0
- package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-IHKHCWWG.js} +1 -1
- package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-IY4G44MN.js} +1 -1
- package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
- package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-JH7MVRNJ.js} +1 -1
- package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
- package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-KRO63T4Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-KTDIIF2F.js} +1 -1
- package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-NDNIMC5W.js} +1 -1
- package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-ODRGPC5S.js} +1 -1
- package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-PDG4XWO2.js} +1 -1
- package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-PPECSSWX.js} +1 -1
- package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-PY4ZQHTN.js} +1 -1
- package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-RPW3XQW3.js} +48 -42
- package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-RQ5JTXUC.js} +1 -1
- package/dist/bundle/chunks/chunk-TQ67PB7U.js +13 -0
- package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-UJPG4ROT.js} +1 -1
- package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-XM36IFP3.js} +1 -1
- package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
- package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
- package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-Z6BREXV3.js} +1 -1
- package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
- package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
- package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ZP6JJDYV.js} +1 -1
- package/dist/bundle/chunks/cli-main-Q2GA53XM.js +3 -0
- package/dist/bundle/chunks/client-LOS6GSEW.js +2 -0
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-K5H6BJP3.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
- package/dist/bundle/chunks/cursor-agent.js +3 -3
- package/dist/bundle/chunks/devin-agent.js +3 -3
- package/dist/bundle/chunks/dispatch-5R3TYAYB.js +9 -0
- package/dist/bundle/chunks/google-generative-ai-FHYFXKLA.js +2 -0
- package/dist/bundle/chunks/google-vertex-5DWE5U5K.js +2 -0
- package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-FETLIF3W.js} +1 -1
- package/dist/bundle/chunks/host-command-XEB7SHT3.js +2 -0
- package/dist/bundle/chunks/{host-lifecycle-3VHHIGDL.js → host-lifecycle-HX3EGRRW.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +2 -2
- package/dist/bundle/chunks/{host-session-command-56XF6ACS.js → host-session-command-FFN52LXJ.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-QL2WEZTN.js → host-socket-request-K4LCUOUD.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-5DEKY3YW.js +2 -0
- package/dist/bundle/chunks/{mcp-presenter-bootstrap-XUSCQVRC.js → mcp-presenter-bootstrap-FOMKF4TU.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-DH44NOYV.js → mistral-conversations-I2PI7DDF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-OPR5VBFO.js} +6 -7
- package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-WSEX6SH7.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-YCBHTBSS.js} +1 -1
- package/dist/bundle/chunks/openai-responses-GXNSQF4A.js +3 -0
- package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-W55E3LHP.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-O6GISIRW.js} +2 -2
- package/dist/bundle/chunks/{role-JVSBNKGE.js → role-SHIZQKK4.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-2PCZWNKF.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-IDUJTSYS.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-AE3C6EQV.js → rpc-monitor-source-DRZXJHTL.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-VS7CZC2J.js → schedule-command-B7RRSANU.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-LMZH5JLH.js +3 -0
- package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-DTGBOXDR.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +214 -205
- package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-XGYJJVSE.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +5 -0
- package/dist/cli/host-command.js +7 -1
- package/dist/cli.js +21 -3
- package/dist/core/agent-session.d.ts +11 -1
- package/dist/core/agent-session.js +19 -2
- package/dist/core/extensions/builtin/compaction/index.js +7 -1
- package/dist/core/extensions/builtin/herdr/index.js +9 -5
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
- package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
- package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
- package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
- package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
- package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
- package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/request.js +4 -1
- package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
- package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
- package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
- package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
- package/dist/core/extensions/builtin/mcp/resources.js +4 -0
- package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
- package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
- package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
- package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
- package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
- package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
- package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
- package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-children.d.ts +3 -1
- package/dist/core/lifecycle-children.js +79 -15
- package/dist/core/lifecycle-record.js +1 -24
- package/dist/core/lifecycle-session-binding.js +2 -1
- package/dist/core/lifecycle-wait-items.d.ts +17 -0
- package/dist/core/lifecycle-wait-items.js +72 -0
- package/dist/core/model-runtime.js +6 -1
- package/dist/core/provider-api-key-auth.d.ts +6 -1
- package/dist/core/provider-api-key-auth.js +11 -4
- package/dist/core/provider-composer.d.ts +8 -1
- package/dist/core/provider-composer.js +7 -3
- package/dist/core/sdk.js +4 -1
- package/dist/core/session-activity.js +2 -0
- package/dist/core/session-lifecycle-types.d.ts +20 -4
- package/dist/core/session-lifecycle.js +35 -4
- package/dist/core/structured-session-lane.d.ts +33 -0
- package/dist/core/structured-session-lane.js +62 -0
- package/dist/core/work-custody-record.d.ts +2 -1
- package/dist/core/work-custody-record.js +2 -0
- package/dist/core/work-obligations.d.ts +1 -1
- package/dist/core/work-obligations.js +8 -2
- package/dist/modes/interactive/session-control-commands.js +21 -4
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
- package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
- package/dist/modes/json-event.d.ts +79 -1
- package/dist/modes/json-event.js +79 -1
- package/dist/modes/rpc/compact-content-index.d.ts +85 -0
- package/dist/modes/rpc/compact-content-index.js +254 -0
- package/dist/modes/rpc/compact-content-store.d.ts +59 -0
- package/dist/modes/rpc/compact-content-store.js +134 -0
- package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
- package/dist/modes/rpc/compact-index-pages.js +31 -0
- package/dist/modes/rpc/compact-session-state.d.ts +83 -0
- package/dist/modes/rpc/compact-session-state.js +197 -0
- package/dist/modes/rpc/compact-spool-dir.d.ts +8 -0
- package/dist/modes/rpc/compact-spool-dir.js +94 -0
- package/dist/modes/rpc/compact-spool-storage.d.ts +53 -0
- package/dist/modes/rpc/compact-spool-storage.js +162 -0
- package/dist/modes/rpc/compact-storage-io.d.ts +28 -0
- package/dist/modes/rpc/compact-storage-io.js +25 -0
- package/dist/modes/rpc/compact-transition.d.ts +41 -0
- package/dist/modes/rpc/compact-transition.js +12 -0
- package/dist/modes/rpc/connection-handler.d.ts +4 -0
- package/dist/modes/rpc/connection-handler.js +51 -13
- package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
- package/dist/modes/rpc/connection-question-bridge.js +6 -2
- package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
- package/dist/modes/rpc/extension-ui-response.js +27 -7
- package/dist/modes/rpc/get-snapshot.d.ts +18 -0
- package/dist/modes/rpc/get-snapshot.js +43 -0
- package/dist/modes/rpc/host-capabilities.js +3 -0
- package/dist/modes/rpc/host-ensure-start.js +6 -3
- package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.js +1 -0
- package/dist/modes/rpc/host-handoff.d.ts +2 -0
- package/dist/modes/rpc/host-handover-request.d.ts +1 -1
- package/dist/modes/rpc/host-handover-request.js +4 -3
- package/dist/modes/rpc/host-handover-wire.js +6 -0
- package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
- package/dist/modes/rpc/host-idle-handover.js +3 -1
- package/dist/modes/rpc/host-observe-request.js +1 -0
- package/dist/modes/rpc/host-runner.d.ts +1 -0
- package/dist/modes/rpc/host-runner.js +9 -7
- package/dist/modes/rpc/host-successor.js +4 -0
- package/dist/modes/rpc/json-normal-value.d.ts +6 -0
- package/dist/modes/rpc/json-normal-value.js +100 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
- package/dist/modes/rpc/media-placeholders.d.ts +9 -3
- package/dist/modes/rpc/media-placeholders.js +12 -4
- package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
- package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-session-state.js +2 -0
- package/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/modes/rpc/security-presentations.js +2 -1
- package/dist/modes/rpc/session-attachment-gate.js +1 -0
- package/dist/modes/rpc/session-binding.js +30 -17
- package/dist/modes/rpc/session-command-activity.js +1 -0
- package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
- package/dist/modes/rpc/session-event-fanout.js +17 -9
- package/dist/modes/rpc/session-event-writer.d.ts +2 -0
- package/dist/modes/rpc/session-event-writer.js +24 -5
- package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
- package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
- package/dist/modes/rpc/session-held.js +1 -0
- package/dist/modes/rpc/session-record-stream.d.ts +3 -3
- package/dist/modes/rpc/session-record-stream.js +3 -3
- package/dist/modes/rpc/session-status-stream.d.ts +2 -1
- package/dist/modes/rpc/session-status-stream.js +8 -2
- package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
- package/dist/modes/rpc/session-status-subscriptions.js +4 -0
- package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
- package/dist/modes/rpc/session-structured-lanes.js +24 -0
- package/dist/modes/rpc/session-worker-client.js +6 -5
- package/dist/modes/rpc/session-worker.js +5 -2
- package/dist/modes/rpc/snapshot-types.d.ts +29 -0
- package/dist/modes/rpc/snapshot-types.js +2 -0
- package/dist/modes/rpc/structured-session-records.d.ts +12 -0
- package/dist/modes/rpc/structured-session-records.js +88 -0
- package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
- package/dist/modes/rpc/ui-answer-ledger.js +34 -0
- package/dist/senpi +21 -3
- package/docs/extensions.md +7 -1
- package/docs/mcp.md +20 -0
- package/docs/rpc.md +61 -2
- package/package.json +7 -6
- package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
- package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
- package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
- package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
- package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
- package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
- package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
- package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
- package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
- package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
- package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
- package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
- package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
|
@@ -47,7 +47,7 @@ import { runAbort, runGetEffectStatus, runQueuedInputChange, runSetModel, runSet
|
|
|
47
47
|
import { EffectLedger } from "./effect-ledger.js";
|
|
48
48
|
import { EFFECT_LEDGER_CAPABILITY } from "./effect-record.js";
|
|
49
49
|
import { createRpcEventOutputBuffer } from "./event-output-buffer.js";
|
|
50
|
-
import { settleExtensionUiResponse } from "./extension-ui-response.js";
|
|
50
|
+
import { answeredUiRequestId, settleExtensionUiResponse } from "./extension-ui-response.js";
|
|
51
51
|
import { answerGetEntries } from "./get-entries-command.js";
|
|
52
52
|
import { runHoldInput } from "./held-input-commands.js";
|
|
53
53
|
import { HostSessionControl } from "./host-session-control.js";
|
|
@@ -57,11 +57,14 @@ import { protocolIdentity } from "./protocol-identity.js";
|
|
|
57
57
|
import { buildRpcCommandsForSession, createCommandsChangedEvent, rpcCommandListDigest } from "./rpc-command-surface.js";
|
|
58
58
|
import { rpcCommandPayloadError, rpcCommandShapeError, rpcMessageLengthError, sessionRetryFallbackError, } from "./rpc-input-validation.js";
|
|
59
59
|
import { buildRpcSessionState } from "./rpc-session-state.js";
|
|
60
|
+
import { getSnapshot } from "./get-snapshot.js";
|
|
61
|
+
import { sessionUiAnswerLedger } from "./ui-answer-ledger.js";
|
|
60
62
|
import { RPC_ERROR_EVAL_CELL_NOT_FOUND, RPC_ERROR_EVAL_CELLS_UNAVAILABLE, RPC_ERROR_INVALID_ARGUMENT, RPC_ERROR_INVALID_EVAL_CELL_REQUEST, RPC_ERROR_MEDIA_NOT_FOUND, RPC_ERROR_MODEL_NOT_FOUND, STEER_WHEN_IDLE_CAPABILITY, USER_ACTIVITY_CAPABILITY, } from "./rpc-types.js";
|
|
61
63
|
import { runPauseForRun, runResumeForRun } from "./run-hold-commands.js";
|
|
62
64
|
import { availableModelsData, interruptRunningTurn } from "./session-control-actions-data.js";
|
|
63
65
|
import { SessionExtensionUiRequests, sessionDialogRequests } from "./session-extension-ui-requests.js";
|
|
64
66
|
import { subscribeSessionRecords } from "./session-record-stream.js";
|
|
67
|
+
import { subscribeStructuredSessionRecords } from "./structured-session-records.js";
|
|
65
68
|
import { RpcSessionRelocation } from "./session-relocation.js";
|
|
66
69
|
import { answerHostResponse, isHostResponse, relocationChannel, } from "./session-relocation-channel.js";
|
|
67
70
|
export { buildRpcSessionState } from "./rpc-session-state.js";
|
|
@@ -188,6 +191,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
188
191
|
// Set by the first command that asks this session for a turn; `set_retry_fallback` is refused after it.
|
|
189
192
|
let turnRequested = false;
|
|
190
193
|
let unsubscribe;
|
|
194
|
+
let unsubscribeStructured;
|
|
191
195
|
let unsubscribeBackpressure;
|
|
192
196
|
let unsubscribeLoadedSurfaces;
|
|
193
197
|
let unsubscribeExtensionEvents;
|
|
@@ -197,7 +201,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
197
201
|
let loadedSurfacesDigest;
|
|
198
202
|
let rpcCommandsDigest;
|
|
199
203
|
let suppressLoadedSurfaceEvents = false;
|
|
200
|
-
const eventOutput =
|
|
204
|
+
const eventOutput = sink.writeRecord
|
|
205
|
+
? undefined
|
|
206
|
+
: createRpcEventOutputBuffer(sink.writeRaw, options.eventFlushScheduler);
|
|
201
207
|
const effectContext = () => ({
|
|
202
208
|
session,
|
|
203
209
|
by: { connectionId: options.clientInfo?.connectionId() ?? "local" },
|
|
@@ -205,10 +211,18 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
205
211
|
});
|
|
206
212
|
const tagSessionRecord = (value) => routingSessionId === undefined ? value : { ...value, sessionId: routingSessionId };
|
|
207
213
|
const output = (obj) => {
|
|
208
|
-
|
|
214
|
+
const record = tagSessionRecord(obj);
|
|
215
|
+
if (sink.writeRecord)
|
|
216
|
+
sink.writeRecord(record);
|
|
217
|
+
else
|
|
218
|
+
eventOutput?.writeImmediate(record);
|
|
209
219
|
};
|
|
210
220
|
const outputEvent = (event) => {
|
|
211
|
-
|
|
221
|
+
const record = tagSessionRecord(event);
|
|
222
|
+
if (sink.writeRecord)
|
|
223
|
+
sink.writeRecord(record);
|
|
224
|
+
else
|
|
225
|
+
eventOutput?.enqueueEvent(record);
|
|
212
226
|
};
|
|
213
227
|
const currentLoadedSurfaces = () => {
|
|
214
228
|
const extensions = loadedExtensions(session);
|
|
@@ -268,7 +282,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
268
282
|
});
|
|
269
283
|
});
|
|
270
284
|
const waitForRpcBackpressure = async () => {
|
|
271
|
-
eventOutput
|
|
285
|
+
eventOutput?.flushEvents();
|
|
272
286
|
await sink.waitForBackpressure();
|
|
273
287
|
};
|
|
274
288
|
const success = (id, command, data) => {
|
|
@@ -292,10 +306,15 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
292
306
|
};
|
|
293
307
|
};
|
|
294
308
|
// Pending extension UI requests waiting for response
|
|
295
|
-
const
|
|
309
|
+
const uiRequestChanged = (change) => {
|
|
310
|
+
session.refreshLifecycle();
|
|
311
|
+
if (change && routingSessionId !== undefined)
|
|
312
|
+
output(change);
|
|
313
|
+
};
|
|
314
|
+
const pendingExtensionRequests = new SessionExtensionUiRequests(uiRequestChanged);
|
|
296
315
|
const grantRequests = new Map();
|
|
297
316
|
sessionDialogRequests.set(session, pendingExtensionRequests);
|
|
298
|
-
const questions = new ConnectionQuestionBridge(output,
|
|
317
|
+
const questions = new ConnectionQuestionBridge(output, uiRequestChanged);
|
|
299
318
|
let shutdownRequested = false;
|
|
300
319
|
// In-flight OAuth logins, keyed by provider. login_start registers one; the
|
|
301
320
|
// login promise clears it on completion; login_cancel aborts it. The flow is
|
|
@@ -745,6 +764,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
745
764
|
unsubscribe = subscribeSessionRecords(session, outputEvent, (abortSource) => {
|
|
746
765
|
lastAbortSource = abortSource;
|
|
747
766
|
});
|
|
767
|
+
unsubscribeStructured?.();
|
|
768
|
+
unsubscribeStructured = sink.writeStructured
|
|
769
|
+
? subscribeStructuredSessionRecords(session, (record) => sink.writeStructured?.(tagSessionRecord(record)))
|
|
770
|
+
: undefined;
|
|
748
771
|
unsubscribeBackpressure = session.agent.subscribe(async () => {
|
|
749
772
|
await waitForRpcBackpressure();
|
|
750
773
|
});
|
|
@@ -858,6 +881,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
858
881
|
capabilities: [
|
|
859
882
|
...new Set([
|
|
860
883
|
"multi_session",
|
|
884
|
+
"snapshot_cursor",
|
|
885
|
+
"ui_answer_ledger",
|
|
861
886
|
"session_lifecycle",
|
|
862
887
|
AUTO_TITLE_SESSIONS_CAPABILITY,
|
|
863
888
|
MEDIA_PLACEHOLDERS_CAPABILITY,
|
|
@@ -1080,6 +1105,18 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1080
1105
|
refreshGrantPresentation();
|
|
1081
1106
|
return success(id, "set_client_info");
|
|
1082
1107
|
}
|
|
1108
|
+
case "get_snapshot": {
|
|
1109
|
+
const snapshot = getSnapshot(session, command, {
|
|
1110
|
+
lastAbortSource,
|
|
1111
|
+
enqueue: (markerId) => {
|
|
1112
|
+
if (routingSessionId !== undefined)
|
|
1113
|
+
output({ type: "snapshot_marker", markerId });
|
|
1114
|
+
},
|
|
1115
|
+
});
|
|
1116
|
+
return "error" in snapshot
|
|
1117
|
+
? error(id, "get_snapshot", snapshot.error, RPC_ERROR_INVALID_ARGUMENT)
|
|
1118
|
+
: success(id, "get_snapshot", snapshot.data);
|
|
1119
|
+
}
|
|
1083
1120
|
case "get_state":
|
|
1084
1121
|
return success(id, "get_state", buildRpcSessionState(session, lastAbortSource));
|
|
1085
1122
|
case "request_self_settle":
|
|
@@ -1673,21 +1710,20 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1673
1710
|
parsed !== null &&
|
|
1674
1711
|
"type" in parsed &&
|
|
1675
1712
|
parsed.type === "extension_ui_response") {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
typeof parsed.id === "string" &&
|
|
1679
|
-
grantRequests.has(parsed.id)) {
|
|
1713
|
+
const requestId = answeredUiRequestId(parsed);
|
|
1714
|
+
if (!options.workerSecurityBridge && requestId !== undefined && grantRequests.has(requestId)) {
|
|
1680
1715
|
const sender = options.clientInfo?.connectionId();
|
|
1681
1716
|
const capable = sender === undefined
|
|
1682
1717
|
? clientCapabilities?.includes("grant_select") === true
|
|
1683
1718
|
: options.clientInfo?.hasCapability?.(sender, "grant_select") === true;
|
|
1684
|
-
if (grantRequests.get(
|
|
1719
|
+
if (grantRequests.get(requestId) !== sender || !capable)
|
|
1685
1720
|
return;
|
|
1686
1721
|
}
|
|
1687
1722
|
const reply = settleExtensionUiResponse(parsed, {
|
|
1688
1723
|
questions,
|
|
1689
1724
|
dialogs: pendingExtensionRequests,
|
|
1690
1725
|
routed: routingSessionId !== undefined,
|
|
1726
|
+
ledger: sessionUiAnswerLedger(session),
|
|
1691
1727
|
// An answer is a person's input by its entry point, whatever the connection role.
|
|
1692
1728
|
beforeAnswer: () => session.admitHumanAnswer(),
|
|
1693
1729
|
});
|
|
@@ -1719,7 +1755,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1719
1755
|
// A single-session connection hears its account's new limit windows as they are reported. A host
|
|
1720
1756
|
// session's handler (it has a routing `sessionId`) does not: the host broadcasts the record once.
|
|
1721
1757
|
const stopLimitEvents = options.sessionId === undefined
|
|
1722
|
-
? subscriptionLimits.subscribe((limits) =>
|
|
1758
|
+
? subscriptionLimits.subscribe((limits) => outputEvent({ type: "subscription_limits", ...limits }))
|
|
1723
1759
|
: () => { };
|
|
1724
1760
|
const dispose = async (disposeOptions = {}) => {
|
|
1725
1761
|
unbindMcpForms?.();
|
|
@@ -1735,10 +1771,12 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1735
1771
|
pendingExtensionRequests.close();
|
|
1736
1772
|
unsubscribeProviderAccountEvents();
|
|
1737
1773
|
unsubscribe?.();
|
|
1774
|
+
unsubscribeStructured?.();
|
|
1738
1775
|
unsubscribeBackpressure?.();
|
|
1739
1776
|
unsubscribeLoadedSurfaces?.();
|
|
1740
1777
|
unsubscribeExtensionEvents?.();
|
|
1741
1778
|
unsubscribe = undefined;
|
|
1779
|
+
unsubscribeStructured = undefined;
|
|
1742
1780
|
unsubscribeBackpressure = undefined;
|
|
1743
1781
|
unsubscribeLoadedSurfaces = undefined;
|
|
1744
1782
|
unsubscribeExtensionEvents = undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionUIContext, QuestionRequest, QuestionResponse } from "../../core/extensions/types.ts";
|
|
2
|
+
import type { UiRequestChange } from "./snapshot-types.ts";
|
|
2
3
|
import type { RpcExtensionUIProgress, RpcExtensionUIResponse, RpcQuestionUiRequest } from "./rpc-types.ts";
|
|
3
4
|
type Options = Parameters<NonNullable<ExtensionUIContext["question"]>>[1];
|
|
4
5
|
export declare const sessionQuestionBridges: WeakMap<object, ConnectionQuestionBridge>;
|
|
@@ -7,7 +8,7 @@ export declare class ConnectionQuestionBridge {
|
|
|
7
8
|
private readonly resolved;
|
|
8
9
|
private readonly output;
|
|
9
10
|
private readonly changed;
|
|
10
|
-
constructor(output: (record: object) => void, changed?: () => void);
|
|
11
|
+
constructor(output: (record: object) => void, changed?: (change?: UiRequestChange) => void);
|
|
11
12
|
pendingQuestions(): RpcQuestionUiRequest[];
|
|
12
13
|
ask(request: QuestionRequest, opts?: Options): Promise<QuestionResponse>;
|
|
13
14
|
respond(response: RpcExtensionUIResponse, beforeAnswer?: () => void): boolean | "question_incomplete" | "question_already_resolved";
|
|
@@ -43,7 +43,11 @@ export class ConnectionQuestionBridge {
|
|
|
43
43
|
clearTimeout(timer);
|
|
44
44
|
opts?.signal?.removeEventListener("abort", cancel);
|
|
45
45
|
this.pending.delete(id);
|
|
46
|
-
this.changed?.(
|
|
46
|
+
this.changed?.({
|
|
47
|
+
type: "ui_request_settled",
|
|
48
|
+
uiRequestId: id,
|
|
49
|
+
disposition: status === "answered" || status === "comment-submitted" ? "answered" : "cancelled",
|
|
50
|
+
});
|
|
47
51
|
this.resolved.add(id);
|
|
48
52
|
const result = {
|
|
49
53
|
status,
|
|
@@ -121,7 +125,7 @@ export class ConnectionQuestionBridge {
|
|
|
121
125
|
},
|
|
122
126
|
});
|
|
123
127
|
arm();
|
|
124
|
-
this.changed?.();
|
|
128
|
+
this.changed?.({ type: "ui_request_pending", uiRequestId: id, method: "question", request: frame });
|
|
125
129
|
this.output(frame);
|
|
126
130
|
opts?.signal?.addEventListener("abort", cancel, { once: true });
|
|
127
131
|
if (opts?.signal?.aborted)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { QuestionRequest, QuestionResponse } from "../../core/extensions/types.ts";
|
|
2
2
|
import type { RpcExtensionUIResponse } from "./rpc-types.ts";
|
|
3
|
+
import type { UiAnswerLedger } from "./ui-answer-ledger.ts";
|
|
3
4
|
/**
|
|
4
5
|
* An `extension_ui_response` carries two ids: `id`, the frame's own correlation id that its reply
|
|
5
6
|
* echoes, and `uiRequestId`, the extension UI request it answers. The short form every older client
|
|
@@ -23,6 +24,8 @@ export type ExtensionUiResponseReply = {
|
|
|
23
24
|
readonly id: string;
|
|
24
25
|
readonly type: "response";
|
|
25
26
|
readonly command: "extension_ui_response";
|
|
27
|
+
/** The answer's outcome for its request; absent when a malformed answer was refused before settling anything. */
|
|
28
|
+
readonly disposition?: "applied" | "replay" | "already_answered" | "unknown";
|
|
26
29
|
} & ({
|
|
27
30
|
readonly success: true;
|
|
28
31
|
} | {
|
|
@@ -37,13 +40,15 @@ type DialogAnswers = {
|
|
|
37
40
|
};
|
|
38
41
|
/**
|
|
39
42
|
* Settles one response on a host connection and returns its reply, keyed by the frame `id`.
|
|
40
|
-
* `undefined` only on an unrouted (single-session stdio) connection for
|
|
41
|
-
* matches: that connection has always ignored such a response, and still does.
|
|
43
|
+
* `undefined` only on an unrouted (single-session stdio) connection for an unnamed response no
|
|
44
|
+
* request here matches: that connection has always ignored such a response, and still does. A named
|
|
45
|
+
* response (`answerId`) asks for a receipt, so every connection answers it.
|
|
42
46
|
*/
|
|
43
47
|
export declare function settleExtensionUiResponse(response: RpcExtensionUIResponse, targets: {
|
|
44
48
|
readonly questions: QuestionAnswers;
|
|
45
49
|
readonly dialogs: DialogAnswers;
|
|
46
50
|
readonly routed: boolean;
|
|
51
|
+
readonly ledger?: UiAnswerLedger;
|
|
47
52
|
readonly beforeAnswer?: () => void;
|
|
48
53
|
}): ExtensionUiResponseReply | undefined;
|
|
49
54
|
export {};
|
|
@@ -28,25 +28,45 @@ export function unansweredQuestionIds(questions, answers) {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Settles one response on a host connection and returns its reply, keyed by the frame `id`.
|
|
31
|
-
* `undefined` only on an unrouted (single-session stdio) connection for
|
|
32
|
-
* matches: that connection has always ignored such a response, and still does.
|
|
31
|
+
* `undefined` only on an unrouted (single-session stdio) connection for an unnamed response no
|
|
32
|
+
* request here matches: that connection has always ignored such a response, and still does. A named
|
|
33
|
+
* response (`answerId`) asks for a receipt, so every connection answers it.
|
|
33
34
|
*/
|
|
34
35
|
export function settleExtensionUiResponse(response, targets) {
|
|
36
|
+
if (response.answerId !== undefined &&
|
|
37
|
+
(typeof response.answerId !== "string" || response.answerId.length < 1 || response.answerId.length > 256))
|
|
38
|
+
return refused(response.id, "invalid_answer_id");
|
|
35
39
|
const requestId = answeredUiRequestId(response) ?? response.id;
|
|
40
|
+
const receipt = response.answerId === undefined ? undefined : targets.ledger?.lookup(requestId, response.answerId);
|
|
41
|
+
if (receipt === "replay")
|
|
42
|
+
return { ...reply(response.id), success: true, disposition: "replay" };
|
|
43
|
+
if (receipt === "already_answered")
|
|
44
|
+
return { ...reply(response.id), success: false, error: "already_answered", disposition: receipt };
|
|
36
45
|
const answer = requestId === response.id ? response : { ...response, id: requestId };
|
|
37
46
|
const question = targets.questions.respond(answer, targets.beforeAnswer);
|
|
38
47
|
if (typeof question === "string")
|
|
39
|
-
return refused(response.id, question
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
return refused(response.id, response.answerId !== undefined && question === "question_already_resolved"
|
|
49
|
+
? "unknown_extension_ui_request"
|
|
50
|
+
: question);
|
|
51
|
+
if (question || targets.dialogs.resolve(answer, targets.beforeAnswer)) {
|
|
52
|
+
targets.ledger?.record(requestId, response.answerId);
|
|
53
|
+
return { ...reply(response.id), success: true, disposition: "applied" };
|
|
54
|
+
}
|
|
42
55
|
// A routed binding owns exactly one session's request map: an unmatched response is a protocol
|
|
43
56
|
// error for its sender, never a cross-session match.
|
|
44
|
-
return targets.routed
|
|
57
|
+
return targets.routed || response.answerId !== undefined
|
|
58
|
+
? refused(response.id, "unknown_extension_ui_request")
|
|
59
|
+
: undefined;
|
|
45
60
|
}
|
|
46
61
|
function reply(id) {
|
|
47
62
|
return { id, type: "response", command: "extension_ui_response" };
|
|
48
63
|
}
|
|
49
64
|
function refused(id, error) {
|
|
50
|
-
|
|
65
|
+
const disposition = error === "question_already_resolved"
|
|
66
|
+
? "already_answered"
|
|
67
|
+
: error === "unknown_extension_ui_request"
|
|
68
|
+
? "unknown"
|
|
69
|
+
: undefined;
|
|
70
|
+
return { ...reply(id), success: false, error, ...(disposition === undefined ? {} : { disposition }) };
|
|
51
71
|
}
|
|
52
72
|
//# sourceMappingURL=extension-ui-response.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentAbortSource } from "../../core/agent-abort-provenance.ts";
|
|
2
|
+
import type { AgentSession } from "../../core/agent-session.ts";
|
|
3
|
+
import type { RpcSnapshot } from "./snapshot-types.ts";
|
|
4
|
+
type SnapshotRequest = {
|
|
5
|
+
readonly tail?: unknown;
|
|
6
|
+
readonly maxBytes?: unknown;
|
|
7
|
+
};
|
|
8
|
+
/** Capture and mark on the source loop, before any response-writing await. */
|
|
9
|
+
export declare function getSnapshot(session: AgentSession, request: SnapshotRequest, cursor: {
|
|
10
|
+
readonly enqueue: (markerId: string) => void;
|
|
11
|
+
readonly lastAbortSource?: AgentAbortSource;
|
|
12
|
+
}): {
|
|
13
|
+
readonly data: RpcSnapshot;
|
|
14
|
+
} | {
|
|
15
|
+
readonly error: string;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=get-snapshot.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { describeUsageLimitEntry } from "../../core/retry-fallback/usage-limit.js";
|
|
3
|
+
import { currentResponseByteBudget, DEFAULT_ENTRY_PAGE_BYTES } from "./get-entries-page.js";
|
|
4
|
+
import { buildRpcSessionState } from "./rpc-session-state.js";
|
|
5
|
+
/** Capture and mark on the source loop, before any response-writing await. */
|
|
6
|
+
export function getSnapshot(session, request, cursor) {
|
|
7
|
+
const tail = request.tail === undefined ? 100 : request.tail;
|
|
8
|
+
const maxBytes = request.maxBytes === undefined ? DEFAULT_ENTRY_PAGE_BYTES : request.maxBytes;
|
|
9
|
+
if (typeof tail !== "number" || !Number.isSafeInteger(tail) || tail < 0)
|
|
10
|
+
return { error: "tail must be a non-negative integer" };
|
|
11
|
+
if (typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes <= 0)
|
|
12
|
+
return { error: "maxBytes must be a positive integer" };
|
|
13
|
+
const state = buildRpcSessionState(session, cursor.lastAbortSource);
|
|
14
|
+
const history = session.sessionManager.getEntries();
|
|
15
|
+
const entries = [];
|
|
16
|
+
let bytes = 2;
|
|
17
|
+
for (let i = history.length - 1; i >= 0 && entries.length < tail; i--) {
|
|
18
|
+
const entry = history[i];
|
|
19
|
+
if (entry === undefined)
|
|
20
|
+
break;
|
|
21
|
+
const projected = describeUsageLimitEntry(entry);
|
|
22
|
+
const size = Buffer.byteLength(JSON.stringify(projected)) + (entries.length > 0 ? 1 : 0);
|
|
23
|
+
if (bytes + size > Math.min(maxBytes, currentResponseByteBudget()))
|
|
24
|
+
break;
|
|
25
|
+
entries.push(projected);
|
|
26
|
+
bytes += size;
|
|
27
|
+
}
|
|
28
|
+
entries.reverse();
|
|
29
|
+
const history_truncated = entries.length < history.length;
|
|
30
|
+
const markerId = randomUUID();
|
|
31
|
+
// Some state fields and entry payloads are live references: detach them before yielding, too.
|
|
32
|
+
const data = structuredClone({
|
|
33
|
+
state,
|
|
34
|
+
entries,
|
|
35
|
+
history_truncated,
|
|
36
|
+
next_before: history_truncated ? (entries[0]?.id ?? null) : null,
|
|
37
|
+
markerId,
|
|
38
|
+
});
|
|
39
|
+
session.releaseFullHistoryView();
|
|
40
|
+
cursor.enqueue(markerId);
|
|
41
|
+
return { data };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=get-snapshot.js.map
|
|
@@ -20,6 +20,9 @@ export function multiSessionHostCapabilities(options) {
|
|
|
20
20
|
return [
|
|
21
21
|
...new Set([
|
|
22
22
|
"multi_session",
|
|
23
|
+
"snapshot_cursor",
|
|
24
|
+
"ui_answer_ledger",
|
|
25
|
+
"dialog_observer",
|
|
23
26
|
...(options.mcpPresenters ? ["mcp_form_presenter_v1"] : []),
|
|
24
27
|
"session_lifecycle",
|
|
25
28
|
"expected_host_instance",
|
|
@@ -16,7 +16,7 @@ import { keepOwnerPipe, OWNER_WATCH_FD } from "./host-lifecycle-launch.js";
|
|
|
16
16
|
import { pollProtocolInfo } from "./host-readiness.js";
|
|
17
17
|
import { initialHostEnvironment } from "./host-spawn-environment.js";
|
|
18
18
|
import { hostLaunchProfile } from "./protocol-identity.js";
|
|
19
|
-
import {
|
|
19
|
+
import { ensureSocketSecret, socketSecretPath } from "./socket-transport.js";
|
|
20
20
|
import { relayPresenterBootstrap } from "./mcp-presenter-bootstrap.js";
|
|
21
21
|
export const DEFAULT_READINESS_TIMEOUT_MS = 10_000;
|
|
22
22
|
export { DEFAULT_STOP_TIMEOUT_MS };
|
|
@@ -38,9 +38,11 @@ export async function startHost(paths, socket, options, generation = 0) {
|
|
|
38
38
|
reason,
|
|
39
39
|
});
|
|
40
40
|
// The settings file must exist before the supervisor reads it at boot, so it
|
|
41
|
-
// records the policy before the spawn instead of beside the pidfile.
|
|
41
|
+
// records the policy before the spawn instead of beside the pidfile. A valid public
|
|
42
|
+
// secret belongs to the endpoint, not to this start: clients that already read it
|
|
43
|
+
// derive the pipe name from it, so a start reuses it and never rotates it.
|
|
42
44
|
if (process.platform === "win32")
|
|
43
|
-
await
|
|
45
|
+
await ensureSocketSecret(socketSecretPath(socket));
|
|
44
46
|
await writeHostSettings(paths, {
|
|
45
47
|
socket,
|
|
46
48
|
capabilities: PINNED_HOST_CLIENT_CAPABILITIES,
|
|
@@ -63,6 +65,7 @@ export async function startHost(paths, socket, options, generation = 0) {
|
|
|
63
65
|
socket,
|
|
64
66
|
...(options.owner ? ["--owner-fd", String(OWNER_WATCH_FD)] : []),
|
|
65
67
|
...(options.presenterBootstrap ? ["--mcp-presenter-fd", "4"] : []),
|
|
68
|
+
...(options.mcpPresenterEnforcement ? ["--mcp-presenter-enforcement"] : []),
|
|
66
69
|
...(options.hostArgs ?? []),
|
|
67
70
|
];
|
|
68
71
|
const launch = testOptions?.spawn ?? testOptions?.launch?.(supervisorArgs) ?? defaultHostLaunch(supervisorArgs);
|
|
@@ -12,6 +12,8 @@ import type { Duplex } from "node:stream";
|
|
|
12
12
|
export type HostUpgradePolicy = "never" | "if-engine-differs";
|
|
13
13
|
export interface EnsureHostOptions {
|
|
14
14
|
readonly presenterBootstrap?: Duplex;
|
|
15
|
+
/** Deployment opt-in, never a project setting or proof of enrollment. Defaults off for a new host. */
|
|
16
|
+
readonly mcpPresenterEnforcement?: boolean;
|
|
15
17
|
readonly socket: string;
|
|
16
18
|
readonly agentDir?: string;
|
|
17
19
|
readonly keyRoot?: string;
|
|
@@ -206,6 +206,7 @@ async function upgradeGeneration(paths, socket, options, attachedPid) {
|
|
|
206
206
|
agentDir: options.agentDir ?? getAgentDir(),
|
|
207
207
|
hostArgs: options.hostArgs ?? [],
|
|
208
208
|
presenterBootstrap: options.presenterBootstrap,
|
|
209
|
+
mcpPresenterEnforcement: options.mcpPresenterEnforcement,
|
|
209
210
|
...(options.env ? { env: options.env } : {}),
|
|
210
211
|
...(options.policy ? { policy: options.policy } : {}),
|
|
211
212
|
_test: {
|
|
@@ -7,6 +7,8 @@ import type { HostLifecyclePolicyInput } from "./host-lifecycle.ts";
|
|
|
7
7
|
export declare const HANDOFF_LOCK_HOLD_MS: number;
|
|
8
8
|
export interface HandoffHostOptions {
|
|
9
9
|
readonly presenterBootstrap?: Duplex;
|
|
10
|
+
/** Deployment opt-in. An enforcing predecessor never downgrades during replacement. */
|
|
11
|
+
readonly mcpPresenterEnforcement?: boolean;
|
|
10
12
|
readonly socket: string;
|
|
11
13
|
readonly agentDir?: string;
|
|
12
14
|
/** Extra CLI arguments the successor's host child is launched with (provider pinning, extensions). */
|
|
@@ -9,5 +9,5 @@ export interface IdleHandoverTerms {
|
|
|
9
9
|
export declare function idleHandoverOutcome(target: {
|
|
10
10
|
readonly socket: string;
|
|
11
11
|
readonly agentDir: string;
|
|
12
|
-
}, spec: ResolvedHostLaunchSpec, terms: IdleHandoverTerms): Promise<HostOutcome>;
|
|
12
|
+
}, spec: ResolvedHostLaunchSpec, terms: IdleHandoverTerms, mcpPresenterEnforcement?: boolean): Promise<HostOutcome>;
|
|
13
13
|
//# sourceMappingURL=host-handover-request.d.ts.map
|
|
@@ -28,7 +28,7 @@ import { RUNTIME_IDENTITY_HANDOVER_CAPABILITY } from "./runtime-build-id.js";
|
|
|
28
28
|
const PROBE_TIMEOUT_MS = 10_000;
|
|
29
29
|
/** A host idle at once hands over before answering: its ensure-lock wait plus the handoff itself. */
|
|
30
30
|
const BEGIN_HANDOVER_TIMEOUT_MS = 2 * HANDOFF_LOCK_HOLD_MS + 20_000;
|
|
31
|
-
export async function idleHandoverOutcome(target, spec, terms) {
|
|
31
|
+
export async function idleHandoverOutcome(target, spec, terms, mcpPresenterEnforcement) {
|
|
32
32
|
const { socket } = target;
|
|
33
33
|
if (process.platform === "win32") {
|
|
34
34
|
return refusal("refuse", undefined, { reason: "upgrade_unsupported", socket, operationId: terms.operationId });
|
|
@@ -57,7 +57,7 @@ export async function idleHandoverOutcome(target, spec, terms) {
|
|
|
57
57
|
}
|
|
58
58
|
if (!before.capabilities.includes(RUNTIME_IDENTITY_HANDOVER_CAPABILITY))
|
|
59
59
|
return refuse("handover_unsupported");
|
|
60
|
-
const reply = await requestOnSocket(socket, beginHandoverCommand(target, spec, terms), BEGIN_HANDOVER_TIMEOUT_MS);
|
|
60
|
+
const reply = await requestOnSocket(socket, beginHandoverCommand(target, spec, terms, mcpPresenterEnforcement), BEGIN_HANDOVER_TIMEOUT_MS);
|
|
61
61
|
if (!isRecord(reply))
|
|
62
62
|
return reconcileLostReply(target, terms, before);
|
|
63
63
|
if (typeof reply.refused === "string") {
|
|
@@ -71,7 +71,7 @@ export async function idleHandoverOutcome(target, spec, terms) {
|
|
|
71
71
|
}
|
|
72
72
|
return answered(target, "handover_pending", before, terms, reply);
|
|
73
73
|
}
|
|
74
|
-
function beginHandoverCommand(target, spec, terms) {
|
|
74
|
+
function beginHandoverCommand(target, spec, terms, mcpPresenterEnforcement) {
|
|
75
75
|
const env = {};
|
|
76
76
|
for (const [name, value] of Object.entries(daemonEnvironment(process.env, daemonEnvOverrides(process.env, spec.env)))) {
|
|
77
77
|
if (value !== undefined)
|
|
@@ -88,6 +88,7 @@ function beginHandoverCommand(target, spec, terms) {
|
|
|
88
88
|
launch: { command: launch.command, args: launch.args },
|
|
89
89
|
host_args: spec.hostArgs,
|
|
90
90
|
env,
|
|
91
|
+
...(mcpPresenterEnforcement === true && { mcp_presenter_enforcement: true }),
|
|
91
92
|
...(spec.policy !== undefined && { policy: spec.policy }),
|
|
92
93
|
};
|
|
93
94
|
}
|
|
@@ -33,6 +33,10 @@ export function parseBeginHandover(record) {
|
|
|
33
33
|
}
|
|
34
34
|
const policy = isRecord(record.policy) ? record.policy : undefined;
|
|
35
35
|
const { agent_dir } = record;
|
|
36
|
+
const mcpPresenterEnforcement = record.mcp_presenter_enforcement;
|
|
37
|
+
if (mcpPresenterEnforcement !== undefined && typeof mcpPresenterEnforcement !== "boolean") {
|
|
38
|
+
return "mcp_presenter_enforcement must be a boolean";
|
|
39
|
+
}
|
|
36
40
|
if (agent_dir !== undefined && (typeof agent_dir !== "string" || agent_dir === "")) {
|
|
37
41
|
return "agent_dir must be a non-empty string";
|
|
38
42
|
}
|
|
@@ -44,6 +48,7 @@ export function parseBeginHandover(record) {
|
|
|
44
48
|
launch: { command: launch.command, args: launch.args },
|
|
45
49
|
hostArgs: host_args,
|
|
46
50
|
env: env,
|
|
51
|
+
...(mcpPresenterEnforcement !== undefined && { mcpPresenterEnforcement }),
|
|
47
52
|
...(policy !== undefined && { policy }),
|
|
48
53
|
...(agent_dir !== undefined && { agentDir: agent_dir }),
|
|
49
54
|
};
|
|
@@ -121,6 +126,7 @@ export async function performIdleHandover(context) {
|
|
|
121
126
|
Object.assign(env, request.env);
|
|
122
127
|
const result = await handoffHost({
|
|
123
128
|
presenterBootstrap: context.presenterBootstrap,
|
|
129
|
+
mcpPresenterEnforcement: request.mcpPresenterEnforcement,
|
|
124
130
|
socket,
|
|
125
131
|
agentDir,
|
|
126
132
|
hostArgs: request.hostArgs,
|
|
@@ -36,6 +36,8 @@ export interface HandoverLaunch {
|
|
|
36
36
|
readonly args: readonly string[];
|
|
37
37
|
}
|
|
38
38
|
export interface IdleHandoverRequest {
|
|
39
|
+
/** Successor deployment policy only; never enrollment authority. */
|
|
40
|
+
readonly mcpPresenterEnforcement?: boolean;
|
|
39
41
|
readonly operationId: string;
|
|
40
42
|
readonly ifInstanceId: string;
|
|
41
43
|
readonly ifGeneration: number;
|
|
@@ -40,7 +40,8 @@ export class HostIdleHandover {
|
|
|
40
40
|
: { kind: "refused", reason: "operation_conflict", detail: "this operation id names other terms" };
|
|
41
41
|
}
|
|
42
42
|
if (current !== undefined && current.view.state !== "handover_blocked") {
|
|
43
|
-
return current.request.targetRuntimeBuildId === request.targetRuntimeBuildId
|
|
43
|
+
return current.request.targetRuntimeBuildId === request.targetRuntimeBuildId &&
|
|
44
|
+
Boolean(current.request.mcpPresenterEnforcement) === Boolean(request.mcpPresenterEnforcement)
|
|
44
45
|
? answer(current)
|
|
45
46
|
: { kind: "refused", reason: "handover_in_progress", detail: current.view.operationId };
|
|
46
47
|
}
|
|
@@ -112,6 +113,7 @@ export class HostIdleHandover {
|
|
|
112
113
|
}
|
|
113
114
|
function sameTerms(a, b) {
|
|
114
115
|
return (a.targetRuntimeBuildId === b.targetRuntimeBuildId &&
|
|
116
|
+
Boolean(a.mcpPresenterEnforcement) === Boolean(b.mcpPresenterEnforcement) &&
|
|
115
117
|
a.ifInstanceId === b.ifInstanceId &&
|
|
116
118
|
a.ifGeneration === b.ifGeneration);
|
|
117
119
|
}
|
|
@@ -28,6 +28,7 @@ export const OBSERVE_REQUEST_FIELD = "observe";
|
|
|
28
28
|
export const ORIGIN_HOST_INSTANCE_FIELD = "origin_host_instance";
|
|
29
29
|
const OBSERVING_COMMANDS = new Set([
|
|
30
30
|
"get_protocol_info",
|
|
31
|
+
"get_snapshot",
|
|
31
32
|
"list_sessions",
|
|
32
33
|
"subscribe_sessions",
|
|
33
34
|
"unsubscribe_sessions",
|
|
@@ -52,5 +52,6 @@ export type HostRequest = {
|
|
|
52
52
|
export declare function runHostRequest(request: HostRequest, options?: {
|
|
53
53
|
readonly keyRoot?: string;
|
|
54
54
|
readonly presenterBootstrap?: Duplex;
|
|
55
|
+
readonly mcpPresenterEnforcement?: boolean;
|
|
55
56
|
}): Promise<HostOutcome>;
|
|
56
57
|
//# sourceMappingURL=host-runner.d.ts.map
|
|
@@ -37,9 +37,9 @@ import { hostRequestKeyRoot } from "./host-request-key-root.js";
|
|
|
37
37
|
import { withRpcKeyRoot } from "./rpc-auth-client.js";
|
|
38
38
|
const PROBE_TIMEOUT_MS = 10_000;
|
|
39
39
|
export async function runHostRequest(request, options = {}) {
|
|
40
|
-
return withRpcKeyRoot(hostRequestKeyRoot(request, options.keyRoot), () => runHostRequestInKeyRoot(request, options.presenterBootstrap));
|
|
40
|
+
return withRpcKeyRoot(hostRequestKeyRoot(request, options.keyRoot), () => runHostRequestInKeyRoot(request, options.presenterBootstrap, options.mcpPresenterEnforcement));
|
|
41
41
|
}
|
|
42
|
-
async function runHostRequestInKeyRoot(request, presenterBootstrap) {
|
|
42
|
+
async function runHostRequestInKeyRoot(request, presenterBootstrap, mcpPresenterEnforcement) {
|
|
43
43
|
if (request.action === "ensure" || request.action === "handoff" || request.action === "stop") {
|
|
44
44
|
const { socket, agentDir } = request.target;
|
|
45
45
|
if ((await endpointKindOfSocket(socket, agentDir)) === "tui") {
|
|
@@ -48,7 +48,7 @@ async function runHostRequestInKeyRoot(request, presenterBootstrap) {
|
|
|
48
48
|
}
|
|
49
49
|
switch (request.action) {
|
|
50
50
|
case "ensure":
|
|
51
|
-
return ensureOutcome(request.target, request.spec, request.policy, presenterBootstrap);
|
|
51
|
+
return ensureOutcome(request.target, request.spec, request.policy, presenterBootstrap, mcpPresenterEnforcement);
|
|
52
52
|
case "status":
|
|
53
53
|
return request.all === true
|
|
54
54
|
? statusAllOutcome(request.target, request.includeWorkers)
|
|
@@ -57,8 +57,8 @@ async function runHostRequestInKeyRoot(request, presenterBootstrap) {
|
|
|
57
57
|
return stopOutcome(request);
|
|
58
58
|
case "handoff":
|
|
59
59
|
return request.handover === undefined
|
|
60
|
-
? handoffOutcome(request.target, request.spec, presenterBootstrap)
|
|
61
|
-
: idleHandoverOutcome(request.target, request.spec, request.handover);
|
|
60
|
+
? handoffOutcome(request.target, request.spec, presenterBootstrap, mcpPresenterEnforcement)
|
|
61
|
+
: idleHandoverOutcome(request.target, request.spec, request.handover, mcpPresenterEnforcement);
|
|
62
62
|
case "shard_path":
|
|
63
63
|
return shardPathOutcome(request.kind, request.owner, request.root);
|
|
64
64
|
case "gc":
|
|
@@ -70,7 +70,7 @@ async function runHostRequestInKeyRoot(request, presenterBootstrap) {
|
|
|
70
70
|
return assertNever(request);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
async function ensureOutcome(target, spec, policy, presenterBootstrap) {
|
|
73
|
+
async function ensureOutcome(target, spec, policy, presenterBootstrap, mcpPresenterEnforcement) {
|
|
74
74
|
const before = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);
|
|
75
75
|
if (policy === "fallback") {
|
|
76
76
|
// `fallback` is the caller saying it can live without a host. The decision is made HERE
|
|
@@ -85,6 +85,7 @@ async function ensureOutcome(target, spec, policy, presenterBootstrap) {
|
|
|
85
85
|
ensured = await ensureHost({
|
|
86
86
|
socket: target.socket,
|
|
87
87
|
presenterBootstrap,
|
|
88
|
+
mcpPresenterEnforcement,
|
|
88
89
|
agentDir: target.agentDir,
|
|
89
90
|
hostArgs: spec.hostArgs,
|
|
90
91
|
env: daemonEnvOverrides(process.env, spec.env),
|
|
@@ -182,11 +183,12 @@ async function stopOutcome(request) {
|
|
|
182
183
|
},
|
|
183
184
|
};
|
|
184
185
|
}
|
|
185
|
-
async function handoffOutcome(target, spec, presenterBootstrap) {
|
|
186
|
+
async function handoffOutcome(target, spec, presenterBootstrap, mcpPresenterEnforcement) {
|
|
186
187
|
const before = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);
|
|
187
188
|
const result = await handoffHost({
|
|
188
189
|
socket: target.socket,
|
|
189
190
|
presenterBootstrap,
|
|
191
|
+
mcpPresenterEnforcement,
|
|
190
192
|
agentDir: target.agentDir,
|
|
191
193
|
hostArgs: spec.hostArgs,
|
|
192
194
|
env: daemonEnvOverrides(process.env, spec.env),
|
|
@@ -83,6 +83,10 @@ export async function startSuccessor(context) {
|
|
|
83
83
|
`${replaced.dev}:${replaced.ino}`,
|
|
84
84
|
...(inheritOwnerPipe ? ["--owner-fd", String(OWNER_WATCH_FD)] : []),
|
|
85
85
|
...(options.presenterBootstrap ? ["--mcp-presenter-fd", "4"] : []),
|
|
86
|
+
// Preserve the running host's policy even when an older caller omits the deployment option.
|
|
87
|
+
...(options.mcpPresenterEnforcement || host.capabilities.includes("mcp_form_presenter_v1")
|
|
88
|
+
? ["--mcp-presenter-enforcement"]
|
|
89
|
+
: []),
|
|
86
90
|
...(options.hostArgs ?? []),
|
|
87
91
|
];
|
|
88
92
|
const launch = (options._test?.launch ?? options.launch)?.(argv) ?? defaultHostLaunch(argv);
|