@agwab/pi-workflow 0.2.1 → 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 +52 -19
- package/dist/dynamic-generated-task-runtime.js +3 -1
- package/dist/dynamic-profiles.d.ts +1 -1
- package/dist/engine-run-graph.d.ts +3 -0
- package/dist/engine-run-graph.js +194 -4
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +389 -41
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +30 -8
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -1
- 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 +139 -35
- package/dist/strings.d.ts +11 -0
- package/dist/strings.js +24 -0
- package/dist/subagent-backend.js +710 -40
- package/dist/types.d.ts +107 -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-runtime.js +10 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +194 -52
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +109 -30
- package/docs/usage.md +76 -29
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +1046 -576
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1356 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/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 +63 -18
- package/src/dynamic-generated-task-runtime.ts +3 -1
- package/src/dynamic-profiles.ts +1 -1
- package/src/engine-run-graph.ts +246 -4
- package/src/engine.ts +545 -38
- package/src/extension.ts +36 -6
- package/src/index.ts +52 -1
- package/src/prompt-json.ts +13 -0
- package/src/roles.ts +6 -9
- package/src/store.ts +194 -42
- package/src/strings.ts +38 -0
- package/src/subagent-backend.ts +921 -62
- package/src/types.ts +116 -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-runtime.ts +18 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +654 -232
- package/src/workflow-web-source.ts +153 -39
- 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 +229 -36
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +81 -2
- package/workflows/deep-research/helpers/render-executive.mjs +40 -26
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- 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 -3
- 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 +13 -3
- package/workflows/deep-research/spec.json +32 -12
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/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/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
package/src/types.ts
CHANGED
|
@@ -203,7 +203,14 @@ export interface ArtifactGraphStageSpec {
|
|
|
203
203
|
maxRuntimeMs?: number;
|
|
204
204
|
maxConcurrency?: number;
|
|
205
205
|
maxItems?: number;
|
|
206
|
-
from?:
|
|
206
|
+
from?:
|
|
207
|
+
| string
|
|
208
|
+
| string[]
|
|
209
|
+
| {
|
|
210
|
+
source: string;
|
|
211
|
+
path: string;
|
|
212
|
+
streaming?: { enabled: true; minChunk?: number };
|
|
213
|
+
};
|
|
207
214
|
after?: string | string[];
|
|
208
215
|
sourcePolicy?: "success" | "partial" | "require-success";
|
|
209
216
|
sourceProjection?: {
|
|
@@ -213,12 +220,14 @@ export interface ArtifactGraphStageSpec {
|
|
|
213
220
|
inputPolicy?: {
|
|
214
221
|
requiredReads?: string[];
|
|
215
222
|
enforcement?: "fail";
|
|
223
|
+
artifactAccess?: "enabled" | "none";
|
|
216
224
|
};
|
|
217
225
|
output?: {
|
|
218
226
|
controlSchema?: string;
|
|
219
227
|
analysis?: { required?: boolean };
|
|
220
228
|
refs?: { required?: boolean; minItems?: number };
|
|
221
229
|
maxDigestChars?: number;
|
|
230
|
+
partial?: { paths: string[] };
|
|
222
231
|
};
|
|
223
232
|
each?: Record<string, unknown>;
|
|
224
233
|
stages?: ArtifactGraphStageSpec[];
|
|
@@ -490,8 +499,10 @@ export interface CompiledArtifactGraphTask {
|
|
|
490
499
|
controlSchema?: string;
|
|
491
500
|
controlSchemaPath?: string;
|
|
492
501
|
maxDigestChars?: number;
|
|
502
|
+
partial?: { paths: string[] };
|
|
493
503
|
};
|
|
494
504
|
requiredReads: string[];
|
|
505
|
+
artifactAccess: "enabled" | "none";
|
|
495
506
|
sourceProjection?: {
|
|
496
507
|
include?: string[];
|
|
497
508
|
maxChars?: number;
|
|
@@ -542,6 +553,13 @@ export interface CompiledTask {
|
|
|
542
553
|
branchId?: string;
|
|
543
554
|
outputProfile?: string;
|
|
544
555
|
};
|
|
556
|
+
foreachGenerated?: {
|
|
557
|
+
placeholderSpecId: string;
|
|
558
|
+
itemHash?: string;
|
|
559
|
+
itemSourceSpecId?: string;
|
|
560
|
+
itemSourceKind?: "control" | "partial";
|
|
561
|
+
itemRef?: string;
|
|
562
|
+
};
|
|
545
563
|
loopChild?: CompiledLoopChildTaskRef;
|
|
546
564
|
loopPlaceholder?: {
|
|
547
565
|
loopId: string;
|
|
@@ -568,6 +586,89 @@ export type WorkflowRunStatus =
|
|
|
568
586
|
| "failed"
|
|
569
587
|
| "interrupted";
|
|
570
588
|
|
|
589
|
+
export interface WorkflowTaskUsageValues {
|
|
590
|
+
inputTokens?: number | null;
|
|
591
|
+
outputTokens?: number | null;
|
|
592
|
+
totalTokens?: number | null;
|
|
593
|
+
cachedInputTokens?: number | null;
|
|
594
|
+
cacheCreationInputTokens?: number | null;
|
|
595
|
+
cacheReadInputTokens?: number | null;
|
|
596
|
+
reasoningTokens?: number | null;
|
|
597
|
+
costUsd?: number | null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export interface WorkflowTaskUsageAttemptRecord
|
|
601
|
+
extends WorkflowTaskUsageValues {
|
|
602
|
+
source: string;
|
|
603
|
+
capturedAt: string;
|
|
604
|
+
provider?: string;
|
|
605
|
+
model?: string;
|
|
606
|
+
thinking?: ThinkingLevel | string;
|
|
607
|
+
backendRunId?: string;
|
|
608
|
+
backendAttemptId?: string;
|
|
609
|
+
unavailable?: true;
|
|
610
|
+
raw?: unknown;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export interface WorkflowTaskUsageAggregateRecord
|
|
614
|
+
extends WorkflowTaskUsageValues {
|
|
615
|
+
attempts: number;
|
|
616
|
+
incomplete?: boolean;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export interface WorkflowTaskUsageRecord extends WorkflowTaskUsageValues {
|
|
620
|
+
source: "pi-subagent";
|
|
621
|
+
capturedAt: string;
|
|
622
|
+
provider?: string;
|
|
623
|
+
model?: string;
|
|
624
|
+
thinking?: ThinkingLevel | string;
|
|
625
|
+
incomplete?: boolean;
|
|
626
|
+
aggregate?: WorkflowTaskUsageAggregateRecord;
|
|
627
|
+
attempts?: WorkflowTaskUsageAttemptRecord[];
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export interface WorkflowTaskTimingAttemptRecord {
|
|
631
|
+
source: string;
|
|
632
|
+
capturedAt: string;
|
|
633
|
+
backendRunId?: string;
|
|
634
|
+
backendAttemptId?: string;
|
|
635
|
+
launchQueuedAt?: string;
|
|
636
|
+
launchStartedAt?: string;
|
|
637
|
+
launchCompletedAt?: string;
|
|
638
|
+
launchWaitMs?: number;
|
|
639
|
+
launchDurationMs?: number;
|
|
640
|
+
executionStartedAt?: string;
|
|
641
|
+
executionCompletedAt?: string;
|
|
642
|
+
executionMs?: number | null;
|
|
643
|
+
totalMs?: number;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export interface WorkflowTaskTimingAggregateRecord {
|
|
647
|
+
attempts: number;
|
|
648
|
+
launchWaitMs?: number | null;
|
|
649
|
+
launchDurationMs?: number | null;
|
|
650
|
+
executionMs?: number | null;
|
|
651
|
+
totalMs?: number | null;
|
|
652
|
+
incomplete?: boolean;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export interface WorkflowTaskTimingRecord {
|
|
656
|
+
source: "pi-workflow";
|
|
657
|
+
capturedAt: string;
|
|
658
|
+
launchQueuedAt?: string;
|
|
659
|
+
launchStartedAt?: string;
|
|
660
|
+
launchCompletedAt?: string;
|
|
661
|
+
launchWaitMs?: number;
|
|
662
|
+
launchDurationMs?: number;
|
|
663
|
+
launchSlotReleaseDelayMs?: number;
|
|
664
|
+
executionStartedAt?: string;
|
|
665
|
+
executionCompletedAt?: string;
|
|
666
|
+
executionMs?: number | null;
|
|
667
|
+
totalMs?: number;
|
|
668
|
+
aggregate?: WorkflowTaskTimingAggregateRecord;
|
|
669
|
+
attempts?: WorkflowTaskTimingAttemptRecord[];
|
|
670
|
+
}
|
|
671
|
+
|
|
571
672
|
export interface WorkflowTaskRunRecord {
|
|
572
673
|
taskId: string;
|
|
573
674
|
specId: string;
|
|
@@ -606,6 +707,8 @@ export interface WorkflowTaskRunRecord {
|
|
|
606
707
|
startedAt?: string;
|
|
607
708
|
completedAt?: string;
|
|
608
709
|
elapsedMs?: number;
|
|
710
|
+
usage?: WorkflowTaskUsageRecord;
|
|
711
|
+
timing?: WorkflowTaskTimingRecord;
|
|
609
712
|
exitCode?: number;
|
|
610
713
|
files: {
|
|
611
714
|
systemPrompt: string;
|
|
@@ -634,6 +737,13 @@ export interface WorkflowTaskRunRecord {
|
|
|
634
737
|
branchId?: string;
|
|
635
738
|
outputProfile?: string;
|
|
636
739
|
};
|
|
740
|
+
foreachGenerated?: {
|
|
741
|
+
placeholderSpecId: string;
|
|
742
|
+
itemHash?: string;
|
|
743
|
+
itemSourceSpecId?: string;
|
|
744
|
+
itemSourceKind?: "control" | "partial";
|
|
745
|
+
itemRef?: string;
|
|
746
|
+
};
|
|
637
747
|
launchRetry?: {
|
|
638
748
|
attempts: number;
|
|
639
749
|
maxAttempts?: number;
|
|
@@ -724,7 +834,11 @@ export interface WorkflowIndexRecord {
|
|
|
724
834
|
rootRunId?: string;
|
|
725
835
|
round?: number;
|
|
726
836
|
fanout?: unknown[];
|
|
727
|
-
|
|
837
|
+
/**
|
|
838
|
+
* Deprecated compatibility projection. New index writes omit task rows;
|
|
839
|
+
* consumers that need task-level details should load runJson/run.json.
|
|
840
|
+
*/
|
|
841
|
+
tasks?: Array<{
|
|
728
842
|
taskId: string;
|
|
729
843
|
displayName: string;
|
|
730
844
|
agent: string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const VERIFICATION_STATUS = Object.freeze({
|
|
2
|
+
VERIFIED: "verified",
|
|
3
|
+
PARTIALLY_SUPPORTED: "partially_supported",
|
|
4
|
+
UNSUPPORTED: "unsupported",
|
|
5
|
+
CONFLICTING: "conflicting",
|
|
6
|
+
VERIFICATION_BLOCKED: "verification_blocked",
|
|
7
|
+
UNVERIFIED: "unverified",
|
|
8
|
+
} as const);
|
|
9
|
+
|
|
10
|
+
export type VerificationStatus =
|
|
11
|
+
(typeof VERIFICATION_STATUS)[keyof typeof VERIFICATION_STATUS];
|
|
12
|
+
|
|
13
|
+
export type TerminalVerificationStatus = Exclude<
|
|
14
|
+
VerificationStatus,
|
|
15
|
+
(typeof VERIFICATION_STATUS)["UNVERIFIED"]
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
export const VERIFICATION_STATUS_VALUES = Object.freeze([
|
|
19
|
+
VERIFICATION_STATUS.VERIFIED,
|
|
20
|
+
VERIFICATION_STATUS.PARTIALLY_SUPPORTED,
|
|
21
|
+
VERIFICATION_STATUS.UNSUPPORTED,
|
|
22
|
+
VERIFICATION_STATUS.CONFLICTING,
|
|
23
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED,
|
|
24
|
+
] as const satisfies readonly TerminalVerificationStatus[]);
|
|
25
|
+
|
|
26
|
+
export const VERIFICATION_STATUS_BUCKETS = Object.freeze({
|
|
27
|
+
[VERIFICATION_STATUS.VERIFIED]: "verified",
|
|
28
|
+
[VERIFICATION_STATUS.PARTIALLY_SUPPORTED]: "partiallySupported",
|
|
29
|
+
[VERIFICATION_STATUS.UNSUPPORTED]: "unsupported",
|
|
30
|
+
[VERIFICATION_STATUS.CONFLICTING]: "conflicting",
|
|
31
|
+
[VERIFICATION_STATUS.VERIFICATION_BLOCKED]: "verificationBlocked",
|
|
32
|
+
} as const satisfies Record<TerminalVerificationStatus, string>);
|
|
33
|
+
|
|
34
|
+
export const VERIFICATION_STATUS_LABELS = Object.freeze({
|
|
35
|
+
[VERIFICATION_STATUS.VERIFIED]: "verified",
|
|
36
|
+
[VERIFICATION_STATUS.PARTIALLY_SUPPORTED]: "partially supported",
|
|
37
|
+
[VERIFICATION_STATUS.UNSUPPORTED]: "unsupported",
|
|
38
|
+
[VERIFICATION_STATUS.CONFLICTING]: "conflicting",
|
|
39
|
+
[VERIFICATION_STATUS.VERIFICATION_BLOCKED]: "verification blocked",
|
|
40
|
+
[VERIFICATION_STATUS.UNVERIFIED]: "unverified",
|
|
41
|
+
} as const satisfies Record<VerificationStatus, string>);
|
|
42
|
+
|
|
43
|
+
export function canonicalVerificationStatus(
|
|
44
|
+
status: unknown,
|
|
45
|
+
): VerificationStatus {
|
|
46
|
+
const text = String(status ?? "").trim();
|
|
47
|
+
if (!text) return VERIFICATION_STATUS.UNVERIFIED;
|
|
48
|
+
if (text === "partiallySupported") {
|
|
49
|
+
return VERIFICATION_STATUS.PARTIALLY_SUPPORTED;
|
|
50
|
+
}
|
|
51
|
+
if (text === "verificationBlocked" || text === "blocked") {
|
|
52
|
+
return VERIFICATION_STATUS.VERIFICATION_BLOCKED;
|
|
53
|
+
}
|
|
54
|
+
return Object.values(VERIFICATION_STATUS).includes(text as VerificationStatus)
|
|
55
|
+
? (text as VerificationStatus)
|
|
56
|
+
: VERIFICATION_STATUS.UNVERIFIED;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function verificationStatusBucket(status: unknown): string {
|
|
60
|
+
const canonical = canonicalVerificationStatus(status);
|
|
61
|
+
return canonical in VERIFICATION_STATUS_BUCKETS
|
|
62
|
+
? VERIFICATION_STATUS_BUCKETS[canonical as TerminalVerificationStatus]
|
|
63
|
+
: "other";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isVerifiedStatus(status: unknown): boolean {
|
|
67
|
+
return canonicalVerificationStatus(status) === VERIFICATION_STATUS.VERIFIED;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isVerificationBlockedStatus(status: unknown): boolean {
|
|
71
|
+
return (
|
|
72
|
+
canonicalVerificationStatus(status) ===
|
|
73
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const NON_VERIFIED_TERMINAL_STATUSES = new Set<VerificationStatus>([
|
|
78
|
+
VERIFICATION_STATUS.PARTIALLY_SUPPORTED,
|
|
79
|
+
VERIFICATION_STATUS.UNSUPPORTED,
|
|
80
|
+
VERIFICATION_STATUS.CONFLICTING,
|
|
81
|
+
VERIFICATION_STATUS.VERIFICATION_BLOCKED,
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
export function isNonVerifiedTerminalStatus(status: unknown): boolean {
|
|
85
|
+
return NON_VERIFIED_TERMINAL_STATUSES.has(
|
|
86
|
+
canonicalVerificationStatus(status),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -147,8 +147,7 @@ const WORKFLOW_ARTIFACT_KIND_SET = new Set<string>(WORKFLOW_ARTIFACT_KINDS);
|
|
|
147
147
|
const DEFAULT_MAX_BYTES = 50 * 1024;
|
|
148
148
|
const DEFAULT_MAX_LINES = 2000;
|
|
149
149
|
const SOURCE_NAME_PATTERN = /^[A-Za-z0-9_.:-]+$/;
|
|
150
|
-
const SIMPLE_JSON_PATH_PATTERN =
|
|
151
|
-
/^(\$|\$(\.[A-Za-z0-9_-]+(\[(\*|\d+|\d*:\d*)\])?)+)$/;
|
|
150
|
+
const SIMPLE_JSON_PATH_PATTERN = /^(\$|\$(\.[A-Za-z0-9_-]+)+)$/;
|
|
152
151
|
const JSON_PATH_SEGMENT_ALIASES: Record<string, string> = {
|
|
153
152
|
axes: "researchAxes",
|
|
154
153
|
claimVerdicts: "claimVerdictLedger",
|
|
@@ -466,15 +465,14 @@ async function readProjectedWorkflowArtifact(options: {
|
|
|
466
465
|
path: effectivePath,
|
|
467
466
|
});
|
|
468
467
|
const serialized = JSON.stringify(sliced.value, null, 2);
|
|
468
|
+
const maxChars = options.maxChars ?? DEFAULT_MAX_BYTES;
|
|
469
469
|
const preview =
|
|
470
|
-
|
|
471
|
-
? serialized.slice(0, options.maxChars)
|
|
472
|
-
: serialized;
|
|
470
|
+
serialized.length > maxChars ? serialized.slice(0, maxChars) : serialized;
|
|
473
471
|
const projection: WorkflowArtifactProjectionMetadata = {
|
|
474
472
|
path: effectivePath,
|
|
475
473
|
valueType: jsonValueType(resolved),
|
|
476
474
|
...(options.maxItems === undefined ? {} : { maxItems: options.maxItems }),
|
|
477
|
-
|
|
475
|
+
maxChars,
|
|
478
476
|
...(sliced.totalItems === undefined
|
|
479
477
|
? {}
|
|
480
478
|
: { totalItems: sliced.totalItems }),
|
|
@@ -810,7 +808,7 @@ function normalizeProjectionPath(value: unknown): string | undefined {
|
|
|
810
808
|
if (path === undefined) return undefined;
|
|
811
809
|
if (!SIMPLE_JSON_PATH_PATTERN.test(path)) {
|
|
812
810
|
throw new Error(
|
|
813
|
-
"path must be $ or a simple dot JSON path like $.claims.items",
|
|
811
|
+
"path must be $ or a simple dot JSON path like $.claims.items; array selectors are not supported",
|
|
814
812
|
);
|
|
815
813
|
}
|
|
816
814
|
return path;
|
|
@@ -9,6 +9,7 @@ interface WorkflowTelemetryAccumulator {
|
|
|
9
9
|
launchRetries: number;
|
|
10
10
|
resumeEvents: number;
|
|
11
11
|
resumedTasks: number;
|
|
12
|
+
contextLimitFailures: number;
|
|
12
13
|
retryReasons: WorkflowTelemetrySummary["retryReasons"];
|
|
13
14
|
resumeStatusCounts: StatusCounts;
|
|
14
15
|
outputRepairCounts: OutputRepairCounts;
|
|
@@ -18,6 +19,13 @@ export interface WorkflowTelemetrySummary {
|
|
|
18
19
|
taskCount: number;
|
|
19
20
|
wallClockMs: number | null;
|
|
20
21
|
statusCounts: StatusCounts;
|
|
22
|
+
completion: {
|
|
23
|
+
health: "clean" | "repaired" | "incomplete";
|
|
24
|
+
clean: boolean;
|
|
25
|
+
repaired: boolean;
|
|
26
|
+
repairEvents: number;
|
|
27
|
+
contextLimitFailures: number;
|
|
28
|
+
};
|
|
21
29
|
retryCounts: { output: number; launch: number };
|
|
22
30
|
retryReasons: {
|
|
23
31
|
output: Record<string, number>;
|
|
@@ -77,10 +85,23 @@ export function summarizeWorkflowTelemetry(
|
|
|
77
85
|
stage.outputBytes += taskOutputBytes;
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
const repairEvents =
|
|
89
|
+
accumulator.outputRetries +
|
|
90
|
+
accumulator.launchRetries +
|
|
91
|
+
accumulator.resumeEvents;
|
|
92
|
+
const health = completionHealth(tasks, repairEvents, accumulator);
|
|
93
|
+
|
|
80
94
|
return {
|
|
81
95
|
taskCount: tasks.length,
|
|
82
96
|
wallClockMs: durationBetween(run.createdAt, run.updatedAt),
|
|
83
97
|
statusCounts,
|
|
98
|
+
completion: {
|
|
99
|
+
health,
|
|
100
|
+
clean: health === "clean",
|
|
101
|
+
repaired: health === "repaired",
|
|
102
|
+
repairEvents,
|
|
103
|
+
contextLimitFailures: accumulator.contextLimitFailures,
|
|
104
|
+
},
|
|
84
105
|
retryCounts: {
|
|
85
106
|
output: accumulator.outputRetries,
|
|
86
107
|
launch: accumulator.launchRetries,
|
|
@@ -103,6 +124,7 @@ function createWorkflowTelemetryAccumulator(): WorkflowTelemetryAccumulator {
|
|
|
103
124
|
launchRetries: 0,
|
|
104
125
|
resumeEvents: 0,
|
|
105
126
|
resumedTasks: 0,
|
|
127
|
+
contextLimitFailures: 0,
|
|
106
128
|
retryReasons: { output: {}, launch: {} },
|
|
107
129
|
resumeStatusCounts: {},
|
|
108
130
|
outputRepairCounts: { sameSession: 0, newSession: 0, unknown: 0 },
|
|
@@ -113,6 +135,7 @@ function accumulateTaskReliability(
|
|
|
113
135
|
task: Partial<WorkflowTaskRunRecord>,
|
|
114
136
|
accumulator: WorkflowTelemetryAccumulator,
|
|
115
137
|
): void {
|
|
138
|
+
if (taskHasContextLimitFailure(task)) accumulator.contextLimitFailures += 1;
|
|
116
139
|
const currentOutputAttempts = positiveCount(task.outputRetry?.attempts);
|
|
117
140
|
accumulator.outputRetries += currentOutputAttempts;
|
|
118
141
|
if (currentOutputAttempts > 0) {
|
|
@@ -137,17 +160,40 @@ function accumulateTaskReliability(
|
|
|
137
160
|
for (const event of resumeEvents) accumulateResumeEvent(event, accumulator);
|
|
138
161
|
}
|
|
139
162
|
|
|
163
|
+
function completionHealth(
|
|
164
|
+
tasks: Array<Partial<WorkflowTaskRunRecord>>,
|
|
165
|
+
repairEvents: number,
|
|
166
|
+
accumulator: WorkflowTelemetryAccumulator,
|
|
167
|
+
): WorkflowTelemetrySummary["completion"]["health"] {
|
|
168
|
+
const allCompleted =
|
|
169
|
+
tasks.length > 0 && tasks.every((task) => task.status === "completed");
|
|
170
|
+
if (!allCompleted) return "incomplete";
|
|
171
|
+
return repairEvents === 0 && accumulator.contextLimitFailures === 0
|
|
172
|
+
? "clean"
|
|
173
|
+
: "repaired";
|
|
174
|
+
}
|
|
175
|
+
|
|
140
176
|
function accumulateResumeEvent(
|
|
141
177
|
event: NonNullable<WorkflowTaskRunRecord["resumeEvents"]>[number],
|
|
142
178
|
accumulator: WorkflowTelemetryAccumulator,
|
|
143
179
|
): void {
|
|
144
180
|
accumulator.resumeStatusCounts[event.fromStatus] =
|
|
145
181
|
(accumulator.resumeStatusCounts[event.fromStatus] ?? 0) + 1;
|
|
182
|
+
if (resumeEventHasContextLimitFailure(event))
|
|
183
|
+
accumulator.contextLimitFailures += 1;
|
|
146
184
|
const previousOutputAttempts = positiveCount(event.outputRetryAttempts);
|
|
147
185
|
accumulator.outputRetries += previousOutputAttempts;
|
|
148
|
-
if (previousOutputAttempts
|
|
149
|
-
|
|
150
|
-
|
|
186
|
+
if (previousOutputAttempts > 0) {
|
|
187
|
+
countReason(accumulator.retryReasons.output, event.outputRetryReason);
|
|
188
|
+
countRepairMode(
|
|
189
|
+
accumulator.outputRepairCounts,
|
|
190
|
+
event.outputRetryRepairMode,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const previousLaunchAttempts = positiveCount(event.launchRetryAttempts);
|
|
194
|
+
accumulator.launchRetries += previousLaunchAttempts;
|
|
195
|
+
if (previousLaunchAttempts > 0)
|
|
196
|
+
countReason(accumulator.retryReasons.launch, event.launchRetryReason);
|
|
151
197
|
}
|
|
152
198
|
|
|
153
199
|
function positiveCount(value: number | undefined): number {
|
|
@@ -172,6 +218,40 @@ function countRepairMode(
|
|
|
172
218
|
else counts.unknown += 1;
|
|
173
219
|
}
|
|
174
220
|
|
|
221
|
+
function taskHasContextLimitFailure(
|
|
222
|
+
task: Partial<WorkflowTaskRunRecord>,
|
|
223
|
+
): boolean {
|
|
224
|
+
return [
|
|
225
|
+
task.statusDetail,
|
|
226
|
+
task.lastMessage,
|
|
227
|
+
task.outputRetry?.reason,
|
|
228
|
+
task.outputRetry?.message,
|
|
229
|
+
task.launchRetry?.reason,
|
|
230
|
+
task.launchRetry?.message,
|
|
231
|
+
].some(isContextLimitText);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function resumeEventHasContextLimitFailure(
|
|
235
|
+
event: NonNullable<WorkflowTaskRunRecord["resumeEvents"]>[number],
|
|
236
|
+
): boolean {
|
|
237
|
+
return [
|
|
238
|
+
event.fromStatusDetail,
|
|
239
|
+
event.lastMessage,
|
|
240
|
+
event.outputRetryReason,
|
|
241
|
+
event.launchRetryReason,
|
|
242
|
+
].some(isContextLimitText);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isContextLimitText(value: string | undefined): boolean {
|
|
246
|
+
const text = value?.toLowerCase() ?? "";
|
|
247
|
+
return (
|
|
248
|
+
text.includes("context_or_request_too_large") ||
|
|
249
|
+
/context (window|length)|maximum context|request too large|token limit/.test(
|
|
250
|
+
text,
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
175
255
|
export interface SourceContextPacket {
|
|
176
256
|
tasks: SourceContextTask[];
|
|
177
257
|
byStage: Record<
|
|
@@ -18,6 +18,7 @@ export interface WorkflowFetchCacheConfig {
|
|
|
18
18
|
runId: string;
|
|
19
19
|
taskId: string;
|
|
20
20
|
cacheDir: string;
|
|
21
|
+
maxInlineChars?: number;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export interface WorkflowFetchCacheExtensionWrapperOptions {
|
|
@@ -83,7 +84,7 @@ export function registerWorkflowFetchCacheExtension(
|
|
|
83
84
|
webAccessExtension: WebAccessExtension,
|
|
84
85
|
storage: WebAccessStorage,
|
|
85
86
|
): void {
|
|
86
|
-
|
|
87
|
+
const capturedFetchDataByResponseId = new Map<string, Record<string, unknown>>();
|
|
87
88
|
const adapter = new Proxy(pi, {
|
|
88
89
|
get(target, property, receiver) {
|
|
89
90
|
if (property === "registerTool") {
|
|
@@ -97,32 +98,43 @@ export function registerWorkflowFetchCacheExtension(
|
|
|
97
98
|
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
98
99
|
const cacheKey = cacheKeyForParams(params);
|
|
99
100
|
if (!cacheKey) {
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
return capFetchContentInlineResult(
|
|
102
|
+
await tool.execute!(
|
|
103
|
+
toolCallId,
|
|
104
|
+
params,
|
|
105
|
+
signal,
|
|
106
|
+
onUpdate,
|
|
107
|
+
),
|
|
108
|
+
config.maxInlineChars,
|
|
105
109
|
);
|
|
106
110
|
}
|
|
107
111
|
const hit = await readCacheRecord(config, cacheKey.key);
|
|
108
112
|
if (hit) {
|
|
109
113
|
await recordCacheEvent(config, "hit", cacheKey);
|
|
110
|
-
return
|
|
114
|
+
return capFetchContentInlineResult(
|
|
115
|
+
materializeCacheHit(pi, storage, hit),
|
|
116
|
+
config.maxInlineChars,
|
|
117
|
+
);
|
|
111
118
|
}
|
|
112
119
|
await recordCacheEvent(config, "miss", cacheKey);
|
|
113
|
-
capturedFetchData = undefined;
|
|
114
120
|
const result = await tool.execute!(
|
|
115
121
|
toolCallId,
|
|
116
122
|
params,
|
|
117
123
|
signal,
|
|
118
124
|
onUpdate,
|
|
119
125
|
);
|
|
120
|
-
const
|
|
121
|
-
|
|
126
|
+
const responseId = stringValue(result.details?.responseId);
|
|
127
|
+
const storedData = responseId
|
|
128
|
+
? capturedFetchDataByResponseId.get(responseId)
|
|
129
|
+
: undefined;
|
|
130
|
+
if (responseId) capturedFetchDataByResponseId.delete(responseId);
|
|
122
131
|
const writeReason = cacheWriteSkipReason(result, storedData);
|
|
123
132
|
if (writeReason) {
|
|
124
133
|
await recordCacheEvent(config, "skip", cacheKey, writeReason);
|
|
125
|
-
return
|
|
134
|
+
return capFetchContentInlineResult(
|
|
135
|
+
result,
|
|
136
|
+
config.maxInlineChars,
|
|
137
|
+
);
|
|
126
138
|
}
|
|
127
139
|
await writeCacheRecord(config, {
|
|
128
140
|
schema: WORKFLOW_FETCH_CONTENT_CACHE_SCHEMA,
|
|
@@ -134,7 +146,10 @@ export function registerWorkflowFetchCacheExtension(
|
|
|
134
146
|
storedData: storedData!,
|
|
135
147
|
});
|
|
136
148
|
await recordCacheEvent(config, "write", cacheKey);
|
|
137
|
-
return
|
|
149
|
+
return capFetchContentInlineResult(
|
|
150
|
+
withCacheDetails(result, { hit: false }),
|
|
151
|
+
config.maxInlineChars,
|
|
152
|
+
);
|
|
138
153
|
},
|
|
139
154
|
});
|
|
140
155
|
};
|
|
@@ -142,7 +157,10 @@ export function registerWorkflowFetchCacheExtension(
|
|
|
142
157
|
if (property === "appendEntry") {
|
|
143
158
|
return (type: string, data: unknown) => {
|
|
144
159
|
if (type === "web-search-results" && isFetchStoredData(data)) {
|
|
145
|
-
|
|
160
|
+
const cloned = cloneJsonObject(data);
|
|
161
|
+
const responseId = stringValue(cloned?.id);
|
|
162
|
+
if (responseId && cloned)
|
|
163
|
+
capturedFetchDataByResponseId.set(responseId, cloned);
|
|
146
164
|
}
|
|
147
165
|
return pi.appendEntry?.(type, data);
|
|
148
166
|
};
|
|
@@ -300,6 +318,49 @@ function withCacheDetails(
|
|
|
300
318
|
};
|
|
301
319
|
}
|
|
302
320
|
|
|
321
|
+
function capFetchContentInlineResult(
|
|
322
|
+
result: ToolResult,
|
|
323
|
+
maxInlineChars: number | undefined,
|
|
324
|
+
): ToolResult {
|
|
325
|
+
const maxChars = normalizeInlineCharCap(maxInlineChars);
|
|
326
|
+
if (maxChars === undefined || !Array.isArray(result.content)) return result;
|
|
327
|
+
|
|
328
|
+
let truncated = false;
|
|
329
|
+
const content = result.content.map((entry) => {
|
|
330
|
+
if (entry.type !== "text" || typeof entry.text !== "string")
|
|
331
|
+
return entry;
|
|
332
|
+
if (entry.text.length <= maxChars) return entry;
|
|
333
|
+
truncated = true;
|
|
334
|
+
return {
|
|
335
|
+
...entry,
|
|
336
|
+
text:
|
|
337
|
+
entry.text.slice(0, maxChars) +
|
|
338
|
+
`\n\n[Workflow inline fetch content capped at ${maxChars} chars; full source content remains in workflow source cache.]`,
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
if (!truncated) return result;
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
...result,
|
|
345
|
+
content,
|
|
346
|
+
details: {
|
|
347
|
+
...(result.details ?? {}),
|
|
348
|
+
truncated: true,
|
|
349
|
+
workflowInlineContentCap: {
|
|
350
|
+
type: "fetch_content",
|
|
351
|
+
maxChars,
|
|
352
|
+
truncated: true,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function normalizeInlineCharCap(value: number | undefined): number | undefined {
|
|
359
|
+
if (value === undefined || !Number.isFinite(value)) return undefined;
|
|
360
|
+
const cap = Math.floor(value);
|
|
361
|
+
return cap > 0 ? cap : undefined;
|
|
362
|
+
}
|
|
363
|
+
|
|
303
364
|
function cacheWriteSkipReason(
|
|
304
365
|
result: ToolResult,
|
|
305
366
|
storedData: Record<string, unknown> | undefined,
|
|
@@ -366,6 +427,10 @@ function cloneJsonObject(value: unknown): Record<string, unknown> | undefined {
|
|
|
366
427
|
return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;
|
|
367
428
|
}
|
|
368
429
|
|
|
430
|
+
function stringValue(value: unknown): string | undefined {
|
|
431
|
+
return typeof value === "string" && value ? value : undefined;
|
|
432
|
+
}
|
|
433
|
+
|
|
369
434
|
function isFetchStoredData(value: unknown): value is Record<string, unknown> {
|
|
370
435
|
return isRecord(value) && value.type === "fetch" && Array.isArray(value.urls);
|
|
371
436
|
}
|