@code-yeongyu/senpi 2026.10.24-3 → 2026.10.25-1
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 +38 -0
- package/dist/bundle/chunks/{args-6T5EQVY3.js → args-JXULI4EH.js} +1 -1
- package/dist/bundle/chunks/{chunk-VUKNGMDT.js → chunk-3LNSLJ7N.js} +1 -1
- package/dist/bundle/chunks/{chunk-MRN6IKOQ.js → chunk-3VOG6WFU.js} +1 -1
- package/dist/bundle/chunks/{chunk-AVLYZET7.js → chunk-7WAJCESX.js} +1 -1
- package/dist/bundle/chunks/{chunk-4TRKE3ZC.js → chunk-BMYNMUVZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-LR6Z6V54.js → chunk-BTNYLNWZ.js} +2 -2
- package/dist/bundle/chunks/{chunk-JAXZOEJE.js → chunk-BWA4FVFW.js} +1 -1
- package/dist/bundle/chunks/{chunk-AGQSRWNN.js → chunk-EHQXLHRB.js} +1 -1
- package/dist/bundle/chunks/{chunk-I5NIJZDY.js → chunk-EYBDBR7V.js} +1 -1
- package/dist/bundle/chunks/{chunk-YJA2B7FQ.js → chunk-FDL4CF6F.js} +1 -1
- package/dist/bundle/chunks/{chunk-HCF5M6NA.js → chunk-GXY5JEI5.js} +1 -1
- package/dist/bundle/chunks/{chunk-CYG4LS4S.js → chunk-J5P2VL3Y.js} +1 -1
- package/dist/bundle/chunks/{chunk-DYWHYQUD.js → chunk-PXKF2RHD.js} +1 -1
- package/dist/bundle/chunks/{chunk-H6IG2ITJ.js → chunk-QH2HSCMX.js} +1 -1
- package/dist/bundle/chunks/{chunk-CZYADQ2E.js → chunk-SGDOZT6B.js} +1 -1
- package/dist/bundle/chunks/{chunk-BZ75DRZB.js → chunk-U67ML6CQ.js} +4 -2
- package/dist/bundle/chunks/{chunk-DITSGW4R.js → chunk-URBJS24R.js} +75 -75
- package/dist/bundle/chunks/{chunk-CZR7C6CY.js → chunk-VOBD6FQG.js} +1 -1
- package/dist/bundle/chunks/{chunk-SGATKHO4.js → chunk-Y5OTHMXO.js} +1 -1
- package/dist/bundle/chunks/{chunk-GOB5BLDV.js → chunk-YDCRA5ES.js} +2 -2
- package/dist/bundle/chunks/{cli-main-BOYWBHOB.js → cli-main-TABBP55A.js} +1 -1
- package/dist/bundle/chunks/{dispatch-KQEGIXQW.js → dispatch-DEP4PEDZ.js} +3 -2
- package/dist/bundle/chunks/{help-fast-path-6PVN7WI5.js → help-fast-path-ZHZGXAUG.js} +1 -1
- package/dist/bundle/chunks/{host-command-7MAXSZIH.js → host-command-OQR4RDTM.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-L2UTORL5.js → host-session-command-IGB27ZPJ.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-NVBFOE5R.js → interactive-mode-DGQNSXUA.js} +1 -1
- package/dist/bundle/chunks/{list-tips-BO5RP34W.js → list-tips-CREX4IXZ.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-3TVASSQD.js → multi-session-host-M7HVUMRH.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-5BHVDVJC.js → package-manager-cli-FSR5P6YL.js} +1 -1
- package/dist/bundle/chunks/{role-BHA5ROSF.js → role-M2MTVACJ.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-RXMCJJUL.js → rotation-stream-U7YQMC7H.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-LPP46I7H.js → rpc-mode-SZAJNKKL.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-QW67J57R.js → schedule-command-XBW4EOG5.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-QQBBY5AT.js → session-control-endpoint-WEF3XTJR.js} +1 -1
- package/dist/bundle/chunks/{session-picker-HIC5ZTE6.js → session-picker-KQUFWDQC.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +98 -95
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +27 -0
- package/dist/core/agent-session.js +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/index.js +4 -2
- package/dist/core/extensions/builtin/anthropic-subscription/options.js +2 -1
- package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.js +9 -2
- package/dist/core/extensions/builtin/anthropic-subscription/session-binding.d.ts +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-boundary.d.ts +6 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-boundary.js +18 -7
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-divergence.d.ts +23 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-divergence.js +107 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-cwd.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-cwd.js +9 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-entry-annotations.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-entry-annotations.js +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-observability.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-observability.js +4 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-wiring.js +12 -7
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry.d.ts +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry.js +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/settings.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/settings.js +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/stream.js +4 -1
- package/dist/core/extensions/builtin/btw/index.js +4 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.js +3 -1
- package/dist/core/extensions/builtin/look-at/runner.js +1 -0
- package/dist/core/extensions/builtin/mcp/connection-types.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/connection.js +3 -0
- package/dist/core/extensions/builtin/mcp/elicitation.d.ts +5 -2
- package/dist/core/extensions/builtin/mcp/elicitation.js +57 -11
- package/dist/core/extensions/builtin/mcp/index.js +1 -1
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +6 -9
- package/dist/core/extensions/builtin/mcp/service-connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/service-connection.js +1 -0
- package/dist/core/extensions/builtin/mcp/service.js +1 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.js +5 -5
- package/dist/core/extensions/builtin/mcp/transport.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/transport.js +1 -0
- package/dist/core/external-admission.d.ts +4 -1
- package/dist/core/external-admission.js +18 -2
- package/dist/core/lifecycle-session-binding.js +20 -0
- package/dist/core/sdk.js +1 -0
- package/dist/core/session-spawned-by.d.ts +4 -0
- package/dist/core/session-spawned-by.js +16 -0
- package/dist/core/skill-access.js +15 -3
- package/dist/core/skill-discovery.d.ts +2 -0
- package/dist/core/skill-discovery.js +1 -1
- package/dist/main.js +7 -2
- package/dist/modes/rpc/session-router-open-profile.js +2 -10
- package/docs/usage.md +14 -0
- package/package.json +6 -6
|
@@ -4,7 +4,7 @@ export type AnnotatableRegistry = {
|
|
|
4
4
|
touch(entry: AnthropicSubscriptionSessionEntry): void;
|
|
5
5
|
};
|
|
6
6
|
export declare function switchEntryModel(registry: AnnotatableRegistry, senpiSessionId: string, modelId: string, sdkModel?: string): Promise<boolean>;
|
|
7
|
-
export declare function annotatePendingFork(registry: AnnotatableRegistry, senpiSessionId: string, reason: string): void;
|
|
7
|
+
export declare function annotatePendingFork(registry: AnnotatableRegistry, senpiSessionId: string, reason: string, divergedPath?: string): void;
|
|
8
8
|
export declare function annotateTainted(registry: AnnotatableRegistry, senpiSessionId: string, reason: string): void;
|
|
9
9
|
export declare function annotateBranchInfo(registry: AnnotatableRegistry, senpiSessionId: string, info: SessionBranchInfo): void;
|
|
10
10
|
//# sourceMappingURL=session-entry-annotations.d.ts.map
|
|
@@ -8,11 +8,13 @@ export async function switchEntryModel(registry, senpiSessionId, modelId, sdkMod
|
|
|
8
8
|
registry.touch(entry);
|
|
9
9
|
return true;
|
|
10
10
|
}
|
|
11
|
-
export function annotatePendingFork(registry, senpiSessionId, reason) {
|
|
11
|
+
export function annotatePendingFork(registry, senpiSessionId, reason, divergedPath) {
|
|
12
12
|
const entry = registry.get(senpiSessionId);
|
|
13
13
|
if (!entry)
|
|
14
14
|
return;
|
|
15
15
|
entry.pendingForkReason = reason;
|
|
16
|
+
// A fork recorded without a path (compaction, tree change) must not inherit an earlier rewrite's path.
|
|
17
|
+
entry.pendingForkDivergedPath = divergedPath ?? null;
|
|
16
18
|
registry.touch(entry);
|
|
17
19
|
}
|
|
18
20
|
export function annotateTainted(registry, senpiSessionId, reason) {
|
|
@@ -27,6 +27,8 @@ export type ContinuityObservation = {
|
|
|
27
27
|
seat?: string;
|
|
28
28
|
/** The attempt runs on a different seat than the lineage's last one: its prompt cache is cold. */
|
|
29
29
|
seatChanged?: boolean;
|
|
30
|
+
/** senpi#1975: first diverged field path behind an assistant_rewritten decision. Paths only, never values. */
|
|
31
|
+
divergedPath?: string;
|
|
30
32
|
};
|
|
31
33
|
export type ContinuityObservabilityBoundary = {
|
|
32
34
|
emit: (observation: ContinuityObservation) => void;
|
|
@@ -62,6 +64,7 @@ export declare function observeSessionSyncDecision(input: {
|
|
|
62
64
|
senpiSessionId: string;
|
|
63
65
|
payloadBytes?: number;
|
|
64
66
|
collapsedDirectives?: number;
|
|
67
|
+
divergedPath?: string;
|
|
65
68
|
}): ContinuityObservation;
|
|
66
69
|
export type StagedContinuityDecision = {
|
|
67
70
|
observation: ContinuityObservation;
|
|
@@ -133,6 +133,7 @@ export function consumePendingCloseCause(senpiSessionId) {
|
|
|
133
133
|
* incremental→delta, resume→fork, cold-seed→flatten.
|
|
134
134
|
*/
|
|
135
135
|
export function observeSessionSyncDecision(input) {
|
|
136
|
+
const diverged = input.divergedPath === undefined ? {} : { divergedPath: input.divergedPath };
|
|
136
137
|
if (input.kind === "incremental") {
|
|
137
138
|
return { kind: "delta", reason: "prefix_matched", deltaMessages: input.deltaMessages };
|
|
138
139
|
}
|
|
@@ -142,6 +143,7 @@ export function observeSessionSyncDecision(input) {
|
|
|
142
143
|
kind: "fork",
|
|
143
144
|
reason: retainedCause ?? (input.reason === undefined ? "branch_resume" : sanitizeReason(input.reason)),
|
|
144
145
|
deltaMessages: input.deltaMessages,
|
|
146
|
+
...diverged,
|
|
145
147
|
};
|
|
146
148
|
}
|
|
147
149
|
// Peek, not consume: the staged observation is emitted only when the
|
|
@@ -155,6 +157,7 @@ export function observeSessionSyncDecision(input) {
|
|
|
155
157
|
deltaMessages: input.deltaMessages,
|
|
156
158
|
...(input.payloadBytes !== undefined ? { payloadBytes: input.payloadBytes } : {}),
|
|
157
159
|
...(input.collapsedDirectives !== undefined ? { collapsedDirectives: input.collapsedDirectives } : {}),
|
|
160
|
+
...diverged,
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
/** Stages an attempt's decision; `emit` fires only when the attempt is retained. */
|
|
@@ -181,6 +184,7 @@ export function emitContinuityObservation(observation, sessionId, onDecision) {
|
|
|
181
184
|
...(observation.settingSource === undefined ? {} : { settingSource: observation.settingSource }),
|
|
182
185
|
...(observation.seat === undefined ? {} : { seat: observation.seat }),
|
|
183
186
|
...(observation.seatChanged === undefined ? {} : { seatChanged: observation.seatChanged }),
|
|
187
|
+
...(observation.divergedPath === undefined ? {} : { divergedPath: observation.divergedPath }),
|
|
184
188
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
185
189
|
});
|
|
186
190
|
}
|
|
@@ -9,17 +9,22 @@ import { bindingFromEntry, forgetBinding, getBinding, rememberBinding, rememberB
|
|
|
9
9
|
import { closeSession, getSession, recordBranchInfo, recordPendingFork, switchSessionModel, } from "./session-registry.js";
|
|
10
10
|
import { sentHashesForEntry, sentMessageHashes, sentMessages } from "./session-sync.js";
|
|
11
11
|
const commitBoundary = new AssistantCommitBoundary();
|
|
12
|
-
function persistBindingInvalidation(pi, sessionId, reason) {
|
|
13
|
-
pi.appendEntry?.(BINDING_ENTRY_TYPE, {
|
|
12
|
+
function persistBindingInvalidation(pi, sessionId, reason, divergedPath) {
|
|
13
|
+
pi.appendEntry?.(BINDING_ENTRY_TYPE, {
|
|
14
|
+
schemaVersion: 1,
|
|
15
|
+
invalidated: true,
|
|
16
|
+
reason,
|
|
17
|
+
...(divergedPath === undefined ? {} : { divergedPath }),
|
|
18
|
+
});
|
|
14
19
|
rememberBindingInvalidation(sessionId, reason);
|
|
15
20
|
}
|
|
16
|
-
async function invalidateBinding(pi, ctx, reason) {
|
|
21
|
+
async function invalidateBinding(pi, ctx, reason, divergedPath) {
|
|
17
22
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
18
23
|
forgetBinding(sessionId);
|
|
19
24
|
const sessionFile = ctx.sessionManager.getSessionFile?.();
|
|
20
25
|
if (sessionFile)
|
|
21
26
|
await deleteStoredBinding(sessionFile);
|
|
22
|
-
persistBindingInvalidation(pi, sessionId, reason);
|
|
27
|
+
persistBindingInvalidation(pi, sessionId, reason, divergedPath);
|
|
23
28
|
}
|
|
24
29
|
function keepBindingThenClose(sessionId, reason) {
|
|
25
30
|
const entry = getSession(sessionId);
|
|
@@ -129,10 +134,10 @@ export function registerSessionRegistry(pi) {
|
|
|
129
134
|
commitBoundary.forget(sessionId);
|
|
130
135
|
return;
|
|
131
136
|
}
|
|
132
|
-
const outcome = commitBoundary.commit(sessionId, event.message, modelId);
|
|
137
|
+
const { outcome, divergedPath } = commitBoundary.commit(sessionId, event.message, modelId);
|
|
133
138
|
if (outcome === "rewritten") {
|
|
134
|
-
recordPendingFork(sessionId, "assistant_rewritten");
|
|
135
|
-
await invalidateBinding(pi, ctx, "assistant_rewritten");
|
|
139
|
+
recordPendingFork(sessionId, "assistant_rewritten", divergedPath);
|
|
140
|
+
await invalidateBinding(pi, ctx, "assistant_rewritten", divergedPath);
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
143
|
// Only an assistant this provider actually produced may anchor a record.
|
|
@@ -41,6 +41,8 @@ export interface AnthropicSubscriptionSessionEntry {
|
|
|
41
41
|
taintedReason: string | null;
|
|
42
42
|
/** Wave C seam: a divergence boundary recorded for the next continuity decision. */
|
|
43
43
|
pendingForkReason: string | null;
|
|
44
|
+
/** senpi#1975: first diverged field path the commit boundary named for the pending fork, when it named one. */
|
|
45
|
+
pendingForkDivergedPath: string | null;
|
|
44
46
|
lastUsedAt: number;
|
|
45
47
|
/** Digest of the access token the resident subprocess was spawned with. */
|
|
46
48
|
credentialDigest?: string;
|
|
@@ -91,7 +93,7 @@ export declare function closeSession(senpiSessionId: string, reason: string): vo
|
|
|
91
93
|
export declare function isIdleExpired(entry: Pick<AnthropicSubscriptionSessionEntry, "lastUsedAt">): boolean;
|
|
92
94
|
export declare function markTainted(senpiSessionId: string, reason: string): void;
|
|
93
95
|
export declare function switchSessionModel(senpiSessionId: string, modelId: string, sdkModel?: string): Promise<boolean>;
|
|
94
|
-
export declare function recordPendingFork(senpiSessionId: string, reason: string): void;
|
|
96
|
+
export declare function recordPendingFork(senpiSessionId: string, reason: string, divergedPath?: string): void;
|
|
95
97
|
export declare function recordBranchInfo(senpiSessionId: string, info: SessionBranchInfo): void;
|
|
96
98
|
export declare function isCurrentGeneration(senpiSessionId: string, generation: number): boolean;
|
|
97
99
|
export declare function isBoundAccountTokenExpiring(entry: Pick<AnthropicSubscriptionSessionEntry, "accountName">, accounts: readonly Pick<AccountSlot, "name" | "expires" | "source">[]): boolean;
|
|
@@ -162,6 +162,7 @@ export class AnthropicSubscriptionSessionRegistry {
|
|
|
162
162
|
branchInfo: null,
|
|
163
163
|
taintedReason: null,
|
|
164
164
|
pendingForkReason: null,
|
|
165
|
+
pendingForkDivergedPath: null,
|
|
165
166
|
lastUsedAt: now,
|
|
166
167
|
};
|
|
167
168
|
// oxlint-disable-next-line eslint/prefer-const -- a closure reads it; const would throw in the TDZ if that closure ran before the assignment, so the rewrite is reviewed separately
|
|
@@ -250,8 +251,8 @@ export function markTainted(senpiSessionId, reason) {
|
|
|
250
251
|
export async function switchSessionModel(senpiSessionId, modelId, sdkModel) {
|
|
251
252
|
return switchEntryModel(sessionRegistry, senpiSessionId, modelId, sdkModel);
|
|
252
253
|
}
|
|
253
|
-
export function recordPendingFork(senpiSessionId, reason) {
|
|
254
|
-
annotatePendingFork(sessionRegistry, senpiSessionId, reason);
|
|
254
|
+
export function recordPendingFork(senpiSessionId, reason, divergedPath) {
|
|
255
|
+
annotatePendingFork(sessionRegistry, senpiSessionId, reason, divergedPath);
|
|
255
256
|
}
|
|
256
257
|
export function recordBranchInfo(senpiSessionId, info) {
|
|
257
258
|
annotateBranchInfo(sessionRegistry, senpiSessionId, info);
|
|
@@ -155,6 +155,7 @@ async function createResidentAttempt(input, auth) {
|
|
|
155
155
|
? flattenResult.blocks
|
|
156
156
|
: buildDeltaPromptBlocks(messages.slice(from), input.customToolNameToSdk);
|
|
157
157
|
const payloadBytes = flattenResult ? serializedPayloadBytes(flattenResult.blocks) : undefined;
|
|
158
|
+
const divergedPath = observedReason === "assistant_rewritten" ? (existing?.pendingForkDivergedPath ?? undefined) : undefined;
|
|
158
159
|
const staged = stageContinuityDecision({
|
|
159
160
|
...observeSessionSyncDecision({
|
|
160
161
|
kind: observedKind,
|
|
@@ -166,6 +167,7 @@ async function createResidentAttempt(input, auth) {
|
|
|
166
167
|
...(flattenResult?.collapsedDirectives !== undefined
|
|
167
168
|
? { collapsedDirectives: flattenResult.collapsedDirectives }
|
|
168
169
|
: {}),
|
|
170
|
+
...(divergedPath === undefined ? {} : { divergedPath }),
|
|
169
171
|
}),
|
|
170
172
|
...seat,
|
|
171
173
|
}, sessionId, input.onContinuityDecision,
|
|
@@ -42,6 +42,6 @@ export declare function resolveSystemPromptMode(settings: AnthropicSubscriptionP
|
|
|
42
42
|
/** Load the provider block with env values taking precedence over project and global values. */
|
|
43
43
|
export declare function loadAnthropicSubscriptionProviderSettings(settingsManager: SettingsManager, environment?: Environment): AnthropicSubscriptionProviderSettings;
|
|
44
44
|
/** Load settings from Senpi's configured global and project settings.json paths. */
|
|
45
|
-
export declare function loadAnthropicSubscriptionProviderSettingsFromDisk(
|
|
45
|
+
export declare function loadAnthropicSubscriptionProviderSettingsFromDisk(sessionCwd: string): AnthropicSubscriptionProviderSettings;
|
|
46
46
|
export {};
|
|
47
47
|
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getAgentDir } from "../../../../config.js";
|
|
2
2
|
import { getFileContentRevision } from "../../../../utils/paths.js";
|
|
3
3
|
import { getSettingsPath, SettingsManager } from "../../../settings-manager.js";
|
|
4
|
+
import { requireAnthropicSubscriptionCwd } from "./session-cwd.js";
|
|
4
5
|
/** The one place the default lives: senpi owns compaction unless `compactionOwner: "sdk"` opts out. */
|
|
5
6
|
export function resolveCompactionOwner(settings) {
|
|
6
7
|
return settings.compactionOwner ?? "senpi";
|
|
@@ -152,7 +153,8 @@ function settingsFingerprint(path) {
|
|
|
152
153
|
}
|
|
153
154
|
let cachedAnthropicSubscriptionManager;
|
|
154
155
|
/** Load settings from Senpi's configured global and project settings.json paths. */
|
|
155
|
-
export function loadAnthropicSubscriptionProviderSettingsFromDisk(
|
|
156
|
+
export function loadAnthropicSubscriptionProviderSettingsFromDisk(sessionCwd) {
|
|
157
|
+
const cwd = requireAnthropicSubscriptionCwd(sessionCwd);
|
|
156
158
|
// fallbackEligible() calls this per candidate probe; cache the manager by
|
|
157
159
|
// (cwd, settings content revision) and re-apply env live to avoid locked disk reads.
|
|
158
160
|
const agentDir = getAgentDir();
|
|
@@ -13,6 +13,7 @@ import { refusalError } from "./refusal.js";
|
|
|
13
13
|
import { getSdkBoundary, loadClaudeAgentSdk } from "./sdk-boundary.js";
|
|
14
14
|
import { emitContinuityObservation } from "./session-observability.js";
|
|
15
15
|
import { forgetBinding } from "./session-reattach.js";
|
|
16
|
+
import { requireAnthropicSubscriptionCwd } from "./session-cwd.js";
|
|
16
17
|
import { residentSessionMessages } from "./session-stream.js";
|
|
17
18
|
import { loadAnthropicSubscriptionProviderSettingsFromDisk, resolveCompactionOwner, resumeModeSource, } from "./settings.js";
|
|
18
19
|
import { applyStreamEvent } from "./stream-events.js";
|
|
@@ -58,6 +59,7 @@ export function streamAnthropicSubscription(model, context, options) {
|
|
|
58
59
|
let coldSeedEstimate;
|
|
59
60
|
let nativeCompactionStopped = false;
|
|
60
61
|
try {
|
|
62
|
+
const cwd = requireAnthropicSubscriptionCwd(options?.cwd);
|
|
61
63
|
// Resident before the synchronous SDK member below (getSdkBoundary().query)
|
|
62
64
|
// reads it - see sdk-boundary.lazy.ts.
|
|
63
65
|
await loadClaudeAgentSdk();
|
|
@@ -67,7 +69,7 @@ export function streamAnthropicSubscription(model, context, options) {
|
|
|
67
69
|
if (sessionKey)
|
|
68
70
|
toolWatch.reconcileWithContext(sessionKey, context);
|
|
69
71
|
const toolWatchNote = toolWatch.buildPromptNote(sessionKey, context, resolvedTools.customToolNameToSdk);
|
|
70
|
-
const providerSettings = loadAnthropicSubscriptionProviderSettingsFromDisk(
|
|
72
|
+
const providerSettings = loadAnthropicSubscriptionProviderSettingsFromDisk(cwd);
|
|
71
73
|
const senpiOwnsCompaction = resolveCompactionOwner(providerSettings) === "senpi";
|
|
72
74
|
const toolLessRequest = options?.toolChoice === "none";
|
|
73
75
|
const mcpServers = toolLessRequest ? undefined : await buildCustomToolServers(resolvedTools.customTools);
|
|
@@ -79,6 +81,7 @@ export function streamAnthropicSubscription(model, context, options) {
|
|
|
79
81
|
model,
|
|
80
82
|
context,
|
|
81
83
|
streamOptions: options,
|
|
84
|
+
cwd,
|
|
82
85
|
providerSettings,
|
|
83
86
|
authLane,
|
|
84
87
|
tools: resolvedTools.sdkTools,
|
|
@@ -103,7 +103,10 @@ export default function btwExtension(pi) {
|
|
|
103
103
|
},
|
|
104
104
|
sessionId,
|
|
105
105
|
thinkingLevel: thinkingLevel === "off" ? undefined : thinkingLevel,
|
|
106
|
-
streamFn: (streamModel, streamContext, options) => ctx.modelRegistry.modelRuntime.streamSimple(streamModel, streamContext,
|
|
106
|
+
streamFn: (streamModel, streamContext, options) => ctx.modelRegistry.modelRuntime.streamSimple(streamModel, streamContext, {
|
|
107
|
+
...options,
|
|
108
|
+
cwd: ctx.cwd,
|
|
109
|
+
}),
|
|
107
110
|
}, context, {
|
|
108
111
|
signal: controller.signal,
|
|
109
112
|
onTextDelta: (delta) => {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "../anthropic-subscription/account-management.js";
|
|
24
24
|
import { isColdSeedOverflowMessage } from "../anthropic-subscription/cold-seed-budget.js";
|
|
25
|
+
import { requireAnthropicSubscriptionCwd } from "../anthropic-subscription/session-cwd.js";
|
|
25
26
|
import { loadAnthropicSubscriptionProviderSettingsFromDisk, resolveCompactionOwner, } from "../anthropic-subscription/settings.js";
|
|
26
27
|
/** Custom session entry type carrying a mirrored SDK compaction boundary. */
|
|
27
28
|
export const ANTHROPIC_SUBSCRIPTION_COMPACT_ENTRY_TYPE = "claude-sdk-oauth-compact";
|
|
@@ -76,8 +77,9 @@ export function createCompactionLanePolicy(options = {}) {
|
|
|
76
77
|
// undefined when the settings cannot be read, so callers fail closed to
|
|
77
78
|
// senpi's full behavior.
|
|
78
79
|
const readProviderSettings = (cwd) => {
|
|
80
|
+
const sessionCwd = requireAnthropicSubscriptionCwd(cwd);
|
|
79
81
|
try {
|
|
80
|
-
return load(
|
|
82
|
+
return load(sessionCwd);
|
|
81
83
|
}
|
|
82
84
|
catch {
|
|
83
85
|
return undefined;
|
|
@@ -33,6 +33,7 @@ export async function runLookAt(args, signal, ctx, store, dependencies = {}) {
|
|
|
33
33
|
((model, context, options) => ctx.modelRegistry.modelRuntime.streamSimple(model, context, options));
|
|
34
34
|
const reasoning = toStreamReasoning(resolved.thinkingLevel);
|
|
35
35
|
const response = await streamRunner(resolved.model, { systemPrompt: LOOK_AT_SYSTEM_PROMPT, messages: [userMessage] }, {
|
|
36
|
+
cwd: ctx.cwd,
|
|
36
37
|
...(reasoning === undefined ? {} : { reasoning }),
|
|
37
38
|
maxTokens: 4096,
|
|
38
39
|
signal: request.signal,
|
|
@@ -29,6 +29,8 @@ export interface ServerConnectionOptions {
|
|
|
29
29
|
readonly env?: Record<string, string | undefined>;
|
|
30
30
|
readonly authProvider?: McpOAuthProvider;
|
|
31
31
|
readonly elicitationUiProvider?: McpElicitationUiProvider;
|
|
32
|
+
/** Print/json processes cannot present elicitation; UI-less lessees fail independently. */
|
|
33
|
+
readonly elicitationEnabled?: boolean;
|
|
32
34
|
/** Present when the server declares references: every new transport re-reads them (senpi#3858). */
|
|
33
35
|
readonly resolveConfig?: () => Promise<McpServerConfig>;
|
|
34
36
|
/** Called when a shared renewal finds this owner's generation changed, so the owner re-keys. */
|
|
@@ -14,6 +14,7 @@ export class ServerConnection {
|
|
|
14
14
|
#config;
|
|
15
15
|
#authProvider;
|
|
16
16
|
#elicitationUiProvider;
|
|
17
|
+
#elicitationEnabled;
|
|
17
18
|
#resolveConfig;
|
|
18
19
|
#projectPolicy;
|
|
19
20
|
#policyAbort;
|
|
@@ -34,6 +35,7 @@ export class ServerConnection {
|
|
|
34
35
|
this.#env = options.env;
|
|
35
36
|
this.#authProvider = options.authProvider;
|
|
36
37
|
this.#elicitationUiProvider = options.elicitationUiProvider;
|
|
38
|
+
this.#elicitationEnabled = options.elicitationEnabled;
|
|
37
39
|
this.#resolveConfig = options.resolveConfig;
|
|
38
40
|
this.#projectPolicy = options.projectPolicy;
|
|
39
41
|
this.#startedGrantNames = options.projectPolicy?.initial.grantedNames ?? [];
|
|
@@ -239,6 +241,7 @@ export class ServerConnection {
|
|
|
239
241
|
return current;
|
|
240
242
|
},
|
|
241
243
|
elicitationUiProvider: this.#elicitationUiProvider,
|
|
244
|
+
elicitationEnabled: this.#elicitationEnabled,
|
|
242
245
|
logger: this.#logger,
|
|
243
246
|
resolveConfig: this.#resolveConfig,
|
|
244
247
|
serverName: this.serverName,
|
|
@@ -4,8 +4,11 @@ import type { ExtensionUIContext } from "../../types.ts";
|
|
|
4
4
|
export declare const MCP_CLIENT_ELICITATION_CAPABILITY: {
|
|
5
5
|
readonly elicitation: {};
|
|
6
6
|
};
|
|
7
|
+
export declare function mcpClientCapabilities(elicitationEnabled?: boolean): {
|
|
8
|
+
elicitation?: Record<string, never>;
|
|
9
|
+
};
|
|
7
10
|
export declare const MCP_ELICITATION_TIMEOUT_MS: number;
|
|
8
|
-
type ElicitationUi = Pick<ExtensionUIContext, "input" | "select"
|
|
11
|
+
type ElicitationUi = Pick<ExtensionUIContext, "input" | "select">;
|
|
9
12
|
export type McpElicitationUiProvider = () => ElicitationUi | undefined;
|
|
10
13
|
export interface McpElicitationUiOwner {
|
|
11
14
|
getMcpElicitationUi(): ElicitationUi | undefined;
|
|
@@ -29,7 +32,7 @@ export declare function setMcpElicitationUiProvider(provider: McpElicitationUiPr
|
|
|
29
32
|
* in `transport-sdk.ts`) awaits it before any connect, so a mid-call
|
|
30
33
|
* elicitation still cannot race registration.
|
|
31
34
|
*/
|
|
32
|
-
export declare function configureMcpElicitation(client: Client, owner?: McpElicitationUiOwner | McpElicitationUiProvider | undefined, timeoutMs?: number): Promise<void>;
|
|
35
|
+
export declare function configureMcpElicitation(client: Client, owner?: McpElicitationUiOwner | McpElicitationUiProvider | undefined, timeoutMs?: number, elicitationEnabled?: boolean): Promise<void>;
|
|
33
36
|
/** Exported for tests: drive the form directly with a scripted UI. */
|
|
34
37
|
export declare function runElicitationForm(ui: ElicitationUi, message: string, requestedSchema: {
|
|
35
38
|
properties?: Record<string, ElicitProperty>;
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// The client declares the capability as an EMPTY object — Spring-AI servers
|
|
4
4
|
// reject richer shapes (FieldCoding lesson from the comparison corpus) — and
|
|
5
5
|
// answers elicitation/create requests mid tool-call by walking the requested
|
|
6
|
-
// flat-primitive schema through sequential ctx.ui primitives (input/select
|
|
7
|
-
//
|
|
8
|
-
// (print mode) the request
|
|
6
|
+
// flat-primitive schema through sequential ctx.ui primitives (input/select;
|
|
7
|
+
// NEVER ctx.ui.custom, which is undefined in RPC mode). Without a UI
|
|
8
|
+
// (print mode) the request fails with a processing error, never a user refusal.
|
|
9
9
|
// The whole form is bounded by a timeout that resolves to cancel so a wedged
|
|
10
10
|
// dialog can never wedge the tool-call pipeline. URL mode is deliberately not
|
|
11
11
|
// implemented in v1.
|
|
@@ -14,28 +14,46 @@ import { loadMcpSdkTypes } from "./sdk.lazy.js";
|
|
|
14
14
|
import { safeTimer } from "./wrap.js";
|
|
15
15
|
/** Declared empty on purpose: maximum server compatibility (form mode only). */
|
|
16
16
|
export const MCP_CLIENT_ELICITATION_CAPABILITY = { elicitation: {} };
|
|
17
|
+
export function mcpClientCapabilities(elicitationEnabled = true) {
|
|
18
|
+
return elicitationEnabled ? MCP_CLIENT_ELICITATION_CAPABILITY : {};
|
|
19
|
+
}
|
|
17
20
|
export const MCP_ELICITATION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
18
21
|
let legacyUiProvider;
|
|
19
22
|
/** Legacy classic-session seam. Session-owned services pass themselves directly. */
|
|
20
23
|
export function setMcpElicitationUiProvider(provider) {
|
|
21
24
|
legacyUiProvider = provider;
|
|
22
25
|
}
|
|
26
|
+
async function rejectElicitation() {
|
|
27
|
+
const { ErrorCode, McpError } = await loadMcpSdkTypes();
|
|
28
|
+
throw new McpError(ErrorCode.InternalError, "Elicitation cannot be processed");
|
|
29
|
+
}
|
|
23
30
|
/**
|
|
24
31
|
* Wire the elicitation/create handler onto a fresh client. Async because the
|
|
25
32
|
* request schema comes from the lazily loaded SDK; the caller (`buildMcpClient`
|
|
26
33
|
* in `transport-sdk.ts`) awaits it before any connect, so a mid-call
|
|
27
34
|
* elicitation still cannot race registration.
|
|
28
35
|
*/
|
|
29
|
-
export async function configureMcpElicitation(client, owner = legacyUiProvider, timeoutMs = MCP_ELICITATION_TIMEOUT_MS) {
|
|
36
|
+
export async function configureMcpElicitation(client, owner = legacyUiProvider, timeoutMs = MCP_ELICITATION_TIMEOUT_MS, elicitationEnabled = true) {
|
|
37
|
+
if (!elicitationEnabled) {
|
|
38
|
+
// The SDK requires an advertised capability for setRequestHandler.
|
|
39
|
+
// Its public fallback handles unsolicited requests without claiming UI support.
|
|
40
|
+
const { ErrorCode, McpError } = await loadMcpSdkTypes();
|
|
41
|
+
client.fallbackRequestHandler = async (request) => {
|
|
42
|
+
if (request.method === "elicitation/create")
|
|
43
|
+
return rejectElicitation();
|
|
44
|
+
throw new McpError(ErrorCode.MethodNotFound, "Method not found");
|
|
45
|
+
};
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
30
48
|
const { ElicitRequestSchema } = await loadMcpSdkTypes();
|
|
31
49
|
client.setRequestHandler(ElicitRequestSchema, async (request) => {
|
|
32
50
|
const params = request.params;
|
|
33
51
|
// URL mode is deliberately unsupported in v1 (form mode only).
|
|
34
52
|
if (!("requestedSchema" in params))
|
|
35
|
-
return
|
|
53
|
+
return rejectElicitation();
|
|
36
54
|
const ui = typeof owner === "function" ? owner() : owner?.getMcpElicitationUi();
|
|
37
55
|
if (ui === undefined)
|
|
38
|
-
return
|
|
56
|
+
return rejectElicitation();
|
|
39
57
|
return await runElicitationForm(ui, params.message, params.requestedSchema, timeoutMs);
|
|
40
58
|
});
|
|
41
59
|
}
|
|
@@ -61,31 +79,59 @@ async function collectForm(ui, message, requestedSchema) {
|
|
|
61
79
|
for (const [name, property] of Object.entries(requestedSchema.properties ?? {})) {
|
|
62
80
|
const title = `${message} - ${property.title ?? name}`;
|
|
63
81
|
if (property.type === "boolean") {
|
|
64
|
-
|
|
82
|
+
// confirm collapses cancellation into false, including automated RPC replies.
|
|
83
|
+
// A selection preserves dismissal separately from a human's explicit No.
|
|
84
|
+
const picked = await ui.select(property.description ? `${title}\n${property.description}` : title, [
|
|
85
|
+
"Yes",
|
|
86
|
+
"No",
|
|
87
|
+
]);
|
|
88
|
+
if (picked === undefined)
|
|
89
|
+
return { action: "cancel" };
|
|
90
|
+
// The thread gateway also accepts y/n and true/false, ignoring case and whitespace.
|
|
91
|
+
switch (picked.trim().toLowerCase()) {
|
|
92
|
+
case "yes":
|
|
93
|
+
case "y":
|
|
94
|
+
case "true":
|
|
95
|
+
content[name] = true;
|
|
96
|
+
break;
|
|
97
|
+
case "no":
|
|
98
|
+
case "n":
|
|
99
|
+
case "false":
|
|
100
|
+
content[name] = false;
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
return rejectElicitation();
|
|
104
|
+
}
|
|
65
105
|
continue;
|
|
66
106
|
}
|
|
67
107
|
if (Array.isArray(property.enum) && property.enum.length > 0) {
|
|
68
108
|
const picked = await ui.select(title, property.enum.map(String));
|
|
69
109
|
if (picked === undefined)
|
|
70
|
-
return { action: "
|
|
110
|
+
return { action: "cancel" };
|
|
71
111
|
content[name] = picked;
|
|
72
112
|
continue;
|
|
73
113
|
}
|
|
74
114
|
const answer = await ui.input(title, property.description);
|
|
75
|
-
if (answer === undefined
|
|
115
|
+
if (answer === undefined)
|
|
116
|
+
return { action: "cancel" };
|
|
117
|
+
if (answer.length === 0) {
|
|
76
118
|
if (required.has(name))
|
|
77
|
-
return
|
|
119
|
+
return rejectElicitation();
|
|
78
120
|
continue;
|
|
79
121
|
}
|
|
80
122
|
if (property.type === "number" || property.type === "integer") {
|
|
81
123
|
const parsed = Number(answer);
|
|
82
124
|
if (!Number.isFinite(parsed))
|
|
83
|
-
return
|
|
125
|
+
return rejectElicitation();
|
|
84
126
|
content[name] = parsed;
|
|
85
127
|
continue;
|
|
86
128
|
}
|
|
87
129
|
content[name] = answer;
|
|
88
130
|
}
|
|
131
|
+
// Even a fieldless form needs an explicit submission rather than an automatic accept.
|
|
132
|
+
if (Object.keys(content).length === 0 && (await ui.select(message, ["Submit"])) !== "Submit") {
|
|
133
|
+
return { action: "cancel" };
|
|
134
|
+
}
|
|
89
135
|
return { action: "accept", content };
|
|
90
136
|
}
|
|
91
137
|
//# sourceMappingURL=elicitation.js.map
|
|
@@ -123,7 +123,7 @@ export function createMcpExtension(service, sessionOwned = true) {
|
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
125
|
// Elicitation (todo 41): point mid-call forms at this session's UI.
|
|
126
|
-
service.setMcpElicitationUiProvider(() => ctx.ui);
|
|
126
|
+
service.setMcpElicitationUiProvider(() => (ctx.hasUI ? ctx.ui : undefined));
|
|
127
127
|
await (attachPromise ?? attach({ type: "session_start", reason: "startup" }, ctx));
|
|
128
128
|
}
|
|
129
129
|
const skills = preview ? [] : (event.systemPromptOptions.skills ?? []);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { configureMcpElicitation,
|
|
1
|
+
import { configureMcpElicitation, mcpClientCapabilities } from "../elicitation.js";
|
|
2
2
|
import { ConnectError } from "../errors.js";
|
|
3
3
|
import { loadMcpSdkClient } from "../sdk.lazy.js";
|
|
4
4
|
import { materializeMcpTransport, } from "../transport-sdk.js";
|
|
@@ -9,13 +9,10 @@ import { probeStdioTransport } from "./stdio-probe.js";
|
|
|
9
9
|
import { ModernHttpTransport } from "./transport.js";
|
|
10
10
|
import { MODERN_PROTOCOL_VERSION } from "./wire.js";
|
|
11
11
|
const clientInfo = { name: "senpi-mcp-client", version: "0.0.0" };
|
|
12
|
-
const probeOptions = {
|
|
13
|
-
clientInfo,
|
|
14
|
-
clientCapabilities: MCP_CLIENT_ELICITATION_CAPABILITY,
|
|
15
|
-
supportedVersions: [MODERN_PROTOCOL_VERSION],
|
|
16
|
-
};
|
|
17
12
|
export async function materializeNegotiatedTransport(options, serverName, timeoutMs, signal) {
|
|
18
13
|
signal.throwIfAborted();
|
|
14
|
+
const clientCapabilities = mcpClientCapabilities(options.elicitationEnabled);
|
|
15
|
+
const probeOptions = { clientInfo, clientCapabilities, supportedVersions: [MODERN_PROTOCOL_VERSION] };
|
|
19
16
|
const { spec, logger } = options;
|
|
20
17
|
let outcome = { kind: "legacy-fallback" };
|
|
21
18
|
if (spec.kind === "http") {
|
|
@@ -32,13 +29,13 @@ export async function materializeNegotiatedTransport(options, serverName, timeou
|
|
|
32
29
|
});
|
|
33
30
|
}
|
|
34
31
|
const { Client } = await loadMcpSdkClient();
|
|
35
|
-
const client = new Client(clientInfo, { capabilities:
|
|
36
|
-
await configureMcpElicitation(client, options.elicitationUiProvider);
|
|
32
|
+
const client = new Client(clientInfo, { capabilities: clientCapabilities });
|
|
33
|
+
await configureMcpElicitation(client, options.elicitationUiProvider, undefined, options.elicitationEnabled);
|
|
37
34
|
const transport = new ModernSdkBridge(new ModernHttpTransport({
|
|
38
35
|
url: spec.url,
|
|
39
36
|
fetch,
|
|
40
37
|
clientInfo,
|
|
41
|
-
clientCapabilities
|
|
38
|
+
clientCapabilities,
|
|
42
39
|
protocolVersion: outcome.version,
|
|
43
40
|
}), outcome.discovery, outcome.version);
|
|
44
41
|
const version = outcome.version;
|
|
@@ -14,6 +14,7 @@ interface SessionConnectionOptions {
|
|
|
14
14
|
readonly generation: McpConfigGeneration;
|
|
15
15
|
readonly declared: McpServerDeclaredConfig;
|
|
16
16
|
readonly session: McpSessionOptions;
|
|
17
|
+
readonly elicitationEnabled: boolean;
|
|
17
18
|
readonly cwd: string;
|
|
18
19
|
readonly ui: McpElicitationUiProvider;
|
|
19
20
|
readonly artifacts: McpOutputArtifacts;
|
|
@@ -38,6 +38,7 @@ export function createMcpSessionConnection(options) {
|
|
|
38
38
|
config,
|
|
39
39
|
env: projectPolicy?.initial.env ?? session.env,
|
|
40
40
|
elicitationUiProvider: options.ui,
|
|
41
|
+
elicitationEnabled: options.elicitationEnabled,
|
|
41
42
|
logger,
|
|
42
43
|
serverName: name,
|
|
43
44
|
...(renewal === undefined ? {} : { resolveConfig: renewal.resolveConfig, onGenerationChanged: rekey }),
|
|
@@ -709,6 +709,7 @@ export class McpService {
|
|
|
709
709
|
await this.#resyncToLiveSessions(owner);
|
|
710
710
|
},
|
|
711
711
|
session: options,
|
|
712
|
+
elicitationEnabled: declarer.context.mode !== "print" && declarer.context.mode !== "json",
|
|
712
713
|
cwd: declarer.context.cwd,
|
|
713
714
|
ui: () => this.getMcpElicitationUi(),
|
|
714
715
|
artifacts: this.#outputArtifacts,
|
|
@@ -44,6 +44,7 @@ export interface MaterializeMcpTransportOptions {
|
|
|
44
44
|
readonly logger: McpLogger;
|
|
45
45
|
readonly sink: McpAsyncErrorSink;
|
|
46
46
|
readonly elicitationUiProvider: McpElicitationUiProvider | undefined;
|
|
47
|
+
readonly elicitationEnabled?: boolean;
|
|
47
48
|
/** Invoked once the stdio child has started, so the caller can capture its pid. */
|
|
48
49
|
readonly onStart: () => void;
|
|
49
50
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { configureMcpElicitation,
|
|
1
|
+
import { configureMcpElicitation, mcpClientCapabilities } from "./elicitation.js";
|
|
2
2
|
import { ProjectStdioTransport } from "./project-stdio-transport.js";
|
|
3
3
|
import { loadMcpSdkClient, loadMcpSdkStdioTransport, loadMcpSdkStreamableHttpTransport } from "./sdk.lazy.js";
|
|
4
4
|
import { safeOn } from "./wrap.js";
|
|
5
5
|
export async function materializeMcpTransport(options) {
|
|
6
|
-
const client = await buildMcpClient(options.elicitationUiProvider);
|
|
6
|
+
const client = await buildMcpClient(options.elicitationUiProvider, options.elicitationEnabled);
|
|
7
7
|
if (options.spec.kind === "stdio")
|
|
8
8
|
return materializeStdio(options.spec, options, client);
|
|
9
9
|
return materializeHttp(options.spec, client);
|
|
@@ -48,13 +48,13 @@ async function materializeHttp(spec, client) {
|
|
|
48
48
|
transport,
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
-
async function buildMcpClient(elicitationUiProvider) {
|
|
51
|
+
async function buildMcpClient(elicitationUiProvider, elicitationEnabled = true) {
|
|
52
52
|
// Elicitation capability is declared EMPTY on purpose (form mode only;
|
|
53
53
|
// Spring-AI servers reject richer shapes) and the create-handler is wired
|
|
54
54
|
// before any connect so mid-call requests never race registration.
|
|
55
55
|
const { Client: McpClient } = await loadMcpSdkClient();
|
|
56
|
-
const client = new McpClient({ name: "senpi-mcp-client", version: "0.0.0" }, { capabilities:
|
|
57
|
-
await configureMcpElicitation(client, elicitationUiProvider);
|
|
56
|
+
const client = new McpClient({ name: "senpi-mcp-client", version: "0.0.0" }, { capabilities: mcpClientCapabilities(elicitationEnabled) });
|
|
57
|
+
await configureMcpElicitation(client, elicitationUiProvider, undefined, elicitationEnabled);
|
|
58
58
|
return client;
|
|
59
59
|
}
|
|
60
60
|
// OMP pattern: hand stdio OAuth servers the current access token via env.
|
|
@@ -32,6 +32,7 @@ export type CreateMcpTransportOptions = {
|
|
|
32
32
|
readonly logger: McpLogger;
|
|
33
33
|
readonly env?: Record<string, string | undefined>;
|
|
34
34
|
readonly elicitationUiProvider?: McpElicitationUiProvider;
|
|
35
|
+
readonly elicitationEnabled?: boolean;
|
|
35
36
|
readonly authProvider?: McpOAuthProvider;
|
|
36
37
|
/** Re-reads a declaring server's references for this transport (senpi#3858). */
|
|
37
38
|
readonly resolveConfig?: () => Promise<McpServerConfig>;
|
|
@@ -91,6 +91,7 @@ function createConnection(options, spec, connectTimeoutMs) {
|
|
|
91
91
|
materialize: async () => {
|
|
92
92
|
const materializeWith = (current) => import("./modern/dispatch.js").then(({ materializeNegotiatedTransport }) => materializeNegotiatedTransport({
|
|
93
93
|
elicitationUiProvider: options.elicitationUiProvider,
|
|
94
|
+
elicitationEnabled: options.elicitationEnabled,
|
|
94
95
|
logger: options.logger,
|
|
95
96
|
onStart: captureRootPid,
|
|
96
97
|
sink: asyncErrorSink,
|
|
@@ -60,6 +60,8 @@ export declare class ExternalAdmission {
|
|
|
60
60
|
}): InputHold;
|
|
61
61
|
onInputsSettled(listener: () => void): () => void;
|
|
62
62
|
onEmitted(listener: (deliveryId: string) => void): () => void;
|
|
63
|
+
/** Durable custody will replay this delivery: reserve its id before asynchronous recovery or gateway admission. */
|
|
64
|
+
restoreQueued(deliveryId: string, isOwned: () => boolean): void;
|
|
63
65
|
/**
|
|
64
66
|
* From here on every `admit` throws `reason` and changes nothing: the runtime is being handed to
|
|
65
67
|
* another writer, so the delivery stays with its sender and is delivered there instead.
|
|
@@ -82,8 +84,9 @@ export declare class ExternalAdmission {
|
|
|
82
84
|
* deliveries may be admitted again - unless the file's last write was refused too.
|
|
83
85
|
*/
|
|
84
86
|
observeRunSettled(): void;
|
|
85
|
-
/**
|
|
87
|
+
/** Cleared carriers are no longer held; custody not yet enqueued for recovery still owns its delivery. */
|
|
86
88
|
dropQueued(): void;
|
|
89
|
+
private releaseRestored;
|
|
87
90
|
private endHold;
|
|
88
91
|
private isBusy;
|
|
89
92
|
}
|