@agwab/pi-workflow 0.1.2 → 0.2.1
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 +9 -13
- package/dist/compiler.d.ts +5 -5
- package/dist/compiler.js +82 -24
- package/dist/dynamic-generated-task-runtime.d.ts +2 -0
- package/dist/dynamic-generated-task-runtime.js +21 -8
- package/dist/engine.d.ts +6 -5
- package/dist/engine.js +39 -54
- package/dist/extension.js +211 -24
- package/dist/store.d.ts +3 -1
- package/dist/store.js +135 -38
- package/dist/subagent-backend.d.ts +4 -0
- package/dist/subagent-backend.js +128 -4
- package/dist/types.d.ts +5 -0
- package/dist/workflow-progress-health.d.ts +37 -0
- package/dist/workflow-progress-health.js +296 -0
- package/dist/workflow-runtime.d.ts +8 -0
- package/dist/workflow-runtime.js +63 -10
- package/dist/workflow-view.d.ts +2 -0
- package/dist/workflow-view.js +97 -18
- package/dist/workflow-web-source.js +32 -14
- package/docs/usage.md +12 -1
- package/package.json +6 -6
- package/src/compiler.ts +136 -41
- package/src/dynamic-generated-task-runtime.ts +47 -12
- package/src/engine.ts +55 -100
- package/src/extension.ts +270 -34
- package/src/store.ts +180 -44
- package/src/subagent-backend.ts +170 -6
- package/src/types.ts +10 -0
- package/src/workflow-progress-health.ts +461 -0
- package/src/workflow-runtime.ts +85 -13
- package/src/workflow-view.ts +186 -41
- package/src/workflow-web-source.ts +192 -69
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +111 -37
- package/workflows/deep-research/helpers/final-audit-packet.mjs +191 -14
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +159 -50
- package/workflows/deep-research/helpers/render-executive.mjs +671 -37
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +2 -0
- package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
- package/workflows/deep-research/spec.json +41 -11
package/dist/workflow-view.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare class WorkflowView implements Component {
|
|
|
62
62
|
private taskIdentityLines;
|
|
63
63
|
private taskOverviewLines;
|
|
64
64
|
private taskTimelineLines;
|
|
65
|
+
private taskHealthLines;
|
|
65
66
|
private taskValidationStripLines;
|
|
66
67
|
private taskArtifactViewerLines;
|
|
67
68
|
private currentArtifactSourceLines;
|
|
@@ -83,6 +84,7 @@ export declare class WorkflowView implements Component {
|
|
|
83
84
|
private syncSelectedTaskId;
|
|
84
85
|
private breadcrumbText;
|
|
85
86
|
private runSummaryLines;
|
|
87
|
+
private runHealthLines;
|
|
86
88
|
private runDetailSummaryLines;
|
|
87
89
|
private stageContextLines;
|
|
88
90
|
private footerText;
|
package/dist/workflow-view.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { workflowRunPath, fromProjectPath, listRunRecords, readIndex, readRunRecord, } from "./store.js";
|
|
4
|
+
import { diagnoseWorkflowRunHealth, diagnoseWorkflowTaskHealth, } from "./workflow-progress-health.js";
|
|
4
5
|
import { WORKFLOW_RUN_TYPE, } from "./types.js";
|
|
5
6
|
const REFRESH_INTERVAL_MS = 1_000;
|
|
6
7
|
const MAX_LIST_ROWS = 18;
|
|
@@ -277,14 +278,18 @@ export class WorkflowView {
|
|
|
277
278
|
}
|
|
278
279
|
renderRunsScreen(width) {
|
|
279
280
|
const selected = this.flows[this.selectedFlow];
|
|
281
|
+
const selectedDetail = selected && this.detailRun?.runId === selected.runId
|
|
282
|
+
? this.detailRun
|
|
283
|
+
: undefined;
|
|
280
284
|
const sideLines = [
|
|
281
285
|
accent(this.theme, "All runs"),
|
|
282
286
|
kvRow(this.theme, "total", String(this.flows.length)),
|
|
283
287
|
kvRow(this.theme, "running", String(this.flows.filter((flow) => flow.status === "running").length)),
|
|
288
|
+
kvRow(this.theme, "needs action", String(this.flows.filter((flow) => ["failed", "blocked", "interrupted"].includes(flow.status)).length)),
|
|
284
289
|
"",
|
|
285
290
|
accent(this.theme, "Selected"),
|
|
286
291
|
...(selected
|
|
287
|
-
? this.runSummaryLines(selected)
|
|
292
|
+
? this.runSummaryLines(selected, selectedDetail)
|
|
288
293
|
: [placeholder(this.theme, "none")]),
|
|
289
294
|
];
|
|
290
295
|
return this.renderTwoPane(width, "Filters / Summary", sideLines, "Runs", this.runLines(Math.max(1, this.mainPaneBodyWidth(width))), 32);
|
|
@@ -331,9 +336,10 @@ export class WorkflowView {
|
|
|
331
336
|
return width - leftWidth - 5;
|
|
332
337
|
}
|
|
333
338
|
renderTaskDetail(width, run, task) {
|
|
339
|
+
const taskHealth = diagnoseWorkflowTaskHealth(task, run);
|
|
334
340
|
const lines = [
|
|
335
341
|
...boxed(this.theme, "Task Detail", width, [
|
|
336
|
-
`${statusGlyph(this.theme, task.status)} ${strong(this.theme, task.displayName)} ${statusBadge(this.theme, task.status)} ${muted(this.theme, this.breadcrumbText())}`,
|
|
342
|
+
`${statusGlyph(this.theme, task.status)} ${strong(this.theme, task.displayName)} ${statusBadge(this.theme, task.status)} ${healthInline(this.theme, taskHealth)} ${muted(this.theme, this.breadcrumbText())}`,
|
|
337
343
|
taskMetaLine(this.theme, [
|
|
338
344
|
["agent", task.agent],
|
|
339
345
|
["stage", task.stageId ?? "(none)"],
|
|
@@ -343,6 +349,10 @@ export class WorkflowView {
|
|
|
343
349
|
], statusColor(task.status)),
|
|
344
350
|
"",
|
|
345
351
|
];
|
|
352
|
+
const healthLines = this.taskHealthLines(taskHealth, width - 4);
|
|
353
|
+
if (healthLines.length > 0) {
|
|
354
|
+
lines.push(...boxed(this.theme, "Health", width, healthLines, healthColor(taskHealth)), "");
|
|
355
|
+
}
|
|
346
356
|
const validationLines = this.taskValidationStripLines(task, width - 4);
|
|
347
357
|
if (validationLines.length > 0) {
|
|
348
358
|
lines.push(...boxed(this.theme, "Validation", width, validationLines, task.outputValidation?.status === "invalid" ||
|
|
@@ -388,13 +398,15 @@ export class WorkflowView {
|
|
|
388
398
|
const name = flow.name ?? flow.type;
|
|
389
399
|
const left = `${prefix}${marker} ${selected ? strong(this.theme, name) : name}`;
|
|
390
400
|
const runIdText = shortId(flow.runId).slice(0, 16).padEnd(16, " ");
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
401
|
+
const detailRun = this.detailRun?.runId === flow.runId ? this.detailRun : undefined;
|
|
402
|
+
const health = diagnoseWorkflowRunHealth(detailRun ?? flow);
|
|
403
|
+
const healthText = health.state === "completed"
|
|
404
|
+
? ""
|
|
405
|
+
: ` ${muted(this.theme, "·")} ${healthLabel(this.theme, health)}`;
|
|
394
406
|
const baseRight = `${statusColumn(this.theme, flow.status, runStatusLabel(flow), statusWidth)} ${progressBar(this.theme, flow.taskSummary, 5)} ${metaValue(this.theme, runIdText)}`;
|
|
395
407
|
const right = width >= 90
|
|
396
|
-
? `${baseRight} ${muted(this.theme, "·")} ${metaLabel(this.theme, "start")} ${metaValue(this.theme, timestampText(flow.createdAt))}${
|
|
397
|
-
: `${baseRight}${
|
|
408
|
+
? `${baseRight} ${muted(this.theme, "·")} ${metaLabel(this.theme, "start")} ${metaValue(this.theme, timestampText(flow.createdAt))}${healthText}`
|
|
409
|
+
: `${baseRight}${healthText}`;
|
|
398
410
|
const line = joinColumns(left, right, width, 17);
|
|
399
411
|
lines.push(selectedLine(this.theme, line, width, selected, true));
|
|
400
412
|
}
|
|
@@ -437,7 +449,7 @@ export class WorkflowView {
|
|
|
437
449
|
const selected = index === this.selectedTask;
|
|
438
450
|
const prefix = selected ? accent(this.theme, "› ") : " ";
|
|
439
451
|
const left = `${prefix}${statusGlyph(this.theme, task.status)} ${selected ? strong(this.theme, task.displayName) : task.displayName}`;
|
|
440
|
-
const right = taskListStatusLabel(this.theme, task);
|
|
452
|
+
const right = taskListStatusLabel(this.theme, task, diagnoseWorkflowTaskHealth(task, run));
|
|
441
453
|
const line = joinColumns(left, metaByStatus(this.theme, task.status, right), width, Math.max(22, Math.floor(width * 0.45)));
|
|
442
454
|
lines.push(selectedLine(this.theme, line, width, selected, true));
|
|
443
455
|
}
|
|
@@ -487,6 +499,24 @@ export class WorkflowView {
|
|
|
487
499
|
: "warning"));
|
|
488
500
|
return lines.map((line) => fit(line, width));
|
|
489
501
|
}
|
|
502
|
+
taskHealthLines(health, width) {
|
|
503
|
+
if (health.state === "completed" || health.state === "pending")
|
|
504
|
+
return [];
|
|
505
|
+
const lines = [
|
|
506
|
+
`${healthGlyph(this.theme, health)} ${healthLabel(this.theme, health)} ${muted(this.theme, health.summary)}`,
|
|
507
|
+
kvRow(this.theme, "suggested", health.suggestion, healthColor(health)),
|
|
508
|
+
kvRow(this.theme, "why", health.reason),
|
|
509
|
+
];
|
|
510
|
+
if (health.currentTask?.elapsedMs !== undefined)
|
|
511
|
+
lines.splice(1, 0, kvRow(this.theme, "elapsed", formatDuration(health.currentTask.elapsedMs)));
|
|
512
|
+
if (health.durationClass)
|
|
513
|
+
lines.push(kvRow(this.theme, "duration", `${health.durationClass} expected`));
|
|
514
|
+
if (health.heartbeatAgeMs !== undefined)
|
|
515
|
+
lines.push(kvRow(this.theme, "heartbeat", `${formatDuration(health.heartbeatAgeMs)} ago`));
|
|
516
|
+
if (health.lastActivityAgeMs !== undefined)
|
|
517
|
+
lines.push(kvRow(this.theme, "activity", `${formatDuration(health.lastActivityAgeMs)} ago`));
|
|
518
|
+
return lines.map((line) => fit(line, width));
|
|
519
|
+
}
|
|
490
520
|
taskValidationStripLines(task, width) {
|
|
491
521
|
const summary = taskValidationSummary(task);
|
|
492
522
|
if (!summary)
|
|
@@ -502,9 +532,7 @@ export class WorkflowView {
|
|
|
502
532
|
const total = sourceLines.length;
|
|
503
533
|
const maxStart = Math.max(0, total - TASK_ARTIFACT_VIEW_LINES);
|
|
504
534
|
const start = Math.min(this.artifactScrollLine, maxStart);
|
|
505
|
-
const end = total === 0
|
|
506
|
-
? 0
|
|
507
|
-
: Math.min(total, start + TASK_ARTIFACT_VIEW_LINES);
|
|
535
|
+
const end = total === 0 ? 0 : Math.min(total, start + TASK_ARTIFACT_VIEW_LINES);
|
|
508
536
|
const visible = total === 0
|
|
509
537
|
? [
|
|
510
538
|
this.taskArtifactView === "output"
|
|
@@ -686,7 +714,8 @@ export class WorkflowView {
|
|
|
686
714
|
return this.tasksForSelectedStage(this.detailRun)[this.selectedTask];
|
|
687
715
|
}
|
|
688
716
|
syncSelectedTaskId(tasks) {
|
|
689
|
-
const stageTasks = tasks ??
|
|
717
|
+
const stageTasks = tasks ??
|
|
718
|
+
(this.detailRun ? this.tasksForSelectedStage(this.detailRun) : []);
|
|
690
719
|
this.selectedTaskId = stageTasks[this.selectedTask]?.taskId ?? "";
|
|
691
720
|
}
|
|
692
721
|
breadcrumbText() {
|
|
@@ -704,7 +733,8 @@ export class WorkflowView {
|
|
|
704
733
|
parts.push(task.displayName);
|
|
705
734
|
return parts.join(" › ");
|
|
706
735
|
}
|
|
707
|
-
runSummaryLines(flow) {
|
|
736
|
+
runSummaryLines(flow, detailRun) {
|
|
737
|
+
const health = diagnoseWorkflowRunHealth(detailRun ?? flow);
|
|
708
738
|
return [
|
|
709
739
|
`${statusGlyph(this.theme, flow.status)} ${strong(this.theme, flow.name ?? flow.type)} ${statusBadge(this.theme, flow.status, runStatusLabel(flow))}`,
|
|
710
740
|
progressBar(this.theme, flow.taskSummary, 8),
|
|
@@ -723,9 +753,28 @@ export class WorkflowView {
|
|
|
723
753
|
elapsedText(flow.createdAt, flow.updatedAt, flow.status === "running"),
|
|
724
754
|
],
|
|
725
755
|
]),
|
|
756
|
+
...this.runHealthLines(health),
|
|
757
|
+
];
|
|
758
|
+
}
|
|
759
|
+
runHealthLines(health) {
|
|
760
|
+
if (health.state === "completed")
|
|
761
|
+
return [];
|
|
762
|
+
const lines = [
|
|
763
|
+
"",
|
|
764
|
+
accent(this.theme, "Health"),
|
|
765
|
+
`${healthGlyph(this.theme, health)} ${healthLabel(this.theme, health)} ${muted(this.theme, health.summary)}`,
|
|
726
766
|
];
|
|
767
|
+
if (health.currentTask?.displayName)
|
|
768
|
+
lines.push(kvRow(this.theme, "current", health.currentTask.displayName));
|
|
769
|
+
if (health.lastActivityAgeMs !== undefined)
|
|
770
|
+
lines.push(kvRow(this.theme, "activity", `${formatDuration(health.lastActivityAgeMs)} ago`));
|
|
771
|
+
if (health.heartbeatAgeMs !== undefined)
|
|
772
|
+
lines.push(kvRow(this.theme, "heartbeat", `${formatDuration(health.heartbeatAgeMs)} ago`));
|
|
773
|
+
lines.push(kvRow(this.theme, "suggested", health.suggestion, healthColor(health)));
|
|
774
|
+
return lines;
|
|
727
775
|
}
|
|
728
776
|
runDetailSummaryLines(run) {
|
|
777
|
+
const health = diagnoseWorkflowRunHealth(run);
|
|
729
778
|
const lines = [
|
|
730
779
|
`${statusGlyph(this.theme, run.status)} ${strong(this.theme, run.name ?? run.type)} ${statusBadge(this.theme, run.status)}`,
|
|
731
780
|
progressBar(this.theme, run.taskSummary, 10),
|
|
@@ -745,6 +794,7 @@ export class WorkflowView {
|
|
|
745
794
|
["updated", timestampText(run.updatedAt)],
|
|
746
795
|
]),
|
|
747
796
|
kvRow(this.theme, "run", shortId(run.runId)),
|
|
797
|
+
...this.runHealthLines(health),
|
|
748
798
|
];
|
|
749
799
|
if (run.fanout && run.fanout.length > 0) {
|
|
750
800
|
lines.push("", accent(this.theme, "Fanout"));
|
|
@@ -1034,7 +1084,29 @@ function statusColor(status) {
|
|
|
1034
1084
|
function statusText(status) {
|
|
1035
1085
|
return status;
|
|
1036
1086
|
}
|
|
1037
|
-
function
|
|
1087
|
+
function healthColor(health) {
|
|
1088
|
+
return health.tone;
|
|
1089
|
+
}
|
|
1090
|
+
function healthGlyph(theme, health) {
|
|
1091
|
+
if (health.tone === "success")
|
|
1092
|
+
return success(theme, "✓");
|
|
1093
|
+
if (health.tone === "warning")
|
|
1094
|
+
return warning(theme, "●");
|
|
1095
|
+
if (health.tone === "error")
|
|
1096
|
+
return errorText(theme, "●");
|
|
1097
|
+
if (health.tone === "dim")
|
|
1098
|
+
return muted(theme, "•");
|
|
1099
|
+
return accent(theme, "●");
|
|
1100
|
+
}
|
|
1101
|
+
function healthLabel(theme, health) {
|
|
1102
|
+
return fg(theme, healthColor(health), strong(theme, health.label));
|
|
1103
|
+
}
|
|
1104
|
+
function healthInline(theme, health) {
|
|
1105
|
+
if (health.state === "completed" || health.state === "pending")
|
|
1106
|
+
return "";
|
|
1107
|
+
return `${healthGlyph(theme, health)} ${healthLabel(theme, health)}`;
|
|
1108
|
+
}
|
|
1109
|
+
function taskListStatusLabel(theme, task, health) {
|
|
1038
1110
|
const validation = taskValidationSummary(task);
|
|
1039
1111
|
const label = validation?.status === "invalid"
|
|
1040
1112
|
? "invalid output"
|
|
@@ -1042,8 +1114,13 @@ function taskListStatusLabel(theme, task) {
|
|
|
1042
1114
|
? "valid"
|
|
1043
1115
|
: task.status === "completed"
|
|
1044
1116
|
? "done"
|
|
1045
|
-
:
|
|
1046
|
-
|
|
1117
|
+
: task.status === "running"
|
|
1118
|
+
? health.label
|
|
1119
|
+
: statusText(task.status);
|
|
1120
|
+
const suffix = task.status === "running" && health.currentTask?.elapsedMs !== undefined
|
|
1121
|
+
? ` ${muted(theme, "·")} ${metaValue(theme, formatDuration(health.currentTask.elapsedMs))}`
|
|
1122
|
+
: "";
|
|
1123
|
+
return `${fg(theme, task.status === "running" ? healthColor(health) : statusColor(task.status), strong(theme, label))}${suffix}`;
|
|
1047
1124
|
}
|
|
1048
1125
|
function compactStatusLabel(theme, status) {
|
|
1049
1126
|
const label = status === "completed" ? "done" : statusText(status);
|
|
@@ -1107,7 +1184,7 @@ function taskValidationSummary(task) {
|
|
|
1107
1184
|
: undefined;
|
|
1108
1185
|
const issueMessage = typeof issue === "string"
|
|
1109
1186
|
? issue
|
|
1110
|
-
: issue?.message ?? issue?.path ?? issue?.code ?? "";
|
|
1187
|
+
: (issue?.message ?? issue?.path ?? issue?.code ?? "");
|
|
1111
1188
|
const message = validation.message ?? validation.reason ?? issueMessage;
|
|
1112
1189
|
if (status === "valid" && !message)
|
|
1113
1190
|
return undefined;
|
|
@@ -1263,7 +1340,9 @@ function truncateToWidth(text, width) {
|
|
|
1263
1340
|
return "";
|
|
1264
1341
|
if (visibleWidth(text) <= safeWidth)
|
|
1265
1342
|
return text;
|
|
1266
|
-
const hasAnsi = text.includes("\u001b[") ||
|
|
1343
|
+
const hasAnsi = text.includes("\u001b[") ||
|
|
1344
|
+
text.includes("\u001b]") ||
|
|
1345
|
+
text.includes("\u001b_");
|
|
1267
1346
|
const ellipsis = "…";
|
|
1268
1347
|
const ellipsisWidth = visibleWidth(ellipsis);
|
|
1269
1348
|
const limit = Math.max(0, safeWidth - ellipsisWidth);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { appendFile, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { appendFile, mkdir, readFile, readdir, rename, writeFile, } from "node:fs/promises";
|
|
3
3
|
import { isIP } from "node:net";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
export const WORKFLOW_WEB_SOURCE_CACHE_SCHEMA = "workflow-web-source-cache-v1";
|
|
@@ -238,7 +238,9 @@ export async function findWorkflowWebSourceByUrl(config, url) {
|
|
|
238
238
|
function sourceIndexEntryMatchesUrl(entry, url, redactedUrl, targetKey, targetDisplayKey) {
|
|
239
239
|
if (entry.urlKey)
|
|
240
240
|
return entry.urlKey === targetKey;
|
|
241
|
-
if (redactedUrlIdentityUnsafe(redactedUrl) ||
|
|
241
|
+
if (redactedUrlIdentityUnsafe(redactedUrl) ||
|
|
242
|
+
redactedUrlIdentityUnsafe(entry.redactedUrl) ||
|
|
243
|
+
redactedUrlIdentityUnsafe(entry.url)) {
|
|
242
244
|
return false;
|
|
243
245
|
}
|
|
244
246
|
return (entry.redactedUrl === redactedUrl ||
|
|
@@ -247,7 +249,8 @@ function sourceIndexEntryMatchesUrl(entry, url, redactedUrl, targetKey, targetDi
|
|
|
247
249
|
sourceUrlDisplayCacheKey(entry.url) === targetDisplayKey);
|
|
248
250
|
}
|
|
249
251
|
function redactedUrlIdentityUnsafe(url) {
|
|
250
|
-
return /REDACTED/.test(url) ||
|
|
252
|
+
return (/REDACTED/.test(url) ||
|
|
253
|
+
/[?&#][^=]*(?:token|secret|password|signature|sig|key|auth|session|credential)[^=]*=/i.test(url));
|
|
251
254
|
}
|
|
252
255
|
async function findWorkflowWebSourceByUrlFromSources(config, url, redactedUrl, targetKey, targetDisplayKey) {
|
|
253
256
|
let entries;
|
|
@@ -269,7 +272,9 @@ async function findWorkflowWebSourceByUrlFromSources(config, url, redactedUrl, t
|
|
|
269
272
|
return source;
|
|
270
273
|
continue;
|
|
271
274
|
}
|
|
272
|
-
if (redactedUrlIdentityUnsafe(redactedUrl) ||
|
|
275
|
+
if (redactedUrlIdentityUnsafe(redactedUrl) ||
|
|
276
|
+
redactedUrlIdentityUnsafe(source.redactedUrl) ||
|
|
277
|
+
redactedUrlIdentityUnsafe(source.url)) {
|
|
273
278
|
continue;
|
|
274
279
|
}
|
|
275
280
|
if (source.redactedUrl === redactedUrl ||
|
|
@@ -316,7 +321,7 @@ export function buildWorkflowWebSourceCard(options) {
|
|
|
316
321
|
};
|
|
317
322
|
}
|
|
318
323
|
export function readWorkflowWebSourceSnippet(options) {
|
|
319
|
-
return readWorkflowWebSourceSnippets({
|
|
324
|
+
return (readWorkflowWebSourceSnippets({
|
|
320
325
|
source: options.source,
|
|
321
326
|
requests: [
|
|
322
327
|
{
|
|
@@ -328,7 +333,7 @@ export function readWorkflowWebSourceSnippet(options) {
|
|
|
328
333
|
],
|
|
329
334
|
maxChars: options.maxChars,
|
|
330
335
|
budget: options.budget,
|
|
331
|
-
})[0] ?? { status: "not_found", visibleChars: 0 };
|
|
336
|
+
})[0] ?? { status: "not_found", visibleChars: 0 });
|
|
332
337
|
}
|
|
333
338
|
export function readWorkflowWebSourceSnippets(options) {
|
|
334
339
|
let normalizedSource;
|
|
@@ -491,7 +496,10 @@ function readWorkflowWebSourceSnippetWithCache(options) {
|
|
|
491
496
|
}
|
|
492
497
|
function snippetForTerms(options) {
|
|
493
498
|
const needles = options.terms
|
|
494
|
-
.map((term) => ({
|
|
499
|
+
.map((term) => ({
|
|
500
|
+
raw: term,
|
|
501
|
+
normalized: normalizeForSearch(term).normalized,
|
|
502
|
+
}))
|
|
495
503
|
.filter((term) => term.normalized.length > 0);
|
|
496
504
|
if (needles.length === 0)
|
|
497
505
|
return { status: "not_found", visibleChars: 0 };
|
|
@@ -545,7 +553,8 @@ function scoreTermWindow(text, matchStart, matchEnd, maxChars, terms) {
|
|
|
545
553
|
.filter((term) => !windowNorm.includes(term.normalized))
|
|
546
554
|
.map((term) => term.raw);
|
|
547
555
|
const occurrenceScore = terms.reduce((score, term) => {
|
|
548
|
-
return score +
|
|
556
|
+
return (score +
|
|
557
|
+
(windowNorm.includes(term.normalized) ? term.normalized.length : 0));
|
|
549
558
|
}, 0);
|
|
550
559
|
return {
|
|
551
560
|
start,
|
|
@@ -675,7 +684,8 @@ function nearbySnippet(text, needle, maxChars) {
|
|
|
675
684
|
async function readWorkflowWebSourceIndexFile(config) {
|
|
676
685
|
try {
|
|
677
686
|
const parsed = JSON.parse(await readFile(indexPath(config), "utf8"));
|
|
678
|
-
if (!isRecord(parsed) ||
|
|
687
|
+
if (!isRecord(parsed) ||
|
|
688
|
+
parsed.schema !== WORKFLOW_WEB_SOURCE_INDEX_SCHEMA) {
|
|
679
689
|
throw new Error("invalid index");
|
|
680
690
|
}
|
|
681
691
|
const sources = Array.isArray(parsed.sources)
|
|
@@ -686,7 +696,9 @@ async function readWorkflowWebSourceIndexFile(config) {
|
|
|
686
696
|
: [];
|
|
687
697
|
return {
|
|
688
698
|
schema: WORKFLOW_WEB_SOURCE_INDEX_SCHEMA,
|
|
689
|
-
updatedAt: typeof parsed.updatedAt === "string"
|
|
699
|
+
updatedAt: typeof parsed.updatedAt === "string"
|
|
700
|
+
? parsed.updatedAt
|
|
701
|
+
: new Date().toISOString(),
|
|
690
702
|
runId: typeof parsed.runId === "string" ? parsed.runId : config.runId,
|
|
691
703
|
sources: mergeSourceIndexEntries(sources),
|
|
692
704
|
};
|
|
@@ -719,7 +731,8 @@ async function readWorkflowWebSourceIndexLedger(config) {
|
|
|
719
731
|
continue;
|
|
720
732
|
try {
|
|
721
733
|
const parsed = JSON.parse(line);
|
|
722
|
-
if (!isRecord(parsed) ||
|
|
734
|
+
if (!isRecord(parsed) ||
|
|
735
|
+
parsed.schema !== WORKFLOW_WEB_SOURCE_INDEX_EVENT_SCHEMA)
|
|
723
736
|
continue;
|
|
724
737
|
const entry = sourceIndexEntryFromUnknown(parsed.entry);
|
|
725
738
|
if (entry)
|
|
@@ -734,7 +747,8 @@ async function readWorkflowWebSourceIndexLedger(config) {
|
|
|
734
747
|
function sourceIndexEntryFromUnknown(value) {
|
|
735
748
|
if (!isRecord(value))
|
|
736
749
|
return undefined;
|
|
737
|
-
if (typeof value.sourceRef !== "string" ||
|
|
750
|
+
if (typeof value.sourceRef !== "string" ||
|
|
751
|
+
!isWorkflowWebSourceRef(value.sourceRef))
|
|
738
752
|
return undefined;
|
|
739
753
|
if (typeof value.createdAt !== "string")
|
|
740
754
|
return undefined;
|
|
@@ -822,7 +836,8 @@ function nonPublicIpReason(address) {
|
|
|
822
836
|
}
|
|
823
837
|
if (isIP(lower) === 4) {
|
|
824
838
|
const parts = lower.split(".").map((part) => Number(part));
|
|
825
|
-
if (parts.length !== 4 ||
|
|
839
|
+
if (parts.length !== 4 ||
|
|
840
|
+
parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255))
|
|
826
841
|
return "non_public_ip_blocked";
|
|
827
842
|
const [a, b, c, d] = parts;
|
|
828
843
|
if (a === 0 || a === 10 || a === 127 || a >= 224)
|
|
@@ -859,7 +874,10 @@ function nonPublicIpReason(address) {
|
|
|
859
874
|
return undefined;
|
|
860
875
|
}
|
|
861
876
|
function redactRecordForModel(value) {
|
|
862
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
877
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
878
|
+
key,
|
|
879
|
+
redactValueForModel(item),
|
|
880
|
+
]));
|
|
863
881
|
}
|
|
864
882
|
function redactValueForModel(value) {
|
|
865
883
|
if (typeof value === "string")
|
package/docs/usage.md
CHANGED
|
@@ -187,6 +187,17 @@ A run prints a `workflow_*` id. Use that id for follow-up commands:
|
|
|
187
187
|
|
|
188
188
|
The runtime task is not optional. `/workflow run <workflow>` and `/workflow dynamic` without task text fail before launch.
|
|
189
189
|
|
|
190
|
+
### Opt-in fast mode
|
|
191
|
+
|
|
192
|
+
For lower-latency runs, pass `--thinking low` explicitly:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
/workflow run --thinking low deep-research "Research this repository and summarize the architecture tradeoffs."
|
|
196
|
+
/workflow dynamic --thinking low "Research this repository and summarize the architecture tradeoffs."
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This is an opt-in fast mode. Package defaults remain conservative until a separate holdout evaluation provides enough evidence to change them. Current evidence is limited but encouraging for explicit fast runs: the 2026-07-02 `deep-research` combined gate on P1/P2/P3-style prompts resolved non-support tasks to `low`, completed selected valid runs in about 15-17 minutes, passed the strict gate 9/9, and had zero source-ref join failures across those 9 runs. Treat this as a speed option, not proof that every workflow should default to `low`.
|
|
200
|
+
|
|
190
201
|
### Run-scoped web-source cache
|
|
191
202
|
|
|
192
203
|
Prefer normalized workflow web tools in new workflows:
|
|
@@ -211,7 +222,7 @@ Benchmark note: cache-enabled runs are a distinct cohort from older uncached run
|
|
|
211
222
|
|
|
212
223
|
| Workflow | Required agents | Mode | Use when |
|
|
213
224
|
|---|---|---|---|
|
|
214
|
-
| `deep-research` | `researcher` | plan + foreach questions + normalize-input packet support + normalize + foreach verifier + audit support + final-audit packet support +
|
|
225
|
+
| `deep-research` | `researcher` | plan + foreach questions + normalize-input packet support + normalize + foreach verifier + audit support + final-audit packet support + compact final synthesis reduce + deterministic ledger-backed executive render | Use when you need a grounded answer or summary based on source material. |
|
|
215
226
|
| `deep-review` | `scout` | triage + foreach review lenses + dedup support + foreach devil's advocate + verdict-partition support + reduce | Use when you want code or design reviewed carefully from multiple angles. |
|
|
216
227
|
| `spec-review` | `scout` | extract spec + map implementation + inspect tests -> reduce candidates -> foreach verifier -> reduce report | Use when you want to check whether requirements, an API spec, or a contract are reflected in the implementation and tests. |
|
|
217
228
|
| `impact-review` | `scout` | scope/implementation/validation maps -> impact lenses -> consistency/regression/ship-readiness joins -> final synthesis | Use before merging or releasing a change to check affected areas, risks, missing tests, and missing docs. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agwab/pi-workflow",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Workflow orchestration for Pi subagents.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "rm -rf dist && tsc -p tsconfig.json --outDir dist --noEmit false",
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
|
-
"check:scripts": "node
|
|
46
|
+
"check:scripts": "node tools/release/check-scripts.mjs",
|
|
47
47
|
"validate": "npm run check:scripts && npm run typecheck && npm run test:unit",
|
|
48
48
|
"test": "npm run test:unit",
|
|
49
49
|
"test:build": "rm -rf .tmp/unit && tsc -p tsconfig.json --outDir .tmp/unit --noEmit false",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"e2e": "node test/e2e/run.mjs",
|
|
52
52
|
"pack:dry": "npm pack --dry-run --json",
|
|
53
53
|
"prepack": "npm run build",
|
|
54
|
-
"release:check": "node
|
|
55
|
-
"release:dispatch": "node
|
|
54
|
+
"release:check": "node tools/release/release-check.mjs",
|
|
55
|
+
"release:dispatch": "node tools/release/dispatch-release.mjs"
|
|
56
56
|
},
|
|
57
57
|
"pi": {
|
|
58
58
|
"extensions": [
|
|
@@ -70,7 +70,6 @@
|
|
|
70
70
|
"@earendil-works/pi-ai": "^0.78.0",
|
|
71
71
|
"@earendil-works/pi-coding-agent": "*",
|
|
72
72
|
"@types/node": "^24.0.0",
|
|
73
|
-
"typebox": "^1.1.39",
|
|
74
73
|
"typescript": "^5.0.0"
|
|
75
74
|
},
|
|
76
75
|
"engines": {
|
|
@@ -78,7 +77,8 @@
|
|
|
78
77
|
},
|
|
79
78
|
"dependencies": {
|
|
80
79
|
"@agwab/pi-subagent": "^0.3.6",
|
|
81
|
-
"pi-web-access": "^0.10.7"
|
|
80
|
+
"pi-web-access": "^0.10.7",
|
|
81
|
+
"typebox": "^1.1.39"
|
|
82
82
|
},
|
|
83
83
|
"publishConfig": {
|
|
84
84
|
"access": "public"
|