@kylecheng3146/agent-ops 0.10.0 → 0.11.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 +3 -2
- package/dist/packages/cli/src/args.js +18 -53
- package/dist/packages/cli/src/bin.js +28 -29
- package/dist/packages/cli/src/cli.js +25 -17
- package/dist/packages/cli/src/codex-loop-process.js +0 -6
- package/dist/packages/cli/src/commands/advance.js +2 -1
- package/dist/packages/cli/src/commands/parallel.js +1 -1
- package/dist/packages/cli/src/commands/review.js +8 -11
- package/dist/packages/cli/src/commands/run.js +12 -3
- package/dist/packages/cli/src/commands/task.js +6 -50
- package/dist/packages/cli/src/commands/verify.js +7 -3
- package/dist/packages/cli/src/context.js +22 -4
- package/dist/packages/cli/src/default-base.js +26 -0
- package/dist/packages/cli/src/hook-process.js +19 -83
- package/dist/packages/cli/src/office-entry.js +8 -4
- package/dist/packages/cli/src/owned-run-step.js +4 -4
- package/dist/packages/cli/src/parallel-deps.js +0 -2
- package/dist/packages/cli/src/run-deps.js +12 -9
- package/dist/packages/cli/src/run-entry.js +123 -19
- package/dist/packages/cli/src/run-setup-entry.js +2 -2
- package/dist/runtime/src/adapters/agy/config.js +2 -3
- package/dist/runtime/src/adapters/claude/config.js +2 -2
- package/dist/runtime/src/adapters/codex/config.js +2 -5
- package/dist/runtime/src/hooks/completion-gate.js +0 -15
- package/dist/runtime/src/install/harness.js +7 -4
- package/dist/runtime/src/install/plan.js +3 -3
- package/dist/runtime/src/install/profiles.js +0 -2
- package/dist/runtime/src/office/scene.js +1 -1
- package/dist/runtime/src/office/server.js +43 -16
- package/dist/runtime/src/office/snapshot.js +5 -2
- package/dist/runtime/src/parallel/finish.js +139 -65
- package/dist/runtime/src/parallel/manage.js +0 -3
- package/dist/runtime/src/parallel/service.js +5 -1
- package/dist/runtime/src/review/execute.js +77 -11
- package/dist/runtime/src/review/packet.js +17 -6
- package/dist/runtime/src/review/runner.js +2 -0
- package/dist/runtime/src/run/controls.js +99 -1
- package/dist/runtime/src/run/hosts/codex.js +28 -1
- package/dist/runtime/src/run/hosts/protocol.js +5 -2
- package/dist/runtime/src/run/integration.js +35 -13
- package/dist/runtime/src/run/policy.js +65 -10
- package/dist/runtime/src/run/scheduler.js +10 -5
- package/dist/runtime/src/run/service.js +68 -21
- package/dist/runtime/src/run/supervisor.js +11 -2
- package/dist/runtime/src/run/transport.js +1 -0
- package/dist/runtime/src/run/verification.js +2 -2
- package/dist/runtime/src/schema/validate.js +10 -5
- package/dist/runtime/src/security/permissions.js +85 -0
- package/dist/runtime/src/task/completion.js +4 -42
- package/dist/runtime/src/task/contract.js +11 -0
- package/dist/runtime/src/task/service.js +34 -57
- package/dist/runtime/src/task/store.js +3 -7
- package/dist/runtime/src/verify/acceptance-coverage.js +2 -3
- package/dist/runtime/src/verify/acceptance-protocol.js +4 -3
- package/dist/runtime/src/verify/acceptance-replay.js +4 -7
- package/dist/runtime/src/verify/service.js +6 -23
- package/dist/runtime/src/verify/source-fingerprint.js +17 -4
- package/docs/en/guides/configuration.md +8 -4
- package/docs/en/guides/task-debug.md +39 -121
- package/docs/en/spec/acceptance-and-evidence.md +31 -19
- package/docs/en/spec/harness-adapters.md +35 -12
- package/docs/en/spec/loop-engineering.md +40 -6
- package/docs/en/spec/review.md +6 -0
- package/docs/zh-TW/guides/configuration.md +6 -3
- package/docs/zh-TW/guides/task-debug.md +27 -66
- package/docs/zh-TW/spec/acceptance-and-evidence.md +19 -18
- package/docs/zh-TW/spec/harness-adapters.md +28 -9
- package/docs/zh-TW/spec/loop-engineering.md +14 -3
- package/docs/zh-TW/spec/review.md +5 -0
- package/package.json +1 -1
- package/schemas/config.schema.json +1 -21
- package/dist/packages/cli/src/debug-intake.js +0 -245
- package/dist/runtime/src/task/debug-intake.js +0 -15
- package/dist/runtime/src/task/debug-state.js +0 -129
- package/dist/runtime/src/task/debug-user-event.js +0 -95
- package/dist/runtime/src/task/debug.js +0 -612
|
@@ -47,10 +47,10 @@ async function withEscape(envelope, escape, service) {
|
|
|
47
47
|
...facts.map(fact => `- ${fact.path}: ${fact.surface}`),
|
|
48
48
|
...(proof === undefined ? [] : [proof.status === "proven"
|
|
49
49
|
? `Proof: rule ${proof.rule} replays at the escaped commit; backtest ${proof.backtest.fires}/${proof.backtest.commits} commits (${proof.backtest.rate}).`
|
|
50
|
-
: proof.status === "unproven" ? `Proof: ${proof.code} - ${proof.reason}
|
|
50
|
+
: proof.status === "unproven" ? `Proof: ${proof.code} - ${proof.reason} Advisory: finish does not wait for it; add a tracked rule to prove it.`
|
|
51
51
|
: proof.status === "lesson" ? `Lesson (unproven, does not block): ${proof.reason}`
|
|
52
|
-
: proof.check === null ? "Proof: ESCAPE_RULE_UNPROVEN - name the red-to-green check with --escape-promote <runnerId:checkId>."
|
|
53
|
-
: `Proof: promoted check ${proof.check}
|
|
52
|
+
: proof.check === null ? "Proof: ESCAPE_RULE_UNPROVEN (advisory) - name the red-to-green check with --escape-promote <runnerId:checkId>."
|
|
53
|
+
: `Proof: promoted check ${proof.check} should be a proven redCheckId of this task (advisory). Config proposal (not written):\n${JSON.stringify(proposal, null, 2)}`]),
|
|
54
54
|
`Ledger record: ${escape.path} (commit it with the fix).`,
|
|
55
55
|
escape.blame.length === 0 ? "Blame hints: none (the fix only adds lines; an omission has no author)."
|
|
56
56
|
: `Blame hints (a hint only): ${escape.blame.map(hint => `${hint.commit.slice(0, 12)} (${hint.lines} line${hint.lines === 1 ? "" : "s"})`).join(", ")}`
|
|
@@ -98,7 +98,7 @@ function taskAction(args) {
|
|
|
98
98
|
"complete",
|
|
99
99
|
"create",
|
|
100
100
|
"export",
|
|
101
|
-
"status", "revise", "replan", "pin-finding"
|
|
101
|
+
"status", "revise", "replan", "pin-finding"
|
|
102
102
|
].includes(args.action)) {
|
|
103
103
|
throw new AgentOpsError("TASK_ACTION_INVALID", "A supported task action is required.");
|
|
104
104
|
}
|
|
@@ -110,7 +110,7 @@ function taskEnvelope(action, code, message, record) {
|
|
|
110
110
|
message,
|
|
111
111
|
record,
|
|
112
112
|
contractHash: taskContractHash(record.task),
|
|
113
|
-
text: renderTaskMarkdown(record) + "\nContract: " + taskContractHash(record.task)
|
|
113
|
+
text: renderTaskMarkdown(record) + "\nContract: " + taskContractHash(record.task)
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
function renderTaskList(records) {
|
|
@@ -129,8 +129,6 @@ export async function runTaskCommand(options) {
|
|
|
129
129
|
if (options.args.title === undefined) {
|
|
130
130
|
throw new AgentOpsError("TASK_TITLE_REQUIRED", "Task creation requires --title.");
|
|
131
131
|
}
|
|
132
|
-
if (options.args.debug)
|
|
133
|
-
await options.service.assertDebugBaseline();
|
|
134
132
|
const worktree = sessionId === undefined || options.sessionWorktree === undefined
|
|
135
133
|
? undefined
|
|
136
134
|
: await options.sessionWorktree(sessionId);
|
|
@@ -156,12 +154,6 @@ export async function runTaskCommand(options) {
|
|
|
156
154
|
requireSurface: true
|
|
157
155
|
});
|
|
158
156
|
const escape = origin === undefined ? undefined : await (worktree?.service ?? options.service).declareEscape(record.task.id);
|
|
159
|
-
// An explicit CLI DEBUG intake may be the only start signal on hosts
|
|
160
|
-
// without a prompt hook (notably agy). The native session owns the clock;
|
|
161
|
-
// foregroundStart is idempotent for a repeated hook or command.
|
|
162
|
-
if (options.args.debug && sessionId !== undefined) {
|
|
163
|
-
await (worktree?.service ?? options.service).debugService().foregroundStart(record.task.id, sessionId);
|
|
164
|
-
}
|
|
165
157
|
const created = await withEscape(taskEnvelope(action, "TASK_CREATED", sessionId === undefined
|
|
166
158
|
? `Created task ${record.task.id}.`
|
|
167
159
|
: record.task.parentTaskId === undefined
|
|
@@ -187,42 +179,6 @@ export async function runTaskCommand(options) {
|
|
|
187
179
|
}
|
|
188
180
|
};
|
|
189
181
|
}
|
|
190
|
-
if (action === "debug") {
|
|
191
|
-
const id = requireTaskId(options.args), service = options.service.debugService();
|
|
192
|
-
if (options.args.debugAction === "origin") {
|
|
193
|
-
const declared = await service.origin(id, await jsonFile(options.args.payloadFile));
|
|
194
|
-
return await withEscape(taskEnvelope(action, "TASK_DEBUG_ORIGIN_DECLARED", "Origin bound into the contract; re-verify, re-record the confirmed hypothesis, then review.", declared.record), declared.escape, options.service);
|
|
195
|
-
}
|
|
196
|
-
switch (options.args.debugAction) {
|
|
197
|
-
case "hypothesis":
|
|
198
|
-
await service.hypothesis(id, await jsonFile(options.args.payloadFile));
|
|
199
|
-
break;
|
|
200
|
-
case "strategy":
|
|
201
|
-
await service.strategy(id, await jsonFile(options.args.payloadFile));
|
|
202
|
-
break;
|
|
203
|
-
case "investigate":
|
|
204
|
-
await service.investigate(id, options.args.commandId, options.trusted === true);
|
|
205
|
-
break;
|
|
206
|
-
case "checkpoint":
|
|
207
|
-
if (sessionId === undefined)
|
|
208
|
-
throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug checkpoint requires a session.");
|
|
209
|
-
await service.checkpoint(id, sessionId);
|
|
210
|
-
break;
|
|
211
|
-
case "resume":
|
|
212
|
-
if (sessionId === undefined)
|
|
213
|
-
throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug resume requires a session.");
|
|
214
|
-
await service.resume(id, sessionId);
|
|
215
|
-
break;
|
|
216
|
-
case "extend":
|
|
217
|
-
if (sessionId === undefined)
|
|
218
|
-
throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug extension requires a native session.");
|
|
219
|
-
await service.extend(id, sessionId, options.args.eventId);
|
|
220
|
-
break;
|
|
221
|
-
case "status": break;
|
|
222
|
-
default: throw new AgentOpsError("TASK_DEBUG_ACTION_INVALID", "A supported debug operation is required.");
|
|
223
|
-
}
|
|
224
|
-
return taskEnvelope(action, "TASK_DEBUG_UPDATED", "Original issue debug state preserved.", await service.root(id));
|
|
225
|
-
}
|
|
226
182
|
if (action === "revise") {
|
|
227
183
|
const payload = options.args.criteriaFile === undefined ? undefined : await jsonFile(options.args.criteriaFile);
|
|
228
184
|
if (payload !== undefined && !Array.isArray(payload))
|
|
@@ -235,7 +191,7 @@ export async function runTaskCommand(options) {
|
|
|
235
191
|
...(origin === undefined ? {} : { origin }),
|
|
236
192
|
reason: options.args.reason, ...(options.args.baseline === undefined ? {} : { baseline: options.args.baseline })
|
|
237
193
|
});
|
|
238
|
-
return await withEscape(taskEnvelope(action, "TASK_REVISED", "Contract revised; obtain new verification
|
|
194
|
+
return await withEscape(taskEnvelope(action, "TASK_REVISED", "Contract revised; obtain a fresh review, and new verification if the criteria or configuration changed.", record), origin === undefined ? undefined : await options.service.declareEscape(record.task.id), options.service);
|
|
239
195
|
}
|
|
240
196
|
if (action === "replan") {
|
|
241
197
|
const payload = await jsonFile(options.args.planFile);
|
|
@@ -96,12 +96,16 @@ export async function runVerifyCommand(options) {
|
|
|
96
96
|
try {
|
|
97
97
|
const taskId = await resolveTaskId(options), activity = options.activity;
|
|
98
98
|
const report = publicReport(await withActivity(activity?.commonDir, { kind: "verify", root: activity?.root ?? "", taskId, ...(activity?.sessionId === undefined ? {} : { sessionId: activity.sessionId }) }, async () => await options.service.verify(taskId)));
|
|
99
|
-
|
|
99
|
+
// A broken surface check must not hide the verification result.
|
|
100
|
+
const surface = options.surface === undefined ? [] : await options.surface(taskId).catch(() => []);
|
|
101
|
+
const text = formatReport(report) + surface.map(line => `${safe(line)}\n`).join("");
|
|
102
|
+
const advisory = surface.length === 0 ? {} : { surface };
|
|
100
103
|
if (report.status === "PASS") {
|
|
101
104
|
return okEnvelope("VERIFICATION_PASSED", {
|
|
102
105
|
message: "Required verification passed.",
|
|
103
106
|
report,
|
|
104
|
-
text
|
|
107
|
+
text,
|
|
108
|
+
...advisory
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
const code = report.status === "FAIL"
|
|
@@ -113,7 +117,7 @@ export async function runVerifyCommand(options) {
|
|
|
113
117
|
return {
|
|
114
118
|
code,
|
|
115
119
|
status: "error",
|
|
116
|
-
data: { message, report, text },
|
|
120
|
+
data: { message, report, text, ...advisory },
|
|
117
121
|
errors: [{ code, message }]
|
|
118
122
|
};
|
|
119
123
|
}
|
|
@@ -9,7 +9,8 @@ import { mergeConfigLayers } from "../../../runtime/src/config/merge.js";
|
|
|
9
9
|
import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
10
10
|
import { localStatePaths, readPrivateFile } from "../../../runtime/src/security/permissions.js";
|
|
11
11
|
import { FileRunRepository } from "../../../runtime/src/run/service.js";
|
|
12
|
-
import { readRunPolicy, runRuntimeHash } from "../../../runtime/src/run/policy.js";
|
|
12
|
+
import { readRunPolicy, renewRunPolicy, runRuntimeHash } from "../../../runtime/src/run/policy.js";
|
|
13
|
+
import { CLI_VERSION } from "./version.js";
|
|
13
14
|
import { calculateTrustBinding, FileTrustStore } from "../../../runtime/src/security/trust.js";
|
|
14
15
|
export const DEFAULT_CONFIG = {
|
|
15
16
|
schemaVersion: 3,
|
|
@@ -83,6 +84,10 @@ export async function loadEffectiveConfig(root, scope, projectOverride) {
|
|
|
83
84
|
config: run.policy.config
|
|
84
85
|
}]);
|
|
85
86
|
}
|
|
87
|
+
return await loadRepositoryConfig(root, scope, projectOverride);
|
|
88
|
+
}
|
|
89
|
+
/** Read the real config layers when checking renewal, without consulting the run overlay. */
|
|
90
|
+
async function loadRepositoryConfig(root, scope, projectOverride) {
|
|
86
91
|
const home = process.env.AGENT_OPS_HOME ?? homedir();
|
|
87
92
|
const userPath = join(home, ".agent-ops", "config.json");
|
|
88
93
|
const projectPath = join(scope === "user" ? root : projectConfigRoot(root), ".agent-ops", "config.json");
|
|
@@ -212,7 +217,7 @@ export async function repositoryTrust(root, config, cliVersion) {
|
|
|
212
217
|
}
|
|
213
218
|
}
|
|
214
219
|
/** A run-private capability is usable only by its registered checkout or proof group. */
|
|
215
|
-
export async function runPolicyContext(root) {
|
|
220
|
+
export async function runPolicyContext(root, renew = false) {
|
|
216
221
|
let common;
|
|
217
222
|
try {
|
|
218
223
|
common = execFileSync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
|
|
@@ -250,10 +255,23 @@ export async function runPolicyContext(root) {
|
|
|
250
255
|
throw new AgentOpsError("RUN_WORKER_STALE", "Run policy requires the current registered worker generation or proof process.");
|
|
251
256
|
if (saved.status !== "active" || saved.disableRestart || saved.awaitingResume)
|
|
252
257
|
throw new AgentOpsError("RUN_POLICY_DISABLED", "Run execution policy is inactive; explicit resume is required.");
|
|
253
|
-
const
|
|
258
|
+
const state = renew ? await ensureRunPolicy(repository, saved) : saved;
|
|
259
|
+
const policy = await readRunPolicy(state, await runRuntimeHash());
|
|
254
260
|
if (policy === null)
|
|
255
261
|
return null;
|
|
256
|
-
return { repository, state
|
|
262
|
+
return { repository, state, policy, worker };
|
|
263
|
+
}
|
|
264
|
+
/** Only active execution paths call this; status, installation and recovery reads never renew. */
|
|
265
|
+
export async function ensureRunPolicy(repository, state) {
|
|
266
|
+
if (state.policyBinding === undefined || Date.parse(state.policyBinding.expiresAt) > Date.now())
|
|
267
|
+
return state;
|
|
268
|
+
return await renewRunPolicy(repository, state.runId, { authority: async () => {
|
|
269
|
+
const config = (await loadRepositoryConfig(state.root, "project")).config;
|
|
270
|
+
const baseTrustBinding = await repositoryTrustBinding(state.root, config, CLI_VERSION);
|
|
271
|
+
const paths = localStatePaths(process.env.AGENT_OPS_HOME ?? homedir());
|
|
272
|
+
const trusted = (await new FileTrustStore(paths.trustStore, paths.anchorDirectory).status(baseTrustBinding)).status === "TRUSTED";
|
|
273
|
+
return { config, baseTrustBinding, trusted, runtimeHash: await runRuntimeHash() };
|
|
274
|
+
} });
|
|
257
275
|
}
|
|
258
276
|
export async function repositoryTrustBinding(root, config, cliVersion) {
|
|
259
277
|
return await calculateTrustBinding({
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readWorktreeRecord } from "../../../runtime/src/parallel/service.js";
|
|
2
|
+
import { collectChangeSurface, resolveGitCommit } from "../../../runtime/src/verify/change-surface.js";
|
|
3
|
+
/**
|
|
4
|
+
* The base verify and review use when `--base` is absent, so committed work is
|
|
5
|
+
* judged as a range without the agent having to copy it. A dirty tree keeps the
|
|
6
|
+
* working-tree mode; a session worktree compares against its merge-base with the
|
|
7
|
+
* target branch (which follows a finish rebase); elsewhere the task's creation
|
|
8
|
+
* commit counts when it is an ancestor of HEAD.
|
|
9
|
+
*/
|
|
10
|
+
export async function defaultBase(root, runner, createdSourceCommit) {
|
|
11
|
+
if ((await collectChangeSurface(runner)).paths.length > 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
const head = await resolveGitCommit(runner, "HEAD");
|
|
14
|
+
const record = await readWorktreeRecord(root);
|
|
15
|
+
let base;
|
|
16
|
+
if (record !== null) {
|
|
17
|
+
const merge = await runner.run(["merge-base", "HEAD", record.targetBranch]);
|
|
18
|
+
base = merge.exitCode === 0 ? Buffer.from(merge.stdout).toString("utf8").trim() : record.base;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const created = await createdSourceCommit().catch(() => undefined);
|
|
22
|
+
if (created !== undefined && (await runner.run(["merge-base", "--is-ancestor", created, "HEAD"])).exitCode === 0)
|
|
23
|
+
base = created;
|
|
24
|
+
}
|
|
25
|
+
return base === undefined || base === "" || base === head ? undefined : base;
|
|
26
|
+
}
|
|
@@ -14,7 +14,6 @@ import { evaluateWorktreeEnter } from "../../../runtime/src/parallel/enter.js";
|
|
|
14
14
|
import { evaluateWorktreeWrite, resolveMainRoot } from "../../../runtime/src/parallel/guard.js";
|
|
15
15
|
import { ensureSessionWorktree, recordSessionOrigin } from "../../../runtime/src/parallel/service.js";
|
|
16
16
|
import { CompletionGateService } from "../../../runtime/src/hooks/completion-gate.js";
|
|
17
|
-
import { closeDebugSession, debugStopGate, interruptDebugSession, routeDebugPrompt, startDebugSession } from "./debug-intake.js";
|
|
18
17
|
import { TaskService } from "../../../runtime/src/task/service.js";
|
|
19
18
|
import { FileTaskStore } from "../../../runtime/src/task/store.js";
|
|
20
19
|
import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
|
|
@@ -269,8 +268,6 @@ async function closeOfficeSession(io, dependencies) {
|
|
|
269
268
|
root, harness: "claude", event: "SessionEnd", input, validated: true,
|
|
270
269
|
...(typeof fields.session_id === "string" ? { sessionId: fields.session_id } : {})
|
|
271
270
|
});
|
|
272
|
-
if (typeof fields.session_id === "string")
|
|
273
|
-
await closeDebugSession(root, fields.session_id, "claude").catch(() => undefined);
|
|
274
271
|
}
|
|
275
272
|
catch {
|
|
276
273
|
// Office never blocks a session, least of all one that is ending.
|
|
@@ -307,12 +304,8 @@ async function promptSubmitted(harness, io, dependencies) {
|
|
|
307
304
|
const prompt = fields.prompt;
|
|
308
305
|
if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > 64 * 1024 || prompt.includes("\0"))
|
|
309
306
|
return 0;
|
|
310
|
-
if (evaluateGuardrail({ kind: "content", content: prompt, scope: root }).action !== "block")
|
|
311
|
-
const context = await routeDebugPrompt(root, input, harness).catch(error => `DEBUG intake unavailable: ${error instanceof Error ? error.message : "preserve current state"}`);
|
|
312
|
-
if (context !== null)
|
|
313
|
-
io.writeStdout(harness === "claude" ? JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: context } }) : context + "\n");
|
|
307
|
+
if (evaluateGuardrail({ kind: "content", content: prompt, scope: root }).action !== "block")
|
|
314
308
|
return 0;
|
|
315
|
-
}
|
|
316
309
|
if (!config.profiles.includes("guardrails") && !config.profiles.includes("loop") && !config.profiles.includes("run"))
|
|
317
310
|
return 0;
|
|
318
311
|
const reason = "agent-ops blocked a suspected secret.";
|
|
@@ -329,28 +322,14 @@ async function promptSubmitted(harness, io, dependencies) {
|
|
|
329
322
|
}
|
|
330
323
|
export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
331
324
|
const [harness, event] = argv;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const raw = await readStdin(io.stdin), input = parseInput(raw);
|
|
335
|
-
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
336
|
-
return 0;
|
|
337
|
-
const fields = input;
|
|
338
|
-
if (typeof fields.session_id !== "string" || fields.session_id.length === 0)
|
|
339
|
-
return 0;
|
|
340
|
-
const root = dependencies.root ?? (typeof fields.cwd === "string" ? fields.cwd : process.cwd());
|
|
341
|
-
await interruptDebugSession(root, fields.session_id, raw);
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
io.writeStdout(JSON.stringify({ systemMessage: "agent-ops: DEBUG interrupt progress could not be sealed; preserve the task and run agent-ops doctor before resuming." }));
|
|
345
|
-
}
|
|
325
|
+
// Pre-0.11 installs registered Interrupt only for DEBUG; it is now a no-op until update removes it.
|
|
326
|
+
if (harness === "codex" && event === "Interrupt")
|
|
346
327
|
return 0;
|
|
347
|
-
}
|
|
348
328
|
if (harness === "claude" && event === "SessionEnd")
|
|
349
329
|
return await closeOfficeSession(io, dependencies);
|
|
350
330
|
if ((harness === "claude" || harness === "codex") && event === "UserPromptSubmit")
|
|
351
331
|
return await promptSubmitted(harness, io, dependencies);
|
|
352
|
-
// The generic completion gate requires an approval escape hatch
|
|
353
|
-
// only enforces DEBUG through runtime checkpoints and native human control.
|
|
332
|
+
// The generic completion gate requires an approval escape hatch, which Codex lacks.
|
|
354
333
|
const completionGateInstalled = (harness === "agy" || harness === "claude") &&
|
|
355
334
|
event === "Stop" &&
|
|
356
335
|
argv.includes("--completion-gate");
|
|
@@ -400,18 +379,7 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
400
379
|
}
|
|
401
380
|
}
|
|
402
381
|
let configOutcome = await hookConfigOutcome(root, dependencies.loadConfig);
|
|
403
|
-
const nativeStop = normalizeHookInput(harnessId, parsedInput);
|
|
404
|
-
if (harnessId === "codex" && hookEvent === "Stop" && configOutcome.kind === "invalid")
|
|
405
|
-
configOutcome = await hookConfigOutcome(root, dependencies.loadConfig);
|
|
406
382
|
if (configOutcome.kind === "invalid") {
|
|
407
|
-
if (harnessId === "codex" && nativeStop?.event === "stop" && nativeStop.sessionId !== undefined) {
|
|
408
|
-
writeHookOutput(io, harnessDescriptor(harnessId).runtime.formatOutput("Stop", {
|
|
409
|
-
action: "continue", status: "UNKNOWN", code: "TASK_DEBUG_GATE_UNAVAILABLE",
|
|
410
|
-
remedy: "DEBUG remains incomplete: configuration failed after one targeted reload. Preserve the task, evidence and budgets; fix .agent-ops/config.json and run agent-ops doctor. No completion is attested."
|
|
411
|
-
}));
|
|
412
|
-
await closeDebugSession(root, nativeStop.sessionId, harnessId).catch(() => undefined);
|
|
413
|
-
return 0;
|
|
414
|
-
}
|
|
415
383
|
if (completionGateInstalled) {
|
|
416
384
|
writeHookOutput(io, harnessDescriptor(harness).runtime.formatOutput("Stop", {
|
|
417
385
|
action: "block",
|
|
@@ -434,15 +402,6 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
434
402
|
return 0;
|
|
435
403
|
}
|
|
436
404
|
const config = configOutcome.config;
|
|
437
|
-
if (harnessId === "codex" && hookEvent === "Stop" && nativeStop !== null && config.features.debug?.enabled === true) {
|
|
438
|
-
const result = await debugStopGate(root, config).handle(nativeStop);
|
|
439
|
-
if (result !== null) {
|
|
440
|
-
writeHookOutput(io, harnessDescriptor(harnessId).runtime.formatOutput("Stop", result));
|
|
441
|
-
if (result.action === "continue" && nativeStop.sessionId !== undefined)
|
|
442
|
-
await closeDebugSession(root, nativeStop.sessionId, harnessId).catch(() => undefined);
|
|
443
|
-
return 0;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
405
|
if (completionGateInstalled && !config.features.completionGate.enabled) {
|
|
447
406
|
writeHookOutput(io, harnessDescriptor(harness).runtime.formatOutput("Stop", {
|
|
448
407
|
action: "block",
|
|
@@ -457,8 +416,6 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
457
416
|
: await dependencies.trust(root, config, cliVersion);
|
|
458
417
|
const trusted = trustStatus === "TRUSTED";
|
|
459
418
|
const normalized = normalizeHookInput(harnessId, parsedInput);
|
|
460
|
-
if (normalized?.event === "session-start" && normalized.sessionId !== undefined)
|
|
461
|
-
await startDebugSession(root, normalized.sessionId, harnessId).catch(() => undefined);
|
|
462
419
|
if (dependencies.office !== undefined && config.features.office?.enabled === true) {
|
|
463
420
|
if (normalized !== null) {
|
|
464
421
|
const phase = officePhaseHint(normalized) ?? (harnessId === "codex" ? officeLoopPhaseHint(parsedInput, root) : undefined);
|
|
@@ -554,43 +511,22 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
554
511
|
await recordSessionOrigin(dependencies.worktree, { cwd: root, sessionId }).catch(() => undefined);
|
|
555
512
|
}
|
|
556
513
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
input: parsedInput,
|
|
573
|
-
config,
|
|
574
|
-
trusted,
|
|
575
|
-
onResult: result => { hookResult = result; },
|
|
576
|
-
...(worktreeGuard === undefined ? {} : { worktreeGuard }),
|
|
577
|
-
...(worktreeEnter === undefined ? {} : { worktreeEnter }),
|
|
578
|
-
...(dependencies.advisory === undefined
|
|
579
|
-
? {}
|
|
580
|
-
: { advisory: dependencies.advisory }),
|
|
581
|
-
...(stopVerification === undefined ? {} : { stopVerification }),
|
|
582
|
-
...(completionGate === undefined ? {} : { completionGate })
|
|
583
|
-
});
|
|
514
|
+
const output = await runHookCommand({
|
|
515
|
+
harness: harness,
|
|
516
|
+
event: hookEvent,
|
|
517
|
+
stdin: rawInput,
|
|
518
|
+
input: parsedInput,
|
|
519
|
+
config,
|
|
520
|
+
trusted,
|
|
521
|
+
...(worktreeGuard === undefined ? {} : { worktreeGuard }),
|
|
522
|
+
...(worktreeEnter === undefined ? {} : { worktreeEnter }),
|
|
523
|
+
...(dependencies.advisory === undefined
|
|
524
|
+
? {}
|
|
525
|
+
: { advisory: dependencies.advisory }),
|
|
526
|
+
...(stopVerification === undefined ? {} : { stopVerification }),
|
|
527
|
+
...(completionGate === undefined ? {} : { completionGate })
|
|
528
|
+
});
|
|
584
529
|
writeHookOutput(io, output);
|
|
585
|
-
// A Stop can be advisory or gated, but the foreground clock only ends on
|
|
586
|
-
// the same trusted idle model_stop that the native decision accepted.
|
|
587
|
-
if (normalized?.event === "stop" &&
|
|
588
|
-
normalized.sessionId !== undefined &&
|
|
589
|
-
normalized.terminationReason === "model_stop" &&
|
|
590
|
-
normalized.fullyIdle === true &&
|
|
591
|
-
hookResult?.action === "continue") {
|
|
592
|
-
await closeDebugSession(root, normalized.sessionId, harnessId).catch(() => undefined);
|
|
593
|
-
}
|
|
594
530
|
}
|
|
595
531
|
catch {
|
|
596
532
|
if (completionGateInstalled) {
|
|
@@ -5,7 +5,7 @@ import { rm } from "node:fs/promises";
|
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { collectOfficeBuilding } from "../../../runtime/src/office/collect.js";
|
|
8
|
-
import { claimOffice, createOfficeServer, ensureOffice, legacyOfficeHome, officeRecordPath, readLiveOffice,
|
|
8
|
+
import { claimOffice, createOfficeServer, ensureOffice, legacyOfficeHome, officeRecordPath, readLiveOffice, readOfficeRecord, officeUrl, userOfficeHome } from "../../../runtime/src/office/server.js";
|
|
9
9
|
import { registerOfficeRepo } from "../../../runtime/src/office/repos.js";
|
|
10
10
|
import { recordOfficeSession, recordOfficeSessionPhase } from "../../../runtime/src/office/sessions.js";
|
|
11
11
|
import { resolveCheckouts } from "../../../runtime/src/parallel/service.js";
|
|
@@ -254,7 +254,6 @@ export async function serveOffice(cwd, onUrl = () => { }, openBrowser) {
|
|
|
254
254
|
}, 15_000);
|
|
255
255
|
await idle;
|
|
256
256
|
clearInterval(timer);
|
|
257
|
-
await releaseOffice(home, record);
|
|
258
257
|
await office.close();
|
|
259
258
|
}
|
|
260
259
|
/** Reuse the user's live server or start one under launchd; null where launchd is unavailable. */
|
|
@@ -267,10 +266,13 @@ export async function ensureBackgroundOffice(cwd, launchd = new LaunchdControlle
|
|
|
267
266
|
const live = await readLiveOffice(home);
|
|
268
267
|
if (live !== null)
|
|
269
268
|
return officeUrl(live);
|
|
269
|
+
let previousUrl = null;
|
|
270
270
|
let started = false;
|
|
271
271
|
const args = [entry, mainRoot];
|
|
272
272
|
if (!launchd.supported) {
|
|
273
273
|
const url = await ensureOffice(home, async () => {
|
|
274
|
+
const previous = await readOfficeRecord(home);
|
|
275
|
+
previousUrl = previous === null ? null : officeUrl(previous);
|
|
274
276
|
started = true;
|
|
275
277
|
await new Promise((resolve, reject) => {
|
|
276
278
|
const child = spawn(process.execPath, args, { cwd: mainRoot, detached: true, stdio: "ignore", windowsHide: true });
|
|
@@ -278,7 +280,7 @@ export async function ensureBackgroundOffice(cwd, launchd = new LaunchdControlle
|
|
|
278
280
|
child.once("spawn", () => { child.unref(); resolve(); });
|
|
279
281
|
});
|
|
280
282
|
});
|
|
281
|
-
if (started && openBrowser !== undefined)
|
|
283
|
+
if (started && url !== previousUrl && openBrowser !== undefined)
|
|
282
284
|
void openBrowser(url).catch(() => undefined);
|
|
283
285
|
return url;
|
|
284
286
|
}
|
|
@@ -289,6 +291,8 @@ export async function ensureBackgroundOffice(cwd, launchd = new LaunchdControlle
|
|
|
289
291
|
cwd: mainRoot, pathEnvironment: process.env.PATH
|
|
290
292
|
});
|
|
291
293
|
const url = await ensureOffice(home, async () => {
|
|
294
|
+
const previous = await readOfficeRecord(home);
|
|
295
|
+
previousUrl = previous === null ? null : officeUrl(previous);
|
|
292
296
|
started = true;
|
|
293
297
|
// The descriptor write anchors on this directory, so it must exist first.
|
|
294
298
|
await ensurePrivateDirectory(privateDirectory, home.anchor);
|
|
@@ -297,7 +301,7 @@ export async function ensureBackgroundOffice(cwd, launchd = new LaunchdControlle
|
|
|
297
301
|
await launchd.bootout(descriptor).catch(() => { });
|
|
298
302
|
await launchd.bootstrap(descriptor);
|
|
299
303
|
});
|
|
300
|
-
if (started && openBrowser !== undefined)
|
|
304
|
+
if (started && url !== previousUrl && openBrowser !== undefined)
|
|
301
305
|
void openBrowser(url).catch(() => undefined);
|
|
302
306
|
return url;
|
|
303
307
|
}
|
|
@@ -4,13 +4,13 @@ import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
|
4
4
|
import { nativeProcessIdentity, NativeRunTransport } from "../../../runtime/src/run/transport.js";
|
|
5
5
|
import { ClaudeGoalHost } from "../../../runtime/src/run/hosts/claude.js";
|
|
6
6
|
import { CodexGoalHost } from "../../../runtime/src/run/hosts/codex.js";
|
|
7
|
-
import {
|
|
7
|
+
import { remainingRunTimeMs } from "../../../runtime/src/run/scheduler.js";
|
|
8
8
|
import { runPolicyContext } from "./context.js";
|
|
9
9
|
/** Native-issued proof commands use the same dormant group as final evidence. */
|
|
10
10
|
export async function runOwnedLocalProof(argv) {
|
|
11
11
|
if (!["verify", "review", "batch"].includes(argv[0] ?? "") || argv.includes("--help") || process.env.AGENT_OPS_RUN_PROOF_PID !== undefined)
|
|
12
12
|
return null;
|
|
13
|
-
const context = await runPolicyContext(process.cwd());
|
|
13
|
+
const context = await runPolicyContext(process.cwd(), true);
|
|
14
14
|
if (context === null)
|
|
15
15
|
return null;
|
|
16
16
|
const { repository, state, worker } = context;
|
|
@@ -22,7 +22,7 @@ export async function runOwnedLocalProof(argv) {
|
|
|
22
22
|
if (existing.processAlive)
|
|
23
23
|
throw new AgentOpsError("RUN_WORKER_PROOF_ACTIVE", "A registered proof command is already running for this worker.");
|
|
24
24
|
}
|
|
25
|
-
const remaining =
|
|
25
|
+
const remaining = remainingRunTimeMs(state);
|
|
26
26
|
if (remaining <= 0)
|
|
27
27
|
throw new AgentOpsError("RUN_BUDGET_EXHAUSTED", "No active run time remains for local proof.");
|
|
28
28
|
const cli = fileURLToPath(new URL("./bin.js", import.meta.url));
|
|
@@ -34,7 +34,7 @@ export async function runOwnedLocalProof(argv) {
|
|
|
34
34
|
process.kill(process.platform === "win32" ? child.pid : -child.pid, "SIGKILL");
|
|
35
35
|
}
|
|
36
36
|
catch { } };
|
|
37
|
-
const deadline = setTimeout(kill, remaining);
|
|
37
|
+
const deadline = Number.isFinite(remaining) ? setTimeout(kill, remaining) : undefined;
|
|
38
38
|
try {
|
|
39
39
|
const identity = child.pid === undefined ? null : await nativeProcessIdentity(child.pid);
|
|
40
40
|
if (identity === null)
|
|
@@ -9,7 +9,6 @@ import { FileTrustStore } from "../../../runtime/src/security/trust.js";
|
|
|
9
9
|
import { TaskService } from "../../../runtime/src/task/service.js";
|
|
10
10
|
import { FileTaskStore } from "../../../runtime/src/task/store.js";
|
|
11
11
|
import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
|
|
12
|
-
import { registeredRunProofRunner } from "../../../runtime/src/verify/spawn.js";
|
|
13
12
|
import { loadEffectiveConfig, repositoryTrust, runPolicyContext, repositoryTrustBinding } from "./context.js";
|
|
14
13
|
import { CLI_VERSION } from "./version.js";
|
|
15
14
|
// Shared by the CLI and the hook entry point: Claude Code runs hooks through
|
|
@@ -84,7 +83,6 @@ export function worktreeDependencies() {
|
|
|
84
83
|
...(noChangePaths === undefined ? {} : { noChangePaths }),
|
|
85
84
|
loadConfig: async () => (await loadEffectiveConfig(root, "project")).config
|
|
86
85
|
} }),
|
|
87
|
-
processRunner: registeredRunProofRunner(),
|
|
88
86
|
runSetup: async (cwd, step) => await new Promise((resolve) => {
|
|
89
87
|
execFile(step.command, [...step.args], {
|
|
90
88
|
cwd,
|
|
@@ -18,9 +18,9 @@ import { NativeRunTransport } from "../../../runtime/src/run/transport.js";
|
|
|
18
18
|
import { ClaudeGoalHost } from "../../../runtime/src/run/hosts/claude.js";
|
|
19
19
|
import { CodexGoalHost } from "../../../runtime/src/run/hosts/codex.js";
|
|
20
20
|
import { RunSupervisor } from "../../../runtime/src/run/supervisor.js";
|
|
21
|
-
import { activeWallTimeMs } from "../../../runtime/src/run/scheduler.js";
|
|
21
|
+
import { activeWallTimeMs, remainingRunTimeMs } from "../../../runtime/src/run/scheduler.js";
|
|
22
22
|
import { bindRunPolicy, readRunPolicy, runRuntimeHash, runPolicyAssessment } from "../../../runtime/src/run/policy.js";
|
|
23
|
-
import { repositoryTrustBinding } from "./context.js";
|
|
23
|
+
import { repositoryTrustBinding, ensureRunPolicy } from "./context.js";
|
|
24
24
|
import { CLI_VERSION } from "./version.js";
|
|
25
25
|
import { calculateConfigHash, canonicalJson } from "../../../runtime/src/config/hash.js";
|
|
26
26
|
import { discoverPendingPolicyTransition, renewBoundPolicyTransition, currentPolicyArtifact } from "../../../runtime/src/run/policy-transition.js";
|
|
@@ -30,6 +30,7 @@ import { RunControlService } from "../../../runtime/src/run/controls.js";
|
|
|
30
30
|
import { recordRunPhase } from "../../../runtime/src/run/phase.js";
|
|
31
31
|
/** Worktree construction happens before a native lease exists; it must never write global trust. */
|
|
32
32
|
export async function runWorktreeDependencies(deps, state) {
|
|
33
|
+
state = await ensureRunPolicy(new FileRunRepository(join(state.commonDir, "agent-ops/runs"), state.commonDir), state);
|
|
33
34
|
const policy = await readRunPolicy(state, await runRuntimeHash());
|
|
34
35
|
if (policy === null || (await trustStore().status(policy.baseTrustBinding)).status !== "TRUSTED")
|
|
35
36
|
throw new AgentOpsError("RUN_REPO_UNTRUSTED", "Run worktree setup requires the original trusted repository and current scoped policy.");
|
|
@@ -40,7 +41,9 @@ export async function runWorktreeDependencies(deps, state) {
|
|
|
40
41
|
if (index < 0)
|
|
41
42
|
throw new AgentOpsError("RUN_SETUP_UNAUTHORIZED", "Setup must select an exact fixed policy capability.");
|
|
42
43
|
const repository = new FileRunRepository(join(state.commonDir, "agent-ops/runs"), state.commonDir);
|
|
43
|
-
const current = (await repository.read(state.runId));
|
|
44
|
+
const current = await ensureRunPolicy(repository, (await repository.read(state.runId)));
|
|
45
|
+
if (current.policyBinding?.configHash !== state.policyBinding.configHash)
|
|
46
|
+
throw new AgentOpsError("RUN_SETUP_STALE", "Policy capability changed after setup was selected.");
|
|
44
47
|
const coordinator = current.workers.find(w => w.workerId === current.coordinatorId);
|
|
45
48
|
const command = canonicalJson(step), resource = canonicalJson({ checkout: root, capabilityId: "setup:" + index });
|
|
46
49
|
const approved = await runPolicyAssessment(current, policy, "setup:" + index);
|
|
@@ -49,11 +52,11 @@ export async function runWorktreeDependencies(deps, state) {
|
|
|
49
52
|
command, resource, commandDigest: sha256(command), resourceDigest: sha256(resource), reason: approved?.reason ?? "The original user trust authorizes this fixed repository setup capability in an isolated checkout.",
|
|
50
53
|
result: "auto-approved", source: approved === null ? "repo-trust" : "coordinator-policy-review", policyArtifactDigest: current.policyBinding.artifactDigest,
|
|
51
54
|
policyConfigHash: current.policyBinding.configHash, runtimeHash: current.policyBinding.runtimeHash, expiresAt: current.policyBinding.expiresAt });
|
|
52
|
-
const remaining =
|
|
55
|
+
const remaining = remainingRunTimeMs(current);
|
|
53
56
|
if (remaining <= 0)
|
|
54
57
|
throw new AgentOpsError("RUN_BUDGET_EXHAUSTED", "No active time remains for setup.");
|
|
55
58
|
const child = spawn(process.execPath, [fileURLToPath(new URL("./run-step-entry.js", import.meta.url)),
|
|
56
|
-
fileURLToPath(new URL("./run-setup-entry.js", import.meta.url)), state.commonDir, state.runId, root, String(index),
|
|
59
|
+
fileURLToPath(new URL("./run-setup-entry.js", import.meta.url)), state.commonDir, state.runId, root, String(index), current.policyBinding.artifactDigest], { cwd: root, env: process.env, detached: process.platform !== "win32", stdio: ["pipe", "pipe", "pipe"] });
|
|
57
60
|
let output = "";
|
|
58
61
|
const completion = new Promise((resolve, reject) => { child.once("error", reject); child.once("close", resolve); });
|
|
59
62
|
const kill = () => { if (child.pid !== undefined)
|
|
@@ -70,7 +73,7 @@ export async function runWorktreeDependencies(deps, state) {
|
|
|
70
73
|
if (identity === null)
|
|
71
74
|
throw new AgentOpsError("RUN_PROCESS_IDENTITY_REQUIRED", "Cannot register setup process identity.");
|
|
72
75
|
await repository.mutate(state.runId, saved => {
|
|
73
|
-
if (saved.status !== "active" || saved.disableRestart || saved.proofProcess != null || saved.policyBinding?.artifactDigest !==
|
|
76
|
+
if (saved.status !== "active" || saved.disableRestart || saved.proofProcess != null || saved.policyBinding?.artifactDigest !== current.policyBinding.artifactDigest)
|
|
74
77
|
throw new AgentOpsError("RUN_SETUP_STALE", "Stop or another proof process raced setup registration.");
|
|
75
78
|
return { ...saved, proofProcess: { processId: child.pid, processIdentity: identity } };
|
|
76
79
|
});
|
|
@@ -206,7 +209,7 @@ export async function productionRunContext(cwd, options = {}) {
|
|
|
206
209
|
throw cause;
|
|
207
210
|
}
|
|
208
211
|
};
|
|
209
|
-
const resume = async (runId) => {
|
|
212
|
+
const resume = async (runId, retryApprovalIds = [], upgradeBudget = true) => {
|
|
210
213
|
const before = await service.status(runId);
|
|
211
214
|
const runtimeHash = await runRuntimeHash();
|
|
212
215
|
const policy = await readRunPolicy(before, before.policyBinding?.runtimeHash ?? runtimeHash, Date.now(), true);
|
|
@@ -233,7 +236,7 @@ export async function productionRunContext(cwd, options = {}) {
|
|
|
233
236
|
if (exists === null || (await collectChangeSurface(gitRunner(worker.worktree))).paths.length > 0)
|
|
234
237
|
throw new AgentOpsError("RUN_RECOVERY_DIRTY", "Commit or preserve every writer checkout before explicit resume.");
|
|
235
238
|
}
|
|
236
|
-
const result = await service.resume(runId);
|
|
239
|
+
const result = await service.resume(runId, retryApprovalIds, upgradeBudget);
|
|
237
240
|
try {
|
|
238
241
|
if (pending !== null)
|
|
239
242
|
await renewPendingRunPolicy(repository, runId, pending);
|
|
@@ -308,7 +311,7 @@ export async function productionRunContext(cwd, options = {}) {
|
|
|
308
311
|
await writePrivateFile(join(state.commonDir, "agent-ops", "runs", state.runId, "answers", sha256(questionId) + ".json"), JSON.stringify({ questionId, answer: answer.trim() }), state.commonDir);
|
|
309
312
|
const result = await service.respond(runId, questionId, answer);
|
|
310
313
|
try {
|
|
311
|
-
const resumed = await resume(runId);
|
|
314
|
+
const resumed = await resume(runId, [], false);
|
|
312
315
|
return { ...result, state: resumed.state };
|
|
313
316
|
}
|
|
314
317
|
catch (cause) {
|