@code-yeongyu/senpi 2026.10.25-1 → 2026.10.25-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -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-RHHBXIRN.js → chunk-4WZRVZNX.js} +1 -1
- package/dist/bundle/chunks/chunk-6O4SEW2W.js +5 -0
- package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
- package/dist/bundle/chunks/{chunk-YDCRA5ES.js → chunk-7W7OJDIY.js} +4 -4
- package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
- package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-C5UFJ7B3.js} +1 -1
- package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-CNL6QSUB.js} +4 -4
- package/dist/bundle/chunks/{chunk-BTNYLNWZ.js → chunk-FFQOEC76.js} +5 -5
- package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
- package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
- package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-HVJI5T75.js} +1 -1
- package/dist/bundle/chunks/{chunk-EWOEVVQT.js → chunk-JA2R2ZSE.js} +1 -1
- package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
- package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
- package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
- package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
- package/dist/bundle/chunks/{chunk-EYBDBR7V.js → chunk-MJ5NNWX4.js} +1 -1
- package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-O3Q2ET6W.js} +2 -5
- package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
- package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-ODTGC6YU.js} +1 -1
- package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
- package/dist/bundle/chunks/chunk-SWJXRC2I.js +5 -0
- package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
- package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
- package/dist/bundle/chunks/{chunk-7UUOGZKR.js → chunk-UQNRW3VU.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-7WAJCESX.js → chunk-WLHNEUGD.js} +1 -1
- package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
- package/dist/bundle/chunks/{chunk-URBJS24R.js → chunk-YRF4I43T.js} +35 -35
- package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
- package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
- package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
- package/dist/bundle/chunks/dispatch-NYAULNVW.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-Z3WPHJOW.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +9 -7
- package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
- package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.js → host-socket-request-3CWE4265.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
- package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-M7HVUMRH.js → multi-session-host-YI2WZOET.js} +12 -9
- package/dist/bundle/chunks/{package-manager-cli-FSR5P6YL.js → package-manager-cli-GHZPJMRF.js} +1 -1
- package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-OL5KU6NO.js → rpc-monitor-source-FFEQS744.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-YWG6D6ZF.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
- package/dist/bundle/chunks/{session-picker-KQUFWDQC.js → session-picker-25H5KNYP.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +285 -271
- 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 +9 -7
- 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.d.ts +1 -0
- package/dist/core/agent-session.js +50 -16
- package/dist/core/engine-turn-limit.d.ts +2 -0
- package/dist/core/engine-turn-limit.js +7 -1
- package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
- 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/goal/stop-pause.js +2 -1
- package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
- 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 +56 -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-runtime-facts.js +3 -0
- package/dist/core/lifecycle-session-binding.d.ts +11 -1
- package/dist/core/lifecycle-session-binding.js +35 -4
- 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/run-resume-turn.d.ts +1 -0
- package/dist/core/run-resume-turn.js +4 -3
- 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/core/stop-resume-directive.d.ts +1 -1
- package/dist/core/stop-resume-directive.js +10 -1
- 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 +35 -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/pause-footer.d.ts +5 -0
- package/dist/modes/interactive/pause-footer.js +2 -0
- package/dist/modes/rpc/connection-handler.d.ts +1 -0
- package/dist/modes/rpc/connection-handler.js +7 -1
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +2 -1
- 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-reservations.js +3 -2
- package/dist/modes/rpc/host-runner.d.ts +2 -0
- package/dist/modes/rpc/host-runner.js +8 -6
- package/dist/modes/rpc/host-stalled-evidence.js +2 -1
- 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/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/dist/modes/rpc/tool-child/group-ownership.js +4 -2
- package/dist/utils/process-identity.d.ts +7 -0
- package/dist/utils/process-identity.js +32 -5
- package/docs/extensions.md +5 -0
- package/docs/mcp.md +20 -3
- package/docs/rpc.md +111 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-3VOG6WFU.js +0 -27
- package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
- package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
- package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
- package/dist/bundle/chunks/chunk-II22E3CV.js +0 -3
- package/dist/bundle/chunks/chunk-J5P2VL3Y.js +0 -5
- package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
- package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
- package/dist/bundle/chunks/cli-main-TABBP55A.js +0 -3
- package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
- package/dist/bundle/chunks/host-command-OQR4RDTM.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-U36U7JTE.js +0 -3
- package/dist/bundle/chunks/host-session-command-IGB27ZPJ.js +0 -20
- package/dist/bundle/chunks/interactive-mode-DGQNSXUA.js +0 -2
- package/dist/bundle/chunks/rpc-mode-SZAJNKKL.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-WEF3XTJR.js +0 -3
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { modernCredentialSnapshot } from "./modern/http-fetch.js";
|
|
2
|
+
import { createMcpRequestClient } from "./modern/client.js";
|
|
2
3
|
import { ProjectStdioTransport } from "./project-stdio-transport.js";
|
|
3
|
-
import {
|
|
4
|
+
import { loadMcpSdkStdioTransport, loadMcpSdkStreamableHttpTransport } from "./sdk.lazy.js";
|
|
4
5
|
import { safeOn } from "./wrap.js";
|
|
5
|
-
export async function materializeMcpTransport(options) {
|
|
6
|
-
const client = await
|
|
6
|
+
export async function materializeMcpTransport(options, protocol = {}) {
|
|
7
|
+
const client = await createMcpRequestClient(protocol, options.elicitationUiProvider, () => modernCredentialSnapshot(options.spec.authProvider?.tokens()), options.elicitationEnabled);
|
|
7
8
|
if (options.spec.kind === "stdio")
|
|
8
|
-
return materializeStdio(options.spec, options, client);
|
|
9
|
+
return materializeStdio(options.spec, options, client, protocol);
|
|
9
10
|
return materializeHttp(options.spec, client);
|
|
10
11
|
}
|
|
11
|
-
async function materializeStdio(spec, options, client) {
|
|
12
|
+
async function materializeStdio(spec, options, client, protocol) {
|
|
12
13
|
const { getDefaultEnvironment, StdioClientTransport } = await loadMcpSdkStdioTransport();
|
|
13
14
|
const create = (current) => new StdioClientTransport({
|
|
14
15
|
args: current.args,
|
|
@@ -25,7 +26,7 @@ async function materializeStdio(spec, options, client) {
|
|
|
25
26
|
? new ProjectStdioTransport(spec.resolveBeforeStart, create)
|
|
26
27
|
: create(spec);
|
|
27
28
|
trackStdioStart(transport, options.onStart);
|
|
28
|
-
pipeStderr(transport, options.logger, options.sink);
|
|
29
|
+
pipeStderr(transport, options.logger, options.sink, protocol);
|
|
29
30
|
dropNullNextCursor(transport);
|
|
30
31
|
return {
|
|
31
32
|
client,
|
|
@@ -48,15 +49,6 @@ async function materializeHttp(spec, client) {
|
|
|
48
49
|
transport,
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
async function buildMcpClient(elicitationUiProvider, elicitationEnabled = true) {
|
|
52
|
-
// Elicitation capability is declared EMPTY on purpose (form mode only;
|
|
53
|
-
// Spring-AI servers reject richer shapes) and the create-handler is wired
|
|
54
|
-
// before any connect so mid-call requests never race registration.
|
|
55
|
-
const { Client: McpClient } = await loadMcpSdkClient();
|
|
56
|
-
const client = new McpClient({ name: "senpi-mcp-client", version: "0.0.0" }, { capabilities: mcpClientCapabilities(elicitationEnabled) });
|
|
57
|
-
await configureMcpElicitation(client, elicitationUiProvider, undefined, elicitationEnabled);
|
|
58
|
-
return client;
|
|
59
|
-
}
|
|
60
52
|
// OMP pattern: hand stdio OAuth servers the current access token via env.
|
|
61
53
|
function oauthAccessTokenEnv(authProvider) {
|
|
62
54
|
const accessToken = authProvider?.tokens()?.access_token;
|
|
@@ -90,26 +82,65 @@ function trackStdioStart(transport, onStart) {
|
|
|
90
82
|
onStart();
|
|
91
83
|
};
|
|
92
84
|
}
|
|
93
|
-
function pipeStderr(transport, logger, sink) {
|
|
85
|
+
function pipeStderr(transport, logger, sink, protocol) {
|
|
94
86
|
let pending = "";
|
|
87
|
+
let undecided = "";
|
|
88
|
+
let stderrEnded = false;
|
|
95
89
|
const stderr = transport.stderr;
|
|
96
90
|
if (stderr === undefined || stderr === null)
|
|
97
91
|
return;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
const logLine = (line) => {
|
|
93
|
+
// Arbitrary server text may echo private messages, schemas, or answers. Keep diagnostic
|
|
94
|
+
// events and allowlisted system error codes, never untrusted payload text under enforcement.
|
|
95
|
+
const code = protocol.privateForms
|
|
96
|
+
? line.match(/\b(?:EACCES|ENOENT|EPIPE|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOSPC|ENOMEM|EMFILE)\b/)?.[0]
|
|
97
|
+
: undefined;
|
|
98
|
+
logger.stderr(protocol.privateForms ? `[private MCP stderr redacted]${code ? ` ${code}` : ""}` : line);
|
|
99
|
+
};
|
|
100
|
+
const append = (text) => {
|
|
101
|
+
pending += text;
|
|
102
102
|
const lines = pending.split(/\r?\n/);
|
|
103
103
|
pending = lines.pop() ?? "";
|
|
104
|
-
for (const line of lines)
|
|
104
|
+
for (const line of lines)
|
|
105
105
|
if (line.length > 0)
|
|
106
|
-
|
|
106
|
+
logLine(line);
|
|
107
|
+
};
|
|
108
|
+
protocol.resolveStdioDiagnostics = (modern) => {
|
|
109
|
+
if (!modern || protocol.privateForms) {
|
|
110
|
+
append(undecided);
|
|
111
|
+
if (stderrEnded && pending.length > 0) {
|
|
112
|
+
logLine(pending);
|
|
113
|
+
pending = "";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
undecided = "";
|
|
117
|
+
if (modern && !protocol.privateForms)
|
|
118
|
+
pending = "";
|
|
119
|
+
};
|
|
120
|
+
safeOn(stderr, "data", "transport.stderr.data", (chunk) => {
|
|
121
|
+
if (protocol.version !== undefined && !protocol.privateForms) {
|
|
122
|
+
pending = "";
|
|
123
|
+
undecided = "";
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!Buffer.isBuffer(chunk) && typeof chunk !== "string")
|
|
127
|
+
return;
|
|
128
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
129
|
+
if (!protocol.legacy && !protocol.privateForms) {
|
|
130
|
+
// Only legacy-positive negotiation may release startup diagnostics.
|
|
131
|
+
undecided += text.slice(0, Math.max(0, 512 * 1024 - undecided.length));
|
|
132
|
+
return;
|
|
107
133
|
}
|
|
134
|
+
append(text);
|
|
108
135
|
}, sink);
|
|
109
136
|
safeOn(stderr, "end", "transport.stderr.end", () => {
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
stderrEnded = true;
|
|
138
|
+
if ((protocol.privateForms || (protocol.legacy && protocol.version === undefined)) && pending.length > 0)
|
|
139
|
+
logLine(pending);
|
|
112
140
|
pending = "";
|
|
141
|
+
// A crashed probe is classified after EOF; only that decision may release or discard its buffer.
|
|
142
|
+
if (protocol.legacy || protocol.version !== undefined)
|
|
143
|
+
undecided = "";
|
|
113
144
|
}, sink);
|
|
114
145
|
}
|
|
115
146
|
async function closeClientAndTransport(transport, client) {
|
|
@@ -3,6 +3,13 @@ export type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
export type { CallToolResult, ListToolsResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
import type { EventEmitter } from "node:events";
|
|
5
5
|
import type { EventBus } from "../../../event-bus.ts";
|
|
6
|
+
export interface McpClock {
|
|
7
|
+
now(): number;
|
|
8
|
+
schedule(callback: () => void, ms: number): ReturnType<typeof setTimeout>;
|
|
9
|
+
cancel(timer: ReturnType<typeof setTimeout>): void;
|
|
10
|
+
}
|
|
11
|
+
/** The modern operation uses one injectable monotonic clock and scheduler. */
|
|
12
|
+
export declare const mcpElapsedClock: McpClock;
|
|
6
13
|
type MaybePromise<T> = T | Promise<T>;
|
|
7
14
|
export interface McpErrorLogger {
|
|
8
15
|
error(message: string, data?: unknown): void;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { redactMcpLogText } from "./log.js";
|
|
2
|
+
/** The modern operation uses one injectable monotonic clock and scheduler. */
|
|
3
|
+
export const mcpElapsedClock = {
|
|
4
|
+
now: () => performance.now(),
|
|
5
|
+
schedule: setTimeout,
|
|
6
|
+
cancel: clearTimeout,
|
|
7
|
+
};
|
|
2
8
|
export function wrapAsync(scope, fn, sink) {
|
|
3
9
|
return async (...args) => {
|
|
4
10
|
try {
|
|
@@ -6,6 +6,7 @@ import { getAgentDir } from "../../config.js";
|
|
|
6
6
|
import { theme } from "../../modes/interactive/theme/theme.js";
|
|
7
7
|
import { stripAnsi } from "../../utils/ansi.js";
|
|
8
8
|
import { createEventBus, EXTENSION_RPC_EVENT_CHANNEL } from "../event-bus.js";
|
|
9
|
+
import { inheritMcpMrtrPresentation } from "../mcp-mrtr-presentations.js";
|
|
9
10
|
import { bindQuestionInput } from "../question-input.js";
|
|
10
11
|
import { DEFAULT_EXTENSION_EVENT_HANDLER_TIMEOUT_MS, DEFAULT_EXTENSION_EVENT_HANDLER_WARN_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_TIMEOUT_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_WARN_MS, DEFAULT_SESSION_SHUTDOWN_QUIT_BUDGET_MS, SettingsManager, } from "../settings-manager.js";
|
|
11
12
|
import { goalFilePath } from "./builtin/goal/persistence.js";
|
|
@@ -589,7 +590,7 @@ export class ExtensionRunner {
|
|
|
589
590
|
}
|
|
590
591
|
wrapUIPromptContext(ui) {
|
|
591
592
|
const questionFn = ui.question;
|
|
592
|
-
return {
|
|
593
|
+
return inheritMcpMrtrPresentation(ui, {
|
|
593
594
|
...ui,
|
|
594
595
|
select: (title, options, opts) => this.withUIPrompt("select", title, () => ui.select(title, options, opts)),
|
|
595
596
|
confirm: (title, message, opts) => this.withUIPrompt("confirm", title, () => ui.confirm(title, message, opts)),
|
|
@@ -601,7 +602,7 @@ export class ExtensionRunner {
|
|
|
601
602
|
question: (request, opts) => this.withUIPrompt("question", request.questions[0]?.header, () => questionFn(request, opts), request.requestId),
|
|
602
603
|
}
|
|
603
604
|
: {}),
|
|
604
|
-
};
|
|
605
|
+
});
|
|
605
606
|
}
|
|
606
607
|
withUIPrompt(kind, title, run, requestId = crypto.randomUUID()) {
|
|
607
608
|
const outerPrompt = this.uiPromptDepth++ === 0;
|
|
@@ -59,6 +59,8 @@ export type CompactionReason = "manual" | "threshold" | "overflow" | "pre_prompt
|
|
|
59
59
|
export type CompactionRejectionCause = "cancelled-by-extension" | "external-owner" | "would-overflow" | "circuit-breaker" | "per-turn-cap" | "stale-revision";
|
|
60
60
|
/** Options for extension UI dialogs. */
|
|
61
61
|
export interface ExtensionUIDialogOptions {
|
|
62
|
+
/** Private local dialog: emits no generic blocked event or pending marker. Not an RPC field. */
|
|
63
|
+
ephemeral?: boolean;
|
|
62
64
|
/** AbortSignal to programmatically dismiss the dialog. */
|
|
63
65
|
signal?: AbortSignal;
|
|
64
66
|
/** Timeout in milliseconds. Dialog auto-dismisses with live countdown display. */
|
|
@@ -39,6 +39,7 @@ export declare class LifecycleLedger {
|
|
|
39
39
|
readonly waits: LifecycleWaits;
|
|
40
40
|
readonly holds: readonly LifecycleHold[];
|
|
41
41
|
readonly failure?: SessionLifecycle["failure"];
|
|
42
|
+
readonly lastStop?: SessionLifecycle["lastStop"];
|
|
42
43
|
}): void;
|
|
43
44
|
checkpoint(): void;
|
|
44
45
|
/** Publishes a revision or epoch that no record carries yet, once. */
|
|
@@ -80,6 +80,9 @@ export class LifecycleLedger {
|
|
|
80
80
|
startRun(origin) {
|
|
81
81
|
this.lastTurnEnd = undefined;
|
|
82
82
|
this.openEpoch(origin);
|
|
83
|
+
// The previous run's stop never describes this one (senpi#3864).
|
|
84
|
+
const { lastStop: _lastStop, ...current } = this.current;
|
|
85
|
+
this.current = current;
|
|
83
86
|
this.runId = crypto.randomUUID();
|
|
84
87
|
this.runActive = true;
|
|
85
88
|
this.admissionWriteFailed = false;
|
|
@@ -134,7 +137,7 @@ export class LifecycleLedger {
|
|
|
134
137
|
if (input.holds.length > 0 || input.state === "failed")
|
|
135
138
|
this.request = undefined;
|
|
136
139
|
const terminal = input.state === "done" || input.state === "failed";
|
|
137
|
-
const { completionId: _id, completion: _completion, failure: _failure, settleDecision: _decision, ...base } = this.current;
|
|
140
|
+
const { completionId: _id, completion: _completion, failure: _failure, settleDecision: _decision, lastStop: _lastStop, ...base } = this.current;
|
|
138
141
|
const now = new Date().toISOString();
|
|
139
142
|
const standingRequest = this.request?.epoch === base.epoch &&
|
|
140
143
|
this.request.revision === base.humanInputRevision &&
|
|
@@ -163,6 +166,7 @@ export class LifecycleLedger {
|
|
|
163
166
|
}
|
|
164
167
|
: {}),
|
|
165
168
|
...(input.state === "failed" && input.failure ? { failure: input.failure } : {}),
|
|
169
|
+
...(input.lastStop ? { lastStop: input.lastStop } : {}),
|
|
166
170
|
...(input.state === "done" && standingRequest
|
|
167
171
|
? {
|
|
168
172
|
settleDecision: this.current.settleDecision ?? { decisionId: crypto.randomUUID() },
|
|
@@ -238,7 +242,8 @@ function equivalent(previous, next) {
|
|
|
238
242
|
previous.selfSettleEligible === next.selfSettleEligible &&
|
|
239
243
|
JSON.stringify(previous.waitingOn) === JSON.stringify(next.waitingOn) &&
|
|
240
244
|
JSON.stringify(previous.holds) === JSON.stringify(next.holds) &&
|
|
241
|
-
JSON.stringify(previous.failure) === JSON.stringify(next.failure)
|
|
245
|
+
JSON.stringify(previous.failure) === JSON.stringify(next.failure) &&
|
|
246
|
+
JSON.stringify(previous.lastStop) === JSON.stringify(next.lastStop));
|
|
242
247
|
}
|
|
243
248
|
function wireSnapshot(record) {
|
|
244
249
|
const { sessionId: _sessionId, interrupted: _interrupted, completedGoalId: _goal, completedRunId: _completedRun, lastTurnEnd: _lastTurnEnd, attentionSeq: _attentionSeq, synthetic: _synthetic, epochSealed: _sealed, settleRequest: _request, runId: _runId, runActive: _runActive, ...snapshot } = record;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readHumanWait } from "./lifecycle-attention.js";
|
|
2
2
|
import { readLifecycleState } from "./lifecycle-children.js";
|
|
3
|
+
import { readLastStop } from "./lifecycle-stop.js";
|
|
3
4
|
import { isRecord } from "./work-custody-record.js";
|
|
4
5
|
/** Restore the wire snapshot and its private high-water marks from owned JSONL. */
|
|
5
6
|
export function readLifecycleRecord(value) {
|
|
@@ -33,6 +34,7 @@ export function readLifecycleRecord(value) {
|
|
|
33
34
|
const completion = readCompletion(value.completion);
|
|
34
35
|
const failure = readFailure(value.failure);
|
|
35
36
|
const lastTurnEnd = readTurnEnd(value.lastTurnEnd);
|
|
37
|
+
const lastStop = readLastStop(value.lastStop, value.runId);
|
|
36
38
|
return {
|
|
37
39
|
sessionId: value.sessionId,
|
|
38
40
|
state,
|
|
@@ -63,6 +65,7 @@ export function readLifecycleRecord(value) {
|
|
|
63
65
|
...(completion ? { completion } : {}),
|
|
64
66
|
...(failure ? { failure } : {}),
|
|
65
67
|
...(lastTurnEnd ? { lastTurnEnd } : {}),
|
|
68
|
+
...(lastStop ? { lastStop } : {}),
|
|
66
69
|
...(isRecord(value.settleDecision) && typeof value.settleDecision.decisionId === "string"
|
|
67
70
|
? { settleDecision: { decisionId: value.settleDecision.decisionId } }
|
|
68
71
|
: {}),
|
|
@@ -35,6 +35,9 @@ export function readLifecycleRuntimeFacts(session) {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
const holds = session.holds.flatMap((hold) => {
|
|
38
|
+
// The interruption gate is a human wait, not a user Stop. Preserve unattended and real Stop precedence.
|
|
39
|
+
if (hold.kind === "session_hold" && hold.key === "interrupted-run")
|
|
40
|
+
return [];
|
|
38
41
|
switch (hold.kind) {
|
|
39
42
|
case "unattended":
|
|
40
43
|
case "session_hold":
|
|
@@ -5,6 +5,7 @@ import type { ExtensionRunner } from "./extensions/runner.ts";
|
|
|
5
5
|
import type { AgentEndEvent } from "./extensions/types.ts";
|
|
6
6
|
import { SessionLifecycleController } from "./session-lifecycle.ts";
|
|
7
7
|
import type { SessionLifecycleEvent } from "./session-lifecycle-types.ts";
|
|
8
|
+
import type { TurnDeadlineKind } from "./turn-deadline.ts";
|
|
8
9
|
interface LifecycleBindingHost {
|
|
9
10
|
append(type: string, data: unknown): string;
|
|
10
11
|
emit(event: SessionLifecycleEvent): void;
|
|
@@ -20,6 +21,11 @@ export declare class LifecycleSessionBinding {
|
|
|
20
21
|
private readonly unsubscribeAdmission;
|
|
21
22
|
private readonly unsubscribeGoal;
|
|
22
23
|
private lastEnd;
|
|
24
|
+
/** The run each emitted agent_end belonged to, read when it was emitted, not when its queue reaches it. */
|
|
25
|
+
private readonly boundEnds;
|
|
26
|
+
private lastEndRunId;
|
|
27
|
+
/** Captured once inside the bound end's own extension-processing window, never re-scanned by a later run. */
|
|
28
|
+
private lastEndGuard;
|
|
23
29
|
private refreshGeneration;
|
|
24
30
|
private restored;
|
|
25
31
|
private nextRunOrigin;
|
|
@@ -32,9 +38,13 @@ export declare class LifecycleSessionBinding {
|
|
|
32
38
|
prepareRun(): void;
|
|
33
39
|
get isRestored(): boolean;
|
|
34
40
|
startRun(): void;
|
|
41
|
+
deadlineStopped(kind: TurnDeadlineKind, atMs: number): void;
|
|
42
|
+
deadlineStopWriteFailed(): void;
|
|
43
|
+
bindTurnEnd(end: object): void;
|
|
35
44
|
consume(message: AgentMessage): string | undefined;
|
|
36
45
|
consumedEntry(message: AgentMessage): string | undefined;
|
|
37
|
-
|
|
46
|
+
/** `event` is the extension agent_end, never the session-level stamp; `end` is the core event bound at emission. */
|
|
47
|
+
turnEnded(event: AgentEndEvent, end: object, guardStartLeafId?: string | null): Promise<void>;
|
|
38
48
|
refreshFacts(changedGoal?: Goal | null): Promise<void>;
|
|
39
49
|
idle(): Promise<void>;
|
|
40
50
|
restore(): Promise<void>;
|
|
@@ -8,11 +8,14 @@ import { readLifecycleGoal } from "./lifecycle-goal.js";
|
|
|
8
8
|
import { lifecycleHistory } from "./lifecycle-history.js";
|
|
9
9
|
import { bindLifecycleHumanInput } from "./lifecycle-human-input.js";
|
|
10
10
|
import { readLifecycleRuntimeFacts } from "./lifecycle-runtime-facts.js";
|
|
11
|
+
import { goalGuardAfter } from "./lifecycle-stop.js";
|
|
11
12
|
import { SessionLifecycleController } from "./session-lifecycle.js";
|
|
12
13
|
import { isRecord } from "./work-custody-record.js";
|
|
13
14
|
/** Attaches durable lifecycle to the real session event/admission boundaries. */
|
|
14
15
|
export class LifecycleSessionBinding {
|
|
15
16
|
constructor(session, host) {
|
|
17
|
+
/** The run each emitted agent_end belonged to, read when it was emitted, not when its queue reaches it. */
|
|
18
|
+
this.boundEnds = new WeakMap();
|
|
16
19
|
this.refreshGeneration = 0;
|
|
17
20
|
this.restored = false;
|
|
18
21
|
/** After dispose() nothing the controller decides is written or emitted: the session file is no longer this session's. */
|
|
@@ -44,8 +47,12 @@ export class LifecycleSessionBinding {
|
|
|
44
47
|
});
|
|
45
48
|
// A crashed receiver may have durable custody but no transcript entry yet. Recovery owns that
|
|
46
49
|
// delivery already; register it before session_start can let the gateway enqueue a second carrier.
|
|
47
|
-
const
|
|
50
|
+
const deliveries = session.sessionManager.reduceHistory(lifecycleDeliveryEntries);
|
|
48
51
|
for (const record of this.controller.obligations.active) {
|
|
52
|
+
// Discover consumed work before session_start can queue another automatic turn. The later
|
|
53
|
+
// recovery pass still writes interruption/tool records, but must not be the first to close admission.
|
|
54
|
+
if (record.ticketId && (record.state === "consumed" || deliveries.has(record.ticketId)))
|
|
55
|
+
this.controller.ledger.interrupted = true;
|
|
49
56
|
if (record.state !== "queued" || !record.payload || !record.ticketId)
|
|
50
57
|
continue;
|
|
51
58
|
const ticketId = record.ticketId;
|
|
@@ -53,7 +60,7 @@ export class LifecycleSessionBinding {
|
|
|
53
60
|
const deliveryId = deliveryIdOf(message);
|
|
54
61
|
if (deliveryId === undefined)
|
|
55
62
|
continue;
|
|
56
|
-
if (
|
|
63
|
+
if (deliveries.has(ticketId))
|
|
57
64
|
session.externalAdmission.observePersisted(message);
|
|
58
65
|
else
|
|
59
66
|
session.externalAdmission.restoreQueued(deliveryId, () =>
|
|
@@ -98,10 +105,20 @@ export class LifecycleSessionBinding {
|
|
|
98
105
|
}
|
|
99
106
|
startRun() {
|
|
100
107
|
this.lastEnd = undefined;
|
|
108
|
+
this.lastEndGuard = undefined;
|
|
101
109
|
const origin = this.nextRunOrigin;
|
|
102
110
|
this.nextRunOrigin = undefined;
|
|
103
111
|
this.controller.startRun(origin);
|
|
104
112
|
}
|
|
113
|
+
deadlineStopped(kind, atMs) {
|
|
114
|
+
this.controller.deadlineStopped(kind, atMs);
|
|
115
|
+
}
|
|
116
|
+
deadlineStopWriteFailed() {
|
|
117
|
+
this.controller.deadlineStopWriteFailed();
|
|
118
|
+
}
|
|
119
|
+
bindTurnEnd(end) {
|
|
120
|
+
this.boundEnds.set(end, this.controller.ledger.runId);
|
|
121
|
+
}
|
|
105
122
|
consume(message) {
|
|
106
123
|
return this.write(() => consumeLifecycleDelivery(this.session, this.controller, message));
|
|
107
124
|
}
|
|
@@ -111,8 +128,14 @@ export class LifecycleSessionBinding {
|
|
|
111
128
|
const ticketId = messageTicket(message);
|
|
112
129
|
return ticketId ? this.session.sessionManager.reduceHistory(lifecycleDeliveryEntries).get(ticketId) : undefined;
|
|
113
130
|
}
|
|
114
|
-
|
|
131
|
+
/** `event` is the extension agent_end, never the session-level stamp; `end` is the core event bound at emission. */
|
|
132
|
+
async turnEnded(event, end, guardStartLeafId) {
|
|
115
133
|
this.lastEnd = event;
|
|
134
|
+
this.lastEndRunId = this.boundEnds.get(end);
|
|
135
|
+
this.lastEndGuard =
|
|
136
|
+
guardStartLeafId === undefined
|
|
137
|
+
? undefined
|
|
138
|
+
: goalGuardAfter(this.session.sessionManager.getBranch(), guardStartLeafId);
|
|
116
139
|
await this.refreshFacts();
|
|
117
140
|
}
|
|
118
141
|
async refreshFacts(changedGoal) {
|
|
@@ -162,7 +185,15 @@ export class LifecycleSessionBinding {
|
|
|
162
185
|
deadlineStop: input.deadlineStop || saved.deadlineStop,
|
|
163
186
|
providerRetryAtMs: input.providerRetryAtMs ?? saved.providerRetryAtMs,
|
|
164
187
|
};
|
|
165
|
-
this
|
|
188
|
+
// Stop facts belong to the run that just ended in this process; a restored end carries its recorded stop.
|
|
189
|
+
const stop = this.lastEnd
|
|
190
|
+
? {
|
|
191
|
+
runId: this.lastEndRunId,
|
|
192
|
+
guard: this.lastEndGuard,
|
|
193
|
+
todo: collected?.todoOutcome,
|
|
194
|
+
}
|
|
195
|
+
: undefined;
|
|
196
|
+
this.controller.goalFacts(goal, input, stop);
|
|
166
197
|
}
|
|
167
198
|
async idle() {
|
|
168
199
|
await this.refreshFacts();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TodoOwedOutcome } from "./extensions/builtin/goal/todo-owed-backstop.ts";
|
|
2
|
+
import type { TurnEndLivenessInput } from "./extensions/builtin/goal/turn-end-liveness.ts";
|
|
3
|
+
import type { LifecycleStop } from "./session-lifecycle-types.ts";
|
|
4
|
+
import type { TurnDeadlineKind } from "./turn-deadline.ts";
|
|
5
|
+
/** Stop facts the stopping run produced itself, read from that run's own end (senpi#3864). */
|
|
6
|
+
export interface RunStopFacts {
|
|
7
|
+
/** The run the turn end belonged to when its agent_end was emitted. */
|
|
8
|
+
readonly runId?: string;
|
|
9
|
+
readonly guard?: {
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
readonly at: number;
|
|
12
|
+
};
|
|
13
|
+
readonly todo?: TodoOwedOutcome;
|
|
14
|
+
}
|
|
15
|
+
/** A deadline captured when it fired, before its durable write, for the run it stopped. */
|
|
16
|
+
export interface DeadlineStopCapture {
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly kind: TurnDeadlineKind;
|
|
19
|
+
readonly at: string;
|
|
20
|
+
readonly persisted?: false;
|
|
21
|
+
}
|
|
22
|
+
type StopDetail = Omit<LifecycleStop, "runId" | "epoch" | "at"> & {
|
|
23
|
+
readonly at?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* First match wins: the run's own deadline explains its system abort, a guard explains a refused continuation, then
|
|
27
|
+
* the classified abort, and `no_wake` only for a stall no hold or guard owns. Provider failures keep the failed path.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stopDetail(input: TurnEndLivenessInput | undefined, facts: RunStopFacts | undefined, deadline: DeadlineStopCapture | undefined, stalled: boolean): StopDetail | undefined;
|
|
30
|
+
export declare function sameStop(stop: LifecycleStop, detail: StopDetail): boolean;
|
|
31
|
+
/** The latest guard appended within one bound end's extension-processing window, including legacy entries. */
|
|
32
|
+
export declare function goalGuardAfter(branch: readonly {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly type: string;
|
|
35
|
+
readonly customType?: string;
|
|
36
|
+
readonly data?: unknown;
|
|
37
|
+
}[], guardStartLeafId: string | null): RunStopFacts["guard"];
|
|
38
|
+
/** Malformed optional detail is dropped; it never invalidates the record that carries it. */
|
|
39
|
+
export declare function readLastStop(value: unknown, runId: unknown): LifecycleStop | undefined;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=lifecycle-stop.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isRecord } from "./work-custody-record.js";
|
|
2
|
+
const ABORT_SOURCES = {
|
|
3
|
+
"user-abort": "user",
|
|
4
|
+
"system-abort": "system",
|
|
5
|
+
"provider-abort": "provider",
|
|
6
|
+
"unknown-abort": null,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* First match wins: the run's own deadline explains its system abort, a guard explains a refused continuation, then
|
|
10
|
+
* the classified abort, and `no_wake` only for a stall no hold or guard owns. Provider failures keep the failed path.
|
|
11
|
+
*/
|
|
12
|
+
export function stopDetail(input, facts, deadline, stalled) {
|
|
13
|
+
if (input?.endedBy === "provider-error" || input?.endedBy === "policy-rejection")
|
|
14
|
+
return undefined;
|
|
15
|
+
if (deadline)
|
|
16
|
+
return {
|
|
17
|
+
cause: "deadline",
|
|
18
|
+
deadline: deadline.kind,
|
|
19
|
+
at: deadline.at,
|
|
20
|
+
...(deadline.persisted === false ? { persisted: false } : {}),
|
|
21
|
+
};
|
|
22
|
+
if (facts?.guard)
|
|
23
|
+
return { cause: "goal_guard", guard: facts.guard.reason, at: new Date(facts.guard.at).toISOString() };
|
|
24
|
+
if (facts?.todo === "capped" || facts?.todo === "cap")
|
|
25
|
+
return { cause: "todo_guard", todo: "cap" };
|
|
26
|
+
const abortSource = input ? ABORT_SOURCES[input.endedBy] : undefined;
|
|
27
|
+
if (abortSource !== undefined)
|
|
28
|
+
return { cause: "aborted", ...(abortSource === null ? {} : { abortSource }) };
|
|
29
|
+
if (stalled && !input?.deadlineStop && !input?.budgetSpent)
|
|
30
|
+
return { cause: "no_wake" };
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
export function sameStop(stop, detail) {
|
|
34
|
+
return (stop.cause === detail.cause &&
|
|
35
|
+
stop.deadline === detail.deadline &&
|
|
36
|
+
stop.guard === detail.guard &&
|
|
37
|
+
stop.todo === detail.todo &&
|
|
38
|
+
stop.abortSource === detail.abortSource &&
|
|
39
|
+
stop.persisted === detail.persisted);
|
|
40
|
+
}
|
|
41
|
+
/** The latest guard appended within one bound end's extension-processing window, including legacy entries. */
|
|
42
|
+
export function goalGuardAfter(branch, guardStartLeafId) {
|
|
43
|
+
const start = guardStartLeafId === null ? -1 : branch.findIndex((entry) => entry.id === guardStartLeafId);
|
|
44
|
+
if (guardStartLeafId !== null && start < 0)
|
|
45
|
+
return undefined;
|
|
46
|
+
for (let index = branch.length - 1; index > start; index--) {
|
|
47
|
+
const entry = branch[index];
|
|
48
|
+
if (entry?.type !== "custom" || entry.customType !== "goal-continuation-stopped" || !isRecord(entry.data))
|
|
49
|
+
continue;
|
|
50
|
+
const { reason, at } = entry.data;
|
|
51
|
+
return typeof reason === "string" && typeof at === "number" ? { reason, at } : undefined;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/** Malformed optional detail is dropped; it never invalidates the record that carries it. */
|
|
56
|
+
export function readLastStop(value, runId) {
|
|
57
|
+
if (!isRecord(value) ||
|
|
58
|
+
typeof value.runId !== "string" ||
|
|
59
|
+
value.runId !== runId ||
|
|
60
|
+
typeof value.at !== "string" ||
|
|
61
|
+
typeof value.epoch !== "number")
|
|
62
|
+
return undefined;
|
|
63
|
+
const base = { runId: value.runId, at: value.at, epoch: value.epoch };
|
|
64
|
+
switch (value.cause) {
|
|
65
|
+
case "deadline":
|
|
66
|
+
return value.deadline === "inactivity" || value.deadline === "max_runtime"
|
|
67
|
+
? {
|
|
68
|
+
...base,
|
|
69
|
+
cause: value.cause,
|
|
70
|
+
deadline: value.deadline,
|
|
71
|
+
...(value.persisted === false ? { persisted: false } : {}),
|
|
72
|
+
}
|
|
73
|
+
: undefined;
|
|
74
|
+
case "goal_guard":
|
|
75
|
+
return { ...base, cause: value.cause, ...(typeof value.guard === "string" ? { guard: value.guard } : {}) };
|
|
76
|
+
case "todo_guard":
|
|
77
|
+
return { ...base, cause: value.cause, todo: "cap" };
|
|
78
|
+
case "aborted":
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
cause: value.cause,
|
|
82
|
+
...(value.abortSource === "user" || value.abortSource === "system" || value.abortSource === "provider"
|
|
83
|
+
? { abortSource: value.abortSource }
|
|
84
|
+
: {}),
|
|
85
|
+
};
|
|
86
|
+
case "no_wake":
|
|
87
|
+
return { ...base, cause: value.cause };
|
|
88
|
+
default:
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=lifecycle-stop.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { MrtrInputResponse } from "./extensions/builtin/mcp/modern/elicitation.ts";
|
|
2
|
+
import type { ValidatedMcpFormSchema } from "./extensions/builtin/mcp/modern/form-schema.ts";
|
|
3
|
+
export interface McpFormScope {
|
|
4
|
+
readonly hostInstance: string;
|
|
5
|
+
readonly durableSessionId: string;
|
|
6
|
+
readonly bindingEpoch: string;
|
|
7
|
+
readonly attachmentId: string;
|
|
8
|
+
readonly attachmentEpoch: string;
|
|
9
|
+
readonly connectionId: string;
|
|
10
|
+
readonly connectionEpoch: string;
|
|
11
|
+
readonly presenterPrincipalId: string;
|
|
12
|
+
readonly presenterGeneration: string;
|
|
13
|
+
readonly presentationBindingId: string;
|
|
14
|
+
readonly presenterKind: "desktop" | "gateway" | "local_tui";
|
|
15
|
+
readonly purpose: "mcp_form";
|
|
16
|
+
}
|
|
17
|
+
export interface McpFormEnvelope {
|
|
18
|
+
readonly sessionId: string;
|
|
19
|
+
readonly bindingEpoch: string;
|
|
20
|
+
readonly attachmentEpoch: string;
|
|
21
|
+
readonly grantId: string;
|
|
22
|
+
readonly dialogId: string;
|
|
23
|
+
readonly dialogToken: string;
|
|
24
|
+
readonly formRevision: string;
|
|
25
|
+
}
|
|
26
|
+
export interface McpFormRequest extends McpFormEnvelope {
|
|
27
|
+
readonly type: "mcp_form_request";
|
|
28
|
+
readonly server: {
|
|
29
|
+
readonly label: string;
|
|
30
|
+
readonly source: "user" | "project" | "plugin";
|
|
31
|
+
};
|
|
32
|
+
readonly message: string;
|
|
33
|
+
readonly schema: ValidatedMcpFormSchema;
|
|
34
|
+
readonly deadlineAtMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface McpFormResolved {
|
|
37
|
+
readonly type: "mcp_form_resolved";
|
|
38
|
+
readonly sessionId: string;
|
|
39
|
+
readonly grantId: string;
|
|
40
|
+
readonly dialogId: string;
|
|
41
|
+
readonly reason: "answered" | "declined" | "cancelled" | "processing_failed" | "operation_aborted";
|
|
42
|
+
}
|
|
43
|
+
export type McpFormOutcome = ({
|
|
44
|
+
readonly kind: "result";
|
|
45
|
+
readonly result: MrtrInputResponse;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: "processing_failure" | "operation_aborted";
|
|
48
|
+
}) & ({
|
|
49
|
+
readonly source: "presenter_action";
|
|
50
|
+
readonly humanAdmission: true;
|
|
51
|
+
} | {
|
|
52
|
+
readonly source: "host_termination";
|
|
53
|
+
readonly humanAdmission: false;
|
|
54
|
+
});
|
|
55
|
+
export interface McpFormInput {
|
|
56
|
+
readonly operationId: string;
|
|
57
|
+
readonly inputId: string;
|
|
58
|
+
readonly round: number;
|
|
59
|
+
readonly server: McpFormRequest["server"];
|
|
60
|
+
readonly message: string;
|
|
61
|
+
readonly schema: ValidatedMcpFormSchema;
|
|
62
|
+
readonly operationSignal: AbortSignal;
|
|
63
|
+
/** Host reads a worker-owned atomic abort fence at the terminal latch, without an IPC round trip. */
|
|
64
|
+
readonly operationCurrent?: () => boolean;
|
|
65
|
+
readonly presentationSignal: AbortSignal;
|
|
66
|
+
/** Remaining UI budget, measured by the host's monotonic clock. */
|
|
67
|
+
readonly deadlineMs: number;
|
|
68
|
+
}
|
|
69
|
+
export interface McpFormPresentation {
|
|
70
|
+
readonly signal: AbortSignal;
|
|
71
|
+
readonly ready: () => boolean;
|
|
72
|
+
readonly present: (input: McpFormInput) => Promise<McpFormOutcome>;
|
|
73
|
+
/** Worker-backed presenters recheck host readiness before each network attempt. */
|
|
74
|
+
readonly synchronize?: () => Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
export interface McpFormBinding {
|
|
77
|
+
readonly epoch: string;
|
|
78
|
+
readonly signal: AbortSignal;
|
|
79
|
+
readonly capture: () => McpFormPresentation | undefined;
|
|
80
|
+
readonly dispose: () => void;
|
|
81
|
+
readonly version?: () => string | undefined;
|
|
82
|
+
readonly subscribe?: (listener: () => void) => () => void;
|
|
83
|
+
}
|
|
84
|
+
export declare function bindMcpFormPresented(options: object, callback: (() => void) | undefined): void;
|
|
85
|
+
export declare function markMcpFormPresented(options: object | undefined): void;
|
|
86
|
+
/** An enforced binding is part of the host-issued presenter identity, not a UI capability declaration. */
|
|
87
|
+
export declare function bindPrivateMcpForms(ui: object, binding: McpFormBinding): void;
|
|
88
|
+
export declare function privateMcpForms(ui: object): McpFormBinding | undefined;
|
|
89
|
+
//# sourceMappingURL=mcp-form-types.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getMcpMrtrPresentation, registerMcpMrtrPresentation } from "./mcp-mrtr-presentations.js";
|
|
2
|
+
const presentedCallbacks = new WeakMap();
|
|
3
|
+
export function bindMcpFormPresented(options, callback) {
|
|
4
|
+
if (callback)
|
|
5
|
+
presentedCallbacks.set(options, callback);
|
|
6
|
+
else
|
|
7
|
+
presentedCallbacks.delete(options);
|
|
8
|
+
}
|
|
9
|
+
export function markMcpFormPresented(options) {
|
|
10
|
+
if (!options)
|
|
11
|
+
return;
|
|
12
|
+
const callback = presentedCallbacks.get(options);
|
|
13
|
+
presentedCallbacks.delete(options);
|
|
14
|
+
callback?.();
|
|
15
|
+
}
|
|
16
|
+
/** An enforced binding is part of the host-issued presenter identity, not a UI capability declaration. */
|
|
17
|
+
export function bindPrivateMcpForms(ui, binding) {
|
|
18
|
+
registerMcpMrtrPresentation(ui, binding.signal, undefined, binding);
|
|
19
|
+
}
|
|
20
|
+
export function privateMcpForms(ui) {
|
|
21
|
+
return getMcpMrtrPresentation(ui)?.privateForms;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=mcp-form-types.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ExtensionUIContext } from "./extensions/types.ts";
|
|
2
|
+
import type { McpFormBinding } from "./mcp-form-types.ts";
|
|
3
|
+
/** Local host references only. RPC capabilities and serialized UI objects cannot enter this registry. */
|
|
4
|
+
export interface McpMrtrPresentation {
|
|
5
|
+
readonly ui: Pick<ExtensionUIContext, "select" | "input">;
|
|
6
|
+
readonly signal: AbortSignal;
|
|
7
|
+
readonly admit?: () => void;
|
|
8
|
+
readonly presented?: () => void;
|
|
9
|
+
readonly privateForms?: McpFormBinding;
|
|
10
|
+
}
|
|
11
|
+
export declare function registerMcpMrtrPresentation(ui: ExtensionUIContext, signal: AbortSignal, admit?: () => void, privateForms?: McpFormBinding): ExtensionUIContext;
|
|
12
|
+
export declare function getMcpMrtrPresentation(ui: object): McpMrtrPresentation | undefined;
|
|
13
|
+
/** Preserve host identity across runner wrapping; private dialogs still use the raw host UI. */
|
|
14
|
+
export declare function inheritMcpMrtrPresentation(source: ExtensionUIContext, wrapper: ExtensionUIContext): ExtensionUIContext;
|
|
15
|
+
//# sourceMappingURL=mcp-mrtr-presentations.d.ts.map
|