@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12
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 +53 -0
- package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
- package/dist/cli.js +10775 -10762
- package/dist/types/advisor/runtime.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/custom-models.d.ts +31 -0
- package/dist/types/config/model-config-values.d.ts +19 -0
- package/dist/types/config/model-patch.d.ts +93 -0
- package/dist/types/config/model-provider-discovery.d.ts +51 -0
- package/dist/types/config/model-registry.d.ts +9 -52
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/config/settings.d.ts +5 -3
- package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
- package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +33 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/eval/executor-base.d.ts +8 -2
- package/dist/types/eval/kernel-session-registry.d.ts +60 -0
- package/dist/types/export/share.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/lsp/diagnostics.d.ts +96 -0
- package/dist/types/lsp/index.d.ts +7 -128
- package/dist/types/lsp/servers.d.ts +72 -0
- package/dist/types/lsp/tool.d.ts +42 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
- package/dist/types/lsp/writethrough.d.ts +33 -0
- package/dist/types/mcp/transports/header-policy.d.ts +46 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
- package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
- package/dist/types/modes/components/agent-hub.d.ts +2 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/theme/color.d.ts +19 -0
- package/dist/types/modes/theme/loader.d.ts +19 -0
- package/dist/types/modes/theme/schema.d.ts +175 -0
- package/dist/types/modes/theme/symbols.d.ts +28 -0
- package/dist/types/modes/theme/theme-class.d.ts +244 -0
- package/dist/types/modes/theme/theme.d.ts +9 -280
- package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +1 -3
- package/dist/types/secrets/index.d.ts +26 -2
- package/dist/types/secrets/message-transform.d.ts +40 -0
- package/dist/types/secrets/obfuscator.d.ts +0 -108
- package/dist/types/secrets/placeholder-scan.d.ts +95 -0
- package/dist/types/secrets/placeholder.d.ts +94 -0
- package/dist/types/secrets/replacement.d.ts +82 -0
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session-types.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-handoff.d.ts +3 -3
- package/dist/types/session/session-manager.d.ts +32 -0
- package/dist/types/session/session-provider-boundary.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +8 -0
- package/dist/types/session/turn-recovery.d.ts +9 -4
- package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
- package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
- package/dist/types/slash-commands/builtin-control.d.ts +2 -0
- package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
- package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
- package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/slash-commands/builtin-session.d.ts +2 -0
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/tools/gh-common.d.ts +69 -0
- package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
- package/dist/types/tools/gh-pr-diff.d.ts +102 -0
- package/dist/types/tools/gh-run-watch.d.ts +78 -0
- package/dist/types/tools/gh-search.d.ts +61 -0
- package/dist/types/tools/gh-types.d.ts +340 -0
- package/dist/types/tools/gh-view.d.ts +88 -0
- package/dist/types/tools/gh.d.ts +4 -201
- package/dist/types/tools/read-archive.d.ts +16 -0
- package/dist/types/tools/read-format.d.ts +104 -0
- package/dist/types/tools/read-path-resolution.d.ts +19 -0
- package/dist/types/tools/read-pdf-images.d.ts +12 -0
- package/dist/types/tools/read-renderer.d.ts +24 -0
- package/dist/types/tools/read-selector.d.ts +27 -0
- package/dist/types/tools/read-sqlite.d.ts +16 -0
- package/dist/types/tools/read-summary.d.ts +19 -0
- package/dist/types/tools/read.d.ts +1 -23
- package/dist/types/tools/shell-tokenize.d.ts +18 -1
- package/dist/types/utils/changelog.d.ts +0 -5
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +12 -7
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/share.ts +71 -0
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/custom-models.ts +188 -0
- package/src/config/model-config-values.ts +128 -0
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-patch.ts +252 -0
- package/src/config/model-provider-discovery.ts +132 -0
- package/src/config/model-registry.ts +84 -704
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +53 -3
- package/src/debug/raw-sse-buffer.ts +20 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +75 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/edit/hashline/diff.ts +5 -1
- package/src/eval/executor-base.ts +39 -6
- package/src/eval/jl/executor.ts +82 -371
- package/src/eval/kernel-session-registry.ts +398 -0
- package/src/eval/py/executor.ts +53 -261
- package/src/eval/rb/executor.ts +36 -279
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/export/share.ts +2 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +78 -14
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +12 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/lsp/diagnostics.ts +516 -0
- package/src/lsp/index.ts +20 -2819
- package/src/lsp/servers.ts +296 -0
- package/src/lsp/tool.ts +1352 -0
- package/src/lsp/workspace-diagnostics.ts +170 -0
- package/src/lsp/writethrough.ts +561 -0
- package/src/main.ts +3 -2
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +15 -4
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +2 -2
- package/src/modes/components/agent-hub-renderer.ts +3 -7
- package/src/modes/components/agent-hub.ts +5 -0
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +6 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +8 -2
- package/src/modes/controllers/event-controller.ts +15 -15
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/theme/color.ts +133 -0
- package/src/modes/theme/loader.ts +178 -0
- package/src/modes/theme/schema.ts +263 -0
- package/src/modes/theme/symbols.ts +1000 -0
- package/src/modes/theme/theme-class.ts +611 -0
- package/src/modes/theme/theme.ts +27 -2453
- package/src/modes/theme/tui-adapters.ts +279 -0
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/registry/agent-registry.ts +2 -2
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +51 -6
- package/src/secrets/message-transform.ts +287 -0
- package/src/secrets/obfuscator.ts +39 -1303
- package/src/secrets/placeholder-scan.ts +506 -0
- package/src/secrets/placeholder.ts +309 -0
- package/src/secrets/replacement.ts +216 -0
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +146 -21
- package/src/session/indexed-session-storage.ts +7 -2
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-advisors.ts +32 -34
- package/src/session/session-context.ts +3 -5
- package/src/session/session-handoff.ts +39 -16
- package/src/session/session-manager.ts +67 -3
- package/src/session/session-provider-boundary.ts +3 -6
- package/src/session/session-tools.ts +37 -3
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +115 -84
- package/src/slash-commands/builtin-collaboration.ts +504 -0
- package/src/slash-commands/builtin-completions.ts +291 -0
- package/src/slash-commands/builtin-control.ts +78 -0
- package/src/slash-commands/builtin-lifecycle.ts +506 -0
- package/src/slash-commands/builtin-marketplace.ts +567 -0
- package/src/slash-commands/builtin-modes.ts +518 -0
- package/src/slash-commands/builtin-registry.ts +21 -2996
- package/src/slash-commands/builtin-session.ts +592 -0
- package/src/task/executor.ts +17 -14
- package/src/task/index.ts +21 -6
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +10 -11
- package/src/tools/debug.ts +1 -1
- package/src/tools/gh-common.ts +288 -0
- package/src/tools/gh-pr-checkout.ts +679 -0
- package/src/tools/gh-pr-diff.ts +473 -0
- package/src/tools/gh-run-watch.ts +1015 -0
- package/src/tools/gh-search.ts +500 -0
- package/src/tools/gh-types.ts +379 -0
- package/src/tools/gh-view.ts +612 -0
- package/src/tools/gh.ts +109 -3821
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/read-archive.ts +209 -0
- package/src/tools/read-format.ts +593 -0
- package/src/tools/read-path-resolution.ts +36 -0
- package/src/tools/read-pdf-images.ts +250 -0
- package/src/tools/read-renderer.ts +289 -0
- package/src/tools/read-selector.ts +84 -0
- package/src/tools/read-sqlite.ts +215 -0
- package/src/tools/read-summary.ts +199 -0
- package/src/tools/read.ts +82 -1824
- package/src/tools/shell-tokenize.ts +99 -1
- package/src/utils/changelog.ts +1 -1
- package/src/utils/title-generator.ts +3 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/zai.ts +12 -3
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { isEnoent, logger, once, untilAborted } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import type { BunFile } from "bun";
|
|
4
|
+
import { FileChangeType, notifyWorkspaceWatchedFiles } from "./client";
|
|
5
|
+
import { getServersForFile } from "./config";
|
|
6
|
+
import {
|
|
7
|
+
captureDiagnosticVersions,
|
|
8
|
+
captureOpenFileVersions,
|
|
9
|
+
DEFERRED_DIAGNOSTICS_WAIT_TIMEOUT_MS,
|
|
10
|
+
type FileDiagnosticsResult,
|
|
11
|
+
FileFormatResult,
|
|
12
|
+
formatContent,
|
|
13
|
+
getDiagnosticsForFile,
|
|
14
|
+
INLINE_DIAGNOSTICS_WAIT_TIMEOUT_MS,
|
|
15
|
+
limitDiagnosticMessages,
|
|
16
|
+
type ServerVersionMap,
|
|
17
|
+
} from "./diagnostics";
|
|
18
|
+
import { getConfig, notifyFileSaved, splitServers, syncFileContent } from "./servers";
|
|
19
|
+
import type { ServerConfig } from "./types";
|
|
20
|
+
import { summarizeDiagnosticMessages } from "./utils";
|
|
21
|
+
|
|
22
|
+
/** Options for creating the LSP writethrough callback */
|
|
23
|
+
export interface WritethroughOptions {
|
|
24
|
+
/** Whether to format the file using LSP after writing */
|
|
25
|
+
enableFormat?: boolean;
|
|
26
|
+
/** Whether to get LSP diagnostics after writing */
|
|
27
|
+
enableDiagnostics?: boolean;
|
|
28
|
+
/** Called when diagnostics arrive after the main timeout. */
|
|
29
|
+
onDeferredDiagnostics?: (diagnostics: FileDiagnosticsResult) => void;
|
|
30
|
+
/** Signal to cancel a pending deferred diagnostics fetch. */
|
|
31
|
+
deferredSignal?: AbortSignal;
|
|
32
|
+
/** Transform diagnostics before surfacing them after a successful fetch. */
|
|
33
|
+
transformDiagnostics?: (absPath: string, result: FileDiagnosticsResult) => FileDiagnosticsResult;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Internal resolved form of {@link WritethroughOptions} that the writethrough machinery operates on. */
|
|
37
|
+
type ResolvedWritethroughOptions = {
|
|
38
|
+
enableFormat: boolean;
|
|
39
|
+
enableDiagnostics: boolean;
|
|
40
|
+
transformDiagnostics?: (absPath: string, result: FileDiagnosticsResult) => FileDiagnosticsResult;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Per-file deferred LSP diagnostics wiring for {@link WritethroughCallback}. */
|
|
44
|
+
export type WritethroughDeferredHandle = {
|
|
45
|
+
onDeferredDiagnostics: (diagnostics: FileDiagnosticsResult) => void;
|
|
46
|
+
signal: AbortSignal;
|
|
47
|
+
finalize: (diagnostics: FileDiagnosticsResult | undefined) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Callback type for the LSP writethrough */
|
|
51
|
+
export type WritethroughCallback = (
|
|
52
|
+
dst: string,
|
|
53
|
+
content: string,
|
|
54
|
+
signal?: AbortSignal,
|
|
55
|
+
file?: BunFile,
|
|
56
|
+
batch?: LspWritethroughBatchRequest,
|
|
57
|
+
getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
58
|
+
) => Promise<FileDiagnosticsResult | undefined>;
|
|
59
|
+
|
|
60
|
+
/** No-op writethrough callback */
|
|
61
|
+
export async function writethroughNoop(
|
|
62
|
+
dst: string,
|
|
63
|
+
content: string,
|
|
64
|
+
_signal?: AbortSignal,
|
|
65
|
+
file?: BunFile,
|
|
66
|
+
_batch?: LspWritethroughBatchRequest,
|
|
67
|
+
_getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
68
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
69
|
+
if (file) {
|
|
70
|
+
await file.write(content);
|
|
71
|
+
} else {
|
|
72
|
+
await Bun.write(dst, content);
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface PendingWritethrough {
|
|
78
|
+
dst: string;
|
|
79
|
+
file?: BunFile;
|
|
80
|
+
changeType: FileChangeType;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface RunLspWritethroughOptions {
|
|
84
|
+
contentAlreadyWritten?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface LspWritethroughBatchRequest {
|
|
88
|
+
id: string;
|
|
89
|
+
flush: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface LspWritethroughBatchState {
|
|
93
|
+
entries: Map<string, PendingWritethrough>;
|
|
94
|
+
options: ResolvedWritethroughOptions;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const writethroughBatches = new Map<string, LspWritethroughBatchState>();
|
|
98
|
+
|
|
99
|
+
function getOrCreateWritethroughBatch(id: string, options: ResolvedWritethroughOptions): LspWritethroughBatchState {
|
|
100
|
+
const existing = writethroughBatches.get(id);
|
|
101
|
+
if (existing) {
|
|
102
|
+
existing.options.enableFormat ||= options.enableFormat;
|
|
103
|
+
existing.options.enableDiagnostics ||= options.enableDiagnostics;
|
|
104
|
+
existing.options.transformDiagnostics ??= options.transformDiagnostics;
|
|
105
|
+
return existing;
|
|
106
|
+
}
|
|
107
|
+
const batch: LspWritethroughBatchState = {
|
|
108
|
+
entries: new Map<string, PendingWritethrough>(),
|
|
109
|
+
options: { ...options },
|
|
110
|
+
};
|
|
111
|
+
writethroughBatches.set(id, batch);
|
|
112
|
+
return batch;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function flushLspWritethroughBatch(
|
|
116
|
+
id: string,
|
|
117
|
+
cwd: string,
|
|
118
|
+
signal?: AbortSignal,
|
|
119
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
120
|
+
const state = writethroughBatches.get(id);
|
|
121
|
+
if (!state) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
writethroughBatches.delete(id);
|
|
125
|
+
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function mergeDiagnostics(
|
|
129
|
+
results: Array<FileDiagnosticsResult | undefined>,
|
|
130
|
+
options: ResolvedWritethroughOptions,
|
|
131
|
+
): FileDiagnosticsResult | undefined {
|
|
132
|
+
const messages: string[] = [];
|
|
133
|
+
const servers = new Set<string>();
|
|
134
|
+
let hasResults = false;
|
|
135
|
+
let hasFormatter = false;
|
|
136
|
+
let formatted = false;
|
|
137
|
+
|
|
138
|
+
for (const result of results) {
|
|
139
|
+
if (!result) continue;
|
|
140
|
+
hasResults = true;
|
|
141
|
+
if (result.server) {
|
|
142
|
+
for (const server of result.server.split(",")) {
|
|
143
|
+
const trimmed = server.trim();
|
|
144
|
+
if (trimmed) {
|
|
145
|
+
servers.add(trimmed);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (result.messages.length > 0) {
|
|
150
|
+
messages.push(...result.messages);
|
|
151
|
+
}
|
|
152
|
+
if (result.formatter !== undefined) {
|
|
153
|
+
hasFormatter = true;
|
|
154
|
+
if (result.formatter === FileFormatResult.FORMATTED) {
|
|
155
|
+
formatted = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!hasResults && !hasFormatter) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let summary = options.enableDiagnostics ? "no issues" : "OK";
|
|
165
|
+
let errored = false;
|
|
166
|
+
let limitedMessages = messages;
|
|
167
|
+
if (messages.length > 0) {
|
|
168
|
+
const summaryInfo = summarizeDiagnosticMessages(messages);
|
|
169
|
+
summary = summaryInfo.summary;
|
|
170
|
+
errored = summaryInfo.errored;
|
|
171
|
+
limitedMessages = limitDiagnosticMessages(messages);
|
|
172
|
+
}
|
|
173
|
+
const formatter = hasFormatter ? (formatted ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED) : undefined;
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
server: servers.size > 0 ? Array.from(servers).join(", ") : undefined,
|
|
177
|
+
messages: limitedMessages,
|
|
178
|
+
summary,
|
|
179
|
+
errored,
|
|
180
|
+
formatter,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function scheduleDeferredDiagnosticsFetch(args: {
|
|
185
|
+
dst: string;
|
|
186
|
+
cwd: string;
|
|
187
|
+
servers: Array<[string, ServerConfig]>;
|
|
188
|
+
minVersions: ServerVersionMap | undefined;
|
|
189
|
+
expectedDocumentVersions: ServerVersionMap | undefined;
|
|
190
|
+
signal: AbortSignal;
|
|
191
|
+
callback: (diagnostics: FileDiagnosticsResult) => void;
|
|
192
|
+
}): Promise<void> {
|
|
193
|
+
try {
|
|
194
|
+
const deferredTimeout = AbortSignal.timeout(25_000);
|
|
195
|
+
const combined = AbortSignal.any([args.signal, deferredTimeout]);
|
|
196
|
+
const diagnostics = await getDiagnosticsForFile(args.dst, args.cwd, args.servers, {
|
|
197
|
+
signal: combined,
|
|
198
|
+
minVersions: args.minVersions,
|
|
199
|
+
expectedDocumentVersions: args.expectedDocumentVersions,
|
|
200
|
+
timeoutMs: DEFERRED_DIAGNOSTICS_WAIT_TIMEOUT_MS,
|
|
201
|
+
});
|
|
202
|
+
if (args.signal.aborted || diagnostics === undefined) return;
|
|
203
|
+
args.callback(diagnostics);
|
|
204
|
+
} catch {
|
|
205
|
+
// Cancelled or LSP gave up; silently discard.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Fetch post-write diagnostics without making the edit/write block on a slow
|
|
211
|
+
* language server.
|
|
212
|
+
*
|
|
213
|
+
* Blocks inline only briefly ({@link INLINE_DIAGNOSTICS_WAIT_TIMEOUT_MS}) for a
|
|
214
|
+
* fresh result. Freshness is enforced by the pre-edit `minVersions` baseline:
|
|
215
|
+
* exact document-version matches return immediately, and unversioned/mismatched
|
|
216
|
+
* publishes must settle with no newer publish before inline acceptance. If
|
|
217
|
+
* nothing fresh arrives in the inline window and a deferred
|
|
218
|
+
* channel is available, the in-flight fetch is handed off to deliver late via
|
|
219
|
+
* `onDeferredDiagnostics`, and this returns `undefined` so the tool result
|
|
220
|
+
* lands immediately. Without a deferred channel (direct/CI callers) it blocks
|
|
221
|
+
* for the standard budget so the result is still returned inline.
|
|
222
|
+
*/
|
|
223
|
+
async function fetchDiagnosticsWithDeferral(args: {
|
|
224
|
+
dst: string;
|
|
225
|
+
cwd: string;
|
|
226
|
+
servers: Array<[string, ServerConfig]>;
|
|
227
|
+
minVersions: ServerVersionMap | undefined;
|
|
228
|
+
expectedDocumentVersions: ServerVersionMap | undefined;
|
|
229
|
+
transformDiagnostics?: ResolvedWritethroughOptions["transformDiagnostics"];
|
|
230
|
+
deferred?: { onDeferredDiagnostics: (diagnostics: FileDiagnosticsResult) => void; signal: AbortSignal };
|
|
231
|
+
signal?: AbortSignal;
|
|
232
|
+
}): Promise<FileDiagnosticsResult | undefined> {
|
|
233
|
+
const { dst, cwd, servers, minVersions, expectedDocumentVersions, transformDiagnostics, deferred, signal } = args;
|
|
234
|
+
const apply = (d: FileDiagnosticsResult | undefined) =>
|
|
235
|
+
d && transformDiagnostics ? transformDiagnostics(dst, d) : d;
|
|
236
|
+
|
|
237
|
+
if (!deferred) {
|
|
238
|
+
// No late-injection channel: block for the standard budget and return inline.
|
|
239
|
+
return apply(
|
|
240
|
+
await getDiagnosticsForFile(dst, cwd, servers, {
|
|
241
|
+
signal,
|
|
242
|
+
minVersions,
|
|
243
|
+
expectedDocumentVersions,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// One background fetch with a generous inner budget; await it only briefly inline.
|
|
249
|
+
const fetchPromise = getDiagnosticsForFile(dst, cwd, servers, {
|
|
250
|
+
signal: deferred.signal,
|
|
251
|
+
minVersions,
|
|
252
|
+
expectedDocumentVersions,
|
|
253
|
+
timeoutMs: DEFERRED_DIAGNOSTICS_WAIT_TIMEOUT_MS,
|
|
254
|
+
});
|
|
255
|
+
const INLINE_TIMEOUT = Symbol("inline-diagnostics-timeout");
|
|
256
|
+
const raced = await Promise.race([
|
|
257
|
+
fetchPromise,
|
|
258
|
+
Bun.sleep(INLINE_DIAGNOSTICS_WAIT_TIMEOUT_MS).then(() => INLINE_TIMEOUT),
|
|
259
|
+
]);
|
|
260
|
+
if (raced !== INLINE_TIMEOUT) {
|
|
261
|
+
return apply(raced as FileDiagnosticsResult | undefined);
|
|
262
|
+
}
|
|
263
|
+
// Slow server: deliver late via the deferred channel; nothing inline. The
|
|
264
|
+
// deferred sink (edit tool) applies its own dedup, so pass the raw result.
|
|
265
|
+
void fetchPromise
|
|
266
|
+
.then(diagnostics => {
|
|
267
|
+
if (diagnostics && !deferred.signal.aborted) deferred.onDeferredDiagnostics(diagnostics);
|
|
268
|
+
})
|
|
269
|
+
.catch(() => {});
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function runLspWritethrough(
|
|
274
|
+
dst: string,
|
|
275
|
+
content: string,
|
|
276
|
+
cwd: string,
|
|
277
|
+
options: ResolvedWritethroughOptions,
|
|
278
|
+
changeType: FileChangeType,
|
|
279
|
+
signal?: AbortSignal,
|
|
280
|
+
file?: BunFile,
|
|
281
|
+
deferred?: {
|
|
282
|
+
onDeferredDiagnostics: (diagnostics: FileDiagnosticsResult) => void;
|
|
283
|
+
signal: AbortSignal;
|
|
284
|
+
},
|
|
285
|
+
runOptions?: RunLspWritethroughOptions,
|
|
286
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
287
|
+
const { enableFormat, enableDiagnostics } = options;
|
|
288
|
+
const contentAlreadyWritten = runOptions?.contentAlreadyWritten ?? false;
|
|
289
|
+
|
|
290
|
+
let finalContent = content;
|
|
291
|
+
const writeContent = async (value: string) => (file ? file.write(value) : Bun.write(dst, value));
|
|
292
|
+
const getWritePromise = once(() =>
|
|
293
|
+
contentAlreadyWritten && finalContent === content ? Promise.resolve() : writeContent(finalContent),
|
|
294
|
+
);
|
|
295
|
+
let writeNotified = false;
|
|
296
|
+
const notifyWriteCommitted = async (notifySignal: AbortSignal | undefined = signal) => {
|
|
297
|
+
if (writeNotified) return;
|
|
298
|
+
writeNotified = true;
|
|
299
|
+
try {
|
|
300
|
+
await notifyWorkspaceWatchedFiles(cwd, [{ filePath: dst, type: changeType }], notifySignal);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (notifySignal?.aborted && !signal?.aborted) {
|
|
303
|
+
// The operation budget died mid-notify while the caller is still
|
|
304
|
+
// live: allow the post-write retry below to re-announce with the
|
|
305
|
+
// caller's signal (didChangeWatchedFiles is idempotent).
|
|
306
|
+
writeNotified = false;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
if (!enableFormat && !enableDiagnostics) {
|
|
313
|
+
await getWritePromise();
|
|
314
|
+
await notifyWriteCommitted();
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const config = getConfig(cwd);
|
|
319
|
+
const servers = getServersForFile(config, dst);
|
|
320
|
+
|
|
321
|
+
if (servers.length === 0) {
|
|
322
|
+
await getWritePromise();
|
|
323
|
+
await notifyWriteCommitted();
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
const { lspServers, customLinterServers } = splitServers(servers);
|
|
327
|
+
const useCustomFormatter = enableFormat && customLinterServers.length > 0;
|
|
328
|
+
|
|
329
|
+
// Capture diagnostic versions BEFORE syncing to detect stale diagnostics
|
|
330
|
+
// Bound client creation by the writethrough budget: a hung/broken server
|
|
331
|
+
// must not add its full init wait (30s default) to every edit.
|
|
332
|
+
const minVersionsPromise = enableDiagnostics ? captureDiagnosticVersions(cwd, servers, 5_000, signal) : undefined;
|
|
333
|
+
let minVersions = useCustomFormatter ? undefined : await minVersionsPromise;
|
|
334
|
+
let expectedDocumentVersions: ServerVersionMap | undefined;
|
|
335
|
+
|
|
336
|
+
let formatter: FileFormatResult | undefined;
|
|
337
|
+
let diagnostics: FileDiagnosticsResult | undefined;
|
|
338
|
+
let timedOut = false;
|
|
339
|
+
let synced = false;
|
|
340
|
+
let operationSignal: AbortSignal | undefined;
|
|
341
|
+
try {
|
|
342
|
+
const timeoutSignal = AbortSignal.timeout(5_000);
|
|
343
|
+
timeoutSignal.addEventListener(
|
|
344
|
+
"abort",
|
|
345
|
+
() => {
|
|
346
|
+
timedOut = true;
|
|
347
|
+
},
|
|
348
|
+
{ once: true },
|
|
349
|
+
);
|
|
350
|
+
operationSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
351
|
+
await untilAborted(operationSignal, async () => {
|
|
352
|
+
if (useCustomFormatter) {
|
|
353
|
+
// Custom linters operate on on-disk input; the shared pre-write also
|
|
354
|
+
// supports implementations that inspect the file before formatting.
|
|
355
|
+
if (!contentAlreadyWritten) await writeContent(content);
|
|
356
|
+
const [formattedContent, capturedVersions] = await Promise.all([
|
|
357
|
+
formatContent(dst, content, cwd, customLinterServers, operationSignal),
|
|
358
|
+
minVersionsPromise,
|
|
359
|
+
]);
|
|
360
|
+
finalContent = formattedContent;
|
|
361
|
+
minVersions = capturedVersions;
|
|
362
|
+
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
363
|
+
if (!contentAlreadyWritten || finalContent !== content) await writeContent(finalContent);
|
|
364
|
+
await notifyWriteCommitted(operationSignal);
|
|
365
|
+
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal, enableDiagnostics);
|
|
366
|
+
} else {
|
|
367
|
+
// 1. Sync original content to LSP servers
|
|
368
|
+
await syncFileContent(dst, content, cwd, lspServers, operationSignal);
|
|
369
|
+
|
|
370
|
+
// 2. Format in-memory via LSP
|
|
371
|
+
if (enableFormat) {
|
|
372
|
+
finalContent = await formatContent(dst, content, cwd, lspServers, operationSignal);
|
|
373
|
+
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 3. If formatted, sync formatted content to LSP servers
|
|
377
|
+
if (finalContent !== content) {
|
|
378
|
+
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 4. Write to disk
|
|
382
|
+
await getWritePromise();
|
|
383
|
+
await notifyWriteCommitted(operationSignal);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (enableDiagnostics) {
|
|
387
|
+
expectedDocumentVersions = await captureOpenFileVersions(dst, cwd, lspServers, operationSignal);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 5. Notify saved to LSP servers
|
|
391
|
+
await notifyFileSaved(dst, cwd, lspServers, operationSignal, !useCustomFormatter || enableDiagnostics);
|
|
392
|
+
});
|
|
393
|
+
synced = true;
|
|
394
|
+
} catch {
|
|
395
|
+
if (timedOut) {
|
|
396
|
+
formatter = undefined;
|
|
397
|
+
diagnostics = undefined;
|
|
398
|
+
// Schedule background diagnostic fetch if caller wants deferred results
|
|
399
|
+
if (deferred && !deferred.signal.aborted && enableDiagnostics) {
|
|
400
|
+
void scheduleDeferredDiagnosticsFetch({
|
|
401
|
+
dst,
|
|
402
|
+
cwd,
|
|
403
|
+
servers,
|
|
404
|
+
minVersions,
|
|
405
|
+
expectedDocumentVersions,
|
|
406
|
+
signal: deferred.signal,
|
|
407
|
+
callback: deferred.onDeferredDiagnostics,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
await getWritePromise();
|
|
412
|
+
// The write above committed even though the operation budget elapsed:
|
|
413
|
+
// announce it on the caller's signal — the dead `operationSignal` would
|
|
414
|
+
// abort the notify before it ever reaches the server.
|
|
415
|
+
await notifyWriteCommitted();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (synced && enableDiagnostics) {
|
|
419
|
+
diagnostics = await fetchDiagnosticsWithDeferral({
|
|
420
|
+
dst,
|
|
421
|
+
cwd,
|
|
422
|
+
servers,
|
|
423
|
+
minVersions,
|
|
424
|
+
expectedDocumentVersions,
|
|
425
|
+
transformDiagnostics: options.transformDiagnostics,
|
|
426
|
+
deferred,
|
|
427
|
+
signal,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (formatter !== undefined) {
|
|
432
|
+
diagnostics ??= {
|
|
433
|
+
server: servers.map(([name]) => name).join(", "),
|
|
434
|
+
messages: [],
|
|
435
|
+
summary: "OK",
|
|
436
|
+
errored: false,
|
|
437
|
+
};
|
|
438
|
+
diagnostics.formatter = formatter;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return diagnostics;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function flushWritethroughBatch(
|
|
445
|
+
batch: PendingWritethrough[],
|
|
446
|
+
cwd: string,
|
|
447
|
+
options: ResolvedWritethroughOptions,
|
|
448
|
+
signal?: AbortSignal,
|
|
449
|
+
getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
450
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
451
|
+
if (batch.length === 0) {
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
const results: Array<FileDiagnosticsResult | undefined> = [];
|
|
455
|
+
for (const entry of batch) {
|
|
456
|
+
const bundle = getDeferred?.(entry.dst);
|
|
457
|
+
let content: string;
|
|
458
|
+
try {
|
|
459
|
+
content = await fs.promises.readFile(entry.dst, "utf8");
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (!isEnoent(error)) throw error;
|
|
462
|
+
bundle?.finalize(undefined);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const deferredInner =
|
|
466
|
+
bundle &&
|
|
467
|
+
({
|
|
468
|
+
onDeferredDiagnostics: bundle.onDeferredDiagnostics,
|
|
469
|
+
signal: bundle.signal,
|
|
470
|
+
} as const);
|
|
471
|
+
const diag = await runLspWritethrough(
|
|
472
|
+
entry.dst,
|
|
473
|
+
content,
|
|
474
|
+
cwd,
|
|
475
|
+
options,
|
|
476
|
+
entry.changeType,
|
|
477
|
+
signal,
|
|
478
|
+
entry.file,
|
|
479
|
+
deferredInner,
|
|
480
|
+
{ contentAlreadyWritten: true },
|
|
481
|
+
);
|
|
482
|
+
bundle?.finalize(diag);
|
|
483
|
+
results.push(diag);
|
|
484
|
+
}
|
|
485
|
+
return mergeDiagnostics(results, options);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Create a writethrough callback for LSP aware write operations */
|
|
489
|
+
export function createLspWritethrough(cwd: string, options?: WritethroughOptions): WritethroughCallback {
|
|
490
|
+
const resolvedOptions: ResolvedWritethroughOptions = {
|
|
491
|
+
enableFormat: options?.enableFormat ?? false,
|
|
492
|
+
enableDiagnostics: options?.enableDiagnostics ?? false,
|
|
493
|
+
transformDiagnostics: options?.transformDiagnostics,
|
|
494
|
+
};
|
|
495
|
+
return async (
|
|
496
|
+
dst: string,
|
|
497
|
+
content: string,
|
|
498
|
+
signal?: AbortSignal,
|
|
499
|
+
file?: BunFile,
|
|
500
|
+
batch?: LspWritethroughBatchRequest,
|
|
501
|
+
getDeferred?: (dst: string) => WritethroughDeferredHandle | undefined,
|
|
502
|
+
) => {
|
|
503
|
+
const changeType = (await Bun.file(dst).exists()) ? FileChangeType.Changed : FileChangeType.Created;
|
|
504
|
+
if (!batch) {
|
|
505
|
+
const bundle = getDeferred?.(dst);
|
|
506
|
+
const deferredInner =
|
|
507
|
+
bundle &&
|
|
508
|
+
({
|
|
509
|
+
onDeferredDiagnostics: bundle.onDeferredDiagnostics,
|
|
510
|
+
signal: bundle.signal,
|
|
511
|
+
} as const);
|
|
512
|
+
const diagnostics = await runLspWritethrough(
|
|
513
|
+
dst,
|
|
514
|
+
content,
|
|
515
|
+
cwd,
|
|
516
|
+
resolvedOptions,
|
|
517
|
+
changeType,
|
|
518
|
+
signal,
|
|
519
|
+
file,
|
|
520
|
+
deferredInner,
|
|
521
|
+
);
|
|
522
|
+
bundle?.finalize(diagnostics);
|
|
523
|
+
return diagnostics;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// File commits are never deferred: the batch owns only LSP post-processing,
|
|
527
|
+
// so a later flush cannot replay an obsolete whole-file snapshot.
|
|
528
|
+
try {
|
|
529
|
+
await writethroughNoop(dst, content, signal, file);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (batch.flush) {
|
|
532
|
+
const pending = writethroughBatches.get(batch.id);
|
|
533
|
+
if (pending) {
|
|
534
|
+
writethroughBatches.delete(batch.id);
|
|
535
|
+
try {
|
|
536
|
+
await flushWritethroughBatch(
|
|
537
|
+
Array.from(pending.entries.values()),
|
|
538
|
+
cwd,
|
|
539
|
+
pending.options,
|
|
540
|
+
signal,
|
|
541
|
+
getDeferred,
|
|
542
|
+
);
|
|
543
|
+
} catch (flushError) {
|
|
544
|
+
logger.warn("Failed to flush pending LSP batch after final write failure", {
|
|
545
|
+
batchId: batch.id,
|
|
546
|
+
error: flushError instanceof Error ? flushError.message : String(flushError),
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const state = getOrCreateWritethroughBatch(batch.id, resolvedOptions);
|
|
555
|
+
state.entries.set(dst, { dst, file, changeType });
|
|
556
|
+
if (!batch.flush) return undefined;
|
|
557
|
+
|
|
558
|
+
writethroughBatches.delete(batch.id);
|
|
559
|
+
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal, getDeferred);
|
|
560
|
+
};
|
|
561
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -1033,11 +1033,12 @@ export async function buildSessionOptions(
|
|
|
1033
1033
|
if (parsed.noPrewalk && (parsed.prewalk || parsed.prewalkInto !== undefined)) {
|
|
1034
1034
|
throw new Error("--no-prewalk cannot be combined with --prewalk or --prewalk-into");
|
|
1035
1035
|
}
|
|
1036
|
+
const explicitPrewalk = parsed.prewalk === true || parsed.prewalkInto !== undefined;
|
|
1036
1037
|
const prewalkEnabled = parsed.noPrewalk
|
|
1037
1038
|
? false
|
|
1038
|
-
:
|
|
1039
|
+
: explicitPrewalk
|
|
1039
1040
|
? true
|
|
1040
|
-
: activeSettings.get("prewalk.enabled");
|
|
1041
|
+
: !restoringSession && activeSettings.get("prewalk.enabled");
|
|
1041
1042
|
if (prewalkEnabled) {
|
|
1042
1043
|
const rolePattern = expandRoleAlias(parsed.prewalkInto ?? DEFAULT_PREWALK_TARGET, activeSettings);
|
|
1043
1044
|
const resolved = resolveCliModel({ cliModel: rolePattern, modelRegistry, preferences: modelMatchPreferences });
|
package/src/mcp/config.ts
CHANGED
|
@@ -54,6 +54,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
54
54
|
};
|
|
55
55
|
if (server.args) config.args = server.args;
|
|
56
56
|
if (server.env) config.env = server.env;
|
|
57
|
+
if (server.envPolicy) config.envPolicy = server.envPolicy;
|
|
57
58
|
if (server.cwd) config.cwd = server.cwd;
|
|
58
59
|
return config;
|
|
59
60
|
}
|
|
@@ -65,6 +66,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
65
66
|
url: server.url ?? "",
|
|
66
67
|
};
|
|
67
68
|
if (server.headers) config.headers = server.headers;
|
|
69
|
+
if (server.headerPolicy) config.headerPolicy = server.headerPolicy;
|
|
68
70
|
return config;
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -75,6 +77,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
75
77
|
url: server.url ?? "",
|
|
76
78
|
};
|
|
77
79
|
if (server.headers) config.headers = server.headers;
|
|
80
|
+
if (server.headerPolicy) config.headerPolicy = server.headerPolicy;
|
|
78
81
|
return config;
|
|
79
82
|
}
|
|
80
83
|
|
package/src/mcp/manager.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type { McpConnectionStatusEvent } from "./startup-events";
|
|
|
37
37
|
import type { MCPToolDetails } from "./tool-bridge";
|
|
38
38
|
import { DeferredMCPTool, MCPTool } from "./tool-bridge";
|
|
39
39
|
import type { MCPToolCache } from "./tool-cache";
|
|
40
|
+
import { setGeneratedHeader } from "./transports/header-policy";
|
|
40
41
|
import type {
|
|
41
42
|
MCPAuthChallenge,
|
|
42
43
|
MCPGetPromptResult,
|
|
@@ -1458,13 +1459,11 @@ export class MCPManager {
|
|
|
1458
1459
|
|
|
1459
1460
|
if (credential) {
|
|
1460
1461
|
if (resolved.type === "http" || resolved.type === "sse") {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
},
|
|
1467
|
-
};
|
|
1462
|
+
// Client-generated authorization wins over any configured header
|
|
1463
|
+
// with the same case-insensitive name (Agent Plugins §7.2.1).
|
|
1464
|
+
const headers = { ...resolved.headers };
|
|
1465
|
+
setGeneratedHeader(headers, "Authorization", `Bearer ${credential.access}`);
|
|
1466
|
+
resolved = { ...resolved, headers };
|
|
1468
1467
|
} else {
|
|
1469
1468
|
resolved = {
|
|
1470
1469
|
...resolved,
|
|
@@ -1481,7 +1480,9 @@ export class MCPManager {
|
|
|
1481
1480
|
}
|
|
1482
1481
|
|
|
1483
1482
|
if (resolved.type !== "http" && resolved.type !== "sse") {
|
|
1484
|
-
|
|
1483
|
+
// Literal env values (Agent Plugins §§4.1/9.2) are opaque package data:
|
|
1484
|
+
// no env-name lookup, no `!command` execution, no dropping empty values.
|
|
1485
|
+
if (resolved.env && resolved.envPolicy !== "literal") {
|
|
1485
1486
|
const nextEnv: Record<string, string> = {};
|
|
1486
1487
|
for (const [key, value] of Object.entries(resolved.env)) {
|
|
1487
1488
|
const resolvedValue = await resolveConfigValue(value);
|
|
@@ -1490,7 +1491,9 @@ export class MCPManager {
|
|
|
1490
1491
|
resolved = { ...resolved, env: nextEnv };
|
|
1491
1492
|
}
|
|
1492
1493
|
} else {
|
|
1493
|
-
|
|
1494
|
+
// Origin-locked servers (Agent Plugins §9.2) carry literal header
|
|
1495
|
+
// values: no placeholder or environment-variable expansion.
|
|
1496
|
+
if (resolved.headers && resolved.headerPolicy !== "origin-locked") {
|
|
1494
1497
|
const nextHeaders: Record<string, string> = {};
|
|
1495
1498
|
for (const [key, value] of Object.entries(resolved.headers)) {
|
|
1496
1499
|
const resolvedValue = await resolveConfigValue(value);
|