@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.
Files changed (239) hide show
  1. package/README.md +2 -0
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/discover-mcp-server.d.ts +7 -0
  4. package/dist/activities/discover-mcp-server.js +6 -4
  5. package/dist/activities/discover-mcp-server.js.map +1 -1
  6. package/dist/activities/emit-event.d.ts +14 -2
  7. package/dist/activities/emit-event.js +52 -17
  8. package/dist/activities/emit-event.js.map +1 -1
  9. package/dist/activities/execute-cursor/attachment-resolver.d.ts +11 -0
  10. package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
  11. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  12. package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
  13. package/dist/activities/execute-cursor/blueprint-resolver.js +0 -1
  14. package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
  15. package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
  16. package/dist/activities/execute-cursor/error-classifier.js +34 -3
  17. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  18. package/dist/activities/execute-cursor/index.d.ts +52 -12
  19. package/dist/activities/execute-cursor/index.js +141 -67
  20. package/dist/activities/execute-cursor/index.js.map +1 -1
  21. package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
  22. package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
  23. package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
  24. package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -11
  25. package/dist/activities/execute-cursor/prompt-builder.js +57 -10
  26. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  27. package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
  28. package/dist/activities/execute-cursor/turn-recovery.js +193 -0
  29. package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
  30. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +11 -0
  31. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
  32. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  33. package/dist/activities/execute-deep-agent/index.js +0 -15
  34. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  35. package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
  36. package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
  37. package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
  38. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
  39. package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
  40. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  41. package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
  42. package/dist/activities/execute-deep-agent/setup.js +22 -49
  43. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  44. package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
  45. package/dist/activities/execute-deep-agent/shell-env.js +7 -9
  46. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  47. package/dist/activities/execute-deep-agent/subagent-transformer.js +7 -1
  48. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  49. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
  50. package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
  51. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  52. package/dist/activities/hydrate-workflow-execution.js +8 -5
  53. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  54. package/dist/activities/run-command.d.ts +5 -2
  55. package/dist/activities/run-command.js +22 -10
  56. package/dist/activities/run-command.js.map +1 -1
  57. package/dist/activities/run-env.d.ts +40 -0
  58. package/dist/activities/run-env.js +66 -0
  59. package/dist/activities/run-env.js.map +1 -0
  60. package/dist/bootstrap.js +5 -0
  61. package/dist/bootstrap.js.map +1 -1
  62. package/dist/client/stigmer-client.d.ts +83 -26
  63. package/dist/client/stigmer-client.js +88 -24
  64. package/dist/client/stigmer-client.js.map +1 -1
  65. package/dist/config.d.ts +6 -5
  66. package/dist/config.js.map +1 -1
  67. package/dist/encryption/config.d.ts +41 -9
  68. package/dist/encryption/config.js +63 -23
  69. package/dist/encryption/config.js.map +1 -1
  70. package/dist/middleware/index.d.ts +3 -0
  71. package/dist/middleware/index.js +7 -0
  72. package/dist/middleware/index.js.map +1 -1
  73. package/dist/middleware/path-normalization.d.ts +57 -0
  74. package/dist/middleware/path-normalization.js +109 -0
  75. package/dist/middleware/path-normalization.js.map +1 -0
  76. package/dist/middleware/types.d.ts +8 -0
  77. package/dist/payload-codecs.d.ts +2 -1
  78. package/dist/payload-codecs.js +7 -3
  79. package/dist/payload-codecs.js.map +1 -1
  80. package/dist/runner-manager.js +17 -3
  81. package/dist/runner-manager.js.map +1 -1
  82. package/dist/runner.js +7 -3
  83. package/dist/runner.js.map +1 -1
  84. package/dist/shared/artifact-storage.d.ts +34 -1
  85. package/dist/shared/artifact-storage.js +53 -10
  86. package/dist/shared/artifact-storage.js.map +1 -1
  87. package/dist/shared/attachment-download-urls.d.ts +62 -0
  88. package/dist/shared/attachment-download-urls.js +79 -0
  89. package/dist/shared/attachment-download-urls.js.map +1 -0
  90. package/dist/shared/channel-attachment.d.ts +1 -1
  91. package/dist/shared/channel-attachment.js +1 -1
  92. package/dist/shared/checkpointer/http-saver.d.ts +4 -20
  93. package/dist/shared/checkpointer/http-saver.js +15 -57
  94. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  95. package/dist/shared/conversation-attachment.d.ts +2 -3
  96. package/dist/shared/conversation-attachment.js +2 -3
  97. package/dist/shared/conversation-attachment.js.map +1 -1
  98. package/dist/shared/conversation-catchup.d.ts +9 -2
  99. package/dist/shared/conversation-catchup.js +39 -6
  100. package/dist/shared/conversation-catchup.js.map +1 -1
  101. package/dist/shared/grpc-retry.d.ts +5 -1
  102. package/dist/shared/grpc-retry.js +5 -1
  103. package/dist/shared/grpc-retry.js.map +1 -1
  104. package/dist/shared/http-retry.d.ts +51 -9
  105. package/dist/shared/http-retry.js +60 -9
  106. package/dist/shared/http-retry.js.map +1 -1
  107. package/dist/shared/mcp-manager.d.ts +8 -4
  108. package/dist/shared/mcp-manager.js +30 -4
  109. package/dist/shared/mcp-manager.js.map +1 -1
  110. package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
  111. package/dist/shared/mcp-schema-sanitizer.js +197 -0
  112. package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
  113. package/dist/shared/mcp-transport-guard.d.ts +2 -2
  114. package/dist/shared/mcp-transport-guard.js +2 -2
  115. package/dist/shared/model-client.d.ts +6 -0
  116. package/dist/shared/model-client.js +14 -3
  117. package/dist/shared/model-client.js.map +1 -1
  118. package/dist/shared/model-pricing-data.d.ts +2 -0
  119. package/dist/shared/model-pricing-data.js +13 -3
  120. package/dist/shared/model-pricing-data.js.map +1 -1
  121. package/dist/shared/model-registry.js +3 -2
  122. package/dist/shared/model-registry.js.map +1 -1
  123. package/dist/shared/plan-mode-permissions.d.ts +7 -0
  124. package/dist/shared/plan-mode-permissions.js +7 -0
  125. package/dist/shared/plan-mode-permissions.js.map +1 -1
  126. package/dist/shared/registry-endpoint.d.ts +20 -0
  127. package/dist/shared/registry-endpoint.js +25 -0
  128. package/dist/shared/registry-endpoint.js.map +1 -1
  129. package/dist/shared/runner-credential-keys.d.ts +24 -0
  130. package/dist/shared/runner-credential-keys.js +47 -0
  131. package/dist/shared/runner-credential-keys.js.map +1 -0
  132. package/dist/shared/synthesized-attachment.d.ts +4 -4
  133. package/dist/shared/synthesized-attachment.js +4 -4
  134. package/dist/workflow-engine/resolve.d.ts +19 -1
  135. package/dist/workflow-engine/resolve.js +37 -2
  136. package/dist/workflow-engine/resolve.js.map +1 -1
  137. package/dist/workflows/connect-mcp-server.js +2 -0
  138. package/dist/workflows/connect-mcp-server.js.map +1 -1
  139. package/dist/workflows/types.d.ts +8 -0
  140. package/package.json +3 -4
  141. package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
  142. package/src/__test-utils__/fake-artifact-storage.ts +10 -1
  143. package/src/__test-utils__/vitest-global-setup.ts +50 -0
  144. package/src/__tests__/bootstrap.test.ts +43 -0
  145. package/src/__tests__/encryption-codec.test.ts +53 -0
  146. package/src/__tests__/preflight.test.ts +15 -0
  147. package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
  148. package/src/activities/__tests__/error-classifier.test.ts +94 -2
  149. package/src/activities/__tests__/run-command.test.ts +221 -0
  150. package/src/activities/discover-mcp-server.ts +16 -2
  151. package/src/activities/emit-event.ts +65 -21
  152. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
  153. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
  154. package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
  155. package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
  156. package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
  157. package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
  158. package/src/activities/execute-cursor/error-classifier.ts +40 -3
  159. package/src/activities/execute-cursor/index.ts +175 -74
  160. package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
  161. package/src/activities/execute-cursor/prompt-builder.ts +89 -22
  162. package/src/activities/execute-cursor/turn-recovery.ts +208 -0
  163. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
  164. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
  165. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
  166. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
  167. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
  168. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
  169. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +71 -0
  170. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
  171. package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
  172. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +22 -17
  173. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
  174. package/src/activities/execute-deep-agent/index.ts +0 -19
  175. package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
  176. package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
  177. package/src/activities/execute-deep-agent/setup.ts +22 -73
  178. package/src/activities/execute-deep-agent/shell-env.ts +8 -9
  179. package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
  180. package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
  181. package/src/activities/hydrate-workflow-execution.ts +8 -5
  182. package/src/activities/run-command.ts +25 -12
  183. package/src/activities/run-env.ts +79 -0
  184. package/src/bootstrap.ts +5 -0
  185. package/src/client/__tests__/stigmer-client.test.ts +104 -5
  186. package/src/client/stigmer-client.ts +128 -29
  187. package/src/config.ts +6 -5
  188. package/src/encryption/config.ts +87 -23
  189. package/src/middleware/__tests__/path-normalization.test.ts +140 -0
  190. package/src/middleware/index.ts +8 -0
  191. package/src/middleware/path-normalization.ts +125 -0
  192. package/src/middleware/types.ts +11 -0
  193. package/src/payload-codecs.ts +8 -2
  194. package/src/runner-manager.ts +21 -3
  195. package/src/runner.ts +7 -3
  196. package/src/shared/__tests__/artifact-storage.test.ts +154 -2
  197. package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
  198. package/src/shared/__tests__/channel-attachment.test.ts +2 -2
  199. package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
  200. package/src/shared/__tests__/http-retry.test.ts +5 -4
  201. package/src/shared/__tests__/mcp-manager.test.ts +53 -13
  202. package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
  203. package/src/shared/__tests__/model-client.test.ts +46 -0
  204. package/src/shared/__tests__/model-pricing.test.ts +62 -2
  205. package/src/shared/__tests__/model-registry.test.ts +22 -0
  206. package/src/shared/__tests__/synthesized-attachment.test.ts +19 -41
  207. package/src/shared/artifact-storage.ts +86 -10
  208. package/src/shared/attachment-download-urls.ts +98 -0
  209. package/src/shared/channel-attachment.ts +1 -1
  210. package/src/shared/checkpointer/http-saver.ts +15 -60
  211. package/src/shared/conversation-attachment.ts +2 -3
  212. package/src/shared/conversation-catchup.ts +39 -6
  213. package/src/shared/grpc-retry.ts +5 -1
  214. package/src/shared/http-retry.ts +98 -9
  215. package/src/shared/mcp-manager.ts +35 -4
  216. package/src/shared/mcp-schema-sanitizer.ts +224 -0
  217. package/src/shared/mcp-transport-guard.ts +2 -2
  218. package/src/shared/model-client.ts +21 -3
  219. package/src/shared/model-pricing-data.ts +22 -3
  220. package/src/shared/model-registry.ts +7 -2
  221. package/src/shared/plan-mode-permissions.ts +7 -0
  222. package/src/shared/registry-endpoint.ts +27 -0
  223. package/src/shared/runner-credential-keys.ts +46 -0
  224. package/src/shared/synthesized-attachment.ts +4 -4
  225. package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
  226. package/src/workflow-engine/resolve.ts +48 -2
  227. package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
  228. package/src/workflows/connect-mcp-server.ts +2 -0
  229. package/src/workflows/types.ts +8 -0
  230. package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
  231. package/dist/activities/execute-cursor/mcp-config.js +0 -39
  232. package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
  233. package/dist/shared/datastore-attachment.d.ts +0 -99
  234. package/dist/shared/datastore-attachment.js +0 -184
  235. package/dist/shared/datastore-attachment.js.map +0 -1
  236. package/src/activities/execute-cursor/mcp-config.ts +0 -66
  237. package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
  238. package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
  239. package/src/shared/datastore-attachment.ts +0 -209
