@stigmer/runner 3.11.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +6 -4
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/emit-event.d.ts +14 -2
- package/dist/activities/emit-event.js +52 -17
- package/dist/activities/emit-event.js.map +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +11 -0
- package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
- package/dist/activities/execute-cursor/blueprint-resolver.js +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
- package/dist/activities/execute-cursor/error-classifier.js +34 -3
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +52 -12
- package/dist/activities/execute-cursor/index.js +141 -67
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
- package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
- package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -11
- package/dist/activities/execute-cursor/prompt-builder.js +57 -10
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
- package/dist/activities/execute-cursor/turn-recovery.js +193 -0
- package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +11 -0
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +0 -15
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
- package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
- package/dist/activities/execute-deep-agent/setup.js +22 -49
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
- package/dist/activities/execute-deep-agent/shell-env.js +7 -9
- package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +7 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +8 -5
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/run-command.d.ts +5 -2
- package/dist/activities/run-command.js +22 -10
- package/dist/activities/run-command.js.map +1 -1
- package/dist/activities/run-env.d.ts +40 -0
- package/dist/activities/run-env.js +66 -0
- package/dist/activities/run-env.js.map +1 -0
- package/dist/bootstrap.js +5 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +83 -26
- package/dist/client/stigmer-client.js +88 -24
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.js.map +1 -1
- package/dist/encryption/config.d.ts +41 -9
- package/dist/encryption/config.js +63 -23
- package/dist/encryption/config.js.map +1 -1
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/path-normalization.d.ts +57 -0
- package/dist/middleware/path-normalization.js +109 -0
- package/dist/middleware/path-normalization.js.map +1 -0
- package/dist/middleware/types.d.ts +8 -0
- package/dist/payload-codecs.d.ts +2 -1
- package/dist/payload-codecs.js +7 -3
- package/dist/payload-codecs.js.map +1 -1
- package/dist/runner-manager.js +17 -3
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +7 -3
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +34 -1
- package/dist/shared/artifact-storage.js +53 -10
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/attachment-download-urls.d.ts +62 -0
- package/dist/shared/attachment-download-urls.js +79 -0
- package/dist/shared/attachment-download-urls.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +4 -20
- package/dist/shared/checkpointer/http-saver.js +15 -57
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/conversation-attachment.d.ts +2 -3
- package/dist/shared/conversation-attachment.js +2 -3
- package/dist/shared/conversation-attachment.js.map +1 -1
- package/dist/shared/conversation-catchup.d.ts +9 -2
- package/dist/shared/conversation-catchup.js +39 -6
- package/dist/shared/conversation-catchup.js.map +1 -1
- package/dist/shared/grpc-retry.d.ts +5 -1
- package/dist/shared/grpc-retry.js +5 -1
- package/dist/shared/grpc-retry.js.map +1 -1
- package/dist/shared/http-retry.d.ts +51 -9
- package/dist/shared/http-retry.js +60 -9
- package/dist/shared/http-retry.js.map +1 -1
- package/dist/shared/mcp-manager.d.ts +8 -4
- package/dist/shared/mcp-manager.js +30 -4
- package/dist/shared/mcp-manager.js.map +1 -1
- package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
- package/dist/shared/mcp-schema-sanitizer.js +197 -0
- package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
- package/dist/shared/mcp-transport-guard.d.ts +2 -2
- package/dist/shared/mcp-transport-guard.js +2 -2
- package/dist/shared/model-client.d.ts +6 -0
- package/dist/shared/model-client.js +14 -3
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-pricing-data.d.ts +2 -0
- package/dist/shared/model-pricing-data.js +13 -3
- package/dist/shared/model-pricing-data.js.map +1 -1
- package/dist/shared/model-registry.js +3 -2
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +7 -0
- package/dist/shared/plan-mode-permissions.js +7 -0
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- package/dist/shared/registry-endpoint.d.ts +20 -0
- package/dist/shared/registry-endpoint.js +25 -0
- package/dist/shared/registry-endpoint.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +24 -0
- package/dist/shared/runner-credential-keys.js +47 -0
- package/dist/shared/runner-credential-keys.js.map +1 -0
- package/dist/shared/synthesized-attachment.d.ts +4 -4
- package/dist/shared/synthesized-attachment.js +4 -4
- package/dist/workflow-engine/resolve.d.ts +19 -1
- package/dist/workflow-engine/resolve.js +37 -2
- package/dist/workflow-engine/resolve.js.map +1 -1
- package/dist/workflows/connect-mcp-server.js +2 -0
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +3 -4
- package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
- package/src/__test-utils__/fake-artifact-storage.ts +10 -1
- package/src/__test-utils__/vitest-global-setup.ts +50 -0
- package/src/__tests__/bootstrap.test.ts +43 -0
- package/src/__tests__/encryption-codec.test.ts +53 -0
- package/src/__tests__/preflight.test.ts +15 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
- package/src/activities/__tests__/error-classifier.test.ts +94 -2
- package/src/activities/__tests__/run-command.test.ts +221 -0
- package/src/activities/discover-mcp-server.ts +16 -2
- package/src/activities/emit-event.ts +65 -21
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
- package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
- package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
- package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
- package/src/activities/execute-cursor/error-classifier.ts +40 -3
- package/src/activities/execute-cursor/index.ts +175 -74
- package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
- package/src/activities/execute-cursor/prompt-builder.ts +89 -22
- package/src/activities/execute-cursor/turn-recovery.ts +208 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +71 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
- package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +22 -17
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
- package/src/activities/execute-deep-agent/index.ts +0 -19
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
- package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
- package/src/activities/execute-deep-agent/setup.ts +22 -73
- package/src/activities/execute-deep-agent/shell-env.ts +8 -9
- package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
- package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
- package/src/activities/hydrate-workflow-execution.ts +8 -5
- package/src/activities/run-command.ts +25 -12
- package/src/activities/run-env.ts +79 -0
- package/src/bootstrap.ts +5 -0
- package/src/client/__tests__/stigmer-client.test.ts +104 -5
- package/src/client/stigmer-client.ts +128 -29
- package/src/config.ts +6 -5
- package/src/encryption/config.ts +87 -23
- package/src/middleware/__tests__/path-normalization.test.ts +140 -0
- package/src/middleware/index.ts +8 -0
- package/src/middleware/path-normalization.ts +125 -0
- package/src/middleware/types.ts +11 -0
- package/src/payload-codecs.ts +8 -2
- package/src/runner-manager.ts +21 -3
- package/src/runner.ts +7 -3
- package/src/shared/__tests__/artifact-storage.test.ts +154 -2
- package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
- package/src/shared/__tests__/channel-attachment.test.ts +2 -2
- package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
- package/src/shared/__tests__/http-retry.test.ts +5 -4
- package/src/shared/__tests__/mcp-manager.test.ts +53 -13
- package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
- package/src/shared/__tests__/model-client.test.ts +46 -0
- package/src/shared/__tests__/model-pricing.test.ts +62 -2
- package/src/shared/__tests__/model-registry.test.ts +22 -0
- package/src/shared/__tests__/synthesized-attachment.test.ts +19 -41
- package/src/shared/artifact-storage.ts +86 -10
- package/src/shared/attachment-download-urls.ts +98 -0
- package/src/shared/channel-attachment.ts +1 -1
- package/src/shared/checkpointer/http-saver.ts +15 -60
- package/src/shared/conversation-attachment.ts +2 -3
- package/src/shared/conversation-catchup.ts +39 -6
- package/src/shared/grpc-retry.ts +5 -1
- package/src/shared/http-retry.ts +98 -9
- package/src/shared/mcp-manager.ts +35 -4
- package/src/shared/mcp-schema-sanitizer.ts +224 -0
- package/src/shared/mcp-transport-guard.ts +2 -2
- package/src/shared/model-client.ts +21 -3
- package/src/shared/model-pricing-data.ts +22 -3
- package/src/shared/model-registry.ts +7 -2
- package/src/shared/plan-mode-permissions.ts +7 -0
- package/src/shared/registry-endpoint.ts +27 -0
- package/src/shared/runner-credential-keys.ts +46 -0
- package/src/shared/synthesized-attachment.ts +4 -4
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
- package/src/workflow-engine/resolve.ts +48 -2
- package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
- package/src/workflows/connect-mcp-server.ts +2 -0
- package/src/workflows/types.ts +8 -0
- package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
- package/dist/activities/execute-cursor/mcp-config.js +0 -39
- package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
- package/dist/shared/datastore-attachment.d.ts +0 -99
- package/dist/shared/datastore-attachment.js +0 -184
- package/dist/shared/datastore-attachment.js.map +0 -1
- package/src/activities/execute-cursor/mcp-config.ts +0 -66
- package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
- package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
- package/src/shared/datastore-attachment.ts +0 -209
|
@@ -41,7 +41,7 @@ import type { Config } from "../../config.js";
|
|
|
41
41
|
import { StigmerClient } from "../../client/stigmer-client.js";
|
|
42
42
|
import { describeExecutionError } from "../../shared/model-error.js";
|
|
43
43
|
import { resolveAgentWithTransportRecovery } from "./session-lifecycle.js";
|
|
44
|
-
import type { AgentResolution, CreateAgentOptions, CreateCloudAgentOptions } from "./session-lifecycle.js";
|
|
44
|
+
import type { AgentResolution, AgentResolutionReason, CreateAgentOptions, CreateCloudAgentOptions } from "./session-lifecycle.js";
|
|
45
45
|
import { CursorMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
46
46
|
import { determineCursorMode, isCloudMode } from "./cursor-mode.js";
|
|
47
47
|
import { MessageAccumulator, cancelInProgressSubAgentProtos, collapseRedundantToolCallTwins } from "./message-translator.js";
|
|
@@ -72,7 +72,6 @@ import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/stre
|
|
|
72
72
|
import { createCursorEventRecorder } from "./cursor-event-recorder.js";
|
|
73
73
|
import { resolveMcpServers, toCursorMcpConfig, validateMcpServerEnv } from "./mcp-resolver.js";
|
|
74
74
|
import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
|
|
75
|
-
import { synthesizeDatastoreAttachment } from "../../shared/datastore-attachment.js";
|
|
76
75
|
import {
|
|
77
76
|
discoverChannelMessaging,
|
|
78
77
|
synthesizeChannelAttachment,
|
|
@@ -92,7 +91,8 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
|
|
|
92
91
|
import { resolveSkills } from "./skill-resolver.js";
|
|
93
92
|
import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
|
|
94
93
|
import { resolveAttachments } from "./attachment-resolver.js";
|
|
95
|
-
import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
|
|
94
|
+
import { buildEnhancedPrompt, buildHitlRecoveryPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
|
|
95
|
+
import { composeTurnRecoveryDigest } from "./turn-recovery.js";
|
|
96
96
|
import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
|
|
97
97
|
import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
|
|
98
98
|
import {
|
|
@@ -664,35 +664,16 @@ async function executeCursorInner(
|
|
|
664
664
|
?? config.stigmerTokenRef?.current
|
|
665
665
|
?? config.stigmerToken;
|
|
666
666
|
|
|
667
|
-
// Phase 4a2: Synthesize the
|
|
668
|
-
// Deliberately AFTER resolve + backfill: the attachment has
|
|
669
|
-
// McpServerUsage and reports discovered capabilities, so the
|
|
670
|
-
// backfill's destructiveHint tightener can never force-gate
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
backendEndpoint: config.stigmerBackendEndpoint,
|
|
678
|
-
});
|
|
679
|
-
if (attachment) {
|
|
680
|
-
const resolvedServers = injectSynthesizedAttachment(
|
|
681
|
-
mcpResolution.resolvedServers, attachment, "datastore records",
|
|
682
|
-
);
|
|
683
|
-
mcpResolution = {
|
|
684
|
-
resolvedServers,
|
|
685
|
-
cursorConfig: toCursorMcpConfig(resolvedServers),
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Phase 4a3: Synthesize the channel messaging attachment (DD-006
|
|
691
|
-
// D7/D8), the records attachment's twin. The discovery read is the
|
|
692
|
-
// attachment decision — the control plane runs the SAME candidate
|
|
693
|
-
// computation the send authorization uses — and every failure mode
|
|
694
|
-
// (no channel, OSS, registry down, pre-3a control plane) degrades
|
|
695
|
-
// to honest absence: no tool, no section, execution unharmed.
|
|
667
|
+
// Phase 4a2: Synthesize the channel messaging attachment (DD-006
|
|
668
|
+
// D7/D8). Deliberately AFTER resolve + backfill: the attachment has
|
|
669
|
+
// no McpServerUsage and reports discovered capabilities, so the
|
|
670
|
+
// backfill's destructiveHint tightener can never force-gate its
|
|
671
|
+
// tools; empty approval maps keep it approval-free by construction.
|
|
672
|
+
// The discovery read is the attachment decision — the control plane
|
|
673
|
+
// runs the SAME candidate computation the send authorization uses —
|
|
674
|
+
// and every failure mode (no channel, OSS, registry down, pre-3a
|
|
675
|
+
// control plane) degrades to honest absence: no tool, no section,
|
|
676
|
+
// execution unharmed.
|
|
696
677
|
const channelMessaging = await discoverChannelMessaging(client, exchangedRunnerToken);
|
|
697
678
|
if (channelMessaging.length > 0) {
|
|
698
679
|
const attachment = synthesizeChannelAttachment(channelMessaging, {
|
|
@@ -801,6 +782,7 @@ async function executeCursorInner(
|
|
|
801
782
|
const attachmentEntries = attachmentResults.map((a) => ({
|
|
802
783
|
path: a.relativePath,
|
|
803
784
|
...(a.renamedFrom !== undefined ? { renamedFrom: a.renamedFrom } : {}),
|
|
785
|
+
...(a.downloadUrl !== undefined ? { downloadUrl: a.downloadUrl } : {}),
|
|
804
786
|
}));
|
|
805
787
|
// Vision facts, derived once from the single resolution result: the
|
|
806
788
|
// images the model will see inline (in attachment order) and the ones
|
|
@@ -1146,13 +1128,13 @@ async function executeCursorInner(
|
|
|
1146
1128
|
instructions: blueprint.instructions,
|
|
1147
1129
|
userMessage: spec.message,
|
|
1148
1130
|
skills: skillMetadata,
|
|
1149
|
-
datastoreUsages: blueprint.datastoreUsages,
|
|
1150
1131
|
channelMessaging,
|
|
1151
1132
|
subAgents: blueprint.subAgents,
|
|
1152
1133
|
workspaceDirs: blueprint.workspaceDirs,
|
|
1153
1134
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
1154
1135
|
attachments: attachmentEntries,
|
|
1155
1136
|
vision: visionPromptInfo,
|
|
1137
|
+
downloadUrlKind: artifactStorage?.downloadUrlKind,
|
|
1156
1138
|
pendingApprovals: adjudicatedApprovals,
|
|
1157
1139
|
appliedToolCallIds,
|
|
1158
1140
|
interactionMode,
|
|
@@ -1161,28 +1143,43 @@ async function executeCursorInner(
|
|
|
1161
1143
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1162
1144
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1163
1145
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1146
|
+
// The turn's recorded transcript, seeded from the persisted execution
|
|
1147
|
+
// on a reinvocation (Phase 3). Consumed only by the HITL-recovery
|
|
1148
|
+
// shape — reached from HERE when the stored handle failed to resume
|
|
1149
|
+
// at resolution time (issue #366 crossing 2).
|
|
1150
|
+
turnRecoveryDigest: isReinvocation
|
|
1151
|
+
? composeTurnRecoveryDigest(status.messages)
|
|
1152
|
+
: undefined,
|
|
1164
1153
|
});
|
|
1165
1154
|
|
|
1166
|
-
// Phase 10a: Inject structured output instruction for Cursor
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1155
|
+
// Phase 10a: Inject the structured output instruction for the Cursor
|
|
1156
|
+
// harness. A per-turn directive, so like buildFromPlan it must ride every
|
|
1157
|
+
// prompt this turn sends — the primary AND the poisoned-handle recovery
|
|
1158
|
+
// rebuild (the transport retry re-sends effectivePrompt and inherits it).
|
|
1159
|
+
const withStructuredOutputDirective = (basePrompt: string): string =>
|
|
1160
|
+
appendStructuredOutputDirective(basePrompt, structuredOutputSchema);
|
|
1161
|
+
const effectivePrompt = withStructuredOutputDirective(prompt);
|
|
1172
1162
|
|
|
1173
1163
|
// Phase 10a1: The turn's vision payload. The invariant is "images
|
|
1174
|
-
// accompany the user's turn message
|
|
1175
|
-
//
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1180
|
-
//
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1164
|
+
// accompany the user's turn message, wherever the conversation does not
|
|
1165
|
+
// already hold them" (primarySendCarriesImages): the ONLY send that
|
|
1166
|
+
// skips them is a HITL re-invocation of a successfully RESUMED agent,
|
|
1167
|
+
// whose native conversation carries the images from the original send.
|
|
1168
|
+
// Every send that starts an empty conversation re-delivers them — the
|
|
1169
|
+
// ordinary first/fresh-agent primary send, the HITL primary send after a
|
|
1170
|
+
// resolution-time resume failure, and both mid-send recovery retries
|
|
1171
|
+
// (which always run on a fresh agent, so their sites pass turnImages
|
|
1172
|
+
// unconditionally). Attachments re-resolve on every invocation
|
|
1173
|
+
// (Phase 5b), so the bytes are in hand even on a re-invocation.
|
|
1174
|
+
const turnImages = toCursorImages(visionImages);
|
|
1175
|
+
const primarySendImages = primarySendCarriesImages(approvalDecisions, resolution.reason)
|
|
1176
|
+
? turnImages
|
|
1177
|
+
: [];
|
|
1178
|
+
const toSendMessage = (
|
|
1179
|
+
sendPrompt: string,
|
|
1180
|
+
images: { data: string; mimeType: string }[],
|
|
1181
|
+
): string | SDKUserMessage =>
|
|
1182
|
+
images.length > 0 ? { text: sendPrompt, images } : sendPrompt;
|
|
1186
1183
|
|
|
1187
1184
|
// Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
|
|
1188
1185
|
const promptChars = effectivePrompt.length;
|
|
@@ -1298,7 +1295,7 @@ async function executeCursorInner(
|
|
|
1298
1295
|
// The stall watchdog is armed inside consumeCursorTurnStream (it needs the
|
|
1299
1296
|
// run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
|
|
1300
1297
|
// reset it and the activity's finally can stop it as a backstop.
|
|
1301
|
-
const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
|
|
1298
|
+
const run = await resolution.agent.send(toSendMessage(effectivePrompt, primarySendImages), {
|
|
1302
1299
|
onDelta: (event) => {
|
|
1303
1300
|
if (!turnFirstEventEmitted) {
|
|
1304
1301
|
turnFirstEventEmitted = true;
|
|
@@ -1669,10 +1666,11 @@ async function executeCursorInner(
|
|
|
1669
1666
|
// poisoned-handle path leaked the fresh agent — it closed the stale one.)
|
|
1670
1667
|
resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
|
|
1671
1668
|
turnState.streamErrorMessage = undefined;
|
|
1672
|
-
// The retry carries the turn's
|
|
1673
|
-
//
|
|
1674
|
-
// silently lose the user's photo on
|
|
1675
|
-
|
|
1669
|
+
// The retry always carries the turn's full image payload — never the
|
|
1670
|
+
// primary send's HITL-trimmed set: the fresh agent's conversation is
|
|
1671
|
+
// empty, so skipping them here would silently lose the user's photo on
|
|
1672
|
+
// a recovered turn (issue #366's vision corollary).
|
|
1673
|
+
const retryRun = await freshAgent.send(toSendMessage(retryPrompt, turnImages), {
|
|
1676
1674
|
onDelta: makeCursorTurnOnDelta(onDeltaDeps),
|
|
1677
1675
|
});
|
|
1678
1676
|
await consumeCursorTurnStream(retryRun, streamDeps);
|
|
@@ -1804,14 +1802,18 @@ async function executeCursorInner(
|
|
|
1804
1802
|
instructions: blueprint.instructions,
|
|
1805
1803
|
userMessage: spec.message,
|
|
1806
1804
|
skills: skillMetadata,
|
|
1807
|
-
datastoreUsages: blueprint.datastoreUsages,
|
|
1808
1805
|
channelMessaging,
|
|
1809
1806
|
subAgents: blueprint.subAgents,
|
|
1810
1807
|
workspaceDirs: blueprint.workspaceDirs,
|
|
1811
1808
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
1812
1809
|
attachments: attachmentEntries,
|
|
1813
1810
|
vision: visionPromptInfo,
|
|
1811
|
+
downloadUrlKind: artifactStorage?.downloadUrlKind,
|
|
1814
1812
|
pendingApprovals: adjudicatedApprovals,
|
|
1813
|
+
// Without the applied set, the HITL-recovery prompt would tell
|
|
1814
|
+
// the fresh agent to carry out writes the runner already
|
|
1815
|
+
// exact-applied (the primary call at Phase 10 passes it too).
|
|
1816
|
+
appliedToolCallIds,
|
|
1815
1817
|
interactionMode,
|
|
1816
1818
|
// buildFromPlan was silently dropped here until T03 Sitting 3 —
|
|
1817
1819
|
// a build turn that hit handle recovery lost its directive. The
|
|
@@ -1822,6 +1824,10 @@ async function executeCursorInner(
|
|
|
1822
1824
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1823
1825
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1824
1826
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1827
|
+
// Composed fresh (not reused from Phase 10): the failed primary
|
|
1828
|
+
// stream may have appended partial work onto status.messages,
|
|
1829
|
+
// and the replacement agent should know about that too.
|
|
1830
|
+
turnRecoveryDigest: composeTurnRecoveryDigest(status.messages),
|
|
1825
1831
|
});
|
|
1826
1832
|
|
|
1827
1833
|
console.log(
|
|
@@ -1837,7 +1843,14 @@ async function executeCursorInner(
|
|
|
1837
1843
|
console.warn("Failed to update session with fresh agentId (non-fatal):", updateErr);
|
|
1838
1844
|
}
|
|
1839
1845
|
|
|
1840
|
-
|
|
1846
|
+
// Same per-turn directive rule as buildFromPlan above: a
|
|
1847
|
+
// structured-output turn keeps its output contract on the rebuilt
|
|
1848
|
+
// prompt (the transport retry re-sends effectivePrompt and
|
|
1849
|
+
// inherits it without help).
|
|
1850
|
+
const outcome = await runRecoveryStream(
|
|
1851
|
+
freshAgent,
|
|
1852
|
+
withStructuredOutputDirective(freshPrompt),
|
|
1853
|
+
);
|
|
1841
1854
|
if (!outcome.proceeded) {
|
|
1842
1855
|
if (outcome.terminal.kind === "return") return slimStatus(status);
|
|
1843
1856
|
throw new CancelledFailure(outcome.terminal.message);
|
|
@@ -2335,8 +2348,6 @@ export interface BuildPromptInput {
|
|
|
2335
2348
|
instructions: string;
|
|
2336
2349
|
userMessage: string;
|
|
2337
2350
|
skills: import("./prompt-builder.js").SkillMetadata[];
|
|
2338
|
-
/** Datastores attached via `datastore_usages` — the `<available_datastores>` section. */
|
|
2339
|
-
datastoreUsages?: import("@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb").DatastoreUsage[];
|
|
2340
2351
|
/**
|
|
2341
2352
|
* Serving proactive channels + their templates (the DD-006 D2
|
|
2342
2353
|
* discovery read) — the `<available_channel_templates>` section.
|
|
@@ -2356,6 +2367,12 @@ export interface BuildPromptInput {
|
|
|
2356
2367
|
* catchup — it rides both the enhanced prompt and a resumed turn's prefix.
|
|
2357
2368
|
*/
|
|
2358
2369
|
vision?: import("./prompt-builder.js").VisionPromptInfo;
|
|
2370
|
+
/**
|
|
2371
|
+
* What kind of URL the turn's storage backend mints (issue #532) — keys
|
|
2372
|
+
* the input-files hand-off wording. Sourced from the resolved
|
|
2373
|
+
* artifactStorage's self-description; absent when no storage resolved.
|
|
2374
|
+
*/
|
|
2375
|
+
downloadUrlKind?: import("../../shared/attachment-download-urls.js").DownloadUrlKind;
|
|
2359
2376
|
pendingApprovals: import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb").PendingApproval[];
|
|
2360
2377
|
/**
|
|
2361
2378
|
* Approved whole-file writes the runner already applied itself (exact-apply).
|
|
@@ -2400,6 +2417,15 @@ export interface BuildPromptInput {
|
|
|
2400
2417
|
* usually blank.
|
|
2401
2418
|
*/
|
|
2402
2419
|
conversationCatchup?: string;
|
|
2420
|
+
/**
|
|
2421
|
+
* The turn's recorded transcript rendered as digest lines
|
|
2422
|
+
* (turn-recovery.ts), composed from `status.messages` at the call site.
|
|
2423
|
+
* Consumed ONLY by the HITL-recovery shape — a fresh agent that replaced
|
|
2424
|
+
* a lost one mid-HITL needs the story of the work it no longer remembers
|
|
2425
|
+
* (issue #366); every other shape either has native context or no prior
|
|
2426
|
+
* work to tell.
|
|
2427
|
+
*/
|
|
2428
|
+
turnRecoveryDigest?: string;
|
|
2403
2429
|
}
|
|
2404
2430
|
|
|
2405
2431
|
/**
|
|
@@ -2411,20 +2437,28 @@ export interface BuildPromptInput {
|
|
|
2411
2437
|
* volume, or cloud server-side state) — there is no separate continuation
|
|
2412
2438
|
* store. The prompt therefore depends only on how the agent was resolved:
|
|
2413
2439
|
*
|
|
2414
|
-
* 1. HITL reinvocation -> buildReinvocationPrompt (approval decisions
|
|
2415
|
-
*
|
|
2416
|
-
*
|
|
2417
|
-
* 2.
|
|
2418
|
-
*
|
|
2440
|
+
* 1. HITL reinvocation, -> buildReinvocationPrompt (approval decisions
|
|
2441
|
+
* resumed agent only; the resumed agent's native context
|
|
2442
|
+
* carries the prior conversation)
|
|
2443
|
+
* 2. HITL reinvocation, -> buildHitlRecoveryPrompt (full context +
|
|
2444
|
+
* fresh agent after the turn's recorded transcript + decisions;
|
|
2445
|
+
* resume failure the replacement agent's conversation is
|
|
2446
|
+
* empty, and both fresh-agent crossings —
|
|
2447
|
+
* resolution-time resume failure and mid-send
|
|
2448
|
+
* poisoned-handle recovery — land here by
|
|
2449
|
+
* keying on the reason, issue #366)
|
|
2450
|
+
* 3. resumed_successfully -> raw userMessage (native context carries it)
|
|
2451
|
+
* 4. first execution / fresh -> buildEnhancedPrompt (full instructions +
|
|
2419
2452
|
* agent after resume failure skills; no prior conversation to inherit)
|
|
2420
2453
|
*/
|
|
2421
2454
|
/**
|
|
2422
2455
|
* Whether this activity invocation is a HITL re-invocation — the turn resumes
|
|
2423
2456
|
* an agent purely to convey approval decisions, carrying NO user message.
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2426
|
-
* vision payload
|
|
2427
|
-
*
|
|
2457
|
+
* Discriminates the two surfaces that depend on the agent already holding
|
|
2458
|
+
* this turn's content natively — the prompt shape (below) and the primary
|
|
2459
|
+
* send's vision payload — but never alone: both pair it with
|
|
2460
|
+
* `resolution.reason`, because a FRESH agent mid-HITL holds nothing and
|
|
2461
|
+
* needs the full re-delivery (issue #366).
|
|
2428
2462
|
*/
|
|
2429
2463
|
export function isHitlReinvocation(
|
|
2430
2464
|
approvalDecisions: Map<string, ApprovalAction> | undefined,
|
|
@@ -2432,6 +2466,39 @@ export function isHitlReinvocation(
|
|
|
2432
2466
|
return approvalDecisions !== undefined && approvalDecisions.size > 0;
|
|
2433
2467
|
}
|
|
2434
2468
|
|
|
2469
|
+
/**
|
|
2470
|
+
* Whether the PRIMARY send delivers the turn's vision payload. The invariant
|
|
2471
|
+
* is "images accompany the user's turn message, wherever the conversation
|
|
2472
|
+
* does not already hold them" — so the only send that skips them is a HITL
|
|
2473
|
+
* re-invocation of a successfully RESUMED agent, whose native conversation
|
|
2474
|
+
* carries the images from the original send. A fresh agent mid-HITL
|
|
2475
|
+
* (resolution-time resume failure — issue #366's vision corollary) holds
|
|
2476
|
+
* nothing and needs the re-delivery. The mid-send recovery retries always
|
|
2477
|
+
* run on a fresh agent, so their send sites carry the payload
|
|
2478
|
+
* unconditionally rather than consulting this.
|
|
2479
|
+
*/
|
|
2480
|
+
export function primarySendCarriesImages(
|
|
2481
|
+
approvalDecisions: Map<string, ApprovalAction> | undefined,
|
|
2482
|
+
reason: AgentResolutionReason,
|
|
2483
|
+
): boolean {
|
|
2484
|
+
return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
/**
|
|
2488
|
+
* Append the structured-output contract to a prompt when the execution
|
|
2489
|
+
* requests one. A per-turn directive (the buildFromPlan rule): it must ride
|
|
2490
|
+
* every prompt this turn sends — the primary send AND the poisoned-handle
|
|
2491
|
+
* recovery rebuild, which previously lost it (issue #366 ride-along).
|
|
2492
|
+
*/
|
|
2493
|
+
export function appendStructuredOutputDirective(
|
|
2494
|
+
basePrompt: string,
|
|
2495
|
+
schema: Record<string, unknown> | undefined,
|
|
2496
|
+
): string {
|
|
2497
|
+
if (!schema) return basePrompt;
|
|
2498
|
+
const schemaStr = JSON.stringify(schema, null, 2);
|
|
2499
|
+
return basePrompt + `\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.`;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2435
2502
|
export function buildPrompt(input: BuildPromptInput): string {
|
|
2436
2503
|
const {
|
|
2437
2504
|
resolution,
|
|
@@ -2448,10 +2515,44 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2448
2515
|
conversationCatchup,
|
|
2449
2516
|
} = input;
|
|
2450
2517
|
|
|
2451
|
-
// HITL reinvocation: the
|
|
2452
|
-
//
|
|
2453
|
-
//
|
|
2518
|
+
// HITL reinvocation: the decisions-only prompt is correct ONLY while the
|
|
2519
|
+
// agent's native context still carries the prior conversation — which only
|
|
2520
|
+
// resumed_successfully guarantees. Any other reason means a fresh agent
|
|
2521
|
+
// mid-HITL (in practice created_after_resume_failure: the stored handle
|
|
2522
|
+
// failed to resume, or a poisoned handle was replaced mid-send), which
|
|
2523
|
+
// gets the full recovery shape instead — enhanced context + the turn's
|
|
2524
|
+
// recorded transcript + the same decisions — because the bare decisions on
|
|
2525
|
+
// an empty conversation strand the agent with instructions and no story,
|
|
2526
|
+
// and the session inherits that amnesia permanently (issue #366).
|
|
2454
2527
|
if (isHitlReinvocation(approvalDecisions)) {
|
|
2528
|
+
if (resolution.reason !== "resumed_successfully") {
|
|
2529
|
+
return buildHitlRecoveryPrompt(
|
|
2530
|
+
{
|
|
2531
|
+
instructions,
|
|
2532
|
+
userMessage,
|
|
2533
|
+
skills,
|
|
2534
|
+
channelMessaging: input.channelMessaging ?? [],
|
|
2535
|
+
subAgents,
|
|
2536
|
+
workspaceDirs,
|
|
2537
|
+
workspaceFileRefs,
|
|
2538
|
+
attachments,
|
|
2539
|
+
vision: input.vision,
|
|
2540
|
+
downloadUrlKind: input.downloadUrlKind,
|
|
2541
|
+
interactionMode,
|
|
2542
|
+
buildFromPlan,
|
|
2543
|
+
contextBridge: input.contextBridge,
|
|
2544
|
+
senderIdentity: input.senderIdentity,
|
|
2545
|
+
sessionContext: input.sessionContext,
|
|
2546
|
+
conversationCatchup,
|
|
2547
|
+
},
|
|
2548
|
+
{
|
|
2549
|
+
turnDigest: input.turnRecoveryDigest,
|
|
2550
|
+
pendingApprovals: input.pendingApprovals,
|
|
2551
|
+
approvalDecisions,
|
|
2552
|
+
appliedToolCallIds: input.appliedToolCallIds,
|
|
2553
|
+
},
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2455
2556
|
return buildReinvocationPrompt(
|
|
2456
2557
|
input.pendingApprovals,
|
|
2457
2558
|
approvalDecisions,
|
|
@@ -2478,7 +2579,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2478
2579
|
formatInteractionModePrefix(interactionMode),
|
|
2479
2580
|
formatImplementPlanSection(buildFromPlan, attachments),
|
|
2480
2581
|
attachments.length > 0
|
|
2481
|
-
? formatInputFiles(attachments, input.vision)
|
|
2582
|
+
? formatInputFiles(attachments, input.vision, input.downloadUrlKind)
|
|
2482
2583
|
: undefined,
|
|
2483
2584
|
conversationCatchup !== undefined
|
|
2484
2585
|
? formatConversationCatchupSection(conversationCatchup)
|
|
@@ -2496,13 +2597,13 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2496
2597
|
instructions,
|
|
2497
2598
|
userMessage,
|
|
2498
2599
|
skills,
|
|
2499
|
-
datastoreUsages: input.datastoreUsages ?? [],
|
|
2500
2600
|
channelMessaging: input.channelMessaging ?? [],
|
|
2501
2601
|
subAgents,
|
|
2502
2602
|
workspaceDirs,
|
|
2503
2603
|
workspaceFileRefs,
|
|
2504
2604
|
attachments,
|
|
2505
2605
|
vision: input.vision,
|
|
2606
|
+
downloadUrlKind: input.downloadUrlKind,
|
|
2506
2607
|
interactionMode,
|
|
2507
2608
|
buildFromPlan,
|
|
2508
2609
|
contextBridge: input.contextBridge,
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
* endpoint is unreachable.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
resolveModelRegistryUrl,
|
|
13
|
+
buildRegistryHeaders,
|
|
14
|
+
REGISTRY_RETRY_POLICY,
|
|
15
|
+
} from "../../shared/registry-endpoint.js";
|
|
16
|
+
import { fetchWithRetry } from "../../shared/http-retry.js";
|
|
12
17
|
|
|
13
18
|
/** Per-million rates for a speed/mode variant (e.g. "fast") of a base model. */
|
|
14
19
|
export interface CursorVariantPricing {
|
|
@@ -53,6 +58,10 @@ interface RegistryEntry {
|
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
const CACHE_TTL_MS = 3_600_000; // 1 hour
|
|
61
|
+
// Failed fetches are cached much shorter than successes (the model-registry.ts
|
|
62
|
+
// policy): a transient failure must not pin DEFAULT_PRICING — wrong rates for
|
|
63
|
+
// cost tracking — for a full hour.
|
|
64
|
+
const FAILURE_CACHE_TTL_MS = 60_000;
|
|
56
65
|
|
|
57
66
|
const DEFAULT_PRICING: CursorModelPricing = {
|
|
58
67
|
model: "unknown",
|
|
@@ -104,7 +113,11 @@ function parseVariants(
|
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
async function fetchFromApi(): Promise<readonly CursorModelPricing[]> {
|
|
107
|
-
const res = await
|
|
116
|
+
const res = await fetchWithRetry(
|
|
117
|
+
resolveModelRegistryUrl(),
|
|
118
|
+
{ headers: buildRegistryHeaders() },
|
|
119
|
+
REGISTRY_RETRY_POLICY,
|
|
120
|
+
);
|
|
108
121
|
if (!res.ok) throw new Error(`Model registry fetch failed: ${res.status}`);
|
|
109
122
|
const data: unknown = await res.json();
|
|
110
123
|
const table = parsePricingTable(data);
|
|
@@ -138,7 +151,7 @@ export async function getPricingTable(): Promise<readonly CursorModelPricing[]>
|
|
|
138
151
|
`Failed to fetch model registry from API, using default pricing: ${err}`,
|
|
139
152
|
);
|
|
140
153
|
const fallback = [DEFAULT_PRICING];
|
|
141
|
-
cache = { data: fallback, expiresAt: Date.now() +
|
|
154
|
+
cache = { data: fallback, expiresAt: Date.now() + FAILURE_CACHE_TTL_MS };
|
|
142
155
|
return fallback;
|
|
143
156
|
})
|
|
144
157
|
.finally(() => {
|
|
@@ -148,4 +161,10 @@ export async function getPricingTable(): Promise<readonly CursorModelPricing[]>
|
|
|
148
161
|
return inflightFetch;
|
|
149
162
|
}
|
|
150
163
|
|
|
164
|
+
/** Exposed for testing — resets the in-memory cache. */
|
|
165
|
+
export function _resetPricingCache(): void {
|
|
166
|
+
cache = null;
|
|
167
|
+
inflightFetch = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
151
170
|
export { DEFAULT_PRICING };
|
|
@@ -17,12 +17,11 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { resolve } from "node:path";
|
|
20
|
-
import type {
|
|
20
|
+
import type { SubAgent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
21
21
|
import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
22
22
|
import { ApprovalAction, InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
23
23
|
import { formatContextBridgeText } from "../../shared/context-bridge.js";
|
|
24
24
|
import { formatConversationCatchupText } from "../../shared/conversation-catchup.js";
|
|
25
|
-
import { formatDatastoresSection } from "../../shared/datastore-attachment.js";
|
|
26
25
|
import {
|
|
27
26
|
formatChannelTemplatesSection,
|
|
28
27
|
type ChannelMessagingInfo,
|
|
@@ -36,6 +35,11 @@ import {
|
|
|
36
35
|
visionDisclosureLines,
|
|
37
36
|
type NotViewableEntry,
|
|
38
37
|
} from "../../shared/attachment-vision.js";
|
|
38
|
+
import {
|
|
39
|
+
downloadUrlDisclosureLine,
|
|
40
|
+
type DownloadUrlKind,
|
|
41
|
+
} from "../../shared/attachment-download-urls.js";
|
|
42
|
+
import { formatTurnRecoveryText } from "./turn-recovery.js";
|
|
39
43
|
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
40
44
|
import {
|
|
41
45
|
buildImplementPlanDirective,
|
|
@@ -81,21 +85,18 @@ export interface AttachmentPromptEntry {
|
|
|
81
85
|
path: string;
|
|
82
86
|
/** Original filename, present only when a duplicate name was renamed. */
|
|
83
87
|
renamedFrom?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Download URL for the attachment's stored object, when one was minted
|
|
90
|
+
* (shared/attachment-download-urls.ts) — rendered beside the path so the
|
|
91
|
+
* agent can hand the file to tools that cannot read this filesystem.
|
|
92
|
+
*/
|
|
93
|
+
downloadUrl?: string;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
export interface EnhancedPromptOptions {
|
|
87
97
|
instructions: string;
|
|
88
98
|
userMessage: string;
|
|
89
99
|
skills: SkillMetadata[];
|
|
90
|
-
/**
|
|
91
|
-
* Datastores attached via `datastore_usages` — rendered as the
|
|
92
|
-
* `<available_datastores>` section (DD-005 SD-5, skills precedent)
|
|
93
|
-
* pointing the model at the synthesized record tools. Always the healthy
|
|
94
|
-
* rendering here: the Cursor SDK connects MCP itself, so this harness can
|
|
95
|
-
* never observe the live roster — the section's standing
|
|
96
|
-
* failure-disclosure instruction is its whole coverage (issue #325).
|
|
97
|
-
*/
|
|
98
|
-
datastoreUsages?: DatastoreUsage[];
|
|
99
100
|
/**
|
|
100
101
|
* Serving proactive channels + their approved templates — rendered as
|
|
101
102
|
* the `<available_channel_templates>` section (proactive-messaging
|
|
@@ -108,6 +109,12 @@ export interface EnhancedPromptOptions {
|
|
|
108
109
|
attachments: AttachmentPromptEntry[];
|
|
109
110
|
/** Inline/degraded image facts for the input-files section (T04 vision). */
|
|
110
111
|
vision?: VisionPromptInfo;
|
|
112
|
+
/**
|
|
113
|
+
* What kind of URL the turn's storage backend mints — keys the input-files
|
|
114
|
+
* section's hand-off wording (attachment-download-urls.ts). One turn-level
|
|
115
|
+
* fact: all attachments ride the one configured storage.
|
|
116
|
+
*/
|
|
117
|
+
downloadUrlKind?: DownloadUrlKind;
|
|
111
118
|
interactionMode?: InteractionMode;
|
|
112
119
|
/**
|
|
113
120
|
* The execution is a Build-from-plan turn (spec.execution_config
|
|
@@ -182,10 +189,6 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
182
189
|
sections.push(formatSkillsSection(options.skills));
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
if (options.datastoreUsages !== undefined && options.datastoreUsages.length > 0) {
|
|
186
|
-
sections.push(formatDatastoresSection(options.datastoreUsages));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
192
|
if (options.channelMessaging !== undefined && options.channelMessaging.length > 0) {
|
|
190
193
|
// "" when nothing is sendable — the tool alone still serves text
|
|
191
194
|
// sends inside a 24-hour window (DD-006 D6).
|
|
@@ -211,7 +214,7 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
if (options.attachments.length > 0) {
|
|
214
|
-
sections.push(formatInputFiles(options.attachments, options.vision));
|
|
217
|
+
sections.push(formatInputFiles(options.attachments, options.vision, options.downloadUrlKind));
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
if (options.workspaceFileRefs.length > 0) {
|
|
@@ -338,6 +341,56 @@ export function buildReinvocationPrompt(
|
|
|
338
341
|
return parts.join("\n\n");
|
|
339
342
|
}
|
|
340
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Build the prompt for a fresh agent that replaced a lost one MID-HITL —
|
|
346
|
+
* the stored handle failed to resume when the approval landed, or the
|
|
347
|
+
* resumed handle proved poisoned mid-send (issue #366, both crossings).
|
|
348
|
+
*
|
|
349
|
+
* A resumed agent gets {@link buildReinvocationPrompt} alone because its
|
|
350
|
+
* native conversation carries everything else. A replacement agent's
|
|
351
|
+
* conversation is EMPTY, so the bare decisions prompt would strand it with
|
|
352
|
+
* instructions and no story — and, because recovery repoints the session at
|
|
353
|
+
* the new agent, every later turn would inherit that amnesia. This prompt
|
|
354
|
+
* rebuilds the whole story in chronological-narrative order:
|
|
355
|
+
*
|
|
356
|
+
* 1. the full enhanced prompt (blueprint, standing context, protocol, and
|
|
357
|
+
* the turn's ORIGINAL user message as `<user_request>`),
|
|
358
|
+
* 2. `<turn_recovery>` — the state-loss disclosure plus the recorded
|
|
359
|
+
* transcript of the turn so far (turn-recovery.ts; rendered even with no
|
|
360
|
+
* transcript, since without the disclosure the decisions below would read
|
|
361
|
+
* as reactions to proposals this agent never made),
|
|
362
|
+
* 3. the reinvocation decisions verbatim — already-applied / approved /
|
|
363
|
+
* skipped semantics byte-identical to the resumed path, ending on its
|
|
364
|
+
* "continue the task" directive.
|
|
365
|
+
*
|
|
366
|
+
* Appending past `<user_request>` is the chronology speaking (request →
|
|
367
|
+
* work done → decisions → continue), the same shape as the activity-level
|
|
368
|
+
* structured-output suffix.
|
|
369
|
+
*/
|
|
370
|
+
export function buildHitlRecoveryPrompt(
|
|
371
|
+
options: EnhancedPromptOptions,
|
|
372
|
+
recovery: {
|
|
373
|
+
turnDigest: string | undefined;
|
|
374
|
+
pendingApprovals: PendingApproval[];
|
|
375
|
+
approvalDecisions: Map<string, ApprovalAction>;
|
|
376
|
+
appliedToolCallIds?: ReadonlySet<string>;
|
|
377
|
+
},
|
|
378
|
+
): string {
|
|
379
|
+
return [
|
|
380
|
+
buildEnhancedPrompt(options),
|
|
381
|
+
formatTurnRecoverySection(recovery.turnDigest),
|
|
382
|
+
buildReinvocationPrompt(
|
|
383
|
+
recovery.pendingApprovals,
|
|
384
|
+
recovery.approvalDecisions,
|
|
385
|
+
recovery.appliedToolCallIds,
|
|
386
|
+
),
|
|
387
|
+
].join("\n\n---\n\n");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function formatTurnRecoverySection(digest: string | undefined): string {
|
|
391
|
+
return `<turn_recovery>\n${formatTurnRecoveryText(digest)}\n</turn_recovery>`;
|
|
392
|
+
}
|
|
393
|
+
|
|
341
394
|
/**
|
|
342
395
|
* Render a pending approval as a short human-readable action description for the
|
|
343
396
|
* reinvocation prompt. Prefers the already-resolved approval message; falls back
|
|
@@ -497,15 +550,28 @@ export function formatWorkspaceContext(dirs: string[]): string {
|
|
|
497
550
|
export function formatInputFiles(
|
|
498
551
|
attachments: readonly AttachmentPromptEntry[],
|
|
499
552
|
vision?: VisionPromptInfo,
|
|
553
|
+
downloadUrlKind?: DownloadUrlKind,
|
|
500
554
|
): string {
|
|
501
555
|
// A duplicate-renamed file (attachment-naming.ts) discloses its original
|
|
502
556
|
// name so the agent can connect "the two report.pdfs" in the user's
|
|
503
|
-
// message to distinct files on disk.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
557
|
+
// message to distinct files on disk. A file with a minted download URL
|
|
558
|
+
// (attachment-download-urls.ts) lists it beside the path for the remote
|
|
559
|
+
// hand-off story.
|
|
560
|
+
const entries = attachments.map((a) => {
|
|
561
|
+
const rename =
|
|
562
|
+
a.renamedFrom !== undefined
|
|
563
|
+
? ` (renamed from duplicate '${a.renamedFrom}')`
|
|
564
|
+
: "";
|
|
565
|
+
const url = a.downloadUrl !== undefined ? ` — download URL: ${a.downloadUrl}` : "";
|
|
566
|
+
return `- \`${a.path}\`${rename}${url}`;
|
|
567
|
+
});
|
|
568
|
+
// The URL hand-off line (shared wording, attachment-download-urls.ts)
|
|
569
|
+
// renders only when some listed file actually carries a URL — its wording
|
|
570
|
+
// keys on what kind of URL the storage backend mints.
|
|
571
|
+
const urlDisclosure =
|
|
572
|
+
downloadUrlKind !== undefined && attachments.some((a) => a.downloadUrl !== undefined)
|
|
573
|
+
? [downloadUrlDisclosureLine(downloadUrlKind)]
|
|
574
|
+
: [];
|
|
509
575
|
// The vision lines (shared wording, attachment-vision.ts) tell the model
|
|
510
576
|
// which of these files it can already SEE inline versus which degraded to
|
|
511
577
|
// path-only — without them an agent silently ignores a photo the user
|
|
@@ -517,6 +583,7 @@ export function formatInputFiles(
|
|
|
517
583
|
"<input_files>",
|
|
518
584
|
"The following files have been provided as inputs. Read them when relevant to the task:",
|
|
519
585
|
...entries,
|
|
586
|
+
...urlDisclosure,
|
|
520
587
|
...disclosure,
|
|
521
588
|
"</input_files>",
|
|
522
589
|
].join("\n");
|