@tangle-network/agent-app 0.44.8 → 0.44.10

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 (36) 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 +28 -5
  5. package/dist/chat-routes/index.js +45 -5
  6. package/dist/chat-routes/index.js.map +1 -1
  7. package/dist/chat-store/index.d.ts +3 -3
  8. package/dist/chat-store/index.js +2 -2
  9. package/dist/{chunk-V55WJSR4.js → chunk-5GA3MKOC.js} +2 -2
  10. package/dist/{chunk-UDSY2F6N.js → chunk-72TPRENZ.js} +21 -8
  11. package/dist/chunk-72TPRENZ.js.map +1 -0
  12. package/dist/{chunk-AFNTRJQ7.js → chunk-FEUW4G2L.js} +2 -2
  13. package/dist/{chunk-TKRI463Z.js → chunk-J6TNPRQN.js} +4 -4
  14. package/dist/{chunk-F2CBC4DY.js → chunk-V3HO43PG.js} +15 -1
  15. package/dist/chunk-V3HO43PG.js.map +1 -0
  16. package/dist/{chunk-UP33Z633.js → chunk-ZRSUCTST.js} +2 -2
  17. package/dist/{parts-Co_lFgLI.d.ts → parts-DIKcC1p6.d.ts} +1 -1
  18. package/dist/{queue-CD5-_VTX.d.ts → queue-DBkulxW1.d.ts} +1 -1
  19. package/dist/turn-health/index.d.ts +149 -13
  20. package/dist/turn-health/index.js +192 -12
  21. package/dist/turn-health/index.js.map +1 -1
  22. package/dist/{types-XIZqIdX1.d.ts → types-DeEOhQbv.d.ts} +25 -3
  23. package/dist/web-react/index.d.ts +6 -6
  24. package/dist/web-react/index.js +6 -6
  25. package/dist/work-product/index.d.ts +73 -5
  26. package/dist/work-product/index.js +131 -17
  27. package/dist/work-product/index.js.map +1 -1
  28. package/dist/work-product-react/index.d.ts +1 -1
  29. package/dist/work-product-react/index.js +3 -3
  30. package/package.json +1 -1
  31. package/dist/chunk-F2CBC4DY.js.map +0 -1
  32. package/dist/chunk-UDSY2F6N.js.map +0 -1
  33. /package/dist/{chunk-V55WJSR4.js.map → chunk-5GA3MKOC.js.map} +0 -0
  34. /package/dist/{chunk-AFNTRJQ7.js.map → chunk-FEUW4G2L.js.map} +0 -0
  35. /package/dist/{chunk-TKRI463Z.js.map → chunk-J6TNPRQN.js.map} +0 -0
  36. /package/dist/{chunk-UP33Z633.js.map → chunk-ZRSUCTST.js.map} +0 -0
@@ -38,13 +38,35 @@ interface WorkProductArtifact {
38
38
  /** The structured field map lineage targets anchor to (tax: form-line ids). */
39
39
  fields?: Record<string, unknown>;
40
40
  }
41
- /** Point into a source document: page, free-form range, verbatim quote */
41
+ /** A half-open `[start, end)` character range into the source document's text.
42
+ * Offsets index exactly the string the product's `readSourceText` seam
43
+ * returns for the same `sourceRef` — which is the string its document-reading
44
+ * tool pages with an `offset`. The PLATFORM slices `locator.quote` out of it,
45
+ * so a span citation cannot name text the document does not contain. */
46
+ interface EvidenceSpan {
47
+ start: number;
48
+ end: number;
49
+ }
50
+ /** How `locator.quote` got there. Server-set on every path — never read from
51
+ * model args, because the whole value of the distinction is that a reviewer
52
+ * can trust it:
53
+ * - `span` the platform sliced it out of the source bytes (unfalsifiable)
54
+ * - `model` the model supplied the text and the platform PROVED it occurs */
55
+ type QuoteBasis = 'span' | 'model';
56
+ /** Point into a source document: page, free-form range, span, verbatim quote */
42
57
  interface EvidenceLocator {
43
58
  page?: number;
44
59
  /** 'L120-L134' | 'B7' | '¶4' — free-form, non-empty when present. */
45
60
  range?: string;
46
- /** Verbatim supporting quote from the source. */
61
+ /** Verbatim supporting quote from the source. Model-supplied and verified,
62
+ * or platform-sliced from {@link EvidenceLocator.span} — read `quoteBasis`
63
+ * to tell which. */
47
64
  quote?: string;
65
+ /** Character range the quote is sliced from. The preferred citation form:
66
+ * two integers cannot be a fabricated quote. */
67
+ span?: EvidenceSpan;
68
+ /** Server-set provenance for `quote`; a model-supplied value is discarded. */
69
+ quoteBasis?: QuoteBasis;
48
70
  }
