@agwab/pi-workflow 0.2.0 → 0.3.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 +2 -0
- package/dist/compiler.d.ts +4 -6
- package/dist/compiler.js +70 -39
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- package/dist/dynamic-generated-task-runtime.d.ts +2 -0
- package/dist/dynamic-generated-task-runtime.js +21 -8
- package/dist/dynamic-profiles.d.ts +0 -1
- package/dist/dynamic-profiles.js +0 -3
- package/dist/engine-run-graph.d.ts +1 -0
- package/dist/engine-run-graph.js +142 -2
- package/dist/engine.d.ts +10 -6
- package/dist/engine.js +146 -77
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +38 -15
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.d.ts +3 -1
- package/dist/store.js +189 -49
- package/dist/subagent-backend.d.ts +4 -0
- package/dist/subagent-backend.js +281 -31
- package/dist/types.d.ts +9 -1
- package/dist/workflow-runtime.d.ts +2 -0
- package/dist/workflow-runtime.js +40 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +167 -48
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +84 -19
- package/docs/usage.md +11 -0
- 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 +995 -573
- 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 +1352 -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/src/compiler.ts +127 -66
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-generated-task-runtime.ts +47 -12
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +192 -107
- package/src/extension.ts +50 -17
- package/src/index.ts +3 -1
- package/src/store.ts +253 -55
- package/src/subagent-backend.ts +369 -32
- package/src/types.ts +13 -1
- package/src/workflow-runtime.ts +53 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +621 -228
- package/src/workflow-web-source.ts +118 -28
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
- package/workflows/deep-research/helpers/render-executive.mjs +8 -21
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
- 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/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
|
@@ -72,14 +72,26 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
72
72
|
name: "workflow_web_fetch_source",
|
|
73
73
|
description: "Fetch one or more URLs into the workflow web-source cache and return compact source cards with sourceRefs.",
|
|
74
74
|
parameters: Type.Object({
|
|
75
|
-
url: Type.Optional(Type.String({
|
|
76
|
-
|
|
75
|
+
url: Type.Optional(Type.String({
|
|
76
|
+
description: "Single URL to fetch into the workflow web-source cache.",
|
|
77
|
+
})),
|
|
78
|
+
urls: Type.Optional(Type.Array(Type.String(), {
|
|
79
|
+
description: "Multiple URLs to fetch in one tool call. Prefer this over repeated fetch calls when caching several promising sources.",
|
|
80
|
+
})),
|
|
77
81
|
sources: Type.Optional(Type.Array(Type.Object({
|
|
78
|
-
url: Type.String({
|
|
82
|
+
url: Type.String({
|
|
83
|
+
description: "URL to fetch into the workflow web-source cache.",
|
|
84
|
+
}),
|
|
79
85
|
title: Type.Optional(Type.String({ description: "Optional source title override." })),
|
|
80
|
-
}), {
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
}), {
|
|
87
|
+
description: "Multiple URL/title objects to fetch in one tool call.",
|
|
88
|
+
})),
|
|
89
|
+
title: Type.Optional(Type.String({
|
|
90
|
+
description: "Optional source title override for single-url fetches.",
|
|
91
|
+
})),
|
|
92
|
+
titles: Type.Optional(Type.Array(Type.String(), {
|
|
93
|
+
description: "Optional title overrides paired by index with urls.",
|
|
94
|
+
})),
|
|
83
95
|
}),
|
|
84
96
|
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
|
85
97
|
const batchRequested = fetchSourceBatchRequested(params);
|
|
@@ -101,8 +113,12 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
101
113
|
url: sanitizeUrlForModel(request.url),
|
|
102
114
|
status: typeof payload.status === "string" ? payload.status : "unknown",
|
|
103
115
|
...(typeof payload.code === "string" ? { code: payload.code } : {}),
|
|
104
|
-
...(typeof payload.message === "string"
|
|
105
|
-
|
|
116
|
+
...(typeof payload.message === "string"
|
|
117
|
+
? { message: payload.message }
|
|
118
|
+
: {}),
|
|
119
|
+
...(typeof card?.sourceRef === "string"
|
|
120
|
+
? { sourceRef: card.sourceRef }
|
|
121
|
+
: {}),
|
|
106
122
|
...(card ? { cardIndex: cards.length - 1 } : {}),
|
|
107
123
|
});
|
|
108
124
|
}
|
|
@@ -159,10 +175,15 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
159
175
|
url: existing.redactedUrl,
|
|
160
176
|
visibleChars: budget.used,
|
|
161
177
|
});
|
|
162
|
-
return toolResultFromJson({
|
|
178
|
+
return toolResultFromJson({
|
|
179
|
+
status: "ok",
|
|
180
|
+
tool: "workflow_web_fetch_source",
|
|
181
|
+
card,
|
|
182
|
+
});
|
|
163
183
|
}
|
|
164
184
|
const fetchKey = sourceUrlCacheKey(fetchUrl);
|
|
165
|
-
const cachedFailure = fetchFailures.get(fetchKey) ??
|
|
185
|
+
const cachedFailure = fetchFailures.get(fetchKey) ??
|
|
186
|
+
(await readDurableFetchFailure(config, fetchKey));
|
|
166
187
|
if (cachedFailure) {
|
|
167
188
|
fetchFailures.set(fetchKey, cachedFailure);
|
|
168
189
|
await recordWorkflowWebSourceEvent(config, "fetch_negative_cache_hit", {
|
|
@@ -178,25 +199,43 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
178
199
|
if (!source)
|
|
179
200
|
return result;
|
|
180
201
|
sourceCache.set(source.sourceRef, source);
|
|
181
|
-
const card = buildWorkflowWebSourceCard({
|
|
202
|
+
const card = buildWorkflowWebSourceCard({
|
|
203
|
+
source,
|
|
204
|
+
policy,
|
|
205
|
+
budget,
|
|
206
|
+
duplicate: true,
|
|
207
|
+
});
|
|
182
208
|
await recordWorkflowWebSourceEvent(config, "fetch_duplicate", {
|
|
183
209
|
sourceRef: source.sourceRef,
|
|
184
210
|
url: source.redactedUrl,
|
|
185
211
|
visibleChars: budget.used,
|
|
186
212
|
});
|
|
187
|
-
return toolResultFromJson({
|
|
213
|
+
return toolResultFromJson({
|
|
214
|
+
status: "ok",
|
|
215
|
+
tool: "workflow_web_fetch_source",
|
|
216
|
+
card,
|
|
217
|
+
});
|
|
188
218
|
}
|
|
189
219
|
const fetchPromise = withWorkflowWebFetchLock(config, fetchKey, signal, async () => {
|
|
190
220
|
const lockedExisting = await findWorkflowWebSourceByUrl(config, fetchUrl);
|
|
191
221
|
if (lockedExisting) {
|
|
192
222
|
sourceCache.set(lockedExisting.sourceRef, lockedExisting);
|
|
193
|
-
const card = buildWorkflowWebSourceCard({
|
|
223
|
+
const card = buildWorkflowWebSourceCard({
|
|
224
|
+
source: lockedExisting,
|
|
225
|
+
policy,
|
|
226
|
+
budget,
|
|
227
|
+
duplicate: true,
|
|
228
|
+
});
|
|
194
229
|
await recordWorkflowWebSourceEvent(config, "fetch_duplicate", {
|
|
195
230
|
sourceRef: lockedExisting.sourceRef,
|
|
196
231
|
url: lockedExisting.redactedUrl,
|
|
197
232
|
visibleChars: budget.used,
|
|
198
233
|
});
|
|
199
|
-
return toolResultFromJson({
|
|
234
|
+
return toolResultFromJson({
|
|
235
|
+
status: "ok",
|
|
236
|
+
tool: "workflow_web_fetch_source",
|
|
237
|
+
card,
|
|
238
|
+
});
|
|
200
239
|
}
|
|
201
240
|
const lockedFailure = await readDurableFetchFailure(config, fetchKey);
|
|
202
241
|
if (lockedFailure) {
|
|
@@ -221,7 +260,10 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
221
260
|
return await cachedFetchFailureResult(config, fetchFailures, fetchKey, {
|
|
222
261
|
code: "blocked_url",
|
|
223
262
|
message: "URL was blocked by workflow web-source security policy before content fetch.",
|
|
224
|
-
extra: {
|
|
263
|
+
extra: {
|
|
264
|
+
reason: safeFetch.reason,
|
|
265
|
+
url: sanitizeUrlForModel(safeFetch.url),
|
|
266
|
+
},
|
|
225
267
|
reason: safeFetch.reason,
|
|
226
268
|
});
|
|
227
269
|
}
|
|
@@ -311,10 +353,16 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
311
353
|
textChars: source.textChars,
|
|
312
354
|
visibleChars: budget.used,
|
|
313
355
|
});
|
|
314
|
-
return toolResultFromJson({
|
|
356
|
+
return toolResultFromJson({
|
|
357
|
+
status: "ok",
|
|
358
|
+
tool: "workflow_web_fetch_source",
|
|
359
|
+
card,
|
|
360
|
+
});
|
|
315
361
|
}).catch(async (error) => {
|
|
316
362
|
const message = error instanceof Error ? error.message : "workflow_web_fetch_failed";
|
|
317
|
-
const code = message === "fetch_lock_timeout"
|
|
363
|
+
const code = message === "fetch_lock_timeout"
|
|
364
|
+
? "fetch_lock_timeout"
|
|
365
|
+
: "workflow_web_fetch_failed";
|
|
318
366
|
await recordWorkflowWebSourceEvent(config, "fetch_failed", {
|
|
319
367
|
url: sanitizeUrlForModel(fetchUrl),
|
|
320
368
|
code,
|
|
@@ -335,23 +383,47 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
335
383
|
name: "workflow_web_source_read",
|
|
336
384
|
description: "Read one or more narrow exact/fuzzy/term-matched snippets from a cached workflow web source by sourceRef.",
|
|
337
385
|
parameters: Type.Object({
|
|
338
|
-
sourceRef: Type.String({
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
386
|
+
sourceRef: Type.String({
|
|
387
|
+
description: "Opaque sourceRef returned by workflow_web_fetch_source.",
|
|
388
|
+
}),
|
|
389
|
+
query: Type.Optional(Type.String({
|
|
390
|
+
description: "Exact or fuzzy text to locate in the cached source.",
|
|
391
|
+
})),
|
|
392
|
+
queries: Type.Optional(Type.Array(Type.String(), {
|
|
393
|
+
description: "Multiple exact/fuzzy texts to locate in one cached source. Prefer this over repeated calls when reading several snippets from the same sourceRef.",
|
|
394
|
+
})),
|
|
395
|
+
exact: Type.Optional(Type.String({
|
|
396
|
+
description: "Exact text to locate in the cached source.",
|
|
397
|
+
})),
|
|
398
|
+
exactTexts: Type.Optional(Type.Array(Type.String(), {
|
|
399
|
+
description: "Multiple exact texts to locate in one cached source.",
|
|
400
|
+
})),
|
|
401
|
+
claim: Type.Optional(Type.String({
|
|
402
|
+
description: "Claim to locate when the exact quote is not known. Use with terms for deterministic quote harvesting.",
|
|
403
|
+
})),
|
|
404
|
+
terms: Type.Optional(Type.Array(Type.String(), {
|
|
405
|
+
description: "Important terms that should co-occur in the returned source window.",
|
|
406
|
+
})),
|
|
345
407
|
reads: Type.Optional(Type.Array(Type.Object({
|
|
346
408
|
query: Type.Optional(Type.String({ description: "Exact or fuzzy text to locate." })),
|
|
347
409
|
exact: Type.Optional(Type.String({ description: "Exact text to locate." })),
|
|
348
410
|
exactText: Type.Optional(Type.String({ description: "Exact text to locate." })),
|
|
349
411
|
text: Type.Optional(Type.String({ description: "Text to locate." })),
|
|
350
|
-
claim: Type.Optional(Type.String({
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
412
|
+
claim: Type.Optional(Type.String({
|
|
413
|
+
description: "Claim to locate when exact quote is unknown.",
|
|
414
|
+
})),
|
|
415
|
+
terms: Type.Optional(Type.Array(Type.String(), {
|
|
416
|
+
description: "Important terms for deterministic quote harvesting.",
|
|
417
|
+
})),
|
|
418
|
+
maxChars: Type.Optional(Type.Number({
|
|
419
|
+
description: "Maximum visible snippet characters for this read.",
|
|
420
|
+
})),
|
|
421
|
+
}), {
|
|
422
|
+
description: "Mixed batch reads for one sourceRef; each item can use query or claim+terms.",
|
|
423
|
+
})),
|
|
424
|
+
maxChars: Type.Optional(Type.Number({
|
|
425
|
+
description: "Maximum visible snippet characters per query.",
|
|
426
|
+
})),
|
|
355
427
|
}),
|
|
356
428
|
execute: async (_toolCallId, params) => {
|
|
357
429
|
const sourceRef = stringParam(params, "sourceRef") ?? stringParam(params, "source_ref");
|
|
@@ -361,7 +433,9 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
361
433
|
}
|
|
362
434
|
const source = await readCachedWorkflowWebSource(sourceRef);
|
|
363
435
|
if (!source) {
|
|
364
|
-
await recordWorkflowWebSourceEvent(config, "source_read_missing", {
|
|
436
|
+
await recordWorkflowWebSourceEvent(config, "source_read_missing", {
|
|
437
|
+
sourceRef,
|
|
438
|
+
});
|
|
365
439
|
return errorToolResult("source_not_found", "No cached workflow web source exists for sourceRef.", {
|
|
366
440
|
sourceRef,
|
|
367
441
|
});
|
|
@@ -391,6 +465,7 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
391
465
|
missingTerms: read.missingTerms,
|
|
392
466
|
coverageRatio: read.coverageRatio,
|
|
393
467
|
candidateOnly: read.candidateOnly,
|
|
468
|
+
truncated: read.truncated,
|
|
394
469
|
quote: status === "budget_exhausted" ? undefined : read.quote,
|
|
395
470
|
startOffset: read.startOffset,
|
|
396
471
|
endOffset: read.endOffset,
|
|
@@ -420,25 +495,33 @@ export function registerWorkflowWebSourceExtension(pi, config, providerExtension
|
|
|
420
495
|
missingTerms: result.missingTerms,
|
|
421
496
|
coverageRatio: result.coverageRatio,
|
|
422
497
|
candidateOnly: result.candidateOnly,
|
|
498
|
+
truncated: result.truncated,
|
|
423
499
|
quote: result.status === "budget_exhausted" ? undefined : result.quote,
|
|
424
500
|
startOffset: result.startOffset,
|
|
425
501
|
endOffset: result.endOffset,
|
|
426
|
-
budget: budgetSnapshot(result.status === "budget_exhausted"
|
|
502
|
+
budget: budgetSnapshot(result.status === "budget_exhausted" ||
|
|
503
|
+
result.status === "truncated"),
|
|
427
504
|
next: result.status === "budget_exhausted"
|
|
428
505
|
? "Visible web-source budget is exhausted for this task; cite the sourceRef as an evidence gap or use a smaller query in a fresh task."
|
|
429
|
-
:
|
|
506
|
+
: result.status === "truncated"
|
|
507
|
+
? "The matched web-source snippet was truncated by the visible budget or maxChars; use a smaller exact query or a fresh task if the full quote is required."
|
|
508
|
+
: undefined,
|
|
430
509
|
});
|
|
431
510
|
}
|
|
511
|
+
const hasBudgetExhaustedRead = results.some((result) => result.status === "budget_exhausted");
|
|
512
|
+
const hasTruncatedRead = results.some((result) => result.status === "truncated");
|
|
432
513
|
return toolResultFromJson({
|
|
433
514
|
status: responseStatus,
|
|
434
515
|
tool: "workflow_web_source_read",
|
|
435
516
|
sourceRef,
|
|
436
517
|
url: source.redactedUrl,
|
|
437
518
|
results,
|
|
438
|
-
budget: budgetSnapshot(
|
|
439
|
-
next:
|
|
519
|
+
budget: budgetSnapshot(hasBudgetExhaustedRead || hasTruncatedRead),
|
|
520
|
+
next: hasBudgetExhaustedRead
|
|
440
521
|
? "Visible web-source budget is exhausted for this task; cite missing quotes as evidence gaps or use smaller query batches in a fresh task."
|
|
441
|
-
:
|
|
522
|
+
: hasTruncatedRead
|
|
523
|
+
? "One or more matched web-source snippets were truncated by the visible budget or maxChars; use smaller exact queries or a fresh task if full quotes are required."
|
|
524
|
+
: undefined,
|
|
442
525
|
});
|
|
443
526
|
},
|
|
444
527
|
});
|
|
@@ -596,7 +679,9 @@ function normalizeFetchFailure(value) {
|
|
|
596
679
|
message: value.message,
|
|
597
680
|
extra,
|
|
598
681
|
...(typeof value.reason === "string" ? { reason: value.reason } : {}),
|
|
599
|
-
...(typeof value.createdAt === "string"
|
|
682
|
+
...(typeof value.createdAt === "string"
|
|
683
|
+
? { createdAt: value.createdAt }
|
|
684
|
+
: {}),
|
|
600
685
|
};
|
|
601
686
|
}
|
|
602
687
|
function fetchLockPath(config, key) {
|
|
@@ -621,7 +706,9 @@ function shouldCacheFetchFailure(reason) {
|
|
|
621
706
|
reason === "unsupported_content_type");
|
|
622
707
|
}
|
|
623
708
|
function shouldCacheFetchFailureInMemory(reason) {
|
|
624
|
-
return reason === "empty_source" ||
|
|
709
|
+
return (reason === "empty_source" ||
|
|
710
|
+
reason === "dns_resolution_failed" ||
|
|
711
|
+
reason.includes("ENOTFOUND"));
|
|
625
712
|
}
|
|
626
713
|
const WORKFLOW_WEB_FETCH_TIMEOUT_MS = 30_000;
|
|
627
714
|
const WORKFLOW_WEB_FETCH_MAX_CHARS = 1_000_000;
|
|
@@ -636,12 +723,20 @@ async function safeFetchWorkflowWebText(url, security, signal) {
|
|
|
636
723
|
return response;
|
|
637
724
|
if (response.status >= 300 && response.status < 400) {
|
|
638
725
|
if (!response.location)
|
|
639
|
-
return {
|
|
726
|
+
return {
|
|
727
|
+
ok: false,
|
|
728
|
+
reason: "redirect_without_location",
|
|
729
|
+
url: checked.normalizedUrl,
|
|
730
|
+
};
|
|
640
731
|
current = new URL(response.location, checked.normalizedUrl).href;
|
|
641
732
|
continue;
|
|
642
733
|
}
|
|
643
734
|
if (response.status < 200 || response.status >= 300) {
|
|
644
|
-
return {
|
|
735
|
+
return {
|
|
736
|
+
ok: false,
|
|
737
|
+
reason: `http_${response.status}`,
|
|
738
|
+
url: checked.normalizedUrl,
|
|
739
|
+
};
|
|
645
740
|
}
|
|
646
741
|
const extracted = extractWorkflowWebResponseText(response.text, response.contentType);
|
|
647
742
|
return {
|
|
@@ -675,13 +770,17 @@ function safeFetchOnce(url, security, signal) {
|
|
|
675
770
|
lookupPublicAddress(hostname, security)
|
|
676
771
|
.then((address) => {
|
|
677
772
|
if (isLookupAllOptions(options)) {
|
|
678
|
-
callback(null, [
|
|
773
|
+
callback(null, [
|
|
774
|
+
{ address: address.address, family: address.family },
|
|
775
|
+
]);
|
|
679
776
|
return;
|
|
680
777
|
}
|
|
681
778
|
callback(null, address.address, address.family);
|
|
682
779
|
})
|
|
683
780
|
.catch((error) => {
|
|
684
|
-
const reason = error instanceof Error
|
|
781
|
+
const reason = error instanceof Error
|
|
782
|
+
? error.message
|
|
783
|
+
: "dns_resolution_failed";
|
|
685
784
|
callback(new Error(reason), "", 4);
|
|
686
785
|
});
|
|
687
786
|
},
|
|
@@ -693,7 +792,10 @@ function safeFetchOnce(url, security, signal) {
|
|
|
693
792
|
? res.headers["content-type"][0]
|
|
694
793
|
: res.headers["content-type"];
|
|
695
794
|
const status = res.statusCode ?? 0;
|
|
696
|
-
if (status >= 200 &&
|
|
795
|
+
if (status >= 200 &&
|
|
796
|
+
status < 300 &&
|
|
797
|
+
contentType &&
|
|
798
|
+
!isWorkflowWebTextContentType(contentType)) {
|
|
697
799
|
res.resume();
|
|
698
800
|
settle({ ok: false, reason: "unsupported_content_type", url });
|
|
699
801
|
return;
|
|
@@ -772,7 +874,10 @@ async function validateResolvedHost(url, security) {
|
|
|
772
874
|
return { ok: false, reason: "invalid_url", url };
|
|
773
875
|
}
|
|
774
876
|
try {
|
|
775
|
-
const addresses = await lookup(parsed.hostname, {
|
|
877
|
+
const addresses = await lookup(parsed.hostname, {
|
|
878
|
+
all: true,
|
|
879
|
+
verbatim: true,
|
|
880
|
+
});
|
|
776
881
|
for (const address of addresses) {
|
|
777
882
|
const reason = privateIpReason(address.address);
|
|
778
883
|
if (reason)
|
|
@@ -800,7 +905,8 @@ function privateIpReason(address) {
|
|
|
800
905
|
}
|
|
801
906
|
if (isIP(lower) === 4) {
|
|
802
907
|
const parts = lower.split(".").map((part) => Number(part));
|
|
803
|
-
if (parts.length !== 4 ||
|
|
908
|
+
if (parts.length !== 4 ||
|
|
909
|
+
parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255))
|
|
804
910
|
return "private_host_blocked";
|
|
805
911
|
const [a, b, c, d] = parts;
|
|
806
912
|
if (a === 0 || a === 10 || a === 127 || a >= 224)
|
|
@@ -961,7 +1067,9 @@ function fetchSourceRequestsFromParams(params) {
|
|
|
961
1067
|
const titles = Array.isArray(params.titles) ? params.titles : [];
|
|
962
1068
|
if (Array.isArray(params.sources)) {
|
|
963
1069
|
for (const source of params.sources) {
|
|
964
|
-
if (!isRecord(source) ||
|
|
1070
|
+
if (!isRecord(source) ||
|
|
1071
|
+
typeof source.url !== "string" ||
|
|
1072
|
+
!source.url.trim())
|
|
965
1073
|
continue;
|
|
966
1074
|
requests.push({
|
|
967
1075
|
url: source.url.trim(),
|
|
@@ -978,7 +1086,9 @@ function fetchSourceRequestsFromParams(params) {
|
|
|
978
1086
|
const title = titles[index];
|
|
979
1087
|
requests.push({
|
|
980
1088
|
url: url.trim(),
|
|
981
|
-
...(typeof title === "string" && title.trim()
|
|
1089
|
+
...(typeof title === "string" && title.trim()
|
|
1090
|
+
? { title: title.trim() }
|
|
1091
|
+
: {}),
|
|
982
1092
|
});
|
|
983
1093
|
}
|
|
984
1094
|
}
|
|
@@ -1086,12 +1196,18 @@ function dedupeSourceReadRequests(requests) {
|
|
|
1086
1196
|
return deduped;
|
|
1087
1197
|
}
|
|
1088
1198
|
function sourceReadBatchRequested(params) {
|
|
1089
|
-
return ((isRecord(params) &&
|
|
1199
|
+
return ((isRecord(params) &&
|
|
1200
|
+
Array.isArray(params.reads) &&
|
|
1201
|
+
params.reads.length > 0) ||
|
|
1090
1202
|
stringArrayParam(params, "queries").length > 0 ||
|
|
1091
1203
|
stringArrayParam(params, "exactTexts").length > 0 ||
|
|
1092
1204
|
stringArrayParam(params, "texts").length > 0);
|
|
1093
1205
|
}
|
|
1094
1206
|
function sourceReadResponseStatus(read) {
|
|
1207
|
+
if (read.status === "truncated" && !read.quote)
|
|
1208
|
+
return "budget_exhausted";
|
|
1209
|
+
if (read.status === "truncated")
|
|
1210
|
+
return "truncated";
|
|
1095
1211
|
if (read.status === "matched" && !read.quote)
|
|
1096
1212
|
return "budget_exhausted";
|
|
1097
1213
|
if (read.status === "matched" && read.candidateOnly)
|
|
@@ -1105,6 +1221,8 @@ function aggregateSourceReadStatus(statuses) {
|
|
|
1105
1221
|
return "ok";
|
|
1106
1222
|
if (statuses.every((status) => status === "candidate"))
|
|
1107
1223
|
return "candidate";
|
|
1224
|
+
if (statuses.every((status) => status === "truncated"))
|
|
1225
|
+
return "truncated";
|
|
1108
1226
|
if (statuses.every((status) => status === "not_found"))
|
|
1109
1227
|
return "not_found";
|
|
1110
1228
|
if (statuses.every((status) => status === "budget_exhausted"))
|
|
@@ -1140,7 +1258,8 @@ function isWorkflowWebTextContentType(contentType) {
|
|
|
1140
1258
|
return /^(text\/|application\/(json|xml|xhtml\+xml|ld\+json)|[^;]+\+json\b|[^;]+\+xml\b)/i.test(contentType.trim());
|
|
1141
1259
|
}
|
|
1142
1260
|
function extractWorkflowWebResponseText(text, contentType) {
|
|
1143
|
-
const looksHtml = /html/i.test(contentType ?? "") ||
|
|
1261
|
+
const looksHtml = /html/i.test(contentType ?? "") ||
|
|
1262
|
+
/<html[\s>]|<body[\s>]|<title[\s>]/i.test(text);
|
|
1144
1263
|
if (!looksHtml) {
|
|
1145
1264
|
return { text, title: titleFromPlainText(text) };
|
|
1146
1265
|
}
|
|
@@ -67,7 +67,7 @@ export interface WorkflowWebSourceReadRequest {
|
|
|
67
67
|
maxChars?: number;
|
|
68
68
|
}
|
|
69
69
|
export interface WorkflowWebSourceReadResult {
|
|
70
|
-
status: "matched" | "not_found";
|
|
70
|
+
status: "matched" | "truncated" | "not_found";
|
|
71
71
|
matchType?: "exact" | "normalized" | "terms";
|
|
72
72
|
quote?: string;
|
|
73
73
|
startOffset?: number;
|
|
@@ -77,6 +77,7 @@ export interface WorkflowWebSourceReadResult {
|
|
|
77
77
|
missingTerms?: string[];
|
|
78
78
|
coverageRatio?: number;
|
|
79
79
|
candidateOnly?: boolean;
|
|
80
|
+
truncated?: boolean;
|
|
80
81
|
}
|
|
81
82
|
export interface WorkflowWebSourceCard {
|
|
82
83
|
sourceRef: string;
|
|
@@ -526,19 +526,32 @@ function snippetForTerms(options) {
|
|
|
526
526
|
return right.score - left.score;
|
|
527
527
|
return right.matchedTerms.length - left.matchedTerms.length;
|
|
528
528
|
})[0];
|
|
529
|
-
const
|
|
530
|
-
|
|
529
|
+
const consumed = consumeAnchoredSnippet({
|
|
530
|
+
text: options.text,
|
|
531
|
+
anchorStart: best.anchorStart,
|
|
532
|
+
anchorEnd: best.anchorEnd,
|
|
533
|
+
maxChars: options.maxChars,
|
|
534
|
+
budget: options.budget,
|
|
535
|
+
});
|
|
536
|
+
const returnedWindowNorm = normalizeForSearch(options.text.slice(consumed.sourceStart, consumed.sourceEnd)).normalized;
|
|
537
|
+
const matchedTerms = needles
|
|
538
|
+
.filter((term) => returnedWindowNorm.includes(term.normalized))
|
|
539
|
+
.map((term) => term.raw);
|
|
540
|
+
const missingTerms = needles
|
|
541
|
+
.filter((term) => !returnedWindowNorm.includes(term.normalized))
|
|
542
|
+
.map((term) => term.raw);
|
|
531
543
|
return {
|
|
532
|
-
status:
|
|
544
|
+
status: consumed.status,
|
|
533
545
|
matchType: "terms",
|
|
534
|
-
quote: consumed.
|
|
535
|
-
startOffset:
|
|
536
|
-
endOffset:
|
|
537
|
-
visibleChars: consumed.
|
|
538
|
-
matchedTerms
|
|
539
|
-
missingTerms
|
|
540
|
-
coverageRatio:
|
|
546
|
+
quote: consumed.quote || undefined,
|
|
547
|
+
startOffset: consumed.sourceStart,
|
|
548
|
+
endOffset: consumed.sourceEnd,
|
|
549
|
+
visibleChars: consumed.visibleChars,
|
|
550
|
+
matchedTerms,
|
|
551
|
+
missingTerms,
|
|
552
|
+
coverageRatio: matchedTerms.length / Math.max(1, needles.length),
|
|
541
553
|
candidateOnly: true,
|
|
554
|
+
truncated: consumed.truncated || undefined,
|
|
542
555
|
};
|
|
543
556
|
}
|
|
544
557
|
function scoreTermWindow(text, matchStart, matchEnd, maxChars, terms) {
|
|
@@ -559,6 +572,8 @@ function scoreTermWindow(text, matchStart, matchEnd, maxChars, terms) {
|
|
|
559
572
|
return {
|
|
560
573
|
start,
|
|
561
574
|
end,
|
|
575
|
+
anchorStart: matchStart,
|
|
576
|
+
anchorEnd: matchEnd,
|
|
562
577
|
matchedTerms,
|
|
563
578
|
missingTerms,
|
|
564
579
|
score: matchedTerms.length * 1_000 + occurrenceScore,
|
|
@@ -631,20 +646,70 @@ const SOURCE_READ_STOPWORDS = new Set([
|
|
|
631
646
|
"without",
|
|
632
647
|
]);
|
|
633
648
|
function snippetForMatch(options) {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
649
|
+
const consumed = consumeAnchoredSnippet({
|
|
650
|
+
text: options.text,
|
|
651
|
+
anchorStart: options.start,
|
|
652
|
+
anchorEnd: options.end,
|
|
653
|
+
maxChars: options.maxChars,
|
|
654
|
+
budget: options.budget,
|
|
655
|
+
});
|
|
641
656
|
return {
|
|
642
|
-
status:
|
|
657
|
+
status: consumed.status,
|
|
643
658
|
matchType: options.matchType,
|
|
644
|
-
quote: consumed.
|
|
659
|
+
quote: consumed.quote || undefined,
|
|
645
660
|
startOffset: options.start,
|
|
646
661
|
endOffset: options.end,
|
|
662
|
+
visibleChars: consumed.visibleChars,
|
|
663
|
+
truncated: consumed.truncated || undefined,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function consumeAnchoredSnippet(options) {
|
|
667
|
+
const maxChars = Math.max(0, Math.floor(options.maxChars));
|
|
668
|
+
const remainingBefore = Math.max(0, options.budget.limit - options.budget.used);
|
|
669
|
+
const visibleLimit = Math.max(0, Math.min(maxChars, remainingBefore));
|
|
670
|
+
const anchorStart = Math.max(0, Math.min(options.text.length, Math.floor(options.anchorStart)));
|
|
671
|
+
const anchorEnd = Math.max(anchorStart, Math.min(options.text.length, Math.floor(options.anchorEnd)));
|
|
672
|
+
const anchorLength = Math.max(0, anchorEnd - anchorStart);
|
|
673
|
+
if (visibleLimit <= 0) {
|
|
674
|
+
return {
|
|
675
|
+
status: "truncated",
|
|
676
|
+
quote: "",
|
|
677
|
+
visibleChars: 0,
|
|
678
|
+
sourceStart: anchorStart,
|
|
679
|
+
sourceEnd: anchorStart,
|
|
680
|
+
truncated: true,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
let sourceStart;
|
|
684
|
+
let sourceEnd;
|
|
685
|
+
let status = "matched";
|
|
686
|
+
if (anchorLength > visibleLimit) {
|
|
687
|
+
sourceStart = anchorStart;
|
|
688
|
+
sourceEnd = Math.min(options.text.length, sourceStart + visibleLimit);
|
|
689
|
+
status = "truncated";
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
const slack = Math.max(0, visibleLimit - anchorLength);
|
|
693
|
+
sourceStart = Math.max(0, anchorStart - Math.floor(slack / 2));
|
|
694
|
+
sourceEnd = Math.min(options.text.length, sourceStart + visibleLimit);
|
|
695
|
+
if (sourceEnd < anchorEnd) {
|
|
696
|
+
sourceEnd = anchorEnd;
|
|
697
|
+
sourceStart = Math.max(0, sourceEnd - visibleLimit);
|
|
698
|
+
}
|
|
699
|
+
else if (sourceEnd === options.text.length) {
|
|
700
|
+
sourceStart = Math.max(0, sourceEnd - visibleLimit);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const raw = redactInlineSecrets(options.text.slice(sourceStart, sourceEnd));
|
|
704
|
+
const consumed = consumeWorkflowWebVisibleBudget(options.budget, raw, visibleLimit);
|
|
705
|
+
const truncated = status === "truncated" || consumed.truncated;
|
|
706
|
+
return {
|
|
707
|
+
status,
|
|
708
|
+
quote: consumed.text,
|
|
647
709
|
visibleChars: consumed.text.length,
|
|
710
|
+
sourceStart,
|
|
711
|
+
sourceEnd,
|
|
712
|
+
truncated,
|
|
648
713
|
};
|
|
649
714
|
}
|
|
650
715
|
function normalizeForSearch(text) {
|
package/docs/usage.md
CHANGED
|
@@ -187,6 +187,17 @@ A run prints a `workflow_*` id. Use that id for follow-up commands:
|
|
|
187
187
|
|
|
188
188
|
The runtime task is not optional. `/workflow run <workflow>` and `/workflow dynamic` without task text fail before launch.
|
|
189
189
|
|
|
190
|
+
### Opt-in fast mode
|
|
191
|
+
|
|
192
|
+
For lower-latency runs, pass `--thinking low` explicitly:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
/workflow run --thinking low deep-research "Research this repository and summarize the architecture tradeoffs."
|
|
196
|
+
/workflow dynamic --thinking low "Research this repository and summarize the architecture tradeoffs."
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This is an opt-in fast mode. Package defaults remain conservative until a separate holdout evaluation provides enough evidence to change them. Current evidence is limited but encouraging for explicit fast runs: the 2026-07-02 `deep-research` combined gate on P1/P2/P3-style prompts resolved non-support tasks to `low`, completed selected valid runs in about 15-17 minutes, passed the strict gate 9/9, and had zero source-ref join failures across those 9 runs. Treat this as a speed option, not proof that every workflow should default to `low`.
|
|
200
|
+
|
|
190
201
|
### Run-scoped web-source cache
|
|
191
202
|
|
|
192
203
|
Prefer normalized workflow web tools in new workflows:
|
|
@@ -38,7 +38,6 @@ Run this check in a sandboxed worker and report the artifact paths.
|
|
|
38
38
|
Start a background audit and let me inspect it in /subagent panel.
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
|
|
42
41
|
## What it does
|
|
43
42
|
|
|
44
43
|
Tool: `subagent`
|
|
@@ -121,9 +120,11 @@ Existing run:
|
|
|
121
120
|
{ "action": "status", "runId": "run_..." }
|
|
122
121
|
```
|
|
123
122
|
|
|
123
|
+
Recent runs can be addressed by `runId` even when they were launched from another cwd; legacy records still resolve from the explicit or current cwd.
|
|
124
|
+
|
|
124
125
|
### Panel
|
|
125
126
|
|
|
126
|
-
Inspect runs, attempts, artifacts, and log tails in a live TUI.
|
|
127
|
+
Inspect runs, attempts, artifacts, and log tails in a live TUI. The panel defaults to the current Pi session, can switch to current cwd or all indexed runs, and includes status filters plus a scrollable detail pane. It shows active and recent terminal runs by default, with in-panel `m` to show more, and counts stale/malformed run pointers without exposing raw session ids.
|
|
127
128
|
|
|
128
129
|
Open the run monitor:
|
|
129
130
|
|
|
@@ -147,4 +148,3 @@ const status = await getSubagentStatus({ runId: run.runId });
|
|
|
147
148
|
## Detailed docs
|
|
148
149
|
|
|
149
150
|
- [`docs/usage.md`](./docs/usage.md) — full argument reference, code API, `action` behavior, backend selection, sandbox/worktree behavior, artifacts, and validation notes.
|
|
150
|
-
|
|
@@ -9,4 +9,5 @@ export const getSubagentLogs = api.getSubagentLogs;
|
|
|
9
9
|
export const waitForSubagent = api.waitForSubagent;
|
|
10
10
|
export const interruptSubagent = api.interruptSubagent;
|
|
11
11
|
export const reconcileSubagentRun = api.reconcileSubagentRun;
|
|
12
|
+
export const recordSubagentChildEvent = api.recordSubagentChildEvent;
|
|
12
13
|
export const SubagentValidationError = api.SubagentValidationError;
|