@stigmer/runner 3.5.2 → 3.6.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/discover-mcp-server.js +9 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -0
- package/dist/activities/execute-cursor/index.js +49 -9
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +7 -0
- package/dist/activities/execute-cursor/prompt-builder.js +9 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +6 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +3 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +45 -9
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +26 -0
- package/dist/client/stigmer-client.js +37 -0
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +89 -0
- package/dist/shared/caller-identity.js +124 -0
- package/dist/shared/caller-identity.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +85 -0
- package/dist/shared/channel-attachment.js +203 -0
- package/dist/shared/channel-attachment.js.map +1 -0
- package/dist/shared/datastore-attachment.d.ts +2 -25
- package/dist/shared/datastore-attachment.js +1 -28
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/synthesized-attachment.d.ts +51 -0
- package/dist/shared/synthesized-attachment.js +45 -0
- package/dist/shared/synthesized-attachment.js.map +1 -0
- package/package.json +2 -2
- package/src/__test-utils__/mock-client.ts +4 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +49 -0
- package/src/activities/discover-mcp-server.ts +13 -1
- package/src/activities/execute-cursor/index.ts +72 -13
- package/src/activities/execute-cursor/prompt-builder.ts +19 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +10 -0
- package/src/activities/execute-deep-agent/setup.ts +66 -10
- package/src/client/stigmer-client.ts +46 -0
- package/src/shared/__tests__/caller-identity.test.ts +159 -0
- package/src/shared/__tests__/channel-attachment.test.ts +276 -0
- package/src/shared/__tests__/datastore-attachment.test.ts +4 -4
- package/src/shared/caller-identity.ts +161 -0
- package/src/shared/channel-attachment.ts +237 -0
- package/src/shared/datastore-attachment.ts +2 -54
- package/src/shared/synthesized-attachment.ts +77 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runner-synthesized channel messaging attachment (proactive-messaging
|
|
3
|
+
* DD-006 D7/D8) — the datastore records attachment's structural twin.
|
|
4
|
+
*
|
|
5
|
+
* When the control plane says an agent serves at least one
|
|
6
|
+
* proactive-messaging channel (the `listMessagingChannels` discovery
|
|
7
|
+
* read, DD-006 D2 — the SAME candidate computation the send
|
|
8
|
+
* authorization runs, so attachment and authority cannot disagree), the
|
|
9
|
+
* runner synthesizes ONE MCP attachment serving `send_channel_message`,
|
|
10
|
+
* and injects the `<available_channel_templates>` prompt section so the
|
|
11
|
+
* model composes template sends in context without spending a tool
|
|
12
|
+
* round (DD-003 D5).
|
|
13
|
+
*
|
|
14
|
+
* Two connection shapes, one roster (the records pattern):
|
|
15
|
+
* - Bridge endpoint configured (cloud): Streamable HTTP against the
|
|
16
|
+
* bridge's /channels route with the execution's own session-scoped
|
|
17
|
+
* credential as the Bearer token.
|
|
18
|
+
* - No bridge endpoint (OSS/local): a spawned `stigmer mcp-server`
|
|
19
|
+
* stdio child with STIGMER_MCP_ROSTER=channels. In practice OSS
|
|
20
|
+
* answers the discovery read with an empty list (DD-006 D3), so
|
|
21
|
+
* this shape only serves local deployments that grow a messaging
|
|
22
|
+
* runtime later — it exists for symmetry with the deployment
|
|
23
|
+
* topology, not for a live OSS path today.
|
|
24
|
+
*
|
|
25
|
+
* Approval-free by construction, and FORCED, not convenient (DD-002
|
|
26
|
+
* D6): both calling surfaces run APPROVAL_MODE_UNATTENDED, where a
|
|
27
|
+
* gated tool resolves as skip-and-adapt — a gated send tool would mean
|
|
28
|
+
* reminders never send. Empty approval maps + no McpServerUsage keep
|
|
29
|
+
* the connect backfill structurally unable to gate it (see
|
|
30
|
+
* synthesized-attachment.ts). Callers inject AFTER resolve + backfill.
|
|
31
|
+
*
|
|
32
|
+
* Failure posture (DD-006 D4): every discovery failure — OSS's empty
|
|
33
|
+
* answer, a registry outage, a control plane predating the RPC
|
|
34
|
+
* (UNIMPLEMENTED), a reach refusal — degrades to honest absence: no
|
|
35
|
+
* tool, no section, execution unharmed.
|
|
36
|
+
*/
|
|
37
|
+
import type { ChannelTemplate, MessagingChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
|
|
38
|
+
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
39
|
+
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
40
|
+
import { type SynthesizedAttachmentOptions } from "./synthesized-attachment.js";
|
|
41
|
+
/**
|
|
42
|
+
* The synthesized attachment's slug. Reserved: a user McpServer with
|
|
43
|
+
* this slug is shadowed by the synthesized attachment, with a warning.
|
|
44
|
+
* Pinned cross-repo by the mcp-server integration test (the
|
|
45
|
+
* TOOL_CALL_LIMIT precedent).
|
|
46
|
+
*/
|
|
47
|
+
export declare const CHANNEL_ATTACHMENT_SLUG = "stigmer-channels";
|
|
48
|
+
/** The bridge route serving the channels-only roster (mcp-server DD-006 D8). */
|
|
49
|
+
export declare const CHANNELS_ROUTE = "/channels";
|
|
50
|
+
/**
|
|
51
|
+
* The most templates the prompt section carries (DD-006 D6): Meta
|
|
52
|
+
* allows hundreds per WABA, and an unbounded section would tax every
|
|
53
|
+
* run's context. Deterministic (name, language) order plus a withheld
|
|
54
|
+
* count keep the agent's behavior independent of registry ordering.
|
|
55
|
+
*/
|
|
56
|
+
export declare const TEMPLATE_SECTION_CAP = 30;
|
|
57
|
+
/** One channel plus its sendable approved templates, ready to format. */
|
|
58
|
+
export interface ChannelMessagingInfo {
|
|
59
|
+
channel: MessagingChannel;
|
|
60
|
+
templates: ChannelTemplate[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The discovery read plus the per-channel template fetch, with the
|
|
64
|
+
* DD-006 D4 failure posture applied: this function NEVER throws — any
|
|
65
|
+
* failure returns an empty list (no tool, no section), because a
|
|
66
|
+
* messaging hiccup must not fail an execution that may not even want
|
|
67
|
+
* to send anything.
|
|
68
|
+
*/
|
|
69
|
+
export declare function discoverChannelMessaging(client: StigmerClient, scopedCredential: string | undefined): Promise<ChannelMessagingInfo[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Synthesize the channel messaging attachment. Returns undefined when
|
|
72
|
+
* the agent serves no proactive channel — the attachment exists exactly
|
|
73
|
+
* when the discovery read says so.
|
|
74
|
+
*/
|
|
75
|
+
export declare function synthesizeChannelAttachment(channels: ChannelMessagingInfo[], options: SynthesizedAttachmentOptions): ResolvedMcpServer | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* The `<available_channel_templates>` prompt section (DD-003 D5):
|
|
78
|
+
* approved AND sendable templates with their full body text, so the
|
|
79
|
+
* model fills positional placeholders beside the values it composes.
|
|
80
|
+
* Unsendable entries are filtered, not annotated (DD-006 D6 — the
|
|
81
|
+
* console panel is the diagnosis surface, the prompt is a composition
|
|
82
|
+
* aid). Returns "" when nothing survives the filter — the tool alone
|
|
83
|
+
* still serves text sends inside a 24-hour window.
|
|
84
|
+
*/
|
|
85
|
+
export declare function formatChannelTemplatesSection(channels: ChannelMessagingInfo[]): string;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runner-synthesized channel messaging attachment (proactive-messaging
|
|
3
|
+
* DD-006 D7/D8) — the datastore records attachment's structural twin.
|
|
4
|
+
*
|
|
5
|
+
* When the control plane says an agent serves at least one
|
|
6
|
+
* proactive-messaging channel (the `listMessagingChannels` discovery
|
|
7
|
+
* read, DD-006 D2 — the SAME candidate computation the send
|
|
8
|
+
* authorization runs, so attachment and authority cannot disagree), the
|
|
9
|
+
* runner synthesizes ONE MCP attachment serving `send_channel_message`,
|
|
10
|
+
* and injects the `<available_channel_templates>` prompt section so the
|
|
11
|
+
* model composes template sends in context without spending a tool
|
|
12
|
+
* round (DD-003 D5).
|
|
13
|
+
*
|
|
14
|
+
* Two connection shapes, one roster (the records pattern):
|
|
15
|
+
* - Bridge endpoint configured (cloud): Streamable HTTP against the
|
|
16
|
+
* bridge's /channels route with the execution's own session-scoped
|
|
17
|
+
* credential as the Bearer token.
|
|
18
|
+
* - No bridge endpoint (OSS/local): a spawned `stigmer mcp-server`
|
|
19
|
+
* stdio child with STIGMER_MCP_ROSTER=channels. In practice OSS
|
|
20
|
+
* answers the discovery read with an empty list (DD-006 D3), so
|
|
21
|
+
* this shape only serves local deployments that grow a messaging
|
|
22
|
+
* runtime later — it exists for symmetry with the deployment
|
|
23
|
+
* topology, not for a live OSS path today.
|
|
24
|
+
*
|
|
25
|
+
* Approval-free by construction, and FORCED, not convenient (DD-002
|
|
26
|
+
* D6): both calling surfaces run APPROVAL_MODE_UNATTENDED, where a
|
|
27
|
+
* gated tool resolves as skip-and-adapt — a gated send tool would mean
|
|
28
|
+
* reminders never send. Empty approval maps + no McpServerUsage keep
|
|
29
|
+
* the connect backfill structurally unable to gate it (see
|
|
30
|
+
* synthesized-attachment.ts). Callers inject AFTER resolve + backfill.
|
|
31
|
+
*
|
|
32
|
+
* Failure posture (DD-006 D4): every discovery failure — OSS's empty
|
|
33
|
+
* answer, a registry outage, a control plane predating the RPC
|
|
34
|
+
* (UNIMPLEMENTED), a reach refusal — degrades to honest absence: no
|
|
35
|
+
* tool, no section, execution unharmed.
|
|
36
|
+
*/
|
|
37
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
38
|
+
import { grpcTarget } from "./synthesized-attachment.js";
|
|
39
|
+
/**
|
|
40
|
+
* The synthesized attachment's slug. Reserved: a user McpServer with
|
|
41
|
+
* this slug is shadowed by the synthesized attachment, with a warning.
|
|
42
|
+
* Pinned cross-repo by the mcp-server integration test (the
|
|
43
|
+
* TOOL_CALL_LIMIT precedent).
|
|
44
|
+
*/
|
|
45
|
+
export const CHANNEL_ATTACHMENT_SLUG = "stigmer-channels";
|
|
46
|
+
/** The bridge route serving the channels-only roster (mcp-server DD-006 D8). */
|
|
47
|
+
export const CHANNELS_ROUTE = "/channels";
|
|
48
|
+
/**
|
|
49
|
+
* The most templates the prompt section carries (DD-006 D6): Meta
|
|
50
|
+
* allows hundreds per WABA, and an unbounded section would tax every
|
|
51
|
+
* run's context. Deterministic (name, language) order plus a withheld
|
|
52
|
+
* count keep the agent's behavior independent of registry ordering.
|
|
53
|
+
*/
|
|
54
|
+
export const TEMPLATE_SECTION_CAP = 30;
|
|
55
|
+
/**
|
|
56
|
+
* The discovery read plus the per-channel template fetch, with the
|
|
57
|
+
* DD-006 D4 failure posture applied: this function NEVER throws — any
|
|
58
|
+
* failure returns an empty list (no tool, no section), because a
|
|
59
|
+
* messaging hiccup must not fail an execution that may not even want
|
|
60
|
+
* to send anything.
|
|
61
|
+
*/
|
|
62
|
+
export async function discoverChannelMessaging(client, scopedCredential) {
|
|
63
|
+
let channels;
|
|
64
|
+
try {
|
|
65
|
+
channels = await client.listMessagingChannels(scopedCredential);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logDiscoveryFailure("listMessagingChannels", err);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
if (channels.length === 0) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
// Template reads degrade PER CHANNEL: a registry outage on one
|
|
75
|
+
// channel must not strip the section for another, and never the tool
|
|
76
|
+
// (a channel with unreadable templates can still send text inside a
|
|
77
|
+
// 24-hour window).
|
|
78
|
+
return Promise.all(channels.map(async (channel) => {
|
|
79
|
+
try {
|
|
80
|
+
return {
|
|
81
|
+
channel,
|
|
82
|
+
templates: await client.listChannelTemplates(channel.channel, scopedCredential),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
logDiscoveryFailure(`listTemplates(${channel.channel})`, err);
|
|
87
|
+
return { channel, templates: [] };
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Synthesize the channel messaging attachment. Returns undefined when
|
|
93
|
+
* the agent serves no proactive channel — the attachment exists exactly
|
|
94
|
+
* when the discovery read says so.
|
|
95
|
+
*/
|
|
96
|
+
export function synthesizeChannelAttachment(channels, options) {
|
|
97
|
+
if (channels.length === 0) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
// Approval-free by construction + backfill-proof: see file header.
|
|
101
|
+
const base = {
|
|
102
|
+
slug: CHANNEL_ATTACHMENT_SLUG,
|
|
103
|
+
toolApprovals: [],
|
|
104
|
+
pinnedToolApprovals: [],
|
|
105
|
+
discoveredCapabilitiesEmpty: false,
|
|
106
|
+
};
|
|
107
|
+
if (options.bridgeEndpoint !== null && options.bridgeEndpoint !== "") {
|
|
108
|
+
return {
|
|
109
|
+
...base,
|
|
110
|
+
connectionType: "http",
|
|
111
|
+
url: options.bridgeEndpoint.replace(/\/+$/, "") + CHANNELS_ROUTE,
|
|
112
|
+
headers: options.credential !== null && options.credential !== ""
|
|
113
|
+
? { Authorization: `Bearer ${options.credential}` }
|
|
114
|
+
: undefined,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
...base,
|
|
119
|
+
connectionType: "stdio",
|
|
120
|
+
command: "stigmer",
|
|
121
|
+
args: ["mcp-server"],
|
|
122
|
+
env: {
|
|
123
|
+
STIGMER_MCP_ROSTER: "channels",
|
|
124
|
+
STIGMER_SERVER_ADDRESS: grpcTarget(options.backendEndpoint),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The `<available_channel_templates>` prompt section (DD-003 D5):
|
|
130
|
+
* approved AND sendable templates with their full body text, so the
|
|
131
|
+
* model fills positional placeholders beside the values it composes.
|
|
132
|
+
* Unsendable entries are filtered, not annotated (DD-006 D6 — the
|
|
133
|
+
* console panel is the diagnosis surface, the prompt is a composition
|
|
134
|
+
* aid). Returns "" when nothing survives the filter — the tool alone
|
|
135
|
+
* still serves text sends inside a 24-hour window.
|
|
136
|
+
*/
|
|
137
|
+
export function formatChannelTemplatesSection(channels) {
|
|
138
|
+
let withheld = 0;
|
|
139
|
+
let budget = TEMPLATE_SECTION_CAP;
|
|
140
|
+
const channelBlocks = [];
|
|
141
|
+
for (const { channel, templates } of channels) {
|
|
142
|
+
// Sendable-only (unsupportedReason empty), deterministic order —
|
|
143
|
+
// agent behavior must never depend on registry ordering (DD-006 D6).
|
|
144
|
+
const sendable = templates
|
|
145
|
+
.filter((t) => t.unsupportedReason === "")
|
|
146
|
+
.sort((a, b) => a.name.localeCompare(b.name) || a.language.localeCompare(b.language));
|
|
147
|
+
const kept = sendable.slice(0, Math.max(budget, 0));
|
|
148
|
+
withheld += sendable.length - kept.length;
|
|
149
|
+
budget -= kept.length;
|
|
150
|
+
if (kept.length === 0) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const lines = kept.map((t) => {
|
|
154
|
+
const parameters = t.parameterNames.length > 0
|
|
155
|
+
? `, parameters: ${t.parameterNames.join(", ")}`
|
|
156
|
+
: "";
|
|
157
|
+
const header = t.headerFormat === "IMAGE"
|
|
158
|
+
? " (requires header_image_link: a public HTTPS image URL)"
|
|
159
|
+
: "";
|
|
160
|
+
return [
|
|
161
|
+
` - ${t.name} (${t.language}) [${t.category}]${parameters}${header}`,
|
|
162
|
+
` "${t.bodyText}"`,
|
|
163
|
+
].join("\n");
|
|
164
|
+
});
|
|
165
|
+
channelBlocks.push([`channel: ${channel.channel} (${channel.provider})`, ...lines].join("\n"));
|
|
166
|
+
}
|
|
167
|
+
if (channelBlocks.length === 0) {
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
const footer = withheld > 0
|
|
171
|
+
? [`(${withheld} more approved template${withheld === 1 ? "" : "s"} not shown)`]
|
|
172
|
+
: [];
|
|
173
|
+
return [
|
|
174
|
+
"<available_channel_templates>",
|
|
175
|
+
"You can send business-initiated messages on the channels below with the",
|
|
176
|
+
"send_channel_message tool. Outside a 24-hour customer-service window the",
|
|
177
|
+
"provider only accepts a pre-approved template, so prefer a template. Fill",
|
|
178
|
+
"every placeholder from the conversation; never invent a value.",
|
|
179
|
+
"",
|
|
180
|
+
...channelBlocks,
|
|
181
|
+
...footer,
|
|
182
|
+
"</available_channel_templates>",
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Expected absences log quietly; anything else warns so an operator can
|
|
187
|
+
* diagnose a mis-provisioned credential without failing the execution.
|
|
188
|
+
* UNIMPLEMENTED is a control plane predating the discovery RPC — the
|
|
189
|
+
* DD-006 D4 deploy-order self-healing case.
|
|
190
|
+
*/
|
|
191
|
+
function logDiscoveryFailure(what, err) {
|
|
192
|
+
const ce = ConnectError.from(err);
|
|
193
|
+
const expected = ce.code === Code.Unimplemented ||
|
|
194
|
+
ce.code === Code.FailedPrecondition ||
|
|
195
|
+
ce.code === Code.Unavailable;
|
|
196
|
+
if (expected) {
|
|
197
|
+
console.debug(`[channel-attachment] ${what} degraded to honest absence: ${ce.message}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.warn(`[channel-attachment] ${what} failed unexpectedly (no tool, no section): ${ce.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=channel-attachment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-attachment.js","sourceRoot":"","sources":["../../src/shared/channel-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOzD,OAAO,EAAE,UAAU,EAAqC,MAAM,6BAA6B,CAAC;AAE5F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAQvC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAqB,EACrB,gBAAoC;IAEpC,IAAI,QAA4B,CAAC;IACjC,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,mBAAmB,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;QAClD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,+DAA+D;IAC/D,qEAAqE;IACrE,oEAAoE;IACpE,mBAAmB;IACnB,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAChD,IAAI,CAAC;YACH,OAAO;gBACL,OAAO;gBACP,SAAS,EAAE,MAAM,MAAM,CAAC,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC;aAChF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mBAAmB,CAAC,iBAAiB,OAAO,CAAC,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QACpC,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAgC,EAChC,OAAqC;IAErC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,mEAAmE;IACnE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,uBAAuB;QAC7B,aAAa,EAAE,EAAE;QACjB,mBAAmB,EAAE,EAAE;QACvB,2BAA2B,EAAE,KAAK;KACnC,CAAC;IAEF,IAAI,OAAO,CAAC,cAAc,KAAK,IAAI,IAAI,OAAO,CAAC,cAAc,KAAK,EAAE,EAAE,CAAC;QACrE,OAAO;YACL,GAAG,IAAI;YACP,cAAc,EAAE,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,cAAc;YAChE,OAAO,EAAE,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,KAAK,EAAE;gBAC/D,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,UAAU,EAAE,EAAE;gBACnD,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,IAAI;QACP,cAAc,EAAE,OAAO;QACvB,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,CAAC,YAAY,CAAC;QACpB,GAAG,EAAE;YACH,kBAAkB,EAAE,UAAU;YAC9B,sBAAsB,EAAE,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC;SAC5D;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,QAAgC;IAC5E,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,oBAAoB,CAAC;IAElC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,iEAAiE;QACjE,qEAAqE;QACrE,MAAM,QAAQ,GAAG,SAAS;aACvB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,KAAK,EAAE,CAAC;aACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAExF,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QACpD,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1C,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;QACtB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,MAAM,UAAU,GAAG,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;gBAC5C,CAAC,CAAC,iBAAiB,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAChD,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,MAAM,GAAG,CAAC,CAAC,YAAY,KAAK,OAAO;gBACvC,CAAC,CAAC,yDAAyD;gBAC3D,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,QAAQ,IAAI,UAAU,GAAG,MAAM,EAAE;gBACrE,QAAQ,CAAC,CAAC,QAAQ,GAAG;aACtB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC,CAAC,CAAC;QACH,aAAa,CAAC,IAAI,CAAC,CAAC,YAAY,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACjG,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,GAAG,CAAC;QACzB,CAAC,CAAC,CAAC,IAAI,QAAQ,0BAA0B,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC;QAChF,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;QACL,+BAA+B;QAC/B,yEAAyE;QACzE,0EAA0E;QAC1E,2EAA2E;QAC3E,gEAAgE;QAChE,EAAE;QACF,GAAG,aAAa;QAChB,GAAG,MAAM;QACT,gCAAgC;KACjC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,IAAY,EAAE,GAAY;IACrD,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,GACZ,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,aAAa;QAC9B,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,kBAAkB;QACnC,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,CAAC;IAC/B,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,gCAAgC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CACV,wBAAwB,IAAI,+CAA+C,EAAE,CAAC,OAAO,EAAE,CACxF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { DatastoreUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
34
34
|
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
35
|
+
import { type SynthesizedAttachmentOptions } from "./synthesized-attachment.js";
|
|
35
36
|
/**
|
|
36
37
|
* The synthesized attachment's slug. Reserved: a user McpServer with
|
|
37
38
|
* this slug is shadowed by the synthesized attachment (the record tools
|
|
@@ -40,36 +41,12 @@ import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
|
40
41
|
export declare const DATASTORE_ATTACHMENT_SLUG = "stigmer-records";
|
|
41
42
|
/** The bridge route serving the records-only roster (mcp-server T05 R1). */
|
|
42
43
|
export declare const RECORDS_ROUTE = "/records";
|
|
43
|
-
export interface DatastoreAttachmentOptions {
|
|
44
|
-
/**
|
|
45
|
-
* The bridge's HTTP endpoint (STIGMER_MCP_BRIDGE_ENDPOINT, e.g.
|
|
46
|
-
* https://mcp.stigmer.ai). Null selects the OSS stdio shape.
|
|
47
|
-
*/
|
|
48
|
-
bridgeEndpoint: string | null;
|
|
49
|
-
/**
|
|
50
|
-
* The execution's session-scoped credential (the sandbox token a
|
|
51
|
-
* cloud runner holds, or the desktop runner's exchanged scoped
|
|
52
|
-
* token). Null attaches no Authorization header (OSS/local).
|
|
53
|
-
*/
|
|
54
|
-
credential: string | null;
|
|
55
|
-
/**
|
|
56
|
-
* The stigmer backend endpoint the stdio child dials
|
|
57
|
-
* (config.stigmerBackendEndpoint). Only used for the OSS shape.
|
|
58
|
-
*/
|
|
59
|
-
backendEndpoint: string;
|
|
60
|
-
}
|
|
61
44
|
/**
|
|
62
45
|
* Synthesize the records attachment for an agent's datastore usages.
|
|
63
46
|
* Returns undefined when the agent uses no datastores — the attachment
|
|
64
47
|
* exists exactly when the usage edge does.
|
|
65
48
|
*/
|
|
66
|
-
export declare function synthesizeDatastoreAttachment(datastoreUsages: DatastoreUsage[], options:
|
|
67
|
-
/**
|
|
68
|
-
* Inject the synthesized attachment into a resolved server list —
|
|
69
|
-
* AFTER resolve + backfill (see file header). A user server shadowing
|
|
70
|
-
* the reserved slug is replaced, loudly.
|
|
71
|
-
*/
|
|
72
|
-
export declare function injectDatastoreAttachment(resolvedServers: ResolvedMcpServer[], attachment: ResolvedMcpServer): ResolvedMcpServer[];
|
|
49
|
+
export declare function synthesizeDatastoreAttachment(datastoreUsages: DatastoreUsage[], options: SynthesizedAttachmentOptions): ResolvedMcpServer | undefined;
|
|
73
50
|
/**
|
|
74
51
|
* The `<available_datastores>` prompt section (DD-005 SD-5, the
|
|
75
52
|
* skills-section precedent): names the attached datastores and points
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* is structurally unable to touch it. Callers must still inject AFTER
|
|
31
31
|
* resolve + backfill; both harness call sites do.
|
|
32
32
|
*/
|
|
33
|
+
import { grpcTarget } from "./synthesized-attachment.js";
|
|
33
34
|
/**
|
|
34
35
|
* The synthesized attachment's slug. Reserved: a user McpServer with
|
|
35
36
|
* this slug is shadowed by the synthesized attachment (the record tools
|
|
@@ -75,22 +76,6 @@ export function synthesizeDatastoreAttachment(datastoreUsages, options) {
|
|
|
75
76
|
},
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Inject the synthesized attachment into a resolved server list —
|
|
80
|
-
* AFTER resolve + backfill (see file header). A user server shadowing
|
|
81
|
-
* the reserved slug is replaced, loudly.
|
|
82
|
-
*/
|
|
83
|
-
export function injectDatastoreAttachment(resolvedServers, attachment) {
|
|
84
|
-
const shadowed = resolvedServers.some((s) => s.slug === attachment.slug);
|
|
85
|
-
if (shadowed) {
|
|
86
|
-
console.warn(`MCP server slug "${attachment.slug}" is reserved for the datastore ` +
|
|
87
|
-
"records attachment; the user-defined server is replaced.");
|
|
88
|
-
}
|
|
89
|
-
return [
|
|
90
|
-
...resolvedServers.filter((s) => s.slug !== attachment.slug),
|
|
91
|
-
attachment,
|
|
92
|
-
];
|
|
93
|
-
}
|
|
94
79
|
/**
|
|
95
80
|
* The `<available_datastores>` prompt section (DD-005 SD-5, the
|
|
96
81
|
* skills-section precedent): names the attached datastores and points
|
|
@@ -114,16 +99,4 @@ export function formatDatastoresSection(datastoreUsages) {
|
|
|
114
99
|
"</available_datastores>",
|
|
115
100
|
].join("\n");
|
|
116
101
|
}
|
|
117
|
-
/**
|
|
118
|
-
* The gRPC dial target (host:port) for a backend endpoint URL — the
|
|
119
|
-
* shape STIGMER_SERVER_ADDRESS wants (the bridge warns on schemes).
|
|
120
|
-
*/
|
|
121
|
-
function grpcTarget(endpoint) {
|
|
122
|
-
try {
|
|
123
|
-
return new URL(endpoint).host;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return endpoint;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
102
|
//# sourceMappingURL=datastore-attachment.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datastore-attachment.js","sourceRoot":"","sources":["../../src/shared/datastore-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;
|
|
1
|
+
{"version":3,"file":"datastore-attachment.js","sourceRoot":"","sources":["../../src/shared/datastore-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,EAAE,UAAU,EAAqC,MAAM,6BAA6B,CAAC;AAE5F;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,iBAAiB,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC3C,eAAiC,EACjC,OAAqC;IAErC,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,mEAAmE;IACnE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,yBAAyB;QAC/B,aAAa,EAAE,EAAE;QACjB,mBAAmB,EAAE,EAAE;QACvB,2BAA2B,EAAE,KAAK;KACnC,CAAC;IAEF,IAAI,OAAO,CAAC,cAAc,KAAK,IAAI,IAAI,OAAO,CAAC,cAAc,KAAK,EAAE,EAAE,CAAC;QACrE,OAAO;YACL,GAAG,IAAI;YACP,cAAc,EAAE,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,aAAa;YAC/D,OAAO,EAAE,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,KAAK,EAAE;gBAC/D,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,UAAU,EAAE,EAAE;gBACnD,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,IAAI;QACP,cAAc,EAAE,OAAO;QACvB,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,CAAC,YAAY,CAAC;QACpB,GAAG,EAAE;YACH,kBAAkB,EAAE,SAAS;YAC7B,sBAAsB,EAAE,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC;SAC5D;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,eAAiC;IACvE,MAAM,KAAK,GAAG,eAAe;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACjD,OAAO;QACL,wBAAwB;QACxB,sEAAsE;QACtE,kFAAkF;QAClF,6EAA6E;QAC7E,8EAA8E;QAC9E,aAAa;QACb,EAAE;QACF,GAAG,OAAO;QACV,yBAAyB;KAC1B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mechanics of runner-synthesized MCP attachments — the pieces
|
|
3
|
+
* the datastore records attachment (T05) and the channel messaging
|
|
4
|
+
* attachment (proactive-messaging DD-006 D8) have in common, extracted
|
|
5
|
+
* when the second attachment arrived.
|
|
6
|
+
*
|
|
7
|
+
* A synthesized attachment is a first-party MCP server entry the runner
|
|
8
|
+
* builds itself (no McpServer resource, no Environment, no credential in
|
|
9
|
+
* any manifest) on a RESERVED slug. Approval-freedom is structural, not
|
|
10
|
+
* configured: empty toolApprovals + pinnedToolApprovals mean
|
|
11
|
+
* mergeApprovalPolicies emits no entries, and discoveredCapabilitiesEmpty
|
|
12
|
+
* false + no McpServerUsage keep the connect backfill's destructiveHint
|
|
13
|
+
* tightener structurally unable to touch it. Callers must still inject
|
|
14
|
+
* AFTER resolve + backfill; every harness call site does.
|
|
15
|
+
*/
|
|
16
|
+
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
17
|
+
/**
|
|
18
|
+
* Connection options for a synthesized attachment — one shape for every
|
|
19
|
+
* attachment because the deployment topology, not the domain, decides
|
|
20
|
+
* the connection.
|
|
21
|
+
*/
|
|
22
|
+
export interface SynthesizedAttachmentOptions {
|
|
23
|
+
/**
|
|
24
|
+
* The bridge's HTTP endpoint (STIGMER_MCP_BRIDGE_ENDPOINT, e.g.
|
|
25
|
+
* https://mcp.stigmer.ai). Null selects the OSS/local stdio shape.
|
|
26
|
+
*/
|
|
27
|
+
bridgeEndpoint: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* The execution's session-scoped credential (the sandbox token a
|
|
30
|
+
* cloud runner holds, or the desktop runner's exchanged scoped
|
|
31
|
+
* token). Null attaches no Authorization header (OSS/local).
|
|
32
|
+
*/
|
|
33
|
+
credential: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* The stigmer backend endpoint the stdio child dials
|
|
36
|
+
* (config.stigmerBackendEndpoint). Only used for the OSS shape.
|
|
37
|
+
*/
|
|
38
|
+
backendEndpoint: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Inject a synthesized attachment into a resolved server list — AFTER
|
|
42
|
+
* resolve + backfill (see the module header). A user server shadowing
|
|
43
|
+
* the reserved slug is replaced, loudly; `label` names the attachment
|
|
44
|
+
* in that warning (e.g. "datastore records").
|
|
45
|
+
*/
|
|
46
|
+
export declare function injectSynthesizedAttachment(resolvedServers: ResolvedMcpServer[], attachment: ResolvedMcpServer, label: string): ResolvedMcpServer[];
|
|
47
|
+
/**
|
|
48
|
+
* The gRPC dial target (host:port) for a backend endpoint URL — the
|
|
49
|
+
* shape STIGMER_SERVER_ADDRESS wants (the bridge warns on schemes).
|
|
50
|
+
*/
|
|
51
|
+
export declare function grpcTarget(endpoint: string): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mechanics of runner-synthesized MCP attachments — the pieces
|
|
3
|
+
* the datastore records attachment (T05) and the channel messaging
|
|
4
|
+
* attachment (proactive-messaging DD-006 D8) have in common, extracted
|
|
5
|
+
* when the second attachment arrived.
|
|
6
|
+
*
|
|
7
|
+
* A synthesized attachment is a first-party MCP server entry the runner
|
|
8
|
+
* builds itself (no McpServer resource, no Environment, no credential in
|
|
9
|
+
* any manifest) on a RESERVED slug. Approval-freedom is structural, not
|
|
10
|
+
* configured: empty toolApprovals + pinnedToolApprovals mean
|
|
11
|
+
* mergeApprovalPolicies emits no entries, and discoveredCapabilitiesEmpty
|
|
12
|
+
* false + no McpServerUsage keep the connect backfill's destructiveHint
|
|
13
|
+
* tightener structurally unable to touch it. Callers must still inject
|
|
14
|
+
* AFTER resolve + backfill; every harness call site does.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Inject a synthesized attachment into a resolved server list — AFTER
|
|
18
|
+
* resolve + backfill (see the module header). A user server shadowing
|
|
19
|
+
* the reserved slug is replaced, loudly; `label` names the attachment
|
|
20
|
+
* in that warning (e.g. "datastore records").
|
|
21
|
+
*/
|
|
22
|
+
export function injectSynthesizedAttachment(resolvedServers, attachment, label) {
|
|
23
|
+
const shadowed = resolvedServers.some((s) => s.slug === attachment.slug);
|
|
24
|
+
if (shadowed) {
|
|
25
|
+
console.warn(`MCP server slug "${attachment.slug}" is reserved for the ${label} ` +
|
|
26
|
+
"attachment; the user-defined server is replaced.");
|
|
27
|
+
}
|
|
28
|
+
return [
|
|
29
|
+
...resolvedServers.filter((s) => s.slug !== attachment.slug),
|
|
30
|
+
attachment,
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The gRPC dial target (host:port) for a backend endpoint URL — the
|
|
35
|
+
* shape STIGMER_SERVER_ADDRESS wants (the bridge warns on schemes).
|
|
36
|
+
*/
|
|
37
|
+
export function grpcTarget(endpoint) {
|
|
38
|
+
try {
|
|
39
|
+
return new URL(endpoint).host;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return endpoint;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=synthesized-attachment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesized-attachment.js","sourceRoot":"","sources":["../../src/shared/synthesized-attachment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA4BH;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,eAAoC,EACpC,UAA6B,EAC7B,KAAa;IAEb,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC;IACzE,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,oBAAoB,UAAU,CAAC,IAAI,yBAAyB,KAAK,GAAG;YACpE,kDAAkD,CACnD,CAAC;IACJ,CAAC;IACD,OAAO;QACL,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC;QAC5D,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigmer/runner",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
86
86
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
87
87
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
88
|
-
"@stigmer/protos": "3.
|
|
88
|
+
"@stigmer/protos": "3.6.0",
|
|
89
89
|
"@temporalio/activity": "^1.11.0",
|
|
90
90
|
"@temporalio/client": "^1.11.0",
|
|
91
91
|
"@temporalio/common": "^1.11.0",
|
|
@@ -26,6 +26,10 @@ export function mockStigmerClient(overrides: MockMethods = {}): StigmerClient {
|
|
|
26
26
|
getRunnerScopedToken: vi.fn().mockResolvedValue(undefined),
|
|
27
27
|
getSession: vi.fn().mockResolvedValue({}),
|
|
28
28
|
updateSession: vi.fn().mockResolvedValue({}),
|
|
29
|
+
// No proactive channels by default — the everyday answer for most
|
|
30
|
+
// agents (DD-006 D2); tests opt in to a channel-bound agent.
|
|
31
|
+
listMessagingChannels: vi.fn().mockResolvedValue([]),
|
|
32
|
+
listChannelTemplates: vi.fn().mockResolvedValue([]),
|
|
29
33
|
getAgent: vi.fn().mockResolvedValue({}),
|
|
30
34
|
getAgentInstance: vi.fn().mockResolvedValue({}),
|
|
31
35
|
getMcpServer: vi.fn().mockResolvedValue({}),
|
|
@@ -411,6 +411,55 @@ describe("DiscoverMcpServer activity", () => {
|
|
|
411
411
|
expect(mockClient.getExecutionContextByExecutionId).toHaveBeenCalledWith("ctx-abc");
|
|
412
412
|
});
|
|
413
413
|
|
|
414
|
+
it("discovers a caller-identity-templating server via the anonymous sentinel", async () => {
|
|
415
|
+
// Discovery has no session, so declared STIGMER_CALLER_IDENTITY_*
|
|
416
|
+
// placeholders resolve to the anonymous sentinel instead of failing
|
|
417
|
+
// with PlaceholderResolutionError — the failure mode that would
|
|
418
|
+
// leave an identity-consuming server's tools permanently
|
|
419
|
+
// unclassified. The server sees an anonymous caller and must answer
|
|
420
|
+
// tools/list (its authz layer refuses tool CALLS, not discovery).
|
|
421
|
+
const { discoverMcpServer } = await import("../discover-mcp-server.js");
|
|
422
|
+
const { MultiServerMCPClient } = await import("@langchain/mcp-adapters");
|
|
423
|
+
|
|
424
|
+
const spec = makeHttpSpec("https://isc-mcp.example.com/mcp");
|
|
425
|
+
spec.serverType.value.headers = {
|
|
426
|
+
"X-Stigmer-Caller-Kind": "${STIGMER_CALLER_IDENTITY_KIND}",
|
|
427
|
+
"X-Stigmer-Caller-Value": "${STIGMER_CALLER_IDENTITY_VALUE}",
|
|
428
|
+
Authorization: "Bearer ${ISC_SHARED_SECRET}",
|
|
429
|
+
};
|
|
430
|
+
spec.env = {
|
|
431
|
+
STIGMER_CALLER_IDENTITY_KIND: { optional: true },
|
|
432
|
+
STIGMER_CALLER_IDENTITY_VALUE: { optional: true },
|
|
433
|
+
ISC_SHARED_SECRET: { isSecret: true },
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const mockClient = makeMockStigmerClient({
|
|
437
|
+
mcpServer: makeMcpServer({ metadata: { slug: "isc-gym" }, spec }),
|
|
438
|
+
executionContext: {
|
|
439
|
+
spec: { data: { ISC_SHARED_SECRET: { value: "s3cret", isSecret: true } } },
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const mockMcpClient = makeMockMcpClient({
|
|
444
|
+
tools: [{ name: "get_gym_info", description: "Public info", inputSchema: { type: "object" } }],
|
|
445
|
+
});
|
|
446
|
+
mockInitializeConnections.mockResolvedValue({});
|
|
447
|
+
mockGetClient.mockResolvedValue(mockMcpClient);
|
|
448
|
+
|
|
449
|
+
const result = await discoverMcpServer(
|
|
450
|
+
{ mcpServerId: "mcp-isc", executionContextId: "ctx-isc" },
|
|
451
|
+
{ stigmerClient: mockClient as any, transportPosture: "stdio-forbidden" },
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(result.tools).toHaveLength(1);
|
|
455
|
+
const connectionConfig = vi.mocked(MultiServerMCPClient).mock.calls[0][0] as any;
|
|
456
|
+
expect(connectionConfig["isc-gym"].headers).toEqual({
|
|
457
|
+
"X-Stigmer-Caller-Kind": "anonymous",
|
|
458
|
+
"X-Stigmer-Caller-Value": "",
|
|
459
|
+
Authorization: "Bearer s3cret",
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
414
463
|
it("returns empty env when no executionContextId provided", async () => {
|
|
415
464
|
const { discoverMcpServer } = await import("../discover-mcp-server.js");
|
|
416
465
|
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type McpTransportPosture,
|
|
31
31
|
} from "../shared/mcp-transport-guard.js";
|
|
32
32
|
import { detectOAuthChallenge } from "../shared/mcp-oauth-detect.js";
|
|
33
|
+
import { injectAnonymousCallerIdentityForDiscovery } from "../shared/caller-identity.js";
|
|
33
34
|
import { withTimeout } from "../shared/with-timeout.js";
|
|
34
35
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
35
36
|
import type { Config } from "../config.js";
|
|
@@ -249,7 +250,18 @@ export async function discoverMcpServer(
|
|
|
249
250
|
const declaredEnvKeys = mcpServer.spec.env
|
|
250
251
|
? new Set(Object.keys(mcpServer.spec.env))
|
|
251
252
|
: new Set<string>();
|
|
252
|
-
const
|
|
253
|
+
const platformEnv = injectPlatformEnv(declaredEnvKeys, envVars);
|
|
254
|
+
|
|
255
|
+
// Discovery runs with no session, so every declared caller-identity key
|
|
256
|
+
// resolves to the anonymous sentinel — without it, a server templating
|
|
257
|
+
// ${STIGMER_CALLER_IDENTITY_VALUE} in its headers would fail discovery
|
|
258
|
+
// with PlaceholderResolutionError and its tools would never be
|
|
259
|
+
// classified. Servers consuming these keys answer tools/list to
|
|
260
|
+
// anonymous callers by contract.
|
|
261
|
+
const finalEnv = injectAnonymousCallerIdentityForDiscovery(
|
|
262
|
+
declaredEnvKeys,
|
|
263
|
+
platformEnv,
|
|
264
|
+
);
|
|
253
265
|
|
|
254
266
|
const resolved = mcpServerToResolved(mcpServer, slug, finalEnv);
|
|
255
267
|
if (!resolved) {
|