@stigmer/runner 3.11.1-dev.20260812192248 → 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/dist/.build-fingerprint +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/index.d.ts +6 -2
- package/dist/activities/execute-cursor/index.js +16 -32
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +15 -11
- package/dist/activities/execute-cursor/prompt-builder.js +20 -10
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- 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 +6 -42
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +11 -0
- package/dist/shared/artifact-storage.js +2 -0
- 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/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/mcp-transport-guard.d.ts +2 -2
- package/dist/shared/mcp-transport-guard.js +2 -2
- package/dist/shared/synthesized-attachment.d.ts +4 -4
- package/dist/shared/synthesized-attachment.js +4 -4
- package/package.json +2 -2
- package/src/__test-utils__/fake-artifact-storage.ts +10 -1
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +62 -29
- 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/index.ts +22 -37
- package/src/activities/execute-cursor/prompt-builder.ts +38 -22
- 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__/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/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 +6 -65
- package/src/config.ts +6 -5
- package/src/shared/__tests__/artifact-storage.test.ts +9 -0
- 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__/synthesized-attachment.test.ts +19 -41
- package/src/shared/artifact-storage.ts +11 -0
- package/src/shared/attachment-download-urls.ts +98 -0
- package/src/shared/channel-attachment.ts +1 -1
- package/src/shared/conversation-attachment.ts +2 -3
- package/src/shared/mcp-transport-guard.ts +2 -2
- package/src/shared/synthesized-attachment.ts +4 -4
- 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-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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the attachment download-URL hand-off policy (issue #532):
|
|
3
|
+
* the branch-independent mint rule, the non-fatal degrade, and the
|
|
4
|
+
* per-kind disclosure wording both harnesses embed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
mintAttachmentDownloadUrl,
|
|
10
|
+
downloadUrlDisclosureLine,
|
|
11
|
+
} from "../attachment-download-urls.js";
|
|
12
|
+
import { makeInMemoryArtifactStorage } from "../../__test-utils__/fake-artifact-storage.js";
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("mintAttachmentDownloadUrl", () => {
|
|
19
|
+
it("mints a URL for a storage key", async () => {
|
|
20
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
21
|
+
|
|
22
|
+
const url = await mintAttachmentDownloadUrl(storage, "attachments/01A/lease.pdf", "lease.pdf");
|
|
23
|
+
|
|
24
|
+
expect(url).toBe("mem://attachments/01A/lease.pdf");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns undefined when the attachment has no storage key", async () => {
|
|
28
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
29
|
+
|
|
30
|
+
const url = await mintAttachmentDownloadUrl(storage, "", "local.csv");
|
|
31
|
+
|
|
32
|
+
expect(url).toBeUndefined();
|
|
33
|
+
expect(storage.getDownloadUrl).not.toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns undefined when no storage is available", async () => {
|
|
37
|
+
const url = await mintAttachmentDownloadUrl(undefined, "attachments/01A/lease.pdf", "lease.pdf");
|
|
38
|
+
|
|
39
|
+
expect(url).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("degrades to undefined on a mint failure and logs the degrade (never throws)", async () => {
|
|
43
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
44
|
+
storage.getDownloadUrl.mockRejectedValueOnce(new Error("presign endpoint unreachable"));
|
|
45
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
46
|
+
|
|
47
|
+
const url = await mintAttachmentDownloadUrl(storage, "attachments/01A/lease.pdf", "lease.pdf");
|
|
48
|
+
|
|
49
|
+
expect(url).toBeUndefined();
|
|
50
|
+
expect(warn).toHaveBeenCalledOnce();
|
|
51
|
+
expect(warn.mock.calls[0][0]).toContain("lease.pdf");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("downloadUrlDisclosureLine", () => {
|
|
56
|
+
it("promises time-limited single-object access for presigned URLs", () => {
|
|
57
|
+
const line = downloadUrlDisclosureLine("presigned");
|
|
58
|
+
|
|
59
|
+
expect(line).toContain("time-limited");
|
|
60
|
+
expect(line).toContain("single file");
|
|
61
|
+
expect(line).not.toContain("this machine");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("is honest about local-serve reach — same machine only, no expiry claim", () => {
|
|
65
|
+
const line = downloadUrlDisclosureLine("local-serve");
|
|
66
|
+
|
|
67
|
+
expect(line).toContain("reachable only from this machine");
|
|
68
|
+
expect(line).not.toContain("time-limited");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The channel messaging attachment (proactive-messaging DD-006 D7/D8):
|
|
3
3
|
* discovery with the never-throw failure posture, both connection
|
|
4
|
-
* shapes, the structural approval-freedom
|
|
5
|
-
*
|
|
4
|
+
* shapes, the structural approval-freedom of synthesized attachments,
|
|
5
|
+
* and the prompt section's filter/order/cap rules
|
|
6
6
|
* (DD-006 D6). The route is the cross-repo string, guarded here and in
|
|
7
7
|
* the mcp-server integration test (the TOOL_CALL_LIMIT precedent); the
|
|
8
8
|
* slug and roster are runner-internal and guarded here alone.
|
|
@@ -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(
|
|
@@ -31,6 +31,15 @@ import { fetchWithRetry, type FetchRetryPolicy } from "./http-retry.js";
|
|
|
31
31
|
// ── Interface ────────────────────────────────────────────────────────
|
|
32
32
|
|
|
33
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";
|
|
34
43
|
upload(key: string, content: Buffer, contentType?: string): Promise<string>;
|
|
35
44
|
getDownloadUrl(key: string): Promise<string>;
|
|
36
45
|
/**
|
|
@@ -52,6 +61,7 @@ export type ArtifactStorageType = "local" | "proxy";
|
|
|
52
61
|
// ── Local Backend ────────────────────────────────────────────────────
|
|
53
62
|
|
|
54
63
|
export class LocalArtifactStorage implements ArtifactStorage {
|
|
64
|
+
readonly downloadUrlKind = "local-serve" as const;
|
|
55
65
|
private readonly basePath: string;
|
|
56
66
|
private readonly serveUrlBase: string;
|
|
57
67
|
|
|
@@ -153,6 +163,7 @@ const DEFAULT_RETRY = {
|
|
|
153
163
|
} as const;
|
|
154
164
|
|
|
155
165
|
export class ProxyArtifactStorage implements ArtifactStorage {
|
|
166
|
+
readonly downloadUrlKind = "presigned" as const;
|
|
156
167
|
private readonly baseUrl: string;
|
|
157
168
|
private readonly authTokenSource: ProxyAuthTokenSource;
|
|
158
169
|
/** Governs the proxy presign calls and the exists probe. */
|
|
@@ -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
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The runner-synthesized conversation participation attachment
|
|
3
|
-
* (channel-conversations DD-008 D-c, A14) —
|
|
4
|
-
*
|
|
5
|
-
* not the channel module's discovery machinery).
|
|
3
|
+
* (channel-conversations DD-008 D-c, A14) — a cheap local predicate,
|
|
4
|
+
* not the channel module's discovery machinery.
|
|
6
5
|
*
|
|
7
6
|
* When the session IS a live channel conversation, the runner
|
|
8
7
|
* synthesizes ONE MCP attachment serving `escalate_to_human`, so the
|
|
@@ -60,8 +60,8 @@ export function resolveMcpTransportPosture(
|
|
|
60
60
|
/**
|
|
61
61
|
* Assert that a resolved MCP server's transport is allowed under the given
|
|
62
62
|
* posture. Applies only to user-defined McpServer resources flowing through
|
|
63
|
-
* the resolvers — the internal
|
|
64
|
-
* separately and
|
|
63
|
+
* the resolvers — the internal synthesized attachments are built
|
|
64
|
+
* separately and are already HTTP in cloud mode.
|
|
65
65
|
*
|
|
66
66
|
* @throws McpTransportError when the posture forbids stdio and the server uses it
|
|
67
67
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared mechanics of runner-synthesized MCP attachments — the pieces
|
|
3
|
-
* the
|
|
4
|
-
* attachment (
|
|
5
|
-
* when the second attachment arrived.
|
|
3
|
+
* the channel messaging attachment (proactive-messaging DD-006 D8) and
|
|
4
|
+
* the conversation participation attachment (DD-008 D-c) have in
|
|
5
|
+
* common, extracted when the second attachment arrived.
|
|
6
6
|
*
|
|
7
7
|
* A synthesized attachment is a first-party MCP server entry the runner
|
|
8
8
|
* builds itself (no McpServer resource, no Environment, no credential in
|
|
@@ -44,7 +44,7 @@ export interface SynthesizedAttachmentOptions {
|
|
|
44
44
|
* Inject a synthesized attachment into a resolved server list — AFTER
|
|
45
45
|
* resolve + backfill (see the module header). A user server shadowing
|
|
46
46
|
* the reserved slug is replaced, loudly; `label` names the attachment
|
|
47
|
-
* in that warning (e.g. "
|
|
47
|
+
* in that warning (e.g. "channel messaging").
|
|
48
48
|
*/
|
|
49
49
|
export function injectSynthesizedAttachment(
|
|
50
50
|
resolvedServers: ResolvedMcpServer[],
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The runner-synthesized datastore records attachment (T05, DD-001 SD-2).
|
|
3
|
-
*
|
|
4
|
-
* When an agent declares `datastore_usages`, the runner synthesizes ONE
|
|
5
|
-
* MCP attachment serving the five record tools (DD-005) — the agent
|
|
6
|
-
* builder writes a usage line and gets tools; no McpServer resource, no
|
|
7
|
-
* Environment, no credential in any manifest. Harness-agnostic like
|
|
8
|
-
* mcp-resolver.ts: both ExecuteCursor and ExecuteDeepAgent inject the
|
|
9
|
-
* synthesized entry through their existing ResolvedMcpServer paths.
|
|
10
|
-
*
|
|
11
|
-
* Two connection shapes, one roster (the bridge's records-only roster,
|
|
12
|
-
* T05 R1):
|
|
13
|
-
* - Bridge endpoint configured (cloud): Streamable HTTP against the
|
|
14
|
-
* bridge's /records route, with the execution's own session-scoped
|
|
15
|
-
* credential as the Bearer token — the server's reach chain (DD-006
|
|
16
|
-
* Path 1) authorizes from that token; the bridge stays a
|
|
17
|
-
* non-validating passthrough.
|
|
18
|
-
* - No bridge endpoint (OSS/local): a spawned `stigmer mcp-server`
|
|
19
|
-
* stdio child with STIGMER_MCP_ROSTER=records against the local
|
|
20
|
-
* backend (zero new distribution — the CLI already embeds the
|
|
21
|
-
* bridge). No credential: the local backend is unauthenticated and
|
|
22
|
-
* OSS callers resolve as the local principal (T05 R2).
|
|
23
|
-
*
|
|
24
|
-
* Approval-free by construction (DD-001 SD-3): empty toolApprovals +
|
|
25
|
-
* pinnedToolApprovals + toolApprovalOverrides mean mergeApprovalPolicies
|
|
26
|
-
* emits no entries for this server — zero classifier involvement, and no
|
|
27
|
-
* proto surface can target it (the attachment has no McpServerUsage, and
|
|
28
|
-
* since issue #349 an agent's overrides are scoped to their own usage's
|
|
29
|
-
* server, so a same-named override elsewhere cannot reach this one).
|
|
30
|
-
* `discoveredCapabilitiesEmpty`
|
|
31
|
-
* is false and the attachment has no McpServerUsage, so the connect
|
|
32
|
-
* backfill (whose destructiveHint tightener would force-gate
|
|
33
|
-
* delete_record — silently skipped on channels under UNATTENDED mode)
|
|
34
|
-
* is structurally unable to touch it. Callers must still inject AFTER
|
|
35
|
-
* resolve + backfill; both harness call sites do.
|
|
36
|
-
*/
|
|
37
|
-
import type { DatastoreUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
38
|
-
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
39
|
-
import { type SynthesizedAttachmentOptions } from "./synthesized-attachment.js";
|
|
40
|
-
/**
|
|
41
|
-
* The synthesized attachment's slug. Reserved: a user McpServer with
|
|
42
|
-
* this slug is shadowed by the synthesized attachment (the record tools
|
|
43
|
-
* must exist whenever `datastore_usages` says so), with a warning.
|
|
44
|
-
*/
|
|
45
|
-
export declare const DATASTORE_ATTACHMENT_SLUG = "stigmer-records";
|
|
46
|
-
/** The bridge route serving the records-only roster (mcp-server T05 R1). */
|
|
47
|
-
export declare const RECORDS_ROUTE = "/records";
|
|
48
|
-
/**
|
|
49
|
-
* Synthesize the records attachment for an agent's datastore usages.
|
|
50
|
-
* Returns undefined when the agent uses no datastores — the attachment
|
|
51
|
-
* exists exactly when the usage edge does.
|
|
52
|
-
*/
|
|
53
|
-
export declare function synthesizeDatastoreAttachment(datastoreUsages: DatastoreUsage[], options: SynthesizedAttachmentOptions): ResolvedMcpServer | undefined;
|
|
54
|
-
/**
|
|
55
|
-
* The five record tools the records roster always serves (DD-005; the
|
|
56
|
-
* mcp-server's records roster registers all five unconditionally for the
|
|
57
|
-
* agent audience — only the `org` argument shape varies by audience, see
|
|
58
|
-
* mcp-server/src/domains/records/tools.ts). Because the roster never
|
|
59
|
-
* legitimately narrows, any of these missing from the connected toolset
|
|
60
|
-
* means the store is degraded (issue #325) — the prompt section and the
|
|
61
|
-
* reconciliation both derive from this one list so they cannot drift.
|
|
62
|
-
*/
|
|
63
|
-
export declare const EXPECTED_RECORD_TOOLS: readonly ["describe_datastore", "find_records", "insert_record", "update_record", "delete_record"];
|
|
64
|
-
/**
|
|
65
|
-
* Reconcile the record tools actually connected against the roster
|
|
66
|
-
* contract (issue #325). Returns the expected tools absent from
|
|
67
|
-
* `actualToolNames` — empty means healthy. Names are bare tool names
|
|
68
|
-
* exactly as reported by tools/list (the mcp-enabled-tools identity
|
|
69
|
-
* space); extraneous names are ignored.
|
|
70
|
-
*/
|
|
71
|
-
export declare function missingRecordTools(actualToolNames: Iterable<string>): string[];
|
|
72
|
-
/**
|
|
73
|
-
* The operator-facing degradation notice (issue #325): pushed as a
|
|
74
|
-
* MESSAGE_SYSTEM row on the execution status so "declared N datastores,
|
|
75
|
-
* X/5 record tools connected" is visible without reading a transcript.
|
|
76
|
-
* Lives HERE with the rest of the datastore-attachment wording — the
|
|
77
|
-
* harness only threads it.
|
|
78
|
-
*/
|
|
79
|
-
export declare function formatDatastoreDegradationNotice(declaredCount: number, missing: readonly string[]): string;
|
|
80
|
-
/**
|
|
81
|
-
* The datastores prompt section (DD-005 SD-5, the skills-section
|
|
82
|
-
* precedent). Shared by both harnesses so the section text cannot drift
|
|
83
|
-
* (the sender-identity precedent). Two honest renderings (issue #325):
|
|
84
|
-
*
|
|
85
|
-
* - Healthy (`missingToolNames` empty or omitted — the Cursor harness
|
|
86
|
-
* always calls it this way: the Cursor SDK connects MCP itself, so
|
|
87
|
-
* that harness can never observe the live roster):
|
|
88
|
-
* `<available_datastores>` names the attached datastores, points the
|
|
89
|
-
* model at describe_datastore first, and carries a standing
|
|
90
|
-
* failure-disclosure instruction — the only mechanism that covers
|
|
91
|
-
* tools which connected but fail at call time (the WhatsApp-pilot
|
|
92
|
-
* outage shape).
|
|
93
|
-
* - Degraded (deep-agent only, from missingRecordTools against the
|
|
94
|
-
* connected roster): `<unavailable_datastores>` instead — the section
|
|
95
|
-
* must not promise tools the agent does not have. Names the declared
|
|
96
|
-
* datastores, states which record tools are missing, and instructs
|
|
97
|
-
* plain disclosure over improvisation.
|
|
98
|
-
*/
|
|
99
|
-
export declare function formatDatastoresSection(datastoreUsages: DatastoreUsage[], missingToolNames?: readonly string[]): string;
|