@agwab/pi-workflow 0.1.1 → 0.2.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.
Files changed (70) hide show
  1. package/README.md +20 -15
  2. package/agents/researcher.md +17 -7
  3. package/dist/artifact-graph-runtime.js +1 -0
  4. package/dist/compiler.d.ts +2 -0
  5. package/dist/compiler.js +29 -4
  6. package/dist/dynamic-generated-task-runtime.js +4 -3
  7. package/dist/dynamic-runtime-bundle.js +3 -2
  8. package/dist/engine.d.ts +2 -0
  9. package/dist/engine.js +3 -2
  10. package/dist/extension.js +240 -16
  11. package/dist/store.js +1 -0
  12. package/dist/subagent-backend.js +82 -27
  13. package/dist/tool-metadata.d.ts +1 -0
  14. package/dist/tool-metadata.js +13 -1
  15. package/dist/types.d.ts +3 -0
  16. package/dist/workflow-artifact-extension.js +3 -2
  17. package/dist/workflow-artifact-tool.js +84 -4
  18. package/dist/workflow-progress-health.d.ts +37 -0
  19. package/dist/workflow-progress-health.js +296 -0
  20. package/dist/workflow-runtime.d.ts +6 -0
  21. package/dist/workflow-runtime.js +33 -10
  22. package/dist/workflow-view.d.ts +2 -0
  23. package/dist/workflow-view.js +97 -18
  24. package/dist/workflow-web-source-extension.d.ts +43 -0
  25. package/dist/workflow-web-source-extension.js +1194 -0
  26. package/dist/workflow-web-source.d.ts +171 -0
  27. package/dist/workflow-web-source.js +915 -0
  28. package/docs/usage.md +32 -18
  29. package/node_modules/@agwab/pi-subagent/package.json +1 -1
  30. package/node_modules/@agwab/pi-subagent/src/api.ts +245 -132
  31. package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +243 -163
  32. package/node_modules/@agwab/pi-subagent/src/core/constants.ts +117 -90
  33. package/node_modules/@agwab/pi-subagent/src/core/validation.ts +728 -475
  34. package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +305 -209
  35. package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +750 -439
  36. package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +422 -268
  37. package/package.json +7 -7
  38. package/skills/workflow-guide/scaffolds/object-tool-fallback/schemas/fetch-control.schema.json +1 -1
  39. package/skills/workflow-guide/scaffolds/object-tool-fallback/spec.json +4 -3
  40. package/src/artifact-graph-runtime.ts +1 -0
  41. package/src/compiler.ts +43 -3
  42. package/src/dynamic-generated-task-runtime.ts +4 -2
  43. package/src/dynamic-runtime-bundle.ts +3 -2
  44. package/src/engine.ts +7 -16
  45. package/src/extension.ts +299 -22
  46. package/src/store.ts +1 -0
  47. package/src/subagent-backend.ts +121 -37
  48. package/src/tool-metadata.ts +22 -1
  49. package/src/types.ts +4 -0
  50. package/src/workflow-artifact-extension.ts +3 -2
  51. package/src/workflow-artifact-tool.ts +96 -4
  52. package/src/workflow-progress-health.ts +461 -0
  53. package/src/workflow-runtime.ts +50 -13
  54. package/src/workflow-view.ts +186 -41
  55. package/src/workflow-web-source-extension.ts +1411 -0
  56. package/src/workflow-web-source.ts +1294 -0
  57. package/workflows/README.md +1 -1
  58. package/workflows/deep-research/helpers/claim-evidence-gate.mjs +552 -44
  59. package/workflows/deep-research/helpers/final-audit-packet.mjs +396 -0
  60. package/workflows/deep-research/helpers/normalize-input-packet.mjs +545 -0
  61. package/workflows/deep-research/helpers/render-executive.mjs +1199 -192
  62. package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
  63. package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +37 -8
  64. package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
  65. package/workflows/deep-research/schemas/deep-research-normalize-claims-control.schema.json +45 -4
  66. package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +0 -2
  67. package/workflows/deep-research/spec.json +71 -26
  68. package/workflows/deep-review/helpers/render-review-report.mjs +502 -0
  69. package/workflows/deep-review/schemas/deep-review-render-control.schema.json +50 -0
  70. package/workflows/deep-review/spec.json +22 -1
