@indexnetwork/protocol 4.3.4-rc.310.1 → 4.3.4-rc.311.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/chat/chat.graph.d.ts +8 -8
  2. package/dist/chat/chat.state.d.ts +4 -3
  3. package/dist/chat/chat.state.d.ts.map +1 -1
  4. package/dist/chat/chat.state.js +2 -2
  5. package/dist/chat/chat.state.js.map +1 -1
  6. package/dist/chat/chat.streamer.d.ts +3 -2
  7. package/dist/chat/chat.streamer.d.ts.map +1 -1
  8. package/dist/chat/chat.streamer.js +2 -2
  9. package/dist/chat/chat.streamer.js.map +1 -1
  10. package/dist/intent/intent.graph.d.ts +5 -5
  11. package/dist/intent/intent.state.d.ts +2 -1
  12. package/dist/intent/intent.state.d.ts.map +1 -1
  13. package/dist/opportunity/feed/feed.graph.d.ts +10 -0
  14. package/dist/opportunity/feed/feed.graph.d.ts.map +1 -1
  15. package/dist/opportunity/feed/feed.graph.js +4 -0
  16. package/dist/opportunity/feed/feed.graph.js.map +1 -1
  17. package/dist/opportunity/feed/feed.state.d.ts +2 -0
  18. package/dist/opportunity/feed/feed.state.d.ts.map +1 -1
  19. package/dist/opportunity/feed/feed.state.js +8 -0
  20. package/dist/opportunity/feed/feed.state.js.map +1 -1
  21. package/dist/opportunity/opportunity.graph.d.ts +6 -6
  22. package/dist/opportunity/opportunity.state.d.ts +1 -1
  23. package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
  24. package/dist/opportunity/opportunity.tools.js +71 -4
  25. package/dist/opportunity/opportunity.tools.js.map +1 -1
  26. package/dist/premise/premise.graph.d.ts +4 -4
  27. package/dist/premise/premise.state.d.ts +2 -1
  28. package/dist/premise/premise.state.d.ts.map +1 -1
  29. package/dist/questioner/questioner.tools.d.ts.map +1 -1
  30. package/dist/questioner/questioner.tools.js +21 -8
  31. package/dist/questioner/questioner.tools.js.map +1 -1
  32. package/dist/shared/agent/tool.helpers.d.ts +9 -6
  33. package/dist/shared/agent/tool.helpers.d.ts.map +1 -1
  34. package/dist/shared/agent/tool.helpers.js.map +1 -1
  35. package/dist/shared/agent/tool.scope.d.ts +7 -1
  36. package/dist/shared/agent/tool.scope.d.ts.map +1 -1
  37. package/dist/shared/agent/tool.scope.js +10 -0
  38. package/dist/shared/agent/tool.scope.js.map +1 -1
  39. package/dist/shared/hyde/hyde.graph.d.ts +6 -6
  40. package/dist/shared/hyde/hyde.state.d.ts +2 -2
  41. package/dist/shared/interfaces/database.interface.d.ts +3 -0
  42. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  43. package/dist/shared/interfaces/database.interface.js.map +1 -1
  44. package/dist/shared/interfaces/questioner.interface.d.ts +3 -0
  45. package/dist/shared/interfaces/questioner.interface.d.ts.map +1 -1
  46. package/dist/shared/interfaces/questioner.interface.js.map +1 -1
  47. package/dist/shared/schemas/network-assignment.schema.d.ts +2 -2
  48. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { requestContext } from "../shared/observability/request-context.js";
3
3
  import { success, error, UUID_REGEX } from "../shared/agent/tool.helpers.js";