49
71
  /** One lineage row: a source document location supporting one artifact claim */
50
72
  interface EvidenceEntry {
@@ -265,4 +287,4 @@ declare function workProductToPersistedPart(record: WorkProductRecord): WorkProd
265
287
  /** Re-validate a stored/wire part into the typed anchor; null for junk. */
266
288
  declare function persistedPartToWorkProduct(part: Record<string, unknown>): WorkProductPersistedPart | null;
267
289
 
268
- export { type AgentCheckInput as A, type EvidenceEntry as E, type ProfileBacktestSummary as P, type QualityCheck as Q, type WorkProductRef as W, type WorkProductProvenance as a, type WorkProductRecord as b, type WorkProductPersistedPart as c, type WorkProductStorePort as d, type WorkProductAuditEvent as e, type WorkProductArtifact as f, type ExceptionEntry as g, type WorkProductStatus as h, type EvidenceLocator as i, type ExceptionSeverity as j, type WorkProductParseResult as k, type WorkProductPatch as l, type WorkProductUpdateGuard as m, type WorkProductVersionEntry as n, isWorkProductStatus as o, parseAgentCheckInput as p, parseArtifactInput as q, parseEvidenceInput as r, parseExceptionInput as s, persistedPartToWorkProduct as t, unresolvedBlockingExceptions as u, workProductToPersistedPart as w };
290
+ export { type AgentCheckInput as A, type EvidenceEntry as E, type ProfileBacktestSummary as P, type QualityCheck as Q, type WorkProductRef as W, type WorkProductProvenance as a, type WorkProductRecord as b, type WorkProductPersistedPart as c, type WorkProductStorePort as d, type WorkProductAuditEvent as e, type WorkProductArtifact as f, type ExceptionEntry as g, type WorkProductStatus as h, type EvidenceLocator as i, type EvidenceSpan as j, type ExceptionSeverity as k, type QuoteBasis as l, type WorkProductParseResult as m, type WorkProductPatch as n, type WorkProductUpdateGuard as o, type WorkProductVersionEntry as p, isWorkProductStatus as q, parseAgentCheckInput as r, parseArtifactInput as s, parseEvidenceInput as t, parseExceptionInput as u, persistedPartToWorkProduct as v, unresolvedBlockingExceptions as w, workProductToPersistedPart as x };
@@ -5,17 +5,17 @@ export { f as ChatFreeTextField, g as ComposerAnswerDelivery, h as INTERACTION_C
5
5
  import { ChatPlan } from '../plans/index.js';
6
6
  import { InteractionData } from '@tangle-network/agent-interface';
7
7
  export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
8
- import { M as FileMention, g as ChatMentionPart, b as ChatAttachmentPart, a as ChatAttachmentKind, Q as ChatAttachmentInput } from '../parts-Co_lFgLI.js';
9
- export { f as ChatMentionKind, P as ChatTurnFilePartInput, O as ChatTurnPartInput, N as ChatTurnRequestPayload, U as DISPATCH_MAX_MEDIA_PARTS, V as DISPATCH_MAX_PARTS, W as DISPATCH_REQUEST_MAX_BYTES, X as DISPATCH_STRUCTURAL_RESERVE_BYTES, $ as ProducerErrorEvent, a0 as ProducerNoticeEvent, a1 as ProducerPassthroughEvent, a2 as ProducerPassthroughEventType, a3 as ProducerReasoningEvent, a4 as ProducerTextEvent, a5 as ProducerToolCallEvent, a6 as ProducerToolResultEvent, a7 as ProducerUsageEvent, a8 as ProducerWireEvent, u as attachmentInputToPart, v as attachmentKindForMime, w as attachmentPartsFromMessageParts, ab as base64WireLen, ac as buildMentionPromptBlock, ad as chatTurnRequestInit, ae as fileMentionsToParts, z as isChatAttachmentPart, ag as mediaTypeForMentionPath, J as mentionInputToPart, ah as mentionKindForPath, K as mentionPartsFromMessageParts } from '../parts-Co_lFgLI.js';
10
- import { E as EvidenceEntry, g as ExceptionEntry, a as WorkProductProvenance, P as ProfileBacktestSummary, Q as QualityCheck, c as WorkProductPersistedPart, h as WorkProductStatus } from '../types-XIZqIdX1.js';
8
+ import { M as FileMention, g as ChatMentionPart, b as ChatAttachmentPart, a as ChatAttachmentKind, Q as ChatAttachmentInput } from '../parts-DIKcC1p6.js';
9
+ export { f as ChatMentionKind, P as ChatTurnFilePartInput, O as ChatTurnPartInput, N as ChatTurnRequestPayload, U as DISPATCH_MAX_MEDIA_PARTS, V as DISPATCH_MAX_PARTS, W as DISPATCH_REQUEST_MAX_BYTES, X as DISPATCH_STRUCTURAL_RESERVE_BYTES, $ as ProducerErrorEvent, a0 as ProducerNoticeEvent, a1 as ProducerPassthroughEvent, a2 as ProducerPassthroughEventType, a3 as ProducerReasoningEvent, a4 as ProducerTextEvent, a5 as ProducerToolCallEvent, a6 as ProducerToolResultEvent, a7 as ProducerUsageEvent, a8 as ProducerWireEvent, u as attachmentInputToPart, v as attachmentKindForMime, w as attachmentPartsFromMessageParts, ab as base64WireLen, ac as buildMentionPromptBlock, ad as chatTurnRequestInit, ae as fileMentionsToParts, z as isChatAttachmentPart, ag as mediaTypeForMentionPath, J as mentionInputToPart, ah as mentionKindForPath, K as mentionPartsFromMessageParts } from '../parts-DIKcC1p6.js';
10
+ import { E as EvidenceEntry, g as ExceptionEntry, a as WorkProductProvenance, P as ProfileBacktestSummary, Q as QualityCheck, c as WorkProductPersistedPart, h as WorkProductStatus } from '../types-DeEOhQbv.js';
11
11
  import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
12
12
  import { F as FlowTrace } from '../flow-types-CJxEmaRy.js';
13
- import { a as ReviewQueueItem, c as ReviewQueueState } from '../queue-CD5-_VTX.js';
14
- export { p as parseReviewQueueItem } from '../queue-CD5-_VTX.js';
13
+ import { a as ReviewQueueItem, c as ReviewQueueState } from '../queue-DBkulxW1.js';
14
+ export { p as parseReviewQueueItem } from '../queue-DBkulxW1.js';
15
15
  export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-ChNEdHF8.js';
16
16
  import { CatalogModel } from '../catalog/index.js';
17
17
  import { Harness } from '../harness/index.js';
18
- export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-DRuLEjAE.js';
18
+ export { a as ATTACHMENT_ACCEPT, e as FileIndexReadyResponse, f as FileIndexResponse, g as FileIndexWarmingResponse } from '../attachment-validation-yJ6Id4pR.js';
19
19
  export { a as attachmentPartKey } from '../stream-normalizer-DnuqkZvw.js';
20
20
 
21
21
  /** Represent durable plan decisions as either approved or rejected */
@@ -74,7 +74,7 @@ import {
74
74
  useSmoothText,
75
75
  useThinkingSeconds,
76
76
  waterfallLayout
77
- } from "../chunk-TKRI463Z.js";
77
+ } from "../chunk-J6TNPRQN.js";
78
78
  import "../chunk-FBVLEGEG.js";
79
79
  import {
80
80
  EvidenceLineageTable,
@@ -87,17 +87,17 @@ import {
87
87
  reviewQueueStateLabel,
88
88
  workProductPartsFromMessageParts,
89
89
  workProductStatusLabel
90
- } from "../chunk-UDSY2F6N.js";
90
+ } from "../chunk-72TPRENZ.js";
91
91
  import {
92
92
  parseReviewQueueItem
93
- } from "../chunk-UP33Z633.js";
93
+ } from "../chunk-ZRSUCTST.js";
94
94
  import {
95
95
  tabTerminalConnectionId,
96
96
  useSandboxTerminalConnection
97
97
  } from "../chunk-HCOROIRT.js";
