@agwab/pi-workflow 0.2.1 → 0.3.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/dist/compiler.js +6 -8
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- package/dist/dynamic-profiles.d.ts +0 -1
- package/dist/dynamic-profiles.js +0 -3
- package/dist/engine-run-graph.d.ts +1 -0
- package/dist/engine-run-graph.js +142 -2
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +112 -27
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +27 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.js +55 -11
- package/dist/subagent-backend.js +155 -29
- package/dist/types.d.ts +6 -0
- package/dist/workflow-runtime.js +10 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +167 -48
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +84 -19
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +995 -573
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1352 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/src/compiler.ts +14 -9
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +145 -24
- package/src/extension.ts +33 -4
- package/src/index.ts +3 -1
- package/src/store.ts +74 -11
- package/src/subagent-backend.ts +201 -28
- package/src/types.ts +6 -0
- package/src/workflow-runtime.ts +18 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +621 -228
- package/src/workflow-web-source.ts +118 -28
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
- package/workflows/deep-research/helpers/render-executive.mjs +8 -21
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
|
@@ -1,111 +1,187 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
appendRunEvent,
|
|
3
|
+
readRunRecord,
|
|
4
|
+
recordInterruptRequest,
|
|
5
|
+
type RunAttemptRecord,
|
|
6
|
+
type RunRecord,
|
|
7
|
+
} from "../artifacts/index.ts";
|
|
8
|
+
import { resolveRunRef } from "./run-ref.ts";
|
|
2
9
|
import { isTerminalStatus } from "./status.ts";
|
|
3
10
|
|
|
4
11
|
export interface InterruptRunOptions {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
cwd?: string;
|
|
13
|
+
runId: string;
|
|
14
|
+
runsDir?: string;
|
|
15
|
+
attemptId?: string;
|
|
16
|
+
/** @deprecated v1 compatibility alias. */
|
|
17
|
+
taskId?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
signal?: NodeJS.Signals;
|
|
20
|
+
escalateAfterMs?: number;
|
|
21
|
+
killAfterMs?: number;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
export interface InterruptRunResult {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
status:
|
|
26
|
+
| "interrupt-requested"
|
|
27
|
+
| "not-found"
|
|
28
|
+
| "already-terminal"
|
|
29
|
+
| "unsupported";
|
|
30
|
+
runId: string;
|
|
31
|
+
signal: NodeJS.Signals;
|
|
32
|
+
interruptedAttempts: string[];
|
|
33
|
+
unsupportedAttempts: string[];
|
|
34
|
+
/** @deprecated v1 compatibility alias. */
|
|
35
|
+
interruptedTasks: string[];
|
|
36
|
+
/** @deprecated v1 compatibility alias. */
|
|
37
|
+
unsupportedTasks: string[];
|
|
38
|
+
record: RunRecord | null;
|
|
28
39
|
}
|
|
29
40
|
|
|
30
|
-
function sendProcessSignal(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
function sendProcessSignal(
|
|
42
|
+
attempt: RunAttemptRecord,
|
|
43
|
+
signal: NodeJS.Signals,
|
|
44
|
+
): boolean {
|
|
45
|
+
const pid = attempt.process?.pid;
|
|
46
|
+
if (pid === undefined) return false;
|
|
47
|
+
try {
|
|
48
|
+
const target =
|
|
49
|
+
process.platform === "win32"
|
|
50
|
+
? pid
|
|
51
|
+
: -(attempt.process?.processGroupId ?? pid);
|
|
52
|
+
process.kill(target, signal);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid, signal);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
function runningAttempts(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
function runningAttempts(
|
|
65
|
+
record: RunRecord,
|
|
66
|
+
targetAttemptId?: string,
|
|
67
|
+
): RunAttemptRecord[] {
|
|
68
|
+
return record.attempts.filter((attempt) => {
|
|
69
|
+
if (targetAttemptId !== undefined && attempt.attemptId !== targetAttemptId)
|
|
70
|
+
return false;
|
|
71
|
+
return attempt.status === "running" || attempt.status === "pending";
|
|
72
|
+
});
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
async function escalate(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
async function escalate(
|
|
76
|
+
options: InterruptRunOptions,
|
|
77
|
+
signal: NodeJS.Signals,
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
const ref = await resolveRunRef(options);
|
|
80
|
+
const record = await readRunRecord(ref).catch(() => null);
|
|
81
|
+
if (record === null || isTerminalStatus(record.status)) return;
|
|
82
|
+
for (const attempt of runningAttempts(
|
|
83
|
+
record,
|
|
84
|
+
options.attemptId ?? options.taskId,
|
|
85
|
+
))
|
|
86
|
+
sendProcessSignal(attempt, signal);
|
|
87
|
+
await appendRunEvent(ref, {
|
|
88
|
+
type: "run.interrupt_requested",
|
|
89
|
+
status: record.status,
|
|
90
|
+
message: `interrupt escalation ${signal}`,
|
|
91
|
+
data: { signal },
|
|
92
|
+
}).catch(() => undefined);
|
|
59
93
|
}
|
|
60
94
|
|
|
61
|
-
function result(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
95
|
+
function result(
|
|
96
|
+
status: InterruptRunResult["status"],
|
|
97
|
+
runId: string,
|
|
98
|
+
signal: NodeJS.Signals,
|
|
99
|
+
interruptedAttempts: string[],
|
|
100
|
+
unsupportedAttempts: string[],
|
|
101
|
+
record: RunRecord | null,
|
|
102
|
+
): InterruptRunResult {
|
|
103
|
+
return {
|
|
104
|
+
status,
|
|
105
|
+
runId,
|
|
106
|
+
signal,
|
|
107
|
+
interruptedAttempts,
|
|
108
|
+
unsupportedAttempts,
|
|
109
|
+
interruptedTasks: interruptedAttempts,
|
|
110
|
+
unsupportedTasks: unsupportedAttempts,
|
|
111
|
+
record,
|
|
112
|
+
};
|
|
72
113
|
}
|
|
73
114
|
|
|
74
|
-
export async function interruptRun(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
115
|
+
export async function interruptRun(
|
|
116
|
+
options: InterruptRunOptions,
|
|
117
|
+
): Promise<InterruptRunResult> {
|
|
118
|
+
const signal = options.signal ?? "SIGINT";
|
|
119
|
+
const ref = await resolveRunRef(options);
|
|
120
|
+
const record = await readRunRecord(ref);
|
|
121
|
+
if (record === null) {
|
|
122
|
+
return result("not-found", options.runId, signal, [], [], null);
|
|
123
|
+
}
|
|
124
|
+
if (isTerminalStatus(record.status)) {
|
|
125
|
+
return result("already-terminal", options.runId, signal, [], [], record);
|
|
126
|
+
}
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
128
|
+
const candidates = runningAttempts(
|
|
129
|
+
record,
|
|
130
|
+
options.attemptId ?? options.taskId,
|
|
131
|
+
);
|
|
132
|
+
const interruptedAttempts: string[] = [];
|
|
133
|
+
const unsupportedAttempts: string[] = [];
|
|
134
|
+
for (const attempt of candidates) {
|
|
135
|
+
if (sendProcessSignal(attempt, signal))
|
|
136
|
+
interruptedAttempts.push(attempt.attemptId);
|
|
137
|
+
else unsupportedAttempts.push(attempt.attemptId);
|
|
138
|
+
}
|
|
91
139
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
140
|
+
if (interruptedAttempts.length === 0) {
|
|
141
|
+
await appendRunEvent(ref, {
|
|
142
|
+
type: "run.interrupt_requested",
|
|
143
|
+
status: record.status,
|
|
144
|
+
message: "interrupt unsupported: no interruptable process metadata",
|
|
145
|
+
data: { signal, unsupportedAttempts },
|
|
146
|
+
});
|
|
147
|
+
return result(
|
|
148
|
+
"unsupported",
|
|
149
|
+
options.runId,
|
|
150
|
+
signal,
|
|
151
|
+
interruptedAttempts,
|
|
152
|
+
unsupportedAttempts,
|
|
153
|
+
record,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
96
156
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
157
|
+
const updated = await recordInterruptRequest(
|
|
158
|
+
ref,
|
|
159
|
+
signal,
|
|
160
|
+
options.reason ?? null,
|
|
161
|
+
);
|
|
162
|
+
await appendRunEvent(ref, {
|
|
163
|
+
type: "run.interrupt_requested",
|
|
164
|
+
status: updated.status,
|
|
165
|
+
message: `interrupt requested with ${signal}`,
|
|
166
|
+
data: {
|
|
167
|
+
signal,
|
|
168
|
+
interruptedAttempts,
|
|
169
|
+
unsupportedAttempts,
|
|
170
|
+
reason: options.reason ?? null,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
104
173
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
174
|
+
const termDelay = options.escalateAfterMs ?? 1_000;
|
|
175
|
+
const killDelay = options.killAfterMs ?? 3_000;
|
|
176
|
+
setTimeout(() => void escalate(ref, "SIGTERM"), termDelay).unref?.();
|
|
177
|
+
setTimeout(() => void escalate(ref, "SIGKILL"), killDelay).unref?.();
|
|
109
178
|
|
|
110
|
-
|
|
179
|
+
return result(
|
|
180
|
+
"interrupt-requested",
|
|
181
|
+
options.runId,
|
|
182
|
+
signal,
|
|
183
|
+
interruptedAttempts,
|
|
184
|
+
unsupportedAttempts,
|
|
185
|
+
updated,
|
|
186
|
+
);
|
|
111
187
|
}
|
|
@@ -1,98 +1,144 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, resolve, sep } from "node:path";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
appendRunEvent,
|
|
5
|
+
commitAttemptResultIfActive,
|
|
6
|
+
readRunRecord,
|
|
7
|
+
upsertRunAttempt,
|
|
8
|
+
type ResultEnvelope,
|
|
9
|
+
type RunAttemptRecord,
|
|
10
|
+
type RunRef,
|
|
11
|
+
type RunRecord,
|
|
12
12
|
} from "../artifacts/index.ts";
|
|
13
|
+
import { resolveRunRef } from "./run-ref.ts";
|
|
13
14
|
import { isTerminalStatus } from "./status.ts";
|
|
14
15
|
|
|
15
16
|
export interface ReconcileSubagentRunOptions extends RunRef {
|
|
16
|
-
|
|
17
|
+
staleAfterMs?: number;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export interface ReconcileSubagentRunResult {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
status:
|
|
22
|
+
| "not-found"
|
|
23
|
+
| "already-terminal"
|
|
24
|
+
| "running"
|
|
25
|
+
| "committed-result"
|
|
26
|
+
| "marked-stale";
|
|
27
|
+
runId: string;
|
|
28
|
+
record: RunRecord | null;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
function safeArtifactPath(attempt: RunAttemptRecord): string | null {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
if (attempt.resultPath === undefined || attempt.artifactCwd === undefined)
|
|
33
|
+
return null;
|
|
34
|
+
if (
|
|
35
|
+
isAbsolute(attempt.resultPath) ||
|
|
36
|
+
attempt.resultPath.split("/").includes("..")
|
|
37
|
+
)
|
|
38
|
+
return null;
|
|
39
|
+
return resolve(attempt.artifactCwd, attempt.resultPath.split("/").join(sep));
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
async function readAttemptResult(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
async function readAttemptResult(
|
|
43
|
+
attempt: RunAttemptRecord,
|
|
44
|
+
): Promise<ResultEnvelope | null> {
|
|
45
|
+
const path = safeArtifactPath(attempt);
|
|
46
|
+
if (path === null) return null;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(await readFile(path, "utf8")) as ResultEnvelope;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
function pidAlive(pid: number | undefined): boolean {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
if (pid === undefined) return false;
|
|
56
|
+
try {
|
|
57
|
+
process.kill(pid, 0);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
function processAlive(attempt: RunAttemptRecord): boolean {
|
|
52
|
-
|
|
65
|
+
return pidAlive(attempt.process?.pid) || pidAlive(attempt.process?.workerPid);
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
function heartbeatFresh(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
function heartbeatFresh(
|
|
69
|
+
attempt: RunAttemptRecord,
|
|
70
|
+
staleAfterMs: number,
|
|
71
|
+
): boolean {
|
|
72
|
+
if (attempt.heartbeatAt === undefined) return false;
|
|
73
|
+
const time = Date.parse(attempt.heartbeatAt);
|
|
74
|
+
return Number.isFinite(time) && Date.now() - time <= staleAfterMs;
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
function activeAttempt(record: RunRecord): RunAttemptRecord | undefined {
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
const activeId =
|
|
79
|
+
record.activeAttemptId ?? record.latestAttemptId ?? undefined;
|
|
80
|
+
return activeId === undefined
|
|
81
|
+
? record.attempts.at(-1)
|
|
82
|
+
: (record.attempts.find((attempt) => attempt.attemptId === activeId) ??
|
|
83
|
+
record.attempts.at(-1));
|
|
64
84
|
}
|
|
65
85
|
|
|
66
|
-
export async function reconcileSubagentRun(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
export async function reconcileSubagentRun(
|
|
87
|
+
options: ReconcileSubagentRunOptions,
|
|
88
|
+
): Promise<ReconcileSubagentRunResult> {
|
|
89
|
+
const staleAfterMs = options.staleAfterMs ?? 30_000;
|
|
90
|
+
const ref = await resolveRunRef(options);
|
|
91
|
+
const record = await readRunRecord(ref);
|
|
92
|
+
if (record === null)
|
|
93
|
+
return { status: "not-found", runId: options.runId, record: null };
|
|
94
|
+
if (isTerminalStatus(record.status))
|
|
95
|
+
return { status: "already-terminal", runId: options.runId, record };
|
|
71
96
|
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
const attempt = activeAttempt(record);
|
|
98
|
+
if (attempt === undefined)
|
|
99
|
+
return { status: "running", runId: options.runId, record };
|
|
74
100
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
const result = await readAttemptResult(attempt);
|
|
102
|
+
if (
|
|
103
|
+
result !== null &&
|
|
104
|
+
result.attemptId === attempt.attemptId &&
|
|
105
|
+
isTerminalStatus(result.status)
|
|
106
|
+
) {
|
|
107
|
+
const committed = await commitAttemptResultIfActive(ref, result);
|
|
108
|
+
await appendRunEvent(ref, {
|
|
109
|
+
type: "reconcile.completed",
|
|
110
|
+
attemptId: attempt.attemptId,
|
|
111
|
+
status: result.status,
|
|
112
|
+
message: committed.committed
|
|
113
|
+
? "committed terminal attempt result"
|
|
114
|
+
: "terminal attempt result was stale",
|
|
115
|
+
}).catch(() => undefined);
|
|
116
|
+
return {
|
|
117
|
+
status: committed.committed ? "committed-result" : "running",
|
|
118
|
+
runId: options.runId,
|
|
119
|
+
record: committed.record,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
123
|
+
if (processAlive(attempt) || heartbeatFresh(attempt, staleAfterMs)) {
|
|
124
|
+
return { status: "running", runId: options.runId, record };
|
|
125
|
+
}
|
|
85
126
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
const updated = await upsertRunAttempt({
|
|
128
|
+
...ref,
|
|
129
|
+
attemptId: attempt.attemptId,
|
|
130
|
+
status: "failed",
|
|
131
|
+
backend: attempt.backend,
|
|
132
|
+
failureKind: "stale",
|
|
133
|
+
startedAt: attempt.startedAt,
|
|
134
|
+
completedAt: new Date(),
|
|
135
|
+
activate: true,
|
|
136
|
+
});
|
|
137
|
+
await appendRunEvent(ref, {
|
|
138
|
+
type: "reconcile.failed",
|
|
139
|
+
attemptId: attempt.attemptId,
|
|
140
|
+
status: "failed",
|
|
141
|
+
message: "active attempt is stale/orphaned",
|
|
142
|
+
}).catch(() => undefined);
|
|
143
|
+
return { status: "marked-stale", runId: options.runId, record: updated };
|
|
98
144
|
}
|