@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConfigReferenceError } from "@earendil-works/pi-ai/utils/config-reference";
|
|
2
2
|
import { getAgentDir } from "../../../../config.js";
|
|
3
3
|
import { redactResolvedReferenceData } from "../../../config-reference-redaction.js";
|
|
4
|
+
import { getMcpMrtrPresentation } from "../../../mcp-mrtr-presentations.js";
|
|
4
5
|
import { getToolSearchService, getToolSearchServiceForExtension, resetToolSearchServiceForTests, ToolSearchService, } from "../tool-search/service.js";
|
|
5
6
|
import { mcpCredentialIdentity } from "./auth/catalog-identity.js";
|
|
6
7
|
import { resolveAuthMode } from "./auth/context.js";
|
|
@@ -13,6 +14,8 @@ import { HostMcpRegistry } from "./host-registry.js";
|
|
|
13
14
|
import { refreshMcpInstructionsForSession } from "./instructions.js";
|
|
14
15
|
import { createMcpLogger } from "./log.js";
|
|
15
16
|
import { prepareProjectSkillServer } from "./project-skill-policy.js";
|
|
17
|
+
import { createMcpRequestOrigin } from "./request-origin.js";
|
|
18
|
+
import { McpMrtrError } from "./modern/errors.js";
|
|
16
19
|
import { reconnectMcpNow } from "./reconnect.js";
|
|
17
20
|
import { createMcpSessionConnection, disposeEntryConnection } from "./service-connection.js";
|
|
18
21
|
import { getMcpServiceExposureStatus } from "./service-exposure.js";
|
|
@@ -181,8 +184,12 @@ export class McpService {
|
|
|
181
184
|
};
|
|
182
185
|
const previous = this.#bindings.get(pi);
|
|
183
186
|
// A session-owned service serves exactly one session: a new attach takes over its binding.
|
|
184
|
-
|
|
187
|
+
previous?.lifetime.abort();
|
|
188
|
+
if (!this.#servesManySessions) {
|
|
189
|
+
for (const binding of this.#bindings.values())
|
|
190
|
+
binding.lifetime.abort();
|
|
185
191
|
this.#bindings.clear();
|
|
192
|
+
}
|
|
186
193
|
let fallbackToolSearch;
|
|
187
194
|
let holdsProcessFallback = false;
|
|
188
195
|
const sessionToolSearch = getToolSearchServiceForExtension(pi);
|
|
@@ -205,6 +212,8 @@ export class McpService {
|
|
|
205
212
|
}
|
|
206
213
|
}
|
|
207
214
|
const binding = {
|
|
215
|
+
lifetime: new AbortController(),
|
|
216
|
+
presentation: owner.context.ui === undefined ? undefined : getMcpMrtrPresentation(owner.context.ui),
|
|
208
217
|
pi,
|
|
209
218
|
fallbackToolSearch,
|
|
210
219
|
holdsProcessFallback,
|
|
@@ -224,8 +233,10 @@ export class McpService {
|
|
|
224
233
|
#liveBindings() {
|
|
225
234
|
for (const [pi, binding] of this.#bindings) {
|
|
226
235
|
// A disposed session retires its own tool-search service; its binding goes with it.
|
|
227
|
-
if (getToolSearchServiceForExtension(binding.pi)?.isDisposed === true)
|
|
236
|
+
if (getToolSearchServiceForExtension(binding.pi)?.isDisposed === true) {
|
|
237
|
+
binding.lifetime.abort();
|
|
228
238
|
this.#bindings.delete(pi);
|
|
239
|
+
}
|
|
229
240
|
}
|
|
230
241
|
return [...this.#bindings.values()];
|
|
231
242
|
}
|
|
@@ -240,6 +251,7 @@ export class McpService {
|
|
|
240
251
|
*/
|
|
241
252
|
async releaseSession(pi, disposeReason) {
|
|
242
253
|
const released = this.#bindings.get(pi);
|
|
254
|
+
released?.lifetime.abort();
|
|
243
255
|
this.#bindings.delete(pi);
|
|
244
256
|
if (disposeReason !== undefined)
|
|
245
257
|
this.#releasedSessions.add(pi);
|
|
@@ -386,7 +398,7 @@ export class McpService {
|
|
|
386
398
|
? await prepareProjectSkillServer(name, decl, {
|
|
387
399
|
context: binding.context,
|
|
388
400
|
options: binding.options,
|
|
389
|
-
ui:
|
|
401
|
+
ui: this.#elicitationProviderFor(binding),
|
|
390
402
|
notice: (identity, names) => {
|
|
391
403
|
// An untrusted project keeps the skill's references literal (senpi#2345); keep that fix in
|
|
392
404
|
// the one notice instead of a generic withheld line (senpi#3796).
|
|
@@ -527,6 +539,8 @@ export class McpService {
|
|
|
527
539
|
this.#disposed = true;
|
|
528
540
|
this.#disposeCount += 1;
|
|
529
541
|
this.#lastDisposeReason = reason;
|
|
542
|
+
for (const binding of this.#bindings.values())
|
|
543
|
+
binding.lifetime.abort();
|
|
530
544
|
this.#bindings.clear();
|
|
531
545
|
this.#sessionContext = null;
|
|
532
546
|
this.#config = null;
|
|
@@ -711,7 +725,7 @@ export class McpService {
|
|
|
711
725
|
session: options,
|
|
712
726
|
elicitationEnabled: declarer.context.mode !== "print" && declarer.context.mode !== "json",
|
|
713
727
|
cwd: declarer.context.cwd,
|
|
714
|
-
ui:
|
|
728
|
+
ui: this.#elicitationProviderFor(declarer),
|
|
715
729
|
artifacts: this.#outputArtifacts,
|
|
716
730
|
shouldReconnect: (current) => !this.#disposed &&
|
|
717
731
|
this.#entryForName(name) === current &&
|
|
@@ -813,6 +827,8 @@ export class McpService {
|
|
|
813
827
|
onRegistered: (entry, identity) => binding.registeredIdentities.set(entry.key, identity),
|
|
814
828
|
contextRequired: binding.context.mode !== undefined,
|
|
815
829
|
sessionManager: binding.context.sessionManager,
|
|
830
|
+
requestOrigin: (entry) => this.#requestOrigin(binding, entry),
|
|
831
|
+
bindPresentation: (ui) => this.bindMcpMrtrPresentation(binding.pi, ui),
|
|
816
832
|
// A new attach of this session replaces its binding (and with it its config); a peer's attach does not.
|
|
817
833
|
isCurrent: (entry) => !this.#disposed &&
|
|
818
834
|
this.#bindings.get(binding.pi) === binding &&
|
|
@@ -955,9 +971,68 @@ export class McpService {
|
|
|
955
971
|
setMcpElicitationUiProvider(provider) {
|
|
956
972
|
this.#elicitationUiProvider = provider;
|
|
957
973
|
}
|
|
974
|
+
bindMcpMrtrPresentation(pi, ui) {
|
|
975
|
+
const binding = this.#bindings.get(pi);
|
|
976
|
+
const presentation = getMcpMrtrPresentation(ui);
|
|
977
|
+
if (binding && presentation)
|
|
978
|
+
binding.presentation = presentation;
|
|
979
|
+
}
|
|
980
|
+
#requestOrigin(binding, entry) {
|
|
981
|
+
const source = binding.config.servers[entry.name]?.source;
|
|
982
|
+
return createMcpRequestOrigin({
|
|
983
|
+
binding,
|
|
984
|
+
connection: entry.connection,
|
|
985
|
+
signal: AbortSignal.any([binding.lifetime.signal, entry.connection.requestLifetimeSignal]),
|
|
986
|
+
presentation: binding.presentation,
|
|
987
|
+
privateFormsEnforced: binding.presentation?.privateForms !== undefined,
|
|
988
|
+
privateForm: binding.presentation?.privateForms?.capture(),
|
|
989
|
+
serverLabel: entry.name,
|
|
990
|
+
serverSource: source === "project" || source === "claude"
|
|
991
|
+
? "project"
|
|
992
|
+
: source === "skill" || source === "extension"
|
|
993
|
+
? "plugin"
|
|
994
|
+
: "user",
|
|
995
|
+
isCurrent: () => !this.#disposed &&
|
|
996
|
+
!binding.lifetime.signal.aborted &&
|
|
997
|
+
this.#bindings.get(binding.pi) === binding &&
|
|
998
|
+
[...this.#connections.values()].includes(entry) &&
|
|
999
|
+
declares(binding.config, entry.name, entry.configHash) &&
|
|
1000
|
+
entry.isCurrent?.() !== false &&
|
|
1001
|
+
entry.credentialsCurrent?.() !== false,
|
|
1002
|
+
resolveAuthority: async (signal) => {
|
|
1003
|
+
if (await entry.generationChanged?.())
|
|
1004
|
+
throw new McpMrtrError("MCP_MRTR_CONTEXT_CHANGED");
|
|
1005
|
+
// Same observed-revocation boundary as ordinary MCP requests. Never prompts for a new grant.
|
|
1006
|
+
return await entry.connection.projectPolicy?.resolve(signal, false);
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
958
1010
|
getMcpElicitationUi() {
|
|
959
1011
|
return this.#elicitationUiProvider?.();
|
|
960
1012
|
}
|
|
1013
|
+
#elicitationProviderFor(owner) {
|
|
1014
|
+
// Connections outlive their first declarer's extension context. Capture its host-owned
|
|
1015
|
+
// references while it is live; readiness must never read a released session's ctx.ui.
|
|
1016
|
+
const binding = [...this.#bindings.values()].find((candidate) => candidate === owner);
|
|
1017
|
+
const ui = owner.context.ui;
|
|
1018
|
+
const privateBinding = () => {
|
|
1019
|
+
// Keep observing presentation replacements on the binding, not just its initial grant.
|
|
1020
|
+
const presentation = binding
|
|
1021
|
+
? binding.presentation
|
|
1022
|
+
: ui === undefined
|
|
1023
|
+
? undefined
|
|
1024
|
+
: getMcpMrtrPresentation(ui);
|
|
1025
|
+
return presentation?.privateForms;
|
|
1026
|
+
};
|
|
1027
|
+
return Object.assign(() => this.getMcpElicitationUi(), {
|
|
1028
|
+
enforced: () => privateBinding() !== undefined,
|
|
1029
|
+
subscribe: (listener) => privateBinding()?.subscribe?.(listener) ?? (() => { }),
|
|
1030
|
+
eligible: () => {
|
|
1031
|
+
const binding = privateBinding();
|
|
1032
|
+
return binding === undefined || binding.capture()?.ready() === true;
|
|
1033
|
+
},
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
961
1036
|
isMcpPromptCommandRegistered(name) {
|
|
962
1037
|
return this.#promptCommandNames.has(name);
|
|
963
1038
|
}
|
|
@@ -9,6 +9,8 @@ import { safeInterval, safeTimer } from "./wrap.js";
|
|
|
9
9
|
/** Owns one SDK handler set, reconnect loop, cache writer and aggregate lifetime. */
|
|
10
10
|
export class SharedMcpConnection {
|
|
11
11
|
#calls;
|
|
12
|
+
#readinessListeners;
|
|
13
|
+
#readinessSubscriptions;
|
|
12
14
|
#options;
|
|
13
15
|
#onDispose;
|
|
14
16
|
#idleTimeoutMin;
|
|
@@ -22,6 +24,8 @@ export class SharedMcpConnection {
|
|
|
22
24
|
constructor(options, onDispose) {
|
|
23
25
|
this.leases = new Set();
|
|
24
26
|
this.#calls = new Map();
|
|
27
|
+
this.#readinessListeners = new Set();
|
|
28
|
+
this.#readinessSubscriptions = new Map();
|
|
25
29
|
this.#cacheGeneration = -1;
|
|
26
30
|
this.#disposed = false;
|
|
27
31
|
this.#inFlight = 0;
|
|
@@ -64,13 +68,30 @@ export class SharedMcpConnection {
|
|
|
64
68
|
},
|
|
65
69
|
},
|
|
66
70
|
logger,
|
|
67
|
-
elicitationUiProvider: () => {
|
|
71
|
+
elicitationUiProvider: Object.assign(() => {
|
|
68
72
|
// SDK server requests carry no originating tool-call id.
|
|
69
73
|
const owners = [...this.#calls.keys()];
|
|
70
74
|
return owners.length === 1 && this.leases.has(owners[0])
|
|
71
75
|
? owners[0].options.elicitationUiProvider?.()
|
|
72
76
|
: undefined;
|
|
73
|
-
},
|
|
77
|
+
}, {
|
|
78
|
+
enforced: () => [...this.leases].some((lease) => lease.options.elicitationUiProvider?.enforced?.()),
|
|
79
|
+
subscribe: (listener) => {
|
|
80
|
+
this.#readinessListeners.add(listener);
|
|
81
|
+
return () => {
|
|
82
|
+
this.#readinessListeners.delete(listener);
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
eligible: () => {
|
|
86
|
+
const leases = [...this.leases];
|
|
87
|
+
const eligible = (lease) => lease.options.elicitationEnabled !== false &&
|
|
88
|
+
lease.options.elicitationUiProvider?.eligible?.() !== false;
|
|
89
|
+
// Any ready owner permits negotiation; per-operation authority still selects the holder.
|
|
90
|
+
if (leases.some((lease) => lease.options.elicitationUiProvider?.enforced?.()))
|
|
91
|
+
return leases.some(eligible);
|
|
92
|
+
return leases.length > 0 && leases.every(eligible);
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
74
95
|
});
|
|
75
96
|
this.connection.onToolsChanged(() => {
|
|
76
97
|
this.#catalog = undefined;
|
|
@@ -112,6 +133,14 @@ export class SharedMcpConnection {
|
|
|
112
133
|
this.#clearIdle();
|
|
113
134
|
const lease = new SharedMcpLease(this, options, owner, key);
|
|
114
135
|
this.leases.add(lease);
|
|
136
|
+
const unsubscribe = options.elicitationUiProvider?.subscribe?.(() => {
|
|
137
|
+
for (const listener of this.#readinessListeners)
|
|
138
|
+
listener();
|
|
139
|
+
});
|
|
140
|
+
if (unsubscribe)
|
|
141
|
+
this.#readinessSubscriptions.set(lease, unsubscribe);
|
|
142
|
+
for (const listener of this.#readinessListeners)
|
|
143
|
+
listener();
|
|
115
144
|
this.#idleTimeoutMin = Math.max(...[...this.leases].map((item) => item.options.config.idleTimeoutMin));
|
|
116
145
|
this.#refreshKeepAlive();
|
|
117
146
|
return lease;
|
|
@@ -154,6 +183,11 @@ export class SharedMcpConnection {
|
|
|
154
183
|
// Capture the last attached policy before removing the final owner.
|
|
155
184
|
this.#idleTimeoutMin = Math.max(...[...this.leases].map((item) => item.options.config.idleTimeoutMin));
|
|
156
185
|
this.leases.delete(lease);
|
|
186
|
+
this.#readinessSubscriptions.get(lease)?.();
|
|
187
|
+
this.#readinessSubscriptions.delete(lease);
|
|
188
|
+
if (this.leases.size > 0)
|
|
189
|
+
for (const listener of this.#readinessListeners)
|
|
190
|
+
listener();
|
|
157
191
|
this.#refreshKeepAlive();
|
|
158
192
|
this.#armIdle();
|
|
159
193
|
}
|
|
@@ -9,6 +9,7 @@ export declare class SharedMcpLease extends ServerConnection {
|
|
|
9
9
|
readonly key: string;
|
|
10
10
|
readonly options: ServerConnectionOptions;
|
|
11
11
|
readonly shared: SharedMcpConnection;
|
|
12
|
+
get requestLifetimeSignal(): AbortSignal;
|
|
12
13
|
constructor(shared: SharedMcpConnection, options: ServerConnectionOptions, owner: object, key: string);
|
|
13
14
|
get state(): import("./connection-types.ts").ServerConnectionState;
|
|
14
15
|
get generation(): number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ServerConnection } from "./connection.js";
|
|
2
2
|
import { ConnectError } from "./errors.js";
|
|
3
|
+
import { registerMcpClientAlias } from "./request-origin.js";
|
|
3
4
|
const requestMethods = new Set([
|
|
4
5
|
"request",
|
|
5
6
|
"listTools",
|
|
@@ -19,6 +20,10 @@ export class SharedMcpLease extends ServerConnection {
|
|
|
19
20
|
#unsubscribeTools;
|
|
20
21
|
#stateSubscriptions = new Set();
|
|
21
22
|
#released = false;
|
|
23
|
+
#requestLifetime = new AbortController();
|
|
24
|
+
get requestLifetimeSignal() {
|
|
25
|
+
return this.#requestLifetime.signal;
|
|
26
|
+
}
|
|
22
27
|
#localGeneration = 0;
|
|
23
28
|
#client;
|
|
24
29
|
#physicalClient;
|
|
@@ -63,6 +68,7 @@ export class SharedMcpLease extends ServerConnection {
|
|
|
63
68
|
return typeof value === "function" ? value.bind(target) : value;
|
|
64
69
|
},
|
|
65
70
|
});
|
|
71
|
+
registerMcpClientAlias(this.#client, physical);
|
|
66
72
|
}
|
|
67
73
|
if (this.#client === undefined)
|
|
68
74
|
throw new ConnectError("MCP client unavailable", { phase: "client" });
|
|
@@ -79,6 +85,8 @@ export class SharedMcpLease extends ServerConnection {
|
|
|
79
85
|
}
|
|
80
86
|
async bumpGeneration() {
|
|
81
87
|
this.#assertAttached();
|
|
88
|
+
this.#requestLifetime.abort();
|
|
89
|
+
this.#requestLifetime = new AbortController();
|
|
82
90
|
this.#localGeneration++;
|
|
83
91
|
this.#refreshCatalog = true;
|
|
84
92
|
// Re-list only this owner's catalog; never interrupt another owner's call.
|
|
@@ -128,6 +136,7 @@ export class SharedMcpLease extends ServerConnection {
|
|
|
128
136
|
if (this.#released)
|
|
129
137
|
return;
|
|
130
138
|
this.#released = true;
|
|
139
|
+
this.#requestLifetime.abort();
|
|
131
140
|
this.#unsubscribeTools();
|
|
132
141
|
for (const off of this.#stateSubscriptions)
|
|
133
142
|
off();
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12
12
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
13
13
|
import type { McpOAuthProvider } from "./auth/oauth-provider.ts";
|
|
14
|
-
import {
|
|
14
|
+
import type { McpElicitationUiProvider } from "./elicitation.ts";
|
|
15
15
|
import type { McpLogger } from "./log.ts";
|
|
16
|
+
import type { ModernProtocolState } from "./modern/request.ts";
|
|
16
17
|
import { type McpAsyncErrorSink } from "./wrap.ts";
|
|
17
18
|
export type McpStdioTransportSpec = {
|
|
18
19
|
readonly projectPolicy?: boolean;
|
|
@@ -48,5 +49,5 @@ export interface MaterializeMcpTransportOptions {
|
|
|
48
49
|
/** Invoked once the stdio child has started, so the caller can capture its pid. */
|
|
49
50
|
readonly onStart: () => void;
|
|
50
51
|
}
|
|
51
|
-
export declare function materializeMcpTransport(options: MaterializeMcpTransportOptions): Promise<McpMaterializedTransport>;
|
|
52
|
+
export declare function materializeMcpTransport(options: MaterializeMcpTransportOptions, protocol?: ModernProtocolState): Promise<McpMaterializedTransport>;
|
|
52
53
|
//# sourceMappingURL=transport-sdk.d.ts.map
|
|
@@ -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
|
: {}),
|
|
@@ -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>;
|