@agwab/pi-workflow 0.1.1 → 0.1.2
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 +14 -3
- package/agents/researcher.md +17 -7
- package/dist/artifact-graph-runtime.js +1 -0
- package/dist/compiler.js +2 -2
- package/dist/dynamic-generated-task-runtime.js +4 -3
- package/dist/dynamic-runtime-bundle.js +3 -2
- package/dist/extension.js +40 -1
- package/dist/subagent-backend.js +82 -27
- package/dist/tool-metadata.d.ts +1 -0
- package/dist/tool-metadata.js +13 -1
- package/dist/workflow-artifact-extension.js +3 -2
- package/dist/workflow-artifact-tool.js +84 -4
- package/dist/workflow-web-source-extension.d.ts +43 -0
- package/dist/workflow-web-source-extension.js +1194 -0
- package/dist/workflow-web-source.d.ts +171 -0
- package/dist/workflow-web-source.js +897 -0
- package/docs/usage.md +32 -18
- package/node_modules/@agwab/pi-subagent/package.json +1 -1
- package/node_modules/@agwab/pi-subagent/src/api.ts +245 -132
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +243 -163
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +117 -90
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +728 -475
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +305 -209
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +750 -439
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +422 -268
- package/package.json +2 -2
- package/skills/workflow-guide/scaffolds/object-tool-fallback/schemas/fetch-control.schema.json +1 -1
- package/skills/workflow-guide/scaffolds/object-tool-fallback/spec.json +4 -3
- package/src/artifact-graph-runtime.ts +1 -0
- package/src/compiler.ts +2 -1
- package/src/dynamic-generated-task-runtime.ts +4 -2
- package/src/dynamic-runtime-bundle.ts +3 -2
- package/src/extension.ts +46 -1
- package/src/subagent-backend.ts +121 -37
- package/src/tool-metadata.ts +22 -1
- package/src/workflow-artifact-extension.ts +3 -2
- package/src/workflow-artifact-tool.ts +96 -4
- package/src/workflow-web-source-extension.ts +1411 -0
- package/src/workflow-web-source.ts +1171 -0
- package/workflows/README.md +1 -1
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +474 -40
- package/workflows/deep-research/helpers/final-audit-packet.mjs +219 -0
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +436 -0
- package/workflows/deep-research/helpers/render-executive.mjs +571 -198
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +35 -8
- package/workflows/deep-research/schemas/deep-research-normalize-claims-control.schema.json +45 -4
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +0 -2
- package/workflows/deep-research/spec.json +36 -21
- package/workflows/deep-review/helpers/render-review-report.mjs +502 -0
- package/workflows/deep-review/schemas/deep-review-render-control.schema.json +50 -0
- package/workflows/deep-review/spec.json +22 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Deterministic
|
|
1
|
+
// Deterministic evidence-backed renderer for deep-research.
|
|
2
2
|
//
|
|
3
3
|
// Input: final-audit.control.json from the full deep-research final stage.
|
|
4
|
-
// Output:
|
|
4
|
+
// Output: a parent-facing research report in executiveMarkdown plus sidecars.
|
|
5
5
|
//
|
|
6
6
|
// This intentionally treats final-audit.control.json as the source of truth and
|
|
7
7
|
// renders a bounded view. It does not re-verify or invent evidence.
|
|
@@ -20,6 +20,35 @@ function asArray(value) {
|
|
|
20
20
|
return Array.isArray(value) ? value : [];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function flattenItems(value) {
|
|
28
|
+
if (Array.isArray(value)) return value.flatMap((item) => flattenItems(item));
|
|
29
|
+
if (typeof value === "string") return value.trim() ? [value] : [];
|
|
30
|
+
if (!isRecord(value)) return [];
|
|
31
|
+
const renderFields = [
|
|
32
|
+
"gap",
|
|
33
|
+
"finding",
|
|
34
|
+
"claim",
|
|
35
|
+
"note",
|
|
36
|
+
"whyItMatters",
|
|
37
|
+
"parentImpact",
|
|
38
|
+
"recommendation",
|
|
39
|
+
"action",
|
|
40
|
+
"step",
|
|
41
|
+
];
|
|
42
|
+
if (
|
|
43
|
+
renderFields.some(
|
|
44
|
+
(field) => typeof value[field] === "string" && value[field].trim(),
|
|
45
|
+
)
|
|
46
|
+
) {
|
|
47
|
+
return [value];
|
|
48
|
+
}
|
|
49
|
+
return Object.values(value).flatMap((item) => flattenItems(item));
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
function words(text) {
|
|
24
53
|
return (
|
|
25
54
|
String(text ?? "")
|
|
@@ -39,6 +68,21 @@ function cleanText(value) {
|
|
|
39
68
|
.trim();
|
|
40
69
|
}
|
|
41
70
|
|
|
71
|
+
function escapeTableCell(value) {
|
|
72
|
+
return cleanText(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stringifyItem(item) {
|
|
76
|
+
if (typeof item === "string") return cleanText(item) || "(empty string)";
|
|
77
|
+
try {
|
|
78
|
+
const json = JSON.stringify(item);
|
|
79
|
+
if (json) return cleanText(json);
|
|
80
|
+
} catch {
|
|
81
|
+
// Fall through to String below.
|
|
82
|
+
}
|
|
83
|
+
return cleanText(String(item)) || "(empty item)";
|
|
84
|
+
}
|
|
85
|
+
|
|
42
86
|
function truncateWords(text, maxWords) {
|
|
43
87
|
const items = words(text);
|
|
44
88
|
if (items.length <= maxWords) return cleanText(text);
|
|
@@ -56,85 +100,111 @@ function hostOf(url) {
|
|
|
56
100
|
}
|
|
57
101
|
}
|
|
58
102
|
|
|
59
|
-
function
|
|
60
|
-
if (typeof
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
103
|
+
function normalizeUrl(url) {
|
|
104
|
+
if (typeof url !== "string") return null;
|
|
105
|
+
const trimmed = url.trim().replace(/[.,;:]+$/, "");
|
|
106
|
+
if (!/^https?:\/\//i.test(trimmed)) return null;
|
|
107
|
+
try {
|
|
108
|
+
const parsed = new URL(trimmed);
|
|
109
|
+
parsed.hash = "";
|
|
110
|
+
return parsed.toString();
|
|
111
|
+
} catch {
|
|
112
|
+
return trimmed;
|
|
64
113
|
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function collectStructuredUrls(value, urls = []) {
|
|
117
|
+
if (!value || typeof value !== "object") return urls;
|
|
65
118
|
if (Array.isArray(value)) {
|
|
66
|
-
for (const item of value)
|
|
119
|
+
for (const item of value) collectStructuredUrls(item, urls);
|
|
67
120
|
return urls;
|
|
68
121
|
}
|
|
69
|
-
|
|
70
|
-
|
|
122
|
+
for (const [key, item] of Object.entries(value)) {
|
|
123
|
+
if (
|
|
124
|
+
/^(sourceUrls?|evidenceUrls?|urls?|url|uri|href|links?|references?|refs?|basis|sources)$/i.test(
|
|
125
|
+
key,
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
for (const candidate of asArray(item).length ? item : [item]) {
|
|
129
|
+
const normalized = normalizeUrl(candidate);
|
|
130
|
+
if (normalized) urls.push(normalized);
|
|
131
|
+
else if (candidate && typeof candidate === "object") {
|
|
132
|
+
collectStructuredUrls(candidate, urls);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (item && typeof item === "object") collectStructuredUrls(item, urls);
|
|
71
138
|
}
|
|
72
139
|
return urls;
|
|
73
140
|
}
|
|
74
141
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (trimmed) locators.push(trimmed);
|
|
84
|
-
}
|
|
85
|
-
return locators;
|
|
86
|
-
}
|
|
87
|
-
if (Array.isArray(value)) {
|
|
88
|
-
for (const item of value) collectSourceLocators(item, locators, fieldName);
|
|
89
|
-
return locators;
|
|
90
|
-
}
|
|
91
|
-
if (value && typeof value === "object") {
|
|
92
|
-
for (const [key, item] of Object.entries(value)) {
|
|
93
|
-
collectSourceLocators(item, locators, key);
|
|
142
|
+
function uniqueStructuredUrls(...values) {
|
|
143
|
+
const out = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
for (const value of values) {
|
|
146
|
+
for (const url of collectStructuredUrls(value, [])) {
|
|
147
|
+
if (seen.has(url)) continue;
|
|
148
|
+
seen.add(url);
|
|
149
|
+
out.push(url);
|
|
94
150
|
}
|
|
95
151
|
}
|
|
96
|
-
return
|
|
152
|
+
return out;
|
|
97
153
|
}
|
|
98
154
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
155
|
+
function urlsOf(item, limit = 3) {
|
|
156
|
+
return uniqueStructuredUrls(item).slice(0, limit);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function markdownLinkList(urls, maxItems = 3) {
|
|
160
|
+
return urls
|
|
161
|
+
.slice(0, maxItems)
|
|
162
|
+
.map((url) => `[${hostOf(url)}](${url})`)
|
|
163
|
+
.join(", ");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function itemText(item, fields, fallback = "") {
|
|
167
|
+
if (typeof item === "string") return cleanText(item) || fallback;
|
|
168
|
+
if (!item || typeof item !== "object") return fallback;
|
|
108
169
|
for (const field of fields) {
|
|
109
|
-
if (typeof item[field] === "string" && item[field].trim())
|
|
170
|
+
if (typeof item[field] === "string" && item[field].trim()) {
|
|
110
171
|
return cleanText(item[field]);
|
|
172
|
+
}
|
|
111
173
|
}
|
|
112
|
-
return
|
|
174
|
+
return fallback;
|
|
113
175
|
}
|
|
114
176
|
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
if (selected.length === 0) return "";
|
|
126
|
-
return ` (${selected.map((url) => hostOf(url)).join(", ")}: ${selected.join(" ")})`;
|
|
177
|
+
function evidenceStatusOf(item) {
|
|
178
|
+
if (!item || typeof item !== "object") return "not specified";
|
|
179
|
+
return cleanText(
|
|
180
|
+
item.evidenceStatus ??
|
|
181
|
+
item.status ??
|
|
182
|
+
item.confidence ??
|
|
183
|
+
item.sourceQuality ??
|
|
184
|
+
"not specified",
|
|
185
|
+
);
|
|
127
186
|
}
|
|
128
187
|
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
188
|
+
function confidenceOf(item) {
|
|
189
|
+
if (!item || typeof item !== "object") return "";
|
|
190
|
+
return cleanText(item.confidence ?? item.evidenceStatus ?? "");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function finiteNumber(value) {
|
|
194
|
+
const parsed = Number(value);
|
|
195
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function coverageCounts(coverage, fallback) {
|
|
199
|
+
if (!coverage || typeof coverage !== "object") return null;
|
|
200
|
+
return {
|
|
201
|
+
total: finiteNumber(coverage.verificationCandidates) ?? fallback.total,
|
|
202
|
+
verified: finiteNumber(coverage.verified) ?? fallback.verified,
|
|
203
|
+
partially_supported:
|
|
204
|
+
finiteNumber(coverage.partiallySupported) ?? fallback.partially_supported,
|
|
205
|
+
unsupported: finiteNumber(coverage.unsupported) ?? fallback.unsupported,
|
|
206
|
+
conflicting: finiteNumber(coverage.conflicting) ?? fallback.conflicting,
|
|
207
|
+
};
|
|
138
208
|
}
|
|
139
209
|
|
|
140
210
|
function claimCounts(control) {
|
|
@@ -150,163 +220,455 @@ function claimCounts(control) {
|
|
|
150
220
|
const status = claim?.status;
|
|
151
221
|
if (status && Object.hasOwn(counts, status)) counts[status] += 1;
|
|
152
222
|
}
|
|
153
|
-
const coverage =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
223
|
+
const coverage = coverageCounts(
|
|
224
|
+
control?.finalReport?.coverageSummary,
|
|
225
|
+
counts,
|
|
226
|
+
);
|
|
227
|
+
if (claims.length === 0 && coverage) return coverage;
|
|
228
|
+
if (!coverage) return counts;
|
|
229
|
+
|
|
230
|
+
const mismatches = [];
|
|
231
|
+
for (const key of [
|
|
232
|
+
"total",
|
|
233
|
+
"verified",
|
|
234
|
+
"partially_supported",
|
|
235
|
+
"unsupported",
|
|
236
|
+
"conflicting",
|
|
237
|
+
]) {
|
|
238
|
+
if (coverage[key] !== counts[key]) {
|
|
239
|
+
mismatches.push({
|
|
240
|
+
field: key,
|
|
241
|
+
claimVerdictIndex: counts[key],
|
|
242
|
+
coverageSummary: coverage[key],
|
|
243
|
+
});
|
|
244
|
+
}
|
|
169
245
|
}
|
|
170
|
-
return
|
|
246
|
+
return mismatches.length > 0
|
|
247
|
+
? { ...counts, coverageSummaryMismatch: mismatches }
|
|
248
|
+
: counts;
|
|
171
249
|
}
|
|
172
250
|
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
(slot) => slot?.status === "partial",
|
|
184
|
-
).length;
|
|
185
|
-
const missingSlots = factSlots.filter((slot) =>
|
|
186
|
-
["missing", "gap", "conflicting"].includes(slot?.status),
|
|
187
|
-
).length;
|
|
188
|
-
|
|
189
|
-
const sections = [];
|
|
190
|
-
sections.push("# Executive summary");
|
|
191
|
-
sections.push("");
|
|
192
|
-
sections.push(
|
|
193
|
-
`**Bottom line:** ${truncateWords(report.summary ?? control.digest ?? "Research completed with audited evidence.", 85)}`,
|
|
194
|
-
);
|
|
195
|
-
sections.push("");
|
|
251
|
+
function factSlotSummary(factSlots) {
|
|
252
|
+
return {
|
|
253
|
+
total: factSlots.length,
|
|
254
|
+
filled: factSlots.filter((slot) => slot?.status === "filled").length,
|
|
255
|
+
partial: factSlots.filter((slot) => slot?.status === "partial").length,
|
|
256
|
+
missingOrConflicting: factSlots.filter((slot) =>
|
|
257
|
+
["missing", "gap", "conflicting"].includes(slot?.status),
|
|
258
|
+
).length,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
196
261
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
sections.push(...findings);
|
|
207
|
-
sections.push("");
|
|
262
|
+
function statusRank(item) {
|
|
263
|
+
const status =
|
|
264
|
+
`${item?.evidenceStatus ?? item?.status ?? item?.confidence ?? ""}`.toLowerCase();
|
|
265
|
+
if (
|
|
266
|
+
status.includes("missing") ||
|
|
267
|
+
status.includes("gap") ||
|
|
268
|
+
status.includes("conflict")
|
|
269
|
+
) {
|
|
270
|
+
return 0;
|
|
208
271
|
}
|
|
272
|
+
if (status.includes("unsupported")) return 1;
|
|
273
|
+
if (status.includes("partial")) return 2;
|
|
274
|
+
if (status.includes("verified") && !status.includes("partial")) return 3;
|
|
275
|
+
if (status.includes("filled") || status.includes("high")) return 4;
|
|
276
|
+
return 5;
|
|
277
|
+
}
|
|
209
278
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
sections.push("");
|
|
222
|
-
}
|
|
279
|
+
function sortedFactSlots(report) {
|
|
280
|
+
return asArray(report.factSlotCoverage)
|
|
281
|
+
.slice()
|
|
282
|
+
.sort(
|
|
283
|
+
(a, b) =>
|
|
284
|
+
statusRank(a) - statusRank(b) ||
|
|
285
|
+
cleanText(a?.slotId ?? a?.label).localeCompare(
|
|
286
|
+
cleanText(b?.slotId ?? b?.label),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
223
290
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
291
|
+
function renderEvidenceStrength(report) {
|
|
292
|
+
const slots = sortedFactSlots(report);
|
|
293
|
+
const rows = slots.map((slot) => {
|
|
294
|
+
const area = escapeTableCell(
|
|
295
|
+
slot.label ?? slot.slotId ?? slot.bestValue ?? "Evidence area",
|
|
296
|
+
);
|
|
297
|
+
const status = escapeTableCell(evidenceStatusOf(slot));
|
|
298
|
+
const evidence = escapeTableCell(
|
|
299
|
+
markdownLinkList(urlsOf(slot, 2), 2) || "—",
|
|
300
|
+
);
|
|
301
|
+
const impact = escapeTableCell(
|
|
302
|
+
slot.parentImpact ?? slot.whyItMatters ?? slot.notes ?? "",
|
|
303
|
+
);
|
|
304
|
+
return `| ${area || "Evidence area"} | ${status || "—"} | ${evidence} | ${impact || "—"} |`;
|
|
305
|
+
});
|
|
306
|
+
if (rows.length === 0) return [];
|
|
307
|
+
return [
|
|
308
|
+
"## Evidence strength",
|
|
309
|
+
"",
|
|
310
|
+
"| Area | Status | Evidence | Why it matters |",
|
|
311
|
+
"|---|---|---|---|",
|
|
312
|
+
...rows,
|
|
313
|
+
"",
|
|
228
314
|
];
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function mainFindingEntries(report) {
|
|
318
|
+
return asArray(report.mainFindings).map((item) => ({
|
|
319
|
+
item,
|
|
320
|
+
text: itemText(
|
|
321
|
+
item,
|
|
322
|
+
["finding", "summary", "bestValue", "claim"],
|
|
323
|
+
stringifyItem(item),
|
|
324
|
+
),
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function recommendationEntries(report) {
|
|
329
|
+
return asArray(report.recommendations).map((item) => ({
|
|
330
|
+
item,
|
|
331
|
+
text: itemText(
|
|
332
|
+
item,
|
|
333
|
+
["recommendation", "action", "step", "note"],
|
|
334
|
+
stringifyItem(item),
|
|
335
|
+
),
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function actionEntries(report) {
|
|
340
|
+
return asArray(report.actionPlan).map((item) => ({
|
|
341
|
+
item,
|
|
342
|
+
text:
|
|
343
|
+
itemText(item, ["action", "recommendation", "note"]) ||
|
|
344
|
+
(typeof item?.step === "string" && cleanText(item.step)) ||
|
|
345
|
+
stringifyItem(item),
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function renderMainFindings(report) {
|
|
350
|
+
const findings = mainFindingEntries(report);
|
|
351
|
+
if (findings.length === 0) return [];
|
|
352
|
+
const out = ["## Main findings", ""];
|
|
353
|
+
findings.forEach(({ item: finding, text }, index) => {
|
|
354
|
+
const status = evidenceStatusOf(finding);
|
|
355
|
+
const confidence = confidenceOf(finding);
|
|
356
|
+
const urls = markdownLinkList(urlsOf(finding, 4), 4);
|
|
357
|
+
out.push(`### ${index + 1}. ${text}`);
|
|
358
|
+
out.push("");
|
|
359
|
+
out.push(
|
|
360
|
+
`Evidence status: **${status || "not specified"}**${confidence && confidence !== status ? ` \nConfidence: **${confidence}**` : ""}`,
|
|
361
|
+
);
|
|
362
|
+
if (urls) out.push(`Sources: ${urls}`);
|
|
363
|
+
const explanation = itemText(finding, [
|
|
364
|
+
"rationale",
|
|
365
|
+
"explanation",
|
|
366
|
+
"details",
|
|
367
|
+
"notes",
|
|
368
|
+
]);
|
|
369
|
+
if (explanation && explanation !== text) out.push("", explanation);
|
|
370
|
+
out.push("");
|
|
371
|
+
});
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function renderRecommendations(report) {
|
|
376
|
+
const recommendations = recommendationEntries(report);
|
|
377
|
+
if (recommendations.length === 0) return [];
|
|
378
|
+
const out = ["## Recommendations", ""];
|
|
379
|
+
recommendations.forEach(({ item, text }, index) => {
|
|
380
|
+
const status = evidenceStatusOf(item);
|
|
381
|
+
const urls = markdownLinkList(urlsOf(item, 4), 4);
|
|
382
|
+
out.push(`${index + 1}. **${text}**`);
|
|
383
|
+
out.push(` - Evidence status: ${status || "not specified"}`);
|
|
384
|
+
if (urls) out.push(` - Sources: ${urls}`);
|
|
385
|
+
out.push("");
|
|
386
|
+
});
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function renderActionPlan(report) {
|
|
391
|
+
const actions = actionEntries(report);
|
|
392
|
+
if (actions.length === 0) return [];
|
|
393
|
+
const out = ["## Action plan", ""];
|
|
394
|
+
actions.forEach(({ item, text }, index) => {
|
|
395
|
+
const numericStep = Number(item?.step);
|
|
396
|
+
const step = Number.isFinite(numericStep) ? numericStep : index + 1;
|
|
397
|
+
const urls = markdownLinkList(urlsOf(item, 3), 3);
|
|
398
|
+
const evidence = evidenceStatusOf(item);
|
|
399
|
+
out.push(`${step}. ${text}`);
|
|
400
|
+
if (evidence && evidence !== "not specified")
|
|
401
|
+
out.push(` - Evidence: ${evidence}`);
|
|
402
|
+
if (urls) out.push(` - Sources: ${urls}`);
|
|
403
|
+
out.push("");
|
|
404
|
+
});
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function caveatText(item) {
|
|
409
|
+
return itemText(
|
|
410
|
+
item,
|
|
411
|
+
["gap", "finding", "claim", "note", "whyItMatters", "parentImpact"],
|
|
412
|
+
stringifyItem(item),
|
|
236
413
|
);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function caveatCategories(report) {
|
|
417
|
+
return [
|
|
418
|
+
{ kind: "Gap", items: flattenItems(report.remainingGaps) },
|
|
419
|
+
{ kind: "Unsupported", items: flattenItems(report.notableUnsupportedClaims) },
|
|
420
|
+
{ kind: "Contested", items: flattenItems(report.contestedAreas) },
|
|
421
|
+
{ kind: "Caveat", items: flattenItems(report.caveatedFindings) },
|
|
422
|
+
{ kind: "Unverified lead", items: flattenItems(report.unverifiedButRelevant) },
|
|
423
|
+
{ kind: "Decision note", items: flattenItems(report.parentDecisionNotes) },
|
|
424
|
+
]
|
|
425
|
+
.map((category) => ({
|
|
426
|
+
kind: category.kind,
|
|
427
|
+
entries: category.items
|
|
428
|
+
.map((item) => ({ item, text: caveatText(item) }))
|
|
429
|
+
.filter((entry) => entry.text),
|
|
430
|
+
}))
|
|
431
|
+
.filter((category) => category.entries.length > 0);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function selectCaveats(report) {
|
|
435
|
+
const categories = caveatCategories(report);
|
|
436
|
+
const selected = [];
|
|
437
|
+
for (const category of categories) {
|
|
438
|
+
for (const entry of category.entries) {
|
|
439
|
+
selected.push({ kind: category.kind, ...entry });
|
|
440
|
+
}
|
|
241
441
|
}
|
|
442
|
+
return {
|
|
443
|
+
selected,
|
|
444
|
+
total: selected.length,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function renderCaveats(report) {
|
|
449
|
+
const selection = selectCaveats(report);
|
|
450
|
+
if (selection.total === 0) return [];
|
|
451
|
+
const out = ["## Caveats and remaining gaps", ""];
|
|
452
|
+
for (const { kind, item, text } of selection.selected) {
|
|
453
|
+
const urls = markdownLinkList(urlsOf(item, 3), 3);
|
|
454
|
+
out.push(`- **${kind}:** ${text}${urls ? ` (${urls})` : ""}`);
|
|
455
|
+
}
|
|
456
|
+
out.push("");
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
242
459
|
|
|
243
|
-
|
|
244
|
-
|
|
460
|
+
function renderSourceIndex(sourceIndex) {
|
|
461
|
+
if (sourceIndex.length === 0) return [];
|
|
462
|
+
const grouped = new Map();
|
|
463
|
+
for (const url of sourceIndex) {
|
|
464
|
+
const host = hostOf(url);
|
|
465
|
+
if (!grouped.has(host)) grouped.set(host, []);
|
|
466
|
+
grouped.get(host).push(url);
|
|
467
|
+
}
|
|
468
|
+
const out = ["## Source index", ""];
|
|
469
|
+
for (const [host, urls] of grouped) {
|
|
470
|
+
out.push(
|
|
471
|
+
`- **${host}**: ${urls.map((url) => `[${url}](${url})`).join(", ")}`,
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
out.push("");
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function renderAuditSummary(control, claimSummary, slots) {
|
|
479
|
+
const coverage = control?.finalReport?.coverageSummary ?? {};
|
|
480
|
+
const mismatches = asArray(claimSummary.coverageSummaryMismatch);
|
|
481
|
+
return [
|
|
482
|
+
"## Audit summary",
|
|
483
|
+
"",
|
|
484
|
+
`- Claims: ${claimSummary.verified} verified, ${claimSummary.partially_supported} partially supported, ${claimSummary.unsupported} unsupported, ${claimSummary.conflicting} conflicting.`,
|
|
485
|
+
`- Fact slots: ${slots.filled} filled, ${slots.partial} partial, ${slots.missingOrConflicting} missing/conflicting, ${slots.total} total.`,
|
|
486
|
+
...(mismatches.length > 0
|
|
487
|
+
? [
|
|
488
|
+
`- Coverage summary mismatch: displayed claim counts come from \`claimVerdictIndex\`; model coverageSummary disagreed on ${mismatches
|
|
489
|
+
.map((mismatch) => mismatch.field)
|
|
490
|
+
.join(", ")}.`,
|
|
491
|
+
]
|
|
492
|
+
: []),
|
|
493
|
+
...(coverage.researchQuestions != null
|
|
494
|
+
? [`- Research questions: ${coverage.researchQuestions}.`]
|
|
495
|
+
: []),
|
|
496
|
+
"- Audit artifact: `final-audit.control.json`.",
|
|
497
|
+
"",
|
|
498
|
+
];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function renderWarnings(sectionCounts) {
|
|
502
|
+
const checks = [
|
|
503
|
+
["findings", "renderedFindings", "findings"],
|
|
504
|
+
["recommendations", "renderedRecommendations", "recommendations"],
|
|
505
|
+
["actionItems", "renderedActionItems", "action items"],
|
|
506
|
+
["caveatsAndGaps", "renderedCaveatsAndGaps", "caveats/gaps"],
|
|
507
|
+
["factSlots", "renderedFactSlots", "fact slots"],
|
|
508
|
+
["sourceUrls", "renderedSourceUrls", "source URLs"],
|
|
509
|
+
];
|
|
510
|
+
return checks
|
|
511
|
+
.filter(([totalKey, renderedKey]) => {
|
|
512
|
+
const total = Number(sectionCounts[totalKey] ?? 0);
|
|
513
|
+
const rendered = Number(sectionCounts[renderedKey] ?? 0);
|
|
514
|
+
return total !== rendered;
|
|
515
|
+
})
|
|
516
|
+
.map(([totalKey, renderedKey, label]) => ({
|
|
517
|
+
section: totalKey,
|
|
518
|
+
label,
|
|
519
|
+
total: sectionCounts[totalKey],
|
|
520
|
+
rendered: sectionCounts[renderedKey],
|
|
521
|
+
}));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function renderResearchMarkdown(control, options = {}) {
|
|
525
|
+
const report = control?.finalReport ?? {};
|
|
526
|
+
const claimSummary = claimCounts(control);
|
|
527
|
+
const factSlots = sortedFactSlots(report);
|
|
528
|
+
const slots = factSlotSummary(asArray(report.factSlotCoverage));
|
|
529
|
+
const findings = mainFindingEntries(report);
|
|
530
|
+
const recommendations = recommendationEntries(report);
|
|
531
|
+
const actions = actionEntries(report);
|
|
532
|
+
const caveats = selectCaveats(report);
|
|
533
|
+
const allSourceIndex = uniqueStructuredUrls(
|
|
534
|
+
report.factSlotCoverage,
|
|
535
|
+
report.mainFindings,
|
|
536
|
+
report.recommendations,
|
|
537
|
+
report.actionPlan,
|
|
538
|
+
report.caveatedFindings,
|
|
539
|
+
report.contestedAreas,
|
|
540
|
+
report.notableUnsupportedClaims,
|
|
541
|
+
report.remainingGaps,
|
|
542
|
+
report.parentDecisionNotes,
|
|
543
|
+
report.unverifiedButRelevant,
|
|
544
|
+
control?.claimVerdictIndex?.claims,
|
|
245
545
|
);
|
|
546
|
+
const maxUrls = Number.isFinite(Number(options.maxUrls))
|
|
547
|
+
? Math.max(0, Number(options.maxUrls))
|
|
548
|
+
: Infinity;
|
|
549
|
+
const sourceIndex = Number.isFinite(maxUrls)
|
|
550
|
+
? allSourceIndex.slice(0, maxUrls)
|
|
551
|
+
: allSourceIndex;
|
|
552
|
+
const sectionCounts = {
|
|
553
|
+
findings: asArray(report.mainFindings).length,
|
|
554
|
+
renderedFindings: findings.length,
|
|
555
|
+
recommendations: asArray(report.recommendations).length,
|
|
556
|
+
renderedRecommendations: recommendations.length,
|
|
557
|
+
actionItems: asArray(report.actionPlan).length,
|
|
558
|
+
renderedActionItems: actions.length,
|
|
559
|
+
caveatsAndGaps:
|
|
560
|
+
flattenItems(report.remainingGaps).length +
|
|
561
|
+
flattenItems(report.notableUnsupportedClaims).length +
|
|
562
|
+
flattenItems(report.contestedAreas).length +
|
|
563
|
+
flattenItems(report.caveatedFindings).length +
|
|
564
|
+
flattenItems(report.unverifiedButRelevant).length +
|
|
565
|
+
flattenItems(report.parentDecisionNotes).length,
|
|
566
|
+
renderedCaveatsAndGaps: caveats.selected.length,
|
|
567
|
+
factSlots: asArray(report.factSlotCoverage).length,
|
|
568
|
+
renderedFactSlots: factSlots.length,
|
|
569
|
+
sourceUrls: allSourceIndex.length,
|
|
570
|
+
renderedSourceUrls: sourceIndex.length,
|
|
571
|
+
};
|
|
572
|
+
const warnings = renderWarnings(sectionCounts);
|
|
246
573
|
|
|
247
|
-
|
|
574
|
+
const sections = [
|
|
575
|
+
"# Research report",
|
|
576
|
+
"",
|
|
577
|
+
"## Bottom line",
|
|
578
|
+
"",
|
|
579
|
+
cleanText(
|
|
580
|
+
report.summary ??
|
|
581
|
+
control.digest ??
|
|
582
|
+
"Research completed with audited evidence.",
|
|
583
|
+
),
|
|
584
|
+
"",
|
|
585
|
+
...renderEvidenceStrength(report),
|
|
586
|
+
...renderMainFindings(report),
|
|
587
|
+
...renderRecommendations(report),
|
|
588
|
+
...renderActionPlan(report),
|
|
589
|
+
...renderCaveats(report),
|
|
590
|
+
...renderSourceIndex(sourceIndex),
|
|
591
|
+
...renderAuditSummary(control, claimSummary, slots),
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
const markdown = sections
|
|
248
595
|
.join("\n")
|
|
249
596
|
.replace(/\n{3,}/g, "\n\n")
|
|
250
597
|
.trim();
|
|
251
|
-
let truncated = false;
|
|
252
|
-
if (countWords(markdown) > maxWords) {
|
|
253
|
-
truncated = true;
|
|
254
|
-
markdown = truncateWords(markdown, maxWords);
|
|
255
|
-
}
|
|
256
|
-
for (const locator of [...new Set(collectSourceLocators(control))]) {
|
|
257
|
-
if (state.urls.size >= options.maxUrls) break;
|
|
258
|
-
state.urls.add(locator);
|
|
259
|
-
}
|
|
260
598
|
return {
|
|
261
599
|
markdown,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
partial: partialSlots,
|
|
269
|
-
missingOrConflicting: missingSlots,
|
|
270
|
-
},
|
|
600
|
+
sourceIndex,
|
|
601
|
+
allSourceIndex,
|
|
602
|
+
claimSummary,
|
|
603
|
+
factSlotSummary: slots,
|
|
604
|
+
sectionCounts,
|
|
605
|
+
renderWarnings: warnings,
|
|
271
606
|
};
|
|
272
607
|
}
|
|
273
608
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}) {
|
|
609
|
+
function stripLeadingHeading(markdown) {
|
|
610
|
+
return String(markdown ?? "").replace(/^#\s+[^\n]+\n*/i, "");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export default async function renderExecutive({ sources, options = {}, context = {} }) {
|
|
279
614
|
const control =
|
|
280
615
|
findSource(sources, "final-audit") ??
|
|
281
616
|
sources?.[Object.keys(sources ?? {})[0]];
|
|
282
|
-
const opts = {
|
|
283
|
-
maxWords: Number(options.maxWords ?? 600),
|
|
284
|
-
maxUrls: Number(options.maxUrls ?? 5),
|
|
285
|
-
maxFindings: Number(options.maxFindings ?? 3),
|
|
286
|
-
maxRecommendations: Number(options.maxRecommendations ?? 3),
|
|
287
|
-
maxGaps: Number(options.maxGaps ?? 2),
|
|
288
|
-
};
|
|
289
617
|
if (!control || typeof control !== "object") {
|
|
290
618
|
return {
|
|
291
619
|
schema: "deep-research-executive-render-v1",
|
|
292
|
-
digest:
|
|
620
|
+
digest:
|
|
621
|
+
"Research report rendering failed: missing final-audit control source.",
|
|
293
622
|
status: "blocked",
|
|
294
623
|
blockers: ["missing final-audit control source"],
|
|
295
624
|
executiveMarkdown: "",
|
|
625
|
+
reportMarkdown: "",
|
|
296
626
|
wordCount: 0,
|
|
297
627
|
sourceUrlCount: 0,
|
|
298
|
-
|
|
628
|
+
renderWarnings: [],
|
|
629
|
+
gates: {
|
|
630
|
+
renderedAllStructuredItems: false,
|
|
631
|
+
passed: false,
|
|
632
|
+
},
|
|
299
633
|
};
|
|
300
634
|
}
|
|
301
635
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
636
|
+
const opts = {
|
|
637
|
+
maxWords: Number.isFinite(Number(options.maxWords))
|
|
638
|
+
? Math.max(0, Number(options.maxWords))
|
|
639
|
+
: Infinity,
|
|
640
|
+
maxUrls: Number.isFinite(Number(options.maxUrls))
|
|
641
|
+
? Math.max(0, Number(options.maxUrls))
|
|
642
|
+
: Infinity,
|
|
643
|
+
maxFindings: Number.isFinite(Number(options.maxFindings))
|
|
644
|
+
? Math.max(0, Number(options.maxFindings))
|
|
645
|
+
: undefined,
|
|
646
|
+
maxRecommendations: Number.isFinite(Number(options.maxRecommendations))
|
|
647
|
+
? Math.max(0, Number(options.maxRecommendations))
|
|
648
|
+
: undefined,
|
|
649
|
+
maxGaps: Number.isFinite(Number(options.maxGaps))
|
|
650
|
+
? Math.max(0, Number(options.maxGaps))
|
|
651
|
+
: undefined,
|
|
652
|
+
};
|
|
653
|
+
const rendered = renderResearchMarkdown(control, opts);
|
|
654
|
+
let markdown = rendered.markdown;
|
|
655
|
+
let truncated = false;
|
|
656
|
+
if (Number.isFinite(opts.maxWords) && countWords(markdown) > opts.maxWords) {
|
|
657
|
+
truncated = true;
|
|
658
|
+
markdown = truncateWords(markdown, opts.maxWords);
|
|
659
|
+
}
|
|
660
|
+
const wordCount = countWords(markdown);
|
|
661
|
+
const sourceUrlCount = rendered.sourceIndex.length;
|
|
662
|
+
const substantiveRenderWarnings = rendered.renderWarnings.filter(
|
|
663
|
+
(warning) => warning.section !== "sourceUrls",
|
|
664
|
+
);
|
|
665
|
+
const renderedAllStructuredItems = substantiveRenderWarnings.length === 0;
|
|
666
|
+
const truncatedWithOpenGaps =
|
|
667
|
+
truncated && Number(rendered.sectionCounts.caveatsAndGaps ?? 0) > 0;
|
|
668
|
+
const passed = renderedAllStructuredItems && !truncatedWithOpenGaps;
|
|
306
669
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
let sidecarPath;
|
|
670
|
+
let executiveSidecarPath;
|
|
671
|
+
let reportSidecarPath;
|
|
310
672
|
try {
|
|
311
673
|
if (context.cwd && context.runId && context.taskId) {
|
|
312
674
|
const taskDir = join(
|
|
@@ -318,36 +680,47 @@ export default async function renderExecutive({
|
|
|
318
680
|
context.taskId,
|
|
319
681
|
);
|
|
320
682
|
await mkdir(taskDir, { recursive: true });
|
|
321
|
-
|
|
322
|
-
|
|
683
|
+
executiveSidecarPath = join(taskDir, "executive.md");
|
|
684
|
+
reportSidecarPath = join(taskDir, "report.md");
|
|
685
|
+
await writeFile(executiveSidecarPath, `${markdown}\n`, "utf8");
|
|
686
|
+
await writeFile(reportSidecarPath, `${markdown}\n`, "utf8");
|
|
323
687
|
}
|
|
324
688
|
} catch {
|
|
325
|
-
//
|
|
689
|
+
// Sidecars are non-authoritative; keep control output deterministic.
|
|
326
690
|
}
|
|
327
691
|
|
|
328
692
|
return {
|
|
329
693
|
schema: "deep-research-executive-render-v1",
|
|
330
|
-
digest: truncateWords(
|
|
331
|
-
rendered.markdown.replace(/^# Executive summary\s*/i, ""),
|
|
332
|
-
45,
|
|
333
|
-
),
|
|
694
|
+
digest: truncateWords(stripLeadingHeading(markdown), 45),
|
|
334
695
|
status: passed ? "passed" : "failed",
|
|
335
|
-
|
|
696
|
+
renderMode: "evidence-backed-report",
|
|
697
|
+
executiveMarkdown: markdown,
|
|
698
|
+
reportMarkdown: markdown,
|
|
336
699
|
wordCount,
|
|
337
700
|
sourceUrlCount,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
701
|
+
totalSourceUrlCount: rendered.allSourceIndex.length,
|
|
702
|
+
sourceUrls: rendered.sourceIndex,
|
|
703
|
+
sourceIndex: rendered.sourceIndex.map((url) => ({
|
|
704
|
+
url,
|
|
705
|
+
host: hostOf(url),
|
|
706
|
+
})),
|
|
707
|
+
claimSummary: rendered.claimSummary,
|
|
708
|
+
factSlotSummary: rendered.factSlotSummary,
|
|
709
|
+
sectionCounts: rendered.sectionCounts,
|
|
710
|
+
renderWarnings: rendered.renderWarnings,
|
|
341
711
|
gates: {
|
|
342
|
-
|
|
343
|
-
|
|
712
|
+
renderedAllStructuredItems,
|
|
713
|
+
maxWords: Number.isFinite(opts.maxWords) ? opts.maxWords : null,
|
|
714
|
+
maxUrls: Number.isFinite(opts.maxUrls) ? opts.maxUrls : null,
|
|
344
715
|
maxFindings: opts.maxFindings,
|
|
345
716
|
maxRecommendations: opts.maxRecommendations,
|
|
346
717
|
maxGaps: opts.maxGaps,
|
|
347
|
-
truncated
|
|
718
|
+
truncated,
|
|
719
|
+
truncatedWithOpenGaps,
|
|
348
720
|
passed,
|
|
349
721
|
},
|
|
350
722
|
auditArtifact: "final-audit.control.json",
|
|
351
|
-
...(
|
|
723
|
+
...(executiveSidecarPath ? { sidecarPath: "executive.md" } : {}),
|
|
724
|
+
...(reportSidecarPath ? { reportSidecarPath: "report.md" } : {}),
|
|
352
725
|
};
|
|
353
726
|
}
|