@stigmer/runner 3.7.0 → 3.9.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 (76) 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 +56 -4
  5. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  6. package/dist/activities/execute-cursor/index.d.ts +27 -0
  7. package/dist/activities/execute-cursor/index.js +101 -10
  8. package/dist/activities/execute-cursor/index.js.map +1 -1
  9. package/dist/activities/execute-cursor/prompt-builder.d.ts +25 -1
  10. package/dist/activities/execute-cursor/prompt-builder.js +22 -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/mcp-gate.d.ts +28 -0
  21. package/dist/activities/execute-deep-agent/mcp-gate.js +22 -0
  22. package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -0
  23. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +28 -0
  24. package/dist/activities/execute-deep-agent/prompt-builder.js +29 -2
  25. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/setup.js +71 -6
  27. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  28. package/dist/runner-manager.js +14 -0
  29. package/dist/runner-manager.js.map +1 -1
  30. package/dist/runner.js +14 -0
  31. package/dist/runner.js.map +1 -1
  32. package/dist/shared/artifact-storage.d.ts +10 -0
  33. package/dist/shared/artifact-storage.js +49 -8
  34. package/dist/shared/artifact-storage.js.map +1 -1
  35. package/dist/shared/attachment-vision.d.ts +203 -0
  36. package/dist/shared/attachment-vision.js +264 -0
  37. package/dist/shared/attachment-vision.js.map +1 -0
  38. package/dist/shared/channel-attachment.d.ts +3 -1
  39. package/dist/shared/channel-attachment.js +3 -1
  40. package/dist/shared/channel-attachment.js.map +1 -1
  41. package/dist/shared/conversation-attachment.d.ts +81 -0
  42. package/dist/shared/conversation-attachment.js +102 -0
  43. package/dist/shared/conversation-attachment.js.map +1 -0
  44. package/dist/shared/conversation-catchup.d.ts +33 -0
  45. package/dist/shared/conversation-catchup.js +53 -0
  46. package/dist/shared/conversation-catchup.js.map +1 -0
  47. package/package.json +3 -3
  48. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +179 -0
  49. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +183 -2
  50. package/src/activities/execute-cursor/attachment-resolver.ts +90 -4
  51. package/src/activities/execute-cursor/index.ts +143 -11
  52. package/src/activities/execute-cursor/prompt-builder.ts +50 -2
  53. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +185 -0
  54. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +13 -13
  55. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +42 -0
  56. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +39 -1
  57. package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +152 -0
  58. package/src/activities/execute-deep-agent/attachment-injector.ts +65 -4
  59. package/src/activities/execute-deep-agent/hitl.ts +14 -19
  60. package/src/activities/execute-deep-agent/index.ts +4 -5
  61. package/src/activities/execute-deep-agent/mcp-gate.ts +37 -0
  62. package/src/activities/execute-deep-agent/prompt-builder.ts +59 -4
  63. package/src/activities/execute-deep-agent/setup.ts +90 -6
  64. package/src/runner-manager.ts +19 -0
  65. package/src/runner.ts +19 -0
  66. package/src/shared/__tests__/artifact-storage.test.ts +76 -1
  67. package/src/shared/__tests__/attachment-vision.test.ts +323 -0
  68. package/src/shared/__tests__/channel-attachment.test.ts +3 -3
  69. package/src/shared/__tests__/conversation-attachment.test.ts +138 -0
  70. package/src/shared/__tests__/conversation-catchup.test.ts +70 -0
  71. package/src/shared/__tests__/synthesized-attachment.test.ts +120 -0
  72. package/src/shared/artifact-storage.ts +55 -8
  73. package/src/shared/attachment-vision.ts +373 -0
  74. package/src/shared/channel-attachment.ts +3 -1
  75. package/src/shared/conversation-attachment.ts +115 -0
  76. package/src/shared/conversation-catchup.ts +60 -0
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":"f59012b1fb99fae0","builtAt":"2026-08-07T07:55:08.387Z","fileCount":271}
1
+ {"hash":"46c0fe996f031eda","builtAt":"2026-08-09T21:11:53.007Z","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,58 @@ 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
+ const info = await stat(attachment.localPath);
131
+ if (visionBudget.exceedsImageCap(info.size)) {
132
+ await copyFile(attachment.localPath, dest);
133
+ return visionBudget.offerOversized();
134
+ }
135
+ const content = await readFile(attachment.localPath);
136
+ await writeFile(dest, content);
137
+ return visionBudget.offer(filename, attachment.contentType, content);
138
+ }
139
+ function visionOutcomeFields(outcome) {
140
+ if (outcome === undefined || outcome.kind === "skipped")
141
+ return {};
142
+ return outcome.kind === "accepted"
143
+ ? { vision: outcome.image }
144
+ : { visionDegraded: outcome.reason };
145
+ }
146
+ /**
147
+ * Reduce a caller-influenced name to a single, safe path component for writing
148
+ * under the inputs dir. The name (an attachment's original filename, or a
149
+ * storage key's tail) is untrusted — a value like `../../evil.md` would steer
150
+ * the write outside `.stigmer/inputs/`. Taking the basename strips any path
151
+ * structure; the residual `.`/`..`/empty cases (which basename does not strip)
152
+ * are rejected loudly so the write target is always a real file inside inputs.
153
+ */
154
+ function safeInputName(raw) {
155
+ const name = basename(raw);
156
+ if (name === "" || name === "." || name === "..") {
157
+ throw new AttachmentResolutionError(raw, `'${raw}' does not yield a usable filename for materialization`);
158
+ }
159
+ return name;
160
+ }
109
161
  //# 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).
