@indexnetwork/protocol 6.15.0-rc.407.1 → 8.0.2-rc.418.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 (110) hide show
  1. package/CHANGELOG.md +309 -0
  2. package/IMPLEMENTATION.md +7 -4
  3. package/dist/capabilities/opportunities.facade.d.ts +4 -0
  4. package/dist/capabilities/opportunities.facade.js +4 -0
  5. package/dist/chat/chat-streaming.types.d.ts +4 -5
  6. package/dist/chat/chat.agent.d.ts +0 -19
  7. package/dist/chat/chat.agent.js +1 -70
  8. package/dist/chat/chat.persona.d.ts +0 -6
  9. package/dist/chat/chat.persona.js +0 -1
  10. package/dist/chat/chat.prompt.js +10 -10
  11. package/dist/chat/chat.prompt.modules.js +0 -104
  12. package/dist/chat/chat.tools.js +11 -7
  13. package/dist/chat/negotiator.persona.js +0 -1
  14. package/dist/chat/onboarding.persona.js +0 -1
  15. package/dist/chat/reporter.persona.d.ts +1 -1
  16. package/dist/chat/reporter.persona.js +1 -2
  17. package/dist/chat/reporter.prompt.js +5 -5
  18. package/dist/chat/signal.persona.js +0 -1
  19. package/dist/communities/application/network.tools.js +48 -27
  20. package/dist/contacts/application/contact.tools.js +9 -9
  21. package/dist/index.d.ts +9 -2
  22. package/dist/index.js +5 -0
  23. package/dist/maintenance/maintenance.graph.js +7 -0
  24. package/dist/mcp/mcp.authorization-policy.d.ts +250 -0
  25. package/dist/mcp/mcp.authorization-policy.js +522 -0
  26. package/dist/mcp/mcp.server.d.ts +6 -29
  27. package/dist/mcp/mcp.server.js +202 -228
  28. package/dist/negotiation/application/negotiation.agent.js +29 -5
  29. package/dist/negotiation/application/negotiation.graph.d.ts +2 -5
  30. package/dist/negotiation/application/negotiation.graph.js +53 -23
  31. package/dist/negotiation/domain/index.d.ts +3 -0
  32. package/dist/negotiation/domain/index.js +3 -0
  33. package/dist/negotiation/domain/negotiation.deadlock.d.ts +7 -1
  34. package/dist/negotiation/domain/negotiation.deadlock.js +48 -1
  35. package/dist/negotiation/domain/negotiation.stance.contracts.d.ts +85 -0
  36. package/dist/negotiation/domain/negotiation.stance.contracts.js +140 -0
  37. package/dist/negotiation/domain/negotiation.state.d.ts +2 -3
  38. package/dist/negotiation/domain/negotiation.state.js +2 -3
  39. package/dist/negotiation/public/index.d.ts +2 -0
  40. package/dist/negotiation/public/index.js +2 -0
  41. package/dist/opportunity/application/index.d.ts +0 -3
  42. package/dist/opportunity/application/index.js +0 -3
  43. package/dist/opportunity/application/opportunity.graph.d.ts +0 -43
  44. package/dist/opportunity/application/opportunity.graph.js +7 -113
  45. package/dist/opportunity/application/opportunity.introducer-feature.d.ts +2 -0
  46. package/dist/opportunity/application/opportunity.introducer-feature.js +4 -0
  47. package/dist/opportunity/application/opportunity.owner-approval.d.ts +118 -0
  48. package/dist/opportunity/application/opportunity.owner-approval.js +34 -0
  49. package/dist/opportunity/application/opportunity.owner-provenance.d.ts +6 -0
  50. package/dist/opportunity/application/opportunity.owner-provenance.js +28 -0
  51. package/dist/opportunity/application/opportunity.tools.d.ts +3 -3
  52. package/dist/opportunity/application/opportunity.tools.js +77 -870
  53. package/dist/opportunity/discriminator/discriminator.shadow.js +17 -10
  54. package/dist/opportunity/discriminator/discriminator.types.d.ts +2 -0
  55. package/dist/opportunity/domain/index.d.ts +0 -2
  56. package/dist/opportunity/domain/index.js +0 -2
  57. package/dist/opportunity/domain/opportunity.state.d.ts +2 -70
  58. package/dist/opportunity/domain/opportunity.state.js +2 -55
  59. package/dist/opportunity/opportunity.owner-approval.d.ts +5 -0
  60. package/dist/opportunity/opportunity.owner-approval.js +5 -0
  61. package/dist/opportunity/ports/opportunity.tools.port.d.ts +12 -1
  62. package/dist/opportunity/public/index.d.ts +3 -3
  63. package/dist/opportunity/public/index.js +2 -1
  64. package/dist/participant-agents/application/agent.tools.d.ts +1 -1
  65. package/dist/participant-agents/application/agent.tools.js +44 -6
  66. package/dist/participant-agents/application/index.d.ts +5 -3
  67. package/dist/participant-agents/application/index.js +5 -3
  68. package/dist/questions/application/question.tools.js +31 -2
  69. package/dist/runtime/foreground/composition/tool.factory.d.ts +2 -1
  70. package/dist/runtime/foreground/composition/tool.factory.js +9 -2
  71. package/dist/runtime/foreground/composition/tool.registry.d.ts +16 -2
  72. package/dist/runtime/foreground/composition/tool.registry.js +41 -38
  73. package/dist/shared/agent/activity-projection.d.ts +271 -0
  74. package/dist/shared/agent/activity-projection.js +248 -0
  75. package/dist/shared/agent/canonical-guidance.d.ts +30 -0
  76. package/dist/shared/agent/canonical-guidance.js +249 -0
  77. package/dist/shared/agent/tool.helpers.d.ts +9 -17
  78. package/dist/shared/agent/tool.runtime.js +1 -3
  79. package/dist/shared/agent/utility.tools.d.ts +11 -1
  80. package/dist/shared/agent/utility.tools.js +126 -90
  81. package/dist/shared/interfaces/auth.interface.d.ts +2 -8
  82. package/dist/shared/interfaces/chat-session.interface.js +5 -0
  83. package/dist/shared/interfaces/database.interface.d.ts +12 -5
  84. package/dist/shared/observability/request-context.d.ts +4 -40
  85. package/dist/shared/schemas/mcp-auth.schema.d.ts +87 -15
  86. package/dist/shared/schemas/mcp-auth.schema.js +49 -1
  87. package/dist/signals/application/intent.tools.js +7 -8
  88. package/package.json +10 -9
  89. package/dist/opportunity/application/opportunity.discover.d.ts +0 -215
  90. package/dist/opportunity/application/opportunity.discover.js +0 -793
  91. package/dist/opportunity/application/opportunity.discovery-continuation-finalization.d.ts +0 -34
  92. package/dist/opportunity/application/opportunity.discovery-continuation-finalization.js +0 -74
  93. package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.d.ts +0 -44
  94. package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.js +0 -92
  95. package/dist/opportunity/domain/opportunity.discovery-run-coalescing.d.ts +0 -33
  96. package/dist/opportunity/domain/opportunity.discovery-run-coalescing.js +0 -33
  97. package/dist/opportunity/domain/opportunity.discovery.contracts.d.ts +0 -45
  98. package/dist/opportunity/domain/opportunity.discovery.contracts.js +0 -1
  99. package/dist/opportunity/opportunity.discover.d.ts +0 -5
  100. package/dist/opportunity/opportunity.discover.js +0 -5
  101. package/dist/opportunity/opportunity.discovery-continuation-finalization.d.ts +0 -5
  102. package/dist/opportunity/opportunity.discovery-continuation-finalization.js +0 -5
  103. package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.d.ts +0 -5
  104. package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.js +0 -5
  105. package/dist/opportunity/opportunity.discovery-run-coalescing.d.ts +0 -5
  106. package/dist/opportunity/opportunity.discovery-run-coalescing.js +0 -5
  107. package/dist/opportunity/opportunity.discovery.contracts.d.ts +0 -5
  108. package/dist/opportunity/opportunity.discovery.contracts.js +0 -5
  109. package/dist/shared/interfaces/discovery-run.interface.d.ts +0 -70
  110. package/dist/shared/interfaces/discovery-run.interface.js +0 -1