98
98
  import {
99
99
  ATTACHMENT_ACCEPT
100
- } from "../chunk-V55WJSR4.js";
100
+ } from "../chunk-5GA3MKOC.js";
101
101
  import {
102
102
  DISPATCH_MAX_MEDIA_PARTS,
103
103
  DISPATCH_MAX_PARTS,
@@ -115,8 +115,8 @@ import {
115
115
  mentionInputToPart,
116
116
  mentionKindForPath,
117
117
  mentionPartsFromMessageParts
118
- } from "../chunk-AFNTRJQ7.js";
119
- import "../chunk-F2CBC4DY.js";
118
+ } from "../chunk-FEUW4G2L.js";
119
+ import "../chunk-V3HO43PG.js";
120
120
  import {
121
121
  attachmentPartKey
122
122
  } from "../chunk-5EPIPT4V.js";
@@ -1,9 +1,9 @@
1
- import { a as WorkProductProvenance, d as WorkProductStorePort, e as WorkProductAuditEvent, b as WorkProductRecord, f as WorkProductArtifact, Q as QualityCheck, E as EvidenceEntry, g as ExceptionEntry, h as WorkProductStatus, c as WorkProductPersistedPart } from '../types-XIZqIdX1.js';
2
- export { A as AgentCheckInput, i as EvidenceLocator, j as ExceptionSeverity, P as ProfileBacktestSummary, k as WorkProductParseResult, l as WorkProductPatch, W as WorkProductRef, m as WorkProductUpdateGuard, n as WorkProductVersionEntry, o as isWorkProductStatus, p as parseAgentCheckInput, q as parseArtifactInput, r as parseEvidenceInput, s as parseExceptionInput, t as persistedPartToWorkProduct, u as unresolvedBlockingExceptions, w as workProductToPersistedPart } from '../types-XIZqIdX1.js';
1
+ import { a as WorkProductProvenance, d as WorkProductStorePort, e as WorkProductAuditEvent, b as WorkProductRecord, f as WorkProductArtifact, Q as QualityCheck, E as EvidenceEntry, g as ExceptionEntry, h as WorkProductStatus, c as WorkProductPersistedPart } from '../types-DeEOhQbv.js';
2
+ export { A as AgentCheckInput, i as EvidenceLocator, j as EvidenceSpan, k as ExceptionSeverity, P as ProfileBacktestSummary, l as QuoteBasis, m as WorkProductParseResult, n as WorkProductPatch, W as WorkProductRef, o as WorkProductUpdateGuard, p as WorkProductVersionEntry, q as isWorkProductStatus, r as parseAgentCheckInput, s as parseArtifactInput, t as parseEvidenceInput, u as parseExceptionInput, v as persistedPartToWorkProduct, w as unresolvedBlockingExceptions, x as workProductToPersistedPart } from '../types-DeEOhQbv.js';
3
3
  import { a as AppToolContext, c as AppToolDefinition } from '../types-BCxK0wyS.js';
