@indexnetwork/protocol 6.14.0-rc.406.1 → 8.0.1-rc.417.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 (114) hide show
  1. package/CHANGELOG.md +307 -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/capabilities/signals.tools.port.d.ts +1 -1
  6. package/dist/chat/chat-streaming.types.d.ts +4 -5
  7. package/dist/chat/chat.agent.d.ts +0 -19
  8. package/dist/chat/chat.agent.js +1 -70
  9. package/dist/chat/chat.persona.d.ts +0 -6
  10. package/dist/chat/chat.persona.js +0 -1
  11. package/dist/chat/chat.prompt.js +10 -10
  12. package/dist/chat/chat.prompt.modules.js +0 -104
  13. package/dist/chat/chat.tools.js +11 -7
  14. package/dist/chat/negotiator.persona.js +0 -1
  15. package/dist/chat/onboarding.persona.js +0 -1
  16. package/dist/chat/reporter.persona.d.ts +1 -1
  17. package/dist/chat/reporter.persona.js +1 -2
  18. package/dist/chat/reporter.prompt.js +5 -5
  19. package/dist/chat/signal.persona.js +0 -1
  20. package/dist/communities/application/network.tools.js +48 -27
  21. package/dist/contacts/application/contact.tools.js +9 -9
  22. package/dist/index.d.ts +9 -2
  23. package/dist/index.js +5 -0
  24. package/dist/maintenance/maintenance.graph.js +7 -0
  25. package/dist/mcp/mcp.authorization-policy.d.ts +250 -0
  26. package/dist/mcp/mcp.authorization-policy.js +522 -0
  27. package/dist/mcp/mcp.server.d.ts +6 -29
  28. package/dist/mcp/mcp.server.js +202 -228
  29. package/dist/negotiation/application/negotiation.agent.js +29 -5
  30. package/dist/negotiation/application/negotiation.graph.d.ts +2 -5
  31. package/dist/negotiation/application/negotiation.graph.js +53 -23
  32. package/dist/negotiation/domain/index.d.ts +3 -0
  33. package/dist/negotiation/domain/index.js +3 -0
  34. package/dist/negotiation/domain/negotiation.deadlock.d.ts +7 -1
  35. package/dist/negotiation/domain/negotiation.deadlock.js +48 -1
  36. package/dist/negotiation/domain/negotiation.stance.contracts.d.ts +85 -0
  37. package/dist/negotiation/domain/negotiation.stance.contracts.js +140 -0
  38. package/dist/negotiation/domain/negotiation.state.d.ts +2 -3
  39. package/dist/negotiation/domain/negotiation.state.js +2 -3
  40. package/dist/negotiation/public/index.d.ts +2 -0
  41. package/dist/negotiation/public/index.js +2 -0
  42. package/dist/opportunity/application/index.d.ts +0 -3
  43. package/dist/opportunity/application/index.js +0 -3
  44. package/dist/opportunity/application/opportunity.graph.d.ts +0 -43
  45. package/dist/opportunity/application/opportunity.graph.js +7 -113
  46. package/dist/opportunity/application/opportunity.introducer-feature.d.ts +2 -0
  47. package/dist/opportunity/application/opportunity.introducer-feature.js +4 -0
  48. package/dist/opportunity/application/opportunity.owner-approval.d.ts +118 -0
  49. package/dist/opportunity/application/opportunity.owner-approval.js +34 -0
  50. package/dist/opportunity/application/opportunity.owner-provenance.d.ts +6 -0
  51. package/dist/opportunity/application/opportunity.owner-provenance.js +28 -0
  52. package/dist/opportunity/application/opportunity.tools.d.ts +3 -3
  53. package/dist/opportunity/application/opportunity.tools.js +77 -870
  54. package/dist/opportunity/discriminator/discriminator.shadow.js +17 -10
  55. package/dist/opportunity/discriminator/discriminator.types.d.ts +2 -0
  56. package/dist/opportunity/domain/index.d.ts +0 -2
  57. package/dist/opportunity/domain/index.js +0 -2
  58. package/dist/opportunity/domain/opportunity.state.d.ts +2 -70
  59. package/dist/opportunity/domain/opportunity.state.js +2 -55
  60. package/dist/opportunity/opportunity.owner-approval.d.ts +5 -0
  61. package/dist/opportunity/opportunity.owner-approval.js +5 -0
  62. package/dist/opportunity/ports/opportunity.tools.port.d.ts +12 -1
  63. package/dist/opportunity/public/index.d.ts +3 -3
  64. package/dist/opportunity/public/index.js +2 -1
  65. package/dist/participant-agents/application/agent.tools.d.ts +1 -1
  66. package/dist/participant-agents/application/agent.tools.js +44 -6
  67. package/dist/participant-agents/application/index.d.ts +5 -3
  68. package/dist/participant-agents/application/index.js +5 -3
  69. package/dist/questions/application/question.tools.js +31 -2
  70. package/dist/runtime/foreground/composition/tool.factory.d.ts +2 -1
  71. package/dist/runtime/foreground/composition/tool.factory.js +10 -2
  72. package/dist/runtime/foreground/composition/tool.registry.d.ts +16 -2
  73. package/dist/runtime/foreground/composition/tool.registry.js +41 -38
  74. package/dist/shared/agent/activity-projection.d.ts +271 -0
  75. package/dist/shared/agent/activity-projection.js +248 -0
  76. package/dist/shared/agent/canonical-guidance.d.ts +30 -0
  77. package/dist/shared/agent/canonical-guidance.js +249 -0
  78. package/dist/shared/agent/tool.helpers.d.ts +13 -17
  79. package/dist/shared/agent/tool.runtime.js +1 -3
  80. package/dist/shared/agent/utility.tools.d.ts +11 -1
  81. package/dist/shared/agent/utility.tools.js +126 -90
  82. package/dist/shared/interfaces/auth.interface.d.ts +2 -8
  83. package/dist/shared/interfaces/chat-session.interface.js +5 -0
  84. package/dist/shared/interfaces/database.interface.d.ts +12 -5
  85. package/dist/shared/observability/request-context.d.ts +4 -40
  86. package/dist/shared/schemas/mcp-auth.schema.d.ts +87 -15
  87. package/dist/shared/schemas/mcp-auth.schema.js +49 -1
  88. package/dist/signals/application/intent.graph.js +4 -15
  89. package/dist/signals/application/intent.tools.js +34 -14
  90. package/dist/signals/domain/intent.proposal.d.ts +35 -0
  91. package/dist/signals/domain/intent.proposal.js +12 -0
  92. package/package.json +10 -9
  93. package/dist/opportunity/application/opportunity.discover.d.ts +0 -215
  94. package/dist/opportunity/application/opportunity.discover.js +0 -793
  95. package/dist/opportunity/application/opportunity.discovery-continuation-finalization.d.ts +0 -34
  96. package/dist/opportunity/application/opportunity.discovery-continuation-finalization.js +0 -74
  97. package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.d.ts +0 -44
  98. package/dist/opportunity/application/opportunity.discovery-mcp-lifecycle-finalization.js +0 -92
  99. package/dist/opportunity/domain/opportunity.discovery-run-coalescing.d.ts +0 -33
  100. package/dist/opportunity/domain/opportunity.discovery-run-coalescing.js +0 -33
  101. package/dist/opportunity/domain/opportunity.discovery.contracts.d.ts +0 -45
  102. package/dist/opportunity/domain/opportunity.discovery.contracts.js +0 -1
  103. package/dist/opportunity/opportunity.discover.d.ts +0 -5
  104. package/dist/opportunity/opportunity.discover.js +0 -5
  105. package/dist/opportunity/opportunity.discovery-continuation-finalization.d.ts +0 -5
  106. package/dist/opportunity/opportunity.discovery-continuation-finalization.js +0 -5
  107. package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.d.ts +0 -5
  108. package/dist/opportunity/opportunity.discovery-mcp-lifecycle-finalization.js +0 -5
  109. package/dist/opportunity/opportunity.discovery-run-coalescing.d.ts +0 -5
  110. package/dist/opportunity/opportunity.discovery-run-coalescing.js +0 -5
  111. package/dist/opportunity/opportunity.discovery.contracts.d.ts +0 -5
  112. package/dist/opportunity/opportunity.discovery.contracts.js +0 -5
  113. package/dist/shared/interfaces/discovery-run.interface.d.ts +0 -70
  114. package/dist/shared/interfaces/discovery-run.interface.js +0 -1
