jeopi-cli 16.4.2 → 16.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/dist/cli.js +3033 -2991
- package/dist/types/advisor/advise-tool.d.ts +5 -1
- package/dist/types/advisor/config.d.ts +4 -3
- package/dist/types/advisor/runtime.d.ts +15 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/commit/agentic/agent.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +7 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +3 -13
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -6
- package/dist/types/internal-urls/types.d.ts +7 -0
- package/dist/types/irc/bus.d.ts +4 -0
- package/dist/types/lsp/deferred-diagnostics.d.ts +11 -0
- package/dist/types/modes/components/move-overlay.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +27 -7
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +9 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/auth-broker-config.d.ts +2 -2
- package/dist/types/tools/bash-skill-urls.d.ts +3 -2
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -1
- package/dist/types/tools/browser/cmux/rpc.d.ts +20 -0
- package/dist/types/tools/browser/launch.d.ts +1 -0
- package/dist/types/tools/browser/run-cancellation.d.ts +17 -2
- package/dist/types/tools/browser/run-output.d.ts +25 -0
- package/dist/types/tools/browser/tab-worker.d.ts +22 -0
- package/dist/types/utils/changelog.d.ts +35 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +13 -0
- package/src/advisor/__tests__/config.test.ts +36 -0
- package/src/advisor/advise-tool.ts +6 -1
- package/src/advisor/config.ts +11 -8
- package/src/advisor/runtime.ts +127 -0
- package/src/autolearn/controller.ts +15 -3
- package/src/cli/args.ts +12 -0
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +63 -13
- package/src/cli/usage-error.ts +7 -0
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commit/agentic/agent.ts +4 -0
- package/src/commit/agentic/index.ts +46 -21
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +24 -6
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +3 -15
- package/src/edit/index.ts +10 -76
- package/src/edit/renderer.ts +13 -10
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +60 -0
- package/src/eval/executor-base.ts +84 -7
- package/src/eval/js/shared/rewrite-imports.ts +31 -13
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/ttsr.ts +0 -3
- package/src/extensibility/custom-tools/loader.ts +70 -37
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +9 -1
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +30 -3
- package/src/extensibility/plugins/manager.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/memory-protocol.ts +13 -8
- package/src/internal-urls/skill-protocol.ts +1 -1
- package/src/internal-urls/types.ts +7 -0
- package/src/irc/bus.ts +48 -18
- package/src/lsp/client.ts +7 -1
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/main.ts +13 -13
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/mcp/transports/stdio.test.ts +45 -1
- package/src/mcp/transports/stdio.ts +6 -3
- package/src/modes/acp/acp-agent.ts +73 -2
- package/src/modes/acp/acp-event-mapper.ts +5 -0
- package/src/modes/acp/acp-mode.ts +11 -0
- package/src/modes/components/__tests__/move-overlay.test.ts +16 -1
- package/src/modes/components/advisor-config.ts +15 -7
- package/src/modes/components/chat-transcript-builder.ts +31 -11
- package/src/modes/components/move-overlay.ts +2 -3
- package/src/modes/components/plan-review-overlay.ts +7 -0
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/welcome.ts +13 -14
- package/src/modes/controllers/command-controller.ts +8 -10
- package/src/modes/controllers/event-controller.ts +58 -42
- package/src/modes/controllers/input-controller.ts +9 -6
- package/src/modes/controllers/selector-controller.ts +3 -2
- package/src/modes/controllers/tan-command-controller.ts +40 -1
- package/src/modes/interactive-mode.ts +13 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/rpc/host-tools.ts +13 -0
- package/src/modes/rpc/rpc-mode.ts +130 -56
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +44 -0
- package/src/modes/utils/ui-helpers.ts +16 -1
- package/src/modes/workflow.ts +5 -4
- package/src/prompts/system/eager-task.md +2 -2
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/tools/browser.md +5 -5
- package/src/sdk.ts +3 -0
- package/src/session/agent-session.ts +267 -36
- package/src/session/auth-broker-config.ts +4 -4
- package/src/session/snapcompact-inline.ts +3 -19
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/builtin-registry.ts +8 -8
- package/src/task/executor.ts +8 -0
- package/src/task/render.ts +51 -44
- package/src/tools/bash-skill-urls.ts +7 -4
- package/src/tools/bash.ts +30 -33
- package/src/tools/browser/cmux/cmux-tab.ts +49 -50
- package/src/tools/browser/cmux/rpc.ts +50 -0
- package/src/tools/browser/launch.ts +8 -4
- package/src/tools/browser/run-cancellation.ts +44 -5
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/tab-worker.ts +280 -133
- package/src/tools/eval-render.ts +16 -14
- package/src/tools/glob.ts +20 -6
- package/src/tools/grep.ts +1 -0
- package/src/tools/image-gen.ts +3 -4
- package/src/tools/irc.ts +27 -13
- package/src/tools/path-utils.ts +6 -1
- package/src/tools/read.ts +14 -7
- package/src/tools/write.ts +22 -5
- package/src/tools/yield.ts +29 -1
- package/src/utils/changelog.ts +106 -9
- package/src/utils/git.ts +0 -1
- package/src/utils/title-generator.ts +72 -7
- package/src/web/search/index.ts +14 -6
- package/src/web/search/providers/perplexity.ts +18 -2
- package/dist/types/tools/bash-command-fixup.d.ts +0 -11
- package/src/tools/bash-command-fixup.ts +0 -37
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
* Precedence (highest first):
|
|
10
10
|
* 1. `JEOPI_AUTH_BROKER_URL` / `JEOPI_AUTH_BROKER_TOKEN` env vars.
|
|
11
11
|
* 2. `auth.broker.url` / `auth.broker.token` in `~/.jeopi/agent/config.yml`
|
|
12
|
-
* (hidden from the settings UI; `!command` resolution supported).
|
|
12
|
+
* or `~/.jeopi/agent/config.yaml` (hidden from the settings UI; `!command` resolution supported).
|
|
13
13
|
* 3. Token file `~/.jeopi/auth-broker.token` (paired with URL from env or config).
|
|
14
14
|
*
|
|
15
15
|
* Returns null when no broker URL is configured — caller falls back to the
|
|
16
16
|
* local SQLite store.
|
|
17
17
|
*
|
|
18
|
-
* Reads config
|
|
18
|
+
* Reads config YAML directly (instead of going through `Settings.init`) because
|
|
19
19
|
* `discoverAuthStorage` runs before the settings singleton is initialized in
|
|
20
20
|
* `runRootCommand`, and we want hand-edited config entries to be honoured at
|
|
21
21
|
* boot without forcing a startup reorder.
|
|
@@ -36,10 +36,10 @@ export { type AuthBrokerClientConfig, getAuthBrokerTokenFilePath };
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Process-lifetime memo for {@link resolveAuthBrokerConfig}. Keyed on the env
|
|
39
|
-
* inputs (plus agent dir, which decides which config
|
|
39
|
+
* inputs (plus agent dir, which decides which config YAML is read) so tests
|
|
40
40
|
* that flip `JEOPI_AUTH_BROKER_*` between cases still observe the change, while
|
|
41
41
|
* repeated resolution within one CLI invocation (startup, subagent sessions)
|
|
42
|
-
* skips the config
|
|
42
|
+
* skips the config YAML read and any `!command` token resolution.
|
|
43
43
|
*/
|
|
44
44
|
let cachedConfigKey: string | null = null;
|
|
45
45
|
let cachedConfigPromise: Promise<AuthBrokerClientConfig | null> | null = null;
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
import { countTokens } from "jeopi-agent-core";
|
|
18
18
|
import type { Context, ImageContent, Model, TextContent, ToolResultMessage, UserMessage } from "jeopi-ai";
|
|
19
|
-
import { isPersonalGitHubCopilotBaseUrl } from "jeopi-catalog/wire/github-copilot";
|
|
20
19
|
import * as snapcompact from "jeopi-snapcompact";
|
|
21
20
|
import contextFramesNote from "../prompts/system/snapcompact-context-frames-note.md" with { type: "text" };
|
|
22
21
|
import contextStub from "../prompts/system/snapcompact-context-stub.md" with { type: "text" };
|
|
@@ -78,19 +77,6 @@ function passesSavingsGate(frames: number, shape: snapcompact.Shape, textTokens:
|
|
|
78
77
|
return frames * shape.frameTokenEstimate <= textTokens * SAVINGS_MARGIN;
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
/**
|
|
82
|
-
* The model is vision-capable for the endpoint we're actually about to hit.
|
|
83
|
-
* GitHub Copilot business and enterprise hosts respond `400 vision is not
|
|
84
|
-
* supported` on image inputs (issue #3387), so even if a stale cached spec
|
|
85
|
-
* still advertises `["text","image"]` we MUST not rasterize transcripts when
|
|
86
|
-
* the resolved `baseUrl` is non-personal.
|
|
87
|
-
*/
|
|
88
|
-
function canSendImages(model: Model): boolean {
|
|
89
|
-
if (!model.input.includes("image")) return false;
|
|
90
|
-
if (model.provider === "github-copilot" && !isPersonalGitHubCopilotBaseUrl(model.baseUrl)) return false;
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
80
|
interface SystemPromptImageTarget {
|
|
95
81
|
scope: Exclude<SnapcompactSystemPromptMode, "none">;
|
|
96
82
|
text: string;
|
|
@@ -291,7 +277,7 @@ export function estimateInlineSavings(input: {
|
|
|
291
277
|
messages: readonly InlineMessageView[];
|
|
292
278
|
}): SnapcompactSavingsEstimate {
|
|
293
279
|
const { options, model } = input;
|
|
294
|
-
if (!model
|
|
280
|
+
if (!model?.input.includes("image")) {
|
|
295
281
|
return { visionCapable: false, savedTokens: 0 };
|
|
296
282
|
}
|
|
297
283
|
|
|
@@ -430,10 +416,8 @@ export class SnapcompactInlineTransformer {
|
|
|
430
416
|
|
|
431
417
|
async transform(context: Context, model: Model): Promise<Context> {
|
|
432
418
|
// Vision gate: providers silently DROP images on text-only models —
|
|
433
|
-
// rendering would lose the content entirely.
|
|
434
|
-
|
|
435
|
-
// (issue #3387: Copilot business endpoint).
|
|
436
|
-
if (!canSendImages(model)) return context;
|
|
419
|
+
// rendering would lose the content entirely.
|
|
420
|
+
if (!model.input.includes("image")) return context;
|
|
437
421
|
|
|
438
422
|
const shape = snapcompact.resolveShape(model, this.options.shape);
|
|
439
423
|
const budget = snapcompact.providerImageBudget(model.provider) - countContextImages(context);
|
|
@@ -735,6 +735,7 @@ export class OutputSink {
|
|
|
735
735
|
#truncated = false;
|
|
736
736
|
#lastChunkTime = 0;
|
|
737
737
|
#pendingChunk = "";
|
|
738
|
+
#pendingChunkTimer: Timer | undefined;
|
|
738
739
|
|
|
739
740
|
// Per-line column cap streaming state (persists across `push` calls so a
|
|
740
741
|
// long line split across chunks still trips the same trigger).
|
|
@@ -806,20 +807,18 @@ export class OutputSink {
|
|
|
806
807
|
push(chunk: string): void {
|
|
807
808
|
chunk = sanitizeWithOptionalSixelPassthrough(chunk, sanitizeText);
|
|
808
809
|
|
|
809
|
-
// Throttled onChunk: coalesce chunks arriving inside the throttle window
|
|
810
|
-
//
|
|
811
|
-
// final
|
|
810
|
+
// Throttled onChunk: coalesce chunks arriving inside the throttle window.
|
|
811
|
+
// A timer flushes quiet tails at the throttle boundary; dump() catches a
|
|
812
|
+
// final pending chunk when the process exits before that timer fires.
|
|
812
813
|
// Live preview gets the raw (pre-cap) chunk so the TUI never lags behind
|
|
813
814
|
// what reached the sink — the column cap is for the persisted LLM view.
|
|
814
815
|
if (this.#onChunk) {
|
|
815
816
|
const now = Date.now();
|
|
816
817
|
if (now - this.#lastChunkTime >= this.#chunkThrottleMs) {
|
|
817
|
-
this.#
|
|
818
|
-
const merged = this.#pendingChunk + chunk;
|
|
819
|
-
this.#pendingChunk = "";
|
|
820
|
-
this.#onChunk(merged);
|
|
818
|
+
this.#emitPendingChunkWith(chunk, now);
|
|
821
819
|
} else {
|
|
822
820
|
this.#pendingChunk += chunk;
|
|
821
|
+
this.#schedulePendingChunkFlush();
|
|
823
822
|
}
|
|
824
823
|
}
|
|
825
824
|
|
|
@@ -1121,6 +1120,7 @@ export class OutputSink {
|
|
|
1121
1120
|
* branch in `dump()` against stale totals.
|
|
1122
1121
|
*/
|
|
1123
1122
|
replace(text: string): void {
|
|
1123
|
+
this.#clearPendingChunkTimer();
|
|
1124
1124
|
this.#buffer = text;
|
|
1125
1125
|
this.#bufferBytes = Buffer.byteLength(text, "utf-8");
|
|
1126
1126
|
this.#head = "";
|
|
@@ -1138,6 +1138,38 @@ export class OutputSink {
|
|
|
1138
1138
|
this.#pendingChunk = "";
|
|
1139
1139
|
}
|
|
1140
1140
|
|
|
1141
|
+
#clearPendingChunkTimer(): void {
|
|
1142
|
+
if (!this.#pendingChunkTimer) return;
|
|
1143
|
+
clearTimeout(this.#pendingChunkTimer);
|
|
1144
|
+
this.#pendingChunkTimer = undefined;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
#emitPendingChunkWith(chunk: string, now: number): void {
|
|
1148
|
+
this.#clearPendingChunkTimer();
|
|
1149
|
+
this.#lastChunkTime = now;
|
|
1150
|
+
const merged = this.#pendingChunk + chunk;
|
|
1151
|
+
this.#pendingChunk = "";
|
|
1152
|
+
this.#onChunk?.(merged);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
#flushPendingChunk(): void {
|
|
1156
|
+
if (this.#pendingChunk.length === 0) {
|
|
1157
|
+
this.#clearPendingChunkTimer();
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
this.#emitPendingChunkWith("", Date.now());
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
#schedulePendingChunkFlush(): void {
|
|
1164
|
+
if (this.#chunkThrottleMs <= 0 || this.#pendingChunkTimer) return;
|
|
1165
|
+
const elapsed = Date.now() - this.#lastChunkTime;
|
|
1166
|
+
const delay = Math.max(0, this.#chunkThrottleMs - elapsed);
|
|
1167
|
+
this.#pendingChunkTimer = setTimeout(() => {
|
|
1168
|
+
this.#pendingChunkTimer = undefined;
|
|
1169
|
+
this.#flushPendingChunk();
|
|
1170
|
+
}, delay);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1141
1173
|
/**
|
|
1142
1174
|
* Replay the rolling tail ring back into the artifact sink. When bytes
|
|
1143
1175
|
* were actually dropped from the middle (the head budget was exhausted
|
|
@@ -1179,11 +1211,7 @@ export class OutputSink {
|
|
|
1179
1211
|
|
|
1180
1212
|
// Flush any chunk still held back by the throttle so the live preview
|
|
1181
1213
|
// ends with the complete stream.
|
|
1182
|
-
|
|
1183
|
-
const pending = this.#pendingChunk;
|
|
1184
|
-
this.#pendingChunk = "";
|
|
1185
|
-
this.#onChunk(pending);
|
|
1186
|
-
}
|
|
1214
|
+
this.#flushPendingChunk();
|
|
1187
1215
|
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
1188
1216
|
|
|
1189
1217
|
if (this.#file) {
|
|
@@ -34,7 +34,12 @@ import { resolveResumableSession } from "../session/session-listing";
|
|
|
34
34
|
import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
35
35
|
import { expandTilde, resolveToCwd } from "../tools/path-utils";
|
|
36
36
|
import { urlHyperlinkAlways } from "../tui";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
getChangelogPath,
|
|
39
|
+
parseChangelog,
|
|
40
|
+
RECENT_CHANGELOG_ENTRY_LIMIT,
|
|
41
|
+
renderChangelogEntries,
|
|
42
|
+
} from "../utils/changelog";
|
|
38
43
|
import { copyToClipboard } from "../utils/clipboard";
|
|
39
44
|
import { CollabQrCodeComponent } from "./helpers/collab-qrcode";
|
|
40
45
|
import { buildContextReportText } from "./helpers/context-report";
|
|
@@ -1100,17 +1105,12 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
1100
1105
|
const changelogPath = getChangelogPath();
|
|
1101
1106
|
const allEntries = await parseChangelog(changelogPath);
|
|
1102
1107
|
const showFull = command.args.trim().toLowerCase() === "full";
|
|
1103
|
-
const entriesToShow = showFull ? allEntries : allEntries.slice(0,
|
|
1108
|
+
const entriesToShow = showFull ? allEntries : allEntries.slice(0, RECENT_CHANGELOG_ENTRY_LIMIT);
|
|
1104
1109
|
if (entriesToShow.length === 0) {
|
|
1105
1110
|
await runtime.output("No changelog entries found.");
|
|
1106
1111
|
return commandConsumed();
|
|
1107
1112
|
}
|
|
1108
|
-
await runtime.output(
|
|
1109
|
-
[...entriesToShow]
|
|
1110
|
-
.reverse()
|
|
1111
|
-
.map(entry => entry.content)
|
|
1112
|
-
.join("\n\n"),
|
|
1113
|
-
);
|
|
1113
|
+
await runtime.output(renderChangelogEntries(entriesToShow).markdown);
|
|
1114
1114
|
return commandConsumed();
|
|
1115
1115
|
},
|
|
1116
1116
|
handleTui: async (command, runtime) => {
|
package/src/task/executor.ts
CHANGED
|
@@ -2038,14 +2038,22 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2038
2038
|
thinkingLevel: resolvedThinkingLevel,
|
|
2039
2039
|
explicitThinkingLevel,
|
|
2040
2040
|
authFallbackUsed,
|
|
2041
|
+
warning: modelResolutionWarning,
|
|
2041
2042
|
} = await awaitAbortable(
|
|
2042
2043
|
resolveModelOverrideWithAuthFallback(
|
|
2043
2044
|
modelPatterns,
|
|
2044
2045
|
options.parentActiveModelPattern,
|
|
2045
2046
|
modelRegistry,
|
|
2046
2047
|
settings,
|
|
2048
|
+
id,
|
|
2047
2049
|
),
|
|
2048
2050
|
);
|
|
2051
|
+
if (modelResolutionWarning) {
|
|
2052
|
+
logger.warn("Subagent model resolution warning", {
|
|
2053
|
+
warning: modelResolutionWarning,
|
|
2054
|
+
requested: modelPatterns,
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2049
2057
|
if (authFallbackUsed && model) {
|
|
2050
2058
|
logger.warn("Subagent model has no working credentials; falling back to parent session model", {
|
|
2051
2059
|
requested: modelPatterns,
|
package/src/task/render.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import type { Component } from "jeopi-tui";
|
|
9
9
|
import { Container, Markdown, Text } from "jeopi-tui";
|
|
10
|
-
import { formatNumber } from "jeopi-utils";
|
|
10
|
+
import { formatNumber, sanitizeText } from "jeopi-utils";
|
|
11
11
|
import { settings } from "../config/settings";
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
@@ -249,11 +249,11 @@ function getRenderYieldLabels(type: RenderYieldItem["type"]): string[] {
|
|
|
249
249
|
function formatYieldPreview(item: RenderYieldItem): string {
|
|
250
250
|
if (item.useLastTurn === true && item.data === undefined) return "last assistant turn";
|
|
251
251
|
if (item.data === undefined) return "last assistant turn";
|
|
252
|
-
if (typeof item.data === "string") return previewLine(replaceTabs(item.data), 70);
|
|
252
|
+
if (typeof item.data === "string") return previewLine(replaceTabs(sanitizeText(item.data)), 70);
|
|
253
253
|
try {
|
|
254
|
-
return previewLine(replaceTabs(JSON.stringify(item.data) ?? "null"), 70);
|
|
254
|
+
return previewLine(replaceTabs(sanitizeText(JSON.stringify(item.data) ?? "null")), 70);
|
|
255
255
|
} catch {
|
|
256
|
-
return previewLine(replaceTabs(String(item.data)), 70);
|
|
256
|
+
return previewLine(replaceTabs(sanitizeText(String(item.data))), 70);
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -281,7 +281,7 @@ function renderTypedYieldSections(value: unknown, continuePrefix: string, expand
|
|
|
281
281
|
function formatJsonScalar(value: unknown, _theme: Theme): string {
|
|
282
282
|
if (value === null) return "null";
|
|
283
283
|
if (typeof value === "string") {
|
|
284
|
-
const trimmed = truncateToWidth(value, 70);
|
|
284
|
+
const trimmed = truncateToWidth(sanitizeText(value), 70);
|
|
285
285
|
return `"${trimmed}"`;
|
|
286
286
|
}
|
|
287
287
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
@@ -291,8 +291,9 @@ function formatJsonScalar(value: unknown, _theme: Theme): string {
|
|
|
291
291
|
export function formatTaskId(id: string): string {
|
|
292
292
|
// Ids are name-based (e.g. "Anna", "Anna-2"); a "." separates nesting levels
|
|
293
293
|
// (e.g. "Anna.Bob"). Render the hierarchy with a ">" breadcrumb.
|
|
294
|
-
const
|
|
295
|
-
|
|
294
|
+
const sanitizedId = sanitizeText(id);
|
|
295
|
+
const segments = sanitizedId.split(".");
|
|
296
|
+
return segments.length < 2 ? sanitizedId : segments.join(">");
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
const MISSING_YIELD_WARNING_PREFIX = "SYSTEM WARNING: Subagent exited without calling yield tool";
|
|
@@ -347,13 +348,13 @@ function renderJsonTreeLines(
|
|
|
347
348
|
const scalar = formatJsonScalar(val, theme);
|
|
348
349
|
|
|
349
350
|
if (scalar) {
|
|
350
|
-
const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
|
|
351
|
+
const label = key ? theme.fg("muted", sanitizeText(key)) : theme.fg("muted", "value");
|
|
351
352
|
pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", scalar)}`);
|
|
352
353
|
return;
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
if (Array.isArray(val)) {
|
|
356
|
-
const header = key ? theme.fg("muted", key) : theme.fg("muted", "array");
|
|
357
|
+
const header = key ? theme.fg("muted", sanitizeText(key)) : theme.fg("muted", "array");
|
|
357
358
|
pushLine(`${prefix}${iconArray} ${header}`);
|
|
358
359
|
if (val.length === 0) {
|
|
359
360
|
pushLine(
|
|
@@ -385,7 +386,7 @@ function renderJsonTreeLines(
|
|
|
385
386
|
}
|
|
386
387
|
|
|
387
388
|
if (val && typeof val === "object") {
|
|
388
|
-
const header = key ? theme.fg("muted", key) : theme.fg("muted", "object");
|
|
389
|
+
const header = key ? theme.fg("muted", sanitizeText(key)) : theme.fg("muted", "object");
|
|
389
390
|
pushLine(`${prefix}${iconObject} ${header}`);
|
|
390
391
|
const entries = Object.entries(val as Record<string, unknown>);
|
|
391
392
|
if (entries.length === 0) {
|
|
@@ -418,8 +419,8 @@ function renderJsonTreeLines(
|
|
|
418
419
|
return;
|
|
419
420
|
}
|
|
420
421
|
|
|
421
|
-
const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
|
|
422
|
-
pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", String(val))}`);
|
|
422
|
+
const label = key ? theme.fg("muted", sanitizeText(key)) : theme.fg("muted", "value");
|
|
423
|
+
pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", sanitizeText(String(val)))}`);
|
|
423
424
|
};
|
|
424
425
|
|
|
425
426
|
const renderRoot = (val: unknown) => {
|
|
@@ -466,7 +467,7 @@ function stripRecentOutputNoticeLine(text: string): string {
|
|
|
466
467
|
}
|
|
467
468
|
|
|
468
469
|
function sanitizeRecentOutput(output: string): string {
|
|
469
|
-
let text = output.trimEnd();
|
|
470
|
+
let text = sanitizeText(output).trimEnd();
|
|
470
471
|
while (text) {
|
|
471
472
|
const withoutArtifactNotice = stripRawOutputArtifactNotice(text).text;
|
|
472
473
|
if (withoutArtifactNotice !== text) {
|
|
@@ -498,7 +499,8 @@ function renderOutputSection(
|
|
|
498
499
|
warning?: string,
|
|
499
500
|
): string[] {
|
|
500
501
|
const lines: string[] = [];
|
|
501
|
-
const
|
|
502
|
+
const sanitizedOutput = sanitizeText(output);
|
|
503
|
+
const trimmedOutput = sanitizedOutput.trimEnd();
|
|
502
504
|
if (!trimmedOutput && !warning) return lines;
|
|
503
505
|
|
|
504
506
|
if (warning) {
|
|
@@ -506,7 +508,7 @@ function renderOutputSection(
|
|
|
506
508
|
lines.push(
|
|
507
509
|
`${continuePrefix} ${theme.fg("warning", theme.status.warning)} ${theme.fg(
|
|
508
510
|
"dim",
|
|
509
|
-
truncateToWidth(warning, 80),
|
|
511
|
+
truncateToWidth(sanitizeText(warning), 80),
|
|
510
512
|
)}`,
|
|
511
513
|
);
|
|
512
514
|
|
|
@@ -537,8 +539,7 @@ function renderOutputSection(
|
|
|
537
539
|
// Fall back to raw output
|
|
538
540
|
}
|
|
539
541
|
}
|
|
540
|
-
|
|
541
|
-
const outputLines = output.trimEnd().split("\n");
|
|
542
|
+
const outputLines = trimmedOutput.split("\n");
|
|
542
543
|
const previewCount = expanded ? maxExpanded : maxCollapsed;
|
|
543
544
|
for (const line of outputLines.slice(0, previewCount)) {
|
|
544
545
|
lines.push(`${continuePrefix} ${theme.fg("dim", truncateToWidth(replaceTabs(line), 70))}`);
|
|
@@ -581,8 +582,7 @@ function renderOutputSection(
|
|
|
581
582
|
}
|
|
582
583
|
|
|
583
584
|
lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
|
|
584
|
-
|
|
585
|
-
const outputLines = output.trimEnd().split("\n");
|
|
585
|
+
const outputLines = trimmedOutput.split("\n");
|
|
586
586
|
const previewCount = expanded ? maxExpanded : maxCollapsed;
|
|
587
587
|
for (const line of outputLines.slice(0, previewCount)) {
|
|
588
588
|
lines.push(`${continuePrefix} ${theme.fg("dim", truncateToWidth(replaceTabs(line), 70))}`);
|
|
@@ -603,7 +603,7 @@ function renderTaskSection(
|
|
|
603
603
|
maxExpanded = 20,
|
|
604
604
|
): string[] {
|
|
605
605
|
const lines: string[] = [];
|
|
606
|
-
const trimmed = task.trim();
|
|
606
|
+
const trimmed = sanitizeText(task).trim();
|
|
607
607
|
if (!expanded || !trimmed) return lines;
|
|
608
608
|
|
|
609
609
|
lines.push(`${continuePrefix}${theme.fg("dim", "Task")}`);
|
|
@@ -624,10 +624,11 @@ function formatScalarInline(value: unknown, maxLen: number, _theme: Theme): stri
|
|
|
624
624
|
if (typeof value === "boolean") return String(value);
|
|
625
625
|
if (typeof value === "number") return String(value);
|
|
626
626
|
if (typeof value === "string") {
|
|
627
|
-
const
|
|
628
|
-
|
|
627
|
+
const sanitizedValue = sanitizeText(value);
|
|
628
|
+
const firstLine = sanitizedValue.split("\n")[0].trim();
|
|
629
|
+
if (firstLine.length === 0) return `"" (${sanitizedValue.split("\n").length} lines)`;
|
|
629
630
|
const preview = truncateToWidth(firstLine, maxLen);
|
|
630
|
-
if (
|
|
631
|
+
if (sanitizedValue.includes("\n")) return `"${preview}…" (${sanitizedValue.split("\n").length} lines)`;
|
|
631
632
|
return `"${preview}"`;
|
|
632
633
|
}
|
|
633
634
|
if (Array.isArray(value)) return `[${value.length} items]`;
|
|
@@ -635,7 +636,7 @@ function formatScalarInline(value: unknown, maxLen: number, _theme: Theme): stri
|
|
|
635
636
|
const keys = Object.keys(value);
|
|
636
637
|
return `{${keys.length} keys}`;
|
|
637
638
|
}
|
|
638
|
-
return String(value);
|
|
639
|
+
return sanitizeText(String(value));
|
|
639
640
|
}
|
|
640
641
|
|
|
641
642
|
function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string {
|
|
@@ -662,7 +663,7 @@ function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string
|
|
|
662
663
|
|
|
663
664
|
for (const [key, value] of entries) {
|
|
664
665
|
const valueStr = formatScalarInline(value, 24, theme);
|
|
665
|
-
const pairStr = `${key}=${valueStr}`;
|
|
666
|
+
const pairStr = `${sanitizeText(key)}=${valueStr}`;
|
|
666
667
|
const addLen = pairs.length > 0 ? pairStr.length + 2 : pairStr.length; // +2 for ", "
|
|
667
668
|
|
|
668
669
|
if (totalLen + addLen > maxWidth && pairs.length > 0) {
|
|
@@ -760,7 +761,9 @@ function createAssignmentSectionRenderer(
|
|
|
760
761
|
// `renderResult` receives the raw tool args (unlike `renderCall`, which is
|
|
761
762
|
// fed through `repairTaskParams`), so undo any per-field double-encoding
|
|
762
763
|
// here too. The repair is idempotent on already-clean text.
|
|
763
|
-
const assignment =
|
|
764
|
+
const assignment = sanitizeText(
|
|
765
|
+
repairDoubleEncodedJsonString(typeof args?.assignment === "string" ? args.assignment : ""),
|
|
766
|
+
).trim();
|
|
764
767
|
if (!assignment) return undefined;
|
|
765
768
|
return createMarkdownSectionRenderer(assignment, theme);
|
|
766
769
|
}
|
|
@@ -774,7 +777,9 @@ function createContextSectionRenderer(
|
|
|
774
777
|
args: Partial<TaskParams> | undefined,
|
|
775
778
|
theme: Theme,
|
|
776
779
|
): AssignmentSectionRenderer | undefined {
|
|
777
|
-
const context =
|
|
780
|
+
const context = sanitizeText(
|
|
781
|
+
repairDoubleEncodedJsonString(typeof args?.context === "string" ? args.context : ""),
|
|
782
|
+
).trim();
|
|
778
783
|
if (!context) return undefined;
|
|
779
784
|
return createMarkdownSectionRenderer(context, theme);
|
|
780
785
|
}
|
|
@@ -858,7 +863,7 @@ function renderAgentProgress(
|
|
|
858
863
|
: "accent";
|
|
859
864
|
|
|
860
865
|
// Main status line: id: description [status] · stats · ⟨agent⟩
|
|
861
|
-
const trimmedDescription = progress.description
|
|
866
|
+
const trimmedDescription = progress.description ? sanitizeText(progress.description).trim() : undefined;
|
|
862
867
|
const description = trimmedDescription ? previewLine(trimmedDescription, 64) : undefined;
|
|
863
868
|
const displayId = formatTaskId(progress.id);
|
|
864
869
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
@@ -900,7 +905,7 @@ function renderAgentProgress(
|
|
|
900
905
|
const showBadge = settings.get("task.showResolvedModelBadge");
|
|
901
906
|
if (progress.status === "running") {
|
|
902
907
|
if (!description) {
|
|
903
|
-
const taskPreview = previewLine(progress.assignment ?? progress.task, 40);
|
|
908
|
+
const taskPreview = previewLine(sanitizeText(progress.assignment ?? progress.task), 40);
|
|
904
909
|
statusLine += ` ${theme.fg("muted", taskPreview)}`;
|
|
905
910
|
}
|
|
906
911
|
statusLine = appendAgentStats(statusLine, { ...progress, showResolvedModelBadge: showBadge }, theme);
|
|
@@ -915,10 +920,10 @@ function renderAgentProgress(
|
|
|
915
920
|
// Current tool (if running) or most recent completed tool
|
|
916
921
|
if (progress.status === "running") {
|
|
917
922
|
if (progress.currentTool) {
|
|
918
|
-
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
|
|
923
|
+
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", sanitizeText(progress.currentTool))}`;
|
|
919
924
|
const toolDetail = progress.lastIntent ?? progress.currentToolArgs;
|
|
920
925
|
if (toolDetail) {
|
|
921
|
-
toolLine += `: ${theme.fg("dim", previewLine(toolDetail, 40))}`;
|
|
926
|
+
toolLine += `: ${theme.fg("dim", previewLine(sanitizeText(toolDetail), 40))}`;
|
|
922
927
|
}
|
|
923
928
|
if (progress.currentToolStartMs) {
|
|
924
929
|
const elapsed = Date.now() - progress.currentToolStartMs;
|
|
@@ -930,10 +935,10 @@ function renderAgentProgress(
|
|
|
930
935
|
} else if (progress.recentTools.length > 0) {
|
|
931
936
|
// Show most recent completed tool when idle between tools
|
|
932
937
|
const recent = progress.recentTools[0];
|
|
933
|
-
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", recent.tool)}`;
|
|
938
|
+
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", sanitizeText(recent.tool))}`;
|
|
934
939
|
const toolDetail = progress.lastIntent ?? recent.args;
|
|
935
940
|
if (toolDetail) {
|
|
936
|
-
toolLine += `: ${theme.fg("dim", previewLine(toolDetail, 40))}`;
|
|
941
|
+
toolLine += `: ${theme.fg("dim", previewLine(sanitizeText(toolDetail), 40))}`;
|
|
937
942
|
}
|
|
938
943
|
lines.push(toolLine);
|
|
939
944
|
}
|
|
@@ -947,12 +952,12 @@ function renderAgentProgress(
|
|
|
947
952
|
const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now";
|
|
948
953
|
const summary =
|
|
949
954
|
`retrying ${progress.retryState.attempt}/${progress.retryState.maxAttempts} ${waitLabel}: ` +
|
|
950
|
-
previewLine(progress.retryState.errorMessage, 60);
|
|
955
|
+
previewLine(sanitizeText(progress.retryState.errorMessage), 60);
|
|
951
956
|
lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", summary)}`);
|
|
952
957
|
} else if (progress.retryFailure && progress.status !== "running") {
|
|
953
958
|
const summary = `auto-retry gave up after ${progress.retryFailure.attempt} attempt${
|
|
954
959
|
progress.retryFailure.attempt === 1 ? "" : "s"
|
|
955
|
-
}: ${previewLine(progress.retryFailure.errorMessage, 80)}`;
|
|
960
|
+
}: ${previewLine(sanitizeText(progress.retryFailure.errorMessage), 80)}`;
|
|
956
961
|
lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("error", summary)}`);
|
|
957
962
|
}
|
|
958
963
|
|
|
@@ -1098,13 +1103,13 @@ function renderReviewResult(
|
|
|
1098
1103
|
if (summary.explanation) {
|
|
1099
1104
|
if (expanded) {
|
|
1100
1105
|
lines.push(`${continuePrefix}${theme.fg("dim", "Summary")}`);
|
|
1101
|
-
const explanationLines = summary.explanation.split("\n");
|
|
1106
|
+
const explanationLines = sanitizeText(summary.explanation).split("\n");
|
|
1102
1107
|
for (const line of explanationLines) {
|
|
1103
1108
|
lines.push(`${continuePrefix} ${theme.fg("dim", replaceTabs(line))}`);
|
|
1104
1109
|
}
|
|
1105
1110
|
} else {
|
|
1106
1111
|
// Preview: first sentence or ~100 chars (flatten tabs/newlines first)
|
|
1107
|
-
const flat = replaceTabs(summary.explanation).replace(/[\r\n]+/g, " ");
|
|
1112
|
+
const flat = replaceTabs(sanitizeText(summary.explanation)).replace(/[\r\n]+/g, " ");
|
|
1108
1113
|
const firstSentence = flat.split(/[.!?]/)[0].trim();
|
|
1109
1114
|
const preview = truncateToWidth(`${firstSentence}.`, 100);
|
|
1110
1115
|
lines.push(`${continuePrefix}${theme.fg("dim", preview)}`);
|
|
@@ -1145,9 +1150,9 @@ function renderFindings(
|
|
|
1145
1150
|
const findingContinue = isLastFinding ? " " : `${theme.tree.vertical} `;
|
|
1146
1151
|
|
|
1147
1152
|
const { color } = getPriorityInfo(finding.priority);
|
|
1148
|
-
const rawTitle = finding.title?.replace(/^\[P\d\]\s*/, "") ?? "Untitled";
|
|
1153
|
+
const rawTitle = sanitizeText(finding.title?.replace(/^\[P\d\]\s*/, "") ?? "Untitled");
|
|
1149
1154
|
const titleText = replaceTabs(rawTitle).replace(/[\r\n]+/g, " ");
|
|
1150
|
-
const loc = `${path.basename(finding.file_path || "<unknown>")}:${finding.line_start}`;
|
|
1155
|
+
const loc = `${path.basename(sanitizeText(finding.file_path || "<unknown>"))}:${finding.line_start}`;
|
|
1151
1156
|
|
|
1152
1157
|
lines.push(
|
|
1153
1158
|
`${continuePrefix}${findingPrefix} ${theme.fg(color, `[${finding.priority}]`)} ${titleText} ${theme.fg("dim", loc)}`,
|
|
@@ -1156,7 +1161,7 @@ function renderFindings(
|
|
|
1156
1161
|
// Show body when expanded
|
|
1157
1162
|
if (expanded && finding.body) {
|
|
1158
1163
|
// Wrap body text
|
|
1159
|
-
const bodyLines = finding.body.split("\n");
|
|
1164
|
+
const bodyLines = sanitizeText(finding.body).split("\n");
|
|
1160
1165
|
for (const bodyLine of bodyLines) {
|
|
1161
1166
|
lines.push(`${continuePrefix}${findingContinue}${theme.fg("dim", replaceTabs(bodyLine))}`);
|
|
1162
1167
|
}
|
|
@@ -1208,7 +1213,7 @@ function renderAgentResult(
|
|
|
1208
1213
|
: "failed";
|
|
1209
1214
|
|
|
1210
1215
|
// Main status line: id: description [status] · stats · ⟨agent⟩
|
|
1211
|
-
const trimmedDescription = result.description
|
|
1216
|
+
const trimmedDescription = result.description ? sanitizeText(result.description).trim() : undefined;
|
|
1212
1217
|
const description = trimmedDescription ? previewLine(trimmedDescription, 64) : undefined;
|
|
1213
1218
|
const displayId = formatTaskId(result.id);
|
|
1214
1219
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
@@ -1242,7 +1247,7 @@ function renderAgentResult(
|
|
|
1242
1247
|
|
|
1243
1248
|
if (aborted && result.abortReason) {
|
|
1244
1249
|
lines.push(
|
|
1245
|
-
`${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", previewLine(result.abortReason, 80))}`,
|
|
1250
|
+
`${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", previewLine(sanitizeText(result.abortReason), 80))}`,
|
|
1246
1251
|
);
|
|
1247
1252
|
}
|
|
1248
1253
|
// Check for review result, preferring incremental yield sections and falling
|
|
@@ -1346,7 +1351,7 @@ function renderAgentResult(
|
|
|
1346
1351
|
lines.push(
|
|
1347
1352
|
`${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg(
|
|
1348
1353
|
"dim",
|
|
1349
|
-
truncateToWidth(missingCompleteWarning, 80),
|
|
1354
|
+
truncateToWidth(sanitizeText(missingCompleteWarning), 80),
|
|
1350
1355
|
)}`,
|
|
1351
1356
|
);
|
|
1352
1357
|
}
|
|
@@ -1370,7 +1375,9 @@ function renderAgentResult(
|
|
|
1370
1375
|
|
|
1371
1376
|
// Error message
|
|
1372
1377
|
if (result.error && (!success || mergeFailed) && (!aborted || result.error !== result.abortReason)) {
|
|
1373
|
-
lines.push(
|
|
1378
|
+
lines.push(
|
|
1379
|
+
`${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", previewLine(sanitizeText(result.error), 70))}`,
|
|
1380
|
+
);
|
|
1374
1381
|
}
|
|
1375
1382
|
|
|
1376
1383
|
return lines;
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { Skill } from "../extensibility/skills";
|
|
4
4
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
5
5
|
import { validateRelativePath } from "../internal-urls/skill-protocol";
|
|
6
|
-
import type { InternalResource } from "../internal-urls/types";
|
|
6
|
+
import type { InternalResource, ResolveContext } from "../internal-urls/types";
|
|
7
7
|
import { normalizeLocalScheme } from "./path-utils";
|
|
8
8
|
import { ToolError } from "./tool-errors";
|
|
9
9
|
|
|
@@ -19,7 +19,7 @@ type SupportedInternalScheme = (typeof SUPPORTED_INTERNAL_SCHEMES)[number];
|
|
|
19
19
|
|
|
20
20
|
interface InternalUrlResolver {
|
|
21
21
|
canHandle(input: string): boolean;
|
|
22
|
-
resolve(input: string): Promise<InternalResource>;
|
|
22
|
+
resolve(input: string, context?: ResolveContext): Promise<InternalResource>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface InternalUrlExpansionOptions {
|
|
@@ -27,6 +27,7 @@ export interface InternalUrlExpansionOptions {
|
|
|
27
27
|
noEscape?: boolean;
|
|
28
28
|
internalRouter?: InternalUrlResolver;
|
|
29
29
|
localOptions?: LocalProtocolOptions;
|
|
30
|
+
cwd?: string;
|
|
30
31
|
ensureLocalParentDirs?: boolean;
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -66,7 +67,7 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
|
|
|
66
67
|
const hasRelativePath = rawPath !== "" && rawPath !== "/";
|
|
67
68
|
|
|
68
69
|
if (!hasRelativePath) {
|
|
69
|
-
return path.resolve(skill.
|
|
70
|
+
return path.resolve(skill.baseDir);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
let relativePath: string;
|
|
@@ -151,6 +152,7 @@ async function resolveInternalUrlToPath(
|
|
|
151
152
|
internalRouter?: InternalUrlResolver,
|
|
152
153
|
localOptions?: LocalProtocolOptions,
|
|
153
154
|
ensureLocalParentDirs?: boolean,
|
|
155
|
+
cwd?: string,
|
|
154
156
|
): Promise<string> {
|
|
155
157
|
const url = normalizeLocalScheme(rawUrl);
|
|
156
158
|
const scheme = extractScheme(url);
|
|
@@ -184,7 +186,7 @@ async function resolveInternalUrlToPath(
|
|
|
184
186
|
|
|
185
187
|
let resource: InternalResource;
|
|
186
188
|
try {
|
|
187
|
-
resource = await internalRouter.resolve(url);
|
|
189
|
+
resource = await internalRouter.resolve(url, { cwd });
|
|
188
190
|
} catch (error) {
|
|
189
191
|
const message = error instanceof Error ? error.message : String(error);
|
|
190
192
|
throw new ToolError(`Failed to resolve ${scheme}:// URL in bash command: ${url}\n${message}`);
|
|
@@ -239,6 +241,7 @@ export async function expandInternalUrls(command: string, options: InternalUrlEx
|
|
|
239
241
|
options.internalRouter,
|
|
240
242
|
options.localOptions,
|
|
241
243
|
options.ensureLocalParentDirs,
|
|
244
|
+
options.cwd,
|
|
242
245
|
);
|
|
243
246
|
const replacement = options.noEscape ? resolvedPath : shellEscape(resolvedPath);
|
|
244
247
|
expanded = `${expanded.slice(0, index)}${replacement}${expanded.slice(index + token.length)}`;
|