@@ -85,6 +91,18 @@ export interface BuildPromptInput {
85
91
  * turn.
86
92
  */
87
93
  sessionContext?: string;
94
+ /**
95
+ * Conversation catchup from the execution spec's `conversation_catchup`
96
+ * (cloud DD-006): what happened on the channel conversation that the
97
+ * agent has not seen. PER-TURN, so unlike the three standing values
98
+ * above it rides BOTH prompt paths — the enhanced prompt and a resumed
99
+ * turn's prefix (the `interaction_mode` shape). Handback lands
100
+ * mid-session on a resumed agent: the resumed path is the one that
101
+ * matters. Once delivered, the digest persists in the agent's own
102
+ * conversation store; the next turn's field is composed fresh and is
103
+ * usually blank.
104
+ */
105
+ conversationCatchup?: string;
88
106
  }
89
107
  /**
90
108
  * Select and build the appropriate prompt based on resolution reason and
@@ -102,4 +120,13 @@ export interface BuildPromptInput {
102
120
  * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
103
121
  * agent after resume failure skills; no prior conversation to inherit)
104
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>;
105
132
  export declare function buildPrompt(input: BuildPromptInput): string;
@@ -40,12 +40,14 @@ import { MessageAccumulator, cancelInProgressSubAgentProtos, collapseRedundantTo
40
40
  import { utcTimestamp, persistStatus, reportSetupProgress, slimStatus } from "../../shared/status.js";
41
41
  import { TimingRecorder, emitTimingLog } from "../../shared/cold-start-timing.js";
42
42
  import { readContextBridge } from "../../shared/context-bridge.js";
43
+ import { readConversationCatchup } from "../../shared/conversation-catchup.js";
43
44
  import { readSenderIdentity } from "../../shared/sender-identity.js";
44
45
  import { injectCallerIdentityEnv, resolveCallerIdentity, } from "../../shared/caller-identity.js";
45
46
  import { readSessionContext } from "../../shared/session-context.js";
46
47
  import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
47
48
  import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
48
49
  import { resolveUsableArtifactStorage, loadArtifactStorageConfig } from "../../shared/artifact-storage.js";
50
+ import { CURSOR_VISION_PROFILE, VisionBudget, toCursorImages, } from "../../shared/attachment-vision.js";
49
51
  import { publishPlanArtifact } from "../../shared/plan-artifact.js";
50
52
  import { DeltaEnricher } from "./delta-enricher.js";
51
53
  import { TodoTracker } from "./todo-tracker.js";
@@ -55,6 +57,7 @@ import { resolveMcpServers, toCursorMcpConfig, validateMcpServerEnv } from "./mc
55
57
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
56
58
  import { synthesizeDatastoreAttachment } from "../../shared/datastore-attachment.js";
57
59
  import { discoverChannelMessaging, synthesizeChannelAttachment, } from "../../shared/channel-attachment.js";
60
+ import { readChannelConversationId, synthesizeConversationAttachment, } from "../../shared/conversation-attachment.js";
58
61
  import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
59
62
  import { mergeApprovalPolicies } from "./approval-policy.js";
60
63
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
@@ -65,7 +68,7 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
65
68
  import { resolveSkills } from "./skill-resolver.js";
66
69
  import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
67
70
  import { resolveAttachments } from "./attachment-resolver.js";
68
- import { buildEnhancedPrompt, buildReinvocationPrompt, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
71
+ import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
69
72
  import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
70
73
  import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
71
74
  import { acquireWorkspaceLock, WorkspaceLockCancelledError, WorkspaceLockTimeoutError, } from "../../shared/workspace/workspace-lock.js";
@@ -578,6 +581,25 @@ turnSeq) {
578
581
  };
579
582
  }
580
583
  }
584
+ // Phase 4a4: Synthesize the conversation participation attachment
585
+ // (channel-conversations DD-008 D-c) — the third sibling. The
586
+ // channel-id session label IS the attachment decision (stamped
587
+ // server-side on every channel session; a free local read, unlike
588
+ // the channels discovery RPC above). HTTP-only: synthesize answers
589
+ // undefined with no bridge endpoint by design (see
590
+ // shared/conversation-attachment.ts).
591
+ const conversationAttachment = synthesizeConversationAttachment(readChannelConversationId(session.metadata?.labels), {
592
+ bridgeEndpoint: config.mcpBridgeEndpoint,
593
+ credential: attachmentCredential,
594
+ backendEndpoint: config.stigmerBackendEndpoint,
595
+ });
596
+ if (conversationAttachment) {
597
+ const resolvedServers = injectSynthesizedAttachment(mcpResolution.resolvedServers, conversationAttachment, "conversation participation");
598
+ mcpResolution = {
599
+ resolvedServers,
600
+ cursorConfig: toCursorMcpConfig(resolvedServers),
601
+ };
602
+ }
581
603
  const mcpConfig = mcpResolution.cursorConfig;
582
604
  // Phase 4b: Merge approval policies from all layers.
583
605
  //
@@ -610,14 +632,34 @@ turnSeq) {
610
632
  setupTiming.mark("resolve_skills");
611
633
  // Phase 5b: Resolve attachments (fail-hard — explicit user inputs; see
612
634
  // attachment-resolver.ts). Downloads by storage key through the same
613
- // artifactStorage resolved for status offload above.
635
+ // artifactStorage resolved for status offload above. The vision budget
636
+ // rides along so image attachments are selected for inline delivery while
637
+ // their bytes are already in hand (attachment-vision.ts owns all policy).
638
+ const visionBudget = new VisionBudget(CURSOR_VISION_PROFILE);
614
639
  const attachmentResults = await resolveAttachments(spec.attachments, {
615
640
  sessionId,
616
641
  primaryWorkspaceDir,
617
642
  mode: config.mode,
618
643
  storage: artifactStorage,
644
+ visionBudget,
619
645
  });
620
646
  const attachmentPaths = attachmentResults.map((a) => a.relativePath);
647
+ // Vision facts, derived once from the single resolution result: the
648
+ // images the model will see inline (in attachment order) and the ones
649
+ // that degraded to path-only, disclosed in the prompt.
650
+ const visionImages = attachmentResults.flatMap((a) => (a.vision ? [a.vision] : []));
651
+ const visionNotViewable = attachmentResults.flatMap((a) => a.visionDegraded ? [{ path: a.relativePath, reason: a.visionDegraded }] : []);
652
+ const visionPromptInfo = visionImages.length > 0 || visionNotViewable.length > 0
653
+ ? {
654
+ inlineFilenames: visionImages.map((v) => v.filename),
655
+ notViewable: visionNotViewable,
656
+ }
657
+ : undefined;
658
+ if (visionPromptInfo) {
659
+ console.log(`[attachment-vision] execution=${executionId} inline=${visionImages.length} ` +
660
+ `(${visionImages.reduce((n, v) => n + v.byteSize, 0)} bytes) ` +
661
+ `degraded=${JSON.stringify(visionNotViewable.map((d) => `${d.path}:${d.reason}`))}`);
662
+ }
621
663
  setupTiming.mark("resolve_attachments");
622
664
  // Phase 5b3: Exact-apply approved whole-file writes (HITL "what you approve
623
665
  // is what gets applied"). The Cursor deny-only harness reinvokes the model,
@@ -896,6 +938,7 @@ turnSeq) {
896
938
  workspaceDirs: blueprint.workspaceDirs,
897
939
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
898
940
  attachmentPaths,
941
+ vision: visionPromptInfo,
899
942
  pendingApprovals: adjudicatedApprovals,
900
943
  appliedToolCallIds,
901
944
  interactionMode,
@@ -903,6 +946,7 @@ turnSeq) {
903
946
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
904
947
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
905
948
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
949
+ conversationCatchup: readConversationCatchup(spec.conversationCatchup),
906
950
  });
907
951
  // Phase 10a: Inject structured output instruction for Cursor harness
908
952
  let effectivePrompt = prompt;
@@ -910,6 +954,18 @@ turnSeq) {
910
954
  const schemaStr = JSON.stringify(structuredOutputSchema, null, 2);
911
955
  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.`;
912
956
  }
957
+ // Phase 10a1: The turn's vision payload. The invariant is "images
958
+ // accompany the user's turn message — where the message goes, they go":
959
+ // every send that delivers this turn's message carries them (the primary
960
+ // send and both fresh-agent recovery retries, whose empty conversations
961
+ // genuinely need the re-send), while a HITL re-invocation — whose prompt
962
+ // carries no user message and whose resumed agent already holds the
963
+ // images in its native conversation — carries none. Computed ONCE here so
964
+ // all send sites agree by construction.
965
+ const turnImages = isHitlReinvocation(approvalDecisions)
966
+ ? []
967
+ : toCursorImages(visionImages);
968
+ const toSendMessage = (sendPrompt) => turnImages.length > 0 ? { text: sendPrompt, images: turnImages } : sendPrompt;
913
969
  // Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
914
970
  const promptChars = effectivePrompt.length;
915
971
  const promptEstimatedTokens = Math.ceil(promptChars / 4);
@@ -1010,7 +1066,7 @@ turnSeq) {
1010
1066
  // The stall watchdog is armed inside consumeCursorTurnStream (it needs the
1011
1067
  // run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
1012
1068
  // reset it and the activity's finally can stop it as a backstop.
1013
- const run = await resolution.agent.send(effectivePrompt, {
1069
+ const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
1014
1070
  onDelta: (event) => {
1015
1071
  if (!turnFirstEventEmitted) {
1016
1072
  turnFirstEventEmitted = true;
@@ -1327,7 +1383,10 @@ turnSeq) {
1327
1383
  // poisoned-handle path leaked the fresh agent — it closed the stale one.)
1328
1384
  resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
1329
1385
  turnState.streamErrorMessage = undefined;
1330
- const retryRun = await freshAgent.send(retryPrompt, {
1386
+ // The retry carries the turn's images too (same toSendMessage): the
1387
+ // fresh agent's conversation is empty, so skipping them here would
1388
+ // silently lose the user's photo on a recovered turn.
1389
+ const retryRun = await freshAgent.send(toSendMessage(retryPrompt), {
1331
1390
  onDelta: makeCursorTurnOnDelta(onDeltaDeps),
1332
1391
  });
1333
1392
  await consumeCursorTurnStream(retryRun, streamDeps);
@@ -1443,11 +1502,18 @@ turnSeq) {
1443
1502
  workspaceDirs: blueprint.workspaceDirs,
1444
1503
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
1445
1504
  attachmentPaths,
1505
+ vision: visionPromptInfo,
1446
1506
  pendingApprovals: adjudicatedApprovals,
1447
1507
  interactionMode,
1508
+ // buildFromPlan was silently dropped here until T03 Sitting 3 —
1509
+ // a build turn that hit handle recovery lost its directive. The
1510
+ // fresh prompt must carry every per-turn directive the original
1511
+ // did.
1512
+ buildFromPlan,
1448
1513
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
1449
1514
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1450
1515
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1516
+ conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1451
1517
  });
1452
1518
  console.log(`ExecuteCursor retry with fresh agent: execution=${executionId}, ` +
1453
1519
  `newAgentId=${freshAgent.agentId}`);
@@ -1926,26 +1992,49 @@ import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
1926
1992
  * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
1927
1993
  * agent after resume failure skills; no prior conversation to inherit)
1928
1994
  */
