@agwab/pi-workflow 0.3.0 → 0.4.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 -1
- package/dist/artifact-graph-runtime.d.ts +1 -1
- package/dist/artifact-graph-runtime.js +10 -5
- package/dist/artifact-graph-schema.js +127 -5
- package/dist/compiler.js +46 -11
- package/dist/dynamic-decision.d.ts +1 -0
- package/dist/dynamic-decision.js +7 -0
- package/dist/dynamic-generated-task-runtime.js +3 -1
- package/dist/dynamic-profiles.d.ts +1 -0
- package/dist/dynamic-profiles.js +3 -0
- package/dist/engine-run-graph.d.ts +2 -0
- package/dist/engine-run-graph.js +55 -5
- package/dist/engine.js +278 -15
- package/dist/extension.js +3 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/prompt-json.d.ts +7 -0
- package/dist/prompt-json.js +13 -0
- package/dist/roles.d.ts +1 -1
- package/dist/roles.js +5 -8
- package/dist/store.d.ts +20 -1
- package/dist/store.js +89 -29
- package/dist/strings.d.ts +11 -0
- package/dist/strings.js +24 -0
- package/dist/subagent-backend.js +557 -13
- package/dist/types.d.ts +101 -1
- package/dist/verification-ontology.d.ts +31 -0
- package/dist/verification-ontology.js +66 -0
- package/dist/workflow-artifact-tool.js +5 -6
- package/dist/workflow-artifacts.d.ts +7 -0
- package/dist/workflow-artifacts.js +55 -4
- package/dist/workflow-fetch-cache-extension.d.ts +1 -0
- package/dist/workflow-fetch-cache-extension.js +57 -9
- package/dist/workflow-metrics.d.ts +113 -0
- package/dist/workflow-metrics.js +272 -0
- package/dist/workflow-output-artifacts.js +5 -3
- package/dist/workflow-partial-output.d.ts +45 -0
- package/dist/workflow-partial-output.js +205 -0
- package/dist/workflow-progress-health.js +42 -10
- package/dist/workflow-web-source-extension.js +27 -4
- package/dist/workflow-web-source.js +26 -12
- package/docs/usage.md +76 -29
- package/node_modules/@agwab/pi-subagent/package.json +1 -1
- package/node_modules/@agwab/pi-subagent/src/index.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/panel.ts +7 -3
- package/package.json +2 -2
- package/skills/workflow-guide/SKILL.md +1 -0
- package/src/artifact-graph-runtime.ts +19 -13
- package/src/artifact-graph-schema.ts +143 -3
- package/src/cli.mjs +52 -0
- package/src/compiler.ts +49 -9
- package/src/dynamic-decision.ts +11 -0
- package/src/dynamic-generated-task-runtime.ts +3 -1
- package/src/dynamic-profiles.ts +4 -0
- package/src/engine-run-graph.ts +63 -4
- package/src/engine.ts +400 -14
- package/src/extension.ts +3 -2
- package/src/index.ts +49 -0
- package/src/prompt-json.ts +13 -0
- package/src/roles.ts +6 -9
- package/src/store.ts +123 -34
- package/src/strings.ts +38 -0
- package/src/subagent-backend.ts +727 -41
- package/src/types.ts +110 -2
- package/src/verification-ontology.ts +88 -0
- package/src/workflow-artifact-tool.ts +5 -7
- package/src/workflow-artifacts.ts +83 -3
- package/src/workflow-fetch-cache-extension.ts +78 -13
- package/src/workflow-metrics.ts +478 -0
- package/src/workflow-output-artifacts.ts +5 -3
- package/src/workflow-partial-output.ts +299 -0
- package/src/workflow-progress-health.ts +47 -15
- package/src/workflow-web-source-extension.ts +33 -4
- package/src/workflow-web-source.ts +36 -12
- package/workflows/README.md +7 -25
- package/workflows/deep-research/batched-verification.spec.json +253 -0
- package/workflows/deep-research/helpers/batch-verification-candidates.mjs +136 -0
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +173 -20
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +80 -1
- package/workflows/deep-research/helpers/render-executive.mjs +32 -5
- package/workflows/deep-research/helpers/shadow-select-verification.mjs +229 -0
- package/workflows/deep-research/helpers/verification-ontology.mjs +77 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +3 -2
- package/workflows/deep-research/schemas/deep-research-research-questions-control.schema.json +38 -0
- package/workflows/deep-research/schemas/deep-research-sanitize-claims-control.schema.json +63 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-batch-control.schema.json +47 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +10 -3
- package/workflows/deep-research/spec.json +32 -12
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stderr +0 -0
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stdout +0 -13
package/dist/store.js
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULT_INDEX_UPDATE_DEBOUNCE_MS = 500;
|
|
|
13
13
|
let indexUpdateDebounceMs = DEFAULT_INDEX_UPDATE_DEBOUNCE_MS;
|
|
14
14
|
const pendingIndexUpdates = new Map();
|
|
15
15
|
const runLeaseContext = new AsyncLocalStorage();
|
|
16
|
+
let runLeaseTestHooks = {};
|
|
16
17
|
const TASK_STATUSES = [
|
|
17
18
|
"pending",
|
|
18
19
|
"running",
|
|
@@ -80,6 +81,9 @@ export async function writeJsonAtomic(file, value) {
|
|
|
80
81
|
await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
81
82
|
await rename(temp, file);
|
|
82
83
|
}
|
|
84
|
+
export function setRunLeaseTestHooksForTests(hooks) {
|
|
85
|
+
runLeaseTestHooks = hooks ?? {};
|
|
86
|
+
}
|
|
83
87
|
export async function withRunLease(cwd, runId, action) {
|
|
84
88
|
const dir = workflowRunDir(cwd, runId);
|
|
85
89
|
await ensureDir(dir);
|
|
@@ -88,8 +92,15 @@ export async function withRunLease(cwd, runId, action) {
|
|
|
88
92
|
const lock = await acquireLock(lockFile, ownerId);
|
|
89
93
|
if (!lock)
|
|
90
94
|
return undefined;
|
|
95
|
+
const abortController = new AbortController();
|
|
96
|
+
const abortLease = (error) => {
|
|
97
|
+
if (abortController.signal.aborted)
|
|
98
|
+
return;
|
|
99
|
+
abortController.abort(asLeaseError(error));
|
|
100
|
+
};
|
|
91
101
|
const supervisorFile = join(dir, "supervisor.json");
|
|
92
102
|
const heartbeat = async () => {
|
|
103
|
+
assertLeaseNotAborted(abortController.signal);
|
|
93
104
|
await assertLockOwner(lockFile, ownerId);
|
|
94
105
|
const timestamp = nowIso();
|
|
95
106
|
const now = new Date();
|
|
@@ -104,17 +115,37 @@ export async function withRunLease(cwd, runId, action) {
|
|
|
104
115
|
};
|
|
105
116
|
await heartbeat();
|
|
106
117
|
const heartbeatTimer = setInterval(() => {
|
|
107
|
-
void heartbeat().catch(
|
|
108
|
-
},
|
|
118
|
+
void heartbeat().catch(abortLease);
|
|
119
|
+
}, runLeaseHeartbeatIntervalMs());
|
|
109
120
|
heartbeatTimer.unref?.();
|
|
110
121
|
try {
|
|
111
|
-
|
|
122
|
+
const result = await runLeaseContext.run({ cwd, runId, ownerId, abortSignal: abortController.signal }, () => action(abortController.signal));
|
|
123
|
+
assertLeaseNotAborted(abortController.signal);
|
|
124
|
+
return result;
|
|
112
125
|
}
|
|
113
126
|
finally {
|
|
114
127
|
clearInterval(heartbeatTimer);
|
|
115
128
|
await releaseLock(lockFile, ownerId);
|
|
116
129
|
}
|
|
117
130
|
}
|
|
131
|
+
function runLeaseHeartbeatIntervalMs() {
|
|
132
|
+
return Math.max(1, Math.floor(runLeaseTestHooks.heartbeatIntervalMs ??
|
|
133
|
+
Math.max(1000, Math.floor(LEASE_STALE_MS / 3))));
|
|
134
|
+
}
|
|
135
|
+
function assertLeaseNotAborted(signal) {
|
|
136
|
+
if (signal.aborted)
|
|
137
|
+
throw abortSignalError(signal);
|
|
138
|
+
}
|
|
139
|
+
function abortSignalError(signal) {
|
|
140
|
+
return asLeaseError(signal.reason);
|
|
141
|
+
}
|
|
142
|
+
function asLeaseError(error) {
|
|
143
|
+
if (error instanceof Error)
|
|
144
|
+
return error;
|
|
145
|
+
return new Error(error === undefined
|
|
146
|
+
? "Lost supervisor lease"
|
|
147
|
+
: `Lost supervisor lease: ${String(error)}`);
|
|
148
|
+
}
|
|
118
149
|
async function acquireLock(lockFile, ownerId) {
|
|
119
150
|
const tryCreate = async () => {
|
|
120
151
|
try {
|
|
@@ -154,6 +185,7 @@ async function reclaimStaleLock(lockFile) {
|
|
|
154
185
|
return true;
|
|
155
186
|
return false;
|
|
156
187
|
}
|
|
188
|
+
await runLeaseTestHooks.onAfterReclaimRename?.({ lockFile, reclaimFile });
|
|
157
189
|
const claimed = await readLockSnapshot(reclaimFile);
|
|
158
190
|
if (!claimed)
|
|
159
191
|
return true;
|
|
@@ -169,16 +201,20 @@ async function reclaimStaleLock(lockFile) {
|
|
|
169
201
|
return true;
|
|
170
202
|
}
|
|
171
203
|
async function restoreReclaimFile(reclaimFile, lockFile) {
|
|
204
|
+
await runLeaseTestHooks.onBeforeRestoreReclaimFile?.({
|
|
205
|
+
lockFile,
|
|
206
|
+
reclaimFile,
|
|
207
|
+
});
|
|
172
208
|
try {
|
|
173
209
|
await link(reclaimFile, lockFile);
|
|
174
210
|
}
|
|
175
211
|
catch (error) {
|
|
176
|
-
if (error.code
|
|
177
|
-
throw error;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
await unlink(reclaimFile).catch(() => undefined);
|
|
212
|
+
if (error.code === "EEXIST") {
|
|
213
|
+
throw new Error(`Could not restore reclaimed lock because another owner acquired ${lockFile}`, { cause: error });
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
181
216
|
}
|
|
217
|
+
await unlink(reclaimFile).catch(() => undefined);
|
|
182
218
|
}
|
|
183
219
|
function isReclaimableLockSnapshot(snapshot) {
|
|
184
220
|
const now = Date.now();
|
|
@@ -238,8 +274,31 @@ async function acquireLockWithWait(lockFile, ownerId) {
|
|
|
238
274
|
}
|
|
239
275
|
}
|
|
240
276
|
async function releaseLock(lockFile, ownerId) {
|
|
241
|
-
|
|
242
|
-
|
|
277
|
+
const snapshot = await readLockSnapshot(lockFile);
|
|
278
|
+
if (!snapshot || snapshot.ownerId !== ownerId)
|
|
279
|
+
return;
|
|
280
|
+
const releaseFile = `${lockFile}.release-${process.pid}-${randomBytes(3).toString("hex")}`;
|
|
281
|
+
await runLeaseTestHooks.onBeforeReleaseLockRename?.({
|
|
282
|
+
lockFile,
|
|
283
|
+
releaseFile,
|
|
284
|
+
ownerId,
|
|
285
|
+
});
|
|
286
|
+
try {
|
|
287
|
+
await rename(lockFile, releaseFile);
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error.code === "ENOENT")
|
|
291
|
+
return;
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
const claimed = await readLockSnapshot(releaseFile);
|
|
295
|
+
if (!claimed)
|
|
296
|
+
return;
|
|
297
|
+
if (sameLockOwnerSnapshot(snapshot, claimed)) {
|
|
298
|
+
await unlink(releaseFile).catch(() => undefined);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await restoreReclaimFile(releaseFile, lockFile);
|
|
243
302
|
}
|
|
244
303
|
async function assertLockOwner(lockFile, ownerId) {
|
|
245
304
|
if (!(await ownsLock(lockFile, ownerId)))
|
|
@@ -833,6 +892,7 @@ async function assertActiveRunLease(cwd, runId) {
|
|
|
833
892
|
return;
|
|
834
893
|
if (context.cwd !== cwd || context.runId !== runId)
|
|
835
894
|
return;
|
|
895
|
+
assertLeaseNotAborted(context.abortSignal);
|
|
836
896
|
await assertLockOwner(join(workflowRunDir(cwd, runId), "supervisor.lock"), context.ownerId);
|
|
837
897
|
}
|
|
838
898
|
export async function findRunRecordPath(cwd, runIdOrPrefix) {
|
|
@@ -943,6 +1003,7 @@ async function updateIndexIncremental(cwd, changedRunId) {
|
|
|
943
1003
|
const changedEntry = buildIndexEntry(cwd, changedRun);
|
|
944
1004
|
const entries = existing.runs
|
|
945
1005
|
.filter((entry) => entry.runId !== changedRun.runId)
|
|
1006
|
+
.map(stripIndexTaskRows)
|
|
946
1007
|
.concat(changedEntry);
|
|
947
1008
|
return {
|
|
948
1009
|
schemaVersion: 1,
|
|
@@ -978,6 +1039,10 @@ function selectIndexEntries(entries) {
|
|
|
978
1039
|
.slice(0, TERMINAL_INDEX_LIMIT);
|
|
979
1040
|
return [...active, ...terminal].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
980
1041
|
}
|
|
1042
|
+
function stripIndexTaskRows(entry) {
|
|
1043
|
+
const { tasks: _tasks, ...slim } = entry;
|
|
1044
|
+
return slim;
|
|
1045
|
+
}
|
|
981
1046
|
function buildIndexEntry(cwd, run) {
|
|
982
1047
|
return {
|
|
983
1048
|
runId: run.runId,
|
|
@@ -993,28 +1058,20 @@ function buildIndexEntry(cwd, run) {
|
|
|
993
1058
|
round: run.round,
|
|
994
1059
|
fanout: run.fanout,
|
|
995
1060
|
runJson: toProjectPath(cwd, workflowRunPath(cwd, run.runId)),
|
|
996
|
-
tasks: run.tasks.map((task) => ({
|
|
997
|
-
taskId: task.taskId,
|
|
998
|
-
displayName: task.displayName,
|
|
999
|
-
agent: task.agent,
|
|
1000
|
-
kind: task.kind,
|
|
1001
|
-
stageId: task.stageId,
|
|
1002
|
-
backendHandle: task.backendHandle,
|
|
1003
|
-
status: task.status,
|
|
1004
|
-
statusDetail: task.statusDetail,
|
|
1005
|
-
lastMessage: task.lastMessage,
|
|
1006
|
-
})),
|
|
1007
1061
|
};
|
|
1008
1062
|
}
|
|
1009
1063
|
function isIndexRecordLike(value) {
|
|
1010
1064
|
return (value?.schemaVersion === 1 &&
|
|
1011
1065
|
Array.isArray(value.runs) &&
|
|
1012
|
-
value.runs.every((entry) =>
|
|
1013
|
-
typeof entry
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
typeof entry.
|
|
1017
|
-
|
|
1066
|
+
value.runs.every((entry) => {
|
|
1067
|
+
if (!entry || typeof entry !== "object")
|
|
1068
|
+
return false;
|
|
1069
|
+
const tasks = entry.tasks;
|
|
1070
|
+
return (typeof entry.runId === "string" &&
|
|
1071
|
+
typeof entry.updatedAt === "string" &&
|
|
1072
|
+
typeof entry.status === "string" &&
|
|
1073
|
+
(tasks === undefined || Array.isArray(tasks)));
|
|
1074
|
+
}));
|
|
1018
1075
|
}
|
|
1019
1076
|
export function deriveRunStatus(run) {
|
|
1020
1077
|
const next = { ...run, tasks: run.tasks };
|
|
@@ -1072,10 +1129,13 @@ const RESUMABLE_BLOCKED_STATUS_DETAILS = new Set([
|
|
|
1072
1129
|
"dynamic_ui_unavailable",
|
|
1073
1130
|
"dynamic_approval_timeout",
|
|
1074
1131
|
]);
|
|
1132
|
+
export function isBlockedTaskResumableForResume(task) {
|
|
1133
|
+
return (task.status === "blocked" &&
|
|
1134
|
+
RESUMABLE_BLOCKED_STATUS_DETAILS.has(task.statusDetail));
|
|
1135
|
+
}
|
|
1075
1136
|
export function resetTaskForResume(task) {
|
|
1076
1137
|
if (!RESUMABLE_TASK_STATUSES.has(task.status) &&
|
|
1077
|
-
!(task
|
|
1078
|
-
RESUMABLE_BLOCKED_STATUS_DETAILS.has(task.statusDetail))) {
|
|
1138
|
+
!isBlockedTaskResumableForResume(task)) {
|
|
1079
1139
|
return false;
|
|
1080
1140
|
}
|
|
1081
1141
|
recordTaskResumeEvent(task);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface CompactStringsOptions {
|
|
2
|
+
/** Trim returned strings before filtering. Defaults to true. */
|
|
3
|
+
trim?: boolean;
|
|
4
|
+
/** Drop duplicate strings after optional trimming. Defaults to true. */
|
|
5
|
+
unique?: boolean;
|
|
6
|
+
/** Drop strings whose raw/trimmed form is empty. Defaults to true. */
|
|
7
|
+
dropEmpty?: boolean;
|
|
8
|
+
/** Drop strings whose trimmed form is empty even when trim=false. */
|
|
9
|
+
dropWhitespaceOnly?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function compactStrings(values: readonly unknown[], options?: CompactStringsOptions): string[];
|
package/dist/strings.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function compactStrings(values, options = {}) {
|
|
2
|
+
const trim = options.trim ?? true;
|
|
3
|
+
const unique = options.unique ?? true;
|
|
4
|
+
const dropEmpty = options.dropEmpty ?? true;
|
|
5
|
+
const dropWhitespaceOnly = options.dropWhitespaceOnly ?? trim;
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const result = [];
|
|
8
|
+
for (const value of values) {
|
|
9
|
+
if (typeof value !== "string")
|
|
10
|
+
continue;
|
|
11
|
+
const compacted = trim ? value.trim() : value;
|
|
12
|
+
if (dropEmpty &&
|
|
13
|
+
(dropWhitespaceOnly ? value.trim().length === 0 : compacted.length === 0)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (unique) {
|
|
17
|
+
if (seen.has(compacted))
|
|
18
|
+
continue;
|
|
19
|
+
seen.add(compacted);
|
|
20
|
+
}
|
|
21
|
+
result.push(compacted);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|