@oh-my-pi/pi-coding-agent 17.0.7 → 17.0.9
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 +91 -0
- package/dist/cli.js +5025 -4930
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +28 -1
- package/dist/types/config/model-resolver.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +50 -3
- package/dist/types/dap/client.d.ts +19 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +1 -1
- package/dist/types/extensibility/typebox.d.ts +4 -0
- package/dist/types/hindsight/client.d.ts +10 -0
- package/dist/types/hindsight/config.d.ts +8 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/tool-bridge.d.ts +16 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/mnemopi/state.d.ts +2 -0
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +25 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +8 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/types.d.ts +14 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -8
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-error-log.test.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +86 -3
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/prewalk.d.ts +3 -0
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/ask.d.ts +10 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/tool-timeouts.d.ts +8 -2
- package/dist/types/utils/changelog.d.ts +4 -6
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/models-cli.ts +37 -17
- package/src/cli/update-cli.ts +14 -1
- package/src/cli/usage-cli.ts +1 -1
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +87 -24
- package/src/config/model-resolver.ts +56 -32
- package/src/config/settings-schema.ts +46 -3
- package/src/config/settings.ts +68 -5
- package/src/dap/client.ts +86 -7
- package/src/edit/diff.ts +4 -4
- package/src/eval/py/prelude.py +5 -5
- package/src/export/html/template.js +23 -9
- package/src/extensibility/extensions/runner.ts +9 -4
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +851 -310
- package/src/extensibility/typebox.ts +22 -9
- package/src/hindsight/client.test.ts +42 -0
- package/src/hindsight/client.ts +40 -3
- package/src/hindsight/config.ts +18 -0
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/launch/broker.ts +31 -5
- package/src/lsp/client.ts +23 -0
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +26 -8
- package/src/main.ts +16 -5
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/render.ts +94 -35
- package/src/mcp/tool-bridge.ts +116 -11
- package/src/mcp/types.ts +7 -0
- package/src/mnemopi/state.ts +70 -3
- package/src/modes/components/agent-dashboard.ts +6 -2
- package/src/modes/components/agent-hub.ts +26 -9
- package/src/modes/components/chat-transcript-builder.ts +13 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +20 -4
- package/src/modes/components/settings-defs.ts +5 -2
- package/src/modes/components/settings-selector.ts +9 -5
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/user-message.ts +100 -1
- package/src/modes/controllers/command-controller.ts +36 -12
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +18 -0
- package/src/modes/controllers/input-controller.ts +47 -12
- package/src/modes/controllers/mcp-command-controller.ts +70 -40
- package/src/modes/controllers/selector-controller.ts +82 -6
- package/src/modes/interactive-mode.ts +39 -4
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +6 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +16 -3
- package/src/modes/utils/ui-helpers.ts +40 -20
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/task.md +12 -2
- package/src/sdk.ts +179 -51
- package/src/session/agent-session-error-log.test.ts +59 -0
- package/src/session/agent-session.ts +351 -46
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +41 -2
- package/src/session/session-loader.ts +10 -5
- package/src/slash-commands/helpers/usage-report.ts +3 -1
- package/src/system-prompt.ts +1 -2
- package/src/task/executor.ts +44 -27
- package/src/task/index.ts +63 -8
- package/src/task/prewalk.ts +6 -0
- package/src/task/structured-subagent.ts +3 -1
- package/src/task/types.ts +16 -0
- package/src/task/worktree.ts +25 -11
- package/src/tools/ask.ts +15 -0
- package/src/tools/bash.ts +14 -6
- package/src/tools/browser.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval.ts +4 -5
- package/src/tools/fetch.ts +1 -1
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/hub/launch.ts +7 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/tool-timeouts.ts +10 -3
- package/src/tools/write.ts +31 -0
- package/src/utils/changelog.ts +54 -58
- package/src/utils/git.ts +57 -49
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/types.ts +5 -1
package/src/mcp/manager.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type { MCPToolDetails } from "./tool-bridge";
|
|
|
38
38
|
import { DeferredMCPTool, MCPTool } from "./tool-bridge";
|
|
39
39
|
import type { MCPToolCache } from "./tool-cache";
|
|
40
40
|
import type {
|
|
41
|
+
MCPAuthChallenge,
|
|
41
42
|
MCPGetPromptResult,
|
|
42
43
|
MCPPrompt,
|
|
43
44
|
MCPRequestOptions,
|
|
@@ -160,6 +161,9 @@ export interface MCPDiscoverOptions {
|
|
|
160
161
|
onStatus?: (event: McpConnectionStatusEvent) => void;
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
/** Handles an MCP `WWW-Authenticate` challenge and returns refreshed config. */
|
|
165
|
+
export type MCPAuthHandler = (serverName: string, challenge: MCPAuthChallenge) => Promise<MCPServerConfig | undefined>;
|
|
166
|
+
|
|
163
167
|
/**
|
|
164
168
|
* MCP Server Manager.
|
|
165
169
|
*
|
|
@@ -189,6 +193,7 @@ export class MCPManager {
|
|
|
189
193
|
#pendingToolLoads = new Map<string, Promise<ToolLoadResult>>();
|
|
190
194
|
#sources = new Map<string, SourceMeta>();
|
|
191
195
|
#authStorage: AuthStorage | null = null;
|
|
196
|
+
#authHandler?: MCPAuthHandler;
|
|
192
197
|
#onNotification?: (serverName: string, method: string, params: unknown) => void;
|
|
193
198
|
#onToolsChanged?: (tools: CustomTool<TSchema, MCPToolDetails>[]) => void;
|
|
194
199
|
#onResourcesChanged?: (serverName: string, uri: string) => void;
|
|
@@ -201,7 +206,7 @@ export class MCPManager {
|
|
|
201
206
|
/** Preserved configs for reconnection after connection loss. */
|
|
202
207
|
#serverConfigs = new Map<string, MCPServerConfig>();
|
|
203
208
|
/**
|
|
204
|
-
* Timestamps of recent
|
|
209
|
+
* Timestamps of recent reconnectServer invocations per server, used by the
|
|
205
210
|
* crash-storm circuit breaker (see {@link RECONNECT_BURST_LIMIT}).
|
|
206
211
|
*/
|
|
207
212
|
#reconnectHistory = new Map<string, number[]>();
|
|
@@ -312,6 +317,11 @@ export class MCPManager {
|
|
|
312
317
|
this.#authStorage = authStorage;
|
|
313
318
|
}
|
|
314
319
|
|
|
320
|
+
/** Set the callback used to complete OAuth after a tool-level auth challenge. */
|
|
321
|
+
setAuthHandler(handler: MCPAuthHandler | undefined): void {
|
|
322
|
+
this.#authHandler = handler;
|
|
323
|
+
}
|
|
324
|
+
|
|
315
325
|
/**
|
|
316
326
|
* Discover and connect to all MCP servers from .mcp.json files.
|
|
317
327
|
* Returns tools and any connection errors.
|
|
@@ -474,7 +484,8 @@ export class MCPManager {
|
|
|
474
484
|
.then(async ({ connection, serverTools }) => {
|
|
475
485
|
if (this.#pendingToolLoads.get(name) !== toolsPromise) return;
|
|
476
486
|
this.#pendingToolLoads.delete(name);
|
|
477
|
-
const reconnect = () =>
|
|
487
|
+
const reconnect = (options?: { authChallenge?: MCPAuthChallenge }) =>
|
|
488
|
+
this.reconnectServer(name, options);
|
|
478
489
|
const customTools = MCPTool.fromTools(connection, serverTools, reconnect);
|
|
479
490
|
this.#replaceServerTools(name, customTools);
|
|
480
491
|
this.#onToolsChanged?.(this.#tools);
|
|
@@ -810,13 +821,15 @@ export class MCPManager {
|
|
|
810
821
|
* the same server share one reconnection attempt. Returns the new
|
|
811
822
|
* connection, or `null` if reconnection failed or the per-server crash
|
|
812
823
|
* burst limit (see {@link RECONNECT_BURST_LIMIT}) is exceeded.
|
|
813
|
-
*
|
|
814
824
|
* @param options.manual - When `true`, resets the crash-burst window so a
|
|
815
825
|
* user-driven retry (e.g. `/mcp reconnect`) is never blocked by an
|
|
816
826
|
* earlier storm. Defaults to `false`; the transport `onClose` callback
|
|
817
827
|
* and the per-tool-call retry path in `tool-bridge` MUST NOT set it.
|
|
818
828
|
*/
|
|
819
|
-
async reconnectServer(
|
|
829
|
+
async reconnectServer(
|
|
830
|
+
name: string,
|
|
831
|
+
options?: { manual?: boolean; authChallenge?: MCPAuthChallenge },
|
|
832
|
+
): Promise<MCPServerConnection | null> {
|
|
820
833
|
if (options?.manual) {
|
|
821
834
|
this.#reconnectHistory.delete(name);
|
|
822
835
|
}
|
|
@@ -828,7 +841,7 @@ export class MCPManager {
|
|
|
828
841
|
return null;
|
|
829
842
|
}
|
|
830
843
|
|
|
831
|
-
const attempt = this.#doReconnect(name);
|
|
844
|
+
const attempt = this.#doReconnect(name, options?.authChallenge);
|
|
832
845
|
this.#pendingReconnections.set(name, attempt);
|
|
833
846
|
return attempt.finally(() => this.#pendingReconnections.delete(name));
|
|
834
847
|
}
|
|
@@ -873,12 +886,30 @@ export class MCPManager {
|
|
|
873
886
|
return false;
|
|
874
887
|
}
|
|
875
888
|
|
|
876
|
-
async #doReconnect(name: string): Promise<MCPServerConnection | null> {
|
|
889
|
+
async #doReconnect(name: string, authChallenge?: MCPAuthChallenge): Promise<MCPServerConnection | null> {
|
|
877
890
|
const oldConnection = this.#connections.get(name);
|
|
878
|
-
|
|
891
|
+
let config = oldConnection?.config ?? this.#serverConfigs.get(name);
|
|
879
892
|
const source = this.#sources.get(name) ?? oldConnection?._source;
|
|
880
893
|
if (!config) return null;
|
|
881
894
|
|
|
895
|
+
if (authChallenge) {
|
|
896
|
+
if (!this.#authHandler) {
|
|
897
|
+
logger.error("MCP auth challenge cannot be handled; no auth handler is configured", {
|
|
898
|
+
path: `mcp:${name}`,
|
|
899
|
+
});
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
const refreshedConfig = await this.#authHandler(name, authChallenge);
|
|
904
|
+
if (!refreshedConfig) return null;
|
|
905
|
+
config = refreshedConfig;
|
|
906
|
+
this.#serverConfigs.set(name, config);
|
|
907
|
+
} catch (error) {
|
|
908
|
+
logger.error("MCP auth challenge handling failed", { path: `mcp:${name}`, error });
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
882
913
|
logger.debug("MCP reconnecting", { path: `mcp:${name}` });
|
|
883
914
|
|
|
884
915
|
// Close the old transport without removing tools or notifying consumers.
|
|
@@ -987,7 +1018,7 @@ export class MCPManager {
|
|
|
987
1018
|
};
|
|
988
1019
|
try {
|
|
989
1020
|
const serverTools = await listTools(connection);
|
|
990
|
-
const reconnect = () => this.reconnectServer(name);
|
|
1021
|
+
const reconnect = (options?: { authChallenge?: MCPAuthChallenge }) => this.reconnectServer(name, options);
|
|
991
1022
|
const customTools = MCPTool.fromTools(connection, serverTools, reconnect);
|
|
992
1023
|
void this.toolCache?.set(name, config, serverTools);
|
|
993
1024
|
this.#replaceServerTools(name, customTools);
|
package/src/mcp/render.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Provides structured display of MCP tool calls and results,
|
|
5
5
|
* showing args and output in JSON tree format similar to task tool.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type Component, Markdown } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { settings } from "../config/settings";
|
|
8
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
|
-
import type
|
|
10
|
+
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
10
11
|
import {
|
|
11
12
|
formatArgsInline,
|
|
12
13
|
JSON_TREE_MAX_DEPTH_COLLAPSED,
|
|
@@ -48,6 +49,61 @@ export function renderMCPCall(args: Record<string, unknown>, theme: Theme, label
|
|
|
48
49
|
);
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/** Render an MCP status/args prefix followed by Markdown-aware text output. */
|
|
53
|
+
function renderMarkdownMCPResult(
|
|
54
|
+
result: { details?: MCPToolDetails; isError?: boolean },
|
|
55
|
+
trimmedOutput: string,
|
|
56
|
+
truncationWarning: string | null,
|
|
57
|
+
options: RenderResultOptions,
|
|
58
|
+
theme: Theme,
|
|
59
|
+
args?: Record<string, unknown>,
|
|
60
|
+
): Component {
|
|
61
|
+
const markdown = new Markdown(trimmedOutput, 0, 0, getMarkdownTheme(), {
|
|
62
|
+
color: text => theme.fg("toolOutput", text),
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
render(contentWidth: number): readonly string[] {
|
|
66
|
+
const lines: string[] = [];
|
|
67
|
+
const isError = result.isError ?? result.details?.isError ?? false;
|
|
68
|
+
const title = result.details ? `${result.details.serverName}/${result.details.mcpToolName}` : "MCP";
|
|
69
|
+
lines.push(
|
|
70
|
+
renderStatusLine(
|
|
71
|
+
isError ? { icon: "error", title } : { iconOverride: theme.styledSymbol("tool.mcp", "accent"), title },
|
|
72
|
+
theme,
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (options.expanded && args && Object.keys(args).length > 0) {
|
|
77
|
+
lines.push(theme.fg("dim", "Args"));
|
|
78
|
+
const tree = renderJsonTreeLines(
|
|
79
|
+
args,
|
|
80
|
+
theme,
|
|
81
|
+
JSON_TREE_MAX_DEPTH_EXPANDED,
|
|
82
|
+
JSON_TREE_MAX_LINES_EXPANDED,
|
|
83
|
+
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
84
|
+
);
|
|
85
|
+
lines.push(...tree.lines);
|
|
86
|
+
if (tree.truncated) lines.push(theme.fg("dim", "…"));
|
|
87
|
+
lines.push("");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const rendered = markdown.render(Math.max(1, contentWidth));
|
|
91
|
+
const maxOutputLines = options.expanded ? 12 : 4;
|
|
92
|
+
lines.push(...rendered.slice(0, maxOutputLines));
|
|
93
|
+
if (rendered.length > maxOutputLines) {
|
|
94
|
+
lines.push(
|
|
95
|
+
`${theme.fg("dim", `… ${rendered.length - maxOutputLines} more lines`)} ${formatExpandHint(theme, options.expanded, true)}`,
|
|
96
|
+
);
|
|
97
|
+
} else if (!options.expanded) {
|
|
98
|
+
lines.push(formatExpandHint(theme, options.expanded, true));
|
|
99
|
+
}
|
|
100
|
+
if (truncationWarning) lines.push(truncationWarning);
|
|
101
|
+
return lines;
|
|
102
|
+
},
|
|
103
|
+
invalidate(): void {},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
51
107
|
/**
|
|
52
108
|
* Render MCP tool result.
|
|
53
109
|
*/
|
|
@@ -58,6 +114,24 @@ export function renderMCPResult(
|
|
|
58
114
|
args?: Record<string, unknown>,
|
|
59
115
|
): Component {
|
|
60
116
|
const { expanded } = options;
|
|
117
|
+
const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
118
|
+
const trimmedOutput = stripOutputNotice(textContent, result.details?.meta).trimEnd();
|
|
119
|
+
const truncationWarning = result.details?.meta?.truncation
|
|
120
|
+
? formatStyledTruncationWarning(result.details.meta, theme)
|
|
121
|
+
: null;
|
|
122
|
+
let parsedOutput: unknown;
|
|
123
|
+
let isJsonOutput = false;
|
|
124
|
+
if (trimmedOutput.startsWith("{") || trimmedOutput.startsWith("[")) {
|
|
125
|
+
try {
|
|
126
|
+
parsedOutput = JSON.parse(trimmedOutput);
|
|
127
|
+
isJsonOutput = true;
|
|
128
|
+
} catch {
|
|
129
|
+
// Non-JSON text beginning with a bracket is still eligible for Markdown.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (trimmedOutput && settings.get("mcp.renderMarkdownResults") && !isJsonOutput) {
|
|
133
|
+
return renderMarkdownMCPResult(result, trimmedOutput, truncationWarning, options, theme, args);
|
|
134
|
+
}
|
|
61
135
|
return new WidthAwareText(
|
|
62
136
|
contentWidth => {
|
|
63
137
|
const lines: string[] = [];
|
|
@@ -86,46 +160,31 @@ export function renderMCPResult(
|
|
|
86
160
|
lines.push(""); // Blank line before output
|
|
87
161
|
}
|
|
88
162
|
|
|
89
|
-
// Output section
|
|
90
|
-
|
|
91
|
-
// Strip the LLM-facing spill notice before parsing/rendering: a spilled
|
|
92
|
-
// result appends `[Showing… artifact://N]` to the body, which would break
|
|
93
|
-
// JSON detection and bury the recovery link. Surface it as a styled warning
|
|
94
|
-
// instead, mirroring the built-in read/bash/ssh/browser renderers.
|
|
95
|
-
const trimmedOutput = stripOutputNotice(textContent, result.details?.meta).trimEnd();
|
|
96
|
-
const truncationWarning = result.details?.meta?.truncation
|
|
97
|
-
? formatStyledTruncationWarning(result.details.meta, theme)
|
|
98
|
-
: null;
|
|
163
|
+
// Output section. The body and spill metadata are normalized before
|
|
164
|
+
// component selection so the opt-in Markdown path can use its own renderer.
|
|
99
165
|
|
|
100
166
|
if (!trimmedOutput) {
|
|
101
167
|
lines.push(theme.fg("dim", "(no output)"));
|
|
102
168
|
return lines.join("\n");
|
|
103
169
|
}
|
|
104
170
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!expanded) {
|
|
120
|
-
lines.push(formatExpandHint(theme, expanded, true));
|
|
121
|
-
} else if (tree.truncated) {
|
|
122
|
-
lines.push(theme.fg("dim", "…"));
|
|
123
|
-
}
|
|
124
|
-
if (truncationWarning) lines.push(truncationWarning);
|
|
125
|
-
return lines.join("\n");
|
|
171
|
+
// Preserve the existing structured JSON renderer regardless of the
|
|
172
|
+
// Markdown preference; JSON trees remain more useful than styled source.
|
|
173
|
+
if (isJsonOutput) {
|
|
174
|
+
const maxDepth = expanded ? JSON_TREE_MAX_DEPTH_EXPANDED : JSON_TREE_MAX_DEPTH_COLLAPSED;
|
|
175
|
+
const maxLines = expanded ? JSON_TREE_MAX_LINES_EXPANDED : JSON_TREE_MAX_LINES_COLLAPSED;
|
|
176
|
+
const maxScalarLen = expanded ? JSON_TREE_SCALAR_LEN_EXPANDED : JSON_TREE_SCALAR_LEN_COLLAPSED;
|
|
177
|
+
const tree = renderJsonTreeLines(parsedOutput, theme, maxDepth, maxLines, maxScalarLen);
|
|
178
|
+
|
|
179
|
+
if (tree.lines.length > 0) {
|
|
180
|
+
lines.push(...tree.lines);
|
|
181
|
+
if (!expanded) {
|
|
182
|
+
lines.push(formatExpandHint(theme, expanded, true));
|
|
183
|
+
} else if (tree.truncated) {
|
|
184
|
+
lines.push(theme.fg("dim", "…"));
|
|
126
185
|
}
|
|
127
|
-
|
|
128
|
-
|
|
186
|
+
if (truncationWarning) lines.push(truncationWarning);
|
|
187
|
+
return lines.join("\n");
|
|
129
188
|
}
|
|
130
189
|
}
|
|
131
190
|
|
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -22,10 +22,17 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
22
22
|
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
23
23
|
import { callTool } from "./client";
|
|
24
24
|
import { renderMCPCall, renderMCPResult } from "./render";
|
|
25
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
MCPAuthChallenge,
|
|
27
|
+
MCPContent,
|
|
28
|
+
MCPServerConnection,
|
|
29
|
+
MCPToolCallParams,
|
|
30
|
+
MCPToolCallResult,
|
|
31
|
+
MCPToolDefinition,
|
|
32
|
+
} from "./types";
|
|
26
33
|
|
|
27
|
-
/** Reconnect callback: tears down stale connection,
|
|
28
|
-
export type MCPReconnect = () => Promise<MCPServerConnection | null>;
|
|
34
|
+
/** Reconnect callback: tears down a stale connection, optionally authorizing first. */
|
|
35
|
+
export type MCPReconnect = (options?: { authChallenge?: MCPAuthChallenge }) => Promise<MCPServerConnection | null>;
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
38
|
* Network-level and stale-session errors that warrant a reconnect + single retry.
|
|
@@ -174,6 +181,8 @@ export interface MCPToolDetails {
|
|
|
174
181
|
isError?: boolean;
|
|
175
182
|
/** Raw content from MCP response */
|
|
176
183
|
rawContent?: MCPContent[];
|
|
184
|
+
/** Structured metadata from the MCP response */
|
|
185
|
+
mcpMeta?: Record<string, unknown>;
|
|
177
186
|
/** Provider ID (e.g., "claude", "mcp-json") */
|
|
178
187
|
provider?: string;
|
|
179
188
|
/** Provider display name (e.g., "Claude Code", "MCP Config") */
|
|
@@ -222,6 +231,7 @@ function buildResult(
|
|
|
222
231
|
mcpToolName,
|
|
223
232
|
isError: result.isError,
|
|
224
233
|
rawContent: result.content,
|
|
234
|
+
mcpMeta: result._meta,
|
|
225
235
|
provider,
|
|
226
236
|
providerName,
|
|
227
237
|
};
|
|
@@ -249,6 +259,51 @@ function buildErrorResult(
|
|
|
249
259
|
};
|
|
250
260
|
}
|
|
251
261
|
|
|
262
|
+
type MCPToolCallAttempt = {
|
|
263
|
+
connection: MCPServerConnection;
|
|
264
|
+
result?: MCPToolCallResult;
|
|
265
|
+
error?: unknown;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function getMcpAuthChallenge(result: MCPToolCallResult): MCPAuthChallenge | undefined {
|
|
269
|
+
if (!result.isError) return undefined;
|
|
270
|
+
const values = result._meta?.["mcp/www_authenticate"];
|
|
271
|
+
if (!Array.isArray(values)) return undefined;
|
|
272
|
+
const wwwAuthenticate = values.filter((value): value is string => typeof value === "string" && value.trim() !== "");
|
|
273
|
+
return wwwAuthenticate.length > 0 ? { wwwAuthenticate } : undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function callToolWithAuthRetry(
|
|
277
|
+
connection: MCPServerConnection,
|
|
278
|
+
toolName: string,
|
|
279
|
+
args: MCPToolArgs,
|
|
280
|
+
reconnect: MCPReconnect | undefined,
|
|
281
|
+
signal?: AbortSignal,
|
|
282
|
+
): Promise<MCPToolCallAttempt> {
|
|
283
|
+
const result = await callTool(connection, toolName, args, { signal });
|
|
284
|
+
const authChallenge = getMcpAuthChallenge(result);
|
|
285
|
+
if (!authChallenge || !reconnect) return { connection, result };
|
|
286
|
+
|
|
287
|
+
let newConnection: MCPServerConnection | null;
|
|
288
|
+
try {
|
|
289
|
+
newConnection = await reconnectWithAbort(reconnect, signal, { authChallenge });
|
|
290
|
+
} catch (error) {
|
|
291
|
+
rethrowIfAborted(error, signal);
|
|
292
|
+
return { connection, error };
|
|
293
|
+
}
|
|
294
|
+
if (!newConnection) return { connection, result };
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
return {
|
|
298
|
+
connection: newConnection,
|
|
299
|
+
result: await callTool(newConnection, toolName, args, { signal }),
|
|
300
|
+
};
|
|
301
|
+
} catch (error) {
|
|
302
|
+
rethrowIfAborted(error, signal);
|
|
303
|
+
return { connection: newConnection, error };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
252
307
|
/** Re-throw abort-related errors so they bypass error-result handling. */
|
|
253
308
|
function rethrowIfAborted(error: unknown, signal?: AbortSignal): void {
|
|
254
309
|
if (error instanceof ToolAbortError) throw error;
|
|
@@ -256,9 +311,13 @@ function rethrowIfAborted(error: unknown, signal?: AbortSignal): void {
|
|
|
256
311
|
if (signal?.aborted) throw new ToolAbortError();
|
|
257
312
|
}
|
|
258
313
|
|
|
259
|
-
async function reconnectWithAbort(
|
|
314
|
+
async function reconnectWithAbort(
|
|
315
|
+
reconnect: MCPReconnect,
|
|
316
|
+
signal?: AbortSignal,
|
|
317
|
+
options?: { authChallenge?: MCPAuthChallenge },
|
|
318
|
+
): Promise<MCPServerConnection | null> {
|
|
260
319
|
try {
|
|
261
|
-
return await untilAborted(signal, reconnect);
|
|
320
|
+
return await untilAborted(signal, () => reconnect(options));
|
|
262
321
|
} catch (error) {
|
|
263
322
|
rethrowIfAborted(error, signal);
|
|
264
323
|
return null;
|
|
@@ -332,6 +391,13 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
332
391
|
readonly approval = "write" as const;
|
|
333
392
|
/** Render completed MCP calls with the result header replacing the pending call header. */
|
|
334
393
|
readonly mergeCallAndResult = true;
|
|
394
|
+
/**
|
|
395
|
+
* MCP-backed tools opt out of strict structured-output grammar. The server
|
|
396
|
+
* owns validation, and strict mode makes OpenAI-family models over-fill
|
|
397
|
+
* mutually exclusive optional fields (#4336/#4340). Serializers preserve an
|
|
398
|
+
* explicit `false`; an omitted flag would leave nothing to preserve.
|
|
399
|
+
*/
|
|
400
|
+
readonly strict = false as const;
|
|
335
401
|
|
|
336
402
|
/** Create MCPTool instances for all tools from an MCP server connection */
|
|
337
403
|
static fromTools(connection: MCPServerConnection, tools: MCPToolDefinition[], reconnect?: MCPReconnect): MCPTool[] {
|
|
@@ -372,8 +438,27 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
372
438
|
const providerName = this.connection._source?.providerName;
|
|
373
439
|
|
|
374
440
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
441
|
+
const attempt = await callToolWithAuthRetry(this.connection, this.tool.name, args, this.reconnect, signal);
|
|
442
|
+
if (attempt.error !== undefined) {
|
|
443
|
+
return buildErrorResult(attempt.error, this.connection.name, this.tool.name, provider, providerName);
|
|
444
|
+
}
|
|
445
|
+
if (!attempt.result) {
|
|
446
|
+
return buildErrorResult(
|
|
447
|
+
new Error("MCP tool call returned no result"),
|
|
448
|
+
this.connection.name,
|
|
449
|
+
this.tool.name,
|
|
450
|
+
provider,
|
|
451
|
+
providerName,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
this.connection = attempt.connection;
|
|
455
|
+
return buildResult(
|
|
456
|
+
attempt.result,
|
|
457
|
+
attempt.connection.name,
|
|
458
|
+
this.tool.name,
|
|
459
|
+
attempt.connection._source?.provider ?? provider,
|
|
460
|
+
attempt.connection._source?.providerName ?? providerName,
|
|
461
|
+
);
|
|
377
462
|
} catch (error) {
|
|
378
463
|
rethrowIfAborted(error, signal);
|
|
379
464
|
if (this.reconnect && isRetriableConnectionError(error)) {
|
|
@@ -418,6 +503,8 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
418
503
|
readonly approval = "write" as const;
|
|
419
504
|
/** Render completed MCP calls with the result header replacing the pending call header. */
|
|
420
505
|
readonly mergeCallAndResult = true;
|
|
506
|
+
/** See {@link MCPTool.strict}: MCP servers own validation, so stay non-strict. */
|
|
507
|
+
readonly strict = false as const;
|
|
421
508
|
|
|
422
509
|
readonly #fallbackProvider: string | undefined;
|
|
423
510
|
readonly #fallbackProviderName: string | undefined;
|
|
@@ -474,13 +561,31 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
474
561
|
const connection = await untilAborted(signal, () => this.getConnection());
|
|
475
562
|
throwIfAborted(signal);
|
|
476
563
|
try {
|
|
477
|
-
const
|
|
564
|
+
const attempt = await callToolWithAuthRetry(connection, this.tool.name, args, this.reconnect, signal);
|
|
565
|
+
if (attempt.error !== undefined) {
|
|
566
|
+
return buildErrorResult(
|
|
567
|
+
attempt.error,
|
|
568
|
+
this.serverName,
|
|
569
|
+
this.tool.name,
|
|
570
|
+
attempt.connection._source?.provider ?? provider,
|
|
571
|
+
attempt.connection._source?.providerName ?? providerName,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
if (!attempt.result) {
|
|
575
|
+
return buildErrorResult(
|
|
576
|
+
new Error("MCP tool call returned no result"),
|
|
577
|
+
this.serverName,
|
|
578
|
+
this.tool.name,
|
|
579
|
+
provider,
|
|
580
|
+
providerName,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
478
583
|
return buildResult(
|
|
479
|
-
result,
|
|
584
|
+
attempt.result,
|
|
480
585
|
this.serverName,
|
|
481
586
|
this.tool.name,
|
|
482
|
-
connection._source?.provider ?? provider,
|
|
483
|
-
connection._source?.providerName ?? providerName,
|
|
587
|
+
attempt.connection._source?.provider ?? provider,
|
|
588
|
+
attempt.connection._source?.providerName ?? providerName,
|
|
484
589
|
);
|
|
485
590
|
} catch (callError) {
|
|
486
591
|
rethrowIfAborted(callError, signal);
|
package/src/mcp/types.ts
CHANGED
|
@@ -206,10 +206,17 @@ export interface MCPResourceContent {
|
|
|
206
206
|
|
|
207
207
|
export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
|
|
208
208
|
|
|
209
|
+
/** Structured authentication challenge returned in a tool result. */
|
|
210
|
+
export interface MCPAuthChallenge {
|
|
211
|
+
/** Values from `_meta["mcp/www_authenticate"]`. */
|
|
212
|
+
readonly wwwAuthenticate: readonly string[];
|
|
213
|
+
}
|
|
214
|
+
|
|
209
215
|
/** tools/call response */
|
|
210
216
|
export interface MCPToolCallResult {
|
|
211
217
|
content: MCPContent[];
|
|
212
218
|
isError?: boolean;
|
|
219
|
+
_meta?: Record<string, unknown>;
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
// =============================================================================
|
package/src/mnemopi/state.ts
CHANGED
|
@@ -158,6 +158,39 @@ export interface MnemopiScopedMemoryHit {
|
|
|
158
158
|
|
|
159
159
|
type MnemopiRetentionMessage = { role: string; content: string };
|
|
160
160
|
|
|
161
|
+
interface MnemopiRetentionCursorRow {
|
|
162
|
+
content: string;
|
|
163
|
+
sourceId: string | null;
|
|
164
|
+
retainedThroughUserTurn: number | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function countRetainedUserTurns(transcript: string): number {
|
|
168
|
+
let turns = 0;
|
|
169
|
+
for (const line of transcript.split(/\r?\n/)) {
|
|
170
|
+
if (line === "[role: user]") turns++;
|
|
171
|
+
}
|
|
172
|
+
return turns;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function deriveRetainedTurnCursor(rows: readonly MnemopiRetentionCursorRow[], sessionId: string): number {
|
|
176
|
+
let cursor = 0;
|
|
177
|
+
for (const row of rows) {
|
|
178
|
+
if (Number.isInteger(row.retainedThroughUserTurn) && row.retainedThroughUserTurn !== null) {
|
|
179
|
+
cursor = Math.max(cursor, row.retainedThroughUserTurn);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (row.sourceId !== sessionId && !row.sourceId?.startsWith(`${sessionId}-`)) continue;
|
|
183
|
+
// Legacy rows carry no explicit cursor. Summing incremental rows looks
|
|
184
|
+
// right, but pre-fix resumed sessions also wrote cumulative rows under the
|
|
185
|
+
// incremental `${sessionId}-<ts>` id shape, so a sum can overshoot the real
|
|
186
|
+
// retained prefix and permanently skip unseen turns. Per-row max can only
|
|
187
|
+
// under-count, which at worst re-stores one suffix before an explicit
|
|
188
|
+
// cursor row takes over.
|
|
189
|
+
cursor = Math.max(cursor, countRetainedUserTurns(row.content));
|
|
190
|
+
}
|
|
191
|
+
return cursor;
|
|
192
|
+
}
|
|
193
|
+
|
|
161
194
|
function sliceUnretainedMessages(
|
|
162
195
|
messages: MnemopiRetentionMessage[],
|
|
163
196
|
lastRetainedTurn: number,
|
|
@@ -208,6 +241,7 @@ export class MnemopiSessionState {
|
|
|
208
241
|
hasRecalledForFirstTurn: boolean;
|
|
209
242
|
lastRecallSnippet?: string;
|
|
210
243
|
unsubscribe?: () => void;
|
|
244
|
+
#retentionCursorLoaded = false;
|
|
211
245
|
|
|
212
246
|
constructor(options: MnemopiSessionStateOptions) {
|
|
213
247
|
this.sessionId = options.sessionId;
|
|
@@ -222,11 +256,15 @@ export class MnemopiSessionState {
|
|
|
222
256
|
}
|
|
223
257
|
|
|
224
258
|
setSessionId(sessionId: string): void {
|
|
259
|
+
if (this.sessionId === sessionId) return;
|
|
225
260
|
this.sessionId = sessionId;
|
|
261
|
+
this.lastRetainedTurn = 0;
|
|
262
|
+
this.#retentionCursorLoaded = false;
|
|
226
263
|
}
|
|
227
264
|
|
|
228
265
|
resetConversationTracking(): void {
|
|
229
266
|
this.lastRetainedTurn = 0;
|
|
267
|
+
this.#retentionCursorLoaded = false;
|
|
230
268
|
this.hasRecalledForFirstTurn = false;
|
|
231
269
|
this.lastRecallSnippet = undefined;
|
|
232
270
|
}
|
|
@@ -445,11 +483,13 @@ export class MnemopiSessionState {
|
|
|
445
483
|
async maybeRetainOnAgentEnd(_messages: AgentMessage[]): Promise<void> {
|
|
446
484
|
if (!this.config.autoRetain || this.aliasOf) return;
|
|
447
485
|
const flat = extractMessages(this.session.sessionManager);
|
|
486
|
+
this.#restoreRetainedTurnCursor();
|
|
448
487
|
const userTurns = flat.filter(message => message.role === "user").length;
|
|
449
488
|
if (userTurns - this.lastRetainedTurn < this.config.retainEveryNTurns) return;
|
|
450
489
|
await this.retainMessages(
|
|
451
490
|
sliceUnretainedMessages(flat, this.lastRetainedTurn),
|
|
452
491
|
`${this.sessionId}-${Date.now()}`,
|
|
492
|
+
{ retainedThroughUserTurn: userTurns },
|
|
453
493
|
);
|
|
454
494
|
this.lastRetainedTurn = userTurns;
|
|
455
495
|
}
|
|
@@ -457,14 +497,19 @@ export class MnemopiSessionState {
|
|
|
457
497
|
async forceRetainCurrentSession(options: { extract?: boolean } = {}): Promise<void> {
|
|
458
498
|
if (this.aliasOf) return;
|
|
459
499
|
const flat = extractMessages(this.session.sessionManager);
|
|
460
|
-
|
|
461
|
-
|
|
500
|
+
this.#restoreRetainedTurnCursor();
|
|
501
|
+
const userTurns = flat.filter(message => message.role === "user").length;
|
|
502
|
+
await this.retainMessages(sliceUnretainedMessages(flat, this.lastRetainedTurn), this.sessionId, {
|
|
503
|
+
...options,
|
|
504
|
+
retainedThroughUserTurn: userTurns,
|
|
505
|
+
});
|
|
506
|
+
this.lastRetainedTurn = Math.max(this.lastRetainedTurn, userTurns);
|
|
462
507
|
}
|
|
463
508
|
|
|
464
509
|
async retainMessages(
|
|
465
510
|
messages: Array<{ role: string; content: string }>,
|
|
466
511
|
sourceId: string,
|
|
467
|
-
options: { extract?: boolean } = {},
|
|
512
|
+
options: { extract?: boolean; retainedThroughUserTurn?: number } = {},
|
|
468
513
|
): Promise<void> {
|
|
469
514
|
const { transcript, messageCount } = prepareRetentionTranscript(messages, true);
|
|
470
515
|
if (!transcript) return;
|
|
@@ -478,6 +523,9 @@ export class MnemopiSessionState {
|
|
|
478
523
|
session_id: this.sessionId,
|
|
479
524
|
source_id: sourceId,
|
|
480
525
|
message_count: messageCount,
|
|
526
|
+
...(options.retainedThroughUserTurn === undefined
|
|
527
|
+
? {}
|
|
528
|
+
: { retained_through_user_turn: options.retainedThroughUserTurn }),
|
|
481
529
|
cwd: this.session.sessionManager.getCwd(),
|
|
482
530
|
},
|
|
483
531
|
scope: "bank",
|
|
@@ -490,6 +538,25 @@ export class MnemopiSessionState {
|
|
|
490
538
|
});
|
|
491
539
|
}
|
|
492
540
|
|
|
541
|
+
#restoreRetainedTurnCursor(): void {
|
|
542
|
+
if (this.#retentionCursorLoaded) return;
|
|
543
|
+
this.#retentionCursorLoaded = true;
|
|
544
|
+
const rows = this.memory.beam.db
|
|
545
|
+
.prepare<MnemopiRetentionCursorRow, [string]>(`
|
|
546
|
+
SELECT
|
|
547
|
+
content,
|
|
548
|
+
json_extract(metadata_json, '$.source_id') AS sourceId,
|
|
549
|
+
CAST(json_extract(metadata_json, '$.retained_through_user_turn') AS INTEGER)
|
|
550
|
+
AS retainedThroughUserTurn
|
|
551
|
+
FROM working_memory
|
|
552
|
+
WHERE source = 'coding-agent-transcript'
|
|
553
|
+
AND json_extract(metadata_json, '$.session_id') = ?
|
|
554
|
+
ORDER BY rowid
|
|
555
|
+
`)
|
|
556
|
+
.all(this.sessionId);
|
|
557
|
+
this.lastRetainedTurn = Math.max(this.lastRetainedTurn, deriveRetainedTurnCursor(rows, this.sessionId));
|
|
558
|
+
}
|
|
559
|
+
|
|
493
560
|
attachSessionListeners(): void {
|
|
494
561
|
this.unsubscribe?.();
|
|
495
562
|
this.unsubscribe = this.session.subscribe((event: AgentSessionEvent) => {
|
|
@@ -49,6 +49,7 @@ import agentCreationArchitectPrompt from "../../prompts/system/agent-creation-ar
|
|
|
49
49
|
import agentCreationUserPrompt from "../../prompts/system/agent-creation-user.md" with { type: "text" };
|
|
50
50
|
import { createAgentSession } from "../../sdk";
|
|
51
51
|
import { discoverAgents } from "../../task/discovery";
|
|
52
|
+
import { resolveAgentPrewalkDefault } from "../../task/prewalk";
|
|
52
53
|
import type { AgentDefinition, AgentSource } from "../../task/types";
|
|
53
54
|
import { shortenPath } from "../../tools/render-utils";
|
|
54
55
|
import { getEditorTheme, theme } from "../theme/theme";
|
|
@@ -602,7 +603,7 @@ export class AgentDashboard extends Container {
|
|
|
602
603
|
this.#persistPrewalkOverrides();
|
|
603
604
|
const pattern = resolveAgentPrewalkPattern({
|
|
604
605
|
settingsOverride: selected.prewalkOverride,
|
|
605
|
-
agentPrewalk: selected.prewalk,
|
|
606
|
+
agentPrewalk: resolveAgentPrewalkDefault(selected, this.#settingsManager?.get("task.prewalk") ?? false),
|
|
606
607
|
});
|
|
607
608
|
const state = selected.prewalkOverride ?? "agent default";
|
|
608
609
|
this.#notice = `Prewalk for ${selected.name}: ${state}${pattern ? ` (into ${pattern})` : ""}`;
|
|
@@ -1081,7 +1082,10 @@ export class AgentDashboard extends Container {
|
|
|
1081
1082
|
const prewalkPattern = selected
|
|
1082
1083
|
? resolveAgentPrewalkPattern({
|
|
1083
1084
|
settingsOverride: selected.prewalkOverride,
|
|
1084
|
-
agentPrewalk:
|
|
1085
|
+
agentPrewalk: resolveAgentPrewalkDefault(
|
|
1086
|
+
selected,
|
|
1087
|
+
this.#settingsManager?.get("task.prewalk") ?? false,
|
|
1088
|
+
),
|
|
1085
1089
|
})
|
|
1086
1090
|
: undefined;
|
|
1087
1091
|
const prewalkResolution = prewalkPattern ? this.#resolvePatterns([prewalkPattern]) : undefined;
|