@@ -125,11 +125,6 @@ export declare class OpportunityGraphFactory {
125
125
  targetUserId: Id<"users"> | undefined;
126
126
  onBehalfOfUserId: Id<"users"> | undefined;
127
127
  options: import("../domain/opportunity.state.js").OpportunityGraphOptions;
128
- trigger: import("../domain/opportunity.state.js").OpportunityTrigger;
129
- dedupAlreadyAccepted: {
130
- opportunityId: string;
131
- counterpartyUserId: string;
132
- }[];
133
128
  operationMode: "delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing";
134
129
  introductionEntities: EvaluatorEntity[];
135
130
  introductionHint: string | undefined;
@@ -239,14 +234,6 @@ export declare class OpportunityGraphFactory {
239
234
  targetUserId?: Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined;
240
235
  onBehalfOfUserId?: Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined;
241
236
  options?: import("../domain/opportunity.state.js").OpportunityGraphOptions | import("@langchain/langgraph").OverwriteValue<import("../domain/opportunity.state.js").OpportunityGraphOptions> | undefined;
242
- trigger?: import("../domain/opportunity.state.js").OpportunityTrigger | import("@langchain/langgraph").OverwriteValue<import("../domain/opportunity.state.js").OpportunityTrigger> | undefined;
243
- dedupAlreadyAccepted?: {
244
- opportunityId: string;
245
- counterpartyUserId: string;
246
- }[] | import("@langchain/langgraph").OverwriteValue<{
247
- opportunityId: string;
248
- counterpartyUserId: string;
249
- }[]> | undefined;
250
237
  operationMode?: "delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing" | import("@langchain/langgraph").OverwriteValue<"delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing"> | undefined;
251
238
  introductionEntities?: EvaluatorEntity[] | import("@langchain/langgraph").OverwriteValue<EvaluatorEntity[]> | undefined;
252
239
  introductionHint?: string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined;
@@ -420,17 +407,6 @@ export declare class OpportunityGraphFactory {
420
407
  targetUserId: import("@langchain/langgraph").BaseChannel<Id<"users"> | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined, unknown>;
421
408
  onBehalfOfUserId: import("@langchain/langgraph").BaseChannel<Id<"users"> | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined, unknown>;
422
409
  options: import("@langchain/langgraph").BaseChannel<import("../domain/opportunity.state.js").OpportunityGraphOptions, import("../domain/opportunity.state.js").OpportunityGraphOptions | import("@langchain/langgraph").OverwriteValue<import("../domain/opportunity.state.js").OpportunityGraphOptions>, unknown>;
423
- trigger: import("@langchain/langgraph").BaseChannel<import("../domain/opportunity.state.js").OpportunityTrigger, import("../domain/opportunity.state.js").OpportunityTrigger | import("@langchain/langgraph").OverwriteValue<import("../domain/opportunity.state.js").OpportunityTrigger>, unknown>;
424
- dedupAlreadyAccepted: import("@langchain/langgraph").BaseChannel<{
425
- opportunityId: string;
426
- counterpartyUserId: string;
427
- }[], {
428
- opportunityId: string;
429
- counterpartyUserId: string;
430
- }[] | import("@langchain/langgraph").OverwriteValue<{
431
- opportunityId: string;
432
- counterpartyUserId: string;
433
- }[]>, unknown>;
434
410
  operationMode: import("@langchain/langgraph").BaseChannel<"delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing", "delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing" | import("@langchain/langgraph").OverwriteValue<"delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing">, unknown>;
435
411
  introductionEntities: import("@langchain/langgraph").BaseChannel<EvaluatorEntity[], EvaluatorEntity[] | import("@langchain/langgraph").OverwriteValue<EvaluatorEntity[]>, unknown>;
436
412
  introductionHint: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
@@ -668,17 +644,6 @@ export declare class OpportunityGraphFactory {
668
644
  targetUserId: import("@langchain/langgraph").BaseChannel<Id<"users"> | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined, unknown>;
669
645
  onBehalfOfUserId: import("@langchain/langgraph").BaseChannel<Id<"users"> | undefined, Id<"users"> | import("@langchain/langgraph").OverwriteValue<Id<"users"> | undefined> | undefined, unknown>;
670
646
  options: import("@langchain/langgraph").BaseChannel<import("../domain/opportunity.state.js").OpportunityGraphOptions, import("../domain/opportunity.state.js").OpportunityGraphOptions | import("@langchain/langgraph").OverwriteValue<import("../domain/opportunity.state.js").OpportunityGraphOptions>, unknown>;
671
- trigger: import("@langchain/langgraph").BaseChannel<import("../domain/opportunity.state.js").OpportunityTrigger, import("../domain/opportunity.state.js").OpportunityTrigger | import("@langchain/langgraph").OverwriteValue<import("../domain/opportunity.state.js").OpportunityTrigger>, unknown>;
672
- dedupAlreadyAccepted: import("@langchain/langgraph").BaseChannel<{
673
- opportunityId: string;
674
- counterpartyUserId: string;
675
- }[], {
676
- opportunityId: string;
677
- counterpartyUserId: string;
678
- }[] | import("@langchain/langgraph").OverwriteValue<{
679
- opportunityId: string;
680
- counterpartyUserId: string;
681
- }[]>, unknown>;
682
647
  operationMode: import("@langchain/langgraph").BaseChannel<"delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing", "delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing" | import("@langchain/langgraph").OverwriteValue<"delete" | "send" | "update" | "approve_introduction" | "create" | "create_introduction" | "continue_discovery" | "read" | "negotiate_existing">, unknown>;
683
648
  introductionEntities: import("@langchain/langgraph").BaseChannel<EvaluatorEntity[], EvaluatorEntity[] | import("@langchain/langgraph").OverwriteValue<EvaluatorEntity[]>, unknown>;
684
649
  introductionHint: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
@@ -1220,14 +1185,12 @@ export declare class OpportunityGraphFactory {
1220
1185
  finalAtomicConflictCount: number;
1221
1186
  };
1222
1187
  existingBetweenActors?: undefined;
1223
- dedupAlreadyAccepted?: undefined;
1224
1188
  trace?: undefined;
1225
1189
  error?: undefined;
1226
1190
  } | {
1227
1191
  opportunities: never[];
1228
1192
  persistenceOutcome?: undefined;
1229
1193
  existingBetweenActors?: undefined;
1230
- dedupAlreadyAccepted?: undefined;
1231
1194
  trace?: undefined;
1232
1195
  error?: undefined;
1233
1196
  } | {
@@ -1240,10 +1203,6 @@ export declare class OpportunityGraphFactory {
1240
1203
  reason?: "same_trigger_recent_duplicate" | "pair_active_negotiation" | "final_atomic_conflict";
1241
1204
  existingTriggerIntentId?: string;
1242
1205
  }[];
1243
- dedupAlreadyAccepted: {
1244
- opportunityId: string;
1245
- counterpartyUserId: string;
1246
- }[];
1247
1206
  persistenceOutcome: {
1248
1207
  evaluatedCount: number;
1249
1208
  createdCount: number;
@@ -1260,7 +1219,6 @@ export declare class OpportunityGraphFactory {
1260
1219
  created: number;
1261
1220
  reactivated: number;
1262
1221
  existingSkipped: number;
1263
- alreadyAccepted: number;
1264
1222
  totalOutput: number;
1265
1223
  persistenceOutcome: {
1266
1224
  evaluatedCount: number;
@@ -1287,7 +1245,6 @@ export declare class OpportunityGraphFactory {
1287
1245
  };
1288
1246
  }[];
1289
1247
  persistenceOutcome?: undefined;
1290
- dedupAlreadyAccepted?: undefined;
1291
1248
  };
1292
1249
  read: {
1293
1250
  readResult: {
@@ -13,7 +13,6 @@
13
13
  */
14
14
  import { StateGraph, START, END } from '@langchain/langgraph';
15
15
  import { OpportunityGraphState } from '../domain/opportunity.state.js';
16
- import { resolveInitialStatus } from '../domain/opportunity.state.js';
17
16
  import { OpportunityEvaluator } from './opportunity.evaluator.js';
18
17
  import { IntentIndexer } from '../../capabilities/signals.indexing.facade.js';
19
18
  import { getModelName } from '../../shared/agent/model.config.js';
@@ -2028,34 +2027,19 @@ export class OpportunityGraphFactory {
2028
2027
  ? (await Promise.all(uniqueIndexIds.map((networkId) => this.agentDispatcher.hasExternalAgent(discoveryUserId, { action: 'manage:negotiations', scopeType: 'network', scopeId: networkId }).catch(() => false)))).every(Boolean)
2029
2028
  : false)
2030
2029
  : false;
2031
- // Orchestrator (chat-driven a2h) fan-out uses a tight 60s park window —
2032
- // the user is watching the stream, so we cannot afford the 5-min ambient
2033
- // budget. Ambient keeps its heartbeat-aware long/short split.
2034
- const ORCHESTRATOR_PARK_WINDOW_MS = 60000;
2035
- const isOrchestrator = state.trigger === 'orchestrator';
2036
2030
  const useLongTimeout = !isChatPath || hasExternalAgent;
2037
- const timeoutMs = isOrchestrator
2038
- ? ORCHESTRATOR_PARK_WINDOW_MS
2039
- : useLongTimeout ? AMBIENT_PARK_WINDOW_MS : 30000;
2031
+ const timeoutMs = useLongTimeout ? AMBIENT_PARK_WINDOW_MS : 30000;
2040
2032
  logger.info('negotiateNode timeout decision', {
2041
2033
  discoveryUserId,
2042
- trigger: state.trigger,
2043
2034
  isChatPath,
2044
- isOrchestrator,
2045
2035
  hasDispatcher: !!this.agentDispatcher,
2046
2036
  hasExternalAgent,
2047
2037
  useLongTimeout,
2048
2038
  timeoutMs,
2049
2039
  candidateCount: candidates.length,
2050
2040
  });
2051
- // Per-candidate hook always-on. Accumulates negotiation resolutions
2052
- // for discovery question generation. Additionally, for the orchestrator
2053
- // trigger: flips the opp from 'pending' to 'draft' and pushes an
2054
- // `opportunity_draft_ready` event so the frontend can render it
2055
- // inline as soon as it resolves, rather than waiting for the full
2056
- // fan-out. Abort (e.g. user closed the chat) suppresses both the
2057
- // status flip and the event — the in-flight negotiation finishes
2058
- // naturally but its card never reaches the user.
2041
+ // Per-candidate hook accumulates negotiation resolutions for discovery
2042
+ // question generation, preserving candidate-list order below.
2059
2043
  // Build a stable order index so that resolutions accumulated via the
2060
2044
  // per-candidate async hook can be re-sorted to candidate-list order
2061
2045
  // before being handed to buildQuestionPrompt. Without this the LLM
@@ -2064,7 +2048,7 @@ export class OpportunityGraphFactory {
2064
2048
  candidates.forEach((c, i) => candidateOrderById.set(c.userId, i));
2065
2049
  const resolutions = [];
2066
2050
  const resolvedOpportunityIds = new Set();
2067
- const onCandidateResolved = async ({ candidate, accepted, turns, outcome }) => {
2051
+ const onCandidateResolved = async ({ candidate, turns, outcome }) => {
2068
2052
  if (candidate.opportunityId)
2069
2053
  resolvedOpportunityIds.add(candidate.opportunityId);
2070
2054
  resolutions.push({
@@ -2085,62 +2069,11 @@ export class OpportunityGraphFactory {
2085
2069
  if (candidate.opportunityId) {
2086
2070
  await compensateTasklessNegotiatingOpportunity(candidate.opportunityId);
2087
2071
  }
2088
- if (state.trigger !== 'orchestrator')
2089
- return;
2090
- // ─── orchestrator streaming body ───
2091
- const abortSignal = requestContext.getStore()?.abortSignal;
2092
- if (abortSignal?.aborted)
2093
- return;
2094
- if (!accepted || !candidate.opportunityId)
2095
- return;
2096
- // Only emit after a successful status flip — the frontend keys
2097
- // cards off `opportunity.status === 'draft'`, so emitting a row
2098
- // with its pre-flip status would render inconsistently. If the
2099
- // flip fails we log and drop the event; the negotiation result
2100
- // is still captured in acceptedResults for the final summary.
2101
- const updated = await this.database
2102
- .updateOpportunityStatus(candidate.opportunityId, 'draft')
2103
- .catch((err) => {
2104
- negotiateLog.warn('failed to flip opp to draft; suppressing draft-ready event', {
2105
- opportunityId: candidate.opportunityId,
2106
- error: err,
2107
- });
2108
- return null;
2109
- });
2110
- if (!updated || abortSignal?.aborted)
2111
- return;
2112
- const counterpartName = candidate.candidateUser.profile?.name ?? '';
2113
- const viewerName = sourceUser.profile.name;
2114
- const rawReasoning = updated.interpretation?.reasoning ?? '';
2115
- const personalizedSummary = safeFallbackSummary(rawReasoning, {
2116
- counterpartName,
2117
- viewerName,
2118
- emptyText: 'A suggested connection.',
2119
- });
2120
- traceEmitter?.({
2121
- type: 'opportunity_draft_ready',
2122
- opportunityId: candidate.opportunityId,
2123
- opportunity: {
2124
- ...updated,
2125
- interpretation: {
2126
- ...updated.interpretation,
2127
- reasoning: personalizedSummary,
2128
- },
2129
- },
2130
- personalizedSummary,
2131
- counterparty: {
2132
- userId: candidate.candidateUser.id,
2133
- ...(candidate.candidateUser.profile?.name
2134
- ? { name: candidate.candidateUser.profile.name }
2135
- : {}),
2136
- },
2137
- });
2138
2072
  };
2139
2073
  const negotiationWork = negotiateCandidates(this.negotiationGraph, sourceUser, candidates, { networkId: '', prompt: '' }, // base context, overridden per-candidate below
2140
2074
  { maxTurns, traceEmitter: traceEmitter ?? undefined,
2141
2075
  indexContextOverrides: indexContextMap,
2142
2076
  timeoutMs,
2143
- trigger: state.trigger === 'orchestrator' ? 'orchestrator' : 'ambient',
2144
2077
  // v2 initiator stamp: every fresh-discovery origin resolves to the
2145
2078
  // discovery user — querying user (chat/tool), intent owner
2146
2079
  // (from-intent), enriched user (from-enrichment/discovery-run), or
@@ -2525,10 +2458,9 @@ export class OpportunityGraphFactory {
2525
2458
  const persistNode = withNodeTrace("opportunity-persist", async (state) => {
2526
2459
  return timed("OpportunityGraph.persist", async () => {
2527
2460
  const startTime = Date.now();
2528
- const initialStatus = resolveInitialStatus(state.trigger, state.options.initialStatus);
2461
+ const initialStatus = state.options.initialStatus ?? 'pending';
2529
2462
  persistLog.verbose('Starting persistence (dedup-v2)', {
2530
2463
  opportunitiesToCreate: state.evaluatedOpportunities.length,
2531
- trigger: state.trigger,
2532
2464
  initialStatus,
2533
2465
  });
2534
2466
  if (state.evaluatedOpportunities.length === 0) {
@@ -2593,41 +2525,6 @@ export class OpportunityGraphFactory {
2593
2525
  const introducerUserForOnBehalf = state.onBehalfOfUserId
2594
2526
  ? await this.database.getUser(state.userId)
2595
2527
  : null;
2596
- // Orchestrator-only: collect already-accepted pairs so Task 7's
2597
- // discover_opportunities tool can tell the LLM "these pairs are
2598
- // already connected, surface the existing chat rather than
2599
- // creating a new draft". Runs in parallel across unique
2600
- // counterparties (a single evaluator pass can return multiple
2601
- // opps per counterparty; we only hit the DB once per pair).
2602
- // Failures are swallowed — the per-pair query is best-effort.
2603
- const dedupAlreadyAccepted = [];
2604
- if (state.trigger === 'orchestrator') {
2605
- // Use the same viewer-resolution as evaluation/negotiate/persist
2606
- // on-behalf branches so an introducer-driven orchestrator run
2607
- // queries accepted opps between the *target* user and the
2608
- // counterparty, not between the introducer and the counterparty.
2609
- const dedupUserId = (state.onBehalfOfUserId ?? state.userId);
2610
- const uniqueCounterparts = new Set();
2611
- for (const evaluated of evaluatedToPersist) {
2612
- const candidateUserId = evaluated.actors.find(a => a.userId !== dedupUserId)?.userId;
2613
- if (candidateUserId)
2614
- uniqueCounterparts.add(candidateUserId);
2615
- }
2616
- const lookups = await Promise.all([...uniqueCounterparts].map(async (counterpartyUserId) => {
2617
- const accepted = await this.database
2618
- .findOpportunitiesByActors([dedupUserId, counterpartyUserId], { includeIntroducers: true, statuses: ['accepted'] })
2619
- .catch((err) => {
2620
- persistLog.warn('findOpportunitiesByActors (sibling-accept) failed', {
2621
- userId: dedupUserId,
2622
- counterpartyUserId,
2623
- error: err,
2624
- });
2625
- return [];
2626
- });
2627
- return accepted.map((opp) => ({ opportunityId: opp.id, counterpartyUserId }));
2628
- }));
2629
- dedupAlreadyAccepted.push(...lookups.flat());
2630
- }
2631
2528
  for (const evaluated of evaluatedToPersist) {
2632
2529
  const indexIdForActors = state.networkId ?? evaluated.actors[0]?.networkId;
2633
2530
  let actors;
@@ -3233,16 +3130,14 @@ export class OpportunityGraphFactory {
3233
3130
  return {
3234
3131
  opportunities: allOpportunities,
3235
3132
  existingBetweenActors,
3236
- dedupAlreadyAccepted,
3237
3133
  persistenceOutcome,
3238
3134
  trace: [{
3239
3135
  node: "persist",
3240
- detail: `Created ${createdList.length}, reactivated ${reactivatedOpportunities.length}, ${existingBetweenActors.length} existing skipped, ${dedupAlreadyAccepted.length} already-accepted pair(s)`,
3136
+ detail: `Created ${createdList.length}, reactivated ${reactivatedOpportunities.length}, ${existingBetweenActors.length} existing skipped`,
3241
3137
  data: {
3242
3138
  created: createdList.length,
3243
3139
  reactivated: reactivatedOpportunities.length,
3244
3140
  existingSkipped: existingBetweenActors.length,
3245
- alreadyAccepted: dedupAlreadyAccepted.length,
3246
3141
  totalOutput: allOpportunities.length,
3247
3142
  persistenceOutcome,
3248
3143
  durationMs: Date.now() - startTime,
@@ -3308,7 +3203,7 @@ export class OpportunityGraphFactory {
3308
3203
  return {
3309
3204
  readResult: {
3310
3205
  count: 0,
3311
- message: 'You have no opportunities yet. Use discover_opportunities to search for connections.',
3206
+ message: 'You have no opportunities yet. Create or refine an approved signal; matching runs in the background. Use list_opportunities later to review persisted results.',
3312
3207
  opportunities: [],
3313
3208
  },
3314
3209
  };
@@ -3494,7 +3389,6 @@ export class OpportunityGraphFactory {
3494
3389
  maxTurns: Number(process.env.NEGOTIATION_MAX_TURNS_AMBIENT) || 6,
3495
3390
  indexContextOverrides,
3496
3391
  timeoutMs: AMBIENT_PARK_WINDOW_MS,
3497
- trigger: 'ambient',
3498
3392
  ...(execution ? {
3499
3393
  resumeFromTaskId: execution.taskId,
3500
3394
  continuationSettlementId: execution.settlementId,
@@ -0,0 +1,2 @@
1
+ /** Strict, default-off gate for finding opportunities on behalf of another user. */
2
+ export declare function isIntroducerDiscoveryEnabled(): boolean;
@@ -0,0 +1,4 @@
1
+ /** Strict, default-off gate for finding opportunities on behalf of another user. */
2
+ export function isIntroducerDiscoveryEnabled() {
3
+ return process.env.INTRODUCER_DISCOVERY_ENABLED === "true";
4
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * IND-593: protocol-owned owner-approval authority port.
3
+ *
4
+ * Every registered MCP-agent opportunity state change (send/accept/reject)
5
+ * requires an explicit owner-issued, fresh, atomically single-use proof bound
6
+ * to the exact opportunity, target action, owner principal, acting agent, and
7
+ * current server-derived interaction. The protocol owns this verifier/consumer
8
+ * contract and the fail-closed boundary in `update_opportunity`; the host
9
+ * injects the concrete authority (challenge store + signed-proof issuance).
10
+ *
11
+ * Direct authenticated-owner interactions (REST/chat/CLI) traverse the same
12
+ * boundary through `attestOwnerInteraction`: the host attests the
13
+ * server-derived principal and interaction provenance of the already
14
+ * authenticated owner session. Caller-controlled identity or proof-binding
15
+ * fields are never trusted by either path — the binding below is always
16
+ * derived from the resolved tool context.
17
+ *
18
+ * A2A negotiation approvals, agent self-acknowledgment
19
+ * (`acknowledgedUptakeQuestionIds`), and server-generated advisory/challenge
20
+ * values are explicitly NOT substitutes for owner authorization.
21
+ */
22
+ /** Owner-gated opportunity actions. `expired` is a system transition and is not gated. */
23
+ export type OpportunityOwnerAction = 'send' | 'accept' | 'reject';
24
+ /** Maps an update_opportunity target status to its owner-gated action, or null when ungated. */
25
+ export declare function opportunityOwnerActionForStatus(status: string): OpportunityOwnerAction | null;
26
+ /**
27
+ * Server-derived proof binding. Built exclusively from the resolved tool
28
+ * context and validated tool input — never from caller-supplied binding
29
+ * fields. `agentId` is present exactly when a registered agent acts.
30
+ */
31
+ export interface OpportunityOwnerApprovalBinding {
32
+ opportunityId: string;
33
+ action: OpportunityOwnerAction;
34
+ ownerId: string;
35
+ agentId?: string;
36
+ }
37
+ /** Server-derived transport surface of the current interaction. */
38
+ export type OpportunityOwnerInteractionSurface = 'mcp' | 'chat' | 'rest';
39
+ /**
40
+ * Trusted interaction/surface provenance for a non-agent tool call (IND-593
41
+ * Batch B). Derived exclusively from the resolved server context — the host
42
+ * composition binds `isSessionAuth` from the authenticated request identity,
43
+ * and the surface follows the composition root (MCP server, chat factory,
44
+ * REST tool service). Tool arguments and caller-supplied fields can never
45
+ * populate it. The host authority attests ONLY a genuine direct owner
46
+ * session (`sessionAuthenticated` on a non-chat surface); chat/CLI/H2A/A2A
47
+ * and other mediated surfaces fail closed with `untrusted_provenance`.
48
+ */
49
+ export interface OpportunityOwnerInteractionProvenance {
50
+ surface: OpportunityOwnerInteractionSurface;
51
+ /** True only when the host bound an authenticated owner session (never an API key or a mediated orchestrator) to this exact request. */
52
+ sessionAuthenticated: boolean;
53
+ }
54
+ /** Attestation input: the server-derived binding plus trusted provenance. */
55
+ export type OpportunityOwnerApprovalAttestation = OpportunityOwnerApprovalBinding & {
56
+ provenance: OpportunityOwnerInteractionProvenance;
57
+ };
58
+ /** Stable, testable denial reasons for the fail-closed owner-approval boundary. */
59
+ export type OpportunityOwnerApprovalDenialReason =
60
+ /** No proof presented (or no authority wired); a fresh interaction challenge is attached. */
61
+ 'missing'
62
+ /** Proof fails authenticity/structure checks (bad signature, unknown interaction, foreign token). */
63
+ | 'forged'
64
+ /** Proof or underlying challenge has expired. */
65
+ | 'stale'
66
+ /** Proof is not bound to an exact opportunity/action/owner/agent/interaction. */
67
+ | 'generic'
68
+ /** Proof binding mismatches the current server-derived binding. */
69
+ | 'wrong_owner' | 'wrong_agent' | 'wrong_action' | 'wrong_opportunity'
70
+ /** Proof was already consumed; single-use is atomic. */
71
+ | 'replayed'
72
+ /** The interaction's server-derived provenance cannot mint or attest owner authority; an owner-issued proof is required. */
73
+ | 'untrusted_provenance'
74
+ /** The authority's store or configuration failed; nothing is admitted. */
75
+ | 'unavailable';
76
+ /**
77
+ * Fresh, server-derived interaction challenge returned with a `missing`
78
+ * denial. The owner explicitly approves this exact interaction (bound to the
79
+ * full server-derived binding) to obtain a one-time proof.
80
+ */
81
+ export interface OpportunityOwnerApprovalChallenge {
82
+ interactionId: string;
83
+ expiresAt: string;
84
+ }
85
+ export type OpportunityOwnerApprovalVerdict = {
86
+ kind: 'admitted';
87
+ } | {
88
+ kind: 'denied';
89
+ reason: OpportunityOwnerApprovalDenialReason;
90
+ challenge?: OpportunityOwnerApprovalChallenge;
91
+ };
92
+ /**
93
+ * Authoritative owner-proof verifier/consumer, injected by the host.
94
+ * Implementations MUST fail closed and MUST make consumption atomically
95
+ * single-use: a denied verification never consumes a proof; a successful one
96
+ * consumes it exactly once, even under concurrent calls.
97
+ */
98
+ export interface OpportunityOwnerApprovalAuthority {
99
+ /**
100
+ * Verify and atomically consume an agent-presented proof against the
101
+ * server-derived binding. When `proof` is undefined the implementation
102
+ * registers the current interaction as a fresh challenge and denies with
103
+ * reason `missing`, attaching the challenge so the agent can relay it to the
104
+ * owner for explicit approval.
105
+ */
106
+ consumeAgentProof(proof: string | undefined, binding: OpportunityOwnerApprovalBinding & {
107
+ agentId: string;
108
+ }): Promise<OpportunityOwnerApprovalVerdict>;
109
+ /**
110
+ * Traverse the same boundary for a non-agent interaction. The host attests
111
+ * ONLY a genuine direct authenticated-owner session, judged from the
112
+ * server-derived principal and interaction/surface provenance — this is
113
+ * authoritative host derivation, not a bypass. Mediated surfaces
114
+ * (chat/CLI/H2A/A2A) and absent or malformed provenance fail closed with
115
+ * `untrusted_provenance`.
116
+ */
117
+ attestOwnerInteraction(binding: OpportunityOwnerApprovalAttestation): Promise<OpportunityOwnerApprovalVerdict>;
118
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * IND-593: protocol-owned owner-approval authority port.
3
+ *
4
+ * Every registered MCP-agent opportunity state change (send/accept/reject)
5
+ * requires an explicit owner-issued, fresh, atomically single-use proof bound
6
+ * to the exact opportunity, target action, owner principal, acting agent, and
7
+ * current server-derived interaction. The protocol owns this verifier/consumer
8
+ * contract and the fail-closed boundary in `update_opportunity`; the host
9
+ * injects the concrete authority (challenge store + signed-proof issuance).
10
+ *
11
+ * Direct authenticated-owner interactions (REST/chat/CLI) traverse the same
12
+ * boundary through `attestOwnerInteraction`: the host attests the
13
+ * server-derived principal and interaction provenance of the already
14
+ * authenticated owner session. Caller-controlled identity or proof-binding
15
+ * fields are never trusted by either path — the binding below is always
16
+ * derived from the resolved tool context.
17
+ *
18
+ * A2A negotiation approvals, agent self-acknowledgment
19
+ * (`acknowledgedUptakeQuestionIds`), and server-generated advisory/challenge
20
+ * values are explicitly NOT substitutes for owner authorization.
21
+ */
22
+ /** Maps an update_opportunity target status to its owner-gated action, or null when ungated. */
23
+ export function opportunityOwnerActionForStatus(status) {
24
+ switch (status) {
25
+ case 'pending':
26
+ return 'send';
27
+ case 'accepted':
28
+ return 'accept';
29
+ case 'rejected':
30
+ return 'reject';
31
+ default:
32
+ return null;
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ import type { ResolvedToolContext } from "../../shared/agent/tool.helpers.js";
2
+ import type { OpportunityOwnerInteractionProvenance } from "./opportunity.owner-approval.js";
3
+ /** Bind authoritative host provenance to one resolved tool context. */
4
+ export declare function bindOwnerApprovalProvenance(context: ResolvedToolContext, provenance: OpportunityOwnerInteractionProvenance): void;
5
+ /** Read host-bound provenance without accepting any caller-supplied surrogate. */
6
+ export declare function ownerApprovalProvenanceFor(context: ResolvedToolContext): OpportunityOwnerInteractionProvenance | undefined;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Capability-local extension of the resolved tool context for IND-593.
3
+ *
4
+ * The host is the only writer: it binds provenance after authentication and
5
+ * before the registry invokes a tool. Tool input schemas never contain this
6
+ * field, so a caller cannot forge a direct-owner interaction through args or
7
+ * generic context data. Keeping this extension here avoids changing the shared
8
+ * helper that is part of the negotiation/question architecture cycle.
9
+ */
10
+ const OWNER_APPROVAL_PROVENANCE = Symbol("ownerApprovalProvenance");
11
+ function hasOwnerApprovalProvenance(context) {
12
+ return OWNER_APPROVAL_PROVENANCE in context;
13
+ }
14
+ /** Bind authoritative host provenance to one resolved tool context. */
15
+ export function bindOwnerApprovalProvenance(context, provenance) {
16
+ Object.defineProperty(context, OWNER_APPROVAL_PROVENANCE, {
17
+ value: Object.freeze({ ...provenance }),
18
+ enumerable: false,
19
+ configurable: false,
20
+ writable: false,
21
+ });
22
+ }
23
+ /** Read host-bound provenance without accepting any caller-supplied surrogate. */
24
+ export function ownerApprovalProvenanceFor(context) {
25
+ return hasOwnerApprovalProvenance(context)
26
+ ? context[OWNER_APPROVAL_PROVENANCE]
27
+ : undefined;
28
+ }
@@ -13,14 +13,14 @@ import type { ConnectLinkKind } from "../../shared/interfaces/connect-link.inter
13
13
  * flips the opp to accepted and opens the chat with the counterpart.
14
14
  * - `approve_introduction` — draft or latent opp where viewer is an unapproved
15
15
  * introducer. Clicking flips approved=true and triggers negotiation. The
16
- * `draft` case comes from `discover_opportunities` intro mode; the `latent`
16
+ * `draft` case represents a pending introduction; the `latent`
17
17
  * case comes from background-discovered connector-flow cards surfaced in
18
18
  * `list_opportunities`. In both, status remains pre-send and the `/c/<code>`
19
19
  * link is the only MCP path to approve.
20
20
  * - `outreach` — accepted opp where viewer is a non-introducer party.
21
21
  * Clicking opens the existing chat (no state change).
22
22
  * - `send_direct` — draft or latent opp where viewer is a non-introducer
23
- * party. Issued by `discover_opportunities` in direct (no-introducer)
23
+ * party. Issued for a direct opportunity without an introducer
24
24
  * mode: the match has already passed evaluation, the row exists in
25
25
  * draft state, and the sender just needs to release it. Clicking flips
26
26
  * the opp straight to accepted and opens the chat with a greeting —
@@ -128,4 +128,4 @@ export declare function buildMinimalOpportunityCard(opp: Opportunity, viewerId:
128
128
  * failures and drop permanent ones instead of re-spamming the ledger.
129
129
  */
130
130
  export type ConfirmDeliveryErrorCode = "unauthenticated" | "ledger_unavailable" | "invalid_opportunity_id" | "opportunity_not_found" | "not_authorized" | "confirm_failed";
131
- export declare function createOpportunityTools(defineTool: DefineTool, deps: OpportunityToolDeps): readonly [any, any, any, any, any, any];
131
+ export declare function createOpportunityTools(defineTool: DefineTool, deps: OpportunityToolDeps): readonly [any, any, any];