@stigmer/runner 3.2.1 → 3.2.3
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/dist/.build-fingerprint +1 -1
- package/dist/activities/call-transform.js +12 -2
- package/dist/activities/call-transform.js.map +1 -1
- package/dist/activities/call-validate.js +20 -2
- package/dist/activities/call-validate.js.map +1 -1
- package/dist/activities/discover-mcp-server.js +6 -20
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +5 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +27 -2
- package/dist/activities/execute-cursor/approval-state.js +13 -1
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/hook-script.js +38 -0
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +12 -0
- package/dist/activities/execute-cursor/index.js +72 -15
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +26 -0
- package/dist/activities/execute-cursor/message-translator.js +78 -2
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +19 -0
- package/dist/activities/execute-cursor/prompt-builder.js +21 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +45 -0
- package/dist/activities/execute-cursor/session-lifecycle.js +41 -0
- package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.js +15 -2
- package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
- package/dist/activities/execute-deep-agent/hitl.d.ts +25 -0
- package/dist/activities/execute-deep-agent/hitl.js +48 -1
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +10 -1
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +19 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +12 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +14 -0
- package/dist/activities/execute-deep-agent/setup.js +16 -1
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +8 -0
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder.js +6 -0
- package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -0
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/activities/workflow-event-activities.js +11 -0
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +20 -0
- package/dist/middleware/approval-gate.js +16 -1
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/runner-manager.d.ts +2 -0
- package/dist/runner-manager.js +2 -1
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +2 -1
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +24 -1
- package/dist/shared/approval-policy.js +35 -1
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/context-bridge.d.ts +30 -0
- package/dist/shared/context-bridge.js +45 -0
- package/dist/shared/context-bridge.js.map +1 -0
- package/dist/shared/sender-identity.d.ts +50 -0
- package/dist/shared/sender-identity.js +69 -0
- package/dist/shared/sender-identity.js.map +1 -0
- package/dist/shared/with-timeout.d.ts +27 -0
- package/dist/shared/with-timeout.js +47 -0
- package/dist/shared/with-timeout.js.map +1 -0
- package/dist/workflow-engine/do-executor.d.ts +7 -0
- package/dist/workflow-engine/do-executor.js +59 -1
- package/dist/workflow-engine/do-executor.js.map +1 -1
- package/dist/workflow-engine/tasks/call-function.js +68 -1
- package/dist/workflow-engine/tasks/call-function.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.js +1 -0
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +48 -0
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/engine-core.js +5 -1
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/human-input-orchestrator.d.ts +2 -1
- package/dist/workflows/human-input-orchestrator.js +2 -1
- package/dist/workflows/human-input-orchestrator.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/config.test.ts +8 -0
- package/src/activities/__tests__/call-validate.test.ts +137 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +1 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +89 -0
- package/src/activities/call-transform.ts +20 -2
- package/src/activities/call-validate.ts +27 -2
- package/src/activities/discover-mcp-server.ts +7 -28
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +6 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +65 -0
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +105 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +54 -0
- package/src/activities/execute-cursor/__tests__/session-lifecycle.test.ts +122 -1
- package/src/activities/execute-cursor/approval-state.ts +32 -1
- package/src/activities/execute-cursor/hook-script.ts +40 -0
- package/src/activities/execute-cursor/index.ts +86 -17
- package/src/activities/execute-cursor/message-translator.ts +82 -1
- package/src/activities/execute-cursor/prompt-builder.ts +44 -0
- package/src/activities/execute-cursor/session-lifecycle.ts +76 -0
- package/src/activities/execute-cursor/turn-boundary.ts +23 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +103 -2
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +58 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/hitl.ts +51 -0
- package/src/activities/execute-deep-agent/index.ts +11 -1
- package/src/activities/execute-deep-agent/prompt-builder.ts +35 -0
- package/src/activities/execute-deep-agent/setup.ts +31 -0
- package/src/activities/execute-deep-agent/status-builder-shared.ts +8 -0
- package/src/activities/execute-deep-agent/status-builder.ts +7 -0
- package/src/activities/execute-deep-agent/v3-status-builder.ts +7 -0
- package/src/activities/workflow-event-activities.ts +11 -0
- package/src/config.ts +23 -0
- package/src/middleware/__tests__/approval-gate.test.ts +95 -0
- package/src/middleware/approval-gate.ts +37 -0
- package/src/runner-manager.ts +6 -1
- package/src/runner.ts +6 -1
- package/src/shared/__tests__/artifact-storage.test.ts +1 -0
- package/src/shared/__tests__/context-bridge.test.ts +51 -0
- package/src/shared/__tests__/sender-identity.test.ts +92 -0
- package/src/shared/__tests__/with-timeout.test.ts +60 -0
- package/src/shared/approval-policy.ts +41 -2
- package/src/shared/context-bridge.ts +51 -0
- package/src/shared/sender-identity.ts +85 -0
- package/src/shared/with-timeout.ts +53 -0
- package/src/workflow-engine/__tests__/do-executor.test.ts +155 -0
- package/src/workflow-engine/__tests__/tasks/call-function.test.ts +94 -0
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +40 -0
- package/src/workflow-engine/do-executor.ts +65 -1
- package/src/workflow-engine/tasks/call-function.ts +84 -3
- package/src/workflow-engine/tasks/human-input.ts +1 -0
- package/src/workflow-engine/types.ts +50 -0
- package/src/workflows/__tests__/execute-serverless-workflow.test.ts +1 -0
- package/src/workflows/engine-core.ts +5 -1
- package/src/workflows/human-input-orchestrator.ts +2 -1
|
@@ -208,6 +208,64 @@ describe("buildEnhancedSystemPrompt", () => {
|
|
|
208
208
|
expect(prompt).not.toContain("## Workspace");
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
describe("rollover context bridge (DD-013)", () => {
|
|
212
|
+
const base = {
|
|
213
|
+
instructions: "Test",
|
|
214
|
+
provisionResults: [],
|
|
215
|
+
containerRoot: "",
|
|
216
|
+
skillsPromptSection: "",
|
|
217
|
+
workspaceFileRefs: [],
|
|
218
|
+
workspaceRoot: "",
|
|
219
|
+
injectedFiles: [],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
it("appends the bridge as standing session context (every-turn injection)", () => {
|
|
223
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
224
|
+
...base,
|
|
225
|
+
contextBridge: "Subject: Orders\nUser: where is my order?\nAssistant: Shipped.",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(prompt).toContain("## Previous conversation context");
|
|
229
|
+
expect(prompt).toContain("User: where is my order?");
|
|
230
|
+
expect(prompt).toContain("Do not repeat it back");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("omits the section when the session carries no bridge", () => {
|
|
234
|
+
const prompt = buildEnhancedSystemPrompt(base);
|
|
235
|
+
|
|
236
|
+
expect(prompt).not.toContain("## Previous conversation context");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("channel sender identity", () => {
|
|
241
|
+
const base = {
|
|
242
|
+
instructions: "Test",
|
|
243
|
+
provisionResults: [],
|
|
244
|
+
containerRoot: "",
|
|
245
|
+
skillsPromptSection: "",
|
|
246
|
+
workspaceFileRefs: [],
|
|
247
|
+
workspaceRoot: "",
|
|
248
|
+
injectedFiles: [],
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
it("appends the sender as standing session context (every-turn injection)", () => {
|
|
252
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
253
|
+
...base,
|
|
254
|
+
senderIdentity: { value: "15550001111", kind: "whatsapp_phone" },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(prompt).toContain("## Conversation sender");
|
|
258
|
+
expect(prompt).toContain("WhatsApp phone number");
|
|
259
|
+
expect(prompt).toContain("15550001111");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("omits the section when the session carries no identity (console sessions)", () => {
|
|
263
|
+
const prompt = buildEnhancedSystemPrompt(base);
|
|
264
|
+
|
|
265
|
+
expect(prompt).not.toContain("## Conversation sender");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
211
269
|
describe("plan mode", () => {
|
|
212
270
|
const base = {
|
|
213
271
|
instructions: "Test",
|
|
@@ -17,9 +17,11 @@ import { Command } from "@langchain/langgraph";
|
|
|
17
17
|
import type { AgentExecution, AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
18
18
|
import {
|
|
19
19
|
ApprovalAction,
|
|
20
|
+
ApprovalPolicySource,
|
|
20
21
|
ToolCallStatus,
|
|
21
22
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
22
23
|
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
24
|
+
import { POLICY_ENGINE_VERSION, unattendedSkipMessage } from "../../shared/approval-policy.js";
|
|
23
25
|
|
|
24
26
|
// APPROVE_ALL resumes the interrupted tool exactly like APPROVE. Its
|
|
25
27
|
// "auto-approve the rest of the run" effect is realized in setup.ts (the
|
|
@@ -233,3 +235,52 @@ export function reconcileNonExecutingDecisions(status: AgentExecutionStatus): vo
|
|
|
233
235
|
apply(subAgent.messages);
|
|
234
236
|
}
|
|
235
237
|
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Terminalize every tool call the approval gate auto-skipped under UNATTENDED
|
|
241
|
+
* approval mode — the sibling of {@link reconcileNonExecutingDecisions} for
|
|
242
|
+
* skips that have no human decision behind them.
|
|
243
|
+
*
|
|
244
|
+
* The gate (the single writer of the registry) records each auto-skipped
|
|
245
|
+
* tool-call id at the moment it returns the skip ToolMessage; this reconciler
|
|
246
|
+
* (the single writer of the terminal row) folds each id into:
|
|
247
|
+
* - `status = TOOL_CALL_SKIPPED` — the tool did not run, whatever transient
|
|
248
|
+
* status the stream left behind (COMPLETED from a tool_finished that carried
|
|
249
|
+
* the skip message, or RUNNING when no tool events fired);
|
|
250
|
+
* - `approval_policy_source = UNATTENDED_SKIP` — the resolution layer,
|
|
251
|
+
* overriding the gating-layer source stamped at tool-start (the
|
|
252
|
+
* AUTO_APPROVE_ALL precedent: layer-4 resolutions own the resolved call);
|
|
253
|
+
* - a result backfilled from {@link unattendedSkipMessage} when the stream
|
|
254
|
+
* delivered none, so the transcript row is never blank.
|
|
255
|
+
*
|
|
256
|
+
* `approval_action` / `approved_by` are deliberately NOT touched — those are
|
|
257
|
+
* server-owned fields recording HUMAN decisions only (DD-014 D-e). No
|
|
258
|
+
* approval-request event exists for these calls, so the pending-approvals
|
|
259
|
+
* projection stays empty by construction. Idempotent: re-running re-resolves
|
|
260
|
+
* identically. Covers sub-agent transcripts because sub-agent gates inherit
|
|
261
|
+
* the parent's registry instance.
|
|
262
|
+
*/
|
|
263
|
+
export function reconcileUnattendedSkips(
|
|
264
|
+
status: AgentExecutionStatus,
|
|
265
|
+
unattendedSkips: ReadonlySet<string> | undefined,
|
|
266
|
+
): void {
|
|
267
|
+
if (!unattendedSkips || unattendedSkips.size === 0) return;
|
|
268
|
+
|
|
269
|
+
const apply = (messages: readonly AgentMessage[]): void => {
|
|
270
|
+
for (const msg of messages) {
|
|
271
|
+
for (const tc of msg.toolCalls) {
|
|
272
|
+
if (!unattendedSkips.has(tc.id)) continue;
|
|
273
|
+
tc.status = ToolCallStatus.TOOL_CALL_SKIPPED;
|
|
274
|
+
tc.approvalPolicySource = ApprovalPolicySource.UNATTENDED_SKIP;
|
|
275
|
+
tc.policyEngineVersion = POLICY_ENGINE_VERSION;
|
|
276
|
+
tc.isStreaming = false;
|
|
277
|
+
if (!tc.result) tc.result = unattendedSkipMessage(tc.name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
apply(status.messages);
|
|
283
|
+
for (const subAgent of status.subAgentExecutions) {
|
|
284
|
+
apply(subAgent.messages);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -47,7 +47,7 @@ import { StatusBuilder } from "./status-builder.js";
|
|
|
47
47
|
import { InlinePublisher } from "./inline-publisher.js";
|
|
48
48
|
import { WriteBackCoordinator } from "../../shared/workspace/writeback-coordinator.js";
|
|
49
49
|
import { processPostStream } from "./post-stream.js";
|
|
50
|
-
import { resolveResumeInput, reconcileNonExecutingDecisions, type GraphStateSnapshot } from "./hitl.js";
|
|
50
|
+
import { resolveResumeInput, reconcileNonExecutingDecisions, reconcileUnattendedSkips, type GraphStateSnapshot } from "./hitl.js";
|
|
51
51
|
import { captureApprovalArtifacts } from "./approval-file-change.js";
|
|
52
52
|
import {
|
|
53
53
|
applyCaptureDecisions,
|
|
@@ -144,6 +144,7 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
144
144
|
toolServerMap: setup.toolServerMap,
|
|
145
145
|
leasedCategories: setup.leasedCategories,
|
|
146
146
|
globalBypass: setup.globalBypass,
|
|
147
|
+
unattended: setup.unattended,
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
const resume = resolveResumeInput(
|
|
@@ -408,6 +409,7 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
408
409
|
toolServerMap: setup.toolServerMap,
|
|
409
410
|
leasedCategories: setup.leasedCategories,
|
|
410
411
|
globalBypass: setup.globalBypass,
|
|
412
|
+
unattended: setup.unattended,
|
|
411
413
|
},
|
|
412
414
|
streamVersion: setup.streamVersion,
|
|
413
415
|
// Mid-run live capture (DD-32 / DD-33): attach file_change_progress
|
|
@@ -465,6 +467,14 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
465
467
|
// completion persists below so the terminal status is what is persisted.
|
|
466
468
|
reconcileNonExecutingDecisions(initialStatus);
|
|
467
469
|
|
|
470
|
+
// Terminalize every tool call the gate auto-skipped under UNATTENDED
|
|
471
|
+
// approval mode (DD-014): the skip has no human decision behind it, so
|
|
472
|
+
// reconcileNonExecutingDecisions cannot see it — this sibling folds the
|
|
473
|
+
// gate's registry into terminal SKIPPED rows with UNATTENDED_SKIP
|
|
474
|
+
// provenance, whatever transient status the stream left behind. No-op
|
|
475
|
+
// for interactive executions (empty registry).
|
|
476
|
+
reconcileUnattendedSkips(initialStatus, setup.unattendedSkips);
|
|
477
|
+
|
|
468
478
|
// Turn boundary (capture mode): capture the candidate change set from the
|
|
469
479
|
// git diff and author CANDIDATE_CAPTURED, then stamp the flowed file-edit
|
|
470
480
|
// rows with the change set id — they stay visible in place as
|
|
@@ -10,6 +10,11 @@ import { relative } from "node:path";
|
|
|
10
10
|
import { InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
11
11
|
import type { ProvisionResult, GitMetadata } from "../../shared/workspace/types.js";
|
|
12
12
|
import { SourceType } from "../../shared/workspace/types.js";
|
|
13
|
+
import { formatContextBridgeText } from "../../shared/context-bridge.js";
|
|
14
|
+
import {
|
|
15
|
+
formatSenderIdentityText,
|
|
16
|
+
type SenderIdentity,
|
|
17
|
+
} from "../../shared/sender-identity.js";
|
|
13
18
|
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
14
19
|
import {
|
|
15
20
|
buildImplementPlanDirective,
|
|
@@ -106,6 +111,24 @@ export interface PromptBuilderInput {
|
|
|
106
111
|
* message itself is just a short label ("Build from plan").
|
|
107
112
|
*/
|
|
108
113
|
buildFromPlan?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Rollover context bridge (cloud DD-013): a digest of the previous
|
|
116
|
+
* session's conversation, read from `SessionSpec.metadata`. Injected on
|
|
117
|
+
* EVERY turn by design: the native system prompt is rebuilt per
|
|
118
|
+
* invocation (never checkpointed with the message history), so a
|
|
119
|
+
* first-turn-only injection would vanish from turn 2 onward. The bridge
|
|
120
|
+
* is standing session context, like skills.
|
|
121
|
+
*/
|
|
122
|
+
contextBridge?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Channel sender identity (attribution, not authorization): the
|
|
125
|
+
* provider-verified identifier of the person on the channel, read from
|
|
126
|
+
* `SessionSpec.metadata`. Injected on EVERY turn like the bridge — the
|
|
127
|
+
* native system prompt is rebuilt per invocation, and the sender is
|
|
128
|
+
* constant for the session's lifetime (channel sessions are keyed
|
|
129
|
+
* per-sender).
|
|
130
|
+
*/
|
|
131
|
+
senderIdentity?: SenderIdentity;
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
export interface InjectedFile {
|
|
@@ -147,6 +170,18 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
147
170
|
prompt += buildInjectedFilesSection(input.injectedFiles);
|
|
148
171
|
}
|
|
149
172
|
|
|
173
|
+
if (input.senderIdentity) {
|
|
174
|
+
prompt +=
|
|
175
|
+
"\n\n## Conversation sender\n\n" +
|
|
176
|
+
formatSenderIdentityText(input.senderIdentity);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (input.contextBridge) {
|
|
180
|
+
prompt +=
|
|
181
|
+
"\n\n## Previous conversation context\n\n" +
|
|
182
|
+
formatContextBridgeText(input.contextBridge);
|
|
183
|
+
}
|
|
184
|
+
|
|
150
185
|
prompt += RESPONSE_RULES;
|
|
151
186
|
prompt += SUB_AGENT_RULES;
|
|
152
187
|
|
|
@@ -23,6 +23,8 @@ import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
|
23
23
|
import type { Config } from "../../config.js";
|
|
24
24
|
import type { StigmerClient } from "../../client/stigmer-client.js";
|
|
25
25
|
import { createCheckpointer } from "../../shared/checkpointer/factory.js";
|
|
26
|
+
import { readContextBridge } from "../../shared/context-bridge.js";
|
|
27
|
+
import { readSenderIdentity } from "../../shared/sender-identity.js";
|
|
26
28
|
import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
|
|
27
29
|
import { resolveMcpServers } from "../../shared/mcp-resolver.js";
|
|
28
30
|
import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
|
|
@@ -57,6 +59,7 @@ import {
|
|
|
57
59
|
import {
|
|
58
60
|
mergeApprovalPolicies,
|
|
59
61
|
deriveActiveLeases,
|
|
62
|
+
isUnattendedApprovalMode,
|
|
60
63
|
type MergedToolPolicy,
|
|
61
64
|
} from "../../shared/approval-policy.js";
|
|
62
65
|
import type { ToolApprovalCategory } from "../../shared/tool-kind.js";
|
|
@@ -123,6 +126,20 @@ export interface SetupResult {
|
|
|
123
126
|
* the gate (see ActiveLeases / deriveActiveLeases).
|
|
124
127
|
*/
|
|
125
128
|
readonly globalBypass: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Unattended approval mode (ExecutionConfig.approval_mode = UNATTENDED,
|
|
131
|
+
* stamped by approver-less surfaces — channels, guest shares). The gate
|
|
132
|
+
* resolves gated tools as automatic skips instead of interrupting; the
|
|
133
|
+
* builders never seed WAITING_APPROVAL. See isUnattendedApprovalMode.
|
|
134
|
+
*/
|
|
135
|
+
readonly unattended: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Tool-call ids the gate auto-skipped under {@link unattended} this turn.
|
|
138
|
+
* Written ONLY by the approval gate (parent + inherited sub-agent gates
|
|
139
|
+
* share this instance); read by reconcileUnattendedSkips after the stream
|
|
140
|
+
* to stamp terminal SKIPPED rows. Empty set when not unattended.
|
|
141
|
+
*/
|
|
142
|
+
readonly unattendedSkips: Set<string>;
|
|
126
143
|
readonly hasStructuredOutput: boolean;
|
|
127
144
|
readonly streamVersion: "v2" | "v3";
|
|
128
145
|
/**
|
|
@@ -386,6 +403,8 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
386
403
|
injectedFiles,
|
|
387
404
|
interactionMode: execution.spec!.executionConfig?.interactionMode,
|
|
388
405
|
buildFromPlan: execution.spec!.executionConfig?.buildFromPlan,
|
|
406
|
+
contextBridge: readContextBridge(session.spec!.metadata),
|
|
407
|
+
senderIdentity: readSenderIdentity(session.spec!.metadata),
|
|
389
408
|
});
|
|
390
409
|
|
|
391
410
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
|
@@ -436,6 +455,14 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
436
455
|
leases,
|
|
437
456
|
);
|
|
438
457
|
|
|
458
|
+
// Unattended approval mode (DD-014): approver-less surfaces (channels,
|
|
459
|
+
// guest shares) stamp APPROVAL_MODE_UNATTENDED, and the gate resolves
|
|
460
|
+
// gated tools as automatic skips instead of interrupting. The registry is
|
|
461
|
+
// the gate→reconciler channel for the skipped tool-call ids; one instance
|
|
462
|
+
// is shared with sub-agent gates via the inherited config.
|
|
463
|
+
const unattended = isUnattendedApprovalMode(execution);
|
|
464
|
+
const unattendedSkips = new Set<string>();
|
|
465
|
+
|
|
439
466
|
// The approval gate config is the single source of truth for HITL gating,
|
|
440
467
|
// built once and inherited verbatim by sub-agents (so a mutating tool inside
|
|
441
468
|
// a sub-agent is gated identically to one in the parent). Null under the
|
|
@@ -472,6 +499,8 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
472
499
|
isCapturablePath,
|
|
473
500
|
captureIgnored: captureMode && !!artifactStorage,
|
|
474
501
|
recordBlockedSecret: (rawPath: string) => casObserver.recordBlockedSecret(rawPath),
|
|
502
|
+
unattended,
|
|
503
|
+
unattendedSkips,
|
|
475
504
|
}
|
|
476
505
|
: null;
|
|
477
506
|
|
|
@@ -644,6 +673,8 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
644
673
|
toolServerMap,
|
|
645
674
|
leasedCategories: leases.categories,
|
|
646
675
|
globalBypass,
|
|
676
|
+
unattended,
|
|
677
|
+
unattendedSkips,
|
|
647
678
|
hasStructuredOutput: !!outputSchema,
|
|
648
679
|
streamVersion,
|
|
649
680
|
casObserver,
|
|
@@ -157,6 +157,14 @@ export interface ApprovalProvenanceInputs {
|
|
|
157
157
|
readonly leasedCategories?: ReadonlySet<ToolApprovalCategory>;
|
|
158
158
|
/** Pre-armed spec.auto_approve_all — the one whole-run global bypass. */
|
|
159
159
|
readonly globalBypass: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Unattended approval mode (ExecutionConfig.approval_mode = UNATTENDED):
|
|
162
|
+
* the gate auto-skips gated calls instead of interrupting, so the builders
|
|
163
|
+
* must NOT seed WAITING_APPROVAL rows or flip the phase to
|
|
164
|
+
* WAITING_FOR_APPROVAL — the row streams as RUNNING and the post-stream
|
|
165
|
+
* `reconcileUnattendedSkips` stamps the terminal SKIPPED + provenance.
|
|
166
|
+
*/
|
|
167
|
+
readonly unattended?: boolean;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
/** Shared empty set so a provider without leases allocates nothing per call. */
|
|
@@ -319,6 +319,13 @@ export class StatusBuilder {
|
|
|
319
319
|
return { requiresApproval: false, message: "", serverSlug };
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
// Unattended mode: the gate auto-skips instead of interrupting, so no row
|
|
323
|
+
// is ever WAITING_APPROVAL and the phase never flips — the streamed row
|
|
324
|
+
// runs to its skip result and reconcileUnattendedSkips terminalizes it.
|
|
325
|
+
if (this.approvalProvider.unattended) {
|
|
326
|
+
return { requiresApproval: false, message: "", serverSlug };
|
|
327
|
+
}
|
|
328
|
+
|
|
322
329
|
if (serverSlug) {
|
|
323
330
|
const key = `${serverSlug}/${toolName}`;
|
|
324
331
|
const policy = this.approvalProvider.policies.get(key);
|
|
@@ -419,6 +419,13 @@ export class V3StatusBuilder implements ExecutionStatusWriter {
|
|
|
419
419
|
return { requiresApproval: false, message: "", serverSlug };
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
+
// Unattended mode: the gate auto-skips instead of interrupting, so no row
|
|
423
|
+
// is ever WAITING_APPROVAL and the phase never flips — the streamed row
|
|
424
|
+
// runs to its skip result and reconcileUnattendedSkips terminalizes it.
|
|
425
|
+
if (this.approvalProvider.unattended) {
|
|
426
|
+
return { requiresApproval: false, message: "", serverSlug };
|
|
427
|
+
}
|
|
428
|
+
|
|
422
429
|
if (serverSlug) {
|
|
423
430
|
const key = `${serverSlug}/${toolName}`;
|
|
424
431
|
const policy = this.approvalProvider.policies.get(key);
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
ArtifactCreatedPayloadSchema,
|
|
35
35
|
HumanInputOutcomeInfoSchema,
|
|
36
36
|
} from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/event_pb";
|
|
37
|
+
import { ApiResourceAuditActorSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
|
|
37
38
|
import { WorkflowExecutionStatusSchema, WorkflowTaskSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
38
39
|
import {
|
|
39
40
|
WorkflowExecutionUpdateStatusInputSchema,
|
|
@@ -221,6 +222,7 @@ export function toProtoEvent(desc: WorkflowEventDescriptor): WorkflowExecutionEv
|
|
|
221
222
|
case: "taskStarted",
|
|
222
223
|
value: create(TaskStartedPayloadSchema, {
|
|
223
224
|
taskKind: TASK_KIND_MAP[desc.taskKind] ?? 0,
|
|
225
|
+
inputSummary: toJsonObject(desc.inputSummary),
|
|
224
226
|
attemptNumber: desc.attemptNumber,
|
|
225
227
|
}),
|
|
226
228
|
};
|
|
@@ -233,6 +235,7 @@ export function toProtoEvent(desc: WorkflowEventDescriptor): WorkflowExecutionEv
|
|
|
233
235
|
value: create(TaskCompletedPayloadSchema, {
|
|
234
236
|
taskKind: TASK_KIND_MAP[desc.taskKind] ?? 0,
|
|
235
237
|
durationMs: BigInt(desc.durationMs),
|
|
238
|
+
outputSummary: toJsonObject(desc.outputSummary),
|
|
236
239
|
costMicros: BigInt(desc.costMicros),
|
|
237
240
|
tokensUsed: BigInt(desc.tokensUsed),
|
|
238
241
|
}),
|
|
@@ -307,6 +310,14 @@ export function toProtoEvent(desc: WorkflowEventDescriptor): WorkflowExecutionEv
|
|
|
307
310
|
case: "approvalResolved",
|
|
308
311
|
value: create(ApprovalResolvedPayloadSchema, {
|
|
309
312
|
resolvedBy: desc.resolvedBy,
|
|
313
|
+
resolvedByActor: desc.resolvedByActor
|
|
314
|
+
? create(ApiResourceAuditActorSchema, {
|
|
315
|
+
id: desc.resolvedByActor.id,
|
|
316
|
+
displayName: desc.resolvedByActor.display_name ?? "",
|
|
317
|
+
email: desc.resolvedByActor.email ?? "",
|
|
318
|
+
avatar: desc.resolvedByActor.avatar ?? "",
|
|
319
|
+
})
|
|
320
|
+
: undefined,
|
|
310
321
|
comment: desc.comment,
|
|
311
322
|
waitDurationMs: BigInt(desc.waitDurationMs),
|
|
312
323
|
}),
|
package/src/config.ts
CHANGED
|
@@ -37,6 +37,17 @@ import { homedir, tmpdir } from "node:os";
|
|
|
37
37
|
*/
|
|
38
38
|
export const DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS = 180_000;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Default bound for Cursor Agent.create/Agent.resume (ms). These SDK calls
|
|
42
|
+
* have no timeout of their own; a degraded transport (dead proxy connection,
|
|
43
|
+
* stale HTTP/2 session) hangs them forever — which surfaces as an opaque
|
|
44
|
+
* Temporal heartbeat timeout minutes later instead of an actionable error.
|
|
45
|
+
* 120s comfortably covers a slow create (token exchange + workspace
|
|
46
|
+
* registration) while failing fast enough to diagnose. Single source of truth
|
|
47
|
+
* for env-loaded and options-mapped config (mirrors the stall timeout above).
|
|
48
|
+
*/
|
|
49
|
+
export const DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS = 120_000;
|
|
50
|
+
|
|
40
51
|
// Re-exported so the runner/manager options mappers default the lock-wait
|
|
41
52
|
// bound from the same constant the lock module owns (no drift across the
|
|
42
53
|
// three construction sites — mirrors DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS).
|
|
@@ -89,6 +100,13 @@ export interface Config {
|
|
|
89
100
|
* heartbeatTimeout (process liveness) and the 30s keep-alive heartbeat.
|
|
90
101
|
*/
|
|
91
102
|
readonly cursorStreamStallTimeoutMs: number;
|
|
103
|
+
/**
|
|
104
|
+
* Bound (milliseconds) for Cursor Agent.create/Agent.resume. On expiry the
|
|
105
|
+
* execution fails fast with an explicit transport diagnosis instead of
|
|
106
|
+
* hanging into Temporal's heartbeat timeout. See
|
|
107
|
+
* {@link DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS} for why this exists.
|
|
108
|
+
*/
|
|
109
|
+
readonly agentResolveTimeoutMs: number;
|
|
92
110
|
/**
|
|
93
111
|
* Max wait (milliseconds) for the per-workspace turn lock before failing
|
|
94
112
|
* the execution with a "workspace is in use by another session" error.
|
|
@@ -174,6 +192,10 @@ export function loadConfig(): Config {
|
|
|
174
192
|
? parseInt(process.env.WORKSPACE_LOCK_TIMEOUT_MS, 10)
|
|
175
193
|
: DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS;
|
|
176
194
|
|
|
195
|
+
const agentResolveTimeoutMs = process.env.CURSOR_AGENT_RESOLVE_TIMEOUT_MS
|
|
196
|
+
? parseInt(process.env.CURSOR_AGENT_RESOLVE_TIMEOUT_MS, 10)
|
|
197
|
+
: DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS;
|
|
198
|
+
|
|
177
199
|
return {
|
|
178
200
|
taskQueue,
|
|
179
201
|
temporalAddress,
|
|
@@ -191,6 +213,7 @@ export function loadConfig(): Config {
|
|
|
191
213
|
checkpointerProxyEndpoint,
|
|
192
214
|
primaryModel,
|
|
193
215
|
cursorStreamStallTimeoutMs,
|
|
216
|
+
agentResolveTimeoutMs,
|
|
194
217
|
workspaceLockTimeoutMs,
|
|
195
218
|
};
|
|
196
219
|
}
|
|
@@ -784,4 +784,99 @@ describe("ApprovalGateMiddleware", () => {
|
|
|
784
784
|
expect(result).toBeInstanceOf(ToolMessage);
|
|
785
785
|
expect((result as ToolMessage).content).toContain("unknown action");
|
|
786
786
|
});
|
|
787
|
+
|
|
788
|
+
describe("unattended approval mode (DD-014)", () => {
|
|
789
|
+
const gatedMcpPolicies = new Map<string, MergedToolPolicy>([
|
|
790
|
+
["srv/gated_tool", {
|
|
791
|
+
toolName: "gated_tool",
|
|
792
|
+
mcpServerSlug: "srv",
|
|
793
|
+
requiresApproval: true,
|
|
794
|
+
approvalMessage: "Run gated_tool",
|
|
795
|
+
source: "classifier_default",
|
|
796
|
+
}],
|
|
797
|
+
]);
|
|
798
|
+
|
|
799
|
+
it("skips a gated MCP tool without interrupting and records it in the registry", async () => {
|
|
800
|
+
const unattendedSkips = new Set<string>();
|
|
801
|
+
const mw = createApprovalGateMiddleware(makeConfig({
|
|
802
|
+
policies: gatedMcpPolicies,
|
|
803
|
+
toolServerMap: new Map([["gated_tool", "srv"]]),
|
|
804
|
+
unattended: true,
|
|
805
|
+
unattendedSkips,
|
|
806
|
+
}));
|
|
807
|
+
|
|
808
|
+
const handler = vi.fn(passthrough);
|
|
809
|
+
const result = await mw.wrapToolCall!(
|
|
810
|
+
makeRequest({ name: "gated_tool", args: { target: "prod" } }),
|
|
811
|
+
handler,
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
// Gateway invariant: the side effect never ran, and no interrupt was
|
|
815
|
+
// raised — the turn continues instead of parking WAITING_FOR_APPROVAL.
|
|
816
|
+
expect(handler).not.toHaveBeenCalled();
|
|
817
|
+
expect(mockedInterrupt).not.toHaveBeenCalled();
|
|
818
|
+
expect((result as ToolMessage).content).toContain("skipped automatically");
|
|
819
|
+
expect(unattendedSkips.has("call_abc123")).toBe(true);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it("skips a gated built-in (shell) the same way", async () => {
|
|
823
|
+
const unattendedSkips = new Set<string>();
|
|
824
|
+
const mw = createApprovalGateMiddleware(makeConfig({
|
|
825
|
+
unattended: true,
|
|
826
|
+
unattendedSkips,
|
|
827
|
+
}));
|
|
828
|
+
|
|
829
|
+
const handler = vi.fn(passthrough);
|
|
830
|
+
const result = await mw.wrapToolCall!(
|
|
831
|
+
makeRequest({ name: "shell", args: { command: "rm -rf /" } }),
|
|
832
|
+
handler,
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
expect(handler).not.toHaveBeenCalled();
|
|
836
|
+
expect(mockedInterrupt).not.toHaveBeenCalled();
|
|
837
|
+
expect((result as ToolMessage).content).toContain("skipped automatically");
|
|
838
|
+
expect(unattendedSkips.has("call_abc123")).toBe(true);
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it("still runs tools the policy chain auto-approved (un-gating stays the operator lever)", async () => {
|
|
842
|
+
// The operator's tool_approval_overrides path: an un-gated tool is
|
|
843
|
+
// absent from the policy map, so unattended mode never touches it.
|
|
844
|
+
const unattendedSkips = new Set<string>();
|
|
845
|
+
const mw = createApprovalGateMiddleware(makeConfig({
|
|
846
|
+
toolServerMap: new Map([["book_appointment", "clinic"]]),
|
|
847
|
+
unattended: true,
|
|
848
|
+
unattendedSkips,
|
|
849
|
+
}));
|
|
850
|
+
|
|
851
|
+
const result = await mw.wrapToolCall!(
|
|
852
|
+
makeRequest({ name: "book_appointment", args: { slot: "mon-10" } }),
|
|
853
|
+
passthrough,
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
expect((result as ToolMessage).content).toBe("tool result");
|
|
857
|
+
expect(unattendedSkips.size).toBe(0);
|
|
858
|
+
expect(mockedInterrupt).not.toHaveBeenCalled();
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it("never emits an execution receipt for an unattended skip (no side effect)", async () => {
|
|
862
|
+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
863
|
+
try {
|
|
864
|
+
const mw = createApprovalGateMiddleware(makeConfig({
|
|
865
|
+
policies: gatedMcpPolicies,
|
|
866
|
+
toolServerMap: new Map([["gated_tool", "srv"]]),
|
|
867
|
+
unattended: true,
|
|
868
|
+
unattendedSkips: new Set<string>(),
|
|
869
|
+
}));
|
|
870
|
+
|
|
871
|
+
await mw.wrapToolCall!(makeRequest({ name: "gated_tool" }), passthrough);
|
|
872
|
+
|
|
873
|
+
const receipts = logSpy.mock.calls
|
|
874
|
+
.map((c) => String(c[0]))
|
|
875
|
+
.filter((line) => line.includes("[hitl-gateway] receipt"));
|
|
876
|
+
expect(receipts).toHaveLength(0);
|
|
877
|
+
} finally {
|
|
878
|
+
logSpy.mockRestore();
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
});
|
|
787
882
|
});
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
type PolicySource,
|
|
52
52
|
POLICY_ENGINE_VERSION,
|
|
53
53
|
resolveApprovalMessage,
|
|
54
|
+
unattendedSkipMessage,
|
|
54
55
|
} from "../shared/approval-policy.js";
|
|
55
56
|
import { toolApprovalCategory, type ToolApprovalCategory } from "../shared/tool-kind.js";
|
|
56
57
|
import { extractFilePath } from "../shared/file-tools.js";
|
|
@@ -121,6 +122,26 @@ export interface ApprovalGateConfig {
|
|
|
121
122
|
* secret; the CONTENT never leaves the workspace). Absent ⇒ nothing recorded.
|
|
122
123
|
*/
|
|
123
124
|
readonly recordBlockedSecret?: (rawPath: string) => void;
|
|
125
|
+
/**
|
|
126
|
+
* Unattended approval mode (ExecutionConfig.approval_mode = UNATTENDED):
|
|
127
|
+
* the creating surface — a messaging channel, a guest share — has no
|
|
128
|
+
* approver, so a gated tool is resolved as an automatic SKIP (the model is
|
|
129
|
+
* told to adapt) instead of `interrupt()`. The execution never enters
|
|
130
|
+
* WAITING_FOR_APPROVAL. What is gated is unchanged — only the resolution
|
|
131
|
+
* differs; an operator un-gates a specific tool for the agent via
|
|
132
|
+
* tool_approval_overrides, not by weakening this mode.
|
|
133
|
+
*/
|
|
134
|
+
readonly unattended?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Registry of tool-call ids this gate auto-skipped under {@link unattended}
|
|
137
|
+
* — the gate is the single WRITER; `reconcileUnattendedSkips` (hitl.ts) is
|
|
138
|
+
* the reader that folds each id into a terminal TOOL_CALL_SKIPPED row with
|
|
139
|
+
* UNATTENDED_SKIP provenance after the stream. In-process, per-execution
|
|
140
|
+
* state keyed by the framework's own tool-call id (direct identity, no
|
|
141
|
+
* matching); inherited verbatim by sub-agent gates so their skips land in
|
|
142
|
+
* the same registry.
|
|
143
|
+
*/
|
|
144
|
+
readonly unattendedSkips?: Set<string>;
|
|
124
145
|
}
|
|
125
146
|
|
|
126
147
|
interface ApprovalDecision {
|
|
@@ -249,6 +270,22 @@ export function createApprovalGateMiddleware(
|
|
|
249
270
|
return await handler(request);
|
|
250
271
|
}
|
|
251
272
|
|
|
273
|
+
// Unattended surfaces (channels, guest shares) have no approver, so a
|
|
274
|
+
// gate that would interrupt() here resolves as an automatic SKIP: the
|
|
275
|
+
// tool does NOT run (the gateway invariant holds — no side effect
|
|
276
|
+
// without a backing authorization), the model is told to adapt in plain
|
|
277
|
+
// language, and the turn continues to normal completion instead of
|
|
278
|
+
// parking in WAITING_FOR_APPROVAL forever. The registry entry lets the
|
|
279
|
+
// post-stream reconciler stamp the terminal SKIPPED row + provenance.
|
|
280
|
+
if (config.unattended) {
|
|
281
|
+
config.unattendedSkips?.add(toolCall.id);
|
|
282
|
+
return new ToolMessage({
|
|
283
|
+
content: unattendedSkipMessage(toolName),
|
|
284
|
+
tool_call_id: toolCall.id,
|
|
285
|
+
name: toolName,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
252
289
|
const approvalRequest = {
|
|
253
290
|
tool_call_id: toolCall.id,
|
|
254
291
|
tool_name: toolName,
|
package/src/runner-manager.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "@temporalio/worker";
|
|
25
25
|
import type { PayloadCodec } from "@temporalio/common";
|
|
26
26
|
import type { Config } from "./config.js";
|
|
27
|
-
import { DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS, DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./config.js";
|
|
27
|
+
import { DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS, DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./config.js";
|
|
28
28
|
import type { WorkerActivities } from "./worker.js";
|
|
29
29
|
import { resolveWorkflowSource, OTEL_WORKFLOW_INTERCEPTOR_MODULE } from "./workflow-source.js";
|
|
30
30
|
import { resolveRunnerBootstrap, refreshRunnerAccessToken } from "./bootstrap.js";
|
|
@@ -91,6 +91,9 @@ export interface RunnerManagerOptions {
|
|
|
91
91
|
/** No-progress bound for the Cursor harness stream (ms). @default 180000 */
|
|
92
92
|
readonly cursorStreamStallTimeoutMs?: number;
|
|
93
93
|
|
|
94
|
+
/** Bound for Cursor Agent.create/resume (ms). @default 120000 */
|
|
95
|
+
readonly agentResolveTimeoutMs?: number;
|
|
96
|
+
|
|
94
97
|
/** Max wait for the per-workspace turn lock (ms). @default 900000 */
|
|
95
98
|
readonly workspaceLockTimeoutMs?: number;
|
|
96
99
|
|
|
@@ -598,6 +601,8 @@ export function mapManagerOptionsToConfig(
|
|
|
598
601
|
primaryModel: options.primaryModel ?? "gpt-4.1",
|
|
599
602
|
cursorStreamStallTimeoutMs:
|
|
600
603
|
options.cursorStreamStallTimeoutMs ?? DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS,
|
|
604
|
+
agentResolveTimeoutMs:
|
|
605
|
+
options.agentResolveTimeoutMs ?? DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS,
|
|
601
606
|
workspaceLockTimeoutMs:
|
|
602
607
|
options.workspaceLockTimeoutMs ?? DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS,
|
|
603
608
|
};
|