1995
+ /**
1996
+ * Whether this activity invocation is a HITL re-invocation — the turn resumes
1997
+ * an agent purely to convey approval decisions, carrying NO user message.
1998
+ * The single discriminator for everything that must ride with the user's
1999
+ * message and nothing else: the reinvocation prompt shape (below) and the
2000
+ * vision payload (images accompany the message; a resumed agent already holds
2001
+ * them in its native conversation).
2002
+ */
2003
+ export function isHitlReinvocation(approvalDecisions) {
2004
+ return approvalDecisions !== undefined && approvalDecisions.size > 0;
2005
+ }
1929
2006
  export function buildPrompt(input) {
1930
- const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachmentPaths, interactionMode, buildFromPlan, } = input;
1931
- const isHitlReinvocation = approvalDecisions !== undefined && approvalDecisions.size > 0;
2007
+ const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachmentPaths, interactionMode, buildFromPlan, conversationCatchup, } = input;
1932
2008
  // HITL reinvocation: the agent is resumed, so its native context carries the
1933
2009
  // prior conversation; the reinvocation prompt conveys the approval decisions
1934
2010
  // (and which approved writes the runner already exact-applied).
1935
- if (isHitlReinvocation) {
2011
+ if (isHitlReinvocation(approvalDecisions)) {
1936
2012
  return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions, input.appliedToolCallIds);
1937
2013
  }
