@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
package/README.md CHANGED
@@ -170,8 +170,8 @@ All configuration is environment-driven. Names and defaults below are verified a
170
170
  | Variable | Applies to | Required | Default | Purpose |
171
171
  |----------|-----------|----------|---------|---------|
172
172
  | `ARTIFACT_STORAGE_TYPE` | All | No | `proxy` if `STIGMER_PROXY_ENDPOINT` is set, else `local` | Selects the artifact backend: `local` (filesystem, served by the Stigmer backend) or `proxy` (presigned URLs via the proxy). An explicit value always wins. Storage follows transport, not execution location. |
173
- | `LOCAL_ARTIFACT_PATH` | Local storage | No | `/var/stigmer/artifacts` | Filesystem path for the local artifact backend. |
174
- | `LOCAL_ARTIFACT_SERVE_URL` | Local storage | No | `http://localhost:7235` | Base URL the local backend uses to construct artifact download URLs. |
173
+ | `LOCAL_ARTIFACT_PATH` | Local storage | No | `~/.stigmer/data/artifacts` | Filesystem root of the local artifact store. **Must equal the stigmer-server's `ARTIFACT_LOCAL_BASE_PATH`** — in local mode the server writes an artifact to `<ARTIFACT_LOCAL_BASE_PATH>/<key>` and the runner reads it from `<LOCAL_ARTIFACT_PATH>/<key>`, so a mismatch makes every storage-key attachment and offload fail to resolve. The defaults align out of the box; the CLI local daemon sets both explicitly. |
174
+ | `LOCAL_ARTIFACT_SERVE_URL` | Local storage | No | `http://localhost:7235` | Base URL of the server's artifact HTTP file server (its `ARTIFACT_HTTP_PORT`, default `GRPC_PORT + 1`). Used for blob downloads; the runner's own read-back goes straight to disk. |
175
175
 
176
176
  ### Claim-check (large Temporal payloads)
177
177
 
@@ -1 +1 @@
1
- {"hash":"6786feeec9bc110d","builtAt":"2026-08-08T15:50:14.283Z","fileCount":274}
1
+ {"hash":"a2b4e31e40541bd8","builtAt":"2026-08-10T13:04:56.756Z","fileCount":275}
@@ -30,10 +30,20 @@
30
30
  */
31
31
  import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
32
32
  import type { ArtifactStorage } from "../../shared/artifact-storage.js";
33
+ import { type VisionBudget, type VisionDegradedReason, type VisionImage } from "../../shared/attachment-vision.js";
33
34
  export interface ResolvedAttachment {
34
35
  filename: string;
35
36
  /** Workspace-relative path the agent reads (`.stigmer/inputs/{filename}`). */
36
37
  relativePath: string;
38
+ /** Present when the attachment was accepted into the turn's vision payload. */
39
+ vision?: VisionImage;
40
+ /**
41
+ * Present when the attachment was plausibly an image but could not ride
42
+ * inline (see {@link VisionDegradedReason}) — disclosed in the prompt so the
43
+ * agent never silently ignores a photo the user believes it can see.
44
+ * Attachments that were never image-shaped carry neither field.
45
+ */
46
+ visionDegraded?: VisionDegradedReason;
37
47
  }
