@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +1 -1
- package/dist/cli.js +4685 -4614
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +374 -47
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +48 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +35 -0
- package/dist/types/live/visualizer.d.ts +30 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +321 -0
- package/dist/types/session/agent-session.d.ts +135 -753
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +51 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +24 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +158 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/streaming-output.d.ts +8 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -0
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +7 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +10 -29
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/utils/tools-manager.d.ts +1 -2
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/setup-cli.ts +2 -14
- package/src/cli/usage-cli.ts +10 -1
- package/src/cli.ts +8 -0
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +16 -2
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +270 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +90 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +214 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +1 -1
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.ts +28 -112
- package/src/modes/controllers/command-controller.ts +56 -15
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/input-controller.ts +47 -39
- package/src/modes/controllers/live-command-controller.ts +255 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +80 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +176 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +215 -136
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +343 -0
- package/src/session/agent-session.ts +1940 -12218
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/auth-storage.ts +1 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +217 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +266 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.ts +6 -3
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +0 -46
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +980 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +52 -5
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +257 -20
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +45 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +116 -86
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +14 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +9 -1
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +121 -29
- package/src/tts/streaming-player.ts +82 -283
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
- package/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* The relay repo's build script runs this and embeds the output via go:embed.
|
|
11
11
|
*/
|
|
12
12
|
import * as path from "node:path";
|
|
13
|
-
import {
|
|
13
|
+
import { generateThemeStyles, getTemplate } from "../src/export/html";
|
|
14
14
|
|
|
15
15
|
const outPath = process.argv[2];
|
|
16
16
|
if (!outPath) {
|
|
@@ -19,13 +19,11 @@ if (!outPath) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const loaderJs = await Bun.file(new URL("../src/export/html/share-loader.js", import.meta.url).pathname).text();
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
// that should mirror the host's terminal theme.
|
|
25
|
-
const themeVars = await generateThemeVars("web");
|
|
22
|
+
// Public artifacts use the bundled omp web themes rather than TUI themes.
|
|
23
|
+
const themeStyles = await generateThemeStyles("web");
|
|
26
24
|
|
|
27
25
|
const html = getTemplate()
|
|
28
|
-
.replace("<theme-vars/>", () => `<style
|
|
26
|
+
.replace("<theme-vars/>", () => `<style>${themeStyles}</style>`)
|
|
29
27
|
.replace("<title>Session Export</title>", () => "<title>omp session</title>")
|
|
30
28
|
.replace("{{SESSION_DATA}}</script>", () => `</script>\n <script>${loaderJs}</script>`);
|
|
31
29
|
|
|
@@ -1075,6 +1075,56 @@ describe("advisor", () => {
|
|
|
1075
1075
|
// The loop re-checked maintenance for the expanded batch.
|
|
1076
1076
|
expect(maintainCalls).toBe(2);
|
|
1077
1077
|
});
|
|
1078
|
+
it("re-scrubs coalesced pending updates when a later regex value collides with their friendly prefixes", async () => {
|
|
1079
|
+
const obfuscator = new SecretObfuscator([
|
|
1080
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1081
|
+
{ type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
|
|
1082
|
+
]);
|
|
1083
|
+
const promptInputs: string[] = [];
|
|
1084
|
+
const { promise: firstMaintainStarted, resolve: startFirstMaintain } = Promise.withResolvers<void>();
|
|
1085
|
+
const { promise: finishFirstMaintain, resolve: releaseFirstMaintain } = Promise.withResolvers<boolean>();
|
|
1086
|
+
const { promise: promptStarted, resolve: startPrompt } = Promise.withResolvers<void>();
|
|
1087
|
+
let maintainCalls = 0;
|
|
1088
|
+
const agent: AdvisorAgent = {
|
|
1089
|
+
prompt: async input => {
|
|
1090
|
+
promptInputs.push(input);
|
|
1091
|
+
startPrompt();
|
|
1092
|
+
},
|
|
1093
|
+
abort: () => {},
|
|
1094
|
+
reset: () => {},
|
|
1095
|
+
state: { messages: [] },
|
|
1096
|
+
};
|
|
1097
|
+
const messages: AgentMessage[] = [
|
|
1098
|
+
{ role: "user", content: "first OTHERSECRET", timestamp: 1 } as AgentMessage,
|
|
1099
|
+
];
|
|
1100
|
+
const host: AdvisorRuntimeHost = {
|
|
1101
|
+
snapshotMessages: () => messages,
|
|
1102
|
+
enqueueAdvice: () => {},
|
|
1103
|
+
obfuscator,
|
|
1104
|
+
maintainContext: async () => {
|
|
1105
|
+
maintainCalls++;
|
|
1106
|
+
if (maintainCalls === 1) {
|
|
1107
|
+
startFirstMaintain();
|
|
1108
|
+
return await finishFirstMaintain;
|
|
1109
|
+
}
|
|
1110
|
+
return false;
|
|
1111
|
+
},
|
|
1112
|
+
};
|
|
1113
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1114
|
+
|
|
1115
|
+
runtime.onTurnEnd();
|
|
1116
|
+
await firstMaintainStarted;
|
|
1117
|
+
|
|
1118
|
+
messages.push({ role: "user", content: "later tok_abc123", timestamp: 2 } as AgentMessage);
|
|
1119
|
+
runtime.onTurnEnd();
|
|
1120
|
+
|
|
1121
|
+
releaseFirstMaintain(false);
|
|
1122
|
+
await promptStarted;
|
|
1123
|
+
|
|
1124
|
+
expect(promptInputs).toHaveLength(1);
|
|
1125
|
+
expect(promptInputs[0]).not.toContain("TOKABC123_");
|
|
1126
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1127
|
+
});
|
|
1078
1128
|
|
|
1079
1129
|
it("caps maintainContext calls per drain cycle when arrivals never go stable", async () => {
|
|
1080
1130
|
// Regression guard for MAX_COALESCE_ROUNDS=3: during the first drain cycle,
|
|
@@ -1550,6 +1600,599 @@ describe("advisor", () => {
|
|
|
1550
1600
|
expect(promptInputs[0]).not.toContain(secret);
|
|
1551
1601
|
});
|
|
1552
1602
|
|
|
1603
|
+
it("does not scan tool details omitted from advisor history", async () => {
|
|
1604
|
+
const obfuscator = new SecretObfuscator([
|
|
1605
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1606
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1607
|
+
]);
|
|
1608
|
+
const promptInputs: string[] = [];
|
|
1609
|
+
const agent = makeAgent(promptInputs);
|
|
1610
|
+
const messages: AgentMessage[] = [
|
|
1611
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1612
|
+
{
|
|
1613
|
+
role: "assistant",
|
|
1614
|
+
content: [{ type: "toolCall", id: "c1", name: "read", arguments: { path: "config.ts" } }],
|
|
1615
|
+
timestamp: 2,
|
|
1616
|
+
} as unknown as AgentMessage,
|
|
1617
|
+
{
|
|
1618
|
+
role: "toolResult",
|
|
1619
|
+
toolCallId: "c1",
|
|
1620
|
+
toolName: "read",
|
|
1621
|
+
content: "ok",
|
|
1622
|
+
details: { opaque: "tok_abc123" },
|
|
1623
|
+
timestamp: 3,
|
|
1624
|
+
} as unknown as AgentMessage,
|
|
1625
|
+
];
|
|
1626
|
+
const host: AdvisorRuntimeHost = {
|
|
1627
|
+
snapshotMessages: () => messages,
|
|
1628
|
+
enqueueAdvice: () => {},
|
|
1629
|
+
obfuscator,
|
|
1630
|
+
};
|
|
1631
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1632
|
+
|
|
1633
|
+
runtime.onTurnEnd();
|
|
1634
|
+
await Promise.resolve();
|
|
1635
|
+
|
|
1636
|
+
expect(promptInputs).toHaveLength(1);
|
|
1637
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1638
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1639
|
+
});
|
|
1640
|
+
it("does not scan advisor-hidden successful tool-result bodies", async () => {
|
|
1641
|
+
const obfuscator = new SecretObfuscator([
|
|
1642
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1643
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1644
|
+
]);
|
|
1645
|
+
const promptInputs: string[] = [];
|
|
1646
|
+
const agent = makeAgent(promptInputs);
|
|
1647
|
+
const messages: AgentMessage[] = [
|
|
1648
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1649
|
+
{
|
|
1650
|
+
role: "toolResult",
|
|
1651
|
+
toolCallId: "c1",
|
|
1652
|
+
toolName: "read",
|
|
1653
|
+
content: "tok_abc123",
|
|
1654
|
+
isError: false,
|
|
1655
|
+
timestamp: 2,
|
|
1656
|
+
} as unknown as AgentMessage,
|
|
1657
|
+
];
|
|
1658
|
+
const host: AdvisorRuntimeHost = {
|
|
1659
|
+
snapshotMessages: () => messages,
|
|
1660
|
+
enqueueAdvice: () => {},
|
|
1661
|
+
obfuscator,
|
|
1662
|
+
};
|
|
1663
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1664
|
+
|
|
1665
|
+
runtime.onTurnEnd();
|
|
1666
|
+
await Promise.resolve();
|
|
1667
|
+
|
|
1668
|
+
expect(promptInputs).toHaveLength(1);
|
|
1669
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1670
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1671
|
+
});
|
|
1672
|
+
it("does not scan tool-call arguments hidden by the primary-argument preview", async () => {
|
|
1673
|
+
const obfuscator = new SecretObfuscator([
|
|
1674
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1675
|
+
{ type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
|
|
1676
|
+
]);
|
|
1677
|
+
const promptInputs: string[] = [];
|
|
1678
|
+
const agent = makeAgent(promptInputs);
|
|
1679
|
+
const messages: AgentMessage[] = [
|
|
1680
|
+
{ role: "user", content: "remember OTHERSECRET", timestamp: 1 } as AgentMessage,
|
|
1681
|
+
{
|
|
1682
|
+
role: "assistant",
|
|
1683
|
+
content: [
|
|
1684
|
+
{ type: "toolCall", id: "c1", name: "write", arguments: { path: "a.ts", content: "tok_abc123" } },
|
|
1685
|
+
],
|
|
1686
|
+
timestamp: 2,
|
|
1687
|
+
} as unknown as AgentMessage,
|
|
1688
|
+
];
|
|
1689
|
+
const runtime = new AdvisorRuntime(agent, {
|
|
1690
|
+
snapshotMessages: () => messages,
|
|
1691
|
+
enqueueAdvice: () => {},
|
|
1692
|
+
obfuscator,
|
|
1693
|
+
});
|
|
1694
|
+
runtime.onTurnEnd();
|
|
1695
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1696
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1697
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
it("does not scan failed tool-result text beyond its visible preview", async () => {
|
|
1701
|
+
const obfuscator = new SecretObfuscator([
|
|
1702
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1703
|
+
{ type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
|
|
1704
|
+
]);
|
|
1705
|
+
const promptInputs: string[] = [];
|
|
1706
|
+
const agent = makeAgent(promptInputs);
|
|
1707
|
+
const messages: AgentMessage[] = [
|
|
1708
|
+
{ role: "user", content: "remember OTHERSECRET", timestamp: 1 } as AgentMessage,
|
|
1709
|
+
{
|
|
1710
|
+
role: "toolResult",
|
|
1711
|
+
toolCallId: "c1",
|
|
1712
|
+
toolName: "read",
|
|
1713
|
+
content: `${"x".repeat(120)} tok_abc123`,
|
|
1714
|
+
isError: true,
|
|
1715
|
+
timestamp: 2,
|
|
1716
|
+
} as unknown as AgentMessage,
|
|
1717
|
+
];
|
|
1718
|
+
const runtime = new AdvisorRuntime(agent, {
|
|
1719
|
+
snapshotMessages: () => messages,
|
|
1720
|
+
enqueueAdvice: () => {},
|
|
1721
|
+
obfuscator,
|
|
1722
|
+
});
|
|
1723
|
+
runtime.onTurnEnd();
|
|
1724
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1725
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1726
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
it("does not scan advisor-hidden execution output", async () => {
|
|
1730
|
+
const obfuscator = new SecretObfuscator([
|
|
1731
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1732
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1733
|
+
]);
|
|
1734
|
+
const promptInputs: string[] = [];
|
|
1735
|
+
const agent = makeAgent(promptInputs);
|
|
1736
|
+
const messages: AgentMessage[] = [
|
|
1737
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1738
|
+
{
|
|
1739
|
+
role: "bashExecution",
|
|
1740
|
+
command: "echo ok",
|
|
1741
|
+
output: "tok_abc123",
|
|
1742
|
+
exitCode: 0,
|
|
1743
|
+
timestamp: 2,
|
|
1744
|
+
} as unknown as AgentMessage,
|
|
1745
|
+
{
|
|
1746
|
+
role: "pythonExecution",
|
|
1747
|
+
code: "print('ok')",
|
|
1748
|
+
output: "tok_abc123",
|
|
1749
|
+
exitCode: 0,
|
|
1750
|
+
timestamp: 3,
|
|
1751
|
+
} as unknown as AgentMessage,
|
|
1752
|
+
];
|
|
1753
|
+
const host: AdvisorRuntimeHost = {
|
|
1754
|
+
snapshotMessages: () => messages,
|
|
1755
|
+
enqueueAdvice: () => {},
|
|
1756
|
+
obfuscator,
|
|
1757
|
+
};
|
|
1758
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1759
|
+
|
|
1760
|
+
runtime.onTurnEnd();
|
|
1761
|
+
await Promise.resolve();
|
|
1762
|
+
|
|
1763
|
+
expect(promptInputs).toHaveLength(1);
|
|
1764
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1765
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1766
|
+
});
|
|
1767
|
+
it("does not scan execution source after the advisor preview cap", async () => {
|
|
1768
|
+
const obfuscator = new SecretObfuscator([
|
|
1769
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1770
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1771
|
+
]);
|
|
1772
|
+
const promptInputs: string[] = [];
|
|
1773
|
+
const agent = makeAgent(promptInputs);
|
|
1774
|
+
const hiddenSuffix = `${"x".repeat(120)} tok_abc123`;
|
|
1775
|
+
const messages: AgentMessage[] = [
|
|
1776
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1777
|
+
{
|
|
1778
|
+
role: "bashExecution",
|
|
1779
|
+
command: `echo ${hiddenSuffix}`,
|
|
1780
|
+
exitCode: 0,
|
|
1781
|
+
timestamp: 2,
|
|
1782
|
+
} as unknown as AgentMessage,
|
|
1783
|
+
{
|
|
1784
|
+
role: "pythonExecution",
|
|
1785
|
+
code: `print("${hiddenSuffix}")`,
|
|
1786
|
+
exitCode: 0,
|
|
1787
|
+
timestamp: 3,
|
|
1788
|
+
} as unknown as AgentMessage,
|
|
1789
|
+
];
|
|
1790
|
+
const host: AdvisorRuntimeHost = {
|
|
1791
|
+
snapshotMessages: () => messages,
|
|
1792
|
+
enqueueAdvice: () => {},
|
|
1793
|
+
obfuscator,
|
|
1794
|
+
};
|
|
1795
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1796
|
+
|
|
1797
|
+
runtime.onTurnEnd();
|
|
1798
|
+
await Promise.resolve();
|
|
1799
|
+
|
|
1800
|
+
expect(promptInputs).toHaveLength(1);
|
|
1801
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1802
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
it("does not scan advisor-hidden file mention content", async () => {
|
|
1806
|
+
const obfuscator = new SecretObfuscator([
|
|
1807
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1808
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1809
|
+
]);
|
|
1810
|
+
const promptInputs: string[] = [];
|
|
1811
|
+
const agent = makeAgent(promptInputs);
|
|
1812
|
+
const obfuscate = vi.spyOn(obfuscator, "obfuscate");
|
|
1813
|
+
const messages: AgentMessage[] = [
|
|
1814
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1815
|
+
{
|
|
1816
|
+
role: "fileMention",
|
|
1817
|
+
files: [{ path: "config.ts", content: "tok_abc123" }],
|
|
1818
|
+
timestamp: 2,
|
|
1819
|
+
} as unknown as AgentMessage,
|
|
1820
|
+
];
|
|
1821
|
+
const host: AdvisorRuntimeHost = {
|
|
1822
|
+
snapshotMessages: () => messages,
|
|
1823
|
+
enqueueAdvice: () => {},
|
|
1824
|
+
obfuscator,
|
|
1825
|
+
};
|
|
1826
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1827
|
+
|
|
1828
|
+
runtime.onTurnEnd();
|
|
1829
|
+
await Promise.resolve();
|
|
1830
|
+
|
|
1831
|
+
expect(promptInputs).toHaveLength(1);
|
|
1832
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1833
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1834
|
+
expect(obfuscate).not.toHaveBeenCalledWith("tok_abc123", expect.anything());
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
it("does not scan or redact advisor-hidden custom payloads", async () => {
|
|
1838
|
+
const obfuscator = new SecretObfuscator([
|
|
1839
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1840
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1841
|
+
]);
|
|
1842
|
+
const promptInputs: string[] = [];
|
|
1843
|
+
const agent = makeAgent(promptInputs);
|
|
1844
|
+
const obfuscate = vi.spyOn(obfuscator, "obfuscate");
|
|
1845
|
+
const messages: AgentMessage[] = [
|
|
1846
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1847
|
+
{
|
|
1848
|
+
role: "custom",
|
|
1849
|
+
customType: "extension-payload",
|
|
1850
|
+
display: false,
|
|
1851
|
+
content: "tok_abc123",
|
|
1852
|
+
details: { payload: "tok_abc123" },
|
|
1853
|
+
timestamp: 2,
|
|
1854
|
+
} as unknown as AgentMessage,
|
|
1855
|
+
];
|
|
1856
|
+
const host: AdvisorRuntimeHost = {
|
|
1857
|
+
snapshotMessages: () => messages,
|
|
1858
|
+
enqueueAdvice: () => {},
|
|
1859
|
+
obfuscator,
|
|
1860
|
+
};
|
|
1861
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1862
|
+
|
|
1863
|
+
runtime.onTurnEnd();
|
|
1864
|
+
await Promise.resolve();
|
|
1865
|
+
|
|
1866
|
+
expect(promptInputs).toHaveLength(1);
|
|
1867
|
+
expect(promptInputs[0]).toContain("#TOKABC123_");
|
|
1868
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1869
|
+
expect(obfuscate).not.toHaveBeenCalledWith("tok_abc123", expect.anything());
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
it("shares regex-protected values across the whole advisor delta so an earlier field's friendly prefix cannot leak a sibling field's secret", async () => {
|
|
1873
|
+
// Regression: obfuscateAdvisorDelta must precompute regex-protected values
|
|
1874
|
+
// (collectAdvisorRegexSecretValues) across every field of the WHOLE advisor
|
|
1875
|
+
// delta before redacting any single message — mirroring the whole-batch
|
|
1876
|
+
// precomputation obfuscateMessages performs for the primary provider path
|
|
1877
|
+
// (see secrets-obfuscator.test.ts). Redacting message fields independently
|
|
1878
|
+
// would let the EARLIER user message's plain secret (OTHERSECRET) mint a
|
|
1879
|
+
// friendly-prefixed placeholder ("#TOKABC123_<hash>#") before the SIBLING
|
|
1880
|
+
// toolResult's `details.diff` field, later in the same delta, reveals the
|
|
1881
|
+
// regex-protected value that friendly name normalizes to
|
|
1882
|
+
// (tok_abc123 -> TOKABC123) — baking a normalized rendering of that
|
|
1883
|
+
// still-undiscovered secret into the advisor-bound prompt as an "innocent"
|
|
1884
|
+
// friendly label instead of a bare placeholder.
|
|
1885
|
+
const obfuscator = new SecretObfuscator([
|
|
1886
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1887
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
1888
|
+
]);
|
|
1889
|
+
const promptInputs: string[] = [];
|
|
1890
|
+
const agent = makeAgent(promptInputs);
|
|
1891
|
+
const diff = `--- a/config.ts\n+++ b/config.ts\n@@ -1 +1 @@\n-const token = "old";\n+const token = "tok_abc123";`;
|
|
1892
|
+
const messages: AgentMessage[] = [
|
|
1893
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
1894
|
+
{
|
|
1895
|
+
role: "assistant",
|
|
1896
|
+
content: [{ type: "toolCall", id: "c1", name: "edit", arguments: { path: "config.ts" } }],
|
|
1897
|
+
timestamp: 2,
|
|
1898
|
+
} as unknown as AgentMessage,
|
|
1899
|
+
{
|
|
1900
|
+
role: "toolResult",
|
|
1901
|
+
toolCallId: "c1",
|
|
1902
|
+
toolName: "edit",
|
|
1903
|
+
content: "ok",
|
|
1904
|
+
details: { diff },
|
|
1905
|
+
timestamp: 3,
|
|
1906
|
+
} as unknown as AgentMessage,
|
|
1907
|
+
];
|
|
1908
|
+
const host: AdvisorRuntimeHost = {
|
|
1909
|
+
snapshotMessages: () => messages,
|
|
1910
|
+
enqueueAdvice: () => {},
|
|
1911
|
+
obfuscator,
|
|
1912
|
+
};
|
|
1913
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1914
|
+
|
|
1915
|
+
runtime.onTurnEnd();
|
|
1916
|
+
await Promise.resolve();
|
|
1917
|
+
|
|
1918
|
+
expect(promptInputs).toHaveLength(1);
|
|
1919
|
+
const prompt = promptInputs[0]!;
|
|
1920
|
+
expect(prompt).not.toContain("OTHERSECRET");
|
|
1921
|
+
expect(prompt).not.toContain("tok_abc123");
|
|
1922
|
+
// The friendly prefix is itself a normalized rendering of the
|
|
1923
|
+
// later-discovered regex value; sharing regex values across the whole
|
|
1924
|
+
// delta up front must strip it to a bare placeholder rather than bake
|
|
1925
|
+
// it into the earlier user message's rendering.
|
|
1926
|
+
expect(prompt).not.toContain("TOKABC123_");
|
|
1927
|
+
|
|
1928
|
+
// Both originals still round-trip through deobfuscation of the
|
|
1929
|
+
// advisor-bound prompt text.
|
|
1930
|
+
const restored = obfuscator.deobfuscate(prompt);
|
|
1931
|
+
expect(restored).toContain("OTHERSECRET");
|
|
1932
|
+
expect(restored).toContain("tok_abc123");
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
it("keeps replace-regex collisions across advisor deltas", async () => {
|
|
1936
|
+
const obfuscator = new SecretObfuscator([
|
|
1937
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1938
|
+
{ type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
|
|
1939
|
+
]);
|
|
1940
|
+
const promptInputs: string[] = [];
|
|
1941
|
+
const agent = makeAgent(promptInputs);
|
|
1942
|
+
const messages: AgentMessage[] = [{ role: "user", content: "first tok_abc123", timestamp: 1 } as AgentMessage];
|
|
1943
|
+
const host: AdvisorRuntimeHost = {
|
|
1944
|
+
snapshotMessages: () => messages,
|
|
1945
|
+
enqueueAdvice: () => {},
|
|
1946
|
+
obfuscator,
|
|
1947
|
+
};
|
|
1948
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1949
|
+
|
|
1950
|
+
runtime.onTurnEnd();
|
|
1951
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1952
|
+
messages.push({ role: "user", content: "then OTHERSECRET", timestamp: 2 } as AgentMessage);
|
|
1953
|
+
runtime.onTurnEnd();
|
|
1954
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1955
|
+
|
|
1956
|
+
expect(promptInputs).toHaveLength(2);
|
|
1957
|
+
expect(promptInputs[0]).not.toContain("tok_abc123");
|
|
1958
|
+
expect(promptInputs[1]).not.toContain("OTHERSECRET");
|
|
1959
|
+
expect(promptInputs[1]).not.toContain("TOKABC123_");
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
it("scrubs prior advisor prompts when a later replace regex collides with their friendly prefix", async () => {
|
|
1963
|
+
const obfuscator = new SecretObfuscator([
|
|
1964
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
1965
|
+
{ type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
|
|
1966
|
+
]);
|
|
1967
|
+
const promptInputs: string[] = [];
|
|
1968
|
+
const agent = makeAgent(promptInputs);
|
|
1969
|
+
const firstStoredPrompt = (): string => {
|
|
1970
|
+
const message = agent.state.messages[0];
|
|
1971
|
+
if (message?.role !== "user" || !("content" in message) || typeof message.content !== "string") {
|
|
1972
|
+
throw new Error("Expected the first advisor history item to be a user prompt");
|
|
1973
|
+
}
|
|
1974
|
+
return message.content;
|
|
1975
|
+
};
|
|
1976
|
+
const messages: AgentMessage[] = [
|
|
1977
|
+
{ role: "user", content: "remember OTHERSECRET", timestamp: 1 } as AgentMessage,
|
|
1978
|
+
];
|
|
1979
|
+
const host: AdvisorRuntimeHost = {
|
|
1980
|
+
snapshotMessages: () => messages,
|
|
1981
|
+
enqueueAdvice: () => {},
|
|
1982
|
+
obfuscator,
|
|
1983
|
+
};
|
|
1984
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
1985
|
+
|
|
1986
|
+
runtime.onTurnEnd();
|
|
1987
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1988
|
+
agent.state.messages.push({ role: "user", content: promptInputs[0]!, timestamp: 1 } as AgentMessage);
|
|
1989
|
+
expect(firstStoredPrompt()).toContain("TOKABC123_");
|
|
1990
|
+
|
|
1991
|
+
messages.push({ role: "user", content: "later tok_abc123", timestamp: 2 } as AgentMessage);
|
|
1992
|
+
runtime.onTurnEnd();
|
|
1993
|
+
await runtime.waitForCatchup(1000, 1);
|
|
1994
|
+
|
|
1995
|
+
expect(promptInputs).toHaveLength(2);
|
|
1996
|
+
expect(firstStoredPrompt()).not.toContain("TOKABC123_");
|
|
1997
|
+
expect(promptInputs[1]).not.toContain("TOKABC123_");
|
|
1998
|
+
});
|
|
1999
|
+
|
|
2000
|
+
it("redacts secrets inside assistant thinking blocks, honoring the whole-delta friendly-prefix collision set", async () => {
|
|
2001
|
+
// Regression: obfuscateAssistantMessage (the advisor-local redaction path)
|
|
2002
|
+
// must rewrite `thinking` blocks the same way it rewrites `text` blocks.
|
|
2003
|
+
// Mirrors the collision scenario above but sources the friendly-prefixed
|
|
2004
|
+
// placeholder from a PRIOR thinking block: if thinking fell through
|
|
2005
|
+
// unredacted, the advisor prompt would receive both the raw secret AND,
|
|
2006
|
+
// had it been redacted without sharing the regex collision set, a
|
|
2007
|
+
// normalized "#TOKABC123_<hash>#" rendering of the regex-protected value
|
|
2008
|
+
// (tok_abc123) only discovered later in the same delta.
|
|
2009
|
+
const obfuscator = new SecretObfuscator([
|
|
2010
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
2011
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
2012
|
+
]);
|
|
2013
|
+
const promptInputs: string[] = [];
|
|
2014
|
+
const agent = makeAgent(promptInputs);
|
|
2015
|
+
const diff = `--- a/config.ts\n+++ b/config.ts\n@@ -1 +1 @@\n-const token = "old";\n+const token = "tok_abc123";`;
|
|
2016
|
+
const messages: AgentMessage[] = [
|
|
2017
|
+
{
|
|
2018
|
+
role: "assistant",
|
|
2019
|
+
content: [
|
|
2020
|
+
{ type: "thinking", thinking: "Remember OTHERSECRET for later." },
|
|
2021
|
+
{ type: "toolCall", id: "c1", name: "edit", arguments: { path: "config.ts" } },
|
|
2022
|
+
],
|
|
2023
|
+
timestamp: 1,
|
|
2024
|
+
} as unknown as AgentMessage,
|
|
2025
|
+
{
|
|
2026
|
+
role: "toolResult",
|
|
2027
|
+
toolCallId: "c1",
|
|
2028
|
+
toolName: "edit",
|
|
2029
|
+
content: "ok",
|
|
2030
|
+
details: { diff },
|
|
2031
|
+
timestamp: 2,
|
|
2032
|
+
} as unknown as AgentMessage,
|
|
2033
|
+
];
|
|
2034
|
+
const host: AdvisorRuntimeHost = {
|
|
2035
|
+
snapshotMessages: () => messages,
|
|
2036
|
+
enqueueAdvice: () => {},
|
|
2037
|
+
obfuscator,
|
|
2038
|
+
};
|
|
2039
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
2040
|
+
|
|
2041
|
+
runtime.onTurnEnd();
|
|
2042
|
+
await Promise.resolve();
|
|
2043
|
+
|
|
2044
|
+
expect(promptInputs).toHaveLength(1);
|
|
2045
|
+
const prompt = promptInputs[0]!;
|
|
2046
|
+
expect(prompt).toContain("_thinking:_");
|
|
2047
|
+
expect(prompt).not.toContain("OTHERSECRET");
|
|
2048
|
+
expect(prompt).not.toContain("tok_abc123");
|
|
2049
|
+
expect(prompt).not.toContain("TOKABC123_");
|
|
2050
|
+
|
|
2051
|
+
// Both originals still round-trip through deobfuscation of the
|
|
2052
|
+
// advisor-bound prompt text.
|
|
2053
|
+
const restored = obfuscator.deobfuscate(prompt);
|
|
2054
|
+
expect(restored).toContain("OTHERSECRET");
|
|
2055
|
+
expect(restored).toContain("tok_abc123");
|
|
2056
|
+
});
|
|
2057
|
+
it("clears advisor thinking signatures when collision scrubbing rewrites their text", async () => {
|
|
2058
|
+
const obfuscator = new SecretObfuscator([
|
|
2059
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
2060
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
2061
|
+
]);
|
|
2062
|
+
const promptInputs: string[] = [];
|
|
2063
|
+
const agent = makeAgent(promptInputs);
|
|
2064
|
+
const staleThinking = obfuscator.obfuscate("OTHERSECRET");
|
|
2065
|
+
agent.state.messages.push({
|
|
2066
|
+
role: "assistant",
|
|
2067
|
+
content: [{ type: "thinking", thinking: staleThinking, thinkingSignature: "signed-thinking" }],
|
|
2068
|
+
timestamp: 1,
|
|
2069
|
+
} as unknown as AgentMessage);
|
|
2070
|
+
const messages: AgentMessage[] = [{ role: "user", content: "later tok_abc123", timestamp: 2 } as AgentMessage];
|
|
2071
|
+
const host: AdvisorRuntimeHost = {
|
|
2072
|
+
snapshotMessages: () => messages,
|
|
2073
|
+
enqueueAdvice: () => {},
|
|
2074
|
+
obfuscator,
|
|
2075
|
+
};
|
|
2076
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
2077
|
+
|
|
2078
|
+
runtime.onTurnEnd();
|
|
2079
|
+
await runtime.waitForCatchup(1000, 1);
|
|
2080
|
+
|
|
2081
|
+
const storedAssistant = agent.state.messages[0] as AssistantMessage;
|
|
2082
|
+
const thinking = storedAssistant.content.find(block => block.type === "thinking");
|
|
2083
|
+
expect(thinking?.thinking).not.toContain("TOKABC123_");
|
|
2084
|
+
expect(thinking?.thinkingSignature).toBeUndefined();
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
it("skips raw image payload bytes when collecting regex-protected values, so image data cannot spuriously trigger friendly-prefix collision avoidance", async () => {
|
|
2088
|
+
// Regression: collectAdvisorRegexSecretValues's generic tree walk only
|
|
2089
|
+
// skipped strings already shaped like a `data:image/...` URL, but
|
|
2090
|
+
// `ImageContent.data` at rest is raw base64 (that URL form only exists
|
|
2091
|
+
// in the rendered viewer). Left unguarded, every image payload in the
|
|
2092
|
+
// raw message array gets regex-scanned on every advisor turn —
|
|
2093
|
+
// wasteful for large screenshots the advisor never even sees (images
|
|
2094
|
+
// render as the literal "[image]" marker) — and an accidental regex
|
|
2095
|
+
// match inside the base64 bytes would poison the whole-delta collision
|
|
2096
|
+
// set used to decide whether OTHER fields' friendly-name placeholders
|
|
2097
|
+
// are safe to render.
|
|
2098
|
+
const obfuscator = new SecretObfuscator([
|
|
2099
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
2100
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
2101
|
+
]);
|
|
2102
|
+
const promptInputs: string[] = [];
|
|
2103
|
+
const agent = makeAgent(promptInputs);
|
|
2104
|
+
const messages: AgentMessage[] = [
|
|
2105
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
2106
|
+
{
|
|
2107
|
+
role: "user",
|
|
2108
|
+
content: [{ type: "image", data: "binary noise tok_abc123 more noise", mimeType: "image/png" }],
|
|
2109
|
+
timestamp: 2,
|
|
2110
|
+
} as unknown as AgentMessage,
|
|
2111
|
+
];
|
|
2112
|
+
const host: AdvisorRuntimeHost = {
|
|
2113
|
+
snapshotMessages: () => messages,
|
|
2114
|
+
enqueueAdvice: () => {},
|
|
2115
|
+
obfuscator,
|
|
2116
|
+
};
|
|
2117
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
2118
|
+
|
|
2119
|
+
runtime.onTurnEnd();
|
|
2120
|
+
await Promise.resolve();
|
|
2121
|
+
|
|
2122
|
+
expect(promptInputs).toHaveLength(1);
|
|
2123
|
+
const prompt = promptInputs[0]!;
|
|
2124
|
+
expect(prompt).not.toContain("OTHERSECRET");
|
|
2125
|
+
// Because the image bytes were skipped by the collision pre-scan, the
|
|
2126
|
+
// plain secret's friendly-name placeholder needed no collision avoidance.
|
|
2127
|
+
expect(prompt).toContain("TOKABC123_");
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
it("ignores assistant provider replay payloads when collecting regex collision values", async () => {
|
|
2131
|
+
const obfuscator = new SecretObfuscator([
|
|
2132
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
2133
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
2134
|
+
]);
|
|
2135
|
+
const promptInputs: string[] = [];
|
|
2136
|
+
const agent = makeAgent(promptInputs);
|
|
2137
|
+
const messages: AgentMessage[] = [
|
|
2138
|
+
{
|
|
2139
|
+
role: "assistant",
|
|
2140
|
+
content: [{ type: "text", text: "remember OTHERSECRET for later" }],
|
|
2141
|
+
providerPayload: { items: [{ note: "tok_abc123" }] },
|
|
2142
|
+
timestamp: 1,
|
|
2143
|
+
} as unknown as AgentMessage,
|
|
2144
|
+
];
|
|
2145
|
+
const host: AdvisorRuntimeHost = {
|
|
2146
|
+
snapshotMessages: () => messages,
|
|
2147
|
+
enqueueAdvice: () => {},
|
|
2148
|
+
obfuscator,
|
|
2149
|
+
};
|
|
2150
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
2151
|
+
|
|
2152
|
+
runtime.onTurnEnd();
|
|
2153
|
+
await Promise.resolve();
|
|
2154
|
+
|
|
2155
|
+
expect(promptInputs).toHaveLength(1);
|
|
2156
|
+
const prompt = promptInputs[0]!;
|
|
2157
|
+
expect(prompt).not.toContain("OTHERSECRET");
|
|
2158
|
+
expect(prompt).not.toContain("tok_abc123");
|
|
2159
|
+
expect(prompt).toContain("TOKABC123_");
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
it("does not scan tool arguments omitted by the primary-argument preview", async () => {
|
|
2163
|
+
const obfuscator = new SecretObfuscator([
|
|
2164
|
+
{ type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
|
|
2165
|
+
{ type: "regex", content: "tok_[a-z0-9]+" },
|
|
2166
|
+
]);
|
|
2167
|
+
const promptInputs: string[] = [];
|
|
2168
|
+
const agent = makeAgent(promptInputs);
|
|
2169
|
+
const messages: AgentMessage[] = [
|
|
2170
|
+
{ role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
|
|
2171
|
+
{
|
|
2172
|
+
role: "assistant",
|
|
2173
|
+
content: [
|
|
2174
|
+
{ type: "toolCall", id: "call-1", name: "read", arguments: { type: "image", value: "tok_abc123" } },
|
|
2175
|
+
],
|
|
2176
|
+
timestamp: 2,
|
|
2177
|
+
} as unknown as AgentMessage,
|
|
2178
|
+
];
|
|
2179
|
+
const host: AdvisorRuntimeHost = {
|
|
2180
|
+
snapshotMessages: () => messages,
|
|
2181
|
+
enqueueAdvice: () => {},
|
|
2182
|
+
obfuscator,
|
|
2183
|
+
};
|
|
2184
|
+
const runtime = new AdvisorRuntime(agent, host);
|
|
2185
|
+
|
|
2186
|
+
runtime.onTurnEnd();
|
|
2187
|
+
await Promise.resolve();
|
|
2188
|
+
|
|
2189
|
+
expect(promptInputs).toHaveLength(1);
|
|
2190
|
+
const prompt = promptInputs[0]!;
|
|
2191
|
+
expect(prompt).not.toContain("OTHERSECRET");
|
|
2192
|
+
expect(prompt).not.toContain("tok_abc123");
|
|
2193
|
+
expect(prompt).toContain("TOKABC123_");
|
|
2194
|
+
});
|
|
2195
|
+
|
|
1553
2196
|
it("expands plan-mode context once, then collapses an unchanged re-injection", async () => {
|
|
1554
2197
|
const promptInputs: string[] = [];
|
|
1555
2198
|
const { promise: firstPromptDone, resolve: finishFirst } = Promise.withResolvers<void>();
|