@tangle-network/agent-app 0.44.7 → 0.44.9

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.
Files changed (35) hide show
  1. package/dist/assistant/index.d.ts +4 -4
  2. package/dist/assistant/index.js +6 -6
  3. package/dist/{attachment-validation-DRuLEjAE.d.ts → attachment-validation-yJ6Id4pR.d.ts} +1 -1
  4. package/dist/chat-routes/index.d.ts +4 -4
  5. package/dist/chat-routes/index.js +3 -3
  6. package/dist/chat-store/index.d.ts +3 -3
  7. package/dist/chat-store/index.js +2 -2
  8. package/dist/{chunk-V55WJSR4.js → chunk-5GA3MKOC.js} +2 -2
  9. package/dist/{chunk-UDSY2F6N.js → chunk-72TPRENZ.js} +21 -8
  10. package/dist/chunk-72TPRENZ.js.map +1 -0
  11. package/dist/{chunk-AFNTRJQ7.js → chunk-FEUW4G2L.js} +2 -2
  12. package/dist/{chunk-TKRI463Z.js → chunk-J6TNPRQN.js} +4 -4
  13. package/dist/{chunk-F2CBC4DY.js → chunk-V3HO43PG.js} +15 -1
  14. package/dist/chunk-V3HO43PG.js.map +1 -0
  15. package/dist/{chunk-UP33Z633.js → chunk-ZRSUCTST.js} +2 -2
  16. package/dist/{parts-Co_lFgLI.d.ts → parts-DIKcC1p6.d.ts} +1 -1
  17. package/dist/{queue-CD5-_VTX.d.ts → queue-DBkulxW1.d.ts} +1 -1
  18. package/dist/turn-health/index.d.ts +57 -1
  19. package/dist/turn-health/index.js +14 -4
  20. package/dist/turn-health/index.js.map +1 -1
  21. package/dist/{types-XIZqIdX1.d.ts → types-DeEOhQbv.d.ts} +25 -3
  22. package/dist/web-react/index.d.ts +6 -6
  23. package/dist/web-react/index.js +6 -6
  24. package/dist/work-product/index.d.ts +73 -5
  25. package/dist/work-product/index.js +131 -17
  26. package/dist/work-product/index.js.map +1 -1
  27. package/dist/work-product-react/index.d.ts +1 -1
  28. package/dist/work-product-react/index.js +3 -3
  29. package/package.json +1 -1
  30. package/dist/chunk-F2CBC4DY.js.map +0 -1
  31. package/dist/chunk-UDSY2F6N.js.map +0 -1
  32. /package/dist/{chunk-V55WJSR4.js.map → chunk-5GA3MKOC.js.map} +0 -0
  33. /package/dist/{chunk-AFNTRJQ7.js.map → chunk-FEUW4G2L.js.map} +0 -0
  34. /package/dist/{chunk-TKRI463Z.js.map → chunk-J6TNPRQN.js.map} +0 -0
  35. /package/dist/{chunk-UP33Z633.js.map → chunk-ZRSUCTST.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseReviewQueueItem,
3
3
  projectReviewQueue
4
- } from "../chunk-UP33Z633.js";
4
+ } from "../chunk-ZRSUCTST.js";
5
5
  import {
6
6
  isWorkProductStatus,
7
7
  parseAgentCheckInput,
@@ -11,7 +11,7 @@ import {
11
11
  persistedPartToWorkProduct,
12
12
  unresolvedBlockingExceptions,
13
13
  workProductToPersistedPart
14
- } from "../chunk-F2CBC4DY.js";
14
+ } from "../chunk-V3HO43PG.js";
15
15
  import {
16
16
  ToolInputError,
17
17
  defineAppTool
@@ -403,6 +403,20 @@ function sourceContainsQuote(sourceText, quote) {
403
403
  if (normalizedQuote.length === 0) return false;
404
404
  return normalizeQuoteText(sourceText).includes(normalizedQuote);
405
405
  }
406
+ function sliceSourceSpan(sourceText, span) {
407
+ for (const field of ["start", "end"]) {
408
+ const value = span[field];
409
+ if (!Number.isInteger(value)) return { ok: false, failure: { reason: "not_integer", field } };
410
+ if (value < 0) return { ok: false, failure: { reason: "negative", field } };
411
+ }
412
+ if (span.end <= span.start) return { ok: false, failure: { reason: "inverted" } };
413
+ if (span.end > sourceText.length) {
414
+ return { ok: false, failure: { reason: "out_of_range", totalChars: sourceText.length } };
415
+ }
416
+ const quote = sourceText.slice(span.start, span.end);
417
+ if (quote.trim().length === 0) return { ok: false, failure: { reason: "blank" } };
418
+ return { ok: true, quote };
419
+ }
406
420
 
407
421
  // src/work-product/tools.ts
408
422
  var MAX_WORK_PRODUCT_BATCH = 50;
@@ -452,17 +466,61 @@ async function resolveDraft(service, config, scopeKey, ctx) {
452
466
  provenance: stampProvenance(config.provenance(ctx), config.now)
453
467
  });