@@ -6,6 +6,8 @@ import { fromProjectPath, isTerminalTaskStatus, nowIso, toProjectPath, writeRunR
6
6
  import { applyTaskResultArtifact, isTaskTimedOut, markTaskTimedOut, } from "./result.js";
7
7
  import { readWorkflowArtifactReadLedger } from "./workflow-artifact-tool.js";
8
8
  import { writeWorkflowFetchCacheExtensionWrapper } from "./workflow-fetch-cache-extension.js";
9
+ import { writeWorkflowWebSourceExtensionWrapper } from "./workflow-web-source-extension.js";
10
+ import { isWorkflowWebSourceTool } from "./workflow-web-source.js";
9
11
  import { buildWorkflowOutputRetryInstructions, parseWorkflowOutputForBundle, writeWorkflowTaskArtifactBundle, } from "./workflow-output-artifacts.js";
10
12
  const DEFAULT_SUBAGENT_RUNS_ROOT = ".pi/workflow-subagents";
11
13
  const EXTRA_SUBAGENT_EXTENSIONS_ENV = "PI_WORKFLOW_SUBAGENT_EXTRA_EXTENSIONS";
@@ -18,6 +20,7 @@ const MODULE_DIR = dirname(MODULE_PATH);
18
20
  const BUNDLED_PI_WEB_ACCESS_EXTENSION = bundledNodeModulePath("pi-web-access", "index.ts");
19
21
  const BUNDLED_PI_WEB_ACCESS_STORAGE = bundledNodeModulePath("pi-web-access", "storage.ts");
20
22
  const WORKFLOW_FETCH_CACHE_EXTENSION_IMPORT = resolve(MODULE_DIR, `workflow-fetch-cache-extension${extname(MODULE_PATH)}`);
23
+ const WORKFLOW_WEB_SOURCE_EXTENSION_IMPORT = resolve(MODULE_DIR, `workflow-web-source-extension${extname(MODULE_PATH)}`);
21
24
  const TOOL_PROVIDER_EXTENSIONS = {
22
25
  web_search: [BUNDLED_PI_WEB_ACCESS_EXTENSION],
23
26
  code_search: [BUNDLED_PI_WEB_ACCESS_EXTENSION],
@@ -847,36 +850,85 @@ function captureToolCallsEnabled() {
847
850
  return typeof value === "string" && /^(1|true|yes|on)$/i.test(value.trim());
848
851
  }
849
852
  async function workflowTaskExtensions(cwd, run, task, compiledTask) {
850
- const baseExtensions = uniqueStrings([
851
- ...providerExtensionsForTools(compiledTask.runtime.tools, compiledTask.runtime.toolProviders),
853
+ const tools = compiledTask.runtime.tools;
854
+ let extensions = uniqueStrings([
855
+ ...providerExtensionsForTools(tools, compiledTask.runtime.toolProviders),
852
856
  ...extraSubagentExtensionsFromEnv(),
853
857
  ]);
854
- if (!shouldUseFetchContentCache(compiledTask.runtime.tools)) {
855
- return baseExtensions;
856
- }
857
858
  const taskDir = dirname(fromProjectPath(cwd, task.files.result));
858
- const wrapperPath = join(taskDir, "workflow-fetch-cache-extension.ts");
859
- await writeWorkflowFetchCacheExtensionWrapper({
860
- wrapperPath,
861
- importPath: WORKFLOW_FETCH_CACHE_EXTENSION_IMPORT,
862
- webAccessExtensionPath: BUNDLED_PI_WEB_ACCESS_EXTENSION,
863
- webAccessStoragePath: BUNDLED_PI_WEB_ACCESS_STORAGE,
864
- config: {
865
- runId: run.runId,
866
- taskId: task.taskId,
867
- cacheDir: resolve(cwd, ".pi", "workflows", run.runId, "source-cache", "fetch-content"),
868
- },
869
- });
870
- return uniqueStrings([
871
- ...baseExtensions.filter((extension) => resolve(extension) !== BUNDLED_PI_WEB_ACCESS_EXTENSION),
872
- wrapperPath,
873
- ]);
859
+ if (shouldUseFetchContentCache(tools)) {
860
+ const wrapperPath = join(taskDir, "workflow-fetch-cache-extension.ts");
861
+ await writeWorkflowFetchCacheExtensionWrapper({
862
+ wrapperPath,
863
+ importPath: WORKFLOW_FETCH_CACHE_EXTENSION_IMPORT,
864
+ webAccessExtensionPath: BUNDLED_PI_WEB_ACCESS_EXTENSION,
865
+ webAccessStoragePath: BUNDLED_PI_WEB_ACCESS_STORAGE,
866
+ config: {
867
+ runId: run.runId,
868
+ taskId: task.taskId,
869
+ cacheDir: resolve(cwd, ".pi", "workflows", run.runId, "source-cache", "fetch-content"),
870
+ },
871
+ });
872
+ extensions = uniqueStrings([
873
+ ...extensions.filter((extension) => resolve(extension) !== BUNDLED_PI_WEB_ACCESS_EXTENSION),
874
+ wrapperPath,
875
+ ]);
876
+ }
877
+ if (shouldUseWorkflowWebSource(tools)) {
878
+ const providerExtensionPath = workflowWebSourceProviderExtension(tools, compiledTask.runtime.toolProviders);
879
+ const wrapperPath = join(taskDir, "workflow-web-source-extension.ts");
880
+ await writeWorkflowWebSourceExtensionWrapper({
881
+ wrapperPath,
882
+ importPath: WORKFLOW_WEB_SOURCE_EXTENSION_IMPORT,
883
+ providerExtensionPath,
884
+ config: {
885
+ schema: "workflow-web-source-launch-config-v1",
886
+ runId: run.runId,
887
+ taskId: task.taskId,
888
+ cwd,
889
+ cacheDir: resolve(cwd, ".pi", "workflows", run.runId, "web-source-cache"),
890
+ provider: {
891
+ kind: providerExtensionPath === BUNDLED_PI_WEB_ACCESS_EXTENSION
892
+ ? "pi-web-access"
893
+ : "extension",
894
+ extensionPath: providerExtensionPath,
895
+ },
896
+ securityPolicy: {
897
+ allowPrivateHosts: false,
898
+ cacheRawProviderPayloads: false,
899
+ },
900
+ },
901
+ });
902
+ const capturedProviderExtensions = new Set(workflowWebSourceProviderExtensions(tools, compiledTask.runtime.toolProviders));
903
+ extensions = uniqueStrings([
904
+ ...extensions.filter((extension) => !capturedProviderExtensions.has(extension)),
905
+ wrapperPath,
906
+ ]);
907
+ }
908
+ return extensions;
874
909
  }
875
910
  function shouldUseFetchContentCache(tools) {
876
911
  if (!(tools ?? []).includes("fetch_content"))
877
912
  return false;
878
913
  return !isExplicitlyDisabled(fetchContentCacheEnvValue());
879
914
  }
915
+ function shouldUseWorkflowWebSource(tools) {
916
+ return (tools ?? []).some((tool) => isWorkflowWebSourceTool(tool));
917
+ }
918
+ function workflowWebSourceProviderExtension(tools, toolProviders) {
919
+ return (workflowWebSourceProviderExtensions(tools, toolProviders)[0] ??
920
+ BUNDLED_PI_WEB_ACCESS_EXTENSION);
921
+ }
922
+ function workflowWebSourceProviderExtensions(tools, toolProviders) {
923
+ const providers = new Set();
924
+ for (const tool of tools ?? []) {
925
+ if (!isWorkflowWebSourceTool(tool))
926
+ continue;
927
+ for (const provider of toolProviders?.[tool]?.extensions ?? [])
928
+ providers.add(provider);
929
+ }
930
+ return [...providers];
931
+ }
880
932
  function fetchContentCacheEnvValue() {
881
933
  return (process.env[FETCH_CONTENT_CACHE_ENV] ?? process.env[LEGACY_FETCH_CACHE_ENV]);
882
934
  }
@@ -1068,7 +1120,7 @@ function buildSystemPrompt(task) {
1068
1120
  : []),
1069
1121
  ...(workflowRefsUrlValidation
1070
1122
  ? [
1071
- "External URLs in <refs> are validated before completion. Use fetch_content to verify each URL you cite; replace stale or unreachable URLs with working canonical URLs or omit them.",
1123
+ "External URLs in <refs> are validated before completion. Use available workflow web tools to fetch/cache the URL and read exact evidence before citing it; replace stale or unreachable URLs with working canonical URLs or omit them.",
1072
1124
  ]
1073
1125
  : []),
1074
1126
  ]
@@ -1082,11 +1134,14 @@ function buildSystemPrompt(task) {
1082
1134
  ? `Only these tools are enabled for this workflow task: ${enabledTools.join(", ")}.`
1083
1135
  : "No tools are enabled for this workflow task.",
1084
1136
  "If the agent definition below mentions tools that are not in this enabled list, ignore those mentions; unavailable tools cannot be called in this workflow run.",
1085
- !enabledTools.includes("get_search_content") &&
1086
- (enabledTools.includes("web_search") ||
1087
- enabledTools.includes("fetch_content"))
1088
- ? "Full cached search-content hydration is unavailable here. Use web_search/fetch_content results and report evidence gaps instead of broad raw document retrieval."
1089
- : undefined,
1137
+ enabledTools.includes("workflow_web_fetch_source") ||
1138
+ enabledTools.includes("workflow_web_source_read")
1139
+ ? "Workflow web-source tools return compact source cards. Preserve sourceRef values in structured outputs. Use workflow_web_source_read for exact evidence snippets; when several snippets are needed from the same sourceRef, batch them with queries:[...] or reads:[...] instead of making repeated calls. If the exact quote is unknown, pass claim plus 2-6 distinctive terms to harvest a candidate source window and preserve its match metadata. Do not read workflow cache files directly."
1140
+ : !enabledTools.includes("get_search_content") &&
1141
+ (enabledTools.includes("web_search") ||
1142
+ enabledTools.includes("fetch_content"))
1143
+ ? "Full cached search-content hydration is unavailable here. Use web_search/fetch_content results and report evidence gaps instead of broad raw document retrieval."
1144
+ : undefined,
1090
1145
  ].filter((line) => typeof line === "string");
1091
1146
  return [
1092
1147
  `You are Pi workflow subagent '${task.agent}'.`,
@@ -33,3 +33,4 @@ export declare function effectiveToolClassification(tool: string, toolProviders:
33
33
  export declare function classifyToolCapability(tools: string[] | undefined, toolProviders: Record<string, CompiledToolProvider> | undefined, readOnlyDeclared: boolean, options?: ClassifyToolCapabilityOptions): TaskCapability;
34
34
  export declare function buildAvailableToolView(tools?: readonly string[], toolProviders?: Record<string, CompiledToolProvider>): AvailableToolViewItem[];
35
35
  export declare function validateToolAuthority(tools: readonly string[] | undefined, options?: ToolAuthorityValidationOptions): string[];
36
+ export declare function toolAllowedByAuthorityCeiling(tool: string, allowed: ReadonlySet<string>): boolean;
@@ -16,6 +16,9 @@ const BUILTIN_TOOL_METADATA = {
16
16
  code_search: { classification: "read-only" },
17
17
  fetch_content: { classification: "read-only" },
18
18
  get_search_content: { classification: "read-only" },
19
+ workflow_web_search: { classification: "read-only" },
20
+ workflow_web_fetch_source: { classification: "read-only" },
21
+ workflow_web_source_read: { classification: "read-only" },
19
22
  scrapling_fetch: { classification: "read-only" },
20
23
  edit: { classification: "write-capable" },
21
24
  write: { classification: "write-capable" },
@@ -26,6 +29,11 @@ const NON_DOWNGRADABLE_TOOL_FLOORS = {
26
29
  write: "write-capable",
27
30
  bash: "mutation-capable",
28
31
  };
32
+ const TOOL_AUTHORITY_COMPAT_ALIASES = {
33
+ workflow_web_search: ["web_search"],
34
+ workflow_web_fetch_source: ["fetch_content"],
35
+ workflow_web_source_read: ["fetch_content", "get_search_content"],
36
+ };
29
37
  export function toolNameForSpec(tool) {
30
38
  if (typeof tool === "string")
31
39
  return tool;
@@ -168,7 +176,7 @@ export function validateToolAuthority(tools, options = {}) {
168
176
  ? new Set(options.allowedTools)
169
177
  : undefined;
170
178
  for (const tool of tools) {
171
- if (allowed && !allowed.has(tool)) {
179
+ if (allowed && !toolAllowedByAuthorityCeiling(tool, allowed)) {
172
180
  errors.push(`tool "${tool}" is outside the allowed tool ceiling`);
173
181
  continue;
174
182
  }
@@ -179,6 +187,10 @@ export function validateToolAuthority(tools, options = {}) {
179
187
  }
180
188
  return errors;
181
189
  }
190
+ export function toolAllowedByAuthorityCeiling(tool, allowed) {
191
+ return (allowed.has(tool) ||
192
+ (TOOL_AUTHORITY_COMPAT_ALIASES[tool] ?? []).some((alias) => allowed.has(alias)));
193
+ }
182
194
  function maxClassification(...values) {
183
195
  let best;
184
196
  for (const value of values) {
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { WorkflowRuntimeThinkingResolution } from "./workflow-runtime.js";
1
2
  export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh"];
2
3
  export declare const FAST_MODES: readonly ["inherit", "off"];
3
4
  export declare const APPROVAL_MODES: readonly ["non-interactive", "on-request"];
@@ -249,6 +250,7 @@ export interface PermissionPreview {
249
250
  export interface CompiledTaskRuntime {
250
251
  model?: string;
251
252
  thinking?: ThinkingLevel;
253
+ thinkingResolution?: WorkflowRuntimeThinkingResolution;
252
254
  fast?: FastMode;
253
255
  approvalMode: ApprovalMode;
254
256
  tools?: string[];
@@ -505,6 +507,7 @@ export interface WorkflowTaskRunRecord {
505
507
  runtime: {
506
508
  model?: string;
507
509
  thinking?: ThinkingLevel;
510
+ thinkingResolution?: WorkflowRuntimeThinkingResolution;
508
511
  fast?: FastMode;
509
512
  approvalMode: ApprovalMode;
510
513
  maxRuntimeMs?: number;
@@ -32,7 +32,7 @@ const workflowArtifactParameters = {
32
32
  },
33
33
  path: {
34
34
  type: "string",
35
- description: "Optional simple JSON path for projected reads, for example $.claims or $.claimIndex.items. Supported only for JSON artifacts.",
35
+ description: "Optional simple JSON path for projected reads, for example $.claims or $.claimIndex.items. Required when maxItems or maxChars is provided. Supported only for JSON artifacts.",
36
36
  },
37
37
  maxItems: {
38
38
  type: "integer",
@@ -42,7 +42,7 @@ const workflowArtifactParameters = {
42
42
  maxChars: {
43
43
  type: "integer",
44
44
  minimum: 0,
45
- description: "Optional character limit for the projected JSON value after maxItems is applied.",
45
+ description: "Optional character limit for the projected JSON value after maxItems is applied. Requires path; omit maxChars for whole-artifact reads.",
46
46
  },
47
47
  },
48
48
  required: ["action"],
@@ -56,6 +56,7 @@ export function registerWorkflowArtifactTool(pi, config) {
56
56
  promptGuidelines: [
57
57
  "Use workflow_artifact to inspect upstream workflow artifacts when the workflow prompt lists available sources or required reads.",
58
58
  "Call workflow_artifact with action=list to see visible source names before reading an artifact if unsure.",
59
+ "When using maxItems or maxChars, include a JSON path such as $.claims; for whole-artifact reads, omit maxItems/maxChars.",
59
60
  "Do not use repository read for workflow artifacts; workflow_artifact records required-read evidence.",
60
61
  ],
61
62
  parameters: workflowArtifactParameters,
@@ -23,7 +23,24 @@ const WORKFLOW_ARTIFACT_KIND_SET = new Set(WORKFLOW_ARTIFACT_KINDS);
23
23
  const DEFAULT_MAX_BYTES = 50 * 1024;
24
24
  const DEFAULT_MAX_LINES = 2000;
25
25
  const SOURCE_NAME_PATTERN = /^[A-Za-z0-9_.:-]+$/;
26
- const SIMPLE_JSON_PATH_PATTERN = /^(\$|\$(\.[A-Za-z0-9_-]+)+)$/;
26
+ const SIMPLE_JSON_PATH_PATTERN = /^(\$|\$(\.[A-Za-z0-9_-]+(\[(\*|\d+|\d*:\d*)\])?)+)$/;
27
+ const JSON_PATH_SEGMENT_ALIASES = {
28
+ axes: "researchAxes",
29
+ claimVerdicts: "claimVerdictLedger",
30
+ factSlot: "factSlots",
31
+ gaps: "remainingGaps",
32
+ primarySources: "sourcePolicy",
33
+ priorities: "verificationPriorities",
34
+ questions: "researchQuestions",
35
+ requiredSources: "sourcePolicy",
36
+ scope: "researchScope",
37
+ slots: "factSlots",
38
+ sourceQualityRules: "sourcePolicy",
39
+ sourceRequirements: "sourcePolicy",
40
+ verification: "verificationPriorities",
41
+ verificationPriority: "verificationPriorities",
42
+ verdicts: "claimVerdictLedger",
43
+ };
27
44
  export async function loadWorkflowSourceManifest(manifestPath, options = {}) {
28
45
  const absoluteManifestPath = resolve(manifestPath);
29
46
  const runDir = resolve(options.runDir ?? inferRunDirFromManifestPath(absoluteManifestPath));
@@ -192,17 +209,28 @@ export async function readWorkflowArtifact(manifest, sourceName, artifact, optio
192
209
  }
193
210
  async function readProjectedWorkflowArtifact(options) {
194
211
  const parsed = JSON.parse(await readFile(options.artifactPath, "utf8"));
195
- const resolved = readSimpleJsonPath(parsed, options.path);
212
+ let effectivePath = options.path;
213
+ let resolved;
214
+ for (const candidatePath of projectionPathCandidates(options.path, options.source, options.artifact)) {
215
+ resolved = readSimpleJsonPath(parsed, candidatePath);
216
+ if (resolved !== undefined) {
217
+ effectivePath = candidatePath;
218
+ break;
219
+ }
220
+ }
196
221
  if (resolved === undefined) {
197
222
  throw new Error(`workflow_artifact path did not resolve: ${options.path}`);
198
223
  }
199
- const sliced = applyProjectionItemLimit(resolved, options);
224
+ const sliced = applyProjectionItemLimit(resolved, {
225
+ ...options,
226
+ path: effectivePath,
227
+ });
200
228
  const serialized = JSON.stringify(sliced.value, null, 2);
201
229
  const preview = options.maxChars !== undefined && serialized.length > options.maxChars
202
230
  ? serialized.slice(0, options.maxChars)
203
231
  : serialized;
204
232
  const projection = {
205
- path: options.path,
233
+ path: effectivePath,
206
234
  valueType: jsonValueType(resolved),
207
235
  ...(options.maxItems === undefined ? {} : { maxItems: options.maxItems }),
208
236
  ...(options.maxChars === undefined ? {} : { maxChars: options.maxChars }),
@@ -234,6 +262,58 @@ async function readProjectedWorkflowArtifact(options) {
234
262
  projection,
235
263
  };
236
264
  }
265
+ function projectionPathCandidates(path, source, artifact) {
266
+ const candidates = [];
267
+ const seen = new Set();
268
+ const queue = [path];
269
+ for (let index = 0; index < queue.length && index < 32; index += 1) {
270
+ const candidate = queue[index];
271
+ if (seen.has(candidate))
272
+ continue;
273
+ seen.add(candidate);
274
+ candidates.push(candidate);
275
+ for (const next of [
276
+ stripArraySelector(candidate),
277
+ stripSourcePathPrefix(candidate, source),
278
+ stripArtifactPathPrefix(candidate, artifact),
279
+ applyJsonPathSegmentAliases(candidate),
280
+ ]) {
281
+ if (next !== candidate && !seen.has(next))
282
+ queue.push(next);
283
+ }
284
+ }
285
+ return candidates;
286
+ }
287
+ function stripArraySelector(path) {
288
+ return path.replace(/\[(\*|\d+|\d*:\d*)\]/gu, "");
289
+ }
290
+ function stripSourcePathPrefix(path, source) {
291
+ const sourcePrefix = `$.${source}.`;
292
+ if (!path.startsWith(sourcePrefix))
293
+ return path;
294
+ return `$.${path.slice(sourcePrefix.length)}`;
295
+ }
296
+ function stripArtifactPathPrefix(path, artifact) {
297
+ const artifactPath = `$.${artifact}`;
298
+ if (path === artifactPath)
299
+ return "$";
300
+ const artifactPrefix = `${artifactPath}.`;
301
+ if (!path.startsWith(artifactPrefix))
302
+ return path;
303
+ return `$.${path.slice(artifactPrefix.length)}`;
304
+ }
305
+ function applyJsonPathSegmentAliases(path) {
306
+ if (path === "$")
307
+ return path;
308
+ const segments = path
309
+ .slice(2)
310
+ .split(".")
311
+ .map((segment) => segment.replace(/\[(\*|\d+|\d*:\d*)\]$/u, ""));
312
+ const aliased = segments.map((segment) => JSON_PATH_SEGMENT_ALIASES[segment] ?? segment);
313
+ if (aliased.every((segment, index) => segment === segments[index]))
314
+ return path;
315
+ return `$.${aliased.join(".")}`;
316
+ }
237
317
  function applyProjectionItemLimit(value, options) {
238
318
  if (options.maxItems === undefined)
239
319
  return { value };
@@ -0,0 +1,37 @@
1
+ import type { TaskRunStatus, WorkflowRunRecord, WorkflowTaskRunRecord } from "./types.js";
2
+ export type WorkflowHealthState = "completed" | "pending" | "active" | "long-tail" | "stalled" | "likely-stuck" | "needs-action";
3
+ export type WorkflowHealthTone = "success" | "accent" | "warning" | "error" | "dim";
4
+ export type WorkflowDurationClass = "short" | "medium" | "long";
5
+ export type WorkflowHealthSuggestion = "wait" | "inspect" | "resume" | "review";
6
+ export interface WorkflowHealthTaskSummary {
7
+ taskId?: string;
8
+ displayName?: string;
9
+ stageId?: string;
10
+ status?: TaskRunStatus;
11
+ elapsedMs?: number;
12
+ }
13
+ export interface WorkflowProgressHealth {
14
+ state: WorkflowHealthState;
15
+ label: string;
16
+ summary: string;
17
+ tone: WorkflowHealthTone;
18
+ suggestion: WorkflowHealthSuggestion;
19
+ reason: string;
20
+ durationClass?: WorkflowDurationClass;
21
+ currentTask?: WorkflowHealthTaskSummary;
22
+ lastActivityAt?: string;
23
+ lastActivityAgeMs?: number;
24
+ heartbeatAt?: string;
25
+ heartbeatAgeMs?: number;
26
+ }
27
+ type TaskHealthInput = Pick<WorkflowTaskRunRecord, "taskId" | "specId" | "displayName" | "status" | "statusDetail" | "stageId" | "kind" | "startedAt" | "lastMessage" | "runtime" | "backendHandle" | "pid">;
28
+ type RunHealthInput = Pick<WorkflowRunRecord, "status" | "taskSummary" | "createdAt" | "updatedAt"> & {
29
+ tasks?: TaskHealthInput[];
30
+ };
31
+ export interface WorkflowHealthOptions {
32
+ nowMs?: number;
33
+ }
34
+ export declare function diagnoseWorkflowRunHealth(run: RunHealthInput, options?: WorkflowHealthOptions): WorkflowProgressHealth;
35
+ export declare function diagnoseWorkflowTaskHealth(task: TaskHealthInput, run?: Pick<WorkflowRunRecord, "updatedAt">, options?: WorkflowHealthOptions): WorkflowProgressHealth;
36
+ export declare function classifyWorkflowTaskDuration(task: Pick<WorkflowTaskRunRecord, "stageId" | "displayName" | "specId" | "kind" | "statusDetail" | "runtime">): WorkflowDurationClass;
37
+ export {};