4
4
  import { JudgeVerdict } from '@tangle-network/agent-eval';
5
5
  import { T as TrustItem } from '../trust-gate-Dcm5xSva.js';
6
- export { R as ReviewQueueInputs, a as ReviewQueueItem, b as ReviewQueuePendingAsk, c as ReviewQueueState, d as ReviewQueueThread, p as parseReviewQueueItem, e as projectReviewQueue } from '../queue-CD5-_VTX.js';
6
+ export { R as ReviewQueueInputs, a as ReviewQueueItem, b as ReviewQueuePendingAsk, c as ReviewQueueState, d as ReviewQueueThread, p as parseReviewQueueItem, e as projectReviewQueue } from '../queue-DBkulxW1.js';
7
7
 
8
8
  /**
9
9
  * The guarded work-product status machine — the `/missions` service PATTERN
@@ -242,8 +242,22 @@ interface WorkProductToolConfig {
242
242
  * Omit the seam entirely and no quote is checked. */
243
243
  readSourceText?: (ref: string, ctx: AppToolContext) => Promise<string | null>;
244
244
  /** The material targets the platform coverage check requires evidence for.
245
- * Product-owned vocabulary; omit to skip the coverage gate. */
245
+ * Product-owned vocabulary; omit to skip the coverage gate.
246
+ *
247
+ * Return ONLY targets a source document can actually evidence. A gate that
248
+ * demands document lineage for a value the session COMPUTED is not
249
+ * satisfiable by any honest answer, and an unsatisfiable gate does not stop
250
+ * a submit — it selects for an invented one. Computed values belong to a
251
+ * product's own computation check, where "matches what we already
252
+ * calculated" is satisfiable by construction. */
246
253
  materialTargets?: (artifact: WorkProductArtifact) => string[];
