@tangle-network/agent-runtime 0.194.0 → 0.195.1
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/dist/{activation-BQFIiyUG.js → activation-B4adh0sA.js} +3 -3
- package/dist/{activation-BQFIiyUG.js.map → activation-B4adh0sA.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{candidate-execution-B6CDW-fp.js → candidate-execution-nvqVIMyS.js} +5 -4
- package/dist/{candidate-execution-B6CDW-fp.js.map → candidate-execution-nvqVIMyS.js.map} +1 -1
- package/dist/{coordination-driver-iSN-m3VX.js → coordination-driver-CuShDjRV.js} +3 -3
- package/dist/{coordination-driver-iSN-m3VX.js.map → coordination-driver-CuShDjRV.js.map} +1 -1
- package/dist/{delegate-DCC2muUd.js → delegate-CSFC-Kh3.js} +2 -2
- package/dist/{delegate-DCC2muUd.js.map → delegate-CSFC-Kh3.js.map} +1 -1
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +2 -2
- package/dist/{environment-provider-Dr-wfnHg.js → environment-provider-Z1JazbJ1.js} +108 -10
- package/dist/environment-provider-Z1JazbJ1.js.map +1 -0
- package/dist/environment-provider.js +1 -1
- package/dist/{graph-DghidDx5.js → graph-DqPUeri6.js} +3 -3
- package/dist/{graph-DghidDx5.js.map → graph-DqPUeri6.js.map} +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/graph.js +4 -4
- package/dist/{improve-COGzLCiY.d.ts → improve-7VTwse1c.d.ts} +4 -4
- package/dist/{improvement-cycle-DeS1ZC5B.js → improvement-cycle-DEbciBTL.js} +20 -6
- package/dist/{improvement-cycle-DeS1ZC5B.js.map → improvement-cycle-DEbciBTL.js.map} +1 -1
- package/dist/{index-CrBgLCIf.d.ts → index-CVD30soi.d.ts} +2 -2
- package/dist/{index-BEPjOPwH.d.ts → index-DNx0nWZH.d.ts} +9 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +72 -26
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +1 -1
- package/dist/intelligence.js +7 -6
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +9 -9
- package/dist/{knowledge-B5okOzBQ.js → knowledge-B0Mrp-XB.js} +7 -17
- package/dist/knowledge-B0Mrp-XB.js.map +1 -0
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-U1J6x_UF.js → loop-runner-bin-C5K46H0Q.js} +3 -3
- package/dist/{loop-runner-bin-U1J6x_UF.js.map → loop-runner-bin-C5K46H0Q.js.map} +1 -1
- package/dist/{loop-runner-bin-Clc5IAwu.d.ts → loop-runner-bin-b4585v9Z.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +5 -5
- package/dist/{prepare-C29kNAon.js → prepare-DDGp0-rW.js} +4 -54
- package/dist/prepare-DDGp0-rW.js.map +1 -0
- package/dist/profiles.js +454 -1
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-CKYND416.js → protected-model-port-DxFN8DLS.js} +3 -2
- package/dist/{protected-model-port-CKYND416.js.map → protected-model-port-DxFN8DLS.js.map} +1 -1
- package/dist/{protected-redaction--F3v1oo8.js → protected-redaction-wGo44k2K.js} +54 -3
- package/dist/protected-redaction-wGo44k2K.js.map +1 -0
- package/dist/{provision-supervisor-CRPbnJiB.js → provision-supervisor-chyz0_Aj.js} +4 -4
- package/dist/{provision-supervisor-CRPbnJiB.js.map → provision-supervisor-chyz0_Aj.js.map} +1 -1
- package/dist/{redact-BOU77QfQ.js → redact-Dv6PeuQz.js} +2 -2
- package/dist/{redact-BOU77QfQ.js.map → redact-Dv6PeuQz.js.map} +1 -1
- package/dist/{runtime-BVrqSu0-.js → runtime-BZryF6gD.js} +140 -65
- package/dist/runtime-BZryF6gD.js.map +1 -0
- package/dist/{server-D6XmE1Du.js → server-BorSFmn8.js} +3 -3
- package/dist/{server-D6XmE1Du.js.map → server-BorSFmn8.js.map} +1 -1
- package/dist/{structural-rollout-BcPXFzVb.js → structural-rollout-pOqANr8Y.js} +23 -10
- package/dist/structural-rollout-pOqANr8Y.js.map +1 -0
- package/dist/{supervise-C0V3pZXK.js → supervise-BReI5ATC.js} +4 -4
- package/dist/{supervise-C0V3pZXK.js.map → supervise-BReI5ATC.js.map} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +14 -13
- package/dist/testing.js.map +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{workspace-archive-BCezkeIk.js → workspace-archive-Ybomp7AN.js} +3 -2
- package/dist/{workspace-archive-BCezkeIk.js.map → workspace-archive-Ybomp7AN.js.map} +1 -1
- package/package.json +4 -4
- package/skills/agent-graphs/SKILL.md +28 -123
- package/skills/agent-graphs/references/authoring.md +34 -0
- package/skills/agent-graphs/references/measurement.md +18 -0
- package/skills/build-with-agent-runtime/SKILL.md +31 -117
- package/skills/build-with-agent-runtime/references/improvement.md +32 -0
- package/skills/codemode/SKILL.md +22 -34
- package/skills/codemode/references/runtime-execution.md +14 -0
- package/skills/generate-eval/SKILL.md +2 -1
- package/skills/loop-writer/SKILL.md +30 -84
- package/skills/supervise/SKILL.md +43 -95
- package/skills/supervise/references/profile-authoring.md +19 -0
- package/dist/environment-provider-Dr-wfnHg.js.map +0 -1
- package/dist/knowledge-B5okOzBQ.js.map +0 -1
- package/dist/prepare-C29kNAon.js.map +0 -1
- package/dist/protected-redaction--F3v1oo8.js.map +0 -1
- package/dist/researcher-Cp4JRbFp.js +0 -457
- package/dist/researcher-Cp4JRbFp.js.map +0 -1
- package/dist/runtime-BVrqSu0-.js.map +0 -1
- package/dist/structural-rollout-BcPXFzVb.js.map +0 -1
- package/skills/agent-graphs/IMPROVE.md +0 -58
package/dist/profiles.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { i as
|
|
1
|
+
import { i as ConfigError } from "./errors-CDZ8XsVj.js";
|
|
2
|
+
import { t as assertExecutableAgentProfile } from "./model-policy-Wf2MxXJ9.js";
|
|
2
3
|
import { n as UI_LENSES, t as UI_FINDING_SEVERITIES } from "./substrate-B0TYNrXn.js";
|
|
3
4
|
import { t as coderTaskToPrompt } from "./coder-yhVWbdWc.js";
|
|
5
|
+
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
4
6
|
import { promises } from "node:fs";
|
|
5
7
|
import path from "node:path";
|
|
6
8
|
//#region src/profiles/ui-auditor/slugify.ts
|
|
@@ -317,6 +319,457 @@ async function writeAuditIndex(workspaceDir) {
|
|
|
317
319
|
return out;
|
|
318
320
|
}
|
|
319
321
|
//#endregion
|
|
322
|
+
//#region src/profiles/researcher.ts
|
|
323
|
+
/**
|
|
324
|
+
* Opinionated preset for source-grounded research
|
|
325
|
+
* tasks. The agent is told to:
|
|
326
|
+
* - bound its work to a single `knowledgeNamespace`
|
|
327
|
+
* - emit `items[]` carrying provenance + confidence
|
|
328
|
+
* - emit `citations[]` linking quotes back to source urls
|
|
329
|
+
* - emit `proposedWrites[]` — never call materialize itself
|
|
330
|
+
* - describe `gaps` it could not answer
|
|
331
|
+
*
|
|
332
|
+
* The profile is stateless and agent-agnostic. The caller supplies the exact execution profile;
|
|
333
|
+
* this preset adds only the researcher prompt, tools, parser, and validator.
|
|
334
|
+
*
|
|
335
|
+
* Propose-don't-apply: the profile NEVER writes to the knowledge base.
|
|
336
|
+
* It produces `proposedWrites: KnowledgeUpdate[]` in the output. The
|
|
337
|
+
* caller (gtm-agent, journey-eval, user) decides whether to feed those
|
|
338
|
+
* updates through `applyKnowledgeWriteBlocks` / a KbStore put.
|
|
339
|
+
*
|
|
340
|
+
* Namespace isolation: every `KnowledgeItem` + `KnowledgeUpdate` in the
|
|
341
|
+
* output carries `namespace`. The validator hard-fails when any item
|
|
342
|
+
* touches a namespace other than `task.knowledgeNamespace`.
|
|
343
|
+
*
|
|
344
|
+
* @experimental
|
|
345
|
+
*/
|
|
346
|
+
const DEFAULT_CITATION_DENSITY_MIN = .7;
|
|
347
|
+
/** Build a source-grounded researcher profile with output parsing and validation. @experimental */
|
|
348
|
+
function researcherProfile(options) {
|
|
349
|
+
const base = agentProfileSchema.parse(options.profile);
|
|
350
|
+
assertExecutableAgentProfile(base, "researcherProfile");
|
|
351
|
+
const name = options.name ?? base.name;
|
|
352
|
+
const systemPrompt = options.systemPrompt ?? RESEARCHER_SYSTEM_PROMPT;
|
|
353
|
+
const citationDensityMin = options.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN;
|
|
354
|
+
const profile = {
|
|
355
|
+
...base,
|
|
356
|
+
name,
|
|
357
|
+
description: base.description ?? "Source-grounded research agent. Propose-don't-apply.",
|
|
358
|
+
prompt: {
|
|
359
|
+
...base.prompt,
|
|
360
|
+
systemPrompt
|
|
361
|
+
},
|
|
362
|
+
tools: {
|
|
363
|
+
web_search: true,
|
|
364
|
+
fs: true,
|
|
365
|
+
shell: true,
|
|
366
|
+
...base.tools
|
|
367
|
+
},
|
|
368
|
+
metadata: {
|
|
369
|
+
...base.metadata,
|
|
370
|
+
specialty: "researcher"
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
return {
|
|
374
|
+
profile,
|
|
375
|
+
taskToPrompt: formatResearcherPrompt,
|
|
376
|
+
output: { parse: parseResearcherEvents },
|
|
377
|
+
validator: options.task ? createResearcherValidator(options.task, { citationDensityMin }) : createResearcherValidator({
|
|
378
|
+
question: "",
|
|
379
|
+
knowledgeNamespace: ""
|
|
380
|
+
}, {
|
|
381
|
+
citationDensityMin,
|
|
382
|
+
namespaceCheck: false
|
|
383
|
+
}),
|
|
384
|
+
agentRunSpec: {
|
|
385
|
+
name,
|
|
386
|
+
profile,
|
|
387
|
+
taskToPrompt: formatResearcherPrompt
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Build a fanout topology over multiple harnesses. The kernel round-robins
|
|
393
|
+
* `agentRuns` across the N parallel iterations and the `FanoutVote` driver
|
|
394
|
+
* picks the highest-scoring valid output.
|
|
395
|
+
*
|
|
396
|
+
* @experimental
|
|
397
|
+
*/
|
|
398
|
+
function multiHarnessResearcherFanout(options) {
|
|
399
|
+
if (options.profiles.length === 0) throw new ConfigError("multiHarnessResearcherFanout: at least one exact profile is required");
|
|
400
|
+
const agentRuns = options.profiles.map((profile) => {
|
|
401
|
+
const { agentRunSpec } = researcherProfile({ profile });
|
|
402
|
+
return agentRunSpec;
|
|
403
|
+
});
|
|
404
|
+
const { output, validator } = researcherProfile({
|
|
405
|
+
profile: options.profiles[0],
|
|
406
|
+
citationDensityMin: options.citationDensityMin,
|
|
407
|
+
task: options.task
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
agentRuns,
|
|
411
|
+
output,
|
|
412
|
+
validator,
|
|
413
|
+
driver: {
|
|
414
|
+
name: "researcher-fanout",
|
|
415
|
+
async plan(task, history) {
|
|
416
|
+
return history.length === 0 ? Array.from({ length: agentRuns.length }, () => task) : [];
|
|
417
|
+
},
|
|
418
|
+
decide() {
|
|
419
|
+
return "done";
|
|
420
|
+
},
|
|
421
|
+
describePlan() {
|
|
422
|
+
return { kind: "fanout" };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Build a validator that closes over a specific `ResearchTask`'s constraints.
|
|
429
|
+
*
|
|
430
|
+
* Checks in order:
|
|
431
|
+
* 1. Items must be non-empty.
|
|
432
|
+
* 2. Every item carries `evidence.length >= 1`.
|
|
433
|
+
* 3. Every item + proposedWrite is scoped to `task.knowledgeNamespace`
|
|
434
|
+
* (hard-fail on any namespace mismatch — defence in depth for the
|
|
435
|
+
* multi-tenant invariant).
|
|
436
|
+
* 4. Citation density (citations with quote / items) >= floor.
|
|
437
|
+
*
|
|
438
|
+
* Aggregate score:
|
|
439
|
+
* 0.4 · citation_density
|
|
440
|
+
* + 0.2 · source_diversity (distinct sources / max(items, 1))
|
|
441
|
+
* + 0.2 · recency_match (mean fraction within `recencyWindow`)
|
|
442
|
+
* + 0.2 · (1 − gaps/maxGaps), maxGaps = max(items, 1)
|
|
443
|
+
*
|
|
444
|
+
* @experimental
|
|
445
|
+
*/
|
|
446
|
+
function createResearcherValidator(task, config = {}) {
|
|
447
|
+
const citationDensityMin = config.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN;
|
|
448
|
+
const namespaceCheck = config.namespaceCheck ?? true;
|
|
449
|
+
return { async validate(output) {
|
|
450
|
+
const notes = [];
|
|
451
|
+
const scores = {};
|
|
452
|
+
let pass = true;
|
|
453
|
+
if (!Array.isArray(output.items) || output.items.length === 0) {
|
|
454
|
+
pass = false;
|
|
455
|
+
notes.push("no items");
|
|
456
|
+
scores.items = 0;
|
|
457
|
+
} else scores.items = 1;
|
|
458
|
+
const missingEvidence = (output.items ?? []).filter((item) => !Array.isArray(item.evidence) || item.evidence.length === 0);
|
|
459
|
+
if (missingEvidence.length > 0) {
|
|
460
|
+
pass = false;
|
|
461
|
+
notes.push(`${missingEvidence.length} item(s) without evidence`);
|
|
462
|
+
scores.provenance = 0;
|
|
463
|
+
} else scores.provenance = 1;
|
|
464
|
+
if (namespaceCheck) {
|
|
465
|
+
const foreignItems = (output.items ?? []).filter((item) => item.namespace !== task.knowledgeNamespace);
|
|
466
|
+
const foreignWrites = (output.proposedWrites ?? []).filter((write) => write.namespace !== task.knowledgeNamespace);
|
|
467
|
+
if (foreignItems.length > 0 || foreignWrites.length > 0) {
|
|
468
|
+
pass = false;
|
|
469
|
+
notes.push(`namespace violation: ${foreignItems.length} item(s) + ${foreignWrites.length} write(s) outside ${task.knowledgeNamespace}`);
|
|
470
|
+
scores.namespace = 0;
|
|
471
|
+
} else scores.namespace = 1;
|
|
472
|
+
}
|
|
473
|
+
const itemCount = Math.max(output.items?.length ?? 0, 1);
|
|
474
|
+
const citationsWithQuote = (output.citations ?? []).filter((citation) => typeof citation.quote === "string" && citation.quote.length > 0).length;
|
|
475
|
+
const citationDensity = Math.min(1, citationsWithQuote / itemCount);
|
|
476
|
+
if (citationDensity < citationDensityMin) {
|
|
477
|
+
pass = false;
|
|
478
|
+
notes.push(`citation density ${citationDensity.toFixed(2)} below floor ${citationDensityMin.toFixed(2)}`);
|
|
479
|
+
}
|
|
480
|
+
scores.citation_density = citationDensity;
|
|
481
|
+
const sourceSet = /* @__PURE__ */ new Set();
|
|
482
|
+
for (const item of output.items ?? []) for (const evidence of item.evidence ?? []) if (evidence.source) sourceSet.add(evidence.source);
|
|
483
|
+
scores.source_diversity = Math.min(1, sourceSet.size / itemCount);
|
|
484
|
+
scores.recency_match = recencyMatchScore(output.items ?? [], task.recencyWindow);
|
|
485
|
+
const maxGaps = itemCount;
|
|
486
|
+
const gapCount = output.gaps?.length ?? 0;
|
|
487
|
+
scores.gap_coverage = Math.max(0, 1 - gapCount / maxGaps);
|
|
488
|
+
const score = .4 * scores.citation_density + .2 * scores.source_diversity + .2 * scores.recency_match + .2 * scores.gap_coverage;
|
|
489
|
+
const verdict = {
|
|
490
|
+
valid: pass,
|
|
491
|
+
score: Number.isFinite(score) ? score : 0,
|
|
492
|
+
scores
|
|
493
|
+
};
|
|
494
|
+
if (notes.length > 0) verdict.notes = notes.join("; ");
|
|
495
|
+
return verdict;
|
|
496
|
+
} };
|
|
497
|
+
}
|
|
498
|
+
function recencyMatchScore(items, window) {
|
|
499
|
+
if (!window || window.since === void 0 && window.until === void 0) return 1;
|
|
500
|
+
if (items.length === 0) return 0;
|
|
501
|
+
const sinceMs = window.since?.getTime() ?? Number.NEGATIVE_INFINITY;
|
|
502
|
+
const untilMs = window.until?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
503
|
+
let hits = 0;
|
|
504
|
+
let total = 0;
|
|
505
|
+
for (const item of items) for (const evidence of item.evidence ?? []) {
|
|
506
|
+
if (typeof evidence.capturedAt !== "number") continue;
|
|
507
|
+
total += 1;
|
|
508
|
+
if (evidence.capturedAt >= sinceMs && evidence.capturedAt <= untilMs) hits += 1;
|
|
509
|
+
}
|
|
510
|
+
return total === 0 ? 0 : hits / total;
|
|
511
|
+
}
|
|
512
|
+
/** Built-in source-grounded research contract added to a caller-owned exact profile. */
|
|
513
|
+
const RESEARCHER_SYSTEM_PROMPT = [
|
|
514
|
+
"You are a research agent. Your job is to answer a research question with",
|
|
515
|
+
"source-grounded knowledge items that the caller will choose whether to",
|
|
516
|
+
"persist to a multi-tenant knowledge base.",
|
|
517
|
+
"",
|
|
518
|
+
"Hard rules:",
|
|
519
|
+
" 1. Every item you emit MUST carry the task's knowledgeNamespace exactly.",
|
|
520
|
+
" Never write to a different namespace.",
|
|
521
|
+
" 2. Every item MUST carry at least one evidence entry with a source.",
|
|
522
|
+
" A quote + url is strongly preferred; capturedAt is unix ms.",
|
|
523
|
+
" 3. You propose writes — you do NOT apply them. The caller decides.",
|
|
524
|
+
" 4. Self-report confidence honestly in [0, 1]. Do not inflate.",
|
|
525
|
+
" 5. List what you could not answer in `gaps`. Better to admit a gap",
|
|
526
|
+
" than fabricate.",
|
|
527
|
+
"",
|
|
528
|
+
"When you finish, emit a single final structured message of the shape:",
|
|
529
|
+
" ```json",
|
|
530
|
+
" { \"items\": [{ \"id\": \"...\", \"namespace\": \"...\", \"claim\": \"...\",",
|
|
531
|
+
" \"evidence\": [{ \"source\": \"...\", \"quote\": \"...\",",
|
|
532
|
+
" \"url\": \"...\", \"capturedAt\": 0 }],",
|
|
533
|
+
" \"confidence\": 0.0,",
|
|
534
|
+
" \"authoredBy\": { \"kind\": \"agent\", \"id\": \"...\" } }],",
|
|
535
|
+
" \"citations\": [{ \"url\": \"...\", \"quote\": \"...\", \"confidence\": 0.0 }],",
|
|
536
|
+
" \"proposedWrites\": [{ \"kind\": \"insert\", \"namespace\": \"...\",",
|
|
537
|
+
" \"item\": { /* same shape as items[] */ } }],",
|
|
538
|
+
" \"gaps\": [\"...\"],",
|
|
539
|
+
" \"notes\": \"free-form commentary\" }",
|
|
540
|
+
" ```"
|
|
541
|
+
].join("\n");
|
|
542
|
+
function formatResearcherPrompt(task) {
|
|
543
|
+
const sources = task.sources?.length ? task.sources.join(", ") : "(no preference)";
|
|
544
|
+
const window = formatRecencyWindow(task.recencyWindow);
|
|
545
|
+
return [
|
|
546
|
+
`Question: ${task.question}`,
|
|
547
|
+
`Knowledge namespace (DO NOT cross): ${task.knowledgeNamespace}`,
|
|
548
|
+
`Scope: ${task.scope ?? "(unspecified)"}`,
|
|
549
|
+
`Preferred sources: ${sources}`,
|
|
550
|
+
`Recency window: ${window}`,
|
|
551
|
+
`Max items: ${task.maxItems ?? "(no cap)"}`,
|
|
552
|
+
`Per-item minimum confidence: ${task.minConfidence ?? "(no floor)"}`,
|
|
553
|
+
"",
|
|
554
|
+
"Produce knowledge items with provenance + citations + proposed writes.",
|
|
555
|
+
"List gaps for anything you could not answer. Emit the final JSON",
|
|
556
|
+
"result block exactly as instructed."
|
|
557
|
+
].join("\n");
|
|
558
|
+
}
|
|
559
|
+
function formatRecencyWindow(window) {
|
|
560
|
+
if (!window) return "(none)";
|
|
561
|
+
return `${window.since ? window.since.toISOString() : "-∞"} .. ${window.until ? window.until.toISOString() : "now"}`;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Walk the event stream and return the last structured `research.result`
|
|
565
|
+
* payload. Falls back to scanning text deltas for a fenced JSON block.
|
|
566
|
+
*/
|
|
567
|
+
function parseResearcherEvents(events) {
|
|
568
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
569
|
+
const event = events[i];
|
|
570
|
+
if (!event) continue;
|
|
571
|
+
const type = String(event.type ?? "");
|
|
572
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
573
|
+
if (type === "result" || type === "final" || type === "research.result") {
|
|
574
|
+
const direct = coerceResearchOutput(data.result ?? data.output ?? data);
|
|
575
|
+
const finalText = pickString(data.finalText);
|
|
576
|
+
const fenced = finalText ? extractFencedJson(finalText) : void 0;
|
|
577
|
+
const fromFinalText = fenced ? coerceResearchOutput(fenced) : void 0;
|
|
578
|
+
if (direct && !isEmptyOutput(direct)) return direct;
|
|
579
|
+
if (fromFinalText) return fromFinalText;
|
|
580
|
+
if (direct) return direct;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
584
|
+
const event = events[i];
|
|
585
|
+
if (!event) continue;
|
|
586
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
587
|
+
const text = pickString(data.text) ?? pickString(data.delta) ?? pickString(data.finalText);
|
|
588
|
+
if (!text) continue;
|
|
589
|
+
const fenced = extractFencedJson(text);
|
|
590
|
+
if (!fenced) continue;
|
|
591
|
+
const coerced = coerceResearchOutput(fenced);
|
|
592
|
+
if (coerced) return coerced;
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
items: [],
|
|
596
|
+
citations: [],
|
|
597
|
+
proposedWrites: []
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function isRecord(value) {
|
|
601
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
602
|
+
}
|
|
603
|
+
/** A coerced output that carries no items, citations, or proposed writes. */
|
|
604
|
+
function isEmptyOutput(o) {
|
|
605
|
+
return o.items.length === 0 && o.citations.length === 0 && o.proposedWrites.length === 0;
|
|
606
|
+
}
|
|
607
|
+
function pickString(value) {
|
|
608
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
609
|
+
}
|
|
610
|
+
function extractFencedJson(text) {
|
|
611
|
+
const match = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
612
|
+
if (!match) return void 0;
|
|
613
|
+
const body = (match[1] ?? "").trim();
|
|
614
|
+
if (!body) return void 0;
|
|
615
|
+
try {
|
|
616
|
+
return JSON.parse(body);
|
|
617
|
+
} catch {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function coerceResearchOutput(value) {
|
|
622
|
+
if (!isRecord(value)) return void 0;
|
|
623
|
+
const items = coerceItems(value.items);
|
|
624
|
+
const citations = coerceCitations(value.citations);
|
|
625
|
+
const proposedWrites = coerceProposedWrites(value.proposedWrites);
|
|
626
|
+
if (items === void 0 && citations === void 0 && proposedWrites === void 0) return;
|
|
627
|
+
const output = {
|
|
628
|
+
items: items ?? [],
|
|
629
|
+
citations: citations ?? [],
|
|
630
|
+
proposedWrites: proposedWrites ?? []
|
|
631
|
+
};
|
|
632
|
+
if (Array.isArray(value.gaps)) output.gaps = value.gaps.filter((entry) => typeof entry === "string");
|
|
633
|
+
const notes = pickString(value.notes);
|
|
634
|
+
if (notes) output.notes = notes;
|
|
635
|
+
const known = /* @__PURE__ */ new Set([
|
|
636
|
+
"items",
|
|
637
|
+
"citations",
|
|
638
|
+
"proposedWrites",
|
|
639
|
+
"gaps",
|
|
640
|
+
"notes"
|
|
641
|
+
]);
|
|
642
|
+
const extras = {};
|
|
643
|
+
let extrasCount = 0;
|
|
644
|
+
for (const [key, val] of Object.entries(value)) {
|
|
645
|
+
if (known.has(key)) continue;
|
|
646
|
+
extras[key] = val;
|
|
647
|
+
extrasCount += 1;
|
|
648
|
+
}
|
|
649
|
+
if (extrasCount > 0) output.raw = extras;
|
|
650
|
+
return output;
|
|
651
|
+
}
|
|
652
|
+
function coerceItems(value) {
|
|
653
|
+
if (!Array.isArray(value)) return void 0;
|
|
654
|
+
const out = [];
|
|
655
|
+
for (const entry of value) {
|
|
656
|
+
if (!isRecord(entry)) continue;
|
|
657
|
+
const id = pickString(entry.id);
|
|
658
|
+
const namespace = pickString(entry.namespace);
|
|
659
|
+
const claim = pickString(entry.claim);
|
|
660
|
+
const evidence = coerceEvidence(entry.evidence);
|
|
661
|
+
const confidence = toFiniteNumber(entry.confidence);
|
|
662
|
+
const authoredBy = coerceAuthoredBy(entry.authoredBy);
|
|
663
|
+
if (!id || !namespace || !claim || !authoredBy) continue;
|
|
664
|
+
const item = {
|
|
665
|
+
id,
|
|
666
|
+
namespace,
|
|
667
|
+
claim,
|
|
668
|
+
evidence,
|
|
669
|
+
confidence: clamp01(confidence),
|
|
670
|
+
authoredBy
|
|
671
|
+
};
|
|
672
|
+
if (Array.isArray(entry.supersedes)) item.supersedes = entry.supersedes.filter((s) => typeof s === "string");
|
|
673
|
+
const retractedAt = toFiniteNumber(entry.retractedAt);
|
|
674
|
+
if (retractedAt > 0) item.retractedAt = retractedAt;
|
|
675
|
+
out.push(item);
|
|
676
|
+
}
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
function coerceEvidence(value) {
|
|
680
|
+
if (!Array.isArray(value)) return [];
|
|
681
|
+
const out = [];
|
|
682
|
+
for (const entry of value) {
|
|
683
|
+
if (!isRecord(entry)) continue;
|
|
684
|
+
const source = pickString(entry.source);
|
|
685
|
+
if (!source) continue;
|
|
686
|
+
const item = {
|
|
687
|
+
source,
|
|
688
|
+
capturedAt: toFiniteNumber(entry.capturedAt)
|
|
689
|
+
};
|
|
690
|
+
const quote = pickString(entry.quote);
|
|
691
|
+
if (quote) item.quote = quote;
|
|
692
|
+
const url = pickString(entry.url);
|
|
693
|
+
if (url) item.url = url;
|
|
694
|
+
out.push(item);
|
|
695
|
+
}
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
function coerceAuthoredBy(value) {
|
|
699
|
+
if (!isRecord(value)) return void 0;
|
|
700
|
+
const kind = value.kind === "human" || value.kind === "agent" ? value.kind : void 0;
|
|
701
|
+
const id = pickString(value.id);
|
|
702
|
+
if (!kind || !id) return void 0;
|
|
703
|
+
return {
|
|
704
|
+
kind,
|
|
705
|
+
id
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function coerceCitations(value) {
|
|
709
|
+
if (!Array.isArray(value)) return void 0;
|
|
710
|
+
const out = [];
|
|
711
|
+
for (const entry of value) {
|
|
712
|
+
if (!isRecord(entry)) continue;
|
|
713
|
+
const url = pickString(entry.url);
|
|
714
|
+
const quote = pickString(entry.quote);
|
|
715
|
+
if (!url || !quote) continue;
|
|
716
|
+
out.push({
|
|
717
|
+
url,
|
|
718
|
+
quote,
|
|
719
|
+
confidence: clamp01(toFiniteNumber(entry.confidence))
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
return out;
|
|
723
|
+
}
|
|
724
|
+
function coerceProposedWrites(value) {
|
|
725
|
+
if (!Array.isArray(value)) return void 0;
|
|
726
|
+
const out = [];
|
|
727
|
+
for (const entry of value) {
|
|
728
|
+
if (!isRecord(entry)) continue;
|
|
729
|
+
const namespace = pickString(entry.namespace);
|
|
730
|
+
if (!namespace) continue;
|
|
731
|
+
if (entry.kind === "insert") {
|
|
732
|
+
const item = coerceItems([entry.item])?.[0];
|
|
733
|
+
if (!item) continue;
|
|
734
|
+
out.push({
|
|
735
|
+
kind: "insert",
|
|
736
|
+
namespace,
|
|
737
|
+
item
|
|
738
|
+
});
|
|
739
|
+
} else if (entry.kind === "supersede") {
|
|
740
|
+
const previousId = pickString(entry.previousId);
|
|
741
|
+
const item = coerceItems([entry.item])?.[0];
|
|
742
|
+
if (!previousId || !item) continue;
|
|
743
|
+
out.push({
|
|
744
|
+
kind: "supersede",
|
|
745
|
+
namespace,
|
|
746
|
+
previousId,
|
|
747
|
+
item
|
|
748
|
+
});
|
|
749
|
+
} else if (entry.kind === "retract") {
|
|
750
|
+
const itemId = pickString(entry.itemId);
|
|
751
|
+
const reason = pickString(entry.reason);
|
|
752
|
+
if (!itemId || !reason) continue;
|
|
753
|
+
out.push({
|
|
754
|
+
kind: "retract",
|
|
755
|
+
namespace,
|
|
756
|
+
itemId,
|
|
757
|
+
reason
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return out;
|
|
762
|
+
}
|
|
763
|
+
function toFiniteNumber(value) {
|
|
764
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
765
|
+
}
|
|
766
|
+
function clamp01(value) {
|
|
767
|
+
if (!Number.isFinite(value)) return 0;
|
|
768
|
+
if (value < 0) return 0;
|
|
769
|
+
if (value > 1) return 1;
|
|
770
|
+
return value;
|
|
771
|
+
}
|
|
772
|
+
//#endregion
|
|
320
773
|
//#region src/profiles/ui-auditor/lens-prompts.ts
|
|
321
774
|
/** Cross-lens rules injected into every UI audit iteration: finding quality standards and scope limits. @experimental */
|
|
322
775
|
const SHARED_AUDITOR_RULES = `
|