@stigmer/runner 3.11.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +6 -4
- 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/attachment-resolver.d.ts +11 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
- 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 +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
- package/dist/activities/execute-cursor/error-classifier.js +34 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +52 -12
- package/dist/activities/execute-cursor/index.js +141 -67
- package/dist/activities/execute-cursor/index.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 +48 -11
- package/dist/activities/execute-cursor/prompt-builder.js +57 -10
- 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-deep-agent/attachment-injector.d.ts +11 -0
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +0 -15
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
- package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
- package/dist/activities/execute-deep-agent/setup.js +22 -49
- 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.js +7 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -5
- 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/bootstrap.js +5 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +83 -26
- package/dist/client/stigmer-client.js +88 -24
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.js.map +1 -1
- package/dist/encryption/config.d.ts +41 -9
- package/dist/encryption/config.js +63 -23
- package/dist/encryption/config.js.map +1 -1
- 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 +2 -1
- package/dist/payload-codecs.js +7 -3
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +17 -3
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +7 -3
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +34 -1
- package/dist/shared/artifact-storage.js +53 -10
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-download-urls.d.ts +62 -0
- package/dist/shared/attachment-download-urls.js +79 -0
- package/dist/shared/attachment-download-urls.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +4 -20
- package/dist/shared/checkpointer/http-saver.js +15 -57
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.d.ts +2 -3
- package/dist/shared/conversation-attachment.js +2 -3
- 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/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 +51 -9
- package/dist/shared/http-retry.js +60 -9
- package/dist/shared/http-retry.js.map +1 -1
- package/dist/shared/mcp-manager.d.ts +8 -4
- package/dist/shared/mcp-manager.js +30 -4
- package/dist/shared/mcp-manager.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/mcp-transport-guard.d.ts +2 -2
- package/dist/shared/mcp-transport-guard.js +2 -2
- package/dist/shared/model-client.d.ts +6 -0
- package/dist/shared/model-client.js +14 -3
- package/dist/shared/model-client.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 +7 -0
- package/dist/shared/plan-mode-permissions.js +7 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- 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/shared/synthesized-attachment.d.ts +4 -4
- package/dist/shared/synthesized-attachment.js +4 -4
- 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/workflows/connect-mcp-server.js +2 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +3 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/fake-artifact-storage.ts +10 -1
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/encryption-codec.test.ts +53 -0
- package/src/__tests__/preflight.test.ts +15 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -0
- package/src/activities/discover-mcp-server.ts +16 -2
- package/src/activities/emit-event.ts +65 -21
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
- package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
- package/src/activities/execute-cursor/error-classifier.ts +40 -3
- package/src/activities/execute-cursor/index.ts +175 -74
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +89 -22
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
- 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 +71 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
- 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 +22 -17
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
- package/src/activities/execute-deep-agent/index.ts +0 -19
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
- package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
- package/src/activities/execute-deep-agent/setup.ts +22 -73
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
- package/src/activities/hydrate-workflow-execution.ts +8 -5
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/bootstrap.ts +5 -0
- package/src/client/__tests__/stigmer-client.test.ts +104 -5
- package/src/client/stigmer-client.ts +128 -29
- package/src/config.ts +6 -5
- package/src/encryption/config.ts +87 -23
- 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 +8 -2
- package/src/runner-manager.ts +21 -3
- package/src/runner.ts +7 -3
- package/src/shared/__tests__/artifact-storage.test.ts +154 -2
- package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
- package/src/shared/__tests__/channel-attachment.test.ts +2 -2
- package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
- package/src/shared/__tests__/http-retry.test.ts +5 -4
- package/src/shared/__tests__/mcp-manager.test.ts +53 -13
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +46 -0
- 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 +19 -41
- package/src/shared/artifact-storage.ts +86 -10
- package/src/shared/attachment-download-urls.ts +98 -0
- package/src/shared/channel-attachment.ts +1 -1
- package/src/shared/checkpointer/http-saver.ts +15 -60
- package/src/shared/conversation-attachment.ts +2 -3
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +98 -9
- package/src/shared/mcp-manager.ts +35 -4
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/mcp-transport-guard.ts +2 -2
- package/src/shared/model-client.ts +21 -3
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +7 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/shared/synthesized-attachment.ts +4 -4
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
- package/src/workflows/connect-mcp-server.ts +2 -0
- 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/dist/shared/datastore-attachment.d.ts +0 -99
- package/dist/shared/datastore-attachment.js +0 -184
- package/dist/shared/datastore-attachment.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
- package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
- package/src/shared/datastore-attachment.ts +0 -209
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the turn-recovery digest (issue #366).
|
|
3
|
+
*
|
|
4
|
+
* The digest is the replacement agent's only account of the work its lost
|
|
5
|
+
* predecessor did, so these pins cover the three doctrine properties it
|
|
6
|
+
* inherits from the DD-013 bridge composer — bounded lines, drop-oldest
|
|
7
|
+
* budget enforcement with disclosure, never-throw — plus the rendering
|
|
8
|
+
* contract per message/tool-call kind.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from "vitest";
|
|
12
|
+
import { create } from "@bufbuild/protobuf";
|
|
13
|
+
import {
|
|
14
|
+
AgentMessageSchema,
|
|
15
|
+
ToolCallSchema,
|
|
16
|
+
type AgentMessage,
|
|
17
|
+
type ToolCall,
|
|
18
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
19
|
+
import { MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
20
|
+
|
|
21
|
+
import { composeTurnRecoveryDigest, formatTurnRecoveryText } from "../turn-recovery.js";
|
|
22
|
+
|
|
23
|
+
function aiMessage(content: string, toolCalls: ToolCall[] = []): AgentMessage {
|
|
24
|
+
return create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, content, toolCalls });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ToolCallFields = Partial<
|
|
28
|
+
Pick<ToolCall, "name" | "argsPreview" | "approvalMessage" | "status" | "error">
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
function toolCall(overrides: ToolCallFields): ToolCall {
|
|
32
|
+
return create(ToolCallSchema, {
|
|
33
|
+
id: "tc-1",
|
|
34
|
+
name: "Shell",
|
|
35
|
+
status: ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
36
|
+
...overrides,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("composeTurnRecoveryDigest", () => {
|
|
41
|
+
it("renders assistant text and tool calls oldest-first", () => {
|
|
42
|
+
const digest = composeTurnRecoveryDigest([
|
|
43
|
+
aiMessage("Let me check the config first.", [
|
|
44
|
+
toolCall({ name: "Read", argsPreview: '{"path":"config.yaml"}' }),
|
|
45
|
+
]),
|
|
46
|
+
aiMessage("The port is wrong — fixing it."),
|
|
47
|
+
]);
|
|
48
|
+
expect(digest).toBe(
|
|
49
|
+
[
|
|
50
|
+
"Assistant: Let me check the config first.",
|
|
51
|
+
'Tool: Read({"path":"config.yaml"}) — completed',
|
|
52
|
+
"Assistant: The port is wrong — fixing it.",
|
|
53
|
+
].join("\n"),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("prefers the resolved approval message for a tool line — the same description the user approved against", () => {
|
|
58
|
+
const digest = composeTurnRecoveryDigest([
|
|
59
|
+
aiMessage("", [
|
|
60
|
+
toolCall({
|
|
61
|
+
approvalMessage: "Write file: gated.txt",
|
|
62
|
+
argsPreview: '{"path":"gated.txt","content":"..."}',
|
|
63
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
64
|
+
}),
|
|
65
|
+
]),
|
|
66
|
+
]);
|
|
67
|
+
expect(digest).toBe("Tool: Write file: gated.txt — paused for user approval");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("maps each terminal status honestly and treats in-flight calls as interrupted", () => {
|
|
71
|
+
const digest = composeTurnRecoveryDigest([
|
|
72
|
+
aiMessage("", [
|
|
73
|
+
toolCall({ name: "A", status: ToolCallStatus.TOOL_CALL_COMPLETED }),
|
|
74
|
+
toolCall({ name: "B", status: ToolCallStatus.TOOL_CALL_FAILED, error: "exit 1" }),
|
|
75
|
+
toolCall({ name: "C", status: ToolCallStatus.TOOL_CALL_FAILED }),
|
|
76
|
+
toolCall({ name: "D", status: ToolCallStatus.TOOL_CALL_SKIPPED }),
|
|
77
|
+
toolCall({ name: "E", status: ToolCallStatus.TOOL_CALL_RUNNING }),
|
|
78
|
+
toolCall({ name: "F", status: ToolCallStatus.TOOL_CALL_PENDING }),
|
|
79
|
+
]),
|
|
80
|
+
]);
|
|
81
|
+
expect(digest).toBe(
|
|
82
|
+
[
|
|
83
|
+
"Tool: A — completed",
|
|
84
|
+
"Tool: B — failed: exit 1",
|
|
85
|
+
"Tool: C — failed",
|
|
86
|
+
"Tool: D — skipped",
|
|
87
|
+
"Tool: E — interrupted before it finished",
|
|
88
|
+
"Tool: F — interrupted before it finished",
|
|
89
|
+
].join("\n"),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("keeps system notices but skips human messages (already in <user_request>), thinking, and blanks", () => {
|
|
94
|
+
const digest = composeTurnRecoveryDigest([
|
|
95
|
+
create(AgentMessageSchema, { type: MessageType.MESSAGE_HUMAN, content: "Fix the build" }),
|
|
96
|
+
create(AgentMessageSchema, { type: MessageType.MESSAGE_SYSTEM, content: "Budget warning: 80% used" }),
|
|
97
|
+
create(AgentMessageSchema, { type: MessageType.MESSAGE_THINKING, content: "hmm, maybe the lockfile" }),
|
|
98
|
+
aiMessage(" "),
|
|
99
|
+
]);
|
|
100
|
+
expect(digest).toBe("System: Budget warning: 80% used");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("returns undefined when nothing renders", () => {
|
|
104
|
+
expect(composeTurnRecoveryDigest([])).toBeUndefined();
|
|
105
|
+
expect(
|
|
106
|
+
composeTurnRecoveryDigest([
|
|
107
|
+
create(AgentMessageSchema, { type: MessageType.MESSAGE_HUMAN, content: "only the request" }),
|
|
108
|
+
]),
|
|
109
|
+
).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("truncates a long assistant line at the per-line budget with an ellipsis", () => {
|
|
113
|
+
const digest = composeTurnRecoveryDigest([aiMessage("x".repeat(1000))]);
|
|
114
|
+
expect(digest).toBe(`Assistant: ${"x".repeat(400)}\u2026`);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("drops the OLDEST lines when over the whole-digest budget and discloses the omission", () => {
|
|
118
|
+
// 30 lines of ~311 chars each (~9.6k total) against the 4000-char budget:
|
|
119
|
+
// the newest lines must survive, the oldest go, and the notice leads.
|
|
120
|
+
const messages = Array.from({ length: 30 }, (_, i) =>
|
|
121
|
+
aiMessage(`step ${String(i).padStart(2, "0")} ${"y".repeat(300)}`),
|
|
122
|
+
);
|
|
123
|
+
const digest = composeTurnRecoveryDigest(messages);
|
|
124
|
+
expect(digest).toBeDefined();
|
|
125
|
+
expect(digest!.length).toBeLessThanOrEqual(4000);
|
|
126
|
+
const lines = digest!.split("\n");
|
|
127
|
+
expect(lines[0]).toBe("[\u2026 earlier activity in this turn omitted for length]");
|
|
128
|
+
// Recency wins: the last line is the newest entry, the first entries are gone.
|
|
129
|
+
expect(lines[lines.length - 1]).toContain("step 29");
|
|
130
|
+
expect(digest).not.toContain("step 00");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("never throws — a malformed message degrades to no digest, not a failed recovery", () => {
|
|
134
|
+
// Force the internal iteration to blow up: content getter that throws.
|
|
135
|
+
const poison = new Proxy(aiMessage("ok"), {
|
|
136
|
+
get(target, prop, receiver) {
|
|
137
|
+
if (prop === "content") throw new Error("corrupt row");
|
|
138
|
+
return Reflect.get(target, prop, receiver);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
expect(composeTurnRecoveryDigest([poison as AgentMessage])).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("formatTurnRecoveryText", () => {
|
|
146
|
+
it("frames a digest with the work-already-done preamble", () => {
|
|
147
|
+
const text = formatTurnRecoveryText("Assistant: did things");
|
|
148
|
+
expect(text).toContain("session holding that conversation was lost");
|
|
149
|
+
expect(text).toContain("do not start the task over");
|
|
150
|
+
expect(text.endsWith("Assistant: did things")).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("still discloses the state loss when there is no transcript — without it the appended decisions would read as reactions to proposals this agent never made", () => {
|
|
154
|
+
for (const empty of [undefined, "", " "]) {
|
|
155
|
+
const text = formatTurnRecoveryText(empty);
|
|
156
|
+
expect(text).toContain("session holding that conversation was lost");
|
|
157
|
+
expect(text).toContain("no transcript of your progress is available");
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -40,6 +40,7 @@ import { mkdir, copyFile, readFile, stat, writeFile } from "node:fs/promises";
|
|
|
40
40
|
import { join, basename } from "node:path";
|
|
41
41
|
import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
42
42
|
import type { ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
43
|
+
import { mintAttachmentDownloadUrl } from "../../shared/attachment-download-urls.js";
|
|
43
44
|
import { allocateUniqueName } from "../../shared/attachment-naming.js";
|
|
44
45
|
import {
|
|
45
46
|
isVisionCandidate,
|
|
@@ -73,6 +74,17 @@ export interface ResolvedAttachment {
|
|
|
73
74
|
* Attachments that were never image-shaped carry neither field.
|
|
74
75
|
*/
|
|
75
76
|
visionDegraded?: VisionDegradedReason;
|
|
77
|
+
/**
|
|
78
|
+
* A download URL for the attachment's stored object, listed in the prompt's
|
|
79
|
+
* `<input_files>` section so the agent can hand the file to a tool whose
|
|
80
|
+
* backend cannot read the sandbox filesystem (issue #532). Minted whenever
|
|
81
|
+
* the attachment has a `storageKey` and a usable storage — including on the
|
|
82
|
+
* localPath fast path when an uploaded copy also exists. Absent when there
|
|
83
|
+
* is nothing to mint or the mint failed (non-fatal — the policy, the
|
|
84
|
+
* degrade log, and the prompt wording all live in
|
|
85
|
+
* shared/attachment-download-urls.ts).
|
|
86
|
+
*/
|
|
87
|
+
downloadUrl?: string;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
export interface AttachmentResolverOptions {
|
|
@@ -165,11 +177,18 @@ async function resolveAttachment(
|
|
|
165
177
|
`${err instanceof Error ? err.message : String(err)}`,
|
|
166
178
|
);
|
|
167
179
|
}
|
|
180
|
+
// The fast path skips storage for the BYTES, but an uploaded copy (an
|
|
181
|
+
// attachment carrying both localPath and storageKey) still supports the
|
|
182
|
+
// URL hand-off — the mint rule is branch-independent.
|
|
183
|
+
const downloadUrl = await mintAttachmentDownloadUrl(
|
|
184
|
+
options.storage, attachment.storageKey, filename,
|
|
185
|
+
);
|
|
168
186
|
return {
|
|
169
187
|
filename,
|
|
170
188
|
relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, filename),
|
|
171
189
|
...(renamedFrom !== undefined ? { renamedFrom } : {}),
|
|
172
190
|
...visionOutcomeFields(vision),
|
|
191
|
+
...(downloadUrl !== undefined ? { downloadUrl } : {}),
|
|
173
192
|
};
|
|
174
193
|
}
|
|
175
194
|
|
|
@@ -209,11 +228,16 @@ async function resolveAttachment(
|
|
|
209
228
|
// no pre-filter needed on this branch).
|
|
210
229
|
const vision = options.visionBudget?.offer(filename, attachment.contentType, content);
|
|
211
230
|
|
|
231
|
+
const downloadUrl = await mintAttachmentDownloadUrl(
|
|
232
|
+
options.storage, attachment.storageKey, filename,
|
|
233
|
+
);
|
|
234
|
+
|
|
212
235
|
return {
|
|
213
236
|
filename,
|
|
214
237
|
relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, filename),
|
|
215
238
|
...(renamedFrom !== undefined ? { renamedFrom } : {}),
|
|
216
239
|
...visionOutcomeFields(vision),
|
|
240
|
+
...(downloadUrl !== undefined ? { downloadUrl } : {}),
|
|
217
241
|
};
|
|
218
242
|
}
|
|
219
243
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { resolve } from "node:path";
|
|
15
15
|
import type { StigmerClient } from "../../client/stigmer-client.js";
|
|
16
16
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
17
|
-
import type { AgentSpec,
|
|
17
|
+
import type { AgentSpec, McpServerUsage, SubAgent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
18
18
|
import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
19
19
|
import type { SessionSpec } from "@stigmer/protos/ai/stigmer/agentic/session/v1/spec_pb";
|
|
20
20
|
import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
|
|
@@ -46,14 +46,6 @@ export interface ResolvedBlueprint {
|
|
|
46
46
|
subAgents: SubAgent[];
|
|
47
47
|
mergedMcpServerUsages: McpServerUsage[];
|
|
48
48
|
mergedSkillRefs: ApiResourceReference[];
|
|
49
|
-
/**
|
|
50
|
-
* The agent's datastore usages (AgentSpec only — sessions carry no
|
|
51
|
-
* datastore overlay by design: the usage edge is authorization-bearing
|
|
52
|
-
* for the server's reach chain, DD-006, so it binds per-blueprint).
|
|
53
|
-
* Non-empty triggers the synthesized records attachment
|
|
54
|
-
* (shared/datastore-attachment.ts).
|
|
55
|
-
*/
|
|
56
|
-
datastoreUsages: DatastoreUsage[];
|
|
57
49
|
workspaceDirs: string[];
|
|
58
50
|
cloudRepos: CloudRepo[];
|
|
59
51
|
}
|
|
@@ -98,7 +90,6 @@ export async function resolveBlueprint(
|
|
|
98
90
|
subAgents: agentSpec.subAgents,
|
|
99
91
|
mergedMcpServerUsages,
|
|
100
92
|
mergedSkillRefs,
|
|
101
|
-
datastoreUsages: agentSpec.datastoreUsages,
|
|
102
93
|
workspaceDirs,
|
|
103
94
|
cloudRepos,
|
|
104
95
|
};
|
|
@@ -131,6 +131,29 @@ function extractFromCandidate(v: unknown): RunErrorSources | undefined {
|
|
|
131
131
|
return text.length > 0 ? { sdkError: undefined, sdkResultFields: text } : undefined;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Stable lead sentence of the detail-free fallback error (all five detail
|
|
136
|
+
* channels empty — empirically the shape of Cursor-side capacity rejections,
|
|
137
|
+
* oss#492). This is what end users read in embedded surfaces, so it must be
|
|
138
|
+
* actionable product copy, not a diagnostic; the diagnostic context follows
|
|
139
|
+
* in a parenthetical. Mirrors the COST_LIMIT_ERROR_PREFIX convention: a
|
|
140
|
+
* consumer that needs to recognize this failure can match on the prefix.
|
|
141
|
+
* Do not reword without checking consumers, and NEVER include the phrase
|
|
142
|
+
* "retry or resume" — sdk-react's isInterruptedError (MessageThread.tsx)
|
|
143
|
+
* reframes any error containing it as a neutral resumable notice instead of
|
|
144
|
+
* a failure alert.
|
|
145
|
+
*/
|
|
146
|
+
export const DETAIL_FREE_FALLBACK_USER_PREFIX =
|
|
147
|
+
"The model may be temporarily overloaded — please retry, or switch to a different model.";
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Stable lead sentence of the transport-timeout fallback error (0 messages,
|
|
151
|
+
* ~30s duration — the SDK's default timeout with no stream established).
|
|
152
|
+
* Same user-facing rules as DETAIL_FREE_FALLBACK_USER_PREFIX above.
|
|
153
|
+
*/
|
|
154
|
+
export const TRANSPORT_TIMEOUT_USER_PREFIX =
|
|
155
|
+
"The connection to the model could not be established — please retry.";
|
|
156
|
+
|
|
134
157
|
const AUTH_PATTERNS = [
|
|
135
158
|
"unauthenticated", "unauthorized", "401", "forbidden",
|
|
136
159
|
"permission_denied", "invalid api key", "not logged in",
|
|
@@ -317,7 +340,10 @@ function classifyFromSources(opts: SynthesizeErrorOpts): ClassifiedError {
|
|
|
317
340
|
const { model, mode, agentId } = opts.fallbackContext;
|
|
318
341
|
return {
|
|
319
342
|
category: "network",
|
|
320
|
-
message:
|
|
343
|
+
message:
|
|
344
|
+
`${TRANSPORT_TIMEOUT_USER_PREFIX} ` +
|
|
345
|
+
`(Transport timeout: ${opts.durationMs}ms, 0 messages received. ` +
|
|
346
|
+
`Model=${model}, mode=${mode}, agentId=${agentId})`,
|
|
321
347
|
retryable: true,
|
|
322
348
|
source: "fallback",
|
|
323
349
|
};
|
|
@@ -332,11 +358,22 @@ function classifyFromSources(opts: SynthesizeErrorOpts): ClassifiedError {
|
|
|
332
358
|
};
|
|
333
359
|
}
|
|
334
360
|
|
|
361
|
+
// The honest last resort: every detail channel was empty. Observed in prod
|
|
362
|
+
// only during provider capacity incidents (oss#492: Composer 2.5 degradation
|
|
363
|
+
// — the SDK rejection that carries ERROR_RESOURCE_EXHAUSTED in Cursor's IDE
|
|
364
|
+
// arrives here detail-free), so the copy leads with the capacity hypothesis
|
|
365
|
+
// hedged ("may be"), and retryable is true: the observed cause is transient
|
|
366
|
+
// by nature, and nothing gates a recovery loop on unknown+retryable. The
|
|
367
|
+
// parenthetical keeps the exact Model=/mode=/agentId= tokens for log-grep
|
|
368
|
+
// continuity and the env integration test's matcher.
|
|
335
369
|
const { model, mode, agentId } = opts.fallbackContext;
|
|
336
370
|
return {
|
|
337
371
|
category: "unknown",
|
|
338
|
-
message:
|
|
339
|
-
|
|
372
|
+
message:
|
|
373
|
+
`${DETAIL_FREE_FALLBACK_USER_PREFIX} ` +
|
|
374
|
+
`(No error detail from the Cursor SDK. ` +
|
|
375
|
+
`Model=${model}, mode=${mode}, agentId=${agentId})`,
|
|
376
|
+
retryable: true,
|
|
340
377
|
source: "fallback",
|
|
341
378
|
};
|
|
342
379
|
}
|