@code-yeongyu/senpi 2026.10.25-2 → 2026.10.25-4
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 +37 -0
- package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
- package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
- package/dist/bundle/chunks/{chunk-XBUSVKWC.js → chunk-2RB5EL4X.js} +1 -1
- package/dist/bundle/chunks/{chunk-KUE4AQN6.js → chunk-3QCYBOKN.js} +1 -1
- package/dist/bundle/chunks/{chunk-2EFVQWXZ.js → chunk-4MOZXOJX.js} +5 -5
- package/dist/bundle/chunks/{chunk-MXZPAUFB.js → chunk-5JJDR6XI.js} +1 -1
- package/dist/bundle/chunks/chunk-6VWIMHMH.js +2 -0
- package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
- package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
- package/dist/bundle/chunks/{chunk-VNN2V74Y.js → chunk-BZ7726QO.js} +1 -1
- package/dist/bundle/chunks/{chunk-J2U5VDG7.js → chunk-C2MQ4QXN.js} +30 -30
- package/dist/bundle/chunks/{chunk-JRXKLIEQ.js → chunk-DODH3N6U.js} +5 -5
- package/dist/bundle/chunks/{chunk-EHQXLHRB.js → chunk-E6D4RIT7.js} +2 -2
- package/dist/bundle/chunks/chunk-J37GRED2.js +5 -0
- package/dist/bundle/chunks/chunk-JMOUU75H.js +2 -0
- package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
- package/dist/bundle/chunks/chunk-KKD4CYCI.js +3 -0
- package/dist/bundle/chunks/{chunk-YN7ELQ43.js → chunk-KNVNKDER.js} +1 -1
- package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
- package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
- package/dist/bundle/chunks/{chunk-G2UETS2R.js → chunk-LOIF6PHI.js} +4 -4
- package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
- package/dist/bundle/chunks/chunk-N7IN46Q5.js +27 -0
- package/dist/bundle/chunks/{chunk-LYGAV6KU.js → chunk-PT6JJUN2.js} +1 -1
- package/dist/bundle/chunks/{chunk-WFXUCVIR.js → chunk-SBGIXMX3.js} +2 -5
- package/dist/bundle/chunks/{chunk-W2BKZBYJ.js → chunk-STMXIBXF.js} +2 -2
- package/dist/bundle/chunks/chunk-TBU6VYHN.js +13 -0
- package/dist/bundle/chunks/{chunk-RXKNOEQQ.js → chunk-V2UYXTRR.js} +1 -1
- package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
- package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
- package/dist/bundle/chunks/chunk-XMJKZK5H.js +2 -0
- package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
- package/dist/bundle/chunks/cli-main-YYH3IBC6.js +3 -0
- package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
- package/dist/bundle/chunks/dispatch-MXG5YWLV.js +9 -0
- package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
- package/dist/bundle/chunks/host-command-TDLLHXVI.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-3VHHIGDL.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +8 -6
- package/dist/bundle/chunks/host-session-command-56XF6ACS.js +20 -0
- package/dist/bundle/chunks/{host-socket-request-AS2OCRLP.js → host-socket-request-QL2WEZTN.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +2 -0
- package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-VTTY5MFT.js → multi-session-host-Z2CW4SDM.js} +12 -9
- package/dist/bundle/chunks/{package-manager-cli-DZFOC3MF.js → package-manager-cli-N55U6PEO.js} +1 -1
- package/dist/bundle/chunks/rotation-stream-CBGCBYHV.js +2 -0
- package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-B5NQ4ZVH.js → rpc-monitor-source-AE3C6EQV.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-SLISTG4V.js → schedule-command-VS7CZC2J.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +3 -0
- package/dist/bundle/chunks/session-picker-E4KUJDLO.js +2 -0
- package/dist/bundle/chunks/session-worker.js +292 -278
- package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
- package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
- package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
- package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +8 -6
- 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/args.d.ts +2 -0
- package/dist/cli/args.js +9 -0
- package/dist/cli/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +7 -1
- package/dist/cli/host-command.js +3 -1
- package/dist/core/agent-session.js +22 -7
- package/dist/core/extensions/builtin/goal/index.js +1 -0
- package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/connection.js +46 -1
- package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
- package/dist/core/extensions/builtin/mcp/elicitation.js +54 -2
- package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
- package/dist/core/extensions/builtin/mcp/health.js +5 -0
- package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
- package/dist/core/extensions/builtin/mcp/index.js +2 -0
- package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
- package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
- package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
- package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
- package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
- package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
- package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
- package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
- package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
- package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
- package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/resources.js +19 -4
- package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/service.js +79 -4
- package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
- package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
- package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
- package/dist/core/extensions/runner.js +3 -2
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-ledger.d.ts +1 -0
- package/dist/core/lifecycle-ledger.js +7 -2
- package/dist/core/lifecycle-record.js +3 -0
- package/dist/core/lifecycle-session-binding.d.ts +11 -1
- package/dist/core/lifecycle-session-binding.js +29 -2
- package/dist/core/lifecycle-stop.d.ts +41 -0
- package/dist/core/lifecycle-stop.js +92 -0
- package/dist/core/mcp-form-types.d.ts +89 -0
- package/dist/core/mcp-form-types.js +23 -0
- package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
- package/dist/core/mcp-mrtr-presentations.js +17 -0
- package/dist/core/session-control-actions.js +0 -2
- package/dist/core/session-lifecycle-types.d.ts +15 -0
- package/dist/core/session-lifecycle.d.ts +17 -1
- package/dist/core/session-lifecycle.js +40 -2
- package/dist/main.js +5 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.js +34 -8
- package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
- package/dist/modes/interactive/mcp-local-presenter.js +109 -0
- package/dist/modes/interactive/session-control-attach.d.ts +20 -0
- package/dist/modes/interactive/session-control-attach.js +13 -0
- package/dist/modes/interactive/session-control-commands.js +16 -6
- package/dist/modes/interactive/session-control-registry.d.ts +2 -1
- package/dist/modes/interactive/session-control-registry.js +3 -2
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +4 -2
- package/dist/modes/interactive/session-control-rpc-commands.js +114 -4
- package/dist/modes/interactive/session-control-server.js +9 -3
- package/dist/modes/rpc/connection-handler.d.ts +1 -0
- package/dist/modes/rpc/connection-handler.js +13 -41
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/get-entries-command.d.ts +13 -0
- package/dist/modes/rpc/get-entries-command.js +61 -0
- package/dist/modes/rpc/get-entries-page.d.ts +22 -0
- package/dist/modes/rpc/get-entries-page.js +41 -2
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +6 -2
- package/dist/modes/rpc/host-ensure-start.js +13 -2
- 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-wire.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.js +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
- package/dist/modes/rpc/host-observe-request.d.ts +2 -1
- package/dist/modes/rpc/host-observe-request.js +4 -1
- package/dist/modes/rpc/host-runner.d.ts +2 -0
- package/dist/modes/rpc/host-runner.js +8 -6
- package/dist/modes/rpc/host-successor.js +11 -1
- package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
- package/dist/modes/rpc/mcp-form-dialog.js +159 -0
- package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
- package/dist/modes/rpc/mcp-form-worker.js +167 -0
- package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
- package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
- package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
- package/dist/modes/rpc/mcp-presenter-control.js +330 -0
- package/dist/modes/rpc/multi-session-host.d.ts +3 -0
- package/dist/modes/rpc/multi-session-host.js +36 -1
- package/dist/modes/rpc/rpc-read-response-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +8 -1
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-event-writer.d.ts +14 -0
- package/dist/modes/rpc/session-event-writer.js +128 -1
- package/dist/modes/rpc/session-router-routing.js +1 -0
- package/dist/modes/rpc/session-worker-client.d.ts +3 -0
- package/dist/modes/rpc/session-worker-client.js +89 -0
- package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
- package/dist/modes/rpc/session-worker.js +9 -0
- package/docs/extensions.md +5 -0
- package/docs/mcp.md +20 -3
- package/docs/rpc.md +151 -8
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-3LNSLJ7N.js +0 -2
- package/dist/bundle/chunks/chunk-B6TPQDBW.js +0 -5
- package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
- package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
- package/dist/bundle/chunks/chunk-KJZAY5YH.js +0 -27
- package/dist/bundle/chunks/chunk-MSQCTH6V.js +0 -2
- package/dist/bundle/chunks/chunk-OI4NMCKP.js +0 -3
- package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
- package/dist/bundle/chunks/cli-main-SWKFMTZQ.js +0 -3
- package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
- package/dist/bundle/chunks/host-command-BC6UWP33.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-TN5NEGNF.js +0 -3
- package/dist/bundle/chunks/host-session-command-MGVBLBRV.js +0 -20
- package/dist/bundle/chunks/interactive-mode-SCSROHD5.js +0 -2
- package/dist/bundle/chunks/rotation-stream-U7YQMC7H.js +0 -2
- package/dist/bundle/chunks/rpc-mode-TOXYD4XA.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-HFFMBALG.js +0 -3
- package/dist/bundle/chunks/session-picker-465FVXQG.js +0 -2
|
@@ -42,13 +42,13 @@ import { ClientAdmissions } from "./client-admissions.js";
|
|
|
42
42
|
import { handleClientInput } from "./client-input-handler.js";
|
|
43
43
|
import { ConnectionQuestionBridge, degradeQuestion, sessionQuestionBridges } from "./connection-question-bridge.js";
|
|
44
44
|
import { CONNECTION_ROLE_FIXED, ConnectionRoles, roleConnection } from "./connection-roles.js";
|
|
45
|
-
import { AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EVAL_CELLS_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, INTERACTIVE_USER_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, QUESTION_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, TOOL_BARRIER_CAPABILITY, TASK_PARENT_CAPABILITY, } from "./custom-capability.js";
|
|
45
|
+
import { AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EVAL_CELLS_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, ENTRIES_TAIL_CAPABILITY, INTERACTIVE_USER_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, QUESTION_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, TOOL_BARRIER_CAPABILITY, TASK_PARENT_CAPABILITY, } from "./custom-capability.js";
|
|
46
46
|
import { runAbort, runGetEffectStatus, runQueuedInputChange, runSetModel, runSetThinkingLevel, } from "./effect-commands.js";
|
|
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
50
|
import { settleExtensionUiResponse } from "./extension-ui-response.js";
|
|
51
|
-
import {
|
|
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";
|
|
54
54
|
import { createRpcLoginPromptCallbacks } from "./login-prompts.js";
|
|
@@ -57,7 +57,7 @@ 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 {
|
|
60
|
+
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
61
|
import { runPauseForRun, runResumeForRun } from "./run-hold-commands.js";
|
|
62
62
|
import { availableModelsData, interruptRunningTurn } from "./session-control-actions-data.js";
|
|
63
63
|
import { SessionExtensionUiRequests, sessionDialogRequests } from "./session-extension-ui-requests.js";
|
|
@@ -599,6 +599,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
599
599
|
subscribeExtensionEvents();
|
|
600
600
|
};
|
|
601
601
|
const rebindSession = async (deferRefresh = false) => {
|
|
602
|
+
unbindMcpForms?.();
|
|
603
|
+
unbindMcpForms = undefined;
|
|
602
604
|
unsubscribeSecurityNotices?.();
|
|
603
605
|
unsubscribeLoadedSurfaces?.();
|
|
604
606
|
unsubscribeExtensionEvents?.();
|
|
@@ -673,8 +675,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
673
675
|
// reconstruct the session it is bound to. Installing here also drops the
|
|
674
676
|
// previous session's subscription immediately instead of after the bind.
|
|
675
677
|
installSessionSubscriptions();
|
|
678
|
+
const extensionUi = createExtensionUIContext();
|
|
679
|
+
unbindMcpForms = await options.bindMcpForms?.(session, extensionUi);
|
|
676
680
|
const refresh = refreshLoadedSurfacesAfter(() => session.bindExtensions({
|
|
677
|
-
uiContext:
|
|
681
|
+
uiContext: extensionUi,
|
|
678
682
|
mode: "rpc",
|
|
679
683
|
commandContextActions: {
|
|
680
684
|
waitForIdle: () => session.agent.waitForIdle(),
|
|
@@ -867,6 +871,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
867
871
|
TOOL_BARRIER_CAPABILITY,
|
|
868
872
|
USER_ACTIVITY_CAPABILITY,
|
|
869
873
|
RESUME_SESSION_CAPABILITY,
|
|
874
|
+
ENTRIES_TAIL_CAPABILITY,
|
|
870
875
|
...(options.retryFallbackCommand ? [RETRY_FALLBACK_COMMAND_CAPABILITY] : []),
|
|
871
876
|
...(options.toolRuntime ? toolRuntimeCapabilities(options.toolRuntime) : []),
|
|
872
877
|
// Client flags cannot advertise a host guard this classic binding does not run, nor a tool runtime.
|
|
@@ -1453,43 +1458,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1453
1458
|
const messages = session.getUserMessagesForForking();
|
|
1454
1459
|
return success(id, "get_fork_messages", { messages });
|
|
1455
1460
|
}
|
|
1456
|
-
case "get_entries":
|
|
1457
|
-
|
|
1458
|
-
let entries = sessionManager.getEntries();
|
|
1459
|
-
if (command.since !== undefined) {
|
|
1460
|
-
const sinceIndex = entries.findIndex((e) => e.id === command.since);
|
|
1461
|
-
if (sinceIndex === -1) {
|
|
1462
|
-
return error(id, "get_entries", `Entry not found: ${command.since}`, RPC_ERROR_ENTRY_NOT_FOUND);
|
|
1463
|
-
}
|
|
1464
|
-
entries = entries.slice(sinceIndex + 1);
|
|
1465
|
-
}
|
|
1466
|
-
const bounds = entryPageBounds(command);
|
|
1467
|
-
if (!bounds.ok)
|
|
1468
|
-
return error(id, "get_entries", bounds.message, RPC_ERROR_INVALID_ARGUMENT);
|
|
1469
|
-
const page = pageEntries(entries, describeUsageLimitEntry, bounds);
|
|
1470
|
-
// A client walking pages asks again at once; holding the view until the last page spares each page a
|
|
1471
|
-
// full re-read of the file. The hold expires on its own, so a walk the client abandons on an idle
|
|
1472
|
-
// session does not pin the whole history; any other answer lets the view go now.
|
|
1473
|
-
if (bounds.paged && page.hasMore)
|
|
1474
|
-
holdEntryWalk(session, () => session.releaseFullHistoryView());
|
|
1475
|
-
else {
|
|
1476
|
-
endEntryWalk(session);
|
|
1477
|
-
session.releaseFullHistoryView();
|
|
1478
|
-
}
|
|
1479
|
-
const budget = currentResponseByteBudget();
|
|
1480
|
-
if (page.firstEntryBytes > budget) {
|
|
1481
|
-
return error(id, "get_entries", `Entry ${entries[0]?.id ?? ""} alone is larger than one answer can carry (${budget} bytes), so it cannot be sent over RPC.`, RPC_ERROR_RESPONSE_TOO_LARGE);
|
|
1482
|
-
}
|
|
1483
|
-
if (!bounds.paged && page.hasMore) {
|
|
1484
|
-
return error(id, "get_entries", `The session history is larger than one answer can carry (${budget} bytes). Read it in pages: pass limit or maxBytes, then since: nextSince until hasMore is false.`, RPC_ERROR_RESPONSE_TOO_LARGE);
|
|
1485
|
-
}
|
|
1486
|
-
return success(id, "get_entries", {
|
|
1487
|
-
entries: page.entries,
|
|
1488
|
-
leafId: sessionManager.getLeafId(),
|
|
1489
|
-
...(bounds.paged ? { hasMore: page.hasMore } : {}),
|
|
1490
|
-
...(page.nextSince === undefined ? {} : { nextSince: page.nextSince }),
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1461
|
+
case "get_entries":
|
|
1462
|
+
return answerGetEntries(session, command);
|
|
1493
1463
|
case "get_tree": {
|
|
1494
1464
|
const sessionManager = session.sessionManager;
|
|
1495
1465
|
const tree = sessionManager.getTree();
|
|
@@ -1752,6 +1722,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1752
1722
|
? subscriptionLimits.subscribe((limits) => eventOutput.enqueueEvent({ type: "subscription_limits", ...limits }))
|
|
1753
1723
|
: () => { };
|
|
1754
1724
|
const dispose = async (disposeOptions = {}) => {
|
|
1725
|
+
unbindMcpForms?.();
|
|
1755
1726
|
grantPresentationAbort.abort();
|
|
1756
1727
|
detachGrantPresentation?.();
|
|
1757
1728
|
unsubscribeSecurityNotices?.();
|
|
@@ -1777,6 +1748,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1777
1748
|
};
|
|
1778
1749
|
// Perform the initial bind synchronously-scheduled so the handler is ready
|
|
1779
1750
|
// as soon as the caller awaits `ready`.
|
|
1751
|
+
let unbindMcpForms;
|
|
1780
1752
|
const ready = rebindSession();
|
|
1781
1753
|
return {
|
|
1782
1754
|
ready,
|
|
@@ -60,6 +60,12 @@ export declare const DETACH_CONNECTION_CAPABILITY = "detach_connection";
|
|
|
60
60
|
* client's own `set_client_info` and fixed at that first declaration (see `connection-roles.ts`).
|
|
61
61
|
*/
|
|
62
62
|
export declare const INTERACTIVE_USER_CAPABILITY = "interactive_user";
|
|
63
|
+
/** `get_state` and `get_entries` accept `observe: true` and then never count as an attachment (senpi#4267). */
|
|
64
|
+
export declare const OBSERVE_READS_CAPABILITY = "observe_reads";
|
|
65
|
+
/** `get_entries` accepts `before` and `tail` and answers `next_before` (senpi#4267). */
|
|
66
|
+
export declare const ENTRIES_TAIL_CAPABILITY = "entries_tail";
|
|
67
|
+
/** A terminal control endpoint serves the host's observe-and-drive command set (senpi#4267). */
|
|
68
|
+
export declare const TUI_OBSERVER_CAPABILITY = "tui_observer";
|
|
63
69
|
/** The connection owns this session as a task child; only it may pause or resume the session for its run (senpi#3811). */
|
|
64
70
|
export declare const TASK_PARENT_CAPABILITY = "task_parent";
|
|
65
71
|
/**
|
|
@@ -60,6 +60,12 @@ export const DETACH_CONNECTION_CAPABILITY = "detach_connection";
|
|
|
60
60
|
* client's own `set_client_info` and fixed at that first declaration (see `connection-roles.ts`).
|
|
61
61
|
*/
|
|
62
62
|
export const INTERACTIVE_USER_CAPABILITY = "interactive_user";
|
|
63
|
+
/** `get_state` and `get_entries` accept `observe: true` and then never count as an attachment (senpi#4267). */
|
|
64
|
+
export const OBSERVE_READS_CAPABILITY = "observe_reads";
|
|
65
|
+
/** `get_entries` accepts `before` and `tail` and answers `next_before` (senpi#4267). */
|
|
66
|
+
export const ENTRIES_TAIL_CAPABILITY = "entries_tail";
|
|
67
|
+
/** A terminal control endpoint serves the host's observe-and-drive command set (senpi#4267). */
|
|
68
|
+
export const TUI_OBSERVER_CAPABILITY = "tui_observer";
|
|
63
69
|
/** The connection owns this session as a task child; only it may pause or resume the session for its run (senpi#3811). */
|
|
64
70
|
export const TASK_PARENT_CAPABILITY = "task_parent";
|
|
65
71
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one `get_entries` answer: a host connection and a terminal control endpoint both read history through it, so
|
|
3
|
+
* the range rules (`since`, `before`), the forward (`limit`) and backward (`tail`, senpi#4267) pages and the byte
|
|
4
|
+
* budget (senpi#3534) are the same on every surface.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentSession } from "../../core/agent-session.ts";
|
|
7
|
+
import { type RpcCommand, type RpcResponse } from "./rpc-types.ts";
|
|
8
|
+
type GetEntries = Extract<RpcCommand, {
|
|
9
|
+
type: "get_entries";
|
|
10
|
+
}>;
|
|
11
|
+
export declare function answerGetEntries(session: AgentSession, command: GetEntries): RpcResponse;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=get-entries-command.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describeUsageLimitEntry } from "../../core/retry-fallback/usage-limit.js";
|
|
2
|
+
import { currentResponseByteBudget, endEntryWalk, entryPageBounds, holdEntryWalk, pageEntries, pageEntriesFromEnd, } from "./get-entries-page.js";
|
|
3
|
+
import { RPC_ERROR_ENTRY_NOT_FOUND, RPC_ERROR_INVALID_ARGUMENT, RPC_ERROR_RESPONSE_TOO_LARGE, } from "./rpc-types.js";
|
|
4
|
+
function refuse(id, message, errorCode) {
|
|
5
|
+
return { id, type: "response", command: "get_entries", success: false, error: message, errorCode };
|
|
6
|
+
}
|
|
7
|
+
export function answerGetEntries(session, command) {
|
|
8
|
+
const { id } = command;
|
|
9
|
+
const sessionManager = session.sessionManager;
|
|
10
|
+
let entries = sessionManager.getEntries();
|
|
11
|
+
if (command.before !== undefined) {
|
|
12
|
+
const beforeIndex = entries.findIndex((e) => e.id === command.before);
|
|
13
|
+
if (beforeIndex === -1)
|
|
14
|
+
return refuse(id, `Entry not found: ${command.before}`, RPC_ERROR_ENTRY_NOT_FOUND);
|
|
15
|
+
entries = entries.slice(0, beforeIndex);
|
|
16
|
+
}
|
|
17
|
+
if (command.since !== undefined) {
|
|
18
|
+
const sinceIndex = sessionManager.getEntries().findIndex((e) => e.id === command.since);
|
|
19
|
+
if (sinceIndex === -1)
|
|
20
|
+
return refuse(id, `Entry not found: ${command.since}`, RPC_ERROR_ENTRY_NOT_FOUND);
|
|
21
|
+
entries = entries.slice(sinceIndex + 1);
|
|
22
|
+
}
|
|
23
|
+
const bounds = entryPageBounds(command);
|
|
24
|
+
if (!bounds.ok)
|
|
25
|
+
return refuse(id, bounds.message, RPC_ERROR_INVALID_ARGUMENT);
|
|
26
|
+
const backward = bounds.tail !== undefined;
|
|
27
|
+
const page = backward
|
|
28
|
+
? pageEntriesFromEnd(entries, describeUsageLimitEntry, bounds)
|
|
29
|
+
: pageEntries(entries, describeUsageLimitEntry, bounds);
|
|
30
|
+
// A client walking pages asks again at once; holding the view until the last page spares each page a
|
|
31
|
+
// full re-read of the file. The hold expires on its own, so a walk the client abandons on an idle
|
|
32
|
+
// session does not pin the whole history; any other answer lets the view go now.
|
|
33
|
+
if (bounds.paged && page.hasMore)
|
|
34
|
+
holdEntryWalk(session, () => session.releaseFullHistoryView());
|
|
35
|
+
else {
|
|
36
|
+
endEntryWalk(session);
|
|
37
|
+
session.releaseFullHistoryView();
|
|
38
|
+
}
|
|
39
|
+
const budget = currentResponseByteBudget();
|
|
40
|
+
if (page.firstEntryBytes > budget) {
|
|
41
|
+
const entry = backward ? entries.at(-1) : entries[0];
|
|
42
|
+
return refuse(id, `Entry ${entry?.id ?? ""} alone is larger than one answer can carry (${budget} bytes), so it cannot be sent over RPC.`, RPC_ERROR_RESPONSE_TOO_LARGE);
|
|
43
|
+
}
|
|
44
|
+
if (!bounds.paged && page.hasMore) {
|
|
45
|
+
return refuse(id, `The session history is larger than one answer can carry (${budget} bytes). Read it in pages: pass limit or maxBytes, then since: nextSince until hasMore is false.`, RPC_ERROR_RESPONSE_TOO_LARGE);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
type: "response",
|
|
50
|
+
command: "get_entries",
|
|
51
|
+
success: true,
|
|
52
|
+
data: {
|
|
53
|
+
entries: page.entries,
|
|
54
|
+
leafId: sessionManager.getLeafId(),
|
|
55
|
+
...(bounds.paged ? { hasMore: page.hasMore } : {}),
|
|
56
|
+
...("nextSince" in page && page.nextSince !== undefined ? { nextSince: page.nextSince } : {}),
|
|
57
|
+
...("nextBefore" in page && page.nextBefore !== undefined ? { next_before: page.nextBefore } : {}),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=get-entries-command.js.map
|
|
@@ -14,6 +14,8 @@ export declare function currentResponseByteBudget(): number;
|
|
|
14
14
|
export declare function setResponseByteBudgetForTesting(bytes: number): () => void;
|
|
15
15
|
export interface EntryPageRequest {
|
|
16
16
|
readonly limit?: number;
|
|
17
|
+
/** The newest `tail` entries of the range, read backwards (senpi#4267); never combined with `limit`. */
|
|
18
|
+
readonly tail?: number;
|
|
17
19
|
readonly maxBytes?: number;
|
|
18
20
|
}
|
|
19
21
|
export interface EntryPage<T> {
|
|
@@ -27,6 +29,7 @@ export type EntryPageBounds = {
|
|
|
27
29
|
readonly ok: true;
|
|
28
30
|
readonly paged: boolean;
|
|
29
31
|
readonly limit?: number;
|
|
32
|
+
readonly tail?: number;
|
|
30
33
|
readonly maxBytes: number;
|
|
31
34
|
} | {
|
|
32
35
|
readonly ok: false;
|
|
@@ -44,6 +47,25 @@ export declare function pageEntries<S extends {
|
|
|
44
47
|
readonly limit?: number;
|
|
45
48
|
readonly maxBytes: number;
|
|
46
49
|
}): EntryPage<T>;
|
|
50
|
+
export interface TailEntryPage<T> {
|
|
51
|
+
/** In append order: the oldest of the page first. */
|
|
52
|
+
readonly entries: T[];
|
|
53
|
+
readonly hasMore: boolean;
|
|
54
|
+
/** The oldest entry id the page returned, while older entries remain: pass it as the next `before`. */
|
|
55
|
+
readonly nextBefore?: string;
|
|
56
|
+
/** Serialized size of the page's newest entry (0 for an empty page). */
|
|
57
|
+
readonly firstEntryBytes: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The newest entries first, until the count or the byte size would pass the bounds, answered in append order. The
|
|
61
|
+
* newest entry is always taken, so a backward walk always moves even when one entry alone is larger than `maxBytes`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function pageEntriesFromEnd<S extends {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
}, T>(entries: readonly S[], project: (entry: S) => T, bounds: {
|
|
66
|
+
readonly tail?: number;
|
|
67
|
+
readonly maxBytes: number;
|
|
68
|
+
}): TailEntryPage<T>;
|
|
47
69
|
/**
|
|
48
70
|
* Keeps a page walk's full-history view for the next page, and lets it go after `ENTRY_WALK_HOLD_MS` without
|
|
49
71
|
* one: a client that stops mid-walk must not pin the whole history of a session that is already idle.
|
|
@@ -22,17 +22,22 @@ export function setResponseByteBudgetForTesting(bytes) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
export function entryPageBounds(request) {
|
|
25
|
-
const { limit, maxBytes } = request;
|
|
25
|
+
const { limit, tail, maxBytes } = request;
|
|
26
26
|
if (limit !== undefined && !(Number.isInteger(limit) && limit > 0))
|
|
27
27
|
return { ok: false, message: "limit must be a positive integer" };
|
|
28
|
+
if (tail !== undefined && !(Number.isInteger(tail) && tail > 0))
|
|
29
|
+
return { ok: false, message: "tail must be a positive integer" };
|
|
30
|
+
if (limit !== undefined && tail !== undefined)
|
|
31
|
+
return { ok: false, message: "limit and tail cannot be combined: limit pages forward, tail backward" };
|
|
28
32
|
if (maxBytes !== undefined && !(Number.isInteger(maxBytes) && maxBytes > 0))
|
|
29
33
|
return { ok: false, message: "maxBytes must be a positive integer" };
|
|
30
|
-
if (limit === undefined && maxBytes === undefined)
|
|
34
|
+
if (limit === undefined && tail === undefined && maxBytes === undefined)
|
|
31
35
|
return { ok: true, paged: false, maxBytes: responseByteBudget };
|
|
32
36
|
return {
|
|
33
37
|
ok: true,
|
|
34
38
|
paged: true,
|
|
35
39
|
...(limit === undefined ? {} : { limit }),
|
|
40
|
+
...(tail === undefined ? {} : { tail }),
|
|
36
41
|
maxBytes: Math.min(maxBytes ?? DEFAULT_ENTRY_PAGE_BYTES, responseByteBudget),
|
|
37
42
|
};
|
|
38
43
|
}
|
|
@@ -68,6 +73,40 @@ export function pageEntries(entries, project, bounds) {
|
|
|
68
73
|
firstEntryBytes,
|
|
69
74
|
};
|
|
70
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The newest entries first, until the count or the byte size would pass the bounds, answered in append order. The
|
|
78
|
+
* newest entry is always taken, so a backward walk always moves even when one entry alone is larger than `maxBytes`.
|
|
79
|
+
*/
|
|
80
|
+
export function pageEntriesFromEnd(entries, project, bounds) {
|
|
81
|
+
const page = [];
|
|
82
|
+
let bytes = 0;
|
|
83
|
+
let firstEntryBytes = 0;
|
|
84
|
+
let index = entries.length - 1;
|
|
85
|
+
for (; index >= 0; index--) {
|
|
86
|
+
if (bounds.tail !== undefined && page.length >= bounds.tail)
|
|
87
|
+
break;
|
|
88
|
+
const entry = entries[index];
|
|
89
|
+
if (entry === undefined)
|
|
90
|
+
break;
|
|
91
|
+
const projected = project(entry);
|
|
92
|
+
const size = Buffer.byteLength(JSON.stringify(projected)) + 1;
|
|
93
|
+
if (page.length > 0 && bytes + size > bounds.maxBytes)
|
|
94
|
+
break;
|
|
95
|
+
if (page.length === 0)
|
|
96
|
+
firstEntryBytes = size;
|
|
97
|
+
page.push(projected);
|
|
98
|
+
bytes += size;
|
|
99
|
+
}
|
|
100
|
+
page.reverse();
|
|
101
|
+
const oldest = entries[index + 1];
|
|
102
|
+
const hasMore = index >= 0;
|
|
103
|
+
return {
|
|
104
|
+
entries: page,
|
|
105
|
+
hasMore,
|
|
106
|
+
...(hasMore && oldest !== undefined ? { nextBefore: oldest.id } : {}),
|
|
107
|
+
firstEntryBytes,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
71
110
|
const heldWalks = new WeakMap();
|
|
72
111
|
/**
|
|
73
112
|
* Keeps a page walk's full-history view for the next page, and lets it go after `ENTRY_WALK_HOLD_MS` without
|
|
@@ -14,5 +14,6 @@ export declare function multiSessionHostCapabilities(options: {
|
|
|
14
14
|
readonly warm: boolean;
|
|
15
15
|
readonly toolRuntime: ToolRuntimeKind;
|
|
16
16
|
readonly negotiated: readonly string[];
|
|
17
|
+
readonly mcpPresenters?: boolean;
|
|
17
18
|
}): string[];
|
|
18
19
|
//# sourceMappingURL=host-capabilities.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isHostOwnedToolRuntimeCapability, toolRuntimeCapabilities, } from "../../cli/tool-runtime.js";
|
|
2
2
|
import { ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY, AUTO_PERMISSION_PRESET_CAPABILITY, } from "../../core/extensions/builtin/permission-system/config.js";
|
|
3
3
|
import { DURABLE_CLIENT_MESSAGE_ID_CAPABILITY } from "./client-admission-record.js";
|
|
4
|
-
import { ATTENDED_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DETACH_CONNECTION_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, EVAL_CELLS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, MOVED_PATH_GUARD_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_HELD_CAPABILITY, SESSION_KIND_CAPABILITY, SESSION_RELOCATION_CAPABILITY, SESSION_STATUS_CAPABILITY, TOOL_BARRIER_CAPABILITY, WARM_CAPABILITY, } from "./custom-capability.js";
|
|
4
|
+
import { ATTENDED_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DETACH_CONNECTION_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, ENTRIES_TAIL_CAPABILITY, EVAL_CELLS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, MOVED_PATH_GUARD_CAPABILITY, OBSERVE_READS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_HELD_CAPABILITY, SESSION_KIND_CAPABILITY, SESSION_RELOCATION_CAPABILITY, SESSION_STATUS_CAPABILITY, TOOL_BARRIER_CAPABILITY, WARM_CAPABILITY, } from "./custom-capability.js";
|
|
5
5
|
import { EFFECT_LEDGER_CAPABILITY } from "./effect-record.js";
|
|
6
6
|
import { STEER_WHEN_IDLE_CAPABILITY, USER_ACTIVITY_CAPABILITY } from "./rpc-types.js";
|
|
7
7
|
import { MOVE_SESSION_CAPABILITY, MOVE_SESSION_REVOKE_CAPABILITY } from "./session-move-wire.js";
|
|
@@ -20,6 +20,7 @@ export function multiSessionHostCapabilities(options) {
|
|
|
20
20
|
return [
|
|
21
21
|
...new Set([
|
|
22
22
|
"multi_session",
|
|
23
|
+
...(options.mcpPresenters ? ["mcp_form_presenter_v1"] : []),
|
|
23
24
|
"session_lifecycle",
|
|
24
25
|
"expected_host_instance",
|
|
25
26
|
"attach_session",
|
|
@@ -43,6 +44,9 @@ export function multiSessionHostCapabilities(options) {
|
|
|
43
44
|
SESSION_HELD_CAPABILITY,
|
|
44
45
|
RESUME_SESSION_CAPABILITY,
|
|
45
46
|
ATTENDED_CAPABILITY,
|
|
47
|
+
// `get_state`/`get_entries` marked `observe` never attach, and history reads page backward too (senpi#4267).
|
|
48
|
+
OBSERVE_READS_CAPABILITY,
|
|
49
|
+
ENTRIES_TAIL_CAPABILITY,
|
|
46
50
|
// Host capabilities, not client opt-ins: only a multi-session host owns the
|
|
47
51
|
// attachment refcount `open_session.retain_on_disconnect` detaches from, the
|
|
48
52
|
// per-session launch profile `context`/`auto_title` travel on, and the session
|
|
@@ -80,7 +84,7 @@ export function multiSessionHostCapabilities(options) {
|
|
|
80
84
|
...(options.warm ? [WARM_CAPABILITY] : []),
|
|
81
85
|
...toolRuntimeCapabilities(options.toolRuntime),
|
|
82
86
|
// The host alone says where tool work runs; a negotiated copy would make two answers.
|
|
83
|
-
...options.negotiated.filter((capability) => !isHostOwnedToolRuntimeCapability(capability)),
|
|
87
|
+
...options.negotiated.filter((capability) => capability !== "mcp_form_presenter_v1" && !isHostOwnedToolRuntimeCapability(capability)),
|
|
84
88
|
]),
|
|
85
89
|
];
|
|
86
90
|
}
|
|
@@ -17,6 +17,7 @@ import { pollProtocolInfo } from "./host-readiness.js";
|
|
|
17
17
|
import { initialHostEnvironment } from "./host-spawn-environment.js";
|
|
18
18
|
import { hostLaunchProfile } from "./protocol-identity.js";
|
|
19
19
|
import { createSocketSecret, socketSecretPath } from "./socket-transport.js";
|
|
20
|
+
import { relayPresenterBootstrap } from "./mcp-presenter-bootstrap.js";
|
|
20
21
|
export const DEFAULT_READINESS_TIMEOUT_MS = 10_000;
|
|
21
22
|
export { DEFAULT_STOP_TIMEOUT_MS };
|
|
22
23
|
/**
|
|
@@ -55,11 +56,13 @@ export async function startHost(paths, socket, options, generation = 0) {
|
|
|
55
56
|
let child;
|
|
56
57
|
let exitedEarly;
|
|
57
58
|
let childExit;
|
|
59
|
+
let presenterEnrollment;
|
|
58
60
|
try {
|
|
59
61
|
const supervisorArgs = [
|
|
60
62
|
"--socket",
|
|
61
63
|
socket,
|
|
62
64
|
...(options.owner ? ["--owner-fd", String(OWNER_WATCH_FD)] : []),
|
|
65
|
+
...(options.presenterBootstrap ? ["--mcp-presenter-fd", "4"] : []),
|
|
63
66
|
...(options.hostArgs ?? []),
|
|
64
67
|
];
|
|
65
68
|
const launch = testOptions?.spawn ?? testOptions?.launch?.(supervisorArgs) ?? defaultHostLaunch(supervisorArgs);
|
|
@@ -73,8 +76,14 @@ export async function startHost(paths, socket, options, generation = 0) {
|
|
|
73
76
|
instanceId,
|
|
74
77
|
generation,
|
|
75
78
|
}),
|
|
76
|
-
stdio: options.
|
|
79
|
+
stdio: options.presenterBootstrap
|
|
80
|
+
? ["ignore", "ignore", stderr.fd, options.owner ? "pipe" : "ignore", "pipe"]
|
|
81
|
+
: options.owner
|
|
82
|
+
? ["ignore", "ignore", stderr.fd, "pipe"]
|
|
83
|
+
: ["ignore", "ignore", stderr.fd],
|
|
77
84
|
});
|
|
85
|
+
if (options.presenterBootstrap)
|
|
86
|
+
presenterEnrollment = relayPresenterBootstrap(options.presenterBootstrap, child);
|
|
78
87
|
childExit = new Promise((resolveExit) => {
|
|
79
88
|
child.once("exit", (code, signal) => {
|
|
80
89
|
exitedEarly = { code, signal };
|
|
@@ -140,8 +149,10 @@ export async function startHost(paths, socket, options, generation = 0) {
|
|
|
140
149
|
}
|
|
141
150
|
const readinessTimeoutMs = testOptions?.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS;
|
|
142
151
|
const result = await pollProtocolInfo(socket, readinessTimeoutMs, isCompatible, childExit);
|
|
143
|
-
if (result.ready)
|
|
152
|
+
if (result.ready) {
|
|
153
|
+
await presenterEnrollment;
|
|
144
154
|
return { pid: pidFile.pid, socket, reused: false, release: result.hold.release };
|
|
155
|
+
}
|
|
145
156
|
await testOptions?.beforeReadinessTeardown?.();
|
|
146
157
|
// Teardown runs for the diagnostic's sake, so it must never replace it: a stop failure here
|
|
147
158
|
// (unreadable identity, a host that outlives SIGKILL) would otherwise propagate instead of the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** The options and result of `ensureHost`, kept apart so the ensure's helpers can name them without a cycle. */
|
|
2
2
|
import type { HostLifecyclePolicyInput } from "./host-lifecycle.ts";
|
|
3
|
+
import type { Duplex } from "node:stream";
|
|
3
4
|
/**
|
|
4
5
|
* What an ensure may do to a host that is already running.
|
|
5
6
|
*
|
|
@@ -10,6 +11,7 @@ import type { HostLifecyclePolicyInput } from "./host-lifecycle.ts";
|
|
|
10
11
|
*/
|
|
11
12
|
export type HostUpgradePolicy = "never" | "if-engine-differs";
|
|
12
13
|
export interface EnsureHostOptions {
|
|
14
|
+
readonly presenterBootstrap?: Duplex;
|
|
13
15
|
readonly socket: string;
|
|
14
16
|
readonly agentDir?: string;
|
|
15
17
|
readonly keyRoot?: string;
|
|
@@ -205,6 +205,7 @@ async function upgradeGeneration(paths, socket, options, attachedPid) {
|
|
|
205
205
|
socket,
|
|
206
206
|
agentDir: options.agentDir ?? getAgentDir(),
|
|
207
207
|
hostArgs: options.hostArgs ?? [],
|
|
208
|
+
presenterBootstrap: options.presenterBootstrap,
|
|
208
209
|
...(options.env ? { env: options.env } : {}),
|
|
209
210
|
...(options.policy ? { policy: options.policy } : {}),
|
|
210
211
|
_test: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Duplex } from "node:stream";
|
|
1
2
|
import type { HostLifecyclePolicyInput } from "./host-lifecycle.ts";
|
|
2
3
|
/**
|
|
3
4
|
* The longest a handoff holds the ensure lock: its probe of the running host, the two session counts
|
|
@@ -5,6 +6,7 @@ import type { HostLifecyclePolicyInput } from "./host-lifecycle.ts";
|
|
|
5
6
|
*/
|
|
6
7
|
export declare const HANDOFF_LOCK_HOLD_MS: number;
|
|
7
8
|
export interface HandoffHostOptions {
|
|
9
|
+
readonly presenterBootstrap?: Duplex;
|
|
8
10
|
readonly socket: string;
|
|
9
11
|
readonly agentDir?: string;
|
|
10
12
|
/** Extra CLI arguments the successor's host child is launched with (provider pinning, extensions). */
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { HandoverAnswer, HandoverOutcome, HandoverView, IdleHandoverRequest } from "./host-idle-handover.ts";
|
|
10
10
|
import type { RpcResponse } from "./rpc-types.ts";
|
|
11
11
|
import type { RpcSessionRegistry } from "./session-registry.ts";
|
|
12
|
+
import type { Duplex } from "node:stream";
|
|
12
13
|
export declare const BEGIN_HANDOVER_COMMAND = "begin_handover";
|
|
13
14
|
export declare function parseBeginHandover(record: Readonly<Record<string, unknown>>): IdleHandoverRequest | string;
|
|
14
15
|
export declare function beginHandoverResponse(id: unknown, answer: HandoverAnswer | string): Record<string, unknown>;
|
|
@@ -37,5 +38,6 @@ export declare function performIdleHandover(context: {
|
|
|
37
38
|
readonly request: IdleHandoverRequest;
|
|
38
39
|
readonly socket: string;
|
|
39
40
|
readonly agentDir: string;
|
|
41
|
+
readonly presenterBootstrap?: Duplex;
|
|
40
42
|
}): Promise<HandoverOutcome>;
|
|
41
43
|
//# sourceMappingURL=host-handover-wire.d.ts.map
|
|
@@ -120,6 +120,7 @@ export async function performIdleHandover(context) {
|
|
|
120
120
|
env[name] = null;
|
|
121
121
|
Object.assign(env, request.env);
|
|
122
122
|
const result = await handoffHost({
|
|
123
|
+
presenterBootstrap: context.presenterBootstrap,
|
|
123
124
|
socket,
|
|
124
125
|
agentDir,
|
|
125
126
|
hostArgs: request.hostArgs,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { type ChildProcess } from "node:child_process";
|
|
7
7
|
import type { SocketFileIdentity } from "./socket-ownership.ts";
|
|
8
8
|
export interface SupervisorLaunch {
|
|
9
|
+
readonly presenterBootstrapFd?: number;
|
|
9
10
|
readonly socket: string;
|
|
10
11
|
readonly hostArgs: readonly string[];
|
|
11
12
|
/** Optional runtime command used by rebranded/bundled callers. */
|
|
@@ -56,10 +56,16 @@ export function parseSupervisorArgs(argv) {
|
|
|
56
56
|
let childArgs;
|
|
57
57
|
let agentDir;
|
|
58
58
|
let ownerFd;
|
|
59
|
+
let presenterBootstrapFd;
|
|
59
60
|
let bindSocket;
|
|
60
61
|
let replaceIdentity;
|
|
61
62
|
for (let index = 0; index < argv.length; index++) {
|
|
62
63
|
const arg = argv[index];
|
|
64
|
+
if (arg === "--mcp-presenter-fd" && argv[index + 1] === "4") {
|
|
65
|
+
presenterBootstrapFd = 4;
|
|
66
|
+
index++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
63
69
|
if (arg === "--owner-fd" && index + 1 < argv.length) {
|
|
64
70
|
ownerFd = Number(argv[++index]);
|
|
65
71
|
if (!Number.isSafeInteger(ownerFd) || ownerFd < 3)
|
|
@@ -101,7 +107,19 @@ export function parseSupervisorArgs(argv) {
|
|
|
101
107
|
}
|
|
102
108
|
return socket === undefined
|
|
103
109
|
? undefined
|
|
104
|
-
:
|
|
110
|
+
: ownerFd === presenterBootstrapFd && ownerFd !== undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: {
|
|
113
|
+
socket,
|
|
114
|
+
hostArgs,
|
|
115
|
+
childCommand,
|
|
116
|
+
childArgs,
|
|
117
|
+
agentDir,
|
|
118
|
+
ownerFd,
|
|
119
|
+
bindSocket,
|
|
120
|
+
replaceIdentity,
|
|
121
|
+
presenterBootstrapFd,
|
|
122
|
+
};
|
|
105
123
|
}
|
|
106
124
|
export const OWNER_WATCH_FD = 3;
|
|
107
125
|
// Strongly retain the write end until that supervisor exits, but never keep the caller alive.
|
|
@@ -188,6 +206,10 @@ export const HOST_CHILD_WATCH_FD = 3;
|
|
|
188
206
|
export function spawnHostChild(options) {
|
|
189
207
|
const { launch, internalSocket, internal, internalSecret, publicSocket } = options;
|
|
190
208
|
const childLaunch = spawnableChildLaunch(resolveHostChildLaunch(launch, internalSocket));
|
|
209
|
+
// A shell wrapper cannot prove preservation of the private inherited handle.
|
|
210
|
+
const bootstrap = launch.presenterBootstrapFd !== undefined && !childLaunch.shell;
|
|
211
|
+
if (bootstrap)
|
|
212
|
+
childLaunch.args.push("--mcp-presenter-fd", "4");
|
|
191
213
|
const child = spawn(childLaunch.command, childLaunch.args, {
|
|
192
214
|
env: {
|
|
193
215
|
...process.env,
|
|
@@ -210,13 +232,32 @@ export function spawnHostChild(options) {
|
|
|
210
232
|
// Slot 3 is the lifetime pipe: "pipe" gives the child a read end it can
|
|
211
233
|
// wait on and keeps the write end owned by this process alone.
|
|
212
234
|
shell: childLaunch.shell,
|
|
213
|
-
stdio: [
|
|
235
|
+
stdio: [
|
|
236
|
+
process.platform === "win32" ? "ignore" : "pipe",
|
|
237
|
+
"ignore",
|
|
238
|
+
"inherit",
|
|
239
|
+
"pipe",
|
|
240
|
+
...(bootstrap ? ["pipe"] : []),
|
|
241
|
+
],
|
|
214
242
|
// The supervisor is spawned detached, so on win32 it owns no console. A
|
|
215
243
|
// console-subsystem child started from it would allocate a fresh one,
|
|
216
244
|
// which Windows Terminal renders as an empty window that takes focus.
|
|
217
245
|
// CREATE_NO_WINDOW gives the child a console with no window instead.
|
|
218
246
|
windowsHide: true,
|
|
219
247
|
});
|
|
248
|
+
if (launch.presenterBootstrapFd !== undefined) {
|
|
249
|
+
const fd = launch.presenterBootstrapFd;
|
|
250
|
+
// The opt-in helper stays outside the supervisor's fixed startup import budget.
|
|
251
|
+
void import("./mcp-presenter-bootstrap.js")
|
|
252
|
+
.then(({ presenterBootstrapStream, relayPresenterBootstrap }) => {
|
|
253
|
+
const upstream = presenterBootstrapStream(fd);
|
|
254
|
+
if (upstream && bootstrap)
|
|
255
|
+
return relayPresenterBootstrap(upstream, child);
|
|
256
|
+
upstream?.destroy();
|
|
257
|
+
child.stdio[4]?.destroy();
|
|
258
|
+
})
|
|
259
|
+
.catch(() => child.stdio[4]?.destroy());
|
|
260
|
+
}
|
|
220
261
|
if (options.internalKey) {
|
|
221
262
|
child.stdin?.on("error", () => { });
|
|
222
263
|
// One write only. Keep its write end alive until this supervisor exits.
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* itself with `observe: true`; the supervisor classifies a connection by the requests it sends, and
|
|
8
8
|
* only a connection that sends something other than an observing read becomes an attachment.
|
|
9
9
|
*
|
|
10
|
-
* Only the side-effect-free reads can observe (`get_effect_status` answers a receipt and changes nothing
|
|
10
|
+
* Only the side-effect-free reads can observe (`get_effect_status` answers a receipt and changes nothing; `get_state` and
|
|
11
|
+
* `get_entries` read a session's state and history, senpi#4267): `observe: true` on any other command is ignored
|
|
11
12
|
* and the connection attaches, so a client cannot hold a session while its host idles out under it.
|
|
12
13
|
* `warm` never attaches, marked or not: it opens nothing, so a host that only received it idles out
|
|
13
14
|
* on its normal deadline (senpi#2314).
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* itself with `observe: true`; the supervisor classifies a connection by the requests it sends, and
|
|
8
8
|
* only a connection that sends something other than an observing read becomes an attachment.
|
|
9
9
|
*
|
|
10
|
-
* Only the side-effect-free reads can observe (`get_effect_status` answers a receipt and changes nothing
|
|
10
|
+
* Only the side-effect-free reads can observe (`get_effect_status` answers a receipt and changes nothing; `get_state` and
|
|
11
|
+
* `get_entries` read a session's state and history, senpi#4267): `observe: true` on any other command is ignored
|
|
11
12
|
* and the connection attaches, so a client cannot hold a session while its host idles out under it.
|
|
12
13
|
* `warm` never attaches, marked or not: it opens nothing, so a host that only received it idles out
|
|
13
14
|
* on its normal deadline (senpi#2314).
|
|
@@ -33,6 +34,8 @@ const OBSERVING_COMMANDS = new Set([
|
|
|
33
34
|
"get_effect_status",
|
|
34
35
|
"get_pending_inputs",
|
|
35
36
|
"get_subscription_limits",
|
|
37
|
+
"get_state",
|
|
38
|
+
"get_entries",
|
|
36
39
|
]);
|
|
37
40
|
export function isObservingRequest(line) {
|
|
38
41
|
let request;
|
|
@@ -5,6 +5,7 @@ import type { ResolvedHostLaunchSpec } from "./host-launch-spec.ts";
|
|
|
5
5
|
import { type HostOutcome } from "./host-outcome.ts";
|
|
6
6
|
import type { HostStopGeneration } from "./host-stop-generation.ts";
|
|
7
7
|
export { HOST_EXIT_ERROR, HOST_EXIT_FALLBACK, HOST_EXIT_OK, HOST_EXIT_REFUSED, HOST_EXIT_USAGE, type HostOutcome, } from "./host-outcome.ts";
|
|
8
|
+
import type { Duplex } from "node:stream";
|
|
8
9
|
/** Which daemon a request is about: the endpoint, and the agent directory holding its state. */
|
|
9
10
|
export interface HostTarget {
|
|
10
11
|
readonly socket: string;
|
|
@@ -50,5 +51,6 @@ export type HostRequest = {
|
|
|
50
51
|
};
|
|
51
52
|
export declare function runHostRequest(request: HostRequest, options?: {
|
|
52
53
|
readonly keyRoot?: string;
|
|
54
|
+
readonly presenterBootstrap?: Duplex;
|
|
53
55
|
}): Promise<HostOutcome>;
|
|
54
56
|
//# sourceMappingURL=host-runner.d.ts.map
|