@agwab/pi-workflow 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/artifact-graph-runtime.d.ts +1 -1
- package/dist/artifact-graph-runtime.js +10 -5
- package/dist/artifact-graph-schema.js +127 -5
- package/dist/compiler.js +46 -11
- package/dist/dynamic-decision.d.ts +1 -0
- package/dist/dynamic-decision.js +7 -0
- package/dist/dynamic-generated-task-runtime.js +3 -1
- package/dist/dynamic-profiles.d.ts +1 -0
- package/dist/dynamic-profiles.js +3 -0
- package/dist/engine-run-graph.d.ts +2 -0
- package/dist/engine-run-graph.js +55 -5
- package/dist/engine.js +278 -15
- package/dist/extension.js +3 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/prompt-json.d.ts +7 -0
- package/dist/prompt-json.js +13 -0
- package/dist/roles.d.ts +1 -1
- package/dist/roles.js +5 -8
- package/dist/store.d.ts +20 -1
- package/dist/store.js +89 -29
- package/dist/strings.d.ts +11 -0
- package/dist/strings.js +24 -0
- package/dist/subagent-backend.js +557 -13
- package/dist/types.d.ts +101 -1
- package/dist/verification-ontology.d.ts +31 -0
- package/dist/verification-ontology.js +66 -0
- package/dist/workflow-artifact-tool.js +5 -6
- package/dist/workflow-artifacts.d.ts +7 -0
- package/dist/workflow-artifacts.js +55 -4
- package/dist/workflow-fetch-cache-extension.d.ts +1 -0
- package/dist/workflow-fetch-cache-extension.js +57 -9
- package/dist/workflow-metrics.d.ts +113 -0
- package/dist/workflow-metrics.js +272 -0
- package/dist/workflow-output-artifacts.js +5 -3
- package/dist/workflow-partial-output.d.ts +45 -0
- package/dist/workflow-partial-output.js +205 -0
- package/dist/workflow-progress-health.js +42 -10
- package/dist/workflow-web-source-extension.js +27 -4
- package/dist/workflow-web-source.js +26 -12
- package/docs/usage.md +76 -29
- package/node_modules/@agwab/pi-subagent/package.json +1 -1
- package/node_modules/@agwab/pi-subagent/src/index.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/panel.ts +7 -3
- package/package.json +2 -2
- package/skills/workflow-guide/SKILL.md +1 -0
- package/src/artifact-graph-runtime.ts +19 -13
- package/src/artifact-graph-schema.ts +143 -3
- package/src/cli.mjs +52 -0
- package/src/compiler.ts +49 -9
- package/src/dynamic-decision.ts +11 -0
- package/src/dynamic-generated-task-runtime.ts +3 -1
- package/src/dynamic-profiles.ts +4 -0
- package/src/engine-run-graph.ts +63 -4
- package/src/engine.ts +400 -14
- package/src/extension.ts +3 -2
- package/src/index.ts +49 -0
- package/src/prompt-json.ts +13 -0
- package/src/roles.ts +6 -9
- package/src/store.ts +123 -34
- package/src/strings.ts +38 -0
- package/src/subagent-backend.ts +727 -41
- package/src/types.ts +110 -2
- package/src/verification-ontology.ts +88 -0
- package/src/workflow-artifact-tool.ts +5 -7
- package/src/workflow-artifacts.ts +83 -3
- package/src/workflow-fetch-cache-extension.ts +78 -13
- package/src/workflow-metrics.ts +478 -0
- package/src/workflow-output-artifacts.ts +5 -3
- package/src/workflow-partial-output.ts +299 -0
- package/src/workflow-progress-health.ts +47 -15
- package/src/workflow-web-source-extension.ts +33 -4
- package/src/workflow-web-source.ts +36 -12
- package/workflows/README.md +7 -25
- package/workflows/deep-research/batched-verification.spec.json +253 -0
- package/workflows/deep-research/helpers/batch-verification-candidates.mjs +136 -0
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +173 -20
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +80 -1
- package/workflows/deep-research/helpers/render-executive.mjs +32 -5
- package/workflows/deep-research/helpers/shadow-select-verification.mjs +229 -0
- package/workflows/deep-research/helpers/verification-ontology.mjs +77 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +3 -2
- package/workflows/deep-research/schemas/deep-research-research-questions-control.schema.json +38 -0
- package/workflows/deep-research/schemas/deep-research-sanitize-claims-control.schema.json +63 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-batch-control.schema.json +47 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +10 -3
- package/workflows/deep-research/spec.json +32 -12
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stderr +0 -0
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stdout +0 -13
package/dist/subagent-backend.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { copyFile, mkdir, readFile, readdir, rm, writeFile, } from "node:fs/promises";
|
|
3
4
|
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep, } from "node:path";
|
|
@@ -10,13 +11,18 @@ import { writeWorkflowFetchCacheExtensionWrapper } from "./workflow-fetch-cache-
|
|
|
10
11
|
import { writeWorkflowWebSourceExtensionWrapper } from "./workflow-web-source-extension.js";
|
|
11
12
|
import { isWorkflowWebSourceTool } from "./workflow-web-source.js";
|
|
12
13
|
import { buildWorkflowOutputRetryInstructions, parseWorkflowOutputForBundle, writeWorkflowTaskArtifactBundle, } from "./workflow-output-artifacts.js";
|
|
14
|
+
import { writeWorkflowPartialOutputLedgerFromFile } from "./workflow-partial-output.js";
|
|
13
15
|
const DEFAULT_SUBAGENT_RUNS_ROOT = ".pi/workflow-subagents";
|
|
16
|
+
const MAX_SUBAGENT_SESSION_ID_LENGTH = 64;
|
|
14
17
|
const EXTRA_SUBAGENT_EXTENSIONS_ENV = "PI_WORKFLOW_SUBAGENT_EXTRA_EXTENSIONS";
|
|
15
18
|
const FETCH_CONTENT_CACHE_ENV = "PI_WORKFLOW_FETCH_CONTENT_CACHE";
|
|
16
19
|
const LEGACY_FETCH_CACHE_ENV = "PI_WORKFLOW_FETCH_CACHE";
|
|
20
|
+
const FETCH_CONTENT_INLINE_CHARS_ENV = "PI_WORKFLOW_FETCH_CONTENT_INLINE_CHARS";
|
|
21
|
+
const DEFAULT_WORKFLOW_FETCH_CONTENT_INLINE_CHARS = 12_000;
|
|
17
22
|
const DEFAULT_TRANSIENT_MODEL_FAILURE_RETRIES = 5;
|
|
18
23
|
const DEFAULT_ARTIFACT_OUTPUT_RETRIES = 2;
|
|
19
24
|
const MAX_CONCURRENT_LAUNCHES_ENV = "PI_WORKFLOW_MAX_CONCURRENT_LAUNCHES";
|
|
25
|
+
const LAUNCH_SLOT_RELEASE_DELAY_MS_ENV = "PI_WORKFLOW_LAUNCH_SLOT_RELEASE_DELAY_MS";
|
|
20
26
|
const PARENT_SUBAGENT_CWD_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_CWD";
|
|
21
27
|
const PARENT_SUBAGENT_RUNS_DIR_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_RUNS_DIR";
|
|
22
28
|
const PARENT_SUBAGENT_RUN_ID_ENV = "PI_WORKFLOW_PARENT_SUBAGENT_RUN_ID";
|
|
@@ -124,7 +130,7 @@ async function recordTerminalParentSubagentChildEvent(run, task, snapshot) {
|
|
|
124
130
|
message: task.lastMessage,
|
|
125
131
|
});
|
|
126
132
|
}
|
|
127
|
-
let
|
|
133
|
+
let launchSlotReleaseDelayMsForTests;
|
|
128
134
|
let transientRetryJitterForTests;
|
|
129
135
|
const launchWaitQueue = [];
|
|
130
136
|
let activeLaunchSlots = 0;
|
|
@@ -154,6 +160,15 @@ function releaseLaunchSlot() {
|
|
|
154
160
|
}
|
|
155
161
|
activeLaunchSlots = Math.max(0, activeLaunchSlots - 1);
|
|
156
162
|
}
|
|
163
|
+
function resolveLaunchSlotReleaseDelayMs() {
|
|
164
|
+
if (launchSlotReleaseDelayMsForTests !== undefined) {
|
|
165
|
+
return launchSlotReleaseDelayMsForTests;
|
|
166
|
+
}
|
|
167
|
+
const override = Number.parseInt(process.env[LAUNCH_SLOT_RELEASE_DELAY_MS_ENV] ?? "", 10);
|
|
168
|
+
if (Number.isFinite(override))
|
|
169
|
+
return Math.max(0, Math.floor(override));
|
|
170
|
+
return DEFAULT_LAUNCH_SLOT_RELEASE_DELAY_MS;
|
|
171
|
+
}
|
|
157
172
|
function releaseLaunchSlotAfterDelay(delayMs, release) {
|
|
158
173
|
if (delayMs <= 0) {
|
|
159
174
|
release();
|
|
@@ -161,8 +176,9 @@ function releaseLaunchSlotAfterDelay(delayMs, release) {
|
|
|
161
176
|
}
|
|
162
177
|
setTimeout(release, delayMs);
|
|
163
178
|
}
|
|
164
|
-
async function runWithLaunchSlot(action) {
|
|
179
|
+
async function runWithLaunchSlot(action, onAcquired) {
|
|
165
180
|
const release = await acquireLaunchSlot();
|
|
181
|
+
onAcquired?.();
|
|
166
182
|
let holdAfterReturn = false;
|
|
167
183
|
try {
|
|
168
184
|
const result = await action();
|
|
@@ -170,7 +186,7 @@ async function runWithLaunchSlot(action) {
|
|
|
170
186
|
return result;
|
|
171
187
|
}
|
|
172
188
|
finally {
|
|
173
|
-
releaseLaunchSlotAfterDelay(holdAfterReturn ?
|
|
189
|
+
releaseLaunchSlotAfterDelay(holdAfterReturn ? resolveLaunchSlotReleaseDelayMs() : 0, release);
|
|
174
190
|
}
|
|
175
191
|
}
|
|
176
192
|
function transientRetryJitterMs() {
|
|
@@ -183,10 +199,459 @@ function transientRetryJitterMs() {
|
|
|
183
199
|
function sleep(ms) {
|
|
184
200
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
185
201
|
}
|
|
202
|
+
const USAGE_METRIC_KEYS = [
|
|
203
|
+
"inputTokens",
|
|
204
|
+
"outputTokens",
|
|
205
|
+
"totalTokens",
|
|
206
|
+
"cachedInputTokens",
|
|
207
|
+
"cacheCreationInputTokens",
|
|
208
|
+
"cacheReadInputTokens",
|
|
209
|
+
"reasoningTokens",
|
|
210
|
+
"costUsd",
|
|
211
|
+
];
|
|
212
|
+
const USAGE_FIELD_ALIASES = {
|
|
213
|
+
inputTokens: [
|
|
214
|
+
["inputTokens"],
|
|
215
|
+
["input_tokens"],
|
|
216
|
+
["input"],
|
|
217
|
+
["promptTokens"],
|
|
218
|
+
["prompt_tokens"],
|
|
219
|
+
],
|
|
220
|
+
outputTokens: [
|
|
221
|
+
["outputTokens"],
|
|
222
|
+
["output_tokens"],
|
|
223
|
+
["output"],
|
|
224
|
+
["completionTokens"],
|
|
225
|
+
["completion_tokens"],
|
|
226
|
+
],
|
|
227
|
+
totalTokens: [["totalTokens"], ["total_tokens"], ["tokens"], ["total"]],
|
|
228
|
+
cachedInputTokens: [
|
|
229
|
+
["cachedInputTokens"],
|
|
230
|
+
["cached_input_tokens"],
|
|
231
|
+
["prompt_tokens_details", "cached_tokens"],
|
|
232
|
+
["input_tokens_details", "cached_tokens"],
|
|
233
|
+
],
|
|
234
|
+
cacheCreationInputTokens: [
|
|
235
|
+
["cacheCreationInputTokens"],
|
|
236
|
+
["cacheCreationTokens"],
|
|
237
|
+
["cacheWriteTokens"],
|
|
238
|
+
["cache_creation_input_tokens"],
|
|
239
|
+
["cache_write_input_tokens"],
|
|
240
|
+
["cacheWrite"],
|
|
241
|
+
["cache_write"],
|
|
242
|
+
],
|
|
243
|
+
cacheReadInputTokens: [
|
|
244
|
+
["cacheReadInputTokens"],
|
|
245
|
+
["cacheReadTokens"],
|
|
246
|
+
["cache_read_input_tokens"],
|
|
247
|
+
["cacheRead"],
|
|
248
|
+
["cache_read"],
|
|
249
|
+
],
|
|
250
|
+
reasoningTokens: [
|
|
251
|
+
["reasoningTokens"],
|
|
252
|
+
["reasoning_tokens"],
|
|
253
|
+
["reasoning"],
|
|
254
|
+
["completion_tokens_details", "reasoning_tokens"],
|
|
255
|
+
["output_tokens_details", "reasoning_tokens"],
|
|
256
|
+
],
|
|
257
|
+
costUsd: [
|
|
258
|
+
["costUsd"],
|
|
259
|
+
["cost_usd"],
|
|
260
|
+
["totalCostUsd"],
|
|
261
|
+
["total_cost_usd"],
|
|
262
|
+
["estimatedCostUsd"],
|
|
263
|
+
["estimated_cost_usd"],
|
|
264
|
+
["cost", "total"],
|
|
265
|
+
["cost", "totalUsd"],
|
|
266
|
+
["cost", "total_usd"],
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
const TIMING_AGGREGATE_KEYS = [
|
|
270
|
+
"launchWaitMs",
|
|
271
|
+
"launchDurationMs",
|
|
272
|
+
"executionMs",
|
|
273
|
+
"totalMs",
|
|
274
|
+
];
|
|
275
|
+
function isPlainRecord(value) {
|
|
276
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
277
|
+
}
|
|
278
|
+
function hasOwnValue(record, key) {
|
|
279
|
+
return Object.hasOwn(record, key);
|
|
280
|
+
}
|
|
281
|
+
function valueAtPath(record, path) {
|
|
282
|
+
let current = record;
|
|
283
|
+
for (const part of path) {
|
|
284
|
+
if (!isPlainRecord(current) || !hasOwnValue(current, part)) {
|
|
285
|
+
return { found: false, value: undefined };
|
|
286
|
+
}
|
|
287
|
+
current = current[part];
|
|
288
|
+
}
|
|
289
|
+
return { found: true, value: current };
|
|
290
|
+
}
|
|
291
|
+
function usageNumberOrNull(value) {
|
|
292
|
+
if (value === null)
|
|
293
|
+
return null;
|
|
294
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
function normalizedUsageValues(raw) {
|
|
300
|
+
const record = isPlainRecord(raw) ? raw : undefined;
|
|
301
|
+
const values = {};
|
|
302
|
+
if (!record)
|
|
303
|
+
return values;
|
|
304
|
+
for (const key of USAGE_METRIC_KEYS) {
|
|
305
|
+
for (const path of USAGE_FIELD_ALIASES[key]) {
|
|
306
|
+
const candidate = valueAtPath(record, path);
|
|
307
|
+
if (!candidate.found)
|
|
308
|
+
continue;
|
|
309
|
+
const value = usageNumberOrNull(candidate.value);
|
|
310
|
+
if (value === undefined)
|
|
311
|
+
continue;
|
|
312
|
+
values[key] = value;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return values;
|
|
317
|
+
}
|
|
318
|
+
function firstStringValue(records, keys) {
|
|
319
|
+
for (const record of records) {
|
|
320
|
+
if (!record)
|
|
321
|
+
continue;
|
|
322
|
+
for (const key of keys) {
|
|
323
|
+
const value = record[key];
|
|
324
|
+
if (typeof value === "string" && value.trim())
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
function metadataRecord(value) {
|
|
331
|
+
if (!isPlainRecord(value))
|
|
332
|
+
return undefined;
|
|
333
|
+
return isPlainRecord(value.metadata) ? value.metadata : undefined;
|
|
334
|
+
}
|
|
335
|
+
function usageObservation(subagentResult, snapshot) {
|
|
336
|
+
const resultMetadata = metadataRecord(subagentResult);
|
|
337
|
+
if (resultMetadata && hasOwnValue(resultMetadata, "usage")) {
|
|
338
|
+
return {
|
|
339
|
+
source: "subagent-result-metadata",
|
|
340
|
+
raw: resultMetadata.usage,
|
|
341
|
+
present: true,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const snapshotMetadata = isPlainRecord(snapshot.metadata)
|
|
345
|
+
? snapshot.metadata
|
|
346
|
+
: undefined;
|
|
347
|
+
if (snapshotMetadata && hasOwnValue(snapshotMetadata, "usage")) {
|
|
348
|
+
return {
|
|
349
|
+
source: "subagent-snapshot-metadata",
|
|
350
|
+
raw: snapshotMetadata.usage,
|
|
351
|
+
present: true,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (subagentResult && hasOwnValue(subagentResult, "usage")) {
|
|
355
|
+
return {
|
|
356
|
+
source: "subagent-result",
|
|
357
|
+
raw: subagentResult.usage,
|
|
358
|
+
present: true,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const snapshotRecord = snapshot;
|
|
362
|
+
if (hasOwnValue(snapshotRecord, "usage")) {
|
|
363
|
+
return {
|
|
364
|
+
source: "subagent-snapshot",
|
|
365
|
+
raw: snapshotRecord.usage,
|
|
366
|
+
present: true,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
function buildTaskUsageAttempt(options) {
|
|
372
|
+
const resultMetadata = metadataRecord(options.subagentResult);
|
|
373
|
+
const snapshotMetadata = isPlainRecord(options.snapshot.metadata)
|
|
374
|
+
? options.snapshot.metadata
|
|
375
|
+
: undefined;
|
|
376
|
+
const resultRecord = options.subagentResult;
|
|
377
|
+
const snapshotRecord = options.snapshot;
|
|
378
|
+
const records = [
|
|
379
|
+
resultMetadata,
|
|
380
|
+
snapshotMetadata,
|
|
381
|
+
resultRecord,
|
|
382
|
+
snapshotRecord,
|
|
383
|
+
];
|
|
384
|
+
const observed = usageObservation(options.subagentResult, options.snapshot);
|
|
385
|
+
const raw = observed?.raw;
|
|
386
|
+
const unavailable = !observed || raw === null || raw === undefined;
|
|
387
|
+
const provider = firstStringValue(records, ["provider"]);
|
|
388
|
+
const model = firstStringValue(records, ["model"]) ?? options.task.runtime.model;
|
|
389
|
+
const thinking = firstStringValue(records, [
|
|
390
|
+
"thinking",
|
|
391
|
+
"thinkingLevel",
|
|
392
|
+
"reasoningLevel",
|
|
393
|
+
]) ??
|
|
394
|
+
options.task.runtime.thinkingResolution?.resolved ??
|
|
395
|
+
options.task.runtime.thinking;
|
|
396
|
+
return {
|
|
397
|
+
source: observed?.source ?? "subagent-usage-unavailable",
|
|
398
|
+
capturedAt: options.capturedAt,
|
|
399
|
+
backendRunId: options.snapshot.runId,
|
|
400
|
+
backendAttemptId: options.snapshot.attemptId,
|
|
401
|
+
...(provider === undefined ? {} : { provider }),
|
|
402
|
+
...(model === undefined ? {} : { model }),
|
|
403
|
+
...(thinking === undefined ? {} : { thinking }),
|
|
404
|
+
...(unavailable ? { unavailable: true } : {}),
|
|
405
|
+
...(observed?.present && raw !== undefined ? { raw } : {}),
|
|
406
|
+
...normalizedUsageValues(raw),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function usageAttemptKey(attempt) {
|
|
410
|
+
return `${attempt.backendRunId ?? ""}\0${attempt.backendAttemptId ?? ""}\0${attempt.source}`;
|
|
411
|
+
}
|
|
412
|
+
function upsertUsageAttempt(attempts, attempt) {
|
|
413
|
+
const key = usageAttemptKey(attempt);
|
|
414
|
+
const index = attempts.findIndex((candidate) => usageAttemptKey(candidate) === key);
|
|
415
|
+
if (index < 0)
|
|
416
|
+
return [...attempts, attempt];
|
|
417
|
+
return attempts.map((candidate, candidateIndex) => candidateIndex === index ? attempt : candidate);
|
|
418
|
+
}
|
|
419
|
+
function aggregateUsageAttempts(attempts) {
|
|
420
|
+
const values = {};
|
|
421
|
+
let incomplete = attempts.some((attempt) => attempt.unavailable === true);
|
|
422
|
+
for (const key of USAGE_METRIC_KEYS) {
|
|
423
|
+
const anyPresent = attempts.some((attempt) => hasOwnValue(attempt, key));
|
|
424
|
+
if (!anyPresent)
|
|
425
|
+
continue;
|
|
426
|
+
let total = 0;
|
|
427
|
+
let complete = true;
|
|
428
|
+
for (const attempt of attempts) {
|
|
429
|
+
if (!hasOwnValue(attempt, key)) {
|
|
430
|
+
complete = false;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
const value = attempt[key];
|
|
434
|
+
if (typeof value !== "number") {
|
|
435
|
+
complete = false;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
total += value;
|
|
439
|
+
}
|
|
440
|
+
values[key] = complete ? total : null;
|
|
441
|
+
if (!complete)
|
|
442
|
+
incomplete = true;
|
|
443
|
+
}
|
|
444
|
+
return { values, incomplete };
|
|
445
|
+
}
|
|
446
|
+
function latestUsageString(attempts, key) {
|
|
447
|
+
for (let index = attempts.length - 1; index >= 0; index -= 1) {
|
|
448
|
+
const value = attempts[index]?.[key];
|
|
449
|
+
if (typeof value === "string" && value.trim())
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
function recordTaskUsageObservation(options) {
|
|
455
|
+
const attempt = buildTaskUsageAttempt(options);
|
|
456
|
+
const attempts = upsertUsageAttempt(options.task.usage?.attempts ?? [], attempt);
|
|
457
|
+
const aggregate = aggregateUsageAttempts(attempts);
|
|
458
|
+
const usage = {
|
|
459
|
+
source: "pi-subagent",
|
|
460
|
+
capturedAt: options.capturedAt,
|
|
461
|
+
...(latestUsageString(attempts, "provider") === undefined
|
|
462
|
+
? {}
|
|
463
|
+
: { provider: latestUsageString(attempts, "provider") }),
|
|
464
|
+
...(latestUsageString(attempts, "model") === undefined
|
|
465
|
+
? {}
|
|
466
|
+
: { model: latestUsageString(attempts, "model") }),
|
|
467
|
+
...(latestUsageString(attempts, "thinking") === undefined
|
|
468
|
+
? {}
|
|
469
|
+
: { thinking: latestUsageString(attempts, "thinking") }),
|
|
470
|
+
...(aggregate.incomplete ? { incomplete: true } : {}),
|
|
471
|
+
...aggregate.values,
|
|
472
|
+
aggregate: {
|
|
473
|
+
attempts: attempts.length,
|
|
474
|
+
...(aggregate.incomplete ? { incomplete: true } : {}),
|
|
475
|
+
...aggregate.values,
|
|
476
|
+
},
|
|
477
|
+
attempts,
|
|
478
|
+
};
|
|
479
|
+
options.task.usage = usage;
|
|
480
|
+
}
|
|
481
|
+
function isoTimestampMs(timestamp) {
|
|
482
|
+
if (!timestamp)
|
|
483
|
+
return undefined;
|
|
484
|
+
const parsed = Date.parse(timestamp);
|
|
485
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
486
|
+
}
|
|
487
|
+
function durationBetween(startedAt, completedAt) {
|
|
488
|
+
const startedAtMs = isoTimestampMs(startedAt);
|
|
489
|
+
const completedAtMs = isoTimestampMs(completedAt);
|
|
490
|
+
if (startedAtMs === undefined || completedAtMs === undefined)
|
|
491
|
+
return undefined;
|
|
492
|
+
return Math.max(0, completedAtMs - startedAtMs);
|
|
493
|
+
}
|
|
494
|
+
function durationNumber(value) {
|
|
495
|
+
if (value === null)
|
|
496
|
+
return null;
|
|
497
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
498
|
+
return value;
|
|
499
|
+
}
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
function recordTaskLaunchTiming(task, observation) {
|
|
503
|
+
const capturedAt = observation.launchCompletedAt ?? nowIso();
|
|
504
|
+
const launchWaitMs = durationBetween(observation.launchQueuedAt, observation.launchStartedAt);
|
|
505
|
+
const launchDurationMs = durationBetween(observation.launchStartedAt, observation.launchCompletedAt);
|
|
506
|
+
task.timing = {
|
|
507
|
+
source: "pi-workflow",
|
|
508
|
+
capturedAt,
|
|
509
|
+
launchQueuedAt: observation.launchQueuedAt,
|
|
510
|
+
...(observation.launchStartedAt === undefined
|
|
511
|
+
? {}
|
|
512
|
+
: { launchStartedAt: observation.launchStartedAt }),
|
|
513
|
+
...(observation.launchCompletedAt === undefined
|
|
514
|
+
? {}
|
|
515
|
+
: { launchCompletedAt: observation.launchCompletedAt }),
|
|
516
|
+
...(launchWaitMs === undefined ? {} : { launchWaitMs }),
|
|
517
|
+
...(launchDurationMs === undefined ? {} : { launchDurationMs }),
|
|
518
|
+
launchSlotReleaseDelayMs: resolveLaunchSlotReleaseDelayMs(),
|
|
519
|
+
...(task.timing?.aggregate === undefined
|
|
520
|
+
? {}
|
|
521
|
+
: { aggregate: task.timing.aggregate }),
|
|
522
|
+
...(task.timing?.attempts === undefined
|
|
523
|
+
? {}
|
|
524
|
+
: { attempts: task.timing.attempts }),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function buildTaskTimingAttempt(options) {
|
|
528
|
+
const resultDuration = options.subagentResult?.durationMs;
|
|
529
|
+
let executionMs = durationNumber(resultDuration === undefined ? options.snapshot.durationMs : resultDuration);
|
|
530
|
+
if (executionMs === undefined || executionMs === null) {
|
|
531
|
+
executionMs =
|
|
532
|
+
durationBetween(options.startedAt, options.completedAt) ?? executionMs;
|
|
533
|
+
}
|
|
534
|
+
const totalMs = durationBetween(options.task.startedAt ?? options.task.timing?.launchQueuedAt, options.completedAt);
|
|
535
|
+
return {
|
|
536
|
+
source: "pi-subagent",
|
|
537
|
+
capturedAt: options.capturedAt,
|
|
538
|
+
backendRunId: options.snapshot.runId,
|
|
539
|
+
backendAttemptId: options.snapshot.attemptId,
|
|
540
|
+
...(options.task.timing?.launchQueuedAt === undefined
|
|
541
|
+
? {}
|
|
542
|
+
: { launchQueuedAt: options.task.timing.launchQueuedAt }),
|
|
543
|
+
...(options.task.timing?.launchStartedAt === undefined
|
|
544
|
+
? {}
|
|
545
|
+
: { launchStartedAt: options.task.timing.launchStartedAt }),
|
|
546
|
+
...(options.task.timing?.launchCompletedAt === undefined
|
|
547
|
+
? {}
|
|
548
|
+
: { launchCompletedAt: options.task.timing.launchCompletedAt }),
|
|
549
|
+
...(options.task.timing?.launchWaitMs === undefined
|
|
550
|
+
? {}
|
|
551
|
+
: { launchWaitMs: options.task.timing.launchWaitMs }),
|
|
552
|
+
...(options.task.timing?.launchDurationMs === undefined
|
|
553
|
+
? {}
|
|
554
|
+
: { launchDurationMs: options.task.timing.launchDurationMs }),
|
|
555
|
+
...(options.startedAt === undefined
|
|
556
|
+
? {}
|
|
557
|
+
: { executionStartedAt: options.startedAt }),
|
|
558
|
+
...(options.completedAt === undefined
|
|
559
|
+
? {}
|
|
560
|
+
: { executionCompletedAt: options.completedAt }),
|
|
561
|
+
...(executionMs === undefined ? {} : { executionMs }),
|
|
562
|
+
...(totalMs === undefined ? {} : { totalMs }),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function timingAttemptKey(attempt) {
|
|
566
|
+
return `${attempt.backendRunId ?? ""}\0${attempt.backendAttemptId ?? ""}`;
|
|
567
|
+
}
|
|
568
|
+
function upsertTimingAttempt(attempts, attempt) {
|
|
569
|
+
const key = timingAttemptKey(attempt);
|
|
570
|
+
const index = attempts.findIndex((candidate) => timingAttemptKey(candidate) === key);
|
|
571
|
+
if (index < 0)
|
|
572
|
+
return [...attempts, attempt];
|
|
573
|
+
return attempts.map((candidate, candidateIndex) => candidateIndex === index ? attempt : candidate);
|
|
574
|
+
}
|
|
575
|
+
function aggregateTimingAttempts(attempts) {
|
|
576
|
+
const aggregate = {
|
|
577
|
+
attempts: attempts.length,
|
|
578
|
+
};
|
|
579
|
+
let incomplete = false;
|
|
580
|
+
for (const key of TIMING_AGGREGATE_KEYS) {
|
|
581
|
+
const anyPresent = attempts.some((attempt) => hasOwnValue(attempt, key));
|
|
582
|
+
if (!anyPresent)
|
|
583
|
+
continue;
|
|
584
|
+
let total = 0;
|
|
585
|
+
let complete = true;
|
|
586
|
+
for (const attempt of attempts) {
|
|
587
|
+
if (!hasOwnValue(attempt, key)) {
|
|
588
|
+
complete = false;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
const value = attempt[key];
|
|
592
|
+
if (typeof value !== "number") {
|
|
593
|
+
complete = false;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
total += value;
|
|
597
|
+
}
|
|
598
|
+
aggregate[key] = complete ? total : null;
|
|
599
|
+
if (!complete)
|
|
600
|
+
incomplete = true;
|
|
601
|
+
}
|
|
602
|
+
if (incomplete)
|
|
603
|
+
aggregate.incomplete = true;
|
|
604
|
+
return aggregate;
|
|
605
|
+
}
|
|
606
|
+
function recordTaskTerminalTiming(options) {
|
|
607
|
+
const attempt = buildTaskTimingAttempt(options);
|
|
608
|
+
const attempts = upsertTimingAttempt(options.task.timing?.attempts ?? [], attempt);
|
|
609
|
+
options.task.timing = {
|
|
610
|
+
source: "pi-workflow",
|
|
611
|
+
capturedAt: options.capturedAt,
|
|
612
|
+
...(attempt.launchQueuedAt === undefined
|
|
613
|
+
? {}
|
|
614
|
+
: { launchQueuedAt: attempt.launchQueuedAt }),
|
|
615
|
+
...(attempt.launchStartedAt === undefined
|
|
616
|
+
? {}
|
|
617
|
+
: { launchStartedAt: attempt.launchStartedAt }),
|
|
618
|
+
...(attempt.launchCompletedAt === undefined
|
|
619
|
+
? {}
|
|
620
|
+
: { launchCompletedAt: attempt.launchCompletedAt }),
|
|
621
|
+
...(attempt.launchWaitMs === undefined
|
|
622
|
+
? {}
|
|
623
|
+
: { launchWaitMs: attempt.launchWaitMs }),
|
|
624
|
+
...(attempt.launchDurationMs === undefined
|
|
625
|
+
? {}
|
|
626
|
+
: { launchDurationMs: attempt.launchDurationMs }),
|
|
627
|
+
...(options.task.timing?.launchSlotReleaseDelayMs === undefined
|
|
628
|
+
? {}
|
|
629
|
+
: {
|
|
630
|
+
launchSlotReleaseDelayMs: options.task.timing.launchSlotReleaseDelayMs,
|
|
631
|
+
}),
|
|
632
|
+
...(attempt.executionStartedAt === undefined
|
|
633
|
+
? {}
|
|
634
|
+
: { executionStartedAt: attempt.executionStartedAt }),
|
|
635
|
+
...(attempt.executionCompletedAt === undefined
|
|
636
|
+
? {}
|
|
637
|
+
: { executionCompletedAt: attempt.executionCompletedAt }),
|
|
638
|
+
...(attempt.executionMs === undefined
|
|
639
|
+
? {}
|
|
640
|
+
: { executionMs: attempt.executionMs }),
|
|
641
|
+
...(attempt.totalMs === undefined ? {} : { totalMs: attempt.totalMs }),
|
|
642
|
+
aggregate: aggregateTimingAttempts(attempts),
|
|
643
|
+
attempts,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function recordTerminalTaskObservability(options) {
|
|
647
|
+
const capturedAt = nowIso();
|
|
648
|
+
recordTaskUsageObservation({ ...options, capturedAt });
|
|
649
|
+
recordTaskTerminalTiming({ ...options, capturedAt });
|
|
650
|
+
}
|
|
186
651
|
export function setSubagentLaunchControlsForTests(options) {
|
|
187
|
-
|
|
652
|
+
launchSlotReleaseDelayMsForTests =
|
|
188
653
|
options?.releaseDelayMs === undefined
|
|
189
|
-
?
|
|
654
|
+
? undefined
|
|
190
655
|
: Math.max(0, Math.floor(options.releaseDelayMs));
|
|
191
656
|
transientRetryJitterForTests =
|
|
192
657
|
options?.retryJitterMs === undefined
|
|
@@ -283,11 +748,22 @@ export async function launchSubagentTask(cwd, run, task, compiledTask) {
|
|
|
283
748
|
subagentOptions.extensions = extensions;
|
|
284
749
|
if (captureToolCallsEnabled())
|
|
285
750
|
subagentOptions.captureToolCalls = true;
|
|
751
|
+
const launchQueuedAt = nowIso();
|
|
752
|
+
let launchStartedAt;
|
|
753
|
+
recordTaskLaunchTiming(task, { launchQueuedAt });
|
|
286
754
|
if (isLaunchGateSaturated()) {
|
|
287
755
|
task.lastMessage = `waiting for pi-subagent launch slot (${resolveMaxConcurrentLaunches()} max)`;
|
|
288
756
|
await writeRunRecord(cwd, run).catch(() => undefined);
|
|
289
757
|
}
|
|
290
|
-
launched = await runWithLaunchSlot(() => api.runSubagent(subagentOptions))
|
|
758
|
+
launched = await runWithLaunchSlot(() => api.runSubagent(subagentOptions), () => {
|
|
759
|
+
launchStartedAt = nowIso();
|
|
760
|
+
recordTaskLaunchTiming(task, { launchQueuedAt, launchStartedAt });
|
|
761
|
+
});
|
|
762
|
+
recordTaskLaunchTiming(task, {
|
|
763
|
+
launchQueuedAt,
|
|
764
|
+
launchStartedAt,
|
|
765
|
+
launchCompletedAt: nowIso(),
|
|
766
|
+
});
|
|
291
767
|
}
|
|
292
768
|
catch (error) {
|
|
293
769
|
task.status = "pending";
|
|
@@ -378,12 +854,24 @@ export async function refreshRunFromSubagentArtifacts(cwd, run) {
|
|
|
378
854
|
continue;
|
|
379
855
|
}
|
|
380
856
|
const activeAttempt = snapshot.attempts?.find((attempt) => attempt.attemptId === handle.attemptId) ?? snapshot.attempts?.at(-1);
|
|
381
|
-
|
|
857
|
+
const nextPid = activeAttempt?.workerPid ?? activeAttempt?.pid ?? task.pid;
|
|
858
|
+
if (task.pid !== nextPid) {
|
|
859
|
+
task.pid = nextPid;
|
|
860
|
+
changed = true;
|
|
861
|
+
}
|
|
382
862
|
if (snapshot.status === "running" || snapshot.status === "pending") {
|
|
383
|
-
task.
|
|
384
|
-
task.
|
|
863
|
+
await refreshRunningArtifactGraphPartialOutput(cwd, task, snapshot).catch(() => undefined);
|
|
864
|
+
if (task.statusDetail !== "running") {
|
|
865
|
+
task.statusDetail = "running";
|
|
866
|
+
changed = true;
|
|
867
|
+
}
|
|
868
|
+
const nextLastMessage = activeAttempt?.heartbeatAt
|
|
385
869
|
? `pi-subagent heartbeat ${activeAttempt.heartbeatAt}`
|
|
386
870
|
: "pi-subagent running";
|
|
871
|
+
if (task.lastMessage !== nextLastMessage) {
|
|
872
|
+
task.lastMessage = nextLastMessage;
|
|
873
|
+
changed = true;
|
|
874
|
+
}
|
|
387
875
|
if (isTaskTimedOut(task)) {
|
|
388
876
|
await interruptTimedOutSubagent(api, handle);
|
|
389
877
|
markSubagentTaskTimedOut(task);
|
|
@@ -398,6 +886,22 @@ export async function refreshRunFromSubagentArtifacts(cwd, run) {
|
|
|
398
886
|
await writeRunRecord(cwd, run);
|
|
399
887
|
return run;
|
|
400
888
|
}
|
|
889
|
+
async function refreshRunningArtifactGraphPartialOutput(cwd, task, snapshot) {
|
|
890
|
+
const partial = task.artifactGraph?.output.partial;
|
|
891
|
+
if (!partial || partial.paths.length === 0)
|
|
892
|
+
return;
|
|
893
|
+
const outputRef = findLog(snapshot, "output");
|
|
894
|
+
const outputFile = fromProjectPath(cwd, task.files.output);
|
|
895
|
+
const artifactRoot = task.backendFiles?.runsDir
|
|
896
|
+
? fromProjectPath(task.cwd, task.backendFiles.runsDir)
|
|
897
|
+
: undefined;
|
|
898
|
+
await copyLogOrEmpty(snapshot, outputRef, outputFile, artifactRoot);
|
|
899
|
+
await writeWorkflowPartialOutputLedgerFromFile({
|
|
900
|
+
taskDir: dirname(fromProjectPath(cwd, task.files.result)),
|
|
901
|
+
outputFile,
|
|
902
|
+
allowedPaths: partial.paths,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
401
905
|
async function interruptTimedOutSubagent(api, handle) {
|
|
402
906
|
await api
|
|
403
907
|
.interruptSubagent({
|
|
@@ -484,6 +988,13 @@ async function materializeTerminalSubagentResult(cwd, run, task, snapshot) {
|
|
|
484
988
|
: undefined);
|
|
485
989
|
const contextLengthExceeded = Boolean(subagentResult?.metadata?.contextLengthExceeded ??
|
|
486
990
|
snapshot.metadata?.contextLengthExceeded);
|
|
991
|
+
recordTerminalTaskObservability({
|
|
992
|
+
task,
|
|
993
|
+
snapshot,
|
|
994
|
+
subagentResult,
|
|
995
|
+
startedAt,
|
|
996
|
+
completedAt,
|
|
997
|
+
});
|
|
487
998
|
if (task.artifactGraph?.enabled && statusInfo.status === "completed") {
|
|
488
999
|
const changed = await materializeTerminalArtifactGraphResult(cwd, run, task, {
|
|
489
1000
|
outputFile,
|
|
@@ -596,6 +1107,13 @@ function artifactGraphRetrySession(run, task, subagentResult, attempt) {
|
|
|
596
1107
|
async function materializeTerminalArtifactGraphResult(cwd, run, task, options) {
|
|
597
1108
|
const rawOutput = await readFile(options.outputFile, "utf8").catch(() => "");
|
|
598
1109
|
const artifactOptions = task.artifactGraph?.output;
|
|
1110
|
+
if (artifactOptions?.partial && artifactOptions.partial.paths.length > 0) {
|
|
1111
|
+
await writeWorkflowPartialOutputLedgerFromFile({
|
|
1112
|
+
taskDir: dirname(options.resultFile),
|
|
1113
|
+
outputFile: options.outputFile,
|
|
1114
|
+
allowedPaths: artifactOptions.partial.paths,
|
|
1115
|
+
}).catch(() => undefined);
|
|
1116
|
+
}
|
|
599
1117
|
let controlJsonSchema;
|
|
600
1118
|
try {
|
|
601
1119
|
controlJsonSchema = await readTaskControlJsonSchema(task);
|
|
@@ -1097,6 +1615,7 @@ async function workflowTaskExtensions(cwd, run, task, compiledTask) {
|
|
|
1097
1615
|
runId: run.runId,
|
|
1098
1616
|
taskId: task.taskId,
|
|
1099
1617
|
cacheDir: resolve(cwd, ".pi", "workflows", run.runId, "source-cache", "fetch-content"),
|
|
1618
|
+
maxInlineChars: fetchContentInlineCharsEnvValue(),
|
|
1100
1619
|
},
|
|
1101
1620
|
});
|
|
1102
1621
|
extensions = uniqueStrings([
|
|
@@ -1162,6 +1681,17 @@ function workflowWebSourceProviderExtensions(tools, toolProviders) {
|
|
|
1162
1681
|
function fetchContentCacheEnvValue() {
|
|
1163
1682
|
return (process.env[FETCH_CONTENT_CACHE_ENV] ?? process.env[LEGACY_FETCH_CACHE_ENV]);
|
|
1164
1683
|
}
|
|
1684
|
+
function fetchContentInlineCharsEnvValue() {
|
|
1685
|
+
const raw = process.env[FETCH_CONTENT_INLINE_CHARS_ENV];
|
|
1686
|
+
if (raw === undefined || raw.trim() === "")
|
|
1687
|
+
return DEFAULT_WORKFLOW_FETCH_CONTENT_INLINE_CHARS;
|
|
1688
|
+
if (isExplicitlyDisabled(raw))
|
|
1689
|
+
return undefined;
|
|
1690
|
+
const parsed = Number(raw);
|
|
1691
|
+
if (!Number.isFinite(parsed))
|
|
1692
|
+
return DEFAULT_WORKFLOW_FETCH_CONTENT_INLINE_CHARS;
|
|
1693
|
+
return Math.max(1, Math.floor(parsed));
|
|
1694
|
+
}
|
|
1165
1695
|
function isExplicitlyDisabled(value) {
|
|
1166
1696
|
return typeof value === "string" && /^(0|false|no|off)$/i.test(value.trim());
|
|
1167
1697
|
}
|
|
@@ -1335,17 +1865,31 @@ function subagentSessionId(run, task) {
|
|
|
1335
1865
|
return task.outputRetry.sessionId;
|
|
1336
1866
|
const launchAttempt = task.launchRetry?.attempts ?? 0;
|
|
1337
1867
|
if (launchAttempt > 0)
|
|
1338
|
-
return `${baseSessionId}
|
|
1868
|
+
return boundedSubagentSessionId(`${baseSessionId}.launch-retry-${launchAttempt}`);
|
|
1339
1869
|
const resumeAttempt = task.resumeEvents?.length ?? 0;
|
|
1340
1870
|
if (resumeAttempt > 0)
|
|
1341
|
-
return `${baseSessionId}
|
|
1871
|
+
return boundedSubagentSessionId(`${baseSessionId}.resume-${resumeAttempt}`);
|
|
1342
1872
|
return baseSessionId;
|
|
1343
1873
|
}
|
|
1344
1874
|
function baseSubagentSessionId(run, task) {
|
|
1345
|
-
return `pi-workflow.${run.runId}.${task.taskId}
|
|
1875
|
+
return boundedSubagentSessionId(`pi-workflow.${run.runId}.${task.taskId}`);
|
|
1346
1876
|
}
|
|
1347
1877
|
function retrySubagentSessionId(run, task, attempt) {
|
|
1348
|
-
return `${baseSubagentSessionId(run, task)}.retry-${attempt}
|
|
1878
|
+
return boundedSubagentSessionId(`${baseSubagentSessionId(run, task)}.retry-${attempt}`);
|
|
1879
|
+
}
|
|
1880
|
+
function boundedSubagentSessionId(value) {
|
|
1881
|
+
const sanitized = value.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
1882
|
+
if (sanitized.length <= MAX_SUBAGENT_SESSION_ID_LENGTH)
|
|
1883
|
+
return sanitized;
|
|
1884
|
+
const digest = createHash("sha256")
|
|
1885
|
+
.update(sanitized)
|
|
1886
|
+
.digest("hex")
|
|
1887
|
+
.slice(0, 16);
|
|
1888
|
+
const suffix = sanitized.split(".").at(-1) || "session";
|
|
1889
|
+
const prefix = `piwf.${digest}`;
|
|
1890
|
+
const maxSuffixLength = MAX_SUBAGENT_SESSION_ID_LENGTH - prefix.length - 1;
|
|
1891
|
+
const boundedSuffix = suffix.slice(-Math.max(1, maxSuffixLength));
|
|
1892
|
+
return `${prefix}.${boundedSuffix}`;
|
|
1349
1893
|
}
|
|
1350
1894
|
function buildSystemPrompt(task) {
|
|
1351
1895
|
const workflowMaxDigestChars = task.artifactGraph?.output.maxDigestChars;
|