38
48
  export interface AttachmentResolverOptions {
39
49
  sessionId: string;
@@ -45,6 +55,12 @@ export interface AttachmentResolverOptions {
45
55
  * then fails with an actionable error rather than a silent skip.
46
56
  */
47
57
  storage: ArtifactStorage | undefined;
58
+ /**
59
+ * The turn's vision selector (attachment-vision.ts owns all policy).
60
+ * `undefined` disables inline image delivery; file materialization is
61
+ * identical either way — vision is strictly additive.
62
+ */
63
+ visionBudget?: VisionBudget;
48
64
  }
49
65
  export declare class AttachmentResolutionError extends Error {
50
66
  readonly attachmentFilename: string;
@@ -28,8 +28,9 @@
28
28
  * found" class of failure). Any attachment that cannot be materialized aborts
29
29
  * the execution with an actionable error.
30
30
  */
31
- import { mkdir, copyFile, writeFile } from "node:fs/promises";
31
+ import { mkdir, copyFile, readFile, stat, writeFile } from "node:fs/promises";
32
32
  import { join, basename } from "node:path";
33
+ import { isVisionCandidate, } from "../../shared/attachment-vision.js";
33
34
  import { getPlatformDir } from "../../shared/workspace/platform-dir.js";
34
35
  import { ensureStigmerSymlink, STIGMER_LOCAL_STATE_DIR } from "../../shared/workspace/stigmer-link.js";
35
36
  const INPUTS_SUBDIR = "inputs";
@@ -70,9 +71,10 @@ export async function resolveAttachments(attachments, options) {
70
71
  async function resolveAttachment(attachment, inputsDir, options) {
71
72
  // Local-mode fast path: the file is already on this machine's disk.
72
73
  if (options.mode === "local" && attachment.localPath) {
73
- const filename = attachment.filename || basename(attachment.localPath);
74
+ const filename = safeInputName(attachment.filename || attachment.localPath);
75
+ let vision;
74
76
  try {
75
- await copyFile(attachment.localPath, join(inputsDir, filename));
77
+ vision = await materializeLocalFile(attachment, filename, inputsDir, options.visionBudget);
76
78
  }
77
79
  catch (err) {
78
80
  throw new AttachmentResolutionError(attachment.filename, `failed to copy local file '${attachment.localPath}': ` +
@@ -81,6 +83,7 @@ async function resolveAttachment(attachment, inputsDir, options) {
81
83
  return {
82
84
  filename,
83
85
  relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, filename),
86
+ ...visionOutcomeFields(vision),
84
87
  };
85
88
  }
86
89
  // Universal path: download the uploaded content by storage key.
@@ -91,7 +94,7 @@ async function resolveAttachment(attachment, inputsDir, options) {
91
94
  throw new AttachmentResolutionError(attachment.filename, `artifact storage is unavailable, so this attachment ` +
92
95
  `(key: ${attachment.storageKey}) cannot be downloaded`);
93
96
  }
94
- const filename = attachment.filename || basename(attachment.storageKey);
97
+ const filename = safeInputName(attachment.filename || attachment.storageKey);
95
98
  let content;
96
99
  try {
97
100
  content = await options.storage.download(attachment.storageKey);
@@ -101,9 +104,65 @@ async function resolveAttachment(attachment, inputsDir, options) {
101
104
  `${err instanceof Error ? err.message : String(err)}`);
102
105
  }
103
106
  await writeFile(join(inputsDir, filename), content);
107
+ // The bytes are already in hand for the file write — offer them to the
108
+ // vision budget before they go out of scope (the sniff decides eligibility;
109
+ // no pre-filter needed on this branch).
110
+ const vision = options.visionBudget?.offer(filename, attachment.contentType, content);
104
111
  return {
105
112
  filename,
106
113
  relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, filename),
114
+ ...visionOutcomeFields(vision),
107
115
  };
108
116
  }
117
+ /**
118
+ * Materialize a local-path attachment, reading the bytes only when they are
119
+ * plausibly a vision candidate within the per-image cap — a 25 MB PDF (or an
120
+ * oversized image, detected by stat) keeps the plain `copyFile` and never
121
+ * enters memory. Returns the vision outcome, or `undefined` when vision is
122
+ * disabled or the file is not a candidate.
123
+ */
124
+ async function materializeLocalFile(attachment, filename, inputsDir, visionBudget) {
125
+ const dest = join(inputsDir, filename);
126
+ if (!visionBudget || !isVisionCandidate(attachment.contentType, filename)) {
127
+ await copyFile(attachment.localPath, dest);
128
+ return undefined;
129
+ }
130
+ // Blind-model check BEFORE the size check: a blind model's oversized image
131
+ // must report the honest model_no_vision reason, never too_large's "resend
132
+ // smaller" advice — and an in-cap image needn't be read at all.
133
+ if (visionBudget.modelCannotSee()) {
134
+ await copyFile(attachment.localPath, dest);
135
+ return visionBudget.offerBlind();
136
+ }
137
+ const info = await stat(attachment.localPath);
138
+ if (visionBudget.exceedsImageCap(info.size)) {
139
+ await copyFile(attachment.localPath, dest);
140
+ return visionBudget.offerOversized();
141
+ }
142
+ const content = await readFile(attachment.localPath);
143
+ await writeFile(dest, content);
144
+ return visionBudget.offer(filename, attachment.contentType, content);
145
+ }
146
+ function visionOutcomeFields(outcome) {
147
+ if (outcome === undefined || outcome.kind === "skipped")
148
+ return {};
149
+ return outcome.kind === "accepted"
150
+ ? { vision: outcome.image }
151
+ : { visionDegraded: outcome.reason };
152
+ }
153
+ /**
154
+ * Reduce a caller-influenced name to a single, safe path component for writing
155
+ * under the inputs dir. The name (an attachment's original filename, or a
156
+ * storage key's tail) is untrusted — a value like `../../evil.md` would steer
157
+ * the write outside `.stigmer/inputs/`. Taking the basename strips any path
158
+ * structure; the residual `.`/`..`/empty cases (which basename does not strip)
159
+ * are rejected loudly so the write target is always a real file inside inputs.
160
+ */
161
+ function safeInputName(raw) {
162
+ const name = basename(raw);
163
+ if (name === "" || name === "." || name === "..") {
164
+ throw new AttachmentResolutionError(raw, `'${raw}' does not yield a usable filename for materialization`);
165
+ }
166
+ return name;
167
+ }
109
168
  //# sourceMappingURL=attachment-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-resolver.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/attachment-resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attachment-resolver.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/attachment-resolver.ts"],"names":[],"mappings":"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"}
@@ -53,6 +53,12 @@ export interface BuildPromptInput {
53
53
  workspaceDirs: string[];
54
54
  workspaceFileRefs: string[];
55
55
  attachmentPaths: string[];
56
+ /**
57
+ * Vision facts for the input-files section (T04): which attachments the
58
+ * model sees inline and which degraded to path-only. PER-TURN like the
59
+ * catchup — it rides both the enhanced prompt and a resumed turn's prefix.
60
+ */
61
+ vision?: import("./prompt-builder.js").VisionPromptInfo;
56
62
  pendingApprovals: import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb").PendingApproval[];
57
63
  /**
58
64
  * Approved whole-file writes the runner already applied itself (exact-apply).
@@ -114,4 +120,13 @@ export interface BuildPromptInput {
114
120
  * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
115
121
  * agent after resume failure skills; no prior conversation to inherit)
116
122
  */
123
+ /**
124
+ * Whether this activity invocation is a HITL re-invocation — the turn resumes
125
+ * an agent purely to convey approval decisions, carrying NO user message.
126
+ * The single discriminator for everything that must ride with the user's
127
+ * message and nothing else: the reinvocation prompt shape (below) and the
128
+ * vision payload (images accompany the message; a resumed agent already holds
129
+ * them in its native conversation).
130
+ */
131
+ export declare function isHitlReinvocation(approvalDecisions: Map<string, ApprovalAction> | undefined): approvalDecisions is Map<string, ApprovalAction>;
117
132
  export declare function buildPrompt(input: BuildPromptInput): string;
@@ -47,6 +47,8 @@ import { readSessionContext } from "../../shared/session-context.js";
47
47
  import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
48
48
  import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
49
49
  import { resolveUsableArtifactStorage, loadArtifactStorageConfig } from "../../shared/artifact-storage.js";
50
+ import { CURSOR_VISION_PROFILE, VisionBudget, toCursorImages, } from "../../shared/attachment-vision.js";
51
+ import { getModelVisionCapability } from "../../shared/model-registry.js";
50
52
  import { publishPlanArtifact } from "../../shared/plan-artifact.js";
51
53
  import { DeltaEnricher } from "./delta-enricher.js";
52
54
  import { TodoTracker } from "./todo-tracker.js";
@@ -67,7 +69,7 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
67
69
  import { resolveSkills } from "./skill-resolver.js";
68
70
  import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
69
71
  import { resolveAttachments } from "./attachment-resolver.js";
70
- import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
72
+ import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
71
73
  import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
72
74
  import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
73
75
  import { acquireWorkspaceLock, WorkspaceLockCancelledError, WorkspaceLockTimeoutError, } from "../../shared/workspace/workspace-lock.js";
@@ -631,14 +633,40 @@ turnSeq) {
631
633
  setupTiming.mark("resolve_skills");
632
634
  // Phase 5b: Resolve attachments (fail-hard — explicit user inputs; see
633
635
  // attachment-resolver.ts). Downloads by storage key through the same
634
- // artifactStorage resolved for status offload above.
636
+ // artifactStorage resolved for status offload above. The vision budget
637
+ // rides along so image attachments are selected for inline delivery while
638
+ // their bytes are already in hand (attachment-vision.ts owns all policy).
639
+ // The budget also carries the requested model's registry vision
640
+ // capability, looked up from the raw executionConfig name — full model
641
+ // validation (Phase 6) isn't needed for this, and ""/"default" (the Auto
642
+ // pool) resolves to unknown, which the policy treats as sighted.
643
+ const visionBudget = new VisionBudget(CURSOR_VISION_PROFILE, {
644
+ modelVision: await getModelVisionCapability(spec.executionConfig?.modelName ?? ""),
645
+ });
635
646
  const attachmentResults = await resolveAttachments(spec.attachments, {
636
647
  sessionId,
637
648
  primaryWorkspaceDir,
638
649
  mode: config.mode,
639
650
  storage: artifactStorage,
651
+ visionBudget,
640
652
  });
641
653
  const attachmentPaths = attachmentResults.map((a) => a.relativePath);
654
+ // Vision facts, derived once from the single resolution result: the
655
+ // images the model will see inline (in attachment order) and the ones
656
+ // that degraded to path-only, disclosed in the prompt.
657
+ const visionImages = attachmentResults.flatMap((a) => (a.vision ? [a.vision] : []));
658
+ const visionNotViewable = attachmentResults.flatMap((a) => a.visionDegraded ? [{ path: a.relativePath, reason: a.visionDegraded }] : []);
659
+ const visionPromptInfo = visionImages.length > 0 || visionNotViewable.length > 0
660
+ ? {
661
+ inlineFilenames: visionImages.map((v) => v.filename),
662
+ notViewable: visionNotViewable,
663
+ }
664
+ : undefined;
665
+ if (visionPromptInfo) {
666
+ console.log(`[attachment-vision] execution=${executionId} inline=${visionImages.length} ` +
667
+ `(${visionImages.reduce((n, v) => n + v.byteSize, 0)} bytes) ` +
668
+ `degraded=${JSON.stringify(visionNotViewable.map((d) => `${d.path}:${d.reason}`))}`);
669
+ }
642
670
  setupTiming.mark("resolve_attachments");
643
671
  // Phase 5b3: Exact-apply approved whole-file writes (HITL "what you approve
644
672
  // is what gets applied"). The Cursor deny-only harness reinvokes the model,
@@ -917,6 +945,7 @@ turnSeq) {
917
945
  workspaceDirs: blueprint.workspaceDirs,
918
946
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
919
947
  attachmentPaths,
948
+ vision: visionPromptInfo,
920
949
  pendingApprovals: adjudicatedApprovals,
921
950
  appliedToolCallIds,
922
951
  interactionMode,
@@ -932,6 +961,18 @@ turnSeq) {
932
961
  const schemaStr = JSON.stringify(structuredOutputSchema, null, 2);
933
962
  effectivePrompt += `\n\n---\nCRITICAL OUTPUT REQUIREMENT:\nYour final response MUST be a single valid JSON object (no markdown, no commentary, no code fences) that matches this schema:\n${schemaStr}\n\nRespond with ONLY the JSON object. Nothing else.`;
934
963
  }
964
+ // Phase 10a1: The turn's vision payload. The invariant is "images
965
+ // accompany the user's turn message — where the message goes, they go":
966
+ // every send that delivers this turn's message carries them (the primary
967
+ // send and both fresh-agent recovery retries, whose empty conversations
968
+ // genuinely need the re-send), while a HITL re-invocation — whose prompt
969
+ // carries no user message and whose resumed agent already holds the
970
+ // images in its native conversation — carries none. Computed ONCE here so
971
+ // all send sites agree by construction.
972
+ const turnImages = isHitlReinvocation(approvalDecisions)
973
+ ? []
974
+ : toCursorImages(visionImages);
975
+ const toSendMessage = (sendPrompt) => turnImages.length > 0 ? { text: sendPrompt, images: turnImages } : sendPrompt;
935
976
  // Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
936
977
  const promptChars = effectivePrompt.length;
937
978
  const promptEstimatedTokens = Math.ceil(promptChars / 4);
@@ -1032,7 +1073,7 @@ turnSeq) {
1032
1073
  // The stall watchdog is armed inside consumeCursorTurnStream (it needs the
1033
1074
  // run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
1034
1075
  // reset it and the activity's finally can stop it as a backstop.
1035
- const run = await resolution.agent.send(effectivePrompt, {
1076
+ const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
1036
1077
  onDelta: (event) => {
1037
1078
  if (!turnFirstEventEmitted) {
1038
1079
  turnFirstEventEmitted = true;
@@ -1349,7 +1390,10 @@ turnSeq) {
1349
1390
  // poisoned-handle path leaked the fresh agent — it closed the stale one.)
1350
1391
  resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
1351
1392
  turnState.streamErrorMessage = undefined;
1352
- const retryRun = await freshAgent.send(retryPrompt, {
1393
+ // The retry carries the turn's images too (same toSendMessage): the
1394
+ // fresh agent's conversation is empty, so skipping them here would
1395
+ // silently lose the user's photo on a recovered turn.
1396
+ const retryRun = await freshAgent.send(toSendMessage(retryPrompt), {
1353
1397
  onDelta: makeCursorTurnOnDelta(onDeltaDeps),
1354
1398
  });
1355
1399
  await consumeCursorTurnStream(retryRun, streamDeps);
@@ -1465,6 +1509,7 @@ turnSeq) {
1465
1509
  workspaceDirs: blueprint.workspaceDirs,
1466
1510
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
1467
1511
  attachmentPaths,
1512
+ vision: visionPromptInfo,
1468
1513
  pendingApprovals: adjudicatedApprovals,
1469
1514
  interactionMode,
1470
1515
  // buildFromPlan was silently dropped here until T03 Sitting 3 —
@@ -1954,13 +1999,23 @@ import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
1954
1999
  * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
1955
2000
  * agent after resume failure skills; no prior conversation to inherit)
1956
2001
  */
2002
+ /**
2003
+ * Whether this activity invocation is a HITL re-invocation — the turn resumes
2004
+ * an agent purely to convey approval decisions, carrying NO user message.
2005
+ * The single discriminator for everything that must ride with the user's
2006
+ * message and nothing else: the reinvocation prompt shape (below) and the
2007
+ * vision payload (images accompany the message; a resumed agent already holds
2008
+ * them in its native conversation).
2009
+ */
2010
+ export function isHitlReinvocation(approvalDecisions) {
2011
+ return approvalDecisions !== undefined && approvalDecisions.size > 0;
2012
+ }
1957
2013
  export function buildPrompt(input) {
1958
2014
  const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachmentPaths, interactionMode, buildFromPlan, conversationCatchup, } = input;
1959
- const isHitlReinvocation = approvalDecisions !== undefined && approvalDecisions.size > 0;
1960
2015
  // HITL reinvocation: the agent is resumed, so its native context carries the
1961
2016
  // prior conversation; the reinvocation prompt conveys the approval decisions
1962
2017
  // (and which approved writes the runner already exact-applied).
1963
- if (isHitlReinvocation) {
2018
+ if (isHitlReinvocation(approvalDecisions)) {
1964
2019
  return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions, input.appliedToolCallIds);
1965
2020
  }
1966
2021
  // A successfully resumed agent carries its own conversation context via the
@@ -1969,15 +2024,21 @@ export function buildPrompt(input) {
1969
2024
  // session's first turn: the interaction-mode prefix (a follow-up can switch
1970
2025
  // Agent→Plan mid-session, and for Cursor the prompt is the only plan-mode
1971
2026
  // enforcement), the implement-plan directive (the build turn is usually a
1972
- // follow-up on a resumed agent), and the conversation catchup (handback
1973
- // ALWAYS lands mid-session on a resumed agent this prefix is the property
1974
- // the metadata lane structurally cannot deliver, cloud DD-006). Catchup
1975
- // last: it is context, and context sits closest to the task (the enhanced
1976
- // prompt's own ordering doctrine).
2027
+ // follow-up on a resumed agent), THIS turn's attachments (spec.attachments
2028
+ // is per-execution a file sent on a follow-up turn materializes for this
2029
+ // turn and would otherwise never be announced at all), and the conversation
2030
+ // catchup (handback ALWAYS lands mid-session on a resumed agent this
2031
+ // prefix is the property the metadata lane structurally cannot deliver,
2032
+ // cloud DD-006). Catchup last: it is context, and context sits closest to
2033
+ // the task (the enhanced prompt's own ordering doctrine); the input files
2034
+ // precede it because they are this turn's payload, not background.
1977
2035
  if (resolution.reason === "resumed_successfully") {
1978
2036
  const prefixes = [
1979
2037
  formatInteractionModePrefix(interactionMode),
1980
2038
  formatImplementPlanSection(buildFromPlan, attachmentPaths),
2039
+ attachmentPaths.length > 0
2040
+ ? formatInputFiles(attachmentPaths, input.vision)
2041
+ : undefined,
1981
2042
  conversationCatchup !== undefined
1982
2043
  ? formatConversationCatchupSection(conversationCatchup)
1983
2044
  : undefined,
@@ -1999,6 +2060,7 @@ export function buildPrompt(input) {
1999
2060
  workspaceDirs,
2000
2061
  workspaceFileRefs,
2001
2062
  attachmentPaths,
2063
+ vision: input.vision,
2002
2064
  interactionMode,
2003
2065
  buildFromPlan,
2004
2066
  contextBridge: input.contextBridge,