@stigmer/runner 3.8.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.
- package/README.md +2 -2
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +16 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +56 -4
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +15 -0
- package/dist/activities/execute-cursor/index.js +66 -11
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +14 -1
- package/dist/activities/execute-cursor/prompt-builder.js +11 -2
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +17 -0
- package/dist/activities/execute-deep-agent/attachment-injector.js +34 -4
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/hitl.d.ts +15 -7
- package/dist/activities/execute-deep-agent/hitl.js +6 -15
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +4 -1
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +17 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +13 -2
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +41 -2
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/runner-manager.js +14 -0
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +14 -0
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +10 -0
- package/dist/shared/artifact-storage.js +49 -8
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-vision.d.ts +203 -0
- package/dist/shared/attachment-vision.js +264 -0
- package/dist/shared/attachment-vision.js.map +1 -0
- package/package.json +3 -3
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +179 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +105 -3
- package/src/activities/execute-cursor/attachment-resolver.ts +90 -4
- package/src/activities/execute-cursor/index.ts +87 -13
- package/src/activities/execute-cursor/prompt-builder.ts +27 -2
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +185 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +13 -13
- package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +152 -0
- package/src/activities/execute-deep-agent/attachment-injector.ts +65 -4
- package/src/activities/execute-deep-agent/hitl.ts +14 -19
- package/src/activities/execute-deep-agent/index.ts +4 -5
- package/src/activities/execute-deep-agent/prompt-builder.ts +37 -2
- package/src/activities/execute-deep-agent/setup.ts +50 -2
- package/src/runner-manager.ts +19 -0
- package/src/runner.ts +19 -0
- package/src/shared/__tests__/artifact-storage.test.ts +76 -1
- package/src/shared/__tests__/attachment-vision.test.ts +323 -0
- package/src/shared/artifact-storage.ts +55 -8
- package/src/shared/attachment-vision.ts +373 -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 |
|
|
174
|
-
| `LOCAL_ARTIFACT_SERVE_URL` | Local storage | No | `http://localhost:7235` | Base URL the
|
|
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
|
|
package/dist/.build-fingerprint
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"hash":"
|
|
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 ||
|
|
74
|
+
const filename = safeInputName(attachment.filename || attachment.localPath);
|
|
75
|
+
let vision;
|
|
74
76
|
try {
|
|
75
|
-
await
|
|
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 ||
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
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,7 @@ 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";
|
|
50
51
|
import { publishPlanArtifact } from "../../shared/plan-artifact.js";
|
|
51
52
|
import { DeltaEnricher } from "./delta-enricher.js";
|
|
52
53
|
import { TodoTracker } from "./todo-tracker.js";
|
|
@@ -67,7 +68,7 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
|
|
|
67
68
|
import { resolveSkills } from "./skill-resolver.js";
|
|
68
69
|
import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
|
|
69
70
|
import { resolveAttachments } from "./attachment-resolver.js";
|
|
70
|
-
import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
|
|
71
|
+
import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
|
|
71
72
|
import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
|
|
72
73
|
import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
|
|
73
74
|
import { acquireWorkspaceLock, WorkspaceLockCancelledError, WorkspaceLockTimeoutError, } from "../../shared/workspace/workspace-lock.js";
|
|
@@ -631,14 +632,34 @@ turnSeq) {
|
|
|
631
632
|
setupTiming.mark("resolve_skills");
|
|
632
633
|
// Phase 5b: Resolve attachments (fail-hard — explicit user inputs; see
|
|
633
634
|
// attachment-resolver.ts). Downloads by storage key through the same
|
|
634
|
-
// 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);
|
|
635
639
|
const attachmentResults = await resolveAttachments(spec.attachments, {
|
|
636
640
|
sessionId,
|
|
637
641
|
primaryWorkspaceDir,
|
|
638
642
|
mode: config.mode,
|
|
639
643
|
storage: artifactStorage,
|
|
644
|
+
visionBudget,
|
|
640
645
|
});
|
|
641
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
|
+
}
|
|
642
663
|
setupTiming.mark("resolve_attachments");
|
|
643
664
|
// Phase 5b3: Exact-apply approved whole-file writes (HITL "what you approve
|
|
644
665
|
// is what gets applied"). The Cursor deny-only harness reinvokes the model,
|
|
@@ -917,6 +938,7 @@ turnSeq) {
|
|
|
917
938
|
workspaceDirs: blueprint.workspaceDirs,
|
|
918
939
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
919
940
|
attachmentPaths,
|
|
941
|
+
vision: visionPromptInfo,
|
|
920
942
|
pendingApprovals: adjudicatedApprovals,
|
|
921
943
|
appliedToolCallIds,
|
|
922
944
|
interactionMode,
|
|
@@ -932,6 +954,18 @@ turnSeq) {
|
|
|
932
954
|
const schemaStr = JSON.stringify(structuredOutputSchema, null, 2);
|
|
933
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.`;
|
|
934
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;
|
|
935
969
|
// Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
|
|
936
970
|
const promptChars = effectivePrompt.length;
|
|
937
971
|
const promptEstimatedTokens = Math.ceil(promptChars / 4);
|
|
@@ -1032,7 +1066,7 @@ turnSeq) {
|
|
|
1032
1066
|
// The stall watchdog is armed inside consumeCursorTurnStream (it needs the
|
|
1033
1067
|
// run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
|
|
1034
1068
|
// reset it and the activity's finally can stop it as a backstop.
|
|
1035
|
-
const run = await resolution.agent.send(effectivePrompt, {
|
|
1069
|
+
const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
|
|
1036
1070
|
onDelta: (event) => {
|
|
1037
1071
|
if (!turnFirstEventEmitted) {
|
|
1038
1072
|
turnFirstEventEmitted = true;
|
|
@@ -1349,7 +1383,10 @@ turnSeq) {
|
|
|
1349
1383
|
// poisoned-handle path leaked the fresh agent — it closed the stale one.)
|
|
1350
1384
|
resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
|
|
1351
1385
|
turnState.streamErrorMessage = undefined;
|
|
1352
|
-
|
|
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), {
|
|
1353
1390
|
onDelta: makeCursorTurnOnDelta(onDeltaDeps),
|
|
1354
1391
|
});
|
|
1355
1392
|
await consumeCursorTurnStream(retryRun, streamDeps);
|
|
@@ -1465,6 +1502,7 @@ turnSeq) {
|
|
|
1465
1502
|
workspaceDirs: blueprint.workspaceDirs,
|
|
1466
1503
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
1467
1504
|
attachmentPaths,
|
|
1505
|
+
vision: visionPromptInfo,
|
|
1468
1506
|
pendingApprovals: adjudicatedApprovals,
|
|
1469
1507
|
interactionMode,
|
|
1470
1508
|
// buildFromPlan was silently dropped here until T03 Sitting 3 —
|
|
@@ -1954,13 +1992,23 @@ import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
|
|
|
1954
1992
|
* 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
|
|
1955
1993
|
* agent after resume failure skills; no prior conversation to inherit)
|
|
1956
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
|
+
}
|
|
1957
2006
|
export function buildPrompt(input) {
|
|
1958
2007
|
const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachmentPaths, interactionMode, buildFromPlan, conversationCatchup, } = input;
|
|
1959
|
-
const isHitlReinvocation = approvalDecisions !== undefined && approvalDecisions.size > 0;
|
|
1960
2008
|
// HITL reinvocation: the agent is resumed, so its native context carries the
|
|
1961
2009
|
// prior conversation; the reinvocation prompt conveys the approval decisions
|
|
1962
2010
|
// (and which approved writes the runner already exact-applied).
|
|
1963
|
-
if (isHitlReinvocation) {
|
|
2011
|
+
if (isHitlReinvocation(approvalDecisions)) {
|
|
1964
2012
|
return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions, input.appliedToolCallIds);
|
|
1965
2013
|
}
|
|
1966
2014
|
// A successfully resumed agent carries its own conversation context via the
|
|
@@ -1969,15 +2017,21 @@ export function buildPrompt(input) {
|
|
|
1969
2017
|
// session's first turn: the interaction-mode prefix (a follow-up can switch
|
|
1970
2018
|
// Agent→Plan mid-session, and for Cursor the prompt is the only plan-mode
|
|
1971
2019
|
// enforcement), the implement-plan directive (the build turn is usually a
|
|
1972
|
-
// follow-up on a resumed agent),
|
|
1973
|
-
//
|
|
1974
|
-
//
|
|
1975
|
-
//
|
|
1976
|
-
//
|
|
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.
|
|
1977
2028
|
if (resolution.reason === "resumed_successfully") {
|
|
1978
2029
|
const prefixes = [
|
|
1979
2030
|
formatInteractionModePrefix(interactionMode),
|
|
1980
2031
|
formatImplementPlanSection(buildFromPlan, attachmentPaths),
|
|
2032
|
+
attachmentPaths.length > 0
|
|
2033
|
+
? formatInputFiles(attachmentPaths, input.vision)
|
|
2034
|
+
: undefined,
|
|
1981
2035
|
conversationCatchup !== undefined
|
|
1982
2036
|
? formatConversationCatchupSection(conversationCatchup)
|
|
1983
2037
|
: undefined,
|
|
@@ -1999,6 +2053,7 @@ export function buildPrompt(input) {
|
|
|
1999
2053
|
workspaceDirs,
|
|
2000
2054
|
workspaceFileRefs,
|
|
2001
2055
|
attachmentPaths,
|
|
2056
|
+
vision: input.vision,
|
|
2002
2057
|
interactionMode,
|
|
2003
2058
|
buildFromPlan,
|
|
2004
2059
|
contextBridge: input.contextBridge,
|