254
+ /** Require every material target to carry a SOURCE ANCHOR — a span-sliced or
255
+ * verified quote — not merely an evidence row. Off by default (a bare
256
+ * `claim` is legitimate lineage for products with no readable sources);
257
+ * turn it ON once `readSourceText` is wired and `materialTargets` names only
258
+ * document-derived targets, and coverage stops being satisfiable by
259
+ * assertion. */
260
+ requireAnchoredEvidence?: boolean;
247
261
  /** Per-turn provenance closure the ROUTE supplies (profileHash + runId are
248
262
  * known at dispatch; trusted, never read from model args). */
249
263
  provenance: (ctx: AppToolContext) => WorkProductProvenanceBase;
@@ -292,6 +306,60 @@ declare function normalizeQuoteText(value: string): string;
292
306
  * substring of every document and pass the gate vacuously.
293
307
  */
294
308
  declare function sourceContainsQuote(sourceText: string, quote: string): boolean;
309
+ /**
310
+ * Slice a citation out of the source text by character offset — the reason
311
+ * this module exists in its stronger form.
312
+ *
313
+ * Verification above is a REJECTION gate: the model retypes a quote and the
314
+ * shell refuses it when the characters do not occur. That gate is correct and
315
+ * it works, but a model that reproduces a line character-for-character only
316
+ * some of the time cannot USE it — every miss is a refusal, and the package
317
+ * ends up with no lineage at all rather than false lineage. A measured 9 of 59
318
+ * on the live tax surface is what "some of the time" meant in practice.
319
+ *
320
+ * A span inverts it. The model names two integers into text it just read; the
321
+ * PLATFORM produces the quote from the bytes it already holds. There is no
322
+ * retyping step to get wrong, so a fabricated quote is not rejected — it is
323
+ * unrepresentable. `sourceContainsQuote(text, sliceSourceSpan(text, span))` is
324
+ * true for every span this function returns, by construction.
325
+ *
326
+ * Offsets index the SAME string the product's document-reading tool pages
327
+ * with an offset, which is the same string its `readSourceText` seam returns.
328
+ * That is the one contract a product must keep; violate it and spans point at
329
+ * the wrong characters (still real characters of that document — never
330
+ * invented text, but the wrong line).
331
+ *
332
+ * Half-open `[start, end)`, matching `String.prototype.slice` and the
333
+ * `offset`/`offset + text.length` window a paged read already reports.
334
+ */
335
+ type SourceSpanFailure = {
336
+ reason: 'not_integer';
337
+ field: 'start' | 'end';
338
+ } | {
339
+ reason: 'negative';
340
+ field: 'start' | 'end';
341
+ } | {
342
+ reason: 'inverted';
343
+ } | {
344
+ reason: 'out_of_range';
345
+ totalChars: number;
346
+ } | {
347
+ reason: 'blank';
348
+ };
349
+ type SourceSpanResult = {
350
+ ok: true;
351
+ quote: string;
352
+ } | {
353
+ ok: false;
354
+ failure: SourceSpanFailure;
355
+ };
356
+ /** Resolve `[start, end)` against `sourceText`. Every rejection is a caller
357
+ * mistake the model can correct from the paged read it already has, so each
358
+ * carries the discriminator a tool layer turns into a specific message. */
359
+ declare function sliceSourceSpan(sourceText: string, span: {
360
+ start: number;
361
+ end: number;
362
+ }): SourceSpanResult;
295
363
 
