@stigmer/runner 3.8.0 → 3.10.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 (64) hide show
  1. package/README.md +2 -2
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/execute-cursor/attachment-resolver.d.ts +16 -0
  4. package/dist/activities/execute-cursor/attachment-resolver.js +63 -4
  5. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  6. package/dist/activities/execute-cursor/index.d.ts +15 -0
  7. package/dist/activities/execute-cursor/index.js +73 -11
  8. package/dist/activities/execute-cursor/index.js.map +1 -1
  9. package/dist/activities/execute-cursor/prompt-builder.d.ts +14 -1
  10. package/dist/activities/execute-cursor/prompt-builder.js +11 -2
  11. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  12. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +17 -0
  13. package/dist/activities/execute-deep-agent/attachment-injector.js +34 -4
  14. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  15. package/dist/activities/execute-deep-agent/hitl.d.ts +15 -7
  16. package/dist/activities/execute-deep-agent/hitl.js +6 -15
  17. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  18. package/dist/activities/execute-deep-agent/index.js +4 -1
  19. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  20. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +17 -0
  21. package/dist/activities/execute-deep-agent/prompt-builder.js +13 -2
  22. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  23. package/dist/activities/execute-deep-agent/setup.js +49 -3
  24. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  25. package/dist/runner-manager.js +14 -0
  26. package/dist/runner-manager.js.map +1 -1
  27. package/dist/runner.js +14 -0
  28. package/dist/runner.js.map +1 -1
  29. package/dist/shared/artifact-storage.d.ts +10 -0
  30. package/dist/shared/artifact-storage.js +49 -8
  31. package/dist/shared/artifact-storage.js.map +1 -1
  32. package/dist/shared/attachment-vision.d.ts +244 -0
  33. package/dist/shared/attachment-vision.js +330 -0
  34. package/dist/shared/attachment-vision.js.map +1 -0
  35. package/dist/shared/mcp-manager.d.ts +14 -1
  36. package/dist/shared/mcp-manager.js +20 -21
  37. package/dist/shared/mcp-manager.js.map +1 -1
  38. package/dist/shared/model-registry.d.ts +20 -2
  39. package/dist/shared/model-registry.js +37 -2
  40. package/dist/shared/model-registry.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +218 -0
  43. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +105 -3
  44. package/src/activities/execute-cursor/attachment-resolver.ts +97 -4
  45. package/src/activities/execute-cursor/index.ts +94 -13
  46. package/src/activities/execute-cursor/prompt-builder.ts +27 -2
  47. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +207 -0
  48. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +13 -13
  49. package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +152 -0
  50. package/src/activities/execute-deep-agent/attachment-injector.ts +65 -4
  51. package/src/activities/execute-deep-agent/hitl.ts +14 -19
  52. package/src/activities/execute-deep-agent/index.ts +4 -5
  53. package/src/activities/execute-deep-agent/prompt-builder.ts +37 -2
  54. package/src/activities/execute-deep-agent/setup.ts +58 -3
  55. package/src/runner-manager.ts +19 -0
  56. package/src/runner.ts +19 -0
  57. package/src/shared/__tests__/artifact-storage.test.ts +76 -1
  58. package/src/shared/__tests__/attachment-vision.test.ts +420 -0
  59. package/src/shared/__tests__/mcp-manager.test.ts +87 -1
  60. package/src/shared/__tests__/model-registry.test.ts +71 -0
  61. package/src/shared/artifact-storage.ts +55 -8
  62. package/src/shared/attachment-vision.ts +456 -0
  63. package/src/shared/mcp-manager.ts +22 -22
  64. package/src/shared/model-registry.ts +50 -2
@@ -10,9 +10,22 @@
10
10
  * resolution time by shared/mcp-transport-guard.ts — servers reaching
11
11
  * this manager have already passed it.
12
12
  *
13
+ * Stdio env contract (oss#256): a subprocess receives exactly the
14
+ * variables declared in the server's spec.env (resolved upstream by
15
+ * filterEnvToDeclaredKeys) plus the MCP SDK's minimal base environment
16
+ * (HOME, LOGNAME, PATH, SHELL, TERM, USER — getDefaultEnvironment in
17
+ * @modelcontextprotocol/sdk, merged under whatever we pass). The
18
+ * runner's own process env is never passed: it carries runner-internal
19
+ * credentials (STIGMER_TOKEN, STIGMER_RUNNER_HITL_SECRET,
20
+ * CURSOR_API_KEY, LLM provider keys) that no third-party MCP subprocess
21
+ * may see. A declared-empty env and an undeclared env are deliberately
22
+ * equivalent — both yield the SDK base environment.
23
+ *
13
24
  * The Cursor execution path does NOT use this manager — it passes MCP
14
25
  * configs directly to the Cursor SDK via toCursorMcpConfig(). This
15
- * manager is exclusively for LangGraph-based deep agent executions.
26
+ * manager serves LangGraph-based deep agent executions, and discovery
27
+ * (activities/discover-mcp-server.ts) builds its spawn config through
28
+ * toMcpClientConfig too.
16
29
  */
17
30
 
18
31
  import { MultiServerMCPClient } from "@langchain/mcp-adapters";
