@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.
- package/README.md +20 -15
- package/agents/researcher.md +17 -7
- package/dist/artifact-graph-runtime.js +1 -0
- package/dist/compiler.d.ts +2 -0
- package/dist/compiler.js +29 -4
- package/dist/dynamic-generated-task-runtime.js +4 -3
- package/dist/dynamic-runtime-bundle.js +3 -2
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +3 -2
- package/dist/extension.js +240 -16
- package/dist/store.js +1 -0
- package/dist/subagent-backend.js +82 -27
- package/dist/tool-metadata.d.ts +1 -0
- package/dist/tool-metadata.js +13 -1
- package/dist/types.d.ts +3 -0
- package/dist/workflow-artifact-extension.js +3 -2
- package/dist/workflow-artifact-tool.js +84 -4
- package/dist/workflow-progress-health.d.ts +37 -0
- package/dist/workflow-progress-health.js +296 -0
- package/dist/workflow-runtime.d.ts +6 -0
- package/dist/workflow-runtime.js +33 -10
- package/dist/workflow-view.d.ts +2 -0
- package/dist/workflow-view.js +97 -18
- package/dist/workflow-web-source-extension.d.ts +43 -0
- package/dist/workflow-web-source-extension.js +1194 -0
- package/dist/workflow-web-source.d.ts +171 -0
- package/dist/workflow-web-source.js +915 -0
- package/docs/usage.md +32 -18
- package/node_modules/@agwab/pi-subagent/package.json +1 -1
- package/node_modules/@agwab/pi-subagent/src/api.ts +245 -132
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +243 -163
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +117 -90
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +728 -475
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +305 -209
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +750 -439
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +422 -268
- package/package.json +7 -7
- package/skills/workflow-guide/scaffolds/object-tool-fallback/schemas/fetch-control.schema.json +1 -1
- package/skills/workflow-guide/scaffolds/object-tool-fallback/spec.json +4 -3
- package/src/artifact-graph-runtime.ts +1 -0
- package/src/compiler.ts +43 -3
- package/src/dynamic-generated-task-runtime.ts +4 -2
- package/src/dynamic-runtime-bundle.ts +3 -2
- package/src/engine.ts +7 -16
- package/src/extension.ts +299 -22
- package/src/store.ts +1 -0
- package/src/subagent-backend.ts +121 -37
- package/src/tool-metadata.ts +22 -1
- package/src/types.ts +4 -0
- package/src/workflow-artifact-extension.ts +3 -2
- package/src/workflow-artifact-tool.ts +96 -4
- package/src/workflow-progress-health.ts +461 -0
- package/src/workflow-runtime.ts +50 -13
- package/src/workflow-view.ts +186 -41
- package/src/workflow-web-source-extension.ts +1411 -0
- package/src/workflow-web-source.ts +1294 -0
- package/workflows/README.md +1 -1
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +552 -44
- package/workflows/deep-research/helpers/final-audit-packet.mjs +396 -0
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +545 -0
- package/workflows/deep-research/helpers/render-executive.mjs +1199 -192
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +37 -8
- package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
- package/workflows/deep-research/schemas/deep-research-normalize-claims-control.schema.json +45 -4
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +0 -2
- package/workflows/deep-research/spec.json +71 -26
- package/workflows/deep-review/helpers/render-review-report.mjs +502 -0
- package/workflows/deep-review/schemas/deep-review-render-control.schema.json +50 -0
- package/workflows/deep-review/spec.json +22 -1
package/dist/subagent-backend.js
CHANGED
|
@@ -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
|
|
851
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
|
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
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
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}'.`,
|
package/dist/tool-metadata.d.ts
CHANGED
|
@@ -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;
|
package/dist/tool-metadata.js
CHANGED
|
@@ -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 && !
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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 {};
|