1938
2014
  // A successfully resumed agent carries its own conversation context via the
1939
2015
  // SDK's native store — send the raw user message with no preamble. The
1940
- // exceptions are the per-EXECUTION directives, which never inherit from the
2016
+ // exceptions are the per-EXECUTION values, which never inherit from the
1941
2017
  // session's first turn: the interaction-mode prefix (a follow-up can switch
1942
2018
  // Agent→Plan mid-session, and for Cursor the prompt is the only plan-mode
1943
- // enforcement) and the implement-plan directive (the build turn is usually
1944
- // a follow-up on a resumed agent).
2019
+ // enforcement), the implement-plan directive (the build turn is usually a
2020
+ // follow-up on a resumed agent), THIS turn's attachments (spec.attachments
2021
+ // is per-execution — a file sent on a follow-up turn materializes for this
2022
+ // turn and would otherwise never be announced at all), and the conversation
2023
+ // catchup (handback ALWAYS lands mid-session on a resumed agent — this
2024
+ // prefix is the property the metadata lane structurally cannot deliver,
2025
+ // cloud DD-006). Catchup last: it is context, and context sits closest to
2026
+ // the task (the enhanced prompt's own ordering doctrine); the input files
2027
+ // precede it because they are this turn's payload, not background.
1945
2028
  if (resolution.reason === "resumed_successfully") {
1946
2029
  const prefixes = [
1947
2030
  formatInteractionModePrefix(interactionMode),
1948
2031
  formatImplementPlanSection(buildFromPlan, attachmentPaths),
2032
+ attachmentPaths.length > 0
2033
+ ? formatInputFiles(attachmentPaths, input.vision)
2034
+ : undefined,
2035
+ conversationCatchup !== undefined
2036
+ ? formatConversationCatchupSection(conversationCatchup)
2037
+ : undefined,
1949
2038
  ].filter((p) => p !== undefined);
1950
2039
  return prefixes.length > 0
1951
2040
  ? [...prefixes, userMessage].join("\n\n")
@@ -1964,11 +2053,13 @@ export function buildPrompt(input) {
1964
2053
  workspaceDirs,
1965
2054
  workspaceFileRefs,
1966
2055
  attachmentPaths,
2056
+ vision: input.vision,
1967
2057
  interactionMode,
1968
2058
  buildFromPlan,
1969
2059
  contextBridge: input.contextBridge,
1970
2060
  senderIdentity: input.senderIdentity,
1971
2061
  sessionContext: input.sessionContext,
2062
+ conversationCatchup,
1972
2063
  });
1973
2064
  }
1974
2065
  // ---------------------------------------------------------------------------