@stigmer/runner 3.10.0 → 3.11.1-dev.20260812192248
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/README.md +14 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-llm.js +9 -10
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +2 -1
- package/dist/activities/classify-tool-approvals.js +28 -2
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +39 -0
- package/dist/activities/discover-mcp-server.js +165 -28
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/emit-event.d.ts +14 -2
- package/dist/activities/emit-event.js +52 -17
- package/dist/activities/emit-event.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
- package/dist/activities/execute-cursor/approval-state.js +7 -1
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +18 -4
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/env-resolver.js +3 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +59 -3
- package/dist/activities/execute-cursor/error-classifier.js +115 -6
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
- package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
- package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
- package/dist/activities/execute-cursor/hook-script.js +72 -10
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +51 -11
- package/dist/activities/execute-cursor/index.js +177 -93
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
- package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
- package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
- package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
- package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +51 -4
- package/dist/activities/execute-cursor/prompt-builder.js +49 -7
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
- package/dist/activities/execute-cursor/turn-recovery.js +193 -0
- package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
- package/dist/activities/execute-cursor/turn-stream.js +4 -1
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/environment.js +3 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +15 -0
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
- package/dist/activities/execute-deep-agent/setup.js +65 -24
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
- package/dist/activities/execute-deep-agent/shell-env.js +7 -9
- package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +15 -2
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +27 -6
- package/dist/activities/execute-deep-agent/subagent-wiring.js +26 -5
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -3
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/run-command.d.ts +5 -2
- package/dist/activities/run-command.js +22 -10
- package/dist/activities/run-command.js.map +1 -1
- package/dist/activities/run-env.d.ts +40 -0
- package/dist/activities/run-env.js +66 -0
- package/dist/activities/run-env.js.map +1 -0
- package/dist/activities/workflow-event-activities.d.ts +28 -10
- package/dist/activities/workflow-event-activities.js +87 -58
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/bootstrap.js +5 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +21 -1
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +83 -21
- package/dist/client/stigmer-client.js +106 -29
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/encryption/config.d.ts +64 -0
- package/dist/encryption/config.js +108 -0
- package/dist/encryption/config.js.map +1 -0
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.js +3 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/payload-codec.d.ts +41 -0
- package/dist/encryption/payload-codec.js +130 -0
- package/dist/encryption/payload-codec.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/path-normalization.d.ts +57 -0
- package/dist/middleware/path-normalization.js +109 -0
- package/dist/middleware/path-normalization.js.map +1 -0
- package/dist/middleware/types.d.ts +8 -0
- package/dist/payload-codecs.d.ts +17 -0
- package/dist/payload-codecs.js +42 -0
- package/dist/payload-codecs.js.map +1 -0
- package/dist/preflight.d.ts +31 -0
- package/dist/preflight.js +43 -0
- package/dist/preflight.js.map +1 -1
- package/dist/runner-manager.js +21 -17
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +11 -18
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +9 -3
- package/dist/shared/approval-policy.js +15 -6
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +23 -1
- package/dist/shared/artifact-storage.js +51 -10
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-naming.d.ts +53 -0
- package/dist/shared/attachment-naming.js +59 -0
- package/dist/shared/attachment-naming.js.map +1 -0
- package/dist/shared/caller-identity.d.ts +23 -2
- package/dist/shared/caller-identity.js +36 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +1 -0
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +26 -1
- package/dist/shared/checkpointer/http-saver.js +54 -8
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.js +1 -0
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/conversation-catchup.d.ts +9 -2
- package/dist/shared/conversation-catchup.js +39 -6
- package/dist/shared/conversation-catchup.js.map +1 -1
- package/dist/shared/datastore-attachment.d.ts +50 -7
- package/dist/shared/datastore-attachment.js +93 -11
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/grpc-retry.d.ts +5 -1
- package/dist/shared/grpc-retry.js +5 -1
- package/dist/shared/grpc-retry.js.map +1 -1
- package/dist/shared/http-retry.d.ts +85 -0
- package/dist/shared/http-retry.js +101 -0
- package/dist/shared/http-retry.js.map +1 -0
- package/dist/shared/llm-backend.d.ts +275 -0
- package/dist/shared/llm-backend.js +425 -0
- package/dist/shared/llm-backend.js.map +1 -0
- package/dist/shared/llm-proxy.d.ts +8 -0
- package/dist/shared/llm-proxy.js +15 -0
- package/dist/shared/llm-proxy.js.map +1 -1
- package/dist/shared/mcp-enabled-tools.d.ts +57 -0
- package/dist/shared/mcp-enabled-tools.js +86 -0
- package/dist/shared/mcp-enabled-tools.js.map +1 -0
- package/dist/shared/mcp-manager.d.ts +11 -5
- package/dist/shared/mcp-manager.js +47 -8
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +39 -2
- package/dist/shared/mcp-resolver.js +38 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
- package/dist/shared/mcp-schema-sanitizer.js +197 -0
- package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
- package/dist/shared/model-client.d.ts +18 -5
- package/dist/shared/model-client.js +149 -18
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-error.js +198 -5
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/model-pricing-data.d.ts +2 -0
- package/dist/shared/model-pricing-data.js +13 -3
- package/dist/shared/model-pricing-data.js.map +1 -1
- package/dist/shared/model-registry.js +3 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +33 -0
- package/dist/shared/plan-mode-permissions.js +35 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -0
- package/dist/shared/registry-endpoint.d.ts +20 -0
- package/dist/shared/registry-endpoint.js +25 -0
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +24 -0
- package/dist/shared/runner-credential-keys.js +47 -0
- package/dist/shared/runner-credential-keys.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -4
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/resolve.d.ts +19 -1
- package/dist/workflow-engine/resolve.js +37 -2
- package/dist/workflow-engine/resolve.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +18 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
- package/dist/workflows/call-agent-orchestrator.js +1 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +9 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/engine-core.js +23 -2
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +1 -1
- package/dist/workflows/execute-from-execution.js +11 -1
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +9 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/claimcheck-codec.test.ts +36 -0
- package/src/__tests__/encryption-codec.test.ts +287 -0
- package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
- package/src/__tests__/history-encryption-e2e.test.ts +243 -0
- package/src/__tests__/preflight.test.ts +65 -2
- package/src/activities/__tests__/call-llm.test.ts +75 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +242 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
- package/src/activities/call-llm.ts +9 -16
- package/src/activities/classify-tool-approvals.ts +34 -4
- package/src/activities/discover-mcp-server.ts +203 -31
- package/src/activities/emit-event.ts +65 -21
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +53 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +151 -16
- package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
- package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
- package/src/activities/execute-cursor/approval-state.ts +30 -1
- package/src/activities/execute-cursor/attachment-resolver.ts +31 -3
- package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
- package/src/activities/execute-cursor/env-resolver.ts +3 -1
- package/src/activities/execute-cursor/error-classifier.ts +131 -7
- package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
- package/src/activities/execute-cursor/hook-script.ts +74 -10
- package/src/activities/execute-cursor/index.ts +209 -109
- package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +85 -9
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- package/src/activities/execute-cursor/turn-stream.ts +5 -2
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +110 -8
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +178 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
- package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
- package/src/activities/execute-deep-agent/environment.ts +3 -1
- package/src/activities/execute-deep-agent/index.ts +20 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
- package/src/activities/execute-deep-agent/setup.ts +85 -29
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +30 -2
- package/src/activities/execute-deep-agent/subagent-wiring.ts +39 -6
- package/src/activities/hydrate-workflow-execution.ts +8 -3
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/activities/workflow-event-activities.ts +96 -69
- package/src/bootstrap.ts +5 -0
- package/src/claimcheck/payload-codec.ts +33 -1
- package/src/client/__tests__/stigmer-client.test.ts +110 -11
- package/src/client/stigmer-client.ts +150 -37
- package/src/encryption/config.ts +155 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/payload-codec.ts +152 -0
- package/src/middleware/__tests__/path-normalization.test.ts +140 -0
- package/src/middleware/index.ts +8 -0
- package/src/middleware/path-normalization.ts +125 -0
- package/src/middleware/types.ts +11 -0
- package/src/payload-codecs.ts +62 -0
- package/src/preflight.ts +45 -0
- package/src/runner-manager.ts +26 -26
- package/src/runner.ts +12 -27
- package/src/shared/__tests__/approval-policy.test.ts +82 -39
- package/src/shared/__tests__/artifact-storage.test.ts +145 -2
- package/src/shared/__tests__/attachment-naming.test.ts +159 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
- package/src/shared/__tests__/caller-identity.test.ts +25 -0
- package/src/shared/__tests__/channel-attachment.test.ts +1 -1
- package/src/shared/__tests__/connect-backfill.test.ts +1 -0
- package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
- package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
- package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
- package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
- package/src/shared/__tests__/foundry-seam.test.ts +482 -0
- package/src/shared/__tests__/http-retry.test.ts +68 -0
- package/src/shared/__tests__/llm-backend.test.ts +616 -0
- package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
- package/src/shared/__tests__/mcp-manager.test.ts +137 -15
- package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +200 -0
- package/src/shared/__tests__/model-error.test.ts +289 -1
- package/src/shared/__tests__/model-pricing.test.ts +62 -2
- package/src/shared/__tests__/model-registry.test.ts +22 -0
- package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
- package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
- package/src/shared/__tests__/vertex-seam.test.ts +295 -0
- package/src/shared/approval-policy.ts +14 -7
- package/src/shared/artifact-storage.ts +75 -10
- package/src/shared/attachment-naming.ts +78 -0
- package/src/shared/caller-identity.ts +40 -5
- package/src/shared/channel-attachment.ts +1 -0
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
- package/src/shared/checkpointer/http-saver.ts +71 -8
- package/src/shared/conversation-attachment.ts +1 -0
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/datastore-attachment.ts +106 -11
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +139 -0
- package/src/shared/llm-backend.ts +544 -0
- package/src/shared/llm-proxy.ts +15 -0
- package/src/shared/mcp-enabled-tools.ts +105 -0
- package/src/shared/mcp-manager.ts +56 -8
- package/src/shared/mcp-resolver.ts +73 -2
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/model-client.ts +179 -19
- package/src/shared/model-error.ts +222 -4
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +37 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/worker.ts +4 -5
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflow-engine/types.ts +18 -0
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
- package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
- package/src/workflows/call-agent-orchestrator.ts +10 -0
- package/src/workflows/connect-mcp-server.ts +9 -0
- package/src/workflows/engine-core.ts +23 -2
- package/src/workflows/execute-from-execution.ts +12 -2
- package/src/workflows/types.ts +8 -0
- package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
- package/dist/activities/execute-cursor/mcp-config.js +0 -39
- package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
visionDisclosureLines,
|
|
37
37
|
type NotViewableEntry,
|
|
38
38
|
} from "../../shared/attachment-vision.js";
|
|
39
|
+
import { formatTurnRecoveryText } from "./turn-recovery.js";
|
|
39
40
|
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
40
41
|
import {
|
|
41
42
|
buildImplementPlanDirective,
|
|
@@ -71,6 +72,18 @@ export interface VisionPromptInfo {
|
|
|
71
72
|
notViewable: NotViewableEntry[];
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* One resolved attachment as the `<input_files>` section renders it —
|
|
77
|
+
* the final workspace-relative path plus the duplicate-rename disclosure
|
|
78
|
+
* (attachment-resolver.ts / shared/attachment-naming.ts).
|
|
79
|
+
*/
|
|
80
|
+
export interface AttachmentPromptEntry {
|
|
81
|
+
/** Workspace-relative path the agent reads (`.stigmer/inputs/{filename}`). */
|
|
82
|
+
path: string;
|
|
83
|
+
/** Original filename, present only when a duplicate name was renamed. */
|
|
84
|
+
renamedFrom?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
export interface EnhancedPromptOptions {
|
|
75
88
|
instructions: string;
|
|
76
89
|
userMessage: string;
|
|
@@ -78,7 +91,10 @@ export interface EnhancedPromptOptions {
|
|
|
78
91
|
/**
|
|
79
92
|
* Datastores attached via `datastore_usages` — rendered as the
|
|
80
93
|
* `<available_datastores>` section (DD-005 SD-5, skills precedent)
|
|
81
|
-
* pointing the model at the synthesized record tools.
|
|
94
|
+
* pointing the model at the synthesized record tools. Always the healthy
|
|
95
|
+
* rendering here: the Cursor SDK connects MCP itself, so this harness can
|
|
96
|
+
* never observe the live roster — the section's standing
|
|
97
|
+
* failure-disclosure instruction is its whole coverage (issue #325).
|
|
82
98
|
*/
|
|
83
99
|
datastoreUsages?: DatastoreUsage[];
|
|
84
100
|
/**
|
|
@@ -90,7 +106,7 @@ export interface EnhancedPromptOptions {
|
|
|
90
106
|
subAgents: SubAgent[];
|
|
91
107
|
workspaceDirs: string[];
|
|
92
108
|
workspaceFileRefs: string[];
|
|
93
|
-
|
|
109
|
+
attachments: AttachmentPromptEntry[];
|
|
94
110
|
/** Inline/degraded image facts for the input-files section (T04 vision). */
|
|
95
111
|
vision?: VisionPromptInfo;
|
|
96
112
|
interactionMode?: InteractionMode;
|
|
@@ -153,7 +169,7 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
153
169
|
|
|
154
170
|
const implementPlan = formatImplementPlanSection(
|
|
155
171
|
options.buildFromPlan,
|
|
156
|
-
options.
|
|
172
|
+
options.attachments,
|
|
157
173
|
);
|
|
158
174
|
if (implementPlan) {
|
|
159
175
|
sections.push(implementPlan);
|
|
@@ -195,8 +211,8 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
195
211
|
sections.push(formatWorkspaceContext(safeDirs));
|
|
196
212
|
}
|
|
197
213
|
|
|
198
|
-
if (options.
|
|
199
|
-
sections.push(formatInputFiles(options.
|
|
214
|
+
if (options.attachments.length > 0) {
|
|
215
|
+
sections.push(formatInputFiles(options.attachments, options.vision));
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
if (options.workspaceFileRefs.length > 0) {
|
|
@@ -323,6 +339,56 @@ export function buildReinvocationPrompt(
|
|
|
323
339
|
return parts.join("\n\n");
|
|
324
340
|
}
|
|
325
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Build the prompt for a fresh agent that replaced a lost one MID-HITL —
|
|
344
|
+
* the stored handle failed to resume when the approval landed, or the
|
|
345
|
+
* resumed handle proved poisoned mid-send (issue #366, both crossings).
|
|
346
|
+
*
|
|
347
|
+
* A resumed agent gets {@link buildReinvocationPrompt} alone because its
|
|
348
|
+
* native conversation carries everything else. A replacement agent's
|
|
349
|
+
* conversation is EMPTY, so the bare decisions prompt would strand it with
|
|
350
|
+
* instructions and no story — and, because recovery repoints the session at
|
|
351
|
+
* the new agent, every later turn would inherit that amnesia. This prompt
|
|
352
|
+
* rebuilds the whole story in chronological-narrative order:
|
|
353
|
+
*
|
|
354
|
+
* 1. the full enhanced prompt (blueprint, standing context, protocol, and
|
|
355
|
+
* the turn's ORIGINAL user message as `<user_request>`),
|
|
356
|
+
* 2. `<turn_recovery>` — the state-loss disclosure plus the recorded
|
|
357
|
+
* transcript of the turn so far (turn-recovery.ts; rendered even with no
|
|
358
|
+
* transcript, since without the disclosure the decisions below would read
|
|
359
|
+
* as reactions to proposals this agent never made),
|
|
360
|
+
* 3. the reinvocation decisions verbatim — already-applied / approved /
|
|
361
|
+
* skipped semantics byte-identical to the resumed path, ending on its
|
|
362
|
+
* "continue the task" directive.
|
|
363
|
+
*
|
|
364
|
+
* Appending past `<user_request>` is the chronology speaking (request →
|
|
365
|
+
* work done → decisions → continue), the same shape as the activity-level
|
|
366
|
+
* structured-output suffix.
|
|
367
|
+
*/
|
|
368
|
+
export function buildHitlRecoveryPrompt(
|
|
369
|
+
options: EnhancedPromptOptions,
|
|
370
|
+
recovery: {
|
|
371
|
+
turnDigest: string | undefined;
|
|
372
|
+
pendingApprovals: PendingApproval[];
|
|
373
|
+
approvalDecisions: Map<string, ApprovalAction>;
|
|
374
|
+
appliedToolCallIds?: ReadonlySet<string>;
|
|
375
|
+
},
|
|
376
|
+
): string {
|
|
377
|
+
return [
|
|
378
|
+
buildEnhancedPrompt(options),
|
|
379
|
+
formatTurnRecoverySection(recovery.turnDigest),
|
|
380
|
+
buildReinvocationPrompt(
|
|
381
|
+
recovery.pendingApprovals,
|
|
382
|
+
recovery.approvalDecisions,
|
|
383
|
+
recovery.appliedToolCallIds,
|
|
384
|
+
),
|
|
385
|
+
].join("\n\n---\n\n");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function formatTurnRecoverySection(digest: string | undefined): string {
|
|
389
|
+
return `<turn_recovery>\n${formatTurnRecoveryText(digest)}\n</turn_recovery>`;
|
|
390
|
+
}
|
|
391
|
+
|
|
326
392
|
/**
|
|
327
393
|
* Render a pending approval as a short human-readable action description for the
|
|
328
394
|
* reinvocation prompt. Prefers the already-resolved approval message; falls back
|
|
@@ -479,8 +545,18 @@ export function formatWorkspaceContext(dirs: string[]): string {
|
|
|
479
545
|
].join("\n");
|
|
480
546
|
}
|
|
481
547
|
|
|
482
|
-
export function formatInputFiles(
|
|
483
|
-
|
|
548
|
+
export function formatInputFiles(
|
|
549
|
+
attachments: readonly AttachmentPromptEntry[],
|
|
550
|
+
vision?: VisionPromptInfo,
|
|
551
|
+
): string {
|
|
552
|
+
// A duplicate-renamed file (attachment-naming.ts) discloses its original
|
|
553
|
+
// name so the agent can connect "the two report.pdfs" in the user's
|
|
554
|
+
// message to distinct files on disk.
|
|
555
|
+
const entries = attachments.map((a) =>
|
|
556
|
+
a.renamedFrom !== undefined
|
|
557
|
+
? `- \`${a.path}\` (renamed from duplicate '${a.renamedFrom}')`
|
|
558
|
+
: `- \`${a.path}\``,
|
|
559
|
+
);
|
|
484
560
|
// The vision lines (shared wording, attachment-vision.ts) tell the model
|
|
485
561
|
// which of these files it can already SEE inline versus which degraded to
|
|
486
562
|
// path-only — without them an agent silently ignores a photo the user
|
|
@@ -541,14 +617,14 @@ export function formatInteractionModePrefix(
|
|
|
541
617
|
*/
|
|
542
618
|
export function formatImplementPlanSection(
|
|
543
619
|
buildFromPlan: boolean | undefined,
|
|
544
|
-
|
|
620
|
+
attachments: readonly AttachmentPromptEntry[],
|
|
545
621
|
): string | undefined {
|
|
546
622
|
if (!buildFromPlan) {
|
|
547
623
|
return undefined;
|
|
548
624
|
}
|
|
549
625
|
|
|
550
626
|
const directive = buildImplementPlanDirective(
|
|
551
|
-
findApprovedPlanPath(
|
|
627
|
+
findApprovedPlanPath(attachments.map((a) => a.path)),
|
|
552
628
|
);
|
|
553
629
|
return ["<implement_plan>", directive, "</implement_plan>"].join("\n");
|
|
554
630
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn-recovery digest: the platform's recorded transcript of the current
|
|
3
|
+
* turn, rendered as prompt context for a fresh agent that replaced one whose
|
|
4
|
+
* conversation state was lost mid-turn (issue #366).
|
|
5
|
+
*
|
|
6
|
+
* A HITL-paused turn can lose its agent two ways — the stored handle fails
|
|
7
|
+
* to resume when the approval lands, or the resumed handle turns out to be
|
|
8
|
+
* poisoned mid-send. Either way the replacement agent starts with an EMPTY
|
|
9
|
+
* conversation, so the approval decisions alone read as instructions with no
|
|
10
|
+
* story: the agent no longer knows what it was doing. This module rebuilds
|
|
11
|
+
* that story from `status.messages` — the persisted execution transcript
|
|
12
|
+
* seeded on every re-invocation — which is the platform's durable record of
|
|
13
|
+
* the turn AND exactly what the user watched happen (introspecting the dead
|
|
14
|
+
* agent's own conversation is best-effort at most; the handle is the thing
|
|
15
|
+
* that failed).
|
|
16
|
+
*
|
|
17
|
+
* Doctrine (mirrors `ChannelRolloverBridgeComposer`, cloud DD-013 — the
|
|
18
|
+
* platform's reference for conversation digests):
|
|
19
|
+
* - Content here, presentation framing separate: {@link composeTurnRecoveryDigest}
|
|
20
|
+
* emits bare `Assistant:` / `Tool:` / `System:` lines;
|
|
21
|
+
* {@link formatTurnRecoveryText} adds the behavioral preamble; the prompt
|
|
22
|
+
* builder wraps the section tag (the context-bridge / conversation-catchup
|
|
23
|
+
* split).
|
|
24
|
+
* - Oldest-first reading order; over budget, the OLDEST lines drop first
|
|
25
|
+
* (recency wins) and the drop is disclosed.
|
|
26
|
+
* - Best-effort by contract: composition never throws — a failed digest
|
|
27
|
+
* degrades to recovery-without-transcript, never a failed recovery.
|
|
28
|
+
*
|
|
29
|
+
* Scope: the CURRENT turn only. Prior turns live in other execution records
|
|
30
|
+
* and are not recoverable runner-side (that is the rollover bridge's domain,
|
|
31
|
+
* composed cloud-side); the enhanced prompt's standing-context sections carry
|
|
32
|
+
* whatever the session already had.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
36
|
+
import { MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
37
|
+
|
|
38
|
+
/** Per-line text budget; longer texts are cut with an ellipsis (DD-013 twin). */
|
|
39
|
+
const MAX_LINE_CHARS = 400;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Args-preview budget inside a tool line — the `describeApproval` precedent
|
|
43
|
+
* (prompt-builder.ts), so tool renderings are bounded the same way on both
|
|
44
|
+
* surfaces that describe them.
|
|
45
|
+
*/
|
|
46
|
+
const MAX_ARGS_CHARS = 200;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whole-digest budget (DD-013 twin). Enforced by dropping the OLDEST lines
|
|
50
|
+
* first — recency wins, matching how a human skims their own recent work
|
|
51
|
+
* before continuing it.
|
|
52
|
+
*/
|
|
53
|
+
const MAX_DIGEST_CHARS = 4000;
|
|
54
|
+
|
|
55
|
+
const ELLIPSIS = "\u2026";
|
|
56
|
+
|
|
57
|
+
/** Disclosed in place of the dropped prefix so the record never lies by omission. */
|
|
58
|
+
const OMISSION_NOTICE = `[${ELLIPSIS} earlier activity in this turn omitted for length]`;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* How the transcript is introduced to the model. Establishes the three facts
|
|
62
|
+
* the replacement agent must hold: the work below is its OWN (continue, don't
|
|
63
|
+
* restart), completed actions are DONE (don't redo them), and the workspace —
|
|
64
|
+
* not this summary — is the source of truth for exact current state.
|
|
65
|
+
*/
|
|
66
|
+
const TURN_RECOVERY_PREAMBLE =
|
|
67
|
+
"You had already started working on the user's request above, but the " +
|
|
68
|
+
"session holding that conversation was lost, so you do not remember it. " +
|
|
69
|
+
"Below is the platform's recorded transcript of your progress in this " +
|
|
70
|
+
"turn, oldest first. Treat it as work YOU already did: do not start the " +
|
|
71
|
+
"task over, do not redo actions shown as completed, and check the " +
|
|
72
|
+
"workspace's current state where exact details matter.";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fallback framing when the turn produced no renderable transcript (or
|
|
76
|
+
* composition failed): the state-loss disclosure still matters — without it,
|
|
77
|
+
* the appended approval decisions read as reactions to proposals this agent
|
|
78
|
+
* never made.
|
|
79
|
+
*/
|
|
80
|
+
const TURN_RECOVERY_NO_TRANSCRIPT =
|
|
81
|
+
"You had already started working on the user's request above, but the " +
|
|
82
|
+
"session holding that conversation was lost, so you do not remember it " +
|
|
83
|
+
"and no transcript of your progress is available. Check the workspace's " +
|
|
84
|
+
"current state to see what you already did before continuing.";
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Render the turn's persisted transcript as bare digest lines, or undefined
|
|
88
|
+
* when there is nothing worth carrying. Never throws.
|
|
89
|
+
*
|
|
90
|
+
* Human messages are skipped — the turn's user message is already rendered
|
|
91
|
+
* in the enhanced prompt's `<user_request>`, and duplicating it here would
|
|
92
|
+
* dilute the budget. Thinking content is skipped as model-internal (the
|
|
93
|
+
* replacement agent forms its own). System notices are kept: platform
|
|
94
|
+
* messages like budget warnings or degradation notices are turn context the
|
|
95
|
+
* agent acted under.
|
|
96
|
+
*/
|
|
97
|
+
export function composeTurnRecoveryDigest(
|
|
98
|
+
messages: readonly AgentMessage[],
|
|
99
|
+
): string | undefined {
|
|
100
|
+
try {
|
|
101
|
+
const lines: string[] = [];
|
|
102
|
+
for (const message of messages) {
|
|
103
|
+
lines.push(...messageLines(message));
|
|
104
|
+
}
|
|
105
|
+
if (lines.length === 0) return undefined;
|
|
106
|
+
|
|
107
|
+
// Oldest-first; drop from the FRONT when over budget so the newest work
|
|
108
|
+
// always survives, and disclose the drop (DD-013 enforcement shape). The
|
|
109
|
+
// notice participates in the budget so the result never overshoots.
|
|
110
|
+
let first = 0;
|
|
111
|
+
while (first < lines.length && totalLength(lines, first) > MAX_DIGEST_CHARS) {
|
|
112
|
+
first++;
|
|
113
|
+
}
|
|
114
|
+
if (first >= lines.length) {
|
|
115
|
+
// Degenerate case: even the newest line alone busts the budget (it is
|
|
116
|
+
// already line-truncated, so this would take a pathological budget/line
|
|
117
|
+
// ratio) — keep that one line rather than rendering nothing.
|
|
118
|
+
first = lines.length - 1;
|
|
119
|
+
}
|
|
120
|
+
const kept = lines.slice(first);
|
|
121
|
+
if (first > 0) kept.unshift(OMISSION_NOTICE);
|
|
122
|
+
return kept.join("\n");
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn("Turn-recovery digest composition failed — recovering without a transcript:", err);
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The framed recovery body (preamble + digest), ready for section wrapping.
|
|
131
|
+
* Accepts an absent digest: the state-loss disclosure is load-bearing even
|
|
132
|
+
* when there is no transcript to show (see {@link TURN_RECOVERY_NO_TRANSCRIPT}).
|
|
133
|
+
*/
|
|
134
|
+
export function formatTurnRecoveryText(digest: string | undefined): string {
|
|
135
|
+
if (digest === undefined || digest.trim() === "") {
|
|
136
|
+
return TURN_RECOVERY_NO_TRANSCRIPT;
|
|
137
|
+
}
|
|
138
|
+
return `${TURN_RECOVERY_PREAMBLE}\n\n${digest.trim()}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The digest lines one transcript message contributes (possibly none). */
|
|
142
|
+
function messageLines(message: AgentMessage): string[] {
|
|
143
|
+
const lines: string[] = [];
|
|
144
|
+
const text = message.content.trim();
|
|
145
|
+
switch (message.type) {
|
|
146
|
+
case MessageType.MESSAGE_AI:
|
|
147
|
+
if (text) lines.push(`Assistant: ${truncate(text, MAX_LINE_CHARS)}`);
|
|
148
|
+
// Tool calls ride AI messages (message.proto contract); render them
|
|
149
|
+
// even when the surrounding text is blank.
|
|
150
|
+
for (const toolCall of message.toolCalls) {
|
|
151
|
+
lines.push(toolCallLine(toolCall));
|
|
152
|
+
}
|
|
153
|
+
return lines;
|
|
154
|
+
case MessageType.MESSAGE_SYSTEM:
|
|
155
|
+
if (text) lines.push(`System: ${truncate(text, MAX_LINE_CHARS)}`);
|
|
156
|
+
return lines;
|
|
157
|
+
default:
|
|
158
|
+
// Human (already in <user_request>), thinking (model-internal), tool
|
|
159
|
+
// results (each ToolCall line already carries its outcome), unknown.
|
|
160
|
+
return lines;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* One bounded line per tool call: what was attempted and how it ended.
|
|
166
|
+
* Prefers the resolved approval message where one exists — the same
|
|
167
|
+
* human-meaningful description the user approved against
|
|
168
|
+
* (the `describeApproval` convention).
|
|
169
|
+
*/
|
|
170
|
+
function toolCallLine(toolCall: ToolCall): string {
|
|
171
|
+
const action = toolCall.approvalMessage
|
|
172
|
+
? toolCall.approvalMessage
|
|
173
|
+
: toolCall.argsPreview
|
|
174
|
+
? `${toolCall.name}(${truncate(toolCall.argsPreview, MAX_ARGS_CHARS)})`
|
|
175
|
+
: toolCall.name;
|
|
176
|
+
return `Tool: ${truncate(action, MAX_LINE_CHARS)} — ${outcome(toolCall)}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function outcome(toolCall: ToolCall): string {
|
|
180
|
+
switch (toolCall.status) {
|
|
181
|
+
case ToolCallStatus.TOOL_CALL_COMPLETED:
|
|
182
|
+
return "completed";
|
|
183
|
+
case ToolCallStatus.TOOL_CALL_FAILED:
|
|
184
|
+
return toolCall.error
|
|
185
|
+
? `failed: ${truncate(toolCall.error, MAX_ARGS_CHARS)}`
|
|
186
|
+
: "failed";
|
|
187
|
+
case ToolCallStatus.TOOL_CALL_WAITING_APPROVAL:
|
|
188
|
+
return "paused for user approval";
|
|
189
|
+
case ToolCallStatus.TOOL_CALL_SKIPPED:
|
|
190
|
+
return "skipped";
|
|
191
|
+
default:
|
|
192
|
+
// PENDING / RUNNING / unknown: in flight when the session was lost.
|
|
193
|
+
return "interrupted before it finished";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function truncate(text: string, max: number): string {
|
|
198
|
+
return text.length <= max ? text : `${text.slice(0, max)}${ELLIPSIS}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Joined length of lines[first..] plus the omission notice when one would render. */
|
|
202
|
+
function totalLength(lines: readonly string[], first: number): number {
|
|
203
|
+
let total = first > 0 ? OMISSION_NOTICE.length + 1 : 0;
|
|
204
|
+
for (let i = first; i < lines.length; i++) {
|
|
205
|
+
total += lines[i].length + 1;
|
|
206
|
+
}
|
|
207
|
+
return total;
|
|
208
|
+
}
|
|
@@ -387,8 +387,11 @@ export async function consumeCursorTurnStream(
|
|
|
387
387
|
console.log(
|
|
388
388
|
`ExecuteCursor stream status: execution=${executionId}, status=${JSON.stringify(event)}`,
|
|
389
389
|
);
|
|
390
|
-
const statusEvent = event as { status?: string; message?:
|
|
391
|
-
|
|
390
|
+
const statusEvent = event as { status?: string; message?: unknown };
|
|
391
|
+
// The cast is a claim, not a guarantee: message is untyped at runtime
|
|
392
|
+
// (the oss#299 class of bug). A structured value here would crash
|
|
393
|
+
// classification downstream, so only capture actual text.
|
|
394
|
+
if (statusEvent.status === "ERROR" && typeof statusEvent.message === "string" && statusEvent.message.length > 0) {
|
|
392
395
|
state.streamErrorMessage = statusEvent.message;
|
|
393
396
|
}
|
|
394
397
|
}
|
|
@@ -600,24 +600,97 @@ describe("injectAttachments", () => {
|
|
|
600
600
|
expect(Buffer.compare(writtenBuffer, binaryContent)).toBe(0);
|
|
601
601
|
});
|
|
602
602
|
|
|
603
|
-
it("
|
|
603
|
+
it("uniquifies duplicate default-derived filenames instead of failing (issue #364)", async () => {
|
|
604
|
+
const contentA = Buffer.from("first");
|
|
605
|
+
const contentB = Buffer.from("second");
|
|
606
|
+
const storage = makeMockStorage();
|
|
607
|
+
await storage.upload("attachments/a/data.csv", contentA);
|
|
608
|
+
await storage.upload("attachments/b/data.csv", contentB);
|
|
609
|
+
|
|
610
|
+
const backend = mockWorkspaceBackend();
|
|
611
|
+
|
|
612
|
+
const result = await injectAttachments({
|
|
613
|
+
backend,
|
|
614
|
+
attachments: [
|
|
615
|
+
makeAttachment({ filename: "data.csv", storageKey: "attachments/a/data.csv" }),
|
|
616
|
+
makeAttachment({ filename: "data.csv", storageKey: "attachments/b/data.csv" }),
|
|
617
|
+
],
|
|
618
|
+
storage,
|
|
619
|
+
isLocalMode: false,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Both files land, both downloads run — the execution is never burned
|
|
623
|
+
// over a mechanically resolvable name.
|
|
624
|
+
expect(result).toHaveLength(2);
|
|
625
|
+
expect(result[0]).toMatchObject({
|
|
626
|
+
filename: "data.csv",
|
|
627
|
+
path: ".stigmer/inputs/data.csv",
|
|
628
|
+
});
|
|
629
|
+
expect(result[0].renamedFrom).toBeUndefined();
|
|
630
|
+
// The rename follows the platform's stem-2.ext semantics and carries the
|
|
631
|
+
// disclosure payload for the prompt.
|
|
632
|
+
expect(result[1]).toMatchObject({
|
|
633
|
+
filename: "data-2.csv",
|
|
634
|
+
path: ".stigmer/inputs/data-2.csv",
|
|
635
|
+
renamedFrom: "data.csv",
|
|
636
|
+
});
|
|
637
|
+
expect(backend.writeFileBuffer).toHaveBeenCalledWith(
|
|
638
|
+
".stigmer/inputs/data.csv", contentA,
|
|
639
|
+
);
|
|
640
|
+
expect(backend.writeFileBuffer).toHaveBeenCalledWith(
|
|
641
|
+
".stigmer/inputs/data-2.csv", contentB,
|
|
642
|
+
);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it("rejects two attachments explicitly pinning the same mountPath (a user contradiction)", async () => {
|
|
604
646
|
const backend = mockWorkspaceBackend();
|
|
605
647
|
const storage = makeMockStorage();
|
|
606
648
|
|
|
607
649
|
await expect(injectAttachments({
|
|
608
650
|
backend,
|
|
609
651
|
attachments: [
|
|
610
|
-
makeAttachment({ filename: "
|
|
611
|
-
makeAttachment({ filename: "
|
|
652
|
+
makeAttachment({ filename: "a.csv", storageKey: "key1", mountPath: "inputs/data.csv" }),
|
|
653
|
+
makeAttachment({ filename: "b.csv", storageKey: "key2", mountPath: "inputs/data.csv" }),
|
|
612
654
|
],
|
|
613
655
|
storage,
|
|
614
656
|
isLocalMode: false,
|
|
615
657
|
})).rejects.toThrow(/collides with/);
|
|
616
658
|
|
|
617
|
-
//
|
|
659
|
+
// The contradiction is detected before any downloads
|
|
618
660
|
expect(storage.download).not.toHaveBeenCalled();
|
|
619
661
|
});
|
|
620
662
|
|
|
663
|
+
it("default-derived names dodge an explicit mountPath already inside the inputs prefix", async () => {
|
|
664
|
+
const storage = makeMockStorage();
|
|
665
|
+
await storage.upload("attachments/a/pinned", Buffer.from("pinned"));
|
|
666
|
+
await storage.upload("attachments/b/report.pdf", Buffer.from("derived"));
|
|
667
|
+
|
|
668
|
+
const backend = mockWorkspaceBackend();
|
|
669
|
+
|
|
670
|
+
const result = await injectAttachments({
|
|
671
|
+
backend,
|
|
672
|
+
attachments: [
|
|
673
|
+
// Listed AFTER the default-derived attachment: explicit paths claim
|
|
674
|
+
// first regardless of order, so the default still dodges.
|
|
675
|
+
makeAttachment({ filename: "report.pdf", storageKey: "attachments/b/report.pdf" }),
|
|
676
|
+
makeAttachment({
|
|
677
|
+
filename: "report.pdf",
|
|
678
|
+
storageKey: "attachments/a/pinned",
|
|
679
|
+
mountPath: ".stigmer/inputs/report.pdf",
|
|
680
|
+
}),
|
|
681
|
+
],
|
|
682
|
+
storage,
|
|
683
|
+
isLocalMode: false,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
const byPath = new Map(result.map((f) => [f.path, f]));
|
|
687
|
+
expect(byPath.get(".stigmer/inputs/report.pdf")?.renamedFrom).toBeUndefined();
|
|
688
|
+
expect(byPath.get(".stigmer/inputs/report-2.pdf")).toMatchObject({
|
|
689
|
+
filename: "report-2.pdf",
|
|
690
|
+
renamedFrom: "report.pdf",
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
621
694
|
it("derives filename from storageKey when filename is empty", async () => {
|
|
622
695
|
const content = Buffer.from("fallback");
|
|
623
696
|
const localFile = join(tempDir, "fallback.txt");
|
|
@@ -662,7 +735,7 @@ describe("injectAttachments", () => {
|
|
|
662
735
|
expect(result[0].path).toBe("workspace/input.csv");
|
|
663
736
|
});
|
|
664
737
|
|
|
665
|
-
it("error
|
|
738
|
+
it("explicit-collision error names both attachments and stays actionable", async () => {
|
|
666
739
|
const backend = mockWorkspaceBackend();
|
|
667
740
|
const storage = makeMockStorage();
|
|
668
741
|
|
|
@@ -670,8 +743,8 @@ describe("injectAttachments", () => {
|
|
|
670
743
|
await injectAttachments({
|
|
671
744
|
backend,
|
|
672
745
|
attachments: [
|
|
673
|
-
makeAttachment({ filename: "first.csv" }),
|
|
674
|
-
makeAttachment({ filename: "
|
|
746
|
+
makeAttachment({ filename: "first.csv", mountPath: "inputs/shared.csv" }),
|
|
747
|
+
makeAttachment({ filename: "second.csv", storageKey: "key2", mountPath: "inputs/shared.csv" }),
|
|
675
748
|
],
|
|
676
749
|
storage,
|
|
677
750
|
isLocalMode: false,
|
|
@@ -680,7 +753,7 @@ describe("injectAttachments", () => {
|
|
|
680
753
|
} catch (err) {
|
|
681
754
|
expect(err).toBeInstanceOf(AttachmentInjectionError);
|
|
682
755
|
const injErr = err as AttachmentInjectionError;
|
|
683
|
-
expect(injErr.attachmentFilename).toBe("
|
|
756
|
+
expect(injErr.attachmentFilename).toBe("second.csv");
|
|
684
757
|
expect(injErr.message).toContain("first.csv");
|
|
685
758
|
expect(injErr.message).toContain("mountPath");
|
|
686
759
|
}
|
|
@@ -860,6 +933,35 @@ describe("injectAttachments — vision selection", () => {
|
|
|
860
933
|
expect(injected.visionDegraded).toBe("type_mismatch");
|
|
861
934
|
});
|
|
862
935
|
|
|
936
|
+
it("labels a duplicate-renamed image's vision payload with the on-disk name", async () => {
|
|
937
|
+
// Coherence pin: the prompt lists the renamed path and the vision
|
|
938
|
+
// disclosure lists image filenames — if the offer used the original
|
|
939
|
+
// attachment.filename, the agent would see two images with one
|
|
940
|
+
// indistinguishable name.
|
|
941
|
+
const backend = mockWorkspaceBackend();
|
|
942
|
+
const storage = makeMockStorage();
|
|
943
|
+
await storage.upload("attachments/a/photo.png", PNG_BYTES);
|
|
944
|
+
await storage.upload("attachments/b/photo.png", PNG_BYTES);
|
|
945
|
+
|
|
946
|
+
const injected = await injectAttachments({
|
|
947
|
+
backend,
|
|
948
|
+
attachments: [
|
|
949
|
+
makeAttachment({ filename: "photo.png", storageKey: "attachments/a/photo.png", contentType: "image/png" }),
|
|
950
|
+
makeAttachment({ filename: "photo.png", storageKey: "attachments/b/photo.png", contentType: "image/png" }),
|
|
951
|
+
],
|
|
952
|
+
storage,
|
|
953
|
+
isLocalMode: false,
|
|
954
|
+
visionBudget: new VisionBudget(DEEP_AGENT_VISION_PROFILE),
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
expect(injected[0].vision?.filename).toBe("photo.png");
|
|
958
|
+
expect(injected[1]).toMatchObject({
|
|
959
|
+
filename: "photo-2.png",
|
|
960
|
+
renamedFrom: "photo.png",
|
|
961
|
+
});
|
|
962
|
+
expect(injected[1].vision?.filename).toBe("photo-2.png");
|
|
963
|
+
});
|
|
964
|
+
|
|
863
965
|
it("never offers an extract archive to the budget — extracted files carry no vision fields", async () => {
|
|
864
966
|
// A PNG inside a ZIP has no attachment-level bytes; the archive rides the
|
|
865
967
|
// normal extraction story with no vision involvement or disclosure.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datastore degradation disclosure, activity half (issue #325).
|
|
3
|
+
*
|
|
4
|
+
* The activity appends the operator-facing MESSAGE_SYSTEM notice to
|
|
5
|
+
* `initialStatus.messages` BEFORE streaming (index.ts) — deliberately not
|
|
6
|
+
* through a status builder, because the v2 and v3 stream paths each
|
|
7
|
+
* construct their own builder around that same proto. These tests pin the
|
|
8
|
+
* property that push site depends on: BOTH builders preserve pre-seeded
|
|
9
|
+
* messages (ExecutionState only appends; index rebuilds touch runtime
|
|
10
|
+
* tool-call state, and a MESSAGE_SYSTEM row carries no tool calls), so
|
|
11
|
+
* the notice survives whichever stream version runs and rides every
|
|
12
|
+
* persist. The prompt half (section selection) is pinned in
|
|
13
|
+
* shared/__tests__/datastore-attachment.test.ts.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
17
|
+
import { create } from "@bufbuild/protobuf";
|
|
18
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
19
|
+
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
20
|
+
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
21
|
+
import { MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
22
|
+
import {
|
|
23
|
+
EXPECTED_RECORD_TOOLS,
|
|
24
|
+
formatDatastoreDegradationNotice,
|
|
25
|
+
} from "../../../shared/datastore-attachment.js";
|
|
26
|
+
import { StatusBuilder } from "../status-builder.js";
|
|
27
|
+
import { V3StatusBuilder } from "../v3-status-builder.js";
|
|
28
|
+
import { normalize } from "../v3-protocol-normalizer.js";
|
|
29
|
+
import {
|
|
30
|
+
resetSeq,
|
|
31
|
+
makeMessageStart,
|
|
32
|
+
makeMessageFinish,
|
|
33
|
+
makeTextDelta,
|
|
34
|
+
} from "../__test-utils__/v3-event-fixtures.js";
|
|
35
|
+
|
|
36
|
+
beforeEach(() => resetSeq());
|
|
37
|
+
|
|
38
|
+
/** initialStatus as index.ts shapes it when setup reports missing tools. */
|
|
39
|
+
function statusWithDegradationNotice(): AgentExecutionStatus {
|
|
40
|
+
const status = create(AgentExecutionStatusSchema, {});
|
|
41
|
+
status.messages.push(create(AgentMessageSchema, {
|
|
42
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
43
|
+
content: formatDatastoreDegradationNotice(1, [...EXPECTED_RECORD_TOOLS]),
|
|
44
|
+
timestamp: "2026-08-11T00:00:00.000Z",
|
|
45
|
+
}));
|
|
46
|
+
return status;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("datastore degradation notice survives streaming (issue #325)", () => {
|
|
50
|
+
it("v2 StatusBuilder preserves the pre-seeded notice through a streamed turn", () => {
|
|
51
|
+
const initialStatus = statusWithDegradationNotice();
|
|
52
|
+
const sb = new StatusBuilder("exec-1", initialStatus);
|
|
53
|
+
|
|
54
|
+
sb.processEvent({
|
|
55
|
+
event: "on_chat_model_stream",
|
|
56
|
+
run_id: "run-1",
|
|
57
|
+
data: { chunk: { content: "I cannot reach the datastore right now." } },
|
|
58
|
+
});
|
|
59
|
+
sb.processEvent({ event: "on_chat_model_end", run_id: "run-1", data: {} });
|
|
60
|
+
|
|
61
|
+
const messages = sb.currentStatus.messages;
|
|
62
|
+
expect(messages.length).toBeGreaterThanOrEqual(2);
|
|
63
|
+
expect(messages[0].type).toBe(MessageType.MESSAGE_SYSTEM);
|
|
64
|
+
expect(messages[0].content).toContain("0/5 record tools connected");
|
|
65
|
+
expect(messages[0].content).toContain("declared 1 datastore(s)");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("v3 V3StatusBuilder preserves the pre-seeded notice through a streamed turn", () => {
|
|
69
|
+
const initialStatus = statusWithDegradationNotice();
|
|
70
|
+
const sb = new V3StatusBuilder("exec-1", initialStatus);
|
|
71
|
+
|
|
72
|
+
const protocolEvents = [
|
|
73
|
+
makeMessageStart("run-1"),
|
|
74
|
+
makeTextDelta("run-1", "I cannot reach the datastore right now."),
|
|
75
|
+
makeMessageFinish("run-1"),
|
|
76
|
+
];
|
|
77
|
+
for (const raw of protocolEvents) {
|
|
78
|
+
for (const e of normalize(raw)) {
|
|
79
|
+
sb.processEvent(e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const messages = sb.currentStatus.messages;
|
|
84
|
+
expect(messages.length).toBeGreaterThanOrEqual(2);
|
|
85
|
+
expect(messages[0].type).toBe(MessageType.MESSAGE_SYSTEM);
|
|
86
|
+
expect(messages[0].content).toContain("0/5 record tools connected");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("both builders share the initialStatus proto, so the append needs no builder", () => {
|
|
90
|
+
// The push-site contract itself: mutating initialStatus.messages is
|
|
91
|
+
// visible through whichever builder wraps it.
|
|
92
|
+
const initialStatus = create(AgentExecutionStatusSchema, {});
|
|
93
|
+
const sb = new StatusBuilder("exec-1", initialStatus);
|
|
94
|
+
initialStatus.messages.push(create(AgentMessageSchema, {
|
|
95
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
96
|
+
content: formatDatastoreDegradationNotice(2, ["delete_record"]),
|
|
97
|
+
timestamp: "2026-08-11T00:00:00.000Z",
|
|
98
|
+
}));
|
|
99
|
+
expect(sb.currentStatus.messages).toHaveLength(1);
|
|
100
|
+
expect(sb.currentStatus.messages[0].content).toContain(
|
|
101
|
+
"4/5 record tools connected",
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -184,6 +184,7 @@ function fakeHttpRejectSetup(): SetupResult {
|
|
|
184
184
|
session: { spec: { workspaceEntries: [] } },
|
|
185
185
|
workspaceBackend: { rootDir: "/tmp/stigmer-test-ws-reject-http" },
|
|
186
186
|
mcpConnection: null,
|
|
187
|
+
datastoreToolsMissing: [],
|
|
187
188
|
mergedEnvVars: {},
|
|
188
189
|
secretKeys: new Set<string>(),
|
|
189
190
|
modelName: "claude-sonnet",
|
|
@@ -219,6 +220,7 @@ function fakeMemoryRejectSetup(): SetupResult {
|
|
|
219
220
|
session: { spec: { workspaceEntries: [] } },
|
|
220
221
|
workspaceBackend: { rootDir: "/tmp/stigmer-test-ws-reject-mem" },
|
|
221
222
|
mcpConnection: null,
|
|
223
|
+
datastoreToolsMissing: [],
|
|
222
224
|
mergedEnvVars: {},
|
|
223
225
|
secretKeys: new Set<string>(),
|
|
224
226
|
modelName: "claude-sonnet",
|
|
@@ -264,6 +264,7 @@ function fakeDurableResumeSetup(): SetupResult {
|
|
|
264
264
|
// path shared across files would serialize parallel test workers for real.
|
|
265
265
|
workspaceBackend: { rootDir: "/tmp/stigmer-test-ws-hitl-approve-all" },
|
|
266
266
|
mcpConnection: null,
|
|
267
|
+
datastoreToolsMissing: [],
|
|
267
268
|
mergedEnvVars: {},
|
|
268
269
|
secretKeys: new Set<string>(),
|
|
269
270
|
modelName: "claude-sonnet",
|