454
468
  }
455
- async function assertQuotesOccurInSources(config, entries, ctx) {
469
+ function spanErrorDetail(failure) {
470
+ switch (failure.reason) {
471
+ case "not_integer":
472
+ return `${failure.field} must be a whole character offset.`;
473
+ case "negative":
474
+ return `${failure.field} must not be negative.`;
475
+ case "inverted":
476
+ return "end must be greater than start \u2014 the range is half-open [start, end).";
477
+ case "out_of_range":
478
+ return `end is past the end of the document, which is ${failure.totalChars} characters. Offsets are absolute in the whole document: when you read with an offset, add that offset to the position within the returned text.`;
479
+ case "blank":
480
+ return "that range is only whitespace. Widen it to the characters that carry the value.";
481
+ }
482
+ }
483
+ async function resolveEvidenceQuotes(config, entries, ctx) {
456
484
  const readSourceText = config.readSourceText;
457
- if (!readSourceText) return;
458
485
  const texts = /* @__PURE__ */ new Map();
486
+ const readText = async (ref) => {
487
+ if (!texts.has(ref)) texts.set(ref, await readSourceText(ref, ctx));
488
+ return texts.get(ref) ?? null;
489
+ };
459
490
  for (let index = 0; index < entries.length; index += 1) {
460
491
  const entry = entries[index];
492
+ const span = entry.locator.span;
461
493
  const quote = entry.locator.quote;
494
+ if (span) {
495
+ if (!readSourceText) {
496
+ throw new ToolInputError(
497
+ "span_unsupported",
498
+ `entries[${index}].locator.span: this deployment cannot read source text, so a span cannot be resolved into a quote. Record the entry without locator.span.`,
499
+ 500
500
+ );
501
+ }
502
+ const text2 = await readText(entry.sourceRef);
503
+ if (text2 === null) {
504
+ throw new ToolInputError(
505
+ "unverifiable_quote",
506
+ `entries[${index}].locator.span: "${entry.sourceRef}" has no readable text, so the span cannot be resolved. Record the entry without locator.span or locator.quote and state the basis in claim.`
507
+ );
508
+ }
509
+ const sliced = sliceSourceSpan(text2, span);
510
+ if (!sliced.ok) {
511
+ throw new ToolInputError(
512
+ "invalid_span",
513
+ `entries[${index}].locator.span [${span.start}, ${span.end}) into "${entry.sourceRef}": ${spanErrorDetail(sliced.failure)}`
514
+ );
515
+ }
516
+ entry.locator.quote = sliced.quote;
517
+ entry.locator.quoteBasis = "span";
518
+ continue;
519
+ }
462
520
  if (quote === void 0 || quote.trim().length === 0) continue;
463
- if (!texts.has(entry.sourceRef)) texts.set(entry.sourceRef, await readSourceText(entry.sourceRef, ctx));
464
- const text = texts.get(entry.sourceRef);
465
- if (text === null || text === void 0) {
521
+ if (!readSourceText) continue;
522
+ const text = await readText(entry.sourceRef);
523
+ if (text === null) {
466
524
  throw new ToolInputError(
467
525
  "unverifiable_quote",
468
526
  `entries[${index}].locator.quote: "${entry.sourceRef}" has no readable text, so the quote cannot be verified. Record the entry without locator.quote and state the basis in claim.`
@@ -471,9 +529,10 @@ async function assertQuotesOccurInSources(config, entries, ctx) {
471
529
  if (!sourceContainsQuote(text, quote)) {
472
530
  throw new ToolInputError(
473
531
  "quote_not_found",
474
- `entries[${index}].locator.quote: ${JSON.stringify(quote)} does not occur in "${entry.sourceRef}". Copy the supporting line character-for-character out of that document, or \u2014 if this value is COMPUTED rather than read \u2014 omit locator.quote and state the computation in claim.`
532
+ `entries[${index}].locator.quote: ${JSON.stringify(quote)} does not occur in "${entry.sourceRef}". Cite locator.span {start, end} instead \u2014 the character range you read it at, which this platform slices out of the document itself so the text is right by construction. If the value is COMPUTED rather than read, omit both and state the computation in claim.`
475
533
  );
476
534
  }
535
+ entry.locator.quoteBasis = "model";
477
536
  }
478
537
  }
479
538
  async function summarizeQuoteVerification(config, evidence, ctx) {
@@ -481,6 +540,7 @@ async function summarizeQuoteVerification(config, evidence, ctx) {
481
540
  if (!readSourceText) return void 0;
482
541
  const texts = /* @__PURE__ */ new Map();
483
542
  let verified = 0;
543
+ let spanAnchored = 0;
484
544
  let withoutQuote = 0;
485
545
  const failed = [];
486
546
  for (const entry of evidence) {
@@ -491,10 +551,23 @@ async function summarizeQuoteVerification(config, evidence, ctx) {
491
551
  }
492
552
  if (!texts.has(entry.sourceRef)) texts.set(entry.sourceRef, await readSourceText(entry.sourceRef, ctx));
493
553
  const text = texts.get(entry.sourceRef);
494
- if (typeof text === "string" && sourceContainsQuote(text, quote)) verified += 1;
554
+ if (typeof text !== "string") {
555
+ failed.push(entry.id);
556
+ continue;
557
+ }
558
+ const span = entry.locator.span;
559
+ if (span) {
560
+ const sliced = sliceSourceSpan(text, span);
561
+ if (sliced.ok && sliced.quote === quote) {
562
+ verified += 1;
563
+ spanAnchored += 1;
564
+ } else failed.push(entry.id);
565
+ continue;
566
+ }
567
+ if (sourceContainsQuote(text, quote)) verified += 1;
495
568
  else failed.push(entry.id);
496
569
  }
497
- return { verified, withoutQuote, failed };
570
+ return { verified, spanAnchored, withoutQuote, failed };
498
571
  }
499
572
  function buildWorkProductTools(config) {
500
573
  const service = createWorkProductService({
@@ -504,7 +577,7 @@ function buildWorkProductTools(config) {
504
577
  });
505
578
  const upsertEvidence = defineAppTool({
506
579
  name: "upsert_evidence",
507
- description: "Record source\u2192field lineage for the current work product, incrementally as you find it. Each entry links a source document (sourceRef + locator) to one artifact target and states the claim it supports. Re-emitting an entry id replaces that entry. Address the work product by scopeKey; the first call creates the draft.",
580
+ description: "Record source\u2192field lineage for the current work product, incrementally as you find it. Each entry links a source document (sourceRef + locator) to one artifact target and states the claim it supports. Cite by locator.span {start, end} \u2014 the character range you read the value at \u2014 and the platform slices the supporting quote out of the document for you. Re-emitting an entry id replaces that entry. Address the work product by scopeKey; the first call creates the draft.",
508
581
  parameters: {
509
582
  type: "object",
510
583
  properties: {
@@ -523,9 +596,18 @@ function buildWorkProductTools(config) {
523
596
  properties: {
524
597
  page: { type: "number" },
525
598
  range: { type: "string", description: "Free-form location: 'L120-L134' | 'B7' | '\xB64'." },
599
+ span: {
600
+ type: "object",
601
+ description: "PREFERRED. The character range you read the value at, as absolute offsets into the whole document: start is the first character, end is one past the last. If you read with an offset, add that offset to the position within the text you got back. The platform slices the quote out of the document itself and stores it, and returns it to you \u2014 you never retype source text, so the citation cannot be wrong. Use this whenever the value was READ from a document.",
602
+ properties: {
603
+ start: { type: "integer", minimum: 0 },
604
+ end: { type: "integer", minimum: 1 }
605
+ },
606
+ required: ["start", "end"]
607
+ },
526
608
  quote: {
527
609
  type: "string",
528
- description: "Verbatim supporting quote \u2014 copied character-for-character out of the source, not retyped, reformatted, or summarised. The platform checks it occurs in the document and REFUSES the entry if it does not. For a value you COMPUTED rather than read, omit this field and state the computation in claim."
610
+ description: "Fallback for sources that cannot give you character offsets: the supporting text copied character-for-character. The platform checks it occurs in the document and REFUSES the entry if it does not. Prefer span. For a value you COMPUTED rather than read, omit both and state the computation in claim."
529
611
  }
530
612
  }
531
613
  },
@@ -557,10 +639,24 @@ function buildWorkProductTools(config) {
557
639
  );
558
640
  }
559
641
  }
560
- await assertQuotesOccurInSources(config, entries, ctx);
642
+ await resolveEvidenceQuotes(config, entries, ctx);
561
643
  const draft = await resolveDraft(service, config, scopeKey, ctx);
562
644
  const record = await unwrap(() => service.upsertEvidence(draft.id, entries), "evidence_rejected");
563
- return { workProductId: record.id, version: record.version, evidenceCount: record.evidence.length };
645
+ return {
646
+ workProductId: record.id,
647
+ version: record.version,
648
+ evidenceCount: record.evidence.length,
649
+ // Echo what was actually STORED for the entries in this call. A span
650
+ // citation's quote is produced here, not sent here, so the model must
651
+ // be able to see the text its offsets selected — that is how it
652
+ // notices an off-by-a-line span without a second read.
653
+ entries: entries.map((entry) => ({
654
+ id: entry.id,
655
+ target: entry.target,
656
+ ...entry.locator.quote === void 0 ? {} : { quote: entry.locator.quote },
657
+ ...entry.locator.quoteBasis === void 0 ? {} : { quoteBasis: entry.locator.quoteBasis }
658
+ }))
659
+ };
564
660
  }
565
661
  });
566
662
  const flagException = defineAppTool({
@@ -692,7 +788,7 @@ function buildWorkProductTools(config) {
692
788
  id: QUOTE_VERIFICATION_CHECK,
693
789
  name: QUOTE_VERIFICATION_CHECK,
694
790
  passed: quotes.failed.length === 0,
695
- detail: quotes.failed.length === 0 ? `${quotes.verified}/${quoted} quoted evidence entries verified against their source; ${quotes.withoutQuote} recorded without a quote` : `Unverifiable quotes on: ${quotes.failed.join(", ")}`,
791
+ detail: quotes.failed.length === 0 ? `${quotes.verified}/${quoted} quoted evidence entries verified against their source (${quotes.spanAnchored} platform-sliced from a source span, ${quotes.verified - quotes.spanAnchored} model-quoted and proved to occur); ${quotes.withoutQuote} recorded without a quote` : `Unverifiable quotes on: ${quotes.failed.join(", ")}`,
696
792
  source: "platform"
697
793
  });
698
794
  if (quotes.failed.length > 0) {
@@ -707,11 +803,21 @@ function buildWorkProductTools(config) {
707
803
  const targets = config.materialTargets(artifact);
708
804
  const covered = new Set(draft.evidence.map((entry) => entry.target));
709
805
  const missing = targets.filter((target) => !covered.has(target));
806
+ const anchoredTargets = new Set(
807
+ draft.evidence.filter((entry) => entry.locator.quoteBasis !== void 0).map((entry) => entry.target)
808
+ );
809
+ const spanTargets = new Set(
810
+ draft.evidence.filter((entry) => entry.locator.quoteBasis === "span").map((entry) => entry.target)
811
+ );
812
+ const present = targets.filter((target) => covered.has(target));
813
+ const unanchored = present.filter((target) => !anchoredTargets.has(target));
814
+ const spanCount = present.filter((target) => spanTargets.has(target)).length;
815
+ const breakdown = `${spanCount} span-anchored, ${present.length - spanCount - unanchored.length} quote-verified, ${unanchored.length} claim-only`;
710
816
  const coverage = {
711
817
  id: EVIDENCE_COVERAGE_CHECK,
712
818
  name: EVIDENCE_COVERAGE_CHECK,
713
- passed: missing.length === 0,
714
- detail: missing.length === 0 ? `${targets.length}/${targets.length} material targets evidenced` : `Missing evidence for: ${missing.join(", ")}`,
819
+ passed: missing.length === 0 && !(config.requireAnchoredEvidence && unanchored.length > 0),
820
+ detail: missing.length > 0 ? `Missing evidence for: ${missing.join(", ")}` : config.requireAnchoredEvidence && unanchored.length > 0 ? `No source anchor for: ${unanchored.join(", ")}` : `${targets.length}/${targets.length} material targets evidenced (${breakdown})`,
715
821
  source: "platform"
716
822
  };
717
823
  checks.unshift(coverage);
@@ -722,6 +828,13 @@ function buildWorkProductTools(config) {
722
828
  `Cannot submit: material targets lack evidence: ${missing.join(", ")}. Add upsert_evidence entries targeting each, then resubmit.`
723
829
  );
724
830
  }
831
+ if (config.requireAnchoredEvidence && unanchored.length > 0) {
832
+ await unwrap(() => service.recordChecks(draft.id, checks), "checks_rejected");
833
+ throw new ToolInputError(
834
+ "evidence_not_anchored",
835
+ `Cannot submit: these material targets have an evidence row but no source anchor: ${unanchored.join(", ")}. Re-emit each with locator.span {start, end} \u2014 the character range in the document you read the value at. The platform slices the quote out of the document itself, so you never retype source text.`
836
+ );
837
+ }
725
838
  }
726
839
  const provenance = stampProvenance(config.provenance(ctx), config.now);
727
840
  const record = await unwrap(
@@ -851,6 +964,7 @@ export {
851
964
  parseReviewQueueItem,
852
965
  persistedPartToWorkProduct,
853
966
  projectReviewQueue,
967
+ sliceSourceSpan,
854
968
  sourceContainsQuote,
855
969
  stampProvenance,
856
970
  unresolvedBlockingExceptions,