4
- import { deriveDiscoveryNetworkIds, focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
4
+ import { deriveDiscoveryNetworkIds, focusedIntentId, focusedNetworkId, focusedNetworkLabel } from "../shared/agent/tool.scope.js";
5
5
  import { MINIMAL_MAIN_TEXT_MAX_CHARS, getPrimaryActionLabel, SECONDARY_ACTION_LABEL } from "./opportunity.labels.js";
6
6
  import { viewerCentricCardSummary, narratorRemarkFromReasoning, stripUuids } from "./opportunity.presentation.js";
7
7
  import { runDiscoverFromQuery, continueDiscovery } from "./opportunity.discover.js";
@@ -177,6 +177,13 @@ const UPDATE_OPPORTUNITY_BLOCKED_STATUSES = new Set([
177
177
  "expired",
178
178
  "negotiating",
179
179
  ]);
180
+ function matchesSelectedIntentScope(opportunity, viewerId, scope) {
181
+ if (scope?.scopeType !== 'intent' || !scope.scopeId)
182
+ return true;
183
+ if (opportunity.detection?.triggeredBy === scope.scopeId)
184
+ return true;
185
+ return opportunity.actors.some((actor) => actor.userId === viewerId && actor.intent === scope.scopeId);
186
+ }
180
187
  /**
181
188
  * Maximum number of opportunity cards to show per chat response.
182
189
  * Sized for `selectByComposition` to fill both feed buckets — up to 3
@@ -515,7 +522,7 @@ export function createOpportunityTools(defineTool, deps) {
515
522
  "'complementary skills for a startup'). Helps the evaluator produce better match reasoning."),
516
523
  }),
517
524
  handler: async ({ context, query }) => {
518
- const scopedNetworkId = focusedNetworkId(context);
525
+ const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
519
526
  const scopedIndexLabel = focusedNetworkLabel(context);
520
527
  // Strict scope enforcement: when chat is network-scoped, only allow that index
521
528
  if (scopedNetworkId &&
@@ -1301,13 +1308,21 @@ export function createOpportunityTools(defineTool, deps) {
1301
1308
  .string()
1302
1309
  .optional()
1303
1310
  .describe("Network UUID to filter opportunities to a specific community. Get from read_networks. Defaults to the scoped network in network-scoped chats. Omit to see opportunities across all networks."),
1311
+ scopeType: z
1312
+ .enum(['intent'])
1313
+ .optional()
1314
+ .describe("Optional selected scope type. Use 'intent' to narrow listed opportunities to a selected intent."),
1315
+ scopeId: z
1316
+ .string()
1317
+ .optional()
1318
+ .describe("Selected intent UUID when scopeType is 'intent'. Ignored only when absent."),
1304
1319
  includeDigestMarkers: z
1305
1320
  .boolean()
1306
1321
  .optional()
1307
1322
  .describe("Internal scheduled-digest mode only. When true, includes hidden delivery markers so the digest send pass can confirm only edited-in opportunities."),
1308
1323
  }),
1309
1324
  handler: async ({ context, query }) => {
1310
- const scopedNetworkId = focusedNetworkId(context);
1325
+ const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
1311
1326
  const scopedIndexLabel = focusedNetworkLabel(context);
1312
1327
  // Strict scope enforcement: when chat is network-scoped, only allow that index
1313
1328
  if (scopedNetworkId &&
@@ -1319,6 +1334,25 @@ export function createOpportunityTools(defineTool, deps) {
1319
1334
  if (effectiveIndexId && !UUID_REGEX.test(effectiveIndexId)) {
1320
1335
  return error("Invalid network ID format.");
1321
1336
  }
1337
+ const contextIntentId = focusedIntentId(context);
1338
+ const rawScopeId = query.scopeId?.trim() || undefined;
1339
+ if (query.scopeType === 'intent' && !rawScopeId) {
1340
+ return error("scopeId required when scopeType is intent.");
1341
+ }
1342
+ if (!query.scopeType && rawScopeId) {
1343
+ return error("scopeType=intent required when scopeId is provided.");
1344
+ }
1345
+ if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
1346
+ return error("Invalid scope ID format.");
1347
+ }
1348
+ if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
1349
+ return error("This chat is scoped to a different intent.");
1350
+ }
1351
+ const effectiveIntentScope = contextIntentId
1352
+ ? { scopeType: 'intent', scopeId: contextIntentId }
1353
+ : query.scopeType === 'intent' && rawScopeId
1354
+ ? { scopeType: 'intent', scopeId: rawScopeId }
1355
+ : {};
1322
1356
  // The MCP/chat surface exposes actionable opportunities.
1323
1357
  // `latent` is included so the introducer-as-viewer can see their unapproved
1324
1358
  // connector-flow cards ("do you know someone for X?"). Other latent visibility
@@ -1331,6 +1365,7 @@ export function createOpportunityTools(defineTool, deps) {
1331
1365
  // sort order can fill the whole window and starve the other section.
1332
1366
  const fetched = await database.getOpportunitiesForUser(context.userId, {
1333
1367
  networkId: effectiveIndexId,
1368
+ ...effectiveIntentScope,
1334
1369
  statuses,
1335
1370
  limit: CHAT_FETCH_LIMIT,
1336
1371
  });
@@ -1399,8 +1434,10 @@ export function createOpportunityTools(defineTool, deps) {
1399
1434
  if (isDigestMode && deduped.length > 0) {
1400
1435
  const acceptedCounterpartIds = new Set();
1401
1436
  try {
1437
+ // effectiveIntentScope scopes the statuses: ["accepted"] suppression fetch.
1402
1438
  const acceptedOpps = await database.getOpportunitiesForUser(context.userId, {
1403
1439
  ...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
1440
+ ...effectiveIntentScope,
1404
1441
  statuses: ["accepted"],
1405
1442
  limit: ACCEPTED_SUPPRESSION_FETCH_LIMIT,
1406
1443
  });
@@ -1789,12 +1826,39 @@ export function createOpportunityTools(defineTool, deps) {
1789
1826
  .enum(["pending", "accepted", "rejected", "expired"])
1790
1827
  .describe("New status: 'pending' = send the draft to the other party, 'accepted' = accept the connection, " +
1791
1828
  "'rejected' = decline, 'expired' = mark as timed out."),
1829
+ scopeType: z
1830
+ .enum(['intent'])
1831
+ .optional()
1832
+ .describe("Optional selected scope type. Use 'intent' to require this opportunity to belong to a selected intent."),
1833
+ scopeId: z
1834
+ .string()
1835
+ .optional()
1836
+ .describe("Selected intent UUID when scopeType is 'intent'. Must match the chat's focused intent when one exists."),
1792
1837
  }),
1793
1838
  handler: async ({ context, query }) => {
1794
1839
  const opportunityId = query.opportunityId?.trim();
1795
1840
  if (!opportunityId || !UUID_REGEX.test(opportunityId)) {
1796
1841
  return error("Valid opportunityId required.");
1797
1842
  }
1843
+ const contextIntentId = focusedIntentId(context);
1844
+ const rawScopeId = query.scopeId?.trim() || undefined;
1845
+ if (query.scopeType === 'intent' && !rawScopeId) {
1846
+ return error("scopeId required when scopeType is intent.");
1847
+ }
1848
+ if (!query.scopeType && rawScopeId) {
1849
+ return error("scopeType=intent required when scopeId is provided.");
1850
+ }
1851
+ if (rawScopeId && !UUID_REGEX.test(rawScopeId)) {
1852
+ return error("Invalid scope ID format.");
1853
+ }
1854
+ if (contextIntentId && rawScopeId && contextIntentId !== rawScopeId) {
1855
+ return error("This chat is scoped to a different intent.");
1856
+ }
1857
+ const effectiveIntentScope = contextIntentId
1858
+ ? { scopeType: 'intent', scopeId: contextIntentId }
1859
+ : query.scopeType === 'intent' && rawScopeId
1860
+ ? { scopeType: 'intent', scopeId: rawScopeId }
1861
+ : {};
1798
1862
  // Always fetch the opportunity — needed for actor guard and state machine
1799
1863
  const opportunity = await systemDb.getOpportunity(opportunityId);
1800
1864
  if (!opportunity) {
@@ -1816,13 +1880,16 @@ export function createOpportunityTools(defineTool, deps) {
1816
1880
  // Mirrors the per-actor filter in getOpportunitiesForUser — relying on
1817
1881
  // a focus-scope id or any-actor matches would let a counterpart's network
1818
1882
  // presence shadow a viewer whose own actor is elsewhere.
1819
- const scopedNetworkId = focusedNetworkId(context);
1883
+ const scopedNetworkId = focusedNetworkId(context) ?? context.networkId?.trim();
1820
1884
  if (scopedNetworkId) {
1821
1885
  const callerOnBoundNetwork = opportunity.actors?.some((a) => a.userId === context.userId && a.networkId === scopedNetworkId);
1822
1886
  if (!callerOnBoundNetwork) {
1823
1887
  return error("Opportunity not found.");
1824
1888
  }
1825
1889
  }
1890
+ if (!matchesSelectedIntentScope(opportunity, context.userId, effectiveIntentScope)) {
1891
+ return error("Opportunity not found.");
1892
+ }
1826
1893
  const isSend = query.status === "pending";
1827
1894
  const _updateGraphStart = Date.now();
1828
1895
  const _updateTraceEmitter = requestContext.getStore()?.traceEmitter;