@@ -3,6 +3,7 @@ import { IntentGraphState } from "../domain/intent.state.js";
3
3
  import { ExplicitIntentInferrer } from "./intent.inferrer.js";
4
4
  import { SemanticVerifier } from "./intent.verifier.js";
5
5
  import { DEFAULT_SPECIFICITY_WARNING } from "../domain/signal.specificity.js";
6
+ import { normalizeIntentDescription } from "../domain/intent.proposal.js";
6
7
  import { IntentReconciler } from "./intent.reconciler.js";
7
8
  import { getAbortSignalConfig } from "../../shared/agent/model-signal.js";
8
9
  import { protocolLogger } from "../../shared/observability/protocol.logger.js";
@@ -497,18 +498,6 @@ export class IntentGraphFactory {
497
498
  };
498
499
  });
499
500
  };
500
- /** Strip URLs and "More details at [url]" from intent payloads before persisting. */
501
- const sanitizePayload = (payload) => {
502
- if (!payload || typeof payload !== "string")
503
- return payload;
504
- const out = payload
505
- .replace(/\s*More details at\s*:?\s*https?:\/\/[^\s"'<>)\]]+/gi, "")
506
- .replace(/\s*See\s+https?:\/\/[^\s"'<>)\]]+\s+for\s+more[^.]*\.?/gi, "")
507
- .replace(/https?:\/\/[^\s"'<>)\]]+/g, "")
508
- .replace(/\s{2,}/g, " ")
509
- .trim();
510
- return out.replace(/[.,;]\s*$/, "").trim() || payload;
511
- };
512
501
  /**
513
502
  * Generate a flat embedding for an intent payload, swallowing failures so
514
503
  * persistence can continue without an embedding. `intentId` is logging-only
@@ -554,14 +543,14 @@ export class IntentGraphFactory {
554
543
  const verifiedIntentByPayload = new Map();
555
544
  for (const verifiedIntent of state.verifiedIntents) {
556
545
  verifiedIntentByPayload.set(verifiedIntent.description, verifiedIntent);
557
- verifiedIntentByPayload.set(sanitizePayload(verifiedIntent.description), verifiedIntent);
546
+ verifiedIntentByPayload.set(normalizeIntentDescription(verifiedIntent.description), verifiedIntent);
558
547
  }
559
548
  for (const action of actions) {
560
549
  const actionType = action.type.toLowerCase();
561
550
  try {
562
551
  if (actionType === 'create') {
563
552
  const createAction = action;
564
- const sanitizedPayload = sanitizePayload(createAction.payload);
553
+ const sanitizedPayload = normalizeIntentDescription(createAction.payload);
565
554
  const matchedVerifiedIntent = verifiedIntentByPayload.get(createAction.payload) ||
566
555
  verifiedIntentByPayload.get(sanitizedPayload);
567
556
  // Generate embedding for the intent payload
@@ -610,7 +599,7 @@ export class IntentGraphFactory {
610
599
  }
611
600
  else if (actionType === 'update') {
612
601
  const updateAction = action;
613
- const sanitizedPayload = sanitizePayload(updateAction.payload);
602
+ const sanitizedPayload = normalizeIntentDescription(updateAction.payload);
614
603
  const matchedVerifiedIntent = verifiedIntentByPayload.get(updateAction.payload) ||
615
604
  verifiedIntentByPayload.get(sanitizedPayload);
616
605
  // Regenerate embedding for the updated payload
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { IntentClarifier } from "./intent.clarifier.js";
3
3
  import { DEFAULT_SPECIFICITY_WARNING } from "../domain/signal.specificity.js";
4
+ import { normalizeIntentDescription } from "../domain/intent.proposal.js";
4
5
  import { protocolLogger } from "../../shared/observability/protocol.logger.js";
5
6
  import { traceGraph } from "../../shared/observability/trace.js";
6
7
  import { success, error, UUID_REGEX } from "../../shared/agent/tool.helpers.js";
@@ -265,11 +266,11 @@ export function createIntentTools(defineTool, deps) {
265
266
  "**What to pass:** A clear, concept-based description of what the user is looking for (e.g. 'Looking for an AI/ML co-founder in Berlin', " +
266
267
  "'Need a designer for a mobile app project'). If the user provided a URL, scrape it with scrape_url first and synthesize the content into a description.\n\n" +
267
268
  "**What happens:** The system runs inference (extracting structured intents), verification (checking specificity and speech-act type), " +
268
- "and returns a proposal widget. The proposal is NOT yet persisted — the user must approve it first.\n\n" +
269
+ "and durably stores an owner-scoped proposal containing the exact normalized description, optional network scope, and complete verifier output before returning a proposal widget. The intent itself is NOT yet persisted — the user must approve the proposal first.\n\n" +
269
270
  "**Returns:** An intent_proposal code block that MUST be included verbatim in the response. The frontend renders it as an interactive " +
270
271
  "card the user can approve or skip. On approval, the intent is persisted, indexed, and discovery begins.\n\n" +
271
- "**Next steps after approval:** The intent is automatically linked to relevant indexes. Call discover_opportunities(searchQuery) to explicitly trigger discovery, " +
272
- "or wait for background processing to find matches.\n\n" +
272
+ "**Next steps after approval:** The intent is automatically linked to relevant indexes and becomes eligible for background matching. " +
273
+ "Background processing creates opportunities when matches are found; use list_opportunities only to review persisted results.\n\n" +
273
274
  "**Specificity gate.** Before calling this tool, judge whether the description is concrete enough to be " +
274
275
  "useful for matching. If the user says \"find a job\", \"meet people\", or \"learn something\", that's too " +
275
276
  "vague — FIRST call read_user_contexts() + read_intents() to understand their context, THEN propose a " +
@@ -419,13 +420,32 @@ export function createIntentTools(defineTool, deps) {
419
420
  });
420
421
  }
421
422
  // ── Proposal path (for web chat with interactive cards) ──
422
- // Build intent_proposal code fences for each verified intent
423
- const proposalBlocks = verified.map((v) => {
423
+ // Persist complete verifier output before emitting any usable card.
424
+ if (!deps.intentProposalStore) {
425
+ return error("Verified intent proposals are unavailable because durable proposal storage is not configured.");
426
+ }
427
+ const proposals = [];
428
+ const proposalBlocks = [];
429
+ for (const v of verified) {
430
+ if (!v.verification) {
431
+ return error("Intent verification produced no authoritative analysis; no proposal was created.", debugSteps);
432
+ }
424
433
  const proposalId = crypto.randomUUID();
434
+ const normalizedDescription = normalizeIntentDescription(v.description);
425
435
  const isBroad = isBroadAttributiveIntent(v);
436
+ proposals.push({
437
+ proposalId,
438
+ userId: context.userId,
439
+ description: normalizedDescription,
440
+ ...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
441
+ analysis: {
442
+ verifierOutput: v.verification,
443
+ combinedScore: v.score ?? null,
444
+ },
445
+ });
426
446
  const data = {
427
447
  proposalId,
428
- description: v.description,
448
+ description: normalizedDescription,
429
449
  ...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
430
450
  confidence: v.score != null ? Math.round(v.score * 100) / 100 : null,
431
451
  speechActType: v.verification?.classification ?? null,
@@ -434,10 +454,11 @@ export function createIntentTools(defineTool, deps) {
434
454
  missingSelectionalConstraints: v.verification?.missing_selectional_constraints ?? [],
435
455
  specificityWarning: isBroad ? specificityWarningFor(v) : normalizeSpecificityWarning(v.verification?.specificity_warning),
436
456
  };
437
- return ("```intent_proposal\n" +
457
+ proposalBlocks.push("```intent_proposal\n" +
438
458
  sanitizeJsonForCodeFence(JSON.stringify(data)) +
439
459
  "\n```");
440
- });
460
+ }
461
+ await deps.intentProposalStore.createProposals(proposals);
441
462
  const blocksText = proposalBlocks.join("\n\n");
442
463
  return success({
443
464
  proposed: true,
@@ -453,8 +474,8 @@ export function createIntentTools(defineTool, deps) {
453
474
  });
454
475
  const updateIntent = defineTool({
455
476
  name: "update_intent",
456
- description: "Updates an existing intent's description. After updating, the system re-processes the intent through inference and verification, " +
457
- "re-evaluates its index assignments, and triggers fresh opportunity discovery with the new description.\n\n" +
477
+ description: "Updates an existing intent's description. After updating, the system re-processes it through inference and verification, " +
478
+ "re-evaluates its index assignments, and makes the approved signal eligible for background matching.\n\n" +
458
479
  "**When to use:** When the user wants to refine or change what they're looking for — e.g. narrowing scope, adding specificity, " +
459
480
  "or pivoting to a different need. Prefer updating over delete+create to preserve the intent's history and existing index links.\n\n" +
460
481
  "**Returns:** Updated `intentId` and `description`, plus a confirmation message. The intent's embeddings and index relevancy scores are recalculated automatically.",
@@ -585,7 +606,7 @@ export function createIntentTools(defineTool, deps) {
585
606
  // ─────────────────────────────────────────────────────────────────────────────
586
607
  const createIntentIndex = defineTool({
587
608
  name: "create_intent_index",
588
- description: "Manually links an intent to a network (community), making it visible to other members and eligible for opportunity discovery within that index. " +
609
+ description: "Manually links an intent to a network (community), making the approved signal eligible for background matching within that index. " +
589
610
  "Normally intents are auto-assigned to relevant indexes on creation, but use this to explicitly add an intent to an additional index.\n\n" +
590
611
  "**When to use:** When the user wants to share an existing intent with a specific community they belong to, " +
591
612
  "or when auto-assignment missed a network the user considers relevant.\n\n" +
@@ -713,7 +734,7 @@ export function createIntentTools(defineTool, deps) {
713
734
  const deleteIntentIndex = defineTool({
714
735
  name: "delete_intent_index",
715
736
  description: "Removes the link between an intent and a network. The intent itself is NOT deleted — it just stops being visible in that community " +
716
- "and no longer participates in opportunity discovery within that index. The intent may still be linked to other indexes.\n\n" +
737
+ "and is no longer eligible for background matching within that index. The intent may still be linked to other indexes.\n\n" +
717
738
  "**When to use:** When the user wants to withdraw an intent from a specific community without archiving it entirely. " +
718
739
  "Use read_intent_indexes first to verify the link exists.\n\n" +
719
740
  "**Returns:** Confirmation that the link was removed. To fully remove an intent, use delete_intent instead.",
@@ -766,8 +787,7 @@ export function createIntentTools(defineTool, deps) {
766
787
  description: "Text-searches the authenticated user's own active signals by description. Case-insensitive substring " +
767
788
  "match over the signal's payload and summary. Use when the user references a past signal they wrote " +
768
789
  '("find my signal about React mentorship") or wants to audit what they\'ve posted.\n\n' +
769
- "For discovery of OTHER users' signals that match a query, use discover_opportunities(searchQuery=...) " +
770
- "instead — that runs semantic matching across the user's networks.\n\n" +
790
+ "Approved signals are matched in the background. Use list_opportunities only to review persisted opportunities after background matching has produced them.\n\n" +
771
791
  "**Returns:** `intents: [{ id, payload, summary, createdAt }]`, most recent first, up to `limit` (default 25).",
772
792
  querySchema: z.object({
773
793
  query: z.string().min(1).describe("Text to match against payload and summary (case-insensitive)."),
@@ -0,0 +1,35 @@
1
+ /** Verifier output retained verbatim across the host persistence boundary. */
2
+ export interface IntentProposalVerifierOutput {
3
+ reasoning: string;
4
+ classification: "COMMISSIVE" | "DIRECTIVE" | "ASSERTIVE" | "EXPRESSIVE" | "DECLARATION" | "UNKNOWN";
5
+ felicity_scores: {
6
+ clarity: number;
7
+ authority: number;
8
+ sincerity: number;
9
+ };
10
+ semantic_entropy: number;
11
+ referential_anchor: string | null;
12
+ referential_breadth: "narrow" | "moderate" | "broad";
13
+ missing_selectional_constraints: Array<"role" | "outcome" | "location" | "timeframe" | "domain" | "concrete_need">;
14
+ specificity_warning: string | null;
15
+ flags: string[];
16
+ }
17
+ /** Complete server-authoritative analysis captured for a verified intent proposal. */
18
+ export interface IntentProposalAnalysis {
19
+ verifierOutput: IntentProposalVerifierOutput;
20
+ combinedScore: number | null;
21
+ }
22
+ /** One proposal persisted by the host before its display card is emitted. */
23
+ export interface PersistableIntentProposal {
24
+ proposalId: string;
25
+ userId: string;
26
+ description: string;
27
+ networkId?: string;
28
+ analysis: IntentProposalAnalysis;
29
+ }
30
+ /** Host persistence boundary for durable, owner-scoped intent proposals. */
31
+ export interface IntentProposalStore {
32
+ createProposals(proposals: PersistableIntentProposal[]): Promise<void>;
33
+ }
34
+ /** Normalize intent text exactly as the direct graph write path does. */
35
+ export declare function normalizeIntentDescription(description: string): string;
@@ -0,0 +1,12 @@
1
+ /** Normalize intent text exactly as the direct graph write path does. */
2
+ export function normalizeIntentDescription(description) {
3
+ if (!description || typeof description !== "string")
4
+ return description;
5
+ const normalized = description
6
+ .replace(/\s*More details at\s*:?\s*https?:\/\/[^\s"'<>)\]]+/gi, "")
7
+ .replace(/\s*See\s+https?:\/\/[^\s"'<>)\]]+\s+for\s+more[^.]*\.?/gi, "")
8
+ .replace(/https?:\/\/[^\s"'<>)\]]+/g, "")
9
+ .replace(/\s{2,}/g, " ")
10
+ .trim();
11
+ return normalized.replace(/[.,;]\s*$/, "").trim() || description;
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "6.14.0-rc.406.1",
3
+ "version": "8.0.1-rc.417.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -40,19 +40,20 @@
40
40
  "eval:opportunity": "bun --env-file=../../.env.test ./eval/opportunity/opportunity.eval.ts",
41
41
  "eval:clarification": "bun --env-file=../../.env.test ./eval/clarification/clarification.eval.ts",
42
42
  "eval:canary": "bun --env-file=../../.env.test ./eval/canary/canary.eval.ts",
43
+ "eval:stance": "bun --env-file=../../.env.test ./eval/stance/stance.eval.ts",
43
44
  "eval:view": "bun ./eval/viewer/viewer.eval.ts",
44
45
  "eval:verify": "bun ./eval/verify.ts"
45
46
  },
46
47
  "dependencies": {
47
- "@langchain/core": "^1.1.17",
48
- "@langchain/langgraph": "^1.1.2",
49
- "@langchain/openai": "^1.2.3",
50
- "@modelcontextprotocol/server": "^2.0.0-alpha.2",
51
- "zod": "^3.22.4"
48
+ "@langchain/core": "1.1.48",
49
+ "@langchain/langgraph": "1.3.2",
50
+ "@langchain/openai": "1.4.7",
51
+ "@modelcontextprotocol/server": "2.0.0",
52
+ "zod": "3.25.76"
52
53
  },
53
54
  "devDependencies": {
54
- "@types/node": "^25.5.2",
55
- "dotenv": "^16.3.1",
56
- "typescript": "^5.0.0"
55
+ "@types/node": "25.9.1",
56
+ "dotenv": "16.6.1",
57
+ "typescript": "5.9.3"
57
58
  }
58
59
  }
@@ -1,215 +0,0 @@
1
- /**
2
- * Run discovery from an ad-hoc query (e.g. chat "find me a mentor", "who needs a React developer").
3
- *
4
- * Invokes the opportunity graph with the query as sourceText. The HyDE graph's
5
- * LensInferrer automatically infers search lenses from the query, replacing the
6
- * old hardcoded strategy selection. Returns formatted candidates (enriched with
7
- * profile name/bio) for chat display.
8
- *
9
- * Used by the discover_opportunities chat tool.
10
- */
11
- import type { ChatGraphCompositeDatabase, UserRecord } from "../../shared/interfaces/database.interface.js";
12
- import type { Cache } from "../../shared/interfaces/cache.interface.js";
13
- import type { QuestionerEnqueueFn } from "../../capabilities/questions.enqueue.facade.js";
14
- import type { ToolScopeType } from "../../shared/agent/tool.scope.js";
15
- import { OpportunityPresenter, type OpportunityPresentationResult, type HomeCardPresentationResult } from "./opportunity.presenter.js";
16
- import type { ChatSummaryReader } from "../../shared/interfaces/chat-summary.interface.js";
17
- import type { QuestionGeneratorReader } from "../../shared/interfaces/question-generator.interface.js";
18
- import type { NegotiationSummaryReader } from "../../shared/interfaces/negotiation-summary.interface.js";
19
- import type { Question, QuestionStrategy } from "../../shared/schemas/question.schema.js";
20
- import type { DiscoverDebugStep } from '../domain/opportunity.discovery.contracts.js';
21
- /** Compiled opportunity graph (from OpportunityGraphFactory.createGraph()). */
22
- export type CompiledOpportunityGraph = ReturnType<import("./opportunity.graph.js").OpportunityGraphFactory["createGraph"]>;
23
- export interface DiscoverInput {
24
- /** Compiled opportunity graph (already has DB, embedder, cache, HyDE graph). */
25
- opportunityGraph: CompiledOpportunityGraph;
26
- /** Database for enriching candidates with profile (getProfile). */
27
- database: ChatGraphCompositeDatabase;
28
- userId: string;
29
- query: string;
30
- indexScope: string[];
31
- limit?: number;
32
- /** Optional intent to use as discovery source and for triggeredBy (e.g. from opportunity queue). */
33
- triggerIntentId?: string;
34
- /** When set, filter discovery candidates to this specific user only (direct connection). */
35
- targetUserId?: string;
36
- /** When set, discover on behalf of this user (introducer flow). The caller (userId) becomes the introducer. */
37
- onBehalfOfUserId?: string;
38
- /** When provided, each opportunity is enriched with personalized presentation (headline, personalizedSummary, suggestedAction). */
39
- presenter?: OpportunityPresenter;
40
- /**
41
- * When true, use the full home card presentation format (with narratorRemark, action labels, mutualIntentsLabel).
42
- * This enables rendering the same rich opportunity cards in chat as on the home page.
43
- */
44
- useHomeCardFormat?: boolean;
45
- /**
46
- * When true, skip the LLM presenter and return minimal card data only (faster for chat).
47
- * Sets homeCardPresentation and narratorChip from static labels and match reason.
48
- */
49
- minimalForChat?: boolean;
50
- /** When set (e.g. from chat), create opportunities as draft with context.conversationId = chatSessionId. */
51
- chatSessionId?: string;
52
- /** Redis cache for discovery pagination. When provided, remaining candidates are cached for continuation. */
53
- cache?: Cache;
54
- /**
55
- * Which flow is invoking discovery. Drives the graph's trigger-aware branches
56
- * in persist (initial status) and negotiate (park window + streaming). When
57
- * omitted, the graph defaults to 'ambient'. Pass 'orchestrator' from the
58
- * chat `discover_opportunities` tool so users see drafts stream in and the
59
- * accepted-pair lookup surfaces existing connections.
60
- */
61
- trigger?: 'ambient' | 'orchestrator';
62
- /**
63
- * MCP-only. When set, the opportunity graph's negotiate phase is capped at
64
- * this many milliseconds; on timeout the caller gets whichever candidates
65
- * finished, the rest stay in `negotiating` and finalize in the background.
66
- * Chat, ambient queue, and all other callers omit this — existing behavior.
67
- */
68
- negotiateTimeoutMs?: number;
69
- /** Optional read-through chat-session digest reader. Required for chatContext enrichment. */
70
- chatSummary?: ChatSummaryReader;
71
- /**
72
- * Optional negotiation summarizer. When provided, each post-negotiation digest
73
- * replaces the raw negotiation in the decision-question generator's input,
74
- * keeping that prompt small and predictable regardless of candidate count.
75
- * When omitted, a deterministic fallback digest is built per negotiation.
76
- */
77
- negotiationSummary?: NegotiationSummaryReader;
78
- /** Optional decision-question generator. When omitted, no questions are produced. */
79
- questionGenerator?: QuestionGeneratorReader;
80
- /**
81
- * Master switch for decision-question generation. When false, this code path
82
- * is skipped entirely regardless of trigger. The composition root passes
83
- * `isDiscoveryQuestionsEnabled()` (QUESTIONER_ENABLED + QUESTIONER_DISCOVERY_ENABLED).
84
- */
85
- enableQuestions?: boolean;
86
- /**
87
- * Optional async question enqueue callback. When provided, question generation
88
- * is dispatched asynchronously to the QuestionerQueue instead of running inline
89
- * via the `questionGenerator`. The callback receives an enqueue payload and
90
- * returns a promise that resolves when the job is enqueued (not when generation
91
- * completes).
92
- */
93
- questionerEnqueue?: QuestionerEnqueueFn;
94
- /** Focused request scope type for generated discovery questions. */
95
- scopeType?: ToolScopeType;
96
- /** Focused request scope id. When `scopeType === 'network'`, persisted as actor networkId. */
97
- scopeId?: string;
98
- }
99
- /** One formatted opportunity for chat (candidate-facing). */
100
- export interface FormattedDiscoveryCandidate {
101
- opportunityId: string;
102
- userId: string;
103
- name?: string;
104
- avatar?: string | null;
105
- bio?: string;
106
- matchReason: string;
107
- score: number;
108
- status?: string;
109
- /** Present when DiscoverInput.presenter was provided (basic presentation). */
110
- presentation?: OpportunityPresentationResult;
111
- /** Present when DiscoverInput.useHomeCardFormat is true (full home card contract). */
112
- homeCardPresentation?: HomeCardPresentationResult;
113
- /** Viewer's role in this opportunity. */
114
- viewerRole?: string;
115
- /** Whether the viewer (as introducer) has approved the introduction. */
116
- viewerApproved?: boolean;
117
- /** Timestamp set once this viewer has already acted on the opportunity. */
118
- viewerActedAt?: string | null;
119
- /** Full user record for the candidate (needed for socials / Telegram fallback). */
120
- candidateUser?: UserRecord | null;
121
- /** Whether the counterpart is a ghost (not yet onboarded) user. */
122
- isGhost?: boolean;
123
- /** Narrator chip for home card display (name + remark, with optional avatar/userId for introducer). */
124
- narratorChip?: {
125
- name: string;
126
- text: string;
127
- avatar?: string | null;
128
- userId?: string;
129
- };
130
- /** Second party in introducer arrow layout (candidate -> secondParty). Present when viewer is introducer. */
131
- secondParty?: {
132
- name: string;
133
- avatar?: string | null;
134
- userId?: string;
135
- };
136
- }
137
- /** One step for debug visibility (subgraph/subtask). */
138
- export type { DiscoverDebugStep } from '../domain/opportunity.discovery.contracts.js';
139
- /** One existing connection (no new opportunity created; user already has one with this person). */
140
- export interface ExistingConnection {
141
- userId: string;
142
- name: string;
143
- status?: string;
144
- opportunityId?: string;
145
- }
146
- export interface DiscoverResult {
147
- found: boolean;
148
- count: number;
149
- message?: string;
150
- opportunities?: FormattedDiscoveryCandidate[];
151
- /** Existing connections eligible for card display (draft, latent, or pending). Others are mention-only. */
152
- existingConnections?: ExistingConnection[];
153
- /** All existing connections for mention text (e.g. "You already have a connection with: X (pending), Y (draft)."). */
154
- existingConnectionsForMention?: ExistingConnection[];
155
- /**
156
- * Orchestrator-only: accepted opportunities the persist step found between the
157
- * discoverer and a candidate counterparty (status='accepted'). Populated from
158
- * OpportunityGraphState.dedupAlreadyAccepted. Used by the discover_opportunities
159
- * tool to tell the LLM "this pair is already connected — open the existing
160
- * chat rather than creating a new draft". Empty for the ambient trigger.
161
- */
162
- alreadyAcceptedPairs?: Array<{
163
- opportunityId: string;
164
- counterpartyUserId: string;
165
- }>;
166
- /** When true, the chat agent should call create_intent(suggestedIntentDescription) and retry discovery. */
167
- createIntentSuggested?: boolean;
168
- /** Description to pass to create_intent when createIntentSuggested is true. */
169
- suggestedIntentDescription?: string;
170
- /** Internal steps for copy-debug (select_strategies, opportunity_graph, enrich, etc.). */
171
- debugSteps?: DiscoverDebugStep[];
172
- /** Pagination metadata -- present when there are more unevaluated candidates. */
173
- pagination?: {
174
- discoveryId: string;
175
- evaluated: number;
176
- remaining: number;
177
- };
178
- /** 0–3 decision questions produced by the orchestrator path. Omitted when none. */
179
- questions?: Question[];
180
- /** Debug metadata for `debugMeta.discoveryQuestions` plumbing. */
181
- discoveryQuestionsDebug?: {
182
- inputMode: "transcripts" | "insights";
183
- finalCount: number;
184
- strategies: QuestionStrategy[];
185
- durationMs: number;
186
- };
187
- }
188
- /**
189
- * Run discovery from an ad-hoc query (e.g. "find me a mentor", "who needs a React developer").
190
- * The HyDE graph's LensInferrer automatically infers search lenses from the query.
191
- * Invokes the opportunity graph and returns formatted candidates suitable for chat display.
192
- */
193
- export declare function runDiscoverFromQuery(input: DiscoverInput): Promise<DiscoverResult>;
194
- /**
195
- * Continue a paginated discovery by evaluating the next batch of cached candidates.
196
- * Loads candidates from Redis, invokes the opportunity graph in continue_discovery mode,
197
- * then enriches and returns the results with updated pagination metadata.
198
- *
199
- * @param input - Continuation context (graph, database, cache, discoveryId, etc.).
200
- * @returns Discovery result with enriched opportunities and pagination state.
201
- */
202
- export declare function continueDiscovery(input: {
203
- opportunityGraph: CompiledOpportunityGraph;
204
- database: ChatGraphCompositeDatabase;
205
- cache: Cache;
206
- userId: string;
207
- discoveryId: string;
208
- /** If provided, validates the cached session's indexScope contains this network. */
209
- expectedIndexId?: string;
210
- limit?: number;
211
- chatSessionId?: string;
212
- minimalForChat?: boolean;
213
- presenter?: OpportunityPresenter;
214
- useHomeCardFormat?: boolean;
215
- }): Promise<DiscoverResult>;