@productbrain/mcp 0.0.1-beta.46 → 0.0.1-beta.48

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.
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-MRIO53BY.js";
9
9
 
10
10
  // src/tools/smart-capture.ts
11
- import { z } from "zod";
11
+ import { z as z2 } from "zod";
12
12
 
13
13
  // src/client.ts
14
14
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
@@ -666,6 +666,214 @@ function isClassificationAmbiguous(result) {
666
666
  return result.scoreMargin < CLASSIFIER_AMBIGUITY_MARGIN;
667
667
  }
668
668
 
669
+ // src/envelope.ts
670
+ import { z } from "zod";
671
+
672
+ // src/errors.ts
673
+ var GateError = class extends Error {
674
+ constructor(message) {
675
+ super(message);
676
+ this.name = "GateError";
677
+ }
678
+ };
679
+ var BackendError = class extends Error {
680
+ constructor(message) {
681
+ super(message);
682
+ this.name = "BackendError";
683
+ }
684
+ };
685
+ var ValidationError = class extends Error {
686
+ constructor(message) {
687
+ super(message);
688
+ this.name = "ValidationError";
689
+ }
690
+ };
691
+ var GATE_PATTERNS = [
692
+ {
693
+ pattern: /session required|no active.*session|call.*session.*start/i,
694
+ code: "SESSION_REQUIRED",
695
+ recovery: "Start an agent session first.",
696
+ action: { tool: "session", description: "Start session", parameters: { action: "start" } }
697
+ },
698
+ {
699
+ pattern: /session.*closed/i,
700
+ code: "SESSION_CLOSED",
701
+ recovery: "Start a new session.",
702
+ action: { tool: "session", description: "Start new session", parameters: { action: "start" } }
703
+ },
704
+ {
705
+ pattern: /orientation required|call.*orient/i,
706
+ code: "ORIENTATION_REQUIRED",
707
+ recovery: "Orient before writing.",
708
+ action: { tool: "orient", description: "Orient session", parameters: {} }
709
+ },
710
+ {
711
+ pattern: /read.?only.*scope/i,
712
+ code: "READONLY_SCOPE",
713
+ recovery: "This API key cannot write. Use a readwrite key.",
714
+ action: { tool: "health", description: "Check key scope", parameters: { action: "whoami" } }
715
+ }
716
+ ];
717
+ function classifyError(err) {
718
+ const message = err instanceof Error ? err.message : String(err);
719
+ if (err instanceof GateError) {
720
+ return {
721
+ code: "PERMISSION_DENIED",
722
+ message,
723
+ recovery: "Check permissions or use a readwrite API key.",
724
+ availableActions: [{ tool: "health", description: "Check key scope", parameters: { action: "whoami" } }]
725
+ };
726
+ }
727
+ if (err instanceof BackendError) {
728
+ return { code: "BACKEND_ERROR", message, recovery: "Retry the operation or check backend health." };
729
+ }
730
+ if (err instanceof ValidationError) {
731
+ return { code: "VALIDATION_ERROR", message };
732
+ }
733
+ for (const { pattern, code, recovery, action } of GATE_PATTERNS) {
734
+ if (pattern.test(message)) {
735
+ return { code, message, recovery, availableActions: [action] };
736
+ }
737
+ }
738
+ if (/network error|fetch failed|ECONNREFUSED|ETIMEDOUT/i.test(message)) {
739
+ return { code: "BACKEND_UNAVAILABLE", message, recovery: "Retry in a few seconds." };
740
+ }
741
+ if (/not found/i.test(message)) {
742
+ return {
743
+ code: "NOT_FOUND",
744
+ message,
745
+ recovery: "Use entries action=search to find the correct ID.",
746
+ availableActions: [{ tool: "entries", description: "Search entries", parameters: { action: "search" } }]
747
+ };
748
+ }
749
+ if (/duplicate|already exists/i.test(message)) {
750
+ return {
751
+ code: "DUPLICATE",
752
+ message,
753
+ recovery: "Use entries action=get to inspect the existing entry.",
754
+ availableActions: [{ tool: "entries", description: "Get entry", parameters: { action: "get" } }]
755
+ };
756
+ }
757
+ if (/invalid|validation|required field/i.test(message)) {
758
+ return { code: "VALIDATION_ERROR", message };
759
+ }
760
+ return { code: "INTERNAL_ERROR", message: message || "An unexpected error occurred." };
761
+ }
762
+
763
+ // src/envelope.ts
764
+ function success(summary, data, next) {
765
+ return {
766
+ ok: true,
767
+ summary: summary || "Operation completed.",
768
+ data,
769
+ ...next?.length ? { next } : {}
770
+ };
771
+ }
772
+ function failure(code, message, recovery, availableActions, diagnostics) {
773
+ return {
774
+ ok: false,
775
+ code,
776
+ message,
777
+ ...recovery ? { recovery } : {},
778
+ ...availableActions?.length ? { availableActions } : {},
779
+ ...diagnostics ? { diagnostics } : {}
780
+ };
781
+ }
782
+ function notFound(id, hint) {
783
+ return failure(
784
+ "NOT_FOUND",
785
+ `Entry '${id}' not found.`,
786
+ hint ?? "Use entries action=search to find the correct ID.",
787
+ [{ tool: "entries", description: "Search entries", parameters: { action: "search", query: id } }]
788
+ );
789
+ }
790
+ function validationError(message) {
791
+ return failure("VALIDATION_ERROR", message);
792
+ }
793
+ function textContent(text) {
794
+ return [{ type: "text", text }];
795
+ }
796
+ function parseOrFail(schema, args) {
797
+ const parsed = schema.safeParse(args);
798
+ if (parsed.success) return { ok: true, data: parsed.data };
799
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
800
+ const msg = `Invalid arguments: ${issues}`;
801
+ return {
802
+ ok: false,
803
+ result: { content: textContent(msg), structuredContent: validationError(msg) }
804
+ };
805
+ }
806
+ function unknownAction(action, valid) {
807
+ const msg = `Unknown action '${action}'. Valid actions: ${valid.join(", ")}.`;
808
+ return { content: textContent(msg), structuredContent: validationError(msg) };
809
+ }
810
+ function successResult(text, summary, data, next) {
811
+ return { content: textContent(text), structuredContent: success(summary, data, next) };
812
+ }
813
+ function failureResult(text, code, message, recovery, availableActions, diagnostics) {
814
+ return { content: textContent(text), structuredContent: failure(code, message, recovery, availableActions, diagnostics) };
815
+ }
816
+ function notFoundResult(id, text, hint) {
817
+ return { content: textContent(text ?? `Entry '${id}' not found.`), structuredContent: notFound(id, hint) };
818
+ }
819
+ function validationResult(message) {
820
+ return { content: textContent(message), structuredContent: validationError(message) };
821
+ }
822
+ var nextActionSchema = z.object({
823
+ tool: z.string(),
824
+ description: z.string(),
825
+ parameters: z.record(z.unknown())
826
+ });
827
+ var metaSchema = z.object({
828
+ durationMs: z.number().optional()
829
+ }).optional();
830
+ function withEnvelope(handler) {
831
+ return async (args) => {
832
+ const start = Date.now();
833
+ try {
834
+ const result = await handler(args);
835
+ const durationMs = Date.now() - start;
836
+ const sc = result.structuredContent;
837
+ if (sc && typeof sc === "object" && "ok" in sc) {
838
+ sc._meta = { ...sc._meta, durationMs };
839
+ return {
840
+ content: result.content ?? [],
841
+ structuredContent: sc,
842
+ ...sc.ok === false ? { isError: true } : {}
843
+ };
844
+ }
845
+ console.warn(`[withEnvelope] Handler returned without envelope shape. Wrapping automatically. Content preview: "${result.content?.[0]?.text?.slice(0, 60) ?? "(empty)"}"`);
846
+ return {
847
+ content: result.content ?? [],
848
+ structuredContent: {
849
+ ...success(
850
+ result.content?.[0]?.text?.slice(0, 100) ?? "",
851
+ sc ?? {}
852
+ ),
853
+ _meta: { durationMs }
854
+ }
855
+ };
856
+ } catch (err) {
857
+ const durationMs = Date.now() - start;
858
+ const classified = classifyError(err);
859
+ const envelope = {
860
+ ...failure(
861
+ classified.code,
862
+ classified.message,
863
+ classified.recovery,
864
+ classified.availableActions
865
+ ),
866
+ _meta: { durationMs }
867
+ };
868
+ return {
869
+ content: [{ type: "text", text: classified.message }],
870
+ structuredContent: envelope,
871
+ isError: true
872
+ };
873
+ }
874
+ };
875
+ }
876
+
669
877
  // src/tools/smart-capture.ts
