@gajae-code/coding-agent 0.18.12 → 0.19.0
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 +467 -0
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +7 -2
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +8 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +23 -1
- package/dist/types/config/config-hot-reload.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +44 -2
- package/dist/types/config/model-profile-activation.d.ts +19 -5
- package/dist/types/config/model-registry.d.ts +16 -3
- package/dist/types/config/model-resolver.d.ts +5 -4
- package/dist/types/config/settings-schema.d.ts +11 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +9 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +1 -1
- package/dist/types/migrate/adapters/index.d.ts +35 -3
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +69 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +22 -1
- package/dist/types/sdk/broker/ensure.d.ts +5 -2
- package/dist/types/sdk/broker/hop.d.ts +9 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/session-index.d.ts +2 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/index.d.ts +21 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +8 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/session/agent-session.d.ts +50 -8
- package/dist/types/session/session-manager.d.ts +16 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +13 -2
- package/dist/types/tools/fetch.d.ts +6 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/package.json +9 -8
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/generate-sdk-operation-inventory.ts +16 -0
- package/src/capability/fs.ts +31 -3
- package/src/capability/rule.ts +11 -2
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/fast-help.ts +0 -2
- package/src/cli/migrate-cli.ts +14 -3
- package/src/cli/public-command-errors.ts +2 -0
- package/src/cli/root-flags.ts +0 -2
- package/src/cli/update-cli.ts +247 -6
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +81 -11
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +20 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload.ts +8 -0
- package/src/config/file-lock.ts +1431 -304
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-profile-activation.ts +177 -39
- package/src/config/model-registry.ts +380 -55
- package/src/config/model-resolver.ts +49 -21
- package/src/config/settings-schema.ts +13 -1
- package/src/config/settings.ts +2 -0
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +3 -1
- package/src/customization/import.ts +276 -7
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +87 -5
- package/src/debug/report-bundle.ts +71 -2
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +24 -14
- package/src/edit/index.ts +191 -5
- package/src/edit/modes/patch.ts +27 -0
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +60 -20
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/extensions/loader.ts +2 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +4 -1
- package/src/gjc-runtime/launch-tmux.ts +23 -13
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +3 -1
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +14 -14
- package/src/internal-urls/local-protocol.ts +28 -3
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +517 -2
- package/src/migrate/adapters/claude-code.ts +141 -3
- package/src/migrate/adapters/codex.ts +57 -5
- package/src/migrate/adapters/index.ts +240 -5
- package/src/migrate/adapters/opencode.ts +4 -0
- package/src/migrate/executor.ts +141 -3
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +45 -1
- package/src/migrate/types.ts +73 -3
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +271 -16
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +49 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +14 -1
- package/src/modes/interactive-mode.ts +55 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/runtime-mcp/manager.ts +8 -3
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/tool-bridge.ts +68 -4
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +2 -0
- package/src/sdk/broker/broker.ts +143 -4
- package/src/sdk/broker/ensure.ts +74 -6
- package/src/sdk/broker/hop.ts +13 -1
- package/src/sdk/broker/lifecycle-ledger.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/session-index.ts +29 -4
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +408 -14
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1537 -24
- package/src/sdk/host/session-runtime.ts +332 -36
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +14 -1
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +28 -1
- package/src/sdk/protocol/operation-inventory.generated.json +160 -0
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/session.ts +123 -36
- package/src/session/agent-session.ts +1614 -329
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +47 -18
- package/src/session/internal/managed-session-storage.ts +71 -1
- package/src/session/session-manager.ts +90 -5
- package/src/session-import/redact.ts +53 -2
- package/src/skill-state/workflow-mutation-guard.ts +1498 -18
- package/src/slash-commands/builtin-registry.ts +6 -3
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +12 -8
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/auto-generated-guard.ts +12 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +61 -1
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +99 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +9 -2
- package/src/tools/descriptors.test.ts +4 -4
- package/src/tools/descriptors.ts +16 -4
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +90 -2
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +51 -3
- package/src/tools/read.ts +9 -2
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent.ts +10 -4
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +7 -0
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +3 -2
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +46 -6
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +65 -20
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapePromptMetadata } from "../../session/messages";
|
|
1
2
|
import { ToolError } from "../tool-errors";
|
|
2
3
|
|
|
3
4
|
export interface RunnerTask {
|
|
@@ -197,23 +198,33 @@ function findAmbiguityExample(runners: DetectedRunner[]): { runner: string; task
|
|
|
197
198
|
return firstRunner && firstTask ? { runner: firstRunner.id, task: firstTask.name } : undefined;
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
function escapeRecipePromptText(value: string | undefined): string | undefined {
|
|
202
|
+
return value === undefined ? undefined : escapePromptMetadata(value);
|
|
203
|
+
}
|
|
204
|
+
|
|
200
205
|
export function buildPromptModel(runners: DetectedRunner[]): RecipePromptModel {
|
|
201
206
|
const ambiguityExample = findAmbiguityExample(runners);
|
|
202
207
|
return {
|
|
203
208
|
hasMultipleRunners: runners.length > 1,
|
|
204
|
-
ambiguityExampleRunner: ambiguityExample?.runner,
|
|
205
|
-
ambiguityExampleTask: ambiguityExample?.task,
|
|
209
|
+
ambiguityExampleRunner: escapeRecipePromptText(ambiguityExample?.runner),
|
|
210
|
+
ambiguityExampleTask: escapeRecipePromptText(ambiguityExample?.task),
|
|
206
211
|
runners: runners.map(runner => ({
|
|
207
|
-
id: runner.id,
|
|
208
|
-
label: runner.label,
|
|
209
|
-
commandPrefix: runner.commandPrefix,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
id: escapePromptMetadata(runner.id),
|
|
213
|
+
label: escapePromptMetadata(runner.label),
|
|
214
|
+
commandPrefix: escapePromptMetadata(runner.commandPrefix),
|
|
215
|
+
// recipe.md is compiled with noEscape. Cap the list first, then encode the
|
|
216
|
+
// kept repository fields so a script key or path cannot close <instruction> or <runner>.
|
|
217
|
+
tasks: runner.tasks.slice(0, PROMPT_TASK_LIMIT).map(task => {
|
|
218
|
+
const paramSig = task.parameters.length > 0 ? task.parameters.join(" ") : undefined;
|
|
219
|
+
const command = buildCommand(task.commandPrefix ?? runner.commandPrefix, task.commandName ?? task.name, "");
|
|
220
|
+
return {
|
|
221
|
+
name: escapePromptMetadata(task.name),
|
|
222
|
+
paramSig: escapeRecipePromptText(paramSig),
|
|
223
|
+
command: escapePromptMetadata(command),
|
|
224
|
+
doc: escapeRecipePromptText(task.doc),
|
|
225
|
+
cwd: escapeRecipePromptText(task.cwd),
|
|
226
|
+
};
|
|
227
|
+
}),
|
|
217
228
|
})),
|
|
218
229
|
};
|
|
219
230
|
}
|
|
@@ -10,7 +10,7 @@ import * as path from "node:path";
|
|
|
10
10
|
import type { ToolCallContext } from "@gajae-code/agent-core";
|
|
11
11
|
import type { Component, Ellipsis } from "@gajae-code/tui";
|
|
12
12
|
import { replaceTabs, truncateToWidth } from "@gajae-code/tui";
|
|
13
|
-
import { pluralize } from "@gajae-code/utils";
|
|
13
|
+
import { pluralize, sanitizeText } from "@gajae-code/utils";
|
|
14
14
|
import { settings } from "../config/settings";
|
|
15
15
|
import type { Theme } from "../modes/theme/theme";
|
|
16
16
|
import { Hasher } from "../tui/utils";
|
|
@@ -175,7 +175,7 @@ export function formatMeta(meta: string[], theme: Theme): string {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
export function formatErrorMessage(message: string | undefined, theme: Theme): string {
|
|
178
|
-
const clean = (message ?? "").replace(/^Error:\s*/, "").trim();
|
|
178
|
+
const clean = sanitizeText((message ?? "").replace(/^Error:\s*/, "").trim());
|
|
179
179
|
return `${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${clean || "Unknown error"}`)}`;
|
|
180
180
|
}
|
|
181
181
|
|
package/src/tools/search.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
openArchive,
|
|
20
20
|
parseArchivePathCandidates,
|
|
21
21
|
} from "./archive-reader";
|
|
22
|
+
import { wrapUntrustedContent } from "./fetch";
|
|
22
23
|
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
23
24
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
24
25
|
import { formatMatchLine } from "./match-line-format";
|
|
@@ -552,7 +553,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
552
553
|
missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
|
|
553
554
|
};
|
|
554
555
|
const text = warningNote ? `No matches found\n${warningNote}` : "No matches found";
|
|
555
|
-
const noMatchResult = toolResult(details).text(text).done();
|
|
556
|
+
const noMatchResult = toolResult(details).text(wrapUntrustedContent(text)).done();
|
|
556
557
|
await cleanupArchiveScratch();
|
|
557
558
|
throwIfSearchCancelled();
|
|
558
559
|
return noMatchResult;
|
|
@@ -676,7 +677,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
676
677
|
if (truncation.truncated) details.truncation = truncation;
|
|
677
678
|
if (linesTruncated) details.linesTruncated = true;
|
|
678
679
|
const resultBuilder = toolResult(details)
|
|
679
|
-
.text(output)
|
|
680
|
+
.text(wrapUntrustedContent(output))
|
|
680
681
|
.limits({ columnMax: linesTruncated ? DEFAULT_MAX_COLUMN : undefined });
|
|
681
682
|
if (truncation.truncated) {
|
|
682
683
|
resultBuilder.truncation(truncation, { direction: "head" });
|
package/src/tools/ssh.ts
CHANGED
|
@@ -5,18 +5,35 @@ import { prompt } from "@gajae-code/utils";
|
|
|
5
5
|
import * as z from "zod/v4";
|
|
6
6
|
import type { SSHHost } from "../capability/ssh";
|
|
7
7
|
import { sshCapability } from "../capability/ssh";
|
|
8
|
+
import { getDefault, type Settings } from "../config/settings";
|
|
8
9
|
import { loadCapability } from "../discovery";
|
|
9
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
11
|
import type { Theme } from "../modes/theme/theme";
|
|
11
12
|
import sshDescriptionBase from "../prompts/tools/ssh.md" with { type: "text" };
|
|
12
|
-
import {
|
|
13
|
+
import type { SessionArtifactPublication } from "../session/session-manager";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_MAX_BYTES,
|
|
16
|
+
streamTailUpdates,
|
|
17
|
+
TailBuffer,
|
|
18
|
+
type TerminalArtifactPublisher,
|
|
19
|
+
type TruncationResult,
|
|
20
|
+
truncateMiddle,
|
|
21
|
+
truncateMiddleWindows,
|
|
22
|
+
truncateTail,
|
|
23
|
+
} from "../session/streaming-output";
|
|
13
24
|
import type { SSHHostInfo } from "../ssh/connection-manager";
|
|
14
25
|
import { ensureHostInfo, getHostInfoForHost } from "../ssh/connection-manager";
|
|
15
|
-
import { executeSSH } from "../ssh/ssh-executor";
|
|
26
|
+
import { executeSSH, type SSHResult } from "../ssh/ssh-executor";
|
|
16
27
|
import { renderStatusLine } from "../tui";
|
|
17
28
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
18
29
|
import type { ToolSession } from ".";
|
|
19
|
-
import {
|
|
30
|
+
import { wrapUntrustedContent } from "./fetch";
|
|
31
|
+
import {
|
|
32
|
+
formatArtifactReference,
|
|
33
|
+
formatStyledTruncationWarning,
|
|
34
|
+
type OutputMeta,
|
|
35
|
+
stripOutputNotice,
|
|
36
|
+
} from "./output-meta";
|
|
20
37
|
import { ToolError } from "./tool-errors";
|
|
21
38
|
import { toolResult } from "./tool-result";
|
|
22
39
|
import { clampTimeout } from "./tool-timeouts";
|
|
@@ -89,6 +106,181 @@ function quoteCmdPath(value: string): string {
|
|
|
89
106
|
return `"${escaped}"`;
|
|
90
107
|
}
|
|
91
108
|
|
|
109
|
+
const FENCE_OPEN = "<untrusted-content>\n";
|
|
110
|
+
const FENCE_CLOSE = "\n</untrusted-content>";
|
|
111
|
+
const UNTRUSTED_FENCE_OVERHEAD = Buffer.byteLength(`${FENCE_OPEN}${FENCE_CLOSE}`, "utf-8");
|
|
112
|
+
const INLINE_MARKER_RESERVE = 256;
|
|
113
|
+
/** `</untrusted-content>` grows when `wrapUntrustedContent` escapes it. */
|
|
114
|
+
const CLOSER_RAW_BYTES = "</untrusted-content>".length;
|
|
115
|
+
const CLOSER_ESCAPED_BYTES = "</untrusted-content>".length;
|
|
116
|
+
|
|
117
|
+
type OutputBudgetPath = "tools.maxInlineResultBytes" | "tools.artifactHeadBytes" | "tools.artifactTailLines";
|
|
118
|
+
|
|
119
|
+
function settingNumber(settings: Settings | undefined, path: OutputBudgetPath): number {
|
|
120
|
+
return settings?.get(path) ?? getDefault(path);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type RemoteFrame = {
|
|
124
|
+
text: string;
|
|
125
|
+
truncation?: TruncationResult;
|
|
126
|
+
direction?: "head" | "tail" | "middle";
|
|
127
|
+
/** True only after the full remote text is already stored. */
|
|
128
|
+
shrunk: boolean;
|
|
129
|
+
visibleLines: number;
|
|
130
|
+
visibleBytes: number;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function inlineCapBytes(settings: Settings | undefined): number {
|
|
134
|
+
return settingNumber(settings, "tools.maxInlineResultBytes") * 1024;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function exceedsInlineCap(output: string, settings: Settings | undefined): boolean {
|
|
138
|
+
const cap = inlineCapBytes(settings);
|
|
139
|
+
if (cap <= 0) return false;
|
|
140
|
+
// Measure the escaped frame. A raw-size check misses closer expansion.
|
|
141
|
+
return Buffer.byteLength(wrapUntrustedContent(output), "utf-8") > cap;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function frameFullRemoteOutput(output: string): RemoteFrame {
|
|
145
|
+
return { text: wrapUntrustedContent(output), shrunk: false, visibleLines: 0, visibleBytes: 0 };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Cut to the inline window, then frame that excerpt. Call only when the full
|
|
150
|
+
* remote text is already stored, so a tail cut cannot drop the opening fence
|
|
151
|
+
* or the only copy of the output.
|
|
152
|
+
*/
|
|
153
|
+
function frameShrunkRemoteOutput(output: string, settings: Settings | undefined): RemoteFrame {
|
|
154
|
+
const cap = inlineCapBytes(settings);
|
|
155
|
+
const escapedRoom = Math.max(1, cap - UNTRUSTED_FENCE_OVERHEAD - INLINE_MARKER_RESERVE);
|
|
156
|
+
// Budget in remote-output bytes so framing and closer escaping cannot distort
|
|
157
|
+
// the source coordinates reported by truncation metadata.
|
|
158
|
+
let budget = Math.max(1, Math.floor((escapedRoom * CLOSER_RAW_BYTES) / CLOSER_ESCAPED_BYTES));
|
|
159
|
+
const headBytes = settingNumber(settings, "tools.artifactHeadBytes") * 1024;
|
|
160
|
+
const tailLines = settingNumber(settings, "tools.artifactTailLines");
|
|
161
|
+
const headBudget = Math.max(
|
|
162
|
+
1,
|
|
163
|
+
Math.floor((Math.min(headBytes, escapedRoom / 2) * CLOSER_RAW_BYTES) / CLOSER_ESCAPED_BYTES),
|
|
164
|
+
);
|
|
165
|
+
const cutRemoteOutput = (maxBytes: number) => {
|
|
166
|
+
if (headBytes > 0) {
|
|
167
|
+
const options = {
|
|
168
|
+
maxBytes,
|
|
169
|
+
maxLines: tailLines * 2,
|
|
170
|
+
maxHeadBytes: Math.min(headBudget, Math.floor(maxBytes / 2)),
|
|
171
|
+
maxHeadLines: tailLines,
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
truncation: truncateMiddle(output, options),
|
|
175
|
+
windows: truncateMiddleWindows(output, options),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const options = { maxBytes, maxLines: tailLines };
|
|
179
|
+
return {
|
|
180
|
+
truncation: truncateTail(output, options),
|
|
181
|
+
windows: truncateMiddleWindows(output, { ...options, maxHeadBytes: 0, maxHeadLines: 0 }),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
let { truncation, windows } = cutRemoteOutput(budget);
|
|
185
|
+
let excerpt = truncation.truncated ? truncation.content : output;
|
|
186
|
+
let text = wrapUntrustedContent(excerpt);
|
|
187
|
+
let textBytes = Buffer.byteLength(text, "utf-8");
|
|
188
|
+
while (textBytes > cap && budget > 1) {
|
|
189
|
+
const scaledBudget = Math.floor((budget * cap) / textBytes);
|
|
190
|
+
budget = Math.max(1, Math.min(budget - 1, scaledBudget));
|
|
191
|
+
({ truncation, windows } = cutRemoteOutput(budget));
|
|
192
|
+
excerpt = truncation.truncated ? truncation.content : output;
|
|
193
|
+
text = wrapUntrustedContent(excerpt);
|
|
194
|
+
textBytes = Buffer.byteLength(text, "utf-8");
|
|
195
|
+
}
|
|
196
|
+
if (text === wrapUntrustedContent(output)) return frameFullRemoteOutput(output);
|
|
197
|
+
if (windows.kind === "full") return frameFullRemoteOutput(output);
|
|
198
|
+
const direction = windows.kind === "middle" ? "middle" : windows.kind === "head-only" ? "head" : "tail";
|
|
199
|
+
return {
|
|
200
|
+
text,
|
|
201
|
+
truncation,
|
|
202
|
+
direction,
|
|
203
|
+
shrunk: true,
|
|
204
|
+
visibleLines: (windows.head?.lines ?? 0) + (windows.tail?.lines ?? 0),
|
|
205
|
+
visibleBytes: (windows.head?.bytes ?? 0) + (windows.tail?.bytes ?? 0),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Store the full remote text. Never write an already-shortened excerpt.
|
|
210
|
+
* The command has already finished, so a rejected reservation must not discard it.
|
|
211
|
+
*/
|
|
212
|
+
async function storeFullRemoteOutput(
|
|
213
|
+
session: ToolSession,
|
|
214
|
+
output: string,
|
|
215
|
+
artifactPublication: SessionArtifactPublication | undefined,
|
|
216
|
+
): Promise<string | undefined> {
|
|
217
|
+
let allocated: { id?: string; path?: string } | undefined;
|
|
218
|
+
try {
|
|
219
|
+
allocated = await session.allocateOutputArtifact?.("ssh");
|
|
220
|
+
} catch {
|
|
221
|
+
allocated = undefined;
|
|
222
|
+
}
|
|
223
|
+
if (allocated?.path && allocated.id) {
|
|
224
|
+
try {
|
|
225
|
+
await Bun.write(allocated.path, output);
|
|
226
|
+
return allocated.id;
|
|
227
|
+
} catch {
|
|
228
|
+
// Fall through to the session-owned publisher when a writable path failed.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!artifactPublication) return undefined;
|
|
232
|
+
try {
|
|
233
|
+
return await artifactPublication(output, "ssh");
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function createSshArtifactPublisher(
|
|
240
|
+
artifactPublication: SessionArtifactPublication | undefined,
|
|
241
|
+
): TerminalArtifactPublisher | undefined {
|
|
242
|
+
if (!artifactPublication) return undefined;
|
|
243
|
+
return async (content, info) => {
|
|
244
|
+
try {
|
|
245
|
+
const artifactId = await artifactPublication(content, "ssh");
|
|
246
|
+
if (!artifactId) return { status: "unavailable" };
|
|
247
|
+
return info.omittedBytes > 0
|
|
248
|
+
? { status: "published", artifactId, omittedBytes: info.omittedBytes }
|
|
249
|
+
: { status: "published", artifactId };
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return { status: "failed", diagnostic: error instanceof Error ? error.message : String(error) };
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function hasCompleteRemoteOutput(result: SSHResult, output: string): boolean {
|
|
257
|
+
return (
|
|
258
|
+
!result.truncated &&
|
|
259
|
+
result.outputBytes === result.totalBytes &&
|
|
260
|
+
Buffer.byteLength(output, "utf-8") === result.outputBytes
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function incompleteOutputNotice(result: SSHResult, artifactId: string | undefined): string | undefined {
|
|
265
|
+
const retainedWindowIncomplete = result.truncated || result.outputBytes < result.totalBytes;
|
|
266
|
+
if (!retainedWindowIncomplete) return undefined;
|
|
267
|
+
|
|
268
|
+
const artifactIncomplete =
|
|
269
|
+
(result.artifactTruncatedBytes ?? 0) > 0 || result.artifactFailureDiagnostic !== undefined;
|
|
270
|
+
const completeArtifactAvailable = artifactId !== undefined && !artifactIncomplete;
|
|
271
|
+
if (completeArtifactAvailable) return undefined;
|
|
272
|
+
|
|
273
|
+
const notes = [`SSH output is incomplete: retained ${result.outputBytes} of ${result.totalBytes} bytes`];
|
|
274
|
+
notes.push("no complete output artifact is available");
|
|
275
|
+
if ((result.artifactTruncatedBytes ?? 0) > 0) {
|
|
276
|
+
notes.push(`artifact storage omitted at least ${result.artifactTruncatedBytes} bytes`);
|
|
277
|
+
}
|
|
278
|
+
if (result.artifactFailureDiagnostic) {
|
|
279
|
+
notes.push(`Artifact storage failed: ${result.artifactFailureDiagnostic}`);
|
|
280
|
+
}
|
|
281
|
+
return notes.join("; ");
|
|
282
|
+
}
|
|
283
|
+
|
|
92
284
|
function buildRemoteCommand(command: string, cwd: string | undefined, info: SSHHostInfo): string {
|
|
93
285
|
if (!cwd) return command;
|
|
94
286
|
|
|
@@ -161,6 +353,8 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
|
|
|
161
353
|
// Clamp to reasonable range: 1s - 3600s (1 hour)
|
|
162
354
|
const timeoutSec = clampTimeout("ssh", rawTimeout);
|
|
163
355
|
const timeoutMs = timeoutSec * 1000;
|
|
356
|
+
const outputSettings = _ctx?.settings ?? this.session.settings;
|
|
357
|
+
const artifactPublication = this.session.captureArtifactPublication?.();
|
|
164
358
|
|
|
165
359
|
const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES);
|
|
166
360
|
const { path: artifactPath, id: artifactId } = (await this.session.allocateOutputArtifact?.("ssh")) ?? {};
|
|
@@ -172,22 +366,94 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
|
|
|
172
366
|
compatEnabled: hostInfo.compatEnabled,
|
|
173
367
|
artifactPath,
|
|
174
368
|
artifactId,
|
|
369
|
+
artifactPublisher: createSshArtifactPublisher(artifactPublication),
|
|
175
370
|
onChunk: streamTailUpdates(tailBuffer, onUpdate),
|
|
176
371
|
});
|
|
177
372
|
|
|
373
|
+
const remote = result.output ?? "";
|
|
374
|
+
let storedArtifactId = result.artifactId;
|
|
375
|
+
let framed: RemoteFrame | undefined;
|
|
376
|
+
if (!remote) {
|
|
377
|
+
framed = undefined;
|
|
378
|
+
} else if (!exceedsInlineCap(remote, outputSettings)) {
|
|
379
|
+
framed = frameFullRemoteOutput(remote);
|
|
380
|
+
} else {
|
|
381
|
+
if (!storedArtifactId && hasCompleteRemoteOutput(result, remote)) {
|
|
382
|
+
storedArtifactId = await storeFullRemoteOutput(this.session, remote, artifactPublication);
|
|
383
|
+
}
|
|
384
|
+
framed = storedArtifactId ? frameShrunkRemoteOutput(remote, outputSettings) : frameFullRemoteOutput(remote);
|
|
385
|
+
}
|
|
386
|
+
// The artifact id stays in truncation metadata. A line after the closing
|
|
387
|
+
// fence makes the inline backstop drop the opening fence.
|
|
388
|
+
const modelText = framed?.text;
|
|
389
|
+
const storedNote =
|
|
390
|
+
(framed?.shrunk || result.truncated) && storedArtifactId
|
|
391
|
+
? `\n\n${formatArtifactReference(storedArtifactId, result.artifactTruncatedBytes)}`
|
|
392
|
+
: "";
|
|
393
|
+
const incompleteNote = incompleteOutputNotice(result, storedArtifactId);
|
|
394
|
+
const artifactFailureNote =
|
|
395
|
+
result.artifactFailureDiagnostic && !incompleteNote
|
|
396
|
+
? `SSH streaming artifact storage failed: ${result.artifactFailureDiagnostic}`
|
|
397
|
+
: undefined;
|
|
178
398
|
if (result.cancelled) {
|
|
179
|
-
|
|
399
|
+
const message = [
|
|
400
|
+
modelText || "Command aborted",
|
|
401
|
+
storedNote.trim() || undefined,
|
|
402
|
+
incompleteNote,
|
|
403
|
+
artifactFailureNote,
|
|
404
|
+
]
|
|
405
|
+
.filter((part): part is string => part !== undefined)
|
|
406
|
+
.join("\n\n");
|
|
407
|
+
throw new ToolError(message);
|
|
180
408
|
}
|
|
181
409
|
|
|
182
|
-
const outputText =
|
|
410
|
+
const outputText = modelText ?? "(no output)";
|
|
183
411
|
const details: SSHToolDetails = {};
|
|
184
|
-
const resultBuilder = toolResult(details).text(outputText)
|
|
412
|
+
const resultBuilder = toolResult(details).text(outputText);
|
|
413
|
+
if (framed?.shrunk && framed.truncation && storedArtifactId) {
|
|
414
|
+
resultBuilder.truncation(framed.truncation, {
|
|
415
|
+
direction: framed.direction ?? "tail",
|
|
416
|
+
artifactId: storedArtifactId,
|
|
417
|
+
});
|
|
418
|
+
} else {
|
|
419
|
+
resultBuilder.truncationFromSummary(result, { direction: "tail" });
|
|
420
|
+
}
|
|
185
421
|
|
|
186
422
|
if (result.exitCode !== 0 && result.exitCode !== undefined) {
|
|
187
|
-
|
|
423
|
+
const message = [
|
|
424
|
+
outputText,
|
|
425
|
+
storedNote.trim() || undefined,
|
|
426
|
+
incompleteNote,
|
|
427
|
+
artifactFailureNote,
|
|
428
|
+
`Command exited with code ${result.exitCode}`,
|
|
429
|
+
]
|
|
430
|
+
.filter((part): part is string => part !== undefined)
|
|
431
|
+
.join("\n\n");
|
|
432
|
+
throw new ToolError(message);
|
|
188
433
|
}
|
|
189
434
|
|
|
190
|
-
|
|
435
|
+
const built = resultBuilder.done();
|
|
436
|
+
const truncation = built.details?.meta?.truncation;
|
|
437
|
+
if (framed?.shrunk && truncation) {
|
|
438
|
+
// The cut is a window of an earlier excerpt. Keep the stream totals
|
|
439
|
+
// and do not present that window's coordinates as the original range.
|
|
440
|
+
truncation.totalLines = result.totalLines;
|
|
441
|
+
truncation.totalBytes = result.totalBytes;
|
|
442
|
+
truncation.outputLines = framed.visibleLines;
|
|
443
|
+
truncation.outputBytes = framed.visibleBytes;
|
|
444
|
+
truncation.artifactId = storedArtifactId;
|
|
445
|
+
truncation.artifactTruncatedBytes = result.artifactTruncatedBytes;
|
|
446
|
+
truncation.artifactFailureDiagnostic = result.artifactFailureDiagnostic;
|
|
447
|
+
delete truncation.shownRange;
|
|
448
|
+
delete truncation.headRange;
|
|
449
|
+
delete truncation.tailRange;
|
|
450
|
+
delete truncation.nextOffset;
|
|
451
|
+
if (framed.direction === "middle") {
|
|
452
|
+
truncation.elidedLines = Math.max(0, result.totalLines - framed.visibleLines);
|
|
453
|
+
truncation.elidedBytes = Math.max(0, result.totalBytes - framed.visibleBytes);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return built;
|
|
191
457
|
}
|
|
192
458
|
}
|
|
193
459
|
|
package/src/tools/subagent.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type AsyncJob, AsyncJobManager, jobElapsedMs, type SubagentRecord } fro
|
|
|
6
6
|
import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
|
|
7
7
|
import type { AgentProgress, AgentSource, LocalErrorSummary } from "../task/types";
|
|
8
8
|
import { Ellipsis, truncateToWidth } from "../tui";
|
|
9
|
+
import { wrapUntrustedContent } from "./fetch";
|
|
9
10
|
import type { ToolSession } from "./index";
|
|
10
11
|
import { replaceTabs } from "./render-utils";
|
|
11
12
|
import { ToolError } from "./tool-errors";
|
|
@@ -799,7 +800,11 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
799
800
|
}
|
|
800
801
|
if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
|
|
801
802
|
if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
|
|
802
|
-
|
|
803
|
+
// A markdown fence is not a boundary: subagent text can contain ``` and
|
|
804
|
+
// close it, leaving the rest as raw tool-result text. Frame the data.
|
|
805
|
+
if (snapshot.setupFailureSummary) {
|
|
806
|
+
lines.push(wrapUntrustedContent(`Setup failure: ${snapshot.setupFailureSummary}`));
|
|
807
|
+
}
|
|
803
808
|
if (snapshot.localErrorSummary) {
|
|
804
809
|
const local = snapshot.localErrorSummary;
|
|
805
810
|
lines.push(`Local failure (${local.kind}): ${local.summary}`);
|
|
@@ -816,13 +821,14 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
816
821
|
);
|
|
817
822
|
}
|
|
818
823
|
}
|
|
819
|
-
if (snapshot.assignment) lines.push(
|
|
824
|
+
if (snapshot.assignment) lines.push(wrapUntrustedContent(`Assignment:\n${snapshot.assignment}`));
|
|
820
825
|
if (snapshot.steerMessage) {
|
|
821
|
-
lines.push(`Steer (${snapshot.steerState ?? "queued"})
|
|
826
|
+
lines.push(wrapUntrustedContent(`Steer (${snapshot.steerState ?? "queued"}):\n${snapshot.steerMessage}`));
|
|
822
827
|
lines.push(STEER_QUEUED_GUIDANCE);
|
|
823
828
|
}
|
|
824
829
|
if (snapshot.resultPreview) {
|
|
825
|
-
|
|
830
|
+
const previewLabel = snapshot.errorText ? "Error preview:" : "Result preview:";
|
|
831
|
+
lines.push(wrapUntrustedContent(`${previewLabel}\n${snapshot.resultPreview}`));
|
|
826
832
|
if (snapshot.truncated)
|
|
827
833
|
lines.push("Preview truncated; use the output ref or explicit ids with `verbosity=full` for more.");
|
|
828
834
|
}
|