296
364
  /**
297
365
  * Framework-neutral work-product review endpoints — the
@@ -382,4 +450,4 @@ interface WorkProductRoutes {
382
450
  /** Create the work-product review endpoints over the store port and product seams */
383
451
  declare function createWorkProductRoutes(options: WorkProductRoutesOptions): WorkProductRoutes;
384
452
 
385
- export { type CreateWorkProductInput, EVIDENCE_COVERAGE_CHECK, EvidenceEntry, ExceptionEntry, type FinalizeWorkProductProvenanceInput, type InMemoryWorkProductStore, MAX_WORK_PRODUCT_BATCH, QUOTE_VERIFICATION_CHECK, QualityCheck, type SubmitWorkProductInput, TrustItem, WorkProductArtifact, WorkProductAuditEvent, type WorkProductAuthorizeArgs, type WorkProductOutcome, WorkProductPersistedPart, WorkProductProvenance, type WorkProductProvenanceBase, WorkProductRecord, type WorkProductRouteAuthorization, type WorkProductRoutes, type WorkProductRoutesOptions, type WorkProductService, type WorkProductServiceOptions, WorkProductStatus, WorkProductStorePort, type WorkProductToolConfig, type WorkProductVerdictBody, type WorkProductVerdictInput, buildWorkProductTools, canTransitionWorkProduct, createInMemoryWorkProductStore, createWorkProductRoutes, createWorkProductService, finalizeWorkProductProvenance, isWorkProductTerminal, normalizeQuoteText, sourceContainsQuote, stampProvenance, validateWorkProductVerdictBody, workProductTrustInputs };
453
+ export { type CreateWorkProductInput, EVIDENCE_COVERAGE_CHECK, EvidenceEntry, ExceptionEntry, type FinalizeWorkProductProvenanceInput, type InMemoryWorkProductStore, MAX_WORK_PRODUCT_BATCH, QUOTE_VERIFICATION_CHECK, QualityCheck, type SourceSpanFailure, type SourceSpanResult, type SubmitWorkProductInput, TrustItem, WorkProductArtifact, WorkProductAuditEvent, type WorkProductAuthorizeArgs, type WorkProductOutcome, WorkProductPersistedPart, WorkProductProvenance, type WorkProductProvenanceBase, WorkProductRecord, type WorkProductRouteAuthorization, type WorkProductRoutes, type WorkProductRoutesOptions, type WorkProductService, type WorkProductServiceOptions, WorkProductStatus, WorkProductStorePort, type WorkProductToolConfig, type WorkProductVerdictBody, type WorkProductVerdictInput, buildWorkProductTools, canTransitionWorkProduct, createInMemoryWorkProductStore, createWorkProductRoutes, createWorkProductService, finalizeWorkProductProvenance, isWorkProductTerminal, normalizeQuoteText, sliceSourceSpan, sourceContainsQuote, stampProvenance, validateWorkProductVerdictBody, workProductTrustInputs };
@@ -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,