670
878
  var AREA_KEYWORDS = {
671
879
  "Architecture": ["convex", "schema", "database", "migration", "api", "backend", "infrastructure", "scaling", "performance"],
@@ -1264,43 +1472,46 @@ var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
1264
1472
  var MAX_AUTO_LINKS = 5;
1265
1473
  var MAX_SUGGESTIONS = 5;
1266
1474
  var CAPTURE_WITHOUT_THINKING_FLAG = "capture-without-thinking";
1267
- var captureSchema = z.object({
1268
- collection: z.string().optional().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'. Optional when `capture-without-thinking` is enabled."),
1269
- name: z.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
1270
- description: z.string().describe("Full context \u2014 what's happening, why it matters, what you observed"),
1271
- context: z.string().optional().describe("Optional additional context (e.g. 'Observed during context gather calls taking 700ms+')"),
1272
- entryId: z.string().optional().describe("Optional custom entry ID (e.g. 'TEN-my-id'). Auto-generated if omitted."),
1273
- canonicalKey: z.string().optional().describe("Semantic type (e.g. 'decision', 'tension', 'vision'). Auto-assigned from collection if omitted."),
1274
- data: z.record(z.unknown()).optional().describe("Explicit field values when you know the schema (e.g. canonical_key, cardinality_rule, required_fields). Merged with inferred values; user-provided wins."),
1275
- links: z.array(z.object({
1276
- to: z.string().describe("Target entry ID (e.g. 'BR-64', 'ARCH-8')"),
1277
- type: z.string().describe("Relation type (e.g. 'governs', 'related_to', 'informs')")
1475
+ var captureSchema = z2.object({
1476
+ collection: z2.string().optional().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'. Optional when `capture-without-thinking` is enabled."),
1477
+ name: z2.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
1478
+ description: z2.string().describe("Full context \u2014 what's happening, why it matters, what you observed"),
1479
+ context: z2.string().optional().describe("Optional additional context (e.g. 'Observed during context gather calls taking 700ms+')"),
1480
+ entryId: z2.string().optional().describe("Optional custom entry ID (e.g. 'TEN-my-id'). Auto-generated if omitted."),
1481
+ canonicalKey: z2.string().optional().describe("Semantic type (e.g. 'decision', 'tension', 'vision'). Auto-assigned from collection if omitted."),
1482
+ data: z2.record(z2.unknown()).optional().describe("Explicit field values when you know the schema (e.g. canonical_key, cardinality_rule, required_fields). Merged with inferred values; user-provided wins."),
1483
+ links: z2.array(z2.object({
1484
+ to: z2.string().describe("Target entry ID (e.g. 'BR-64', 'ARCH-8')"),
1485
+ type: z2.string().describe("Relation type (e.g. 'governs', 'related_to', 'informs')")
1278
1486
  })).optional().describe("Relations to create after capture. Skips auto-link discovery when provided."),
1279
- autoCommit: z.boolean().optional().describe("If true, commits the entry immediately after capture + linking. Use for ungoverned collections or when you're certain.")
1487
+ autoCommit: z2.boolean().optional().describe("If true, commits the entry immediately after capture + linking. Use for ungoverned collections or when you're certain.")
1280
1488
  });
1281
- var batchCaptureSchema = z.object({
1282
- entries: z.array(z.object({
1283
- collection: z.string().describe("Collection slug"),
1284
- name: z.string().describe("Display name"),
1285
- description: z.string().describe("Full context / definition"),
1286
- entryId: z.string().optional().describe("Optional custom entry ID")
1489
+ var batchCaptureSchema = z2.object({
1490
+ entries: z2.array(z2.object({
1491
+ collection: z2.string().describe("Collection slug"),
1492
+ name: z2.string().describe("Display name"),
1493
+ description: z2.string().describe("Full context / definition"),
1494
+ entryId: z2.string().optional().describe("Optional custom entry ID")
1287
1495
  })).min(1).max(50).describe("Array of entries to capture")
1288
1496
  });
1289
- var captureClassifierSchema = z.object({
1290
- enabled: z.boolean(),
1291
- autoRouted: z.boolean(),
1292
- topConfidence: z.number(),
1497
+ var captureClassifierSchema = z2.object({
1498
+ enabled: z2.boolean(),
1499
+ autoRouted: z2.boolean(),
1500
+ agrees: z2.boolean(),
1501
+ topConfidence: z2.number(),
1293
1502
  // Backward-compatible alias for topConfidence.
1294
- confidence: z.number(),
1295
- reasons: z.array(z.string()),
1296
- candidates: z.array(
1297
- z.object({
1298
- collection: z.enum(STARTER_COLLECTIONS),
1299
- signalScore: z.number(),
1503
+ confidence: z2.number(),
1504
+ reasons: z2.array(z2.string()),
1505
+ candidates: z2.array(
1506
+ z2.object({
1507
+ collection: z2.enum(STARTER_COLLECTIONS),
1508
+ signalScore: z2.number(),
1300
1509
  // Backward-compatible alias for signalScore.
1301
- confidence: z.number()
1510
+ confidence: z2.number()
1302
1511
  })
1303
- )
1512
+ ),
1513
+ agentProvidedCollection: z2.string().optional(),
1514
+ overrideCommand: z2.string().optional()
1304
1515
  });
1305
1516
  function trackClassifierTelemetry(params) {
1306
1517
  const telemetry = {
@@ -1322,7 +1533,13 @@ function buildCollectionRequiredResult() {
1322
1533
  content: [{
1323
1534
  type: "text",
1324
1535
  text: "Collection is required unless `capture-without-thinking` is enabled.\n\nProvide `collection` explicitly, or enable the feature flag for this workspace."
1325
- }]
1536
+ }],
1537
+ structuredContent: failure(
1538
+ "VALIDATION_ERROR",
1539
+ "Collection is required unless capture-without-thinking is enabled.",
1540
+ "Provide collection explicitly, or enable the feature flag.",
1541
+ [{ tool: "collections", description: "List available collections", parameters: { action: "list" } }]
1542
+ )
1326
1543
  };
1327
1544
  }
1328
1545
  function buildClassifierUnknownResult() {
@@ -1331,16 +1548,13 @@ function buildClassifierUnknownResult() {
1331
1548
  type: "text",
1332
1549
  text: "I could not infer a collection confidently from this input.\n\nPlease provide `collection`, or rewrite with clearer intent (decision/problem/definition/insight/bet)."
1333
1550
  }],
1334
- structuredContent: {
1335
- classifier: {
1336
- enabled: true,
1337
- autoRouted: false,
1338
- topConfidence: 0,
1339
- confidence: 0,
1340
- reasons: [],
1341
- candidates: []
1342
- }
1343
- }
1551
+ structuredContent: failure(
1552
+ "VALIDATION_ERROR",
1553
+ "Could not infer collection from input.",
1554
+ "Provide collection explicitly, or rewrite with clearer intent.",
1555
+ [{ tool: "collections", description: "List available collections", parameters: { action: "list" } }],
1556
+ { classifier: { enabled: true, autoRouted: false, agrees: true, topConfidence: 0, confidence: 0, reasons: [], candidates: [] } }
1557
+ )
1344
1558
  };
1345
1559
  }
1346
1560
  function buildProvisionedCollectionSuggestions(candidates) {
@@ -1348,10 +1562,7 @@ function buildProvisionedCollectionSuggestions(candidates) {
1348
1562
  }
1349
1563
  function buildUnsupportedProvisioningResult(classified, provisionedCandidates) {
1350
1564
  const suggestions = buildProvisionedCollectionSuggestions(provisionedCandidates);
1351
- return {
1352
- content: [{
1353
- type: "text",
1354
- text: `Collection inference is not safe to auto-route yet.
1565
+ const textBody = `Collection inference is not safe to auto-route yet.
1355
1566
 
1356
1567
  Predicted collection \`${classified.collection}\` is not provisioned/supported for auto-routing in this workspace.
1357
1568
  Reason: ${classified.reasons.join("; ") || "low signal"}
@@ -1359,36 +1570,50 @@ Reason: ${classified.reasons.join("; ") || "low signal"}
1359
1570
  Choose one of these provisioned starter collections and retry with \`collection\`:
1360
1571
  ${suggestions}
1361
1572
 
1362
- Correction path: rerun with explicit \`collection\`.`
1363
- }],
1364
- structuredContent: {
1365
- classifier: {
1366
- enabled: true,
1367
- autoRouted: false,
1368
- topConfidence: classified.topConfidence,
1369
- confidence: classified.confidence,
1370
- reasons: classified.reasons,
1371
- candidates: provisionedCandidates
1573
+ Correction path: rerun with explicit \`collection\`.`;
1574
+ return {
1575
+ content: [{ type: "text", text: textBody }],
1576
+ structuredContent: failure(
1577
+ "VALIDATION_ERROR",
1578
+ `Collection '${classified.collection}' is not provisioned for auto-routing.`,
1579
+ "Rerun with explicit collection.",
1580
+ [{ tool: "collections", description: "List available collections", parameters: { action: "list" } }],
1581
+ {
1582
+ classifier: {
1583
+ enabled: true,
1584
+ autoRouted: false,
1585
+ agrees: false,
1586
+ topConfidence: classified.topConfidence,
1587
+ confidence: classified.confidence,
1588
+ reasons: classified.reasons,
1589
+ candidates: provisionedCandidates.map((c) => ({ collection: c.collection, signalScore: c.signalScore, confidence: c.confidence }))
1590
+ }
1372
1591
  }
1373
- }
1592
+ )
1374
1593
  };
1375
1594
  }
1376
1595
  function buildAmbiguousRouteResult(classified, classifierMeta, ambiguousRoute) {
1377
1596
  const suggestions = buildProvisionedCollectionSuggestions(classifierMeta.candidates);
1378
- return {
1379
- content: [{
1380
- type: "text",
1381
- text: "Collection inference is not safe to auto-route yet.\n\n" + (ambiguousRoute ? "Routing held because intent is ambiguous across top candidates.\n\n" : "") + `Predicted: \`${classified.collection}\` (${classified.topConfidence}% top confidence)
1597
+ const textBody = "Collection inference is not safe to auto-route yet.\n\n" + (ambiguousRoute ? "Routing held because intent is ambiguous across top candidates.\n\n" : "") + `Predicted: \`${classified.collection}\` (${classified.topConfidence}% top confidence)
1382
1598
  Reason: ${classified.reasons.join("; ") || "low signal"}
1383
1599
 
1384
1600
  Choose one of these and retry with \`collection\`:
1385
1601
  ${suggestions}
1386
1602
 
1387
- Correction path: if this was close, rerun with your chosen \`collection\`.`
1388
- }],
1389
- structuredContent: {
1390
- classifier: classifierMeta
1391
- }
1603
+ Correction path: if this was close, rerun with your chosen \`collection\`.`;
1604
+ return {
1605
+ content: [{ type: "text", text: textBody }],
1606
+ structuredContent: failure(
1607
+ "VALIDATION_ERROR",
1608
+ ambiguousRoute ? `Ambiguous routing \u2014 top candidates too close (${classified.topConfidence}% confidence).` : `Low confidence routing to '${classified.collection}' (${classified.topConfidence}%).`,
1609
+ "Rerun with explicit collection.",
1610
+ classifierMeta.candidates.map((c) => ({
1611
+ tool: "capture",
1612
+ description: `Capture to ${c.collection}`,
1613
+ parameters: { collection: c.collection }
1614
+ })),
1615
+ { classifier: classifierMeta }
1616
+ )
1392
1617
  };
1393
1618
  }
1394
1619
  async function getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections) {
@@ -1414,7 +1639,48 @@ async function resolveCaptureCollection(params) {
1414
1639
  explicitCollectionProvided
1415
1640
  } = params;
1416
1641
  if (collection) {
1417
- return { resolvedCollection: collection };
1642
+ const classified2 = classifyStarterCollection(name, description);
1643
+ if (classified2) {
1644
+ const agrees = classified2.collection === collection;
1645
+ const classifierMeta2 = {
1646
+ enabled: true,
1647
+ autoRouted: false,
1648
+ agrees,
1649
+ topConfidence: classified2.topConfidence,
1650
+ confidence: classified2.confidence,
1651
+ reasons: classified2.reasons,
1652
+ candidates: classified2.candidates.slice(0, 3),
1653
+ agentProvidedCollection: collection,
1654
+ ...!agrees && {
1655
+ overrideCommand: `capture collection="${classified2.collection}"`
1656
+ }
1657
+ };
1658
+ if (!agrees) {
1659
+ trackClassifierTelemetry({
1660
+ workspaceId,
1661
+ predictedCollection: classified2.collection,
1662
+ confidence: classified2.confidence,
1663
+ autoRouted: false,
1664
+ reasonCategory: "low-confidence",
1665
+ explicitCollectionProvided: true,
1666
+ outcome: "fallback"
1667
+ });
1668
+ }
1669
+ return { resolvedCollection: collection, classifierMeta: classifierMeta2 };
1670
+ }
1671
+ return {
1672
+ resolvedCollection: collection,
1673
+ classifierMeta: {
1674
+ enabled: true,
1675
+ autoRouted: false,
1676
+ agrees: true,
1677
+ topConfidence: 0,
1678
+ confidence: 0,
1679
+ reasons: [],
1680
+ candidates: [],
1681
+ agentProvidedCollection: collection
1682
+ }
1683
+ };
1418
1684
  }
1419
1685
  if (!classifierFlagOn) {
1420
1686
  return { earlyResult: buildCollectionRequiredResult() };
@@ -1452,6 +1718,7 @@ async function resolveCaptureCollection(params) {
1452
1718
  const classifierMeta = {
1453
1719
  enabled: true,
1454
1720
  autoRouted: autoRoute,
1721
+ agrees: true,
1455
1722
  topConfidence: classified.topConfidence,
1456
1723
  confidence: classified.confidence,
1457
1724
  reasons: classified.reasons,
@@ -1486,36 +1753,36 @@ async function resolveCaptureCollection(params) {
1486
1753
  classifierMeta
1487
1754
  };
1488
1755
  }
1489
- var captureSuccessOutputSchema = z.object({
1490
- entryId: z.string(),
1491
- collection: z.string(),
1492
- name: z.string(),
1493
- status: z.enum(["draft", "committed"]),
1494
- qualityScore: z.number(),
1495
- qualityVerdict: z.record(z.unknown()).optional(),
1756
+ var captureSuccessOutputSchema = z2.object({
1757
+ entryId: z2.string(),
1758
+ collection: z2.string(),
1759
+ name: z2.string(),
1760
+ status: z2.enum(["draft", "committed"]),
1761
+ qualityScore: z2.number(),
1762
+ qualityVerdict: z2.record(z2.unknown()).optional(),
1496
1763
  classifier: captureClassifierSchema.optional(),
1497
- studioUrl: z.string().optional()
1764
+ studioUrl: z2.string().optional()
1498
1765
  }).strict();
1499
- var captureClassifierOnlyOutputSchema = z.object({
1766
+ var captureClassifierOnlyOutputSchema = z2.object({
1500
1767
  classifier: captureClassifierSchema
1501
1768
  }).strict();
1502
- var captureOutputSchema = z.union([
1769
+ var captureOutputSchema = z2.union([
1503
1770
  captureSuccessOutputSchema,
1504
1771
  captureClassifierOnlyOutputSchema
1505
1772
  ]);
1506
- var batchCaptureOutputSchema = z.object({
1507
- captured: z.array(z.object({
1508
- entryId: z.string(),
1509
- collection: z.string(),
1510
- name: z.string()
1773
+ var batchCaptureOutputSchema = z2.object({
1774
+ captured: z2.array(z2.object({
1775
+ entryId: z2.string(),
1776
+ collection: z2.string(),
1777
+ name: z2.string()
1511
1778
  })),
1512
- total: z.number(),
1513
- failed: z.number(),
1514
- failedEntries: z.array(z.object({
1515
- index: z.number(),
1516
- collection: z.string(),
1517
- name: z.string(),
1518
- error: z.string()
1779
+ total: z2.number(),
1780
+ failed: z2.number(),
1781
+ failedEntries: z2.array(z2.object({
1782
+ index: z2.number(),
1783
+ collection: z2.string(),
1784
+ name: z2.string(),
1785
+ error: z2.string()
1519
1786
  })).optional()
1520
1787
  });
1521
1788
  function registerSmartCaptureTools(server) {
@@ -1530,7 +1797,7 @@ function registerSmartCaptureTools(server) {
1530
1797
  inputSchema: captureSchema.shape,
1531
1798
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1532
1799
  },
1533
- async ({ collection, name, description, context, entryId, canonicalKey, data: userData, links, autoCommit }) => {
1800
+ withEnvelope(async ({ collection, name, description, context, entryId, canonicalKey, data: userData, links, autoCommit }) => {
1534
1801
  requireWriteAccess();
1535
1802
  const wsCtx = await getWorkspaceContext();
1536
1803
  const explicitCollectionProvided = typeof collection === "string" && collection.trim().length > 0;
@@ -1571,7 +1838,16 @@ collections action=create slug="${resolvedCollection}" name="${displayName}" des
1571
1838
  \`\`\`
1572
1839
 
1573
1840
  Or use \`collections action=list\` to see available collections.`
1574
- }]
1841
+ }],
1842
+ structuredContent: failure(
1843
+ "NOT_FOUND",
1844
+ `Collection '${resolvedCollection}' not found.`,
1845
+ "Create the collection first, or use collections action=list to see available ones.",
1846
+ [
1847
+ { tool: "collections", description: "Create collection", parameters: { action: "create", slug: resolvedCollection, name: displayName } },
1848
+ { tool: "collections", description: "List collections", parameters: { action: "list" } }
1849
+ ]
1850
+ )
1575
1851
  };
1576
1852
  }
1577
1853
  const data = {};
@@ -1647,7 +1923,16 @@ Or use \`collections action=list\` to see available collections.`
1647
1923
  ${msg}
1648
1924
 
1649
1925
  Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to modify it.`
1650
- }]
1926
+ }],
1927
+ structuredContent: failure(
1928
+ "DUPLICATE",
1929
+ msg,
1930
+ "Use entries action=get to inspect the existing entry.",
1931
+ [
1932
+ { tool: "entries", description: "Get existing entry", parameters: { action: "get", entryId: entryId ?? name } },
1933
+ { tool: "update-entry", description: "Update existing entry", parameters: { entryId: entryId ?? "" } }
1934
+ ]
1935
+ )
1651
1936
  };
1652
1937
  }
1653
1938
  throw error;
@@ -1809,14 +2094,27 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1809
2094
  `**${name}** added to \`${resolvedCollection}\` as \`${finalStatus}\``,
1810
2095
  `**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
1811
2096
  ];
1812
- if (classifierMeta?.autoRouted) {
2097
+ if (classifierMeta && classifierMeta.topConfidence > 0) {
1813
2098
  lines.push("");
1814
- lines.push("## Collection routing");
1815
- lines.push(`Auto-routed to \`${resolvedCollection}\` (${classifierMeta.topConfidence}% top confidence).`);
1816
- if (classifierMeta.reasons.length > 0) {
1817
- lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2099
+ lines.push("## Classification");
2100
+ if (classifierMeta.autoRouted) {
2101
+ lines.push(`Auto-routed to \`${resolvedCollection}\` (${classifierMeta.topConfidence}% confidence).`);
2102
+ if (classifierMeta.reasons.length > 0) {
2103
+ lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2104
+ }
2105
+ lines.push(`Override: rerun with explicit \`collection\` if wrong.`);
2106
+ } else if (classifierMeta.agrees) {
2107
+ lines.push(`Classifier confirms \`${resolvedCollection}\` (${classifierMeta.topConfidence}% confidence).`);
2108
+ } else {
2109
+ const suggested = classifierMeta.candidates[0]?.collection ?? "unknown";
2110
+ lines.push(`Agent chose \`${resolvedCollection}\`, classifier suggests \`${suggested}\` (${classifierMeta.topConfidence}% confidence).`);
2111
+ if (classifierMeta.reasons.length > 0) {
2112
+ lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2113
+ }
2114
+ if (classifierMeta.overrideCommand) {
2115
+ lines.push(`Override: \`${classifierMeta.overrideCommand}\``);
2116
+ }
1818
2117
  }
1819
- lines.push("Correction path: rerun capture with explicit `collection` if this routing is wrong.");
1820
2118
  }
1821
2119
  const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
1822
2120
  const studioUrl = resolvedCollection === "bets" ? `${appUrl.replace(/\/$/, "")}/w/${wsCtx.workspaceSlug}/studio/${internalId}` : void 0;
@@ -1924,21 +2222,35 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1924
2222
  }
1925
2223
  } catch {
1926
2224
  }
2225
+ const next = [];
2226
+ if (finalStatus === "committed") {
2227
+ next.push({ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: finalEntryId } });
2228
+ } else {
2229
+ if (userLinkResults.length === 0) {
2230
+ next.push({ tool: "graph", description: "Discover links", parameters: { action: "suggest", entryId: finalEntryId } });
2231
+ }
2232
+ next.push({ tool: "commit-entry", description: "Commit to Chain", parameters: { entryId: finalEntryId } });
2233
+ }
2234
+ const summary = finalStatus === "committed" ? `Captured and committed ${finalEntryId} (${name}) to ${resolvedCollection}. Quality ${quality.score}/10.` : `Captured ${finalEntryId} (${name}) as draft in ${resolvedCollection}. Quality ${quality.score}/10.`;
1927
2235
  const toolResult = {
1928
2236
  content: [{ type: "text", text: lines.join("\n") }],
1929
- structuredContent: {
1930
- entryId: finalEntryId,
1931
- collection: resolvedCollection,
1932
- name,
1933
- status: finalStatus,
1934
- qualityScore: quality.score,
1935
- qualityVerdict: verdictResult?.verdict ? { ...verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0,
1936
- ...classifierMeta && { classifier: classifierMeta },
1937
- ...studioUrl && { studioUrl }
1938
- }
2237
+ structuredContent: success(
2238
+ summary,
2239
+ {
2240
+ entryId: finalEntryId,
2241
+ collection: resolvedCollection,
2242
+ name,
2243
+ status: finalStatus,
2244
+ qualityScore: quality.score,
2245
+ qualityVerdict: verdictResult?.verdict ? { ...verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0,
2246
+ ...classifierMeta && { classifier: classifierMeta },
2247
+ ...studioUrl && { studioUrl }
2248
+ },
2249
+ next
2250
+ )
1939
2251
  };
1940
2252
  return toolResult;
1941
- }
2253
+ })
1942
2254
  );
1943
2255
  trackWriteTool(captureTool);
1944
2256
  const batchCaptureTool = server.registerTool(
@@ -1949,7 +2261,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1949
2261
  inputSchema: batchCaptureSchema.shape,
1950
2262
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1951
2263
  },
1952
- async ({ entries }) => {
2264
+ withEnvelope(async ({ entries }) => {
1953
2265
  requireWriteAccess();
1954
2266
  const agentId = getAgentSessionId();
1955
2267
  const createdBy = agentId ? `agent:${agentId}` : "capture";
@@ -2113,25 +2425,32 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2113
2425
  lines.push(`- **Commit:** Use \`commit-entry\` to promote drafts to SSOT`);
2114
2426
  lines.push(`- **Quality:** Run \`quality action=check\` on individual entries to assess completeness`);
2115
2427
  }
2428
+ const summary = failed.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed).` : `Batch captured ${created.length} entries successfully.`;
2429
+ const next = created.length > 0 ? [
2430
+ { tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: created[0].entryId } },
2431
+ { tool: "commit-entry", description: "Commit first entry", parameters: { entryId: created[0].entryId } }
2432
+ ] : [];
2116
2433
  return {
2117
2434
  content: [{ type: "text", text: lines.join("\n") }],
2118
- structuredContent: {
2119
- captured: created.map((r) => ({ entryId: r.entryId, collection: r.collection, name: r.name })),
2120
- total: created.length,
2121
- failed: failed.length,
2122
- // failedEntries provides enough detail for the agent to retry individually.
2123
- // Only populated when failures occurred — avoids noise in the happy path.
2124
- ...failed.length > 0 && {
2125
- failedEntries: failed.map((r, i) => ({
2126
- index: results.indexOf(r),
2127
- collection: r.collection,
2128
- name: r.name,
2129
- error: r.error ?? "unknown error"
2130
- }))
2131
- }
2132
- }
2435
+ structuredContent: success(
2436
+ summary,
2437
+ {
2438
+ captured: created.map((r) => ({ entryId: r.entryId, collection: r.collection, name: r.name })),
2439
+ total: created.length,
2440
+ failed: failed.length,
2441
+ ...failed.length > 0 && {
2442
+ failedEntries: failed.map((r) => ({
2443
+ index: results.indexOf(r),
2444
+ collection: r.collection,
2445
+ name: r.name,
2446
+ error: r.error ?? "unknown error"
2447
+ }))
2448
+ }
2449
+ },
2450
+ next
2451
+ )
2133
2452
  };
2134
- }
2453
+ })
2135
2454
  );
2136
2455
  trackWriteTool(batchCaptureTool);
2137
2456
  }
@@ -2362,6 +2681,15 @@ export {
2362
2681
  STARTER_COLLECTIONS,
2363
2682
  classifyStarterCollection,
2364
2683
  isClassificationAmbiguous,
2684
+ success,
2685
+ failure,
2686
+ parseOrFail,
2687
+ unknownAction,
2688
+ successResult,
2689
+ failureResult,
2690
+ notFoundResult,
2691
+ validationResult,
2692
+ withEnvelope,
2365
2693
  formatQualityReport,
2366
2694
  checkEntryQuality,
2367
2695
  captureSchema,
@@ -2374,4 +2702,4 @@ export {
2374
2702
  formatRubricCoaching,
2375
2703
  formatRubricVerdictSection
2376
2704
  };
2377
- //# sourceMappingURL=chunk-5ZCY6NLZ.js.map
2705
+ //# sourceMappingURL=chunk-4ZJEIAA6.js.map