@@ -32,11 +45,18 @@ export function toMcpClientConfig(
32
45
  for (const server of servers) {
33
46
  if (server.connectionType === "stdio") {
34
47
  if (!server.command) continue;
48
+ if (!server.env || Object.keys(server.env).length === 0) {
49
+ console.log(
50
+ `[MCP] Server '${server.slug}' declares no env — its subprocess ` +
51
+ `starts with the minimal base environment only. Declare variables ` +
52
+ `in the McpServer's spec.env to pass them.`,
53
+ );
54
+ }
35
55
  config[server.slug] = {
36
56
  transport: "stdio",
37
57
  command: server.command,
38
58
  args: server.args ?? [],
39
- env: server.env ?? processEnvAsStrings(),
59
+ env: server.env,
40
60
  cwd: server.cwd,
41
61
  };
42
62
  } else if (server.connectionType === "http" || server.connectionType === "sse") {
@@ -91,23 +111,3 @@ export async function connectMcpServers(
91
111
 
92
112
  return { client, tools, serverToolMap };
93
113
  }
94
-
95
- /**
96
- * Snapshot of process.env as a string-only record (no undefined values).
97
- * Used as a fallback when an MCP server has no declared env — the
98
- * subprocess inherits the runner's full environment, matching standard
99
- * Unix child-process behavior.
100
- *
101
- * Without this, @modelcontextprotocol/sdk only passes a restricted
102
- * whitelist (HOME, PATH, USER, etc.) which drops platform variables
103
- * like STIGMER_SERVER_ADDRESS.
104
- */
105
- function processEnvAsStrings(): Record<string, string> {
106
- const env: Record<string, string> = {};
107
- for (const [key, value] of Object.entries(process.env)) {
108
- if (value !== undefined) {
109
- env[key] = value;
110
- }
111
- }
112
- return env;
113
- }
@@ -1,10 +1,13 @@
1
1
  /**
2
- * Model registry — provider lookup and economy-tier model derivation.
2
+ * Model registry — provider lookup, economy-tier model derivation, and
3
+ * model capability resolution.
3
4
  *
4
5
  * Fetches the model registry from the runner's control plane (see
5
6
  * registry-endpoint.ts for endpoint resolution — same endpoint as
6
7
  * model-pricing-data.ts) and uses `costTier` + `harness` fields to
7
- * dynamically resolve economy-tier models for extraction/summarization.
8
+ * dynamically resolve economy-tier models for extraction/summarization,
9
+ * plus `capabilities` catalog metadata for per-model capability lookups
10
+ * (getModelVisionCapability).
8
11
  */
9
12
 
10
13
  import { resolveModelRegistryUrl, buildRegistryHeaders } from "./registry-endpoint.js";
@@ -22,6 +25,14 @@ interface RegistryModel {
22
25
  costTier: string;
23
26
  harness: string;
24
27
  featured: boolean;
28
+ /**
29
+ * Tri-state vision capability from the registry's `capabilities` block.
30
+ * The registry serializes `capabilities` only for models whose capabilities
31
+ * have actually been assessed, so `undefined` means "never assessed" —
32
+ * deliberately distinct from an explicit `false` ("assessed as blind").
33
+ * Consumers gate only on the explicit `false` (see attachment-vision.ts).
34
+ */
35
+ visionCapability?: boolean;
25
36
  }
26
37
 
27
38
  let cache: { models: readonly RegistryModel[]; expiresAt: number } | null = null;
@@ -41,9 +52,20 @@ function parseRegistry(json: unknown): RegistryModel[] {
41
52
  costTier: (m.costTier as string) ?? "standard",
42
53
  harness: (m.harness as string) ?? "native",
43
54
  featured: !!m.featured,
55
+ visionCapability: parseVisionCapability(m.capabilities),
44
56
  }));
45
57
  }
46
58
 
59
+ /**
60
+ * Extract `capabilities.vision` preserving the tri-state: a missing or
61
+ * malformed `capabilities` block stays `undefined` (never coerced to false).
62
+ */
63
+ function parseVisionCapability(capabilities: unknown): boolean | undefined {
64
+ if (!capabilities || typeof capabilities !== "object") return undefined;
65
+ const vision = (capabilities as Record<string, unknown>).vision;
66
+ return typeof vision === "boolean" ? vision : undefined;
67
+ }
68
+
47
69
  async function fetchRegistry(): Promise<readonly RegistryModel[]> {
48
70
  const url = resolveModelRegistryUrl();
49
71
  const res = await fetch(url, { headers: buildRegistryHeaders() });
@@ -211,6 +233,32 @@ export async function resolveToApiModelId(registryId: string): Promise<string> {
211
233
  return entry.apiModelId ?? registryId;
212
234
  }
213
235
 
236
+ /**
237
+ * Look up a model's vision capability from the registry's `capabilities`
238
+ * catalog metadata. Returns the tri-state the vision policy expects
239
+ * (attachment-vision.ts): `false` only when the registry explicitly says the
240
+ * model cannot see images; `undefined` whenever the answer is unknown —
241
+ * capability never assessed, model not in the registry, registry
242
+ * unreachable, or no concrete model name (the Cursor harness's ""/"default"
243
+ * Auto pool). Callers gate on the explicit `false` only, so every unknown
244
+ * degrades to today's behavior instead of blocking images.
245
+ *
246
+ * Matches by registry `id` OR `apiModelId`: getDefaultModel() hands the
247
+ * deep-agent harness the provider API id, while executionConfig.modelName
248
+ * carries the registry id, so both forms arrive here.
249
+ */
250
+ export async function getModelVisionCapability(
251
+ modelName: string,
252
+ ): Promise<boolean | undefined> {
253
+ if (!modelName || modelName === "default") return undefined;
254
+
255
+ const registry = await getRegistry();
256
+ const entry = registry.find(
257
+ (m) => m.id === modelName || m.apiModelId === modelName,
258
+ );
259
+ return entry?.visionCapability;
260
+ }
261
+
214
262
  /** Exposed for testing — resets the in-memory cache. */
215
263
  export function _resetRegistryCache(): void {
216
264
  cache = null;