@productbrain/mcp 0.0.1-beta.10771 → 0.0.1-beta.10786

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.
@@ -625,98 +625,80 @@ async function foldWhyIntoData(slug, why, userData) {
625
625
  // src/tools/smart-capture.ts
626
626
  import { z as z4 } from "zod/v3";
627
627
 
628
+ // src/lib/statusCategories.ts
629
+ var STATUS_CATEGORIES = ["draft", "current", "past", "discarded"];
630
+ var STATUS_CATEGORY_SET = new Set(STATUS_CATEGORIES);
631
+ var TRUST_ACCEPTED_WORDS = ["accepted", "active", "current"];
632
+ function isTrustAccepted(status2) {
633
+ return TRUST_ACCEPTED_WORDS.includes(status2);
634
+ }
635
+ var TRUST_ARCHIVED_WORDS = ["archived", "deprecated", "past", "discarded"];
636
+ var ENTRY_STATUS_READ_WORDS = ["draft", ...TRUST_ACCEPTED_WORDS, ...TRUST_ARCHIVED_WORDS];
637
+ var ENTRY_STATUS_WRITE_WORDS = ["draft", "archived"];
638
+
628
639
  // src/lib/captureTimeoutOutcome.ts
629
640
  function isGatewayTimeout(error) {
630
641
  return error?.name === "GatewayTimeoutError";
631
642
  }
632
- function buildCaptureOutcomeUnknownResult(error, name, msg) {
633
- if (!error.mayHaveLanded) return buildDryRunTimeoutResult(error, msg);
634
- const recovery = `Search for "${name}" before retrying \u2014 a retry will duplicate it if the write landed. If found, read back its commit status and coaching output before assuming either is missing.`;
643
+ function buildCaptureOutcomeUnknownResult(error, msg) {
644
+ const recovery = error.mayHaveLanded ? "Re-send the same capture; the kernel replays it if the first one landed, so it never writes twice." : "Re-run the preview; nothing was written.";
635
645
  return {
636
646
  content: [{
637
647
  type: "text",
638
- text: `# Capture Outcome Unknown \u2014 Not Failed
648
+ text: `# Capture Timed Out
639
649
 
640
650
  ${msg}
641
651
 
642
- **What is known:** the request reached the server and this client gave up waiting after ${error.elapsedMs}ms. The entry **may or may not** have been written.
643
-
644
- **What definitely did NOT run:** relation linking \u2014 a separate mutation issued after the create returns, so it is never reached when create itself times out.
645
-
646
- **What may also have run \u2014 outcome unknown, same reason as the entry itself:** auto-commit and quality coaching. Both execute INSIDE the create call, before it returns, so if the server finished after this client stopped waiting, they ran too. Do not assume either is absent \u2014 read the entry back to find out.
647
-
648
- **Do this next:** ${recovery}`
652
+ This client stopped waiting after ${error.elapsedMs}ms. ${recovery}`
649
653
  }],
650
- structuredContent: failure(
651
- "CAPTURE_OUTCOME_UNKNOWN",
652
- msg,
653
- recovery,
654
- [{ tool: "context", description: "Search for the entry before retrying", parameters: { action: "search", query: name } }],
655
- {
656
- // Structured so a caller can branch on this without parsing prose — the
657
- // reconciliation signal TEN-1126 says the capture path never had.
658
- partialSuccess: true,
659
- mayHaveLanded: error.mayHaveLanded,
660
- budgetMs: error.budgetMs,
661
- elapsedMs: error.elapsedMs,
662
- completedBeforeTimeout: [],
663
- // WP-575 review (P1): genuinely definite — relation linking is a separate mutation
664
- // never reached when create throws. See the doc comment above for why `commit` and
665
- // `coaching` were removed from this list.
666
- didNotRun: ["relations"],
667
- // WP-575 review (P1): distinct from `didNotRun` on purpose — these ran INSIDE the
668
- // same server call as the create, so their outcome is exactly as unknown as the
669
- // entry's. A caller must read the entry back, never assume either ran or didn't.
670
- mayHaveRun: ["commit", "coaching"]
671
- }
672
- )
654
+ structuredContent: failure("CAPTURE_OUTCOME_UNKNOWN", msg, recovery, [], {
655
+ mayHaveLanded: error.mayHaveLanded,
656
+ budgetMs: error.budgetMs,
657
+ elapsedMs: error.elapsedMs
658
+ })
673
659
  };
674
660
  }
675
- function buildDryRunTimeoutResult(error, msg) {
676
- const recovery = "Re-run the preview \u2014 nothing was written, so there is nothing to search for and nothing to duplicate.";
661
+ async function resumeReplayedCapture(replay, name, links, sessionId) {
662
+ const requested = (links ?? []).slice(0, 25);
663
+ let replayedLinks = [];
664
+ if (requested.length > 0) {
665
+ const batch = await kernelMutation("chain.createEntryRelations", {
666
+ relations: requested.map((l) => ({ fromEntryId: replay.entryId, toEntryId: l.to, type: l.type, proposedBy: "user" })),
667
+ sessionId
668
+ }).catch((err) => ({ results: requested.map((l) => ({ toEntryId: l.to, type: l.type, ok: false, error: err instanceof Error ? err.message : "unknown error" })) }));
669
+ replayedLinks = requested.map((l) => {
670
+ const row = batch.results.find((r) => r.toEntryId === l.to && r.type === l.type);
671
+ if (row?.ok) return { to: l.to, type: l.type, outcome: "written" };
672
+ const error = row?.error ?? "no answer for this link";
673
+ return /^Duplicate relation/.test(error) ? { to: l.to, type: l.type, outcome: "existing" } : { to: l.to, type: l.type, outcome: "failed", error };
674
+ });
675
+ }
676
+ const status2 = isTrustAccepted(replay.status) ? "committed" : "draft";
677
+ const summary = `Already captured as ${replay.entryId}; this re-send returned that entry and wrote no second one.`;
678
+ const linkLines = replayedLinks.map((l) => `- ${l.type} ${l.to}: ${l.outcome === "written" ? "written now" : l.outcome === "existing" ? "already there" : `not written (${l.error})`}`);
677
679
  return {
678
680
  content: [{
679
681
  type: "text",
680
- text: `# Preview Outcome Unknown \u2014 Nothing Was Written
682
+ text: `# Already Captured: ${replay.entryId}
681
683
 
682
- ${msg}
683
-
684
- **What is known:** the request reached the server and this client gave up waiting after ${error.elapsedMs}ms. This was a **preview**, and the server returns its validation result before performing any write \u2014 so no entry, auto-commit, quality coaching, or relation linking happened.
684
+ ${summary}${linkLines.length ? `
685
685
 
686
- **What is unknown:** the validation verdict this preview was asked for.
686
+ **Links:**
687
+ ${linkLines.join("\n")}` : ""}
687
688
 
688
- **Do this next:** ${recovery}`
689
+ Read it with \`context action=get\`.`
689
690
  }],
690
- structuredContent: failure(
691
- "CAPTURE_OUTCOME_UNKNOWN",
692
- msg,
693
- recovery,
694
- [],
695
- {
696
- // No partial success to reconcile: a dry run has no server-side state to converge on.
697
- partialSuccess: false,
698
- mayHaveLanded: false,
699
- budgetMs: error.budgetMs,
700
- elapsedMs: error.elapsedMs,
701
- completedBeforeTimeout: [],
702
- didNotRun: ["entry", "relations", "commit", "coaching"],
703
- mayHaveRun: []
704
- }
705
- )
691
+ structuredContent: success(summary, {
692
+ entryId: replay.entryId,
693
+ collection: replay.collection,
694
+ name,
695
+ status: status2,
696
+ replayed: true,
697
+ ...replayedLinks.length ? { replayedLinks } : {}
698
+ })
706
699
  };
707
700
  }
708
701
 
709
- // src/lib/statusCategories.ts
710
- var STATUS_CATEGORIES = ["draft", "current", "past", "discarded"];
711
- var STATUS_CATEGORY_SET = new Set(STATUS_CATEGORIES);
712
- var TRUST_ACCEPTED_WORDS = ["accepted", "active", "current"];
713
- function isTrustAccepted(status2) {
714
- return TRUST_ACCEPTED_WORDS.includes(status2);
715
- }
716
- var TRUST_ARCHIVED_WORDS = ["archived", "deprecated", "past", "discarded"];
717
- var ENTRY_STATUS_READ_WORDS = ["draft", ...TRUST_ACCEPTED_WORDS, ...TRUST_ARCHIVED_WORDS];
718
- var ENTRY_STATUS_WRITE_WORDS = ["draft", "archived"];
719
-
720
702
  // src/lib/scopedCache.ts
721
703
  var ScopedCache = class {
722
704
  constructor(ttlMs, maxScopes = 100) {
@@ -1466,8 +1448,7 @@ var captureSuccessOutputSchema = z4.object({
1466
1448
  collection: z4.string(),
1467
1449
  name: z4.string(),
1468
1450
  status: z4.enum(["draft", "committed", "proposed", "draft_on_failure"]),
1469
- // WP-480 S1: `qualityScore` (required, client N/10) deleted — a named, accepted
1470
- // breaking output-contract change. The server verdict stays as `qualityVerdict`.
1451
+ // WP-480 S1: `qualityScore` deleted (accepted breaking output change); the server verdict stays as `qualityVerdict`.
1471
1452
  qualityVerdict: z4.record(z4.unknown()).optional(),
1472
1453
  qualityHints: z4.array(z4.record(z4.unknown())).optional(),
1473
1454
  groundingReport: captureSinglePreviewOutputSchema.shape.groundingReport.optional(),
@@ -1485,10 +1466,11 @@ var captureSuccessOutputSchema = z4.object({
1485
1466
  })).optional(),
1486
1467
  // TASK-160: the server's birth-floor verdict must survive the strict output contract.
1487
1468
  spineFloor: z4.object({ relationType: z4.string(), parentKind: z4.string(), exempt: z4.boolean() }).optional(),
1488
- // WP-751 M3a-1b (TASK-189, DEC-1700): `coherencyRefusal` and `contradictionAdvisory` are
1489
- // deleted — both were only ever populated by the client-side auto-commit attempt (surfacing
1490
- // its own gate refusal, or the commit response's contradiction advisory), and that attempt is
1491
- // deleted entirely. Neither field is set anywhere in this file anymore.
1469
+ // WP-751 M3a-1b (TASK-189, DEC-1700): `coherencyRefusal` and `contradictionAdvisory` are deleted with the
1470
+ // client-side auto-commit attempt that alone populated them (its gate refusal, the commit's advisory).
1471
+ replayed: z4.literal(true).optional(),
1472
+ // TASK-898: a re-sent capture answered with the first capture's entry
1473
+ replayedLinks: z4.array(z4.object({ to: z4.string(), type: z4.string(), outcome: z4.enum(["written", "existing", "failed"]), error: z4.string().optional() })).optional(),
1492
1474
  fallbackApplied: z4.boolean().optional()
1493
1475
  // round 2 (PRRT_kwDORS4ils6cTNbF): classifier field above still names the original (pre-substitution) prediction
1494
1476
  }).strict();
@@ -1726,6 +1708,7 @@ async function handleCapture(server, { collection, name, description, context, c
1726
1708
  // accepted shape as the CLI dying between create and re-eval).
1727
1709
  ...links && links.length > 0 ? { deferLLMSchedule: true } : {}
1728
1710
  });
1711
+ if (result2 && "replayed" in result2) return resumeReplayedCapture(result2, name, links, agentId ?? void 0);
1729
1712
  if (result2 && isPreviewCreateEntryResult(result2)) {
1730
1713
  const preflight = await resolvePreviewPreflight(result2, links);
1731
1714
  const dupCount = groundingReport.duplicates.length;
@@ -1800,7 +1783,7 @@ No DB writes \u2014 call without \`preview:true\` to capture for real.${result2.
1800
1783
  spineFloor = created.spineFloor;
1801
1784
  } catch (error) {
1802
1785
  const msg = error instanceof Error ? error.message : String(error);
1803
- if (isGatewayTimeout(error)) return buildCaptureOutcomeUnknownResult(error, name, msg);
1786
+ if (isGatewayTimeout(error)) return buildCaptureOutcomeUnknownResult(error, msg);
1804
1787
  if (msg.includes("Duplicate") || msg.includes("already exists")) {
1805
1788
  return {
1806
1789
  content: [{
@@ -12849,4 +12832,4 @@ export {
12849
12832
  createProductBrainServer,
12850
12833
  initFeatureFlags
12851
12834
  };
12852
- //# sourceMappingURL=chunk-NIYRIGLR.js.map
12835
+ //# sourceMappingURL=chunk-W3NKFJSJ.js.map