@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
|
@@ -56,6 +56,7 @@ describe("buildChatModel", () => {
|
|
|
56
56
|
delete process.env.STIGMER_ANTHROPIC_BACKEND;
|
|
57
57
|
delete process.env.STIGMER_OPENAI_BACKEND;
|
|
58
58
|
delete process.env.CLOUD_ML_REGION;
|
|
59
|
+
delete process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS;
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
afterEach(() => {
|
|
@@ -215,6 +216,51 @@ describe("buildChatModel", () => {
|
|
|
215
216
|
},
|
|
216
217
|
});
|
|
217
218
|
});
|
|
219
|
+
|
|
220
|
+
// The env default resolves INSIDE buildChatModel (#468): before this,
|
|
221
|
+
// each caller parsed STIGMER_LLM_REQUEST_TIMEOUT_MS itself, and every
|
|
222
|
+
// caller except the deep-agent main path — including its own sub-agent
|
|
223
|
+
// modelFactory — dropped the bound.
|
|
224
|
+
it("defaults to STIGMER_LLM_REQUEST_TIMEOUT_MS when the caller passes no timeout", async () => {
|
|
225
|
+
process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS = "7000";
|
|
226
|
+
mockRegistryResponse([
|
|
227
|
+
{ id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
await buildChatModel({ modelName: "claude-haiku-4.5" });
|
|
231
|
+
|
|
232
|
+
expect(lastAnthropicArgs()).toMatchObject({
|
|
233
|
+
clientOptions: { timeout: 7000 },
|
|
234
|
+
maxRetries: 0,
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("lets an explicit caller timeout win over the env default", async () => {
|
|
239
|
+
process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS = "7000";
|
|
240
|
+
mockRegistryResponse([
|
|
241
|
+
{ id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
await buildChatModel({ modelName: "claude-haiku-4.5", timeoutMs: 5000 });
|
|
245
|
+
|
|
246
|
+
expect(lastAnthropicArgs()).toMatchObject({ clientOptions: { timeout: 5000 } });
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it.each(["0", "-1", "not-a-number", ""])(
|
|
250
|
+
"normalizes a non-positive env value (%j) to no bound",
|
|
251
|
+
async (envValue) => {
|
|
252
|
+
process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS = envValue;
|
|
253
|
+
mockRegistryResponse([
|
|
254
|
+
{ id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
await buildChatModel({ modelName: "claude-haiku-4.5" });
|
|
258
|
+
|
|
259
|
+
const args = lastAnthropicArgs();
|
|
260
|
+
expect(args).not.toHaveProperty("clientOptions");
|
|
261
|
+
expect(args).not.toHaveProperty("maxRetries");
|
|
262
|
+
},
|
|
263
|
+
);
|
|
218
264
|
});
|
|
219
265
|
|
|
220
266
|
// ─── Provider backends (STIGMER_ANTHROPIC_BACKEND) ─────────────────────────
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { computeLlmCostMicros, computeTurnCost, getModelPricing, resolveModelId, ensureLoaded } from "../model-pricing.js";
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
getPricingTable,
|
|
5
|
+
_resetPricingCache,
|
|
6
|
+
DEFAULT_PRICING,
|
|
7
|
+
type ModelPricing,
|
|
8
|
+
} from "../model-pricing-data.js";
|
|
4
9
|
|
|
5
10
|
describe("computeLlmCostMicros", () => {
|
|
6
11
|
it("falls back to DEFAULT_PRICING when registry is not loaded", () => {
|
|
@@ -83,3 +88,58 @@ describe("getModelPricing", () => {
|
|
|
83
88
|
expect(pricing.model).toBe("custom-model");
|
|
84
89
|
});
|
|
85
90
|
});
|
|
91
|
+
|
|
92
|
+
describe("getPricingTable failure caching (#468)", () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
_resetPricingCache();
|
|
95
|
+
vi.restoreAllMocks();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
_resetPricingCache();
|
|
100
|
+
vi.useRealTimers();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const registryResponse = () =>
|
|
104
|
+
new Response(
|
|
105
|
+
JSON.stringify({
|
|
106
|
+
models: [
|
|
107
|
+
{
|
|
108
|
+
id: "claude-sonnet-4-6",
|
|
109
|
+
displayName: "Claude Sonnet",
|
|
110
|
+
costTier: "standard",
|
|
111
|
+
pricing: {
|
|
112
|
+
inputPricePerMillion: 3.0,
|
|
113
|
+
outputPricePerMillion: 15.0,
|
|
114
|
+
cacheWritePricePerMillion: 3.75,
|
|
115
|
+
cacheReadPricePerMillion: 0.3,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
}),
|
|
120
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
it("retries after the short failure TTL instead of pinning DEFAULT_PRICING for an hour", async () => {
|
|
124
|
+
// The model-registry.ts failure-cache policy, applied here: wrong default
|
|
125
|
+
// rates for cost tracking must not persist a full success TTL.
|
|
126
|
+
vi.useFakeTimers();
|
|
127
|
+
const fetchSpy = vi
|
|
128
|
+
.spyOn(globalThis, "fetch")
|
|
129
|
+
.mockRejectedValueOnce(new Error("network error"))
|
|
130
|
+
.mockResolvedValueOnce(registryResponse());
|
|
131
|
+
|
|
132
|
+
// First call fails and degrades to the default table.
|
|
133
|
+
expect((await getPricingTable())[0]).toBe(DEFAULT_PRICING);
|
|
134
|
+
|
|
135
|
+
// Within the failure TTL the fallback stays cached (no refetch).
|
|
136
|
+
vi.advanceTimersByTime(30_000);
|
|
137
|
+
expect((await getPricingTable())[0]).toBe(DEFAULT_PRICING);
|
|
138
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
139
|
+
|
|
140
|
+
// Past the failure TTL the registry is refetched and real rates recover.
|
|
141
|
+
vi.advanceTimersByTime(31_000);
|
|
142
|
+
expect((await getPricingTable())[0]?.model).toBe("claude-sonnet-4-6");
|
|
143
|
+
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -223,6 +223,28 @@ describe("resolveToApiModelId", () => {
|
|
|
223
223
|
expect(result).toBe("claude-haiku-4.5");
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
+
it("recovers from a transient network failure without engaging the failure cache (#468)", async () => {
|
|
227
|
+
// undici's retryable shape (TypeError) — the shared bounded-backoff loop
|
|
228
|
+
// absorbs it, so resolution succeeds on the SAME call and the 60 s
|
|
229
|
+
// degraded window never opens. Pre-#468 code failed the fetch outright.
|
|
230
|
+
const fetchSpy = vi
|
|
231
|
+
.spyOn(globalThis, "fetch")
|
|
232
|
+
.mockRejectedValueOnce(new TypeError("fetch failed"))
|
|
233
|
+
.mockResolvedValueOnce(
|
|
234
|
+
new Response(
|
|
235
|
+
JSON.stringify({
|
|
236
|
+
models: [
|
|
237
|
+
{ id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic", costTier: "economy", harness: "native" },
|
|
238
|
+
],
|
|
239
|
+
}),
|
|
240
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(await resolveToApiModelId("claude-haiku-4.5")).toBe("claude-haiku-4-5-20251001");
|
|
245
|
+
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
246
|
+
});
|
|
247
|
+
|
|
226
248
|
it("retries after the short failure TTL instead of caching the failure for an hour", async () => {
|
|
227
249
|
vi.useFakeTimers();
|
|
228
250
|
const fetchSpy = vi
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Composition of
|
|
3
|
-
* injectSynthesizedAttachment — the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Composition of the synthesized attachments through
|
|
3
|
+
* injectSynthesizedAttachment — chained the way both harness call sites
|
|
4
|
+
* do (channels, then conversation, each after resolve + backfill).
|
|
5
|
+
* Per-slug independence is the property that makes another attachment
|
|
6
|
+
* safe to add: replacing one reserved slug must never disturb its
|
|
7
|
+
* siblings.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it, vi } from "vitest";
|
|
11
11
|
import type { MessagingChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
|
|
12
|
-
import { create } from "@bufbuild/protobuf";
|
|
13
|
-
import { DatastoreUsageSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
14
|
-
import { ApiResourceReferenceSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
15
12
|
|
|
16
13
|
import {
|
|
17
14
|
CHANNEL_ATTACHMENT_SLUG,
|
|
@@ -21,10 +18,6 @@ import {
|
|
|
21
18
|
CONVERSATION_ATTACHMENT_SLUG,
|
|
22
19
|
synthesizeConversationAttachment,
|
|
23
20
|
} from "../conversation-attachment.js";
|
|
24
|
-
import {
|
|
25
|
-
DATASTORE_ATTACHMENT_SLUG,
|
|
26
|
-
synthesizeDatastoreAttachment,
|
|
27
|
-
} from "../datastore-attachment.js";
|
|
28
21
|
import { injectSynthesizedAttachment } from "../synthesized-attachment.js";
|
|
29
22
|
import type { ResolvedMcpServer } from "../mcp-resolver.js";
|
|
30
23
|
|
|
@@ -44,47 +37,34 @@ const userServer: ResolvedMcpServer = {
|
|
|
44
37
|
discoveredCapabilitiesEmpty: false,
|
|
45
38
|
};
|
|
46
39
|
|
|
47
|
-
function
|
|
48
|
-
const datastore = synthesizeDatastoreAttachment(
|
|
49
|
-
[create(DatastoreUsageSchema, {
|
|
50
|
-
datastoreRef: create(ApiResourceReferenceSchema, { slug: "clinic" }),
|
|
51
|
-
})],
|
|
52
|
-
options,
|
|
53
|
-
)!;
|
|
40
|
+
function bothAttachments(): ResolvedMcpServer[] {
|
|
54
41
|
const channels = synthesizeChannelAttachment(
|
|
55
42
|
[{ channel: { channel: "isc-whatsapp", provider: "whatsapp" } as MessagingChannel, templates: [] }],
|
|
56
43
|
options,
|
|
57
44
|
)!;
|
|
58
45
|
const conversation = synthesizeConversationAttachment("agch_1", options)!;
|
|
59
46
|
|
|
60
|
-
// The harness order at both call sites:
|
|
61
|
-
let servers = injectSynthesizedAttachment([userServer],
|
|
62
|
-
servers = injectSynthesizedAttachment(servers, channels, "channel messaging");
|
|
47
|
+
// The harness order at both call sites: channels, then conversation.
|
|
48
|
+
let servers = injectSynthesizedAttachment([userServer], channels, "channel messaging");
|
|
63
49
|
return injectSynthesizedAttachment(servers, conversation, "conversation participation");
|
|
64
50
|
}
|
|
65
51
|
|
|
66
|
-
describe("
|
|
67
|
-
it("composes
|
|
68
|
-
expect(
|
|
52
|
+
describe("synthesized attachments in one chain", () => {
|
|
53
|
+
it("composes after the user's servers, in injection order", () => {
|
|
54
|
+
expect(bothAttachments().map((s) => s.slug)).toEqual([
|
|
69
55
|
"github",
|
|
70
|
-
DATASTORE_ATTACHMENT_SLUG,
|
|
71
56
|
CHANNEL_ATTACHMENT_SLUG,
|
|
72
57
|
CONVERSATION_ATTACHMENT_SLUG,
|
|
73
58
|
]);
|
|
74
59
|
});
|
|
75
60
|
|
|
76
61
|
it("each rides its own bridge route with the shared credential", () => {
|
|
77
|
-
const bySlug = new Map(
|
|
78
|
-
expect(bySlug.get(DATASTORE_ATTACHMENT_SLUG)?.url).toBe("https://mcp.stigmer.ai/records");
|
|
62
|
+
const bySlug = new Map(bothAttachments().map((s) => [s.slug, s]));
|
|
79
63
|
expect(bySlug.get(CHANNEL_ATTACHMENT_SLUG)?.url).toBe("https://mcp.stigmer.ai/channels");
|
|
80
64
|
expect(bySlug.get(CONVERSATION_ATTACHMENT_SLUG)?.url).toBe(
|
|
81
65
|
"https://mcp.stigmer.ai/conversation",
|
|
82
66
|
);
|
|
83
|
-
for (const slug of [
|
|
84
|
-
DATASTORE_ATTACHMENT_SLUG,
|
|
85
|
-
CHANNEL_ATTACHMENT_SLUG,
|
|
86
|
-
CONVERSATION_ATTACHMENT_SLUG,
|
|
87
|
-
]) {
|
|
67
|
+
for (const slug of [CHANNEL_ATTACHMENT_SLUG, CONVERSATION_ATTACHMENT_SLUG]) {
|
|
88
68
|
expect(bySlug.get(slug)?.headers).toEqual({ Authorization: "Bearer sandbox-token" });
|
|
89
69
|
}
|
|
90
70
|
});
|
|
@@ -96,20 +76,18 @@ describe("three synthesized attachments in one chain", () => {
|
|
|
96
76
|
slug: CONVERSATION_ATTACHMENT_SLUG,
|
|
97
77
|
url: "https://evil.example.com",
|
|
98
78
|
};
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
[create(DatastoreUsageSchema, {
|
|
102
|
-
datastoreRef: create(ApiResourceReferenceSchema, { slug: "clinic" }),
|
|
103
|
-
})],
|
|
79
|
+
const channels = synthesizeChannelAttachment(
|
|
80
|
+
[{ channel: { channel: "isc-whatsapp", provider: "whatsapp" } as MessagingChannel, templates: [] }],
|
|
104
81
|
options,
|
|
105
82
|
)!;
|
|
83
|
+
const conversation = synthesizeConversationAttachment("agch_1", options)!;
|
|
106
84
|
|
|
107
|
-
let servers = injectSynthesizedAttachment([impostor, userServer],
|
|
85
|
+
let servers = injectSynthesizedAttachment([impostor, userServer], channels, "channel messaging");
|
|
108
86
|
servers = injectSynthesizedAttachment(servers, conversation, "conversation participation");
|
|
109
87
|
|
|
110
88
|
expect(servers.map((s) => s.slug)).toEqual([
|
|
111
89
|
"github",
|
|
112
|
-
|
|
90
|
+
CHANNEL_ATTACHMENT_SLUG,
|
|
113
91
|
CONVERSATION_ATTACHMENT_SLUG,
|
|
114
92
|
]);
|
|
115
93
|
expect(servers.find((s) => s.slug === CONVERSATION_ATTACHMENT_SLUG)?.url).toBe(
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
* The runner never holds R2/S3 credentials — in cloud mode it calls the proxy
|
|
14
14
|
* to obtain a presigned upload URL, then PUTs content over plain HTTPS.
|
|
15
15
|
*
|
|
16
|
+
* Every proxy-backend request rides the shared bounded-backoff loop
|
|
17
|
+
* (http-retry.ts) with a per-request abort timeout, so a momentary
|
|
18
|
+
* stigmer-service interruption is retried and a hung connection cannot
|
|
19
|
+
* stall an agent turn unboundedly (stigmer/stigmer#468).
|
|
20
|
+
*
|
|
16
21
|
* DD-6: No direct R2 backend. Local + Proxy only.
|
|
17
22
|
*/
|
|
18
23
|
|
|
@@ -20,10 +25,21 @@ import { mkdir, writeFile, readFile, access, rm } from "node:fs/promises";
|
|
|
20
25
|
import { dirname, join, resolve, sep } from "node:path";
|
|
21
26
|
import { homedir } from "node:os";
|
|
22
27
|
import type { Config } from "../config.js";
|
|
28
|
+
import type { RetryOptions } from "./grpc-retry.js";
|
|
29
|
+
import { fetchWithRetry, type FetchRetryPolicy } from "./http-retry.js";
|
|
23
30
|
|
|
24
31
|
// ── Interface ────────────────────────────────────────────────────────
|
|
25
32
|
|
|
26
33
|
export interface ArtifactStorage {
|
|
34
|
+
/**
|
|
35
|
+
* What kind of URL {@link getDownloadUrl} mints — self-described by the
|
|
36
|
+
* backend so consumers (the attachment hand-off prompt wording,
|
|
37
|
+
* attachment-download-urls.ts) can never disagree with the storage actually
|
|
38
|
+
* in use. "presigned": time-limited, single-object, remotely fetchable.
|
|
39
|
+
* "local-serve": the stigmer-server's unauthenticated loopback serve URL,
|
|
40
|
+
* reachable only from this machine.
|
|
41
|
+
*/
|
|
42
|
+
readonly downloadUrlKind: "presigned" | "local-serve";
|
|
27
43
|
upload(key: string, content: Buffer, contentType?: string): Promise<string>;
|
|
28
44
|
getDownloadUrl(key: string): Promise<string>;
|
|
29
45
|
/**
|
|
@@ -45,6 +61,7 @@ export type ArtifactStorageType = "local" | "proxy";
|
|
|
45
61
|
// ── Local Backend ────────────────────────────────────────────────────
|
|
46
62
|
|
|
47
63
|
export class LocalArtifactStorage implements ArtifactStorage {
|
|
64
|
+
readonly downloadUrlKind = "local-serve" as const;
|
|
48
65
|
private readonly basePath: string;
|
|
49
66
|
private readonly serveUrlBase: string;
|
|
50
67
|
|
|
@@ -117,13 +134,65 @@ export class LocalArtifactStorage implements ArtifactStorage {
|
|
|
117
134
|
|
|
118
135
|
// ── Proxy Backend ────────────────────────────────────────────────────
|
|
119
136
|
|
|
137
|
+
/** Retry policy plus the two per-request bounds; every field is defaulted. */
|
|
138
|
+
export interface ProxyArtifactStorageOptions extends RetryOptions {
|
|
139
|
+
/** Bound for proxy presign calls and the 1-byte exists probe. Default 30 s. */
|
|
140
|
+
readonly requestTimeoutMs?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Bound for the presigned R2 PUT/GET, which move whole artifact payloads
|
|
143
|
+
* (claim-check offloads, tool outputs, file-review captures — MB scale).
|
|
144
|
+
* Deliberately wider than requestTimeoutMs so a legitimate slow transfer
|
|
145
|
+
* is never aborted by a bound sized for JSON round-trips. Default 120 s.
|
|
146
|
+
*/
|
|
147
|
+
readonly transferTimeoutMs?: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Default transient-retry policy (stigmer/stigmer#468): the checkpointer's
|
|
152
|
+
* budget — sized to span a pod-restart reroute, ~7.75 s of delays across
|
|
153
|
+
* 5 retries. Retrying is safe at every site: the presign POSTs are
|
|
154
|
+
* idempotent mints, the PUT re-sends identical bytes to the same key (a
|
|
155
|
+
* keyed overwrite), and the GETs are reads.
|
|
156
|
+
*/
|
|
157
|
+
const DEFAULT_RETRY = {
|
|
158
|
+
baseDelayMs: 250,
|
|
159
|
+
backoffFactor: 2,
|
|
160
|
+
maxRetries: 5,
|
|
161
|
+
requestTimeoutMs: 30_000,
|
|
162
|
+
transferTimeoutMs: 120_000,
|
|
163
|
+
} as const;
|
|
164
|
+
|
|
120
165
|
export class ProxyArtifactStorage implements ArtifactStorage {
|
|
166
|
+
readonly downloadUrlKind = "presigned" as const;
|
|
121
167
|
private readonly baseUrl: string;
|
|
122
168
|
private readonly authTokenSource: ProxyAuthTokenSource;
|
|
123
|
-
|
|
124
|
-
|
|
169
|
+
/** Governs the proxy presign calls and the exists probe. */
|
|
170
|
+
private readonly requestPolicy: FetchRetryPolicy;
|
|
171
|
+
/** Governs the presigned R2 transfers; same budget, wider timeout. */
|
|
172
|
+
private readonly transferPolicy: FetchRetryPolicy;
|
|
173
|
+
|
|
174
|
+
constructor(
|
|
175
|
+
proxyEndpoint: string,
|
|
176
|
+
authToken: ProxyAuthTokenSource,
|
|
177
|
+
options: ProxyArtifactStorageOptions = {},
|
|
178
|
+
) {
|
|
125
179
|
this.baseUrl = `${proxyEndpoint.replace(/\/+$/, "")}/v1/proxy/artifacts`;
|
|
126
180
|
this.authTokenSource = authToken;
|
|
181
|
+
const shared = {
|
|
182
|
+
label: "ProxyArtifactStorage",
|
|
183
|
+
baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,
|
|
184
|
+
backoffFactor: options.backoffFactor ?? DEFAULT_RETRY.backoffFactor,
|
|
185
|
+
maxRetries: options.maxRetries ?? DEFAULT_RETRY.maxRetries,
|
|
186
|
+
delayFn: options.delayFn,
|
|
187
|
+
};
|
|
188
|
+
this.requestPolicy = {
|
|
189
|
+
...shared,
|
|
190
|
+
requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_RETRY.requestTimeoutMs,
|
|
191
|
+
};
|
|
192
|
+
this.transferPolicy = {
|
|
193
|
+
...shared,
|
|
194
|
+
requestTimeoutMs: options.transferTimeoutMs ?? DEFAULT_RETRY.transferTimeoutMs,
|
|
195
|
+
};
|
|
127
196
|
}
|
|
128
197
|
|
|
129
198
|
/**
|
|
@@ -141,14 +210,14 @@ export class ProxyArtifactStorage implements ArtifactStorage {
|
|
|
141
210
|
async upload(key: string, content: Buffer, contentType?: string): Promise<string> {
|
|
142
211
|
const ct = contentType ?? "application/octet-stream";
|
|
143
212
|
|
|
144
|
-
const presignResp = await
|
|
213
|
+
const presignResp = await fetchWithRetry(`${this.baseUrl}/presigned-upload-url`, {
|
|
145
214
|
method: "POST",
|
|
146
215
|
headers: {
|
|
147
216
|
"Authorization": `Bearer ${this.authToken}`,
|
|
148
217
|
"Content-Type": "application/json",
|
|
149
218
|
},
|
|
150
219
|
body: JSON.stringify({ key, content_type: ct }),
|
|
151
|
-
});
|
|
220
|
+
}, this.requestPolicy);
|
|
152
221
|
if (!presignResp.ok) {
|
|
153
222
|
throw new Error(
|
|
154
223
|
`Failed to get presigned upload URL (HTTP ${presignResp.status}): ` +
|
|
@@ -180,11 +249,14 @@ export class ProxyArtifactStorage implements ArtifactStorage {
|
|
|
180
249
|
uploadHeaders["Content-Type"] = ct;
|
|
181
250
|
}
|
|
182
251
|
|
|
183
|
-
|
|
252
|
+
// Retrying the PUT alone (not the presign+PUT pair) is deliberate: the
|
|
253
|
+
// retry budget's few seconds of delays sit far inside presign validity,
|
|
254
|
+
// and re-sending the same Buffer to the same key is a keyed overwrite.
|
|
255
|
+
const putResp = await fetchWithRetry(data.url, {
|
|
184
256
|
method: "PUT",
|
|
185
257
|
headers: uploadHeaders,
|
|
186
258
|
body: content,
|
|
187
|
-
});
|
|
259
|
+
}, this.transferPolicy);
|
|
188
260
|
if (!putResp.ok) {
|
|
189
261
|
throw new Error(
|
|
190
262
|
`Presigned upload failed (HTTP ${putResp.status}): ` +
|
|
@@ -196,14 +268,14 @@ export class ProxyArtifactStorage implements ArtifactStorage {
|
|
|
196
268
|
}
|
|
197
269
|
|
|
198
270
|
async getDownloadUrl(key: string): Promise<string> {
|
|
199
|
-
const resp = await
|
|
271
|
+
const resp = await fetchWithRetry(`${this.baseUrl}/presigned-download-url`, {
|
|
200
272
|
method: "POST",
|
|
201
273
|
headers: {
|
|
202
274
|
"Authorization": `Bearer ${this.authToken}`,
|
|
203
275
|
"Content-Type": "application/json",
|
|
204
276
|
},
|
|
205
277
|
body: JSON.stringify({ key }),
|
|
206
|
-
});
|
|
278
|
+
}, this.requestPolicy);
|
|
207
279
|
if (!resp.ok) {
|
|
208
280
|
throw new Error(
|
|
209
281
|
`Failed to get presigned download URL (HTTP ${resp.status}): ` +
|
|
@@ -216,7 +288,7 @@ export class ProxyArtifactStorage implements ArtifactStorage {
|
|
|
216
288
|
|
|
217
289
|
async download(key: string): Promise<Buffer> {
|
|
218
290
|
const url = await this.getDownloadUrl(key);
|
|
219
|
-
const resp = await
|
|
291
|
+
const resp = await fetchWithRetry(url, undefined, this.transferPolicy);
|
|
220
292
|
if (!resp.ok) {
|
|
221
293
|
throw new Error(
|
|
222
294
|
`Artifact download failed (HTTP ${resp.status}) for key '${key}': ` +
|
|
@@ -242,7 +314,11 @@ export class ProxyArtifactStorage implements ArtifactStorage {
|
|
|
242
314
|
// addressed CAS blob dedup) then re-uploads, which is idempotent.
|
|
243
315
|
return false;
|
|
244
316
|
}
|
|
245
|
-
const resp = await
|
|
317
|
+
const resp = await fetchWithRetry(
|
|
318
|
+
url,
|
|
319
|
+
{ headers: { Range: "bytes=0-0" } },
|
|
320
|
+
this.requestPolicy,
|
|
321
|
+
);
|
|
246
322
|
await resp.arrayBuffer().catch(() => undefined); // drain the <=1-byte body
|
|
247
323
|
if (resp.status === 404) return false;
|
|
248
324
|
if (resp.status === 200 || resp.status === 206 || resp.status === 416) return true;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download-URL hand-off for execution attachments (issue #532) — the single
|
|
3
|
+
* owner of the mint policy and the prompt wording that lets an agent pass an
|
|
4
|
+
* attachment to a tool whose backend cannot read the sandbox filesystem.
|
|
5
|
+
*
|
|
6
|
+
* Both harnesses (Cursor and deep-agent) materialize attachments to disk and
|
|
7
|
+
* list the workspace paths in their input-files prompt section; that story is
|
|
8
|
+
* unchanged and this module never touches it. What this module adds is the
|
|
9
|
+
* *remote hand-off* story: a per-attachment download URL, minted from the
|
|
10
|
+
* artifact storage the runner already downloads through, surfaced beside the
|
|
11
|
+
* path so the model can quote a short URL string in a tool-call argument
|
|
12
|
+
* instead of the impossible alternative (base64 through the model caps out in
|
|
13
|
+
* the tens of KB; real attachments run 2–8 MB).
|
|
14
|
+
*
|
|
15
|
+
* Mint rule: any attachment with a `storageKey` and a usable storage gets a
|
|
16
|
+
* URL, regardless of which branch materialized the bytes — this covers the
|
|
17
|
+
* local-mode fast path when an uploaded copy also exists. Attachments with no
|
|
18
|
+
* storage key (pure CLI-local files) and extracted ZIP entries (no
|
|
19
|
+
* attachment-level object) get no URL, and their listing is unchanged.
|
|
20
|
+
*
|
|
21
|
+
* Failure is non-fatal and silent in the prompt: the file IS materialized —
|
|
22
|
+
* only the remote hand-off affordance is absent — so a presign hiccup must
|
|
23
|
+
* not abort a turn the way a missing input does (the resolver/injector
|
|
24
|
+
* fail-hard doctrine covers inputs, not affordances). This mirrors how vision
|
|
25
|
+
* delivery degrades without killing materialization. The degrade is logged;
|
|
26
|
+
* the URL value itself is never logged (a presigned URL in the log pipeline
|
|
27
|
+
* would outlive its purpose).
|
|
28
|
+
*
|
|
29
|
+
* What the URL actually is depends on the storage backend, and the prompt
|
|
30
|
+
* must not overpromise: proxy storage mints genuinely presigned, time-limited,
|
|
31
|
+
* single-object URLs a remote service can fetch; local storage returns the
|
|
32
|
+
* stigmer-server's loopback serve URL, which only same-machine tools can
|
|
33
|
+
* reach. The backend self-describes via {@link ArtifactStorage.downloadUrlKind}
|
|
34
|
+
* and {@link downloadUrlDisclosureLine} words each kind honestly.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import type { ArtifactStorage } from "./artifact-storage.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What kind of URL a storage backend's `getDownloadUrl` mints. A property of
|
|
41
|
+
* the backend, not of the runner's execution mode: storage follows transport
|
|
42
|
+
* (see loadArtifactStorageConfig), so a local desktop runner on a cloud proxy
|
|
43
|
+
* mints real presigned URLs.
|
|
44
|
+
*/
|
|
45
|
+
export type DownloadUrlKind = "presigned" | "local-serve";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Mint a download URL for one attachment, or `undefined` when there is
|
|
49
|
+
* nothing to mint (no key / no storage) or the mint fails (logged, non-fatal
|
|
50
|
+
* — see module doc). The caller spreads the result into its per-attachment
|
|
51
|
+
* record with the conditional-spread convention, so an unminted URL leaves no
|
|
52
|
+
* field behind.
|
|
53
|
+
*/
|
|
54
|
+
export async function mintAttachmentDownloadUrl(
|
|
55
|
+
storage: ArtifactStorage | undefined,
|
|
56
|
+
storageKey: string,
|
|
57
|
+
filename: string,
|
|
58
|
+
): Promise<string | undefined> {
|
|
59
|
+
if (!storageKey || !storage) return undefined;
|
|
60
|
+
try {
|
|
61
|
+
return await storage.getDownloadUrl(storageKey);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.warn(
|
|
64
|
+
`[attachment-download-urls] could not mint a download URL for ` +
|
|
65
|
+
`'${filename}' (key: ${storageKey}) — the file is materialized and the ` +
|
|
66
|
+
`turn proceeds without one: ${err instanceof Error ? err.message : String(err)}`,
|
|
67
|
+
);
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The shared hand-off wording both harnesses embed into their input-files
|
|
74
|
+
* prompt section when at least one listed file carries a download URL (each
|
|
75
|
+
* wraps it in its own section framing). Kept here so the two prompts never
|
|
76
|
+
* drift apart in what they promise the agent.
|
|
77
|
+
*
|
|
78
|
+
* Each kind is worded to its real capability. "Time-limited" is deliberately
|
|
79
|
+
* unquantified: the presign TTL belongs to the serving side (the cloud
|
|
80
|
+
* proxy's constant today) and hardcoding it here would silently drift.
|
|
81
|
+
*/
|
|
82
|
+
export function downloadUrlDisclosureLine(kind: DownloadUrlKind): string {
|
|
83
|
+
switch (kind) {
|
|
84
|
+
case "presigned":
|
|
85
|
+
return (
|
|
86
|
+
"Where a file lists a download URL, you can pass that URL to tools " +
|
|
87
|
+
"whose backends cannot read this workspace's filesystem (e.g. remote " +
|
|
88
|
+
"services) — the tool fetches the file's contents itself. These URLs " +
|
|
89
|
+
"are time-limited and each grants access to its single file only."
|
|
90
|
+
);
|
|
91
|
+
case "local-serve":
|
|
92
|
+
return (
|
|
93
|
+
"Where a file lists a download URL, it is served by the local Stigmer " +
|
|
94
|
+
"server and is reachable only from this machine — tools running on " +
|
|
95
|
+
"this machine can fetch it, but remote services cannot."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The runner-synthesized channel messaging attachment (proactive-messaging
|
|
3
|
-
* DD-006 D7/D8)
|
|
3
|
+
* DD-006 D7/D8).
|
|
4
4
|
*
|
|
5
5
|
* When the control plane says an agent serves at least one
|
|
6
6
|
* proactive-messaging channel (the `listMessagingChannels` discovery
|