@@ -1,293 +0,0 @@
1
- // The synthesized datastore records attachment (T05): connection
2
- // shapes, the approval-free-by-construction property, and the
3
- // structural immunity to the connect backfill's destructiveHint
4
- // tightener — the two hazards that would otherwise silently skip
5
- // record writes on channels (UNATTENDED mode).
6
-
7
- import { create } from "@bufbuild/protobuf";
8
- import { DatastoreUsageSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
9
- import { ApiResourceReferenceSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
10
- import { describe, expect, it, vi } from "vitest";
11
-
12
- import { mergeApprovalPolicies, type ActiveLeases } from "../approval-policy.js";
13
- import { needsBackfill } from "../connect-backfill.js";
14
- import {
15
- DATASTORE_ATTACHMENT_SLUG,
16
- EXPECTED_RECORD_TOOLS,
17
- formatDatastoreDegradationNotice,
18
- formatDatastoresSection,
19
- missingRecordTools,
20
- synthesizeDatastoreAttachment,
21
- } from "../datastore-attachment.js";
22
- import { injectSynthesizedAttachment } from "../synthesized-attachment.js";
23
- import type { ResolvedMcpServer } from "../mcp-resolver.js";
24
-
25
- function usage(slug: string) {
26
- return create(DatastoreUsageSchema, {
27
- datastoreRef: create(ApiResourceReferenceSchema, { slug }),
28
- });
29
- }
30
-
31
- const NO_LEASES: ActiveLeases = {
32
- global: false,
33
- categories: new Set(),
34
- servers: new Set(),
35
- };
36
-
37
- describe("synthesizeDatastoreAttachment", () => {
38
- it("returns undefined when the agent uses no datastores", () => {
39
- expect(
40
- synthesizeDatastoreAttachment([], {
41
- bridgeEndpoint: "https://mcp.stigmer.ai",
42
- credential: "tok",
43
- backendEndpoint: "http://localhost:7234",
44
- }),
45
- ).toBeUndefined();
46
- });
47
-
48
- it("builds the HTTP shape against the bridge /records route with the injected credential", () => {
49
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
50
- bridgeEndpoint: "https://mcp.stigmer.ai/",
51
- credential: "sandbox-token",
52
- backendEndpoint: "http://localhost:7234",
53
- });
54
-
55
- expect(attachment).toMatchObject({
56
- slug: DATASTORE_ATTACHMENT_SLUG,
57
- connectionType: "http",
58
- url: "https://mcp.stigmer.ai/records",
59
- headers: { Authorization: "Bearer sandbox-token" },
60
- });
61
- });
62
-
63
- it("omits the Authorization header without a credential", () => {
64
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
65
- bridgeEndpoint: "https://mcp.stigmer.ai",
66
- credential: null,
67
- backendEndpoint: "http://localhost:7234",
68
- });
69
- expect(attachment?.headers).toBeUndefined();
70
- });
71
-
72
- it("builds the OSS stdio shape: the CLI-embedded bridge with the records roster", () => {
73
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
74
- bridgeEndpoint: null,
75
- credential: null,
76
- backendEndpoint: "http://localhost:7234",
77
- });
78
-
79
- expect(attachment).toMatchObject({
80
- slug: DATASTORE_ATTACHMENT_SLUG,
81
- connectionType: "stdio",
82
- command: "stigmer",
83
- args: ["mcp-server"],
84
- env: {
85
- STIGMER_MCP_ROSTER: "records",
86
- STIGMER_SERVER_ADDRESS: "localhost:7234",
87
- },
88
- });
89
- });
90
-
91
- it("is approval-free by construction: zero entries in the merged approval map", () => {
92
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
93
- bridgeEndpoint: "https://mcp.stigmer.ai",
94
- credential: "tok",
95
- backendEndpoint: "http://localhost:7234",
96
- })!;
97
-
98
- // Absence from the map means auto-approved for EVERY consumer (the
99
- // Cursor hook, the deep-agent gate) — DD-001 SD-3's structural bypass.
100
- const merged = mergeApprovalPolicies([attachment], NO_LEASES);
101
- expect(merged.size).toBe(0);
102
- });
103
-
104
- it("stays approval-free when another server's usage overrides a same-named tool (issue #349)", () => {
105
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
106
- bridgeEndpoint: "https://mcp.stigmer.ai",
107
- credential: "tok",
108
- backendEndpoint: "http://localhost:7234",
109
- })!;
110
-
111
- // Before #349 a flat override list applied inside every server's merge
112
- // loop, so this crm override would have force-gated the attachment's
113
- // delete_record too — and on channels (UNATTENDED mode) a gated tool
114
- // is silently skipped. Overrides now ride their own server, so the
115
- // attachment (which has no usage) is immune by construction.
116
- const crm: ResolvedMcpServer = {
117
- slug: "crm",
118
- connectionType: "http",
119
- url: "https://crm.example.com/mcp",
120
- toolApprovals: [],
121
- pinnedToolApprovals: [],
122
- toolApprovalOverrides: [
123
- { toolName: "delete_record", requiresApproval: true, message: "" } as any,
124
- ],
125
- discoveredCapabilitiesEmpty: false,
126
- };
127
- const merged = mergeApprovalPolicies([crm, attachment], NO_LEASES);
128
- expect(merged.has("crm/delete_record")).toBe(true);
129
- expect(merged.has(`${DATASTORE_ATTACHMENT_SLUG}/delete_record`)).toBe(false);
130
- });
131
-
132
- it("is structurally immune to the connect backfill (destructiveHint tightener)", () => {
133
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
134
- bridgeEndpoint: "https://mcp.stigmer.ai",
135
- credential: "tok",
136
- backendEndpoint: "http://localhost:7234",
137
- })!;
138
-
139
- // If this ever became true, the backfill's connect + classification
140
- // would force-gate delete_record (it carries destructiveHint), and on
141
- // channels a gated tool is silently auto-skipped.
142
- expect(attachment.discoveredCapabilitiesEmpty).toBe(false);
143
- expect(needsBackfill(attachment)).toBe(false);
144
- });
145
- });
146
-
147
- describe("injectSynthesizedAttachment (the shared injection path)", () => {
148
- const attachment = synthesizeDatastoreAttachment([usage("clinic")], {
149
- bridgeEndpoint: "https://mcp.stigmer.ai",
150
- credential: "tok",
151
- backendEndpoint: "http://localhost:7234",
152
- })!;
153
-
154
- it("appends to the resolved servers", () => {
155
- const other: ResolvedMcpServer = {
156
- slug: "github",
157
- connectionType: "http",
158
- url: "https://example.com",
159
- toolApprovals: [],
160
- pinnedToolApprovals: [],
161
- toolApprovalOverrides: [],
162
- discoveredCapabilitiesEmpty: false,
163
- };
164
- const result = injectSynthesizedAttachment([other], attachment, "datastore records");
165
- expect(result.map((s) => s.slug)).toEqual(["github", DATASTORE_ATTACHMENT_SLUG]);
166
- });
167
-
168
- it("replaces a user server shadowing the reserved slug, loudly", () => {
169
- const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
170
- const impostor: ResolvedMcpServer = {
171
- slug: DATASTORE_ATTACHMENT_SLUG,
172
- connectionType: "http",
173
- url: "https://evil.example.com",
174
- toolApprovals: [],
175
- pinnedToolApprovals: [],
176
- toolApprovalOverrides: [],
177
- discoveredCapabilitiesEmpty: false,
178
- };
179
-
180
- const result = injectSynthesizedAttachment([impostor], attachment, "datastore records");
181
-
182
- expect(result).toHaveLength(1);
183
- expect(result[0].url).toBe("https://mcp.stigmer.ai/records");
184
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("reserved"));
185
- warnSpy.mockRestore();
186
- });
187
- });
188
-
189
- describe("EXPECTED_RECORD_TOOLS", () => {
190
- it("pins the records-roster contract: exactly the five tools the mcp-server registers", () => {
191
- // Drift pin (issue #325): the reconciliation treats ANY of these as
192
- // mandatory because mcp-server/src/domains/records/tools.ts registers
193
- // all five unconditionally for the agent audience. If a record tool is
194
- // ever added, removed, or renamed there, this pin forces the reviewer
195
- // to update the roster contract deliberately in both places.
196
- expect([...EXPECTED_RECORD_TOOLS]).toEqual([
197
- "describe_datastore",
198
- "find_records",
199
- "insert_record",
200
- "update_record",
201
- "delete_record",
202
- ]);
203
- });
204
- });
205
-
206
- describe("missingRecordTools", () => {
207
- it("returns empty when all five record tools are connected", () => {
208
- expect(missingRecordTools([...EXPECTED_RECORD_TOOLS])).toEqual([]);
209
- });
210
-
211
- it("returns every expected tool when the roster is empty", () => {
212
- expect(missingRecordTools([])).toEqual([...EXPECTED_RECORD_TOOLS]);
213
- });
214
-
215
- it("returns only the absent tools on a partial roster", () => {
216
- expect(
217
- missingRecordTools(["describe_datastore", "find_records", "insert_record"]),
218
- ).toEqual(["update_record", "delete_record"]);
219
- });
220
-
221
- it("ignores extraneous tool names", () => {
222
- expect(
223
- missingRecordTools([...EXPECTED_RECORD_TOOLS, "some_other_tool"]),
224
- ).toEqual([]);
225
- });
226
- });
227
-
228
- describe("formatDatastoresSection", () => {
229
- it("names the attached datastores and points at describe_datastore first", () => {
230
- const section = formatDatastoresSection([usage("clinic"), usage("inventory")]);
231
- expect(section).toContain("<available_datastores>");
232
- expect(section).toContain("- clinic");
233
- expect(section).toContain("- inventory");
234
- expect(section).toContain("describe_datastore");
235
- expect(section).toContain("</available_datastores>");
236
- });
237
-
238
- it("carries the standing failure-disclosure instruction in the healthy section", () => {
239
- // The only mechanism covering tools that connected but fail at call
240
- // time (the WhatsApp-pilot outage shape), and the Cursor harness's
241
- // entire coverage — that harness can never observe the live roster.
242
- const section = formatDatastoresSection([usage("clinic")]);
243
- expect(section).toContain("the datastore is unreachable");
244
- expect(section).toContain("do not answer from memory");
245
- });
246
-
247
- it("advertises exactly the expected record tools (no hand-written drift)", () => {
248
- const section = formatDatastoresSection([usage("clinic")]);
249
- for (const tool of EXPECTED_RECORD_TOOLS) {
250
- expect(section).toContain(tool);
251
- }
252
- });
253
-
254
- it("renders the degraded section instead when record tools are missing", () => {
255
- const section = formatDatastoresSection(
256
- [usage("clinic")],
257
- ["find_records", "insert_record"],
258
- );
259
- // Must NOT promise tools the agent does not have — the issue's core
260
- // complaint (oss#325).
261
- expect(section).not.toContain("<available_datastores>");
262
- expect(section).toContain("<unavailable_datastores>");
263
- expect(section).toContain("find_records, insert_record.");
264
- expect(section).toContain("- clinic");
265
- expect(section).toContain("cannot be reached right now");
266
- expect(section).toContain("Never answer");
267
- expect(section).toContain("</unavailable_datastores>");
268
- });
269
-
270
- it("renders healthy when the missing list is empty (negative control)", () => {
271
- const section = formatDatastoresSection([usage("clinic")], []);
272
- expect(section).toContain("<available_datastores>");
273
- expect(section).not.toContain("<unavailable_datastores>");
274
- });
275
- });
276
-
277
- describe("formatDatastoreDegradationNotice", () => {
278
- it("states declared count, connected fraction, and the missing tools", () => {
279
- const notice = formatDatastoreDegradationNotice(2, [
280
- "update_record",
281
- "delete_record",
282
- ]);
283
- expect(notice).toContain("declared 2 datastore(s)");
284
- expect(notice).toContain("3/5 record tools connected");
285
- expect(notice).toContain("missing: update_record, delete_record");
286
- expect(notice).toContain("instructed to disclose");
287
- });
288
-
289
- it("reads 0/5 on a fully absent roster", () => {
290
- const notice = formatDatastoreDegradationNotice(1, [...EXPECTED_RECORD_TOOLS]);
291
- expect(notice).toContain("0/5 record tools connected");
292
- });
293
- });
@@ -1,209 +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
-
38
- import type { DatastoreUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
39
- import type { ResolvedMcpServer } from "./mcp-resolver.js";
40
- import { grpcTarget, type SynthesizedAttachmentOptions } from "./synthesized-attachment.js";
41
-
42
- /**
43
- * The synthesized attachment's slug. Reserved: a user McpServer with
44
- * this slug is shadowed by the synthesized attachment (the record tools
45
- * must exist whenever `datastore_usages` says so), with a warning.
46
- */
47
- export const DATASTORE_ATTACHMENT_SLUG = "stigmer-records";
48
-
49
- /** The bridge route serving the records-only roster (mcp-server T05 R1). */
50
- export const RECORDS_ROUTE = "/records";
51
-
52
- /**
53
- * Synthesize the records attachment for an agent's datastore usages.
54
- * Returns undefined when the agent uses no datastores — the attachment
55
- * exists exactly when the usage edge does.
56
- */
57
- export function synthesizeDatastoreAttachment(
58
- datastoreUsages: DatastoreUsage[],
59
- options: SynthesizedAttachmentOptions,
60
- ): ResolvedMcpServer | undefined {
61
- if (datastoreUsages.length === 0) {
62
- return undefined;
63
- }
64
-
65
- // Approval-free by construction + backfill-proof: see file header.
66
- const base = {
67
- slug: DATASTORE_ATTACHMENT_SLUG,
68
- toolApprovals: [],
69
- pinnedToolApprovals: [],
70
- toolApprovalOverrides: [],
71
- discoveredCapabilitiesEmpty: false,
72
- };
73
-
74
- if (options.bridgeEndpoint !== null && options.bridgeEndpoint !== "") {
75
- return {
76
- ...base,
77
- connectionType: "http",
78
- url: options.bridgeEndpoint.replace(/\/+$/, "") + RECORDS_ROUTE,
79
- headers: options.credential !== null && options.credential !== ""
80
- ? { Authorization: `Bearer ${options.credential}` }
81
- : undefined,
82
- };
83
- }
84
-
85
- return {
86
- ...base,
87
- connectionType: "stdio",
88
- command: "stigmer",
89
- args: ["mcp-server"],
90
- env: {
91
- STIGMER_MCP_ROSTER: "records",
92
- STIGMER_SERVER_ADDRESS: grpcTarget(options.backendEndpoint),
93
- },
94
- };
95
- }
96
-
97
- /**
98
- * The five record tools the records roster always serves (DD-005; the
99
- * mcp-server's records roster registers all five unconditionally for the
100
- * agent audience — only the `org` argument shape varies by audience, see
101
- * mcp-server/src/domains/records/tools.ts). Because the roster never
102
- * legitimately narrows, any of these missing from the connected toolset
103
- * means the store is degraded (issue #325) — the prompt section and the
104
- * reconciliation both derive from this one list so they cannot drift.
105
- */
106
- export const EXPECTED_RECORD_TOOLS = [
107
- "describe_datastore",
108
- "find_records",
109
- "insert_record",
110
- "update_record",
111
- "delete_record",
112
- ] as const;
113
-
114
- /**
115
- * Reconcile the record tools actually connected against the roster
116
- * contract (issue #325). Returns the expected tools absent from
117
- * `actualToolNames` — empty means healthy. Names are bare tool names
118
- * exactly as reported by tools/list (the mcp-enabled-tools identity
119
- * space); extraneous names are ignored.
120
- */
121
- export function missingRecordTools(actualToolNames: Iterable<string>): string[] {
122
- const present = new Set(actualToolNames);
123
- return EXPECTED_RECORD_TOOLS.filter((name) => !present.has(name));
124
- }
125
-
126
- /**
127
- * The operator-facing degradation notice (issue #325): pushed as a
128
- * MESSAGE_SYSTEM row on the execution status so "declared N datastores,
129
- * X/5 record tools connected" is visible without reading a transcript.
130
- * Lives HERE with the rest of the datastore-attachment wording — the
131
- * harness only threads it.
132
- */
133
- export function formatDatastoreDegradationNotice(
134
- declaredCount: number,
135
- missing: readonly string[],
136
- ): string {
137
- const connected = EXPECTED_RECORD_TOOLS.length - missing.length;
138
- return (
139
- `Datastore record tools unavailable this turn: declared ` +
140
- `${declaredCount} datastore(s), ${connected}/${EXPECTED_RECORD_TOOLS.length} ` +
141
- `record tools connected (missing: ${missing.join(", ")}). The agent has ` +
142
- `been instructed to disclose this instead of answering from memory.`
143
- );
144
- }
145
-
146
- /**
147
- * The datastores prompt section (DD-005 SD-5, the skills-section
148
- * precedent). Shared by both harnesses so the section text cannot drift
149
- * (the sender-identity precedent). Two honest renderings (issue #325):
150
- *
151
- * - Healthy (`missingToolNames` empty or omitted — the Cursor harness
152
- * always calls it this way: the Cursor SDK connects MCP itself, so
153
- * that harness can never observe the live roster):
154
- * `<available_datastores>` names the attached datastores, points the
155
- * model at describe_datastore first, and carries a standing
156
- * failure-disclosure instruction — the only mechanism that covers
157
- * tools which connected but fail at call time (the WhatsApp-pilot
158
- * outage shape).
159
- * - Degraded (deep-agent only, from missingRecordTools against the
160
- * connected roster): `<unavailable_datastores>` instead — the section
161
- * must not promise tools the agent does not have. Names the declared
162
- * datastores, states which record tools are missing, and instructs
163
- * plain disclosure over improvisation.
164
- */
165
- export function formatDatastoresSection(
166
- datastoreUsages: DatastoreUsage[],
167
- missingToolNames: readonly string[] = [],
168
- ): string {
169
- const entries = datastoreUsages
170
- .map((u) => u.datastoreRef?.slug)
171
- .filter((s): s is string => !!s)
172
- .map((slug) => `- ${slug}`);
173
-
174
- if (missingToolNames.length > 0) {
175
- return [
176
- "<unavailable_datastores>",
177
- "This agent is configured to use the datastores listed below, but the",
178
- "following record tools failed to connect this turn:",
179
- missingToolNames.join(", ") + ".",
180
- "Treat these datastores as unreachable for any operation that needs a",
181
- "missing tool.",
182
- "",
183
- ...entries,
184
- "",
185
- "When the user's request depends on one of these datastores, tell them",
186
- "plainly that the datastore cannot be reached right now. Never answer",
187
- "from memory, guess, or improvise a substitute for what the datastore",
188
- "would have returned.",
189
- "</unavailable_datastores>",
190
- ].join("\n");
191
- }
192
-
193
- return [
194
- "<available_datastores>",
195
- "You have access to the following datastores through the record tools",
196
- `(${EXPECTED_RECORD_TOOLS.join(", ")}).`,
197
- "Before your first operation against a datastore, call describe_datastore to",
198
- "learn its collections, field encodings, and which operations you are allowed",
199
- "to perform.",
200
- "",
201
- ...entries,
202
- "",
203
- "If any record tool is missing from your toolset or a record tool call",
204
- "fails, the datastore is unreachable: tell the user plainly that you cannot",
205
- "reach it right now, and do not answer from memory, guess, or improvise a",
206
- "substitute for what it would have returned.",
207
- "</available_datastores>",
208
- ].join("\n");
209
- }