@indexnetwork/protocol 0.16.0-rc.39.1 → 0.17.0-rc.41.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 (42) hide show
  1. package/README.md +0 -2
  2. package/dist/agent/agent.tools.d.ts +1 -1
  3. package/dist/agent/agent.tools.d.ts.map +1 -1
  4. package/dist/agent/agent.tools.js +5 -132
  5. package/dist/agent/agent.tools.js.map +1 -1
  6. package/dist/chat/chat.agent.d.ts +7 -0
  7. package/dist/chat/chat.agent.d.ts.map +1 -1
  8. package/dist/chat/chat.agent.js +16 -2
  9. package/dist/chat/chat.agent.js.map +1 -1
  10. package/dist/negotiation/negotiation.graph.d.ts +12 -0
  11. package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
  12. package/dist/negotiation/negotiation.graph.js +27 -5
  13. package/dist/negotiation/negotiation.graph.js.map +1 -1
  14. package/dist/opportunity/opportunity.discover.d.ts +19 -0
  15. package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
  16. package/dist/opportunity/opportunity.discover.js +25 -2
  17. package/dist/opportunity/opportunity.discover.js.map +1 -1
  18. package/dist/opportunity/opportunity.enricher.d.ts +20 -1
  19. package/dist/opportunity/opportunity.enricher.d.ts.map +1 -1
  20. package/dist/opportunity/opportunity.enricher.js +15 -1
  21. package/dist/opportunity/opportunity.enricher.js.map +1 -1
  22. package/dist/opportunity/opportunity.graph.d.ts +57 -0
  23. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  24. package/dist/opportunity/opportunity.graph.js +92 -5
  25. package/dist/opportunity/opportunity.graph.js.map +1 -1
  26. package/dist/opportunity/opportunity.state.d.ts +65 -0
  27. package/dist/opportunity/opportunity.state.d.ts.map +1 -1
  28. package/dist/opportunity/opportunity.state.js +50 -0
  29. package/dist/opportunity/opportunity.state.js.map +1 -1
  30. package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
  31. package/dist/opportunity/opportunity.tools.js +15 -0
  32. package/dist/opportunity/opportunity.tools.js.map +1 -1
  33. package/dist/shared/agent/tool.helpers.d.ts +3 -3
  34. package/dist/shared/agent/tool.helpers.js +3 -3
  35. package/dist/shared/interfaces/agent.interface.d.ts +2 -2
  36. package/dist/shared/interfaces/agent.interface.d.ts.map +1 -1
  37. package/dist/shared/interfaces/database.interface.d.ts +13 -3
  38. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  39. package/dist/shared/observability/request-context.d.ts +30 -1
  40. package/dist/shared/observability/request-context.d.ts.map +1 -1
  41. package/dist/shared/observability/request-context.js.map +1 -1
  42. package/package.json +1 -1
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { StateGraph, START, END } from '@langchain/langgraph';
15
15
  import { OpportunityGraphState, } from './opportunity.state.js';
16
+ import { resolveInitialStatus } from './opportunity.state.js';
16
17
  import { OpportunityEvaluator, } from './opportunity.evaluator.js';
17
18
  import { IntentIndexer } from '../intent/intent.indexer.js';
18
19
  import { getModelName } from '../shared/agent/model.config.js';
@@ -1564,21 +1565,69 @@ export class OpportunityGraphFactory {
1564
1565
  ? (await Promise.all(uniqueIndexIds.map((networkId) => this.agentDispatcher.hasPersonalAgent(discoveryUserId, { action: 'manage:negotiations', scopeType: 'network', scopeId: networkId }).catch(() => false)))).every(Boolean)
1565
1566
  : false)
1566
1567
  : false;
1568
+ // Orchestrator (chat-driven a2h) fan-out uses a tight 60s park window —
1569
+ // the user is watching the stream, so we cannot afford the 5-min ambient
1570
+ // budget. Ambient keeps its heartbeat-aware long/short split.
1571
+ const ORCHESTRATOR_PARK_WINDOW_MS = 60000;
1572
+ const isOrchestrator = state.trigger === 'orchestrator';
1567
1573
  const useLongTimeout = !isChatPath || hasPersonalAgent;
1568
- const timeoutMs = useLongTimeout ? AMBIENT_PARK_WINDOW_MS : 30000;
1574
+ const timeoutMs = isOrchestrator
1575
+ ? ORCHESTRATOR_PARK_WINDOW_MS
1576
+ : useLongTimeout ? AMBIENT_PARK_WINDOW_MS : 30000;
1569
1577
  logger.info('negotiateNode timeout decision', {
1570
1578
  discoveryUserId,
1579
+ trigger: state.trigger,
1571
1580
  isChatPath,
1581
+ isOrchestrator,
1572
1582
  hasDispatcher: !!this.agentDispatcher,
1573
1583
  hasPersonalAgent,
1574
1584
  useLongTimeout,
1575
1585
  timeoutMs,
1576
1586
  candidateCount: candidates.length,
1577
1587
  });
1588
+ // Orchestrator-only: per-candidate streaming hook. Each accepted
1589
+ // negotiation flips the opp from 'pending' (negotiation finalize's
1590
+ // default) to 'draft' (chat-only surface) and pushes an
1591
+ // `opportunity_draft_ready` event so the frontend can render it
1592
+ // inline as soon as it resolves, rather than waiting for the full
1593
+ // fan-out. Abort (e.g. user closed the chat) suppresses both the
1594
+ // status flip and the event — the in-flight negotiation finishes
1595
+ // naturally but its card never reaches the user.
1596
+ const onCandidateResolved = isOrchestrator
1597
+ ? async ({ candidate, accepted }) => {
1598
+ const abortSignal = requestContext.getStore()?.abortSignal;
1599
+ if (abortSignal?.aborted)
1600
+ return;
1601
+ if (!accepted || !candidate.opportunityId)
1602
+ return;
1603
+ // Only emit after a successful status flip — the frontend keys
1604
+ // cards off `opportunity.status === 'draft'`, so emitting a row
1605
+ // with its pre-flip status would render inconsistently. If the
1606
+ // flip fails we log and drop the event; the negotiation result
1607
+ // is still captured in acceptedResults for the final summary.
1608
+ const updated = await this.database
1609
+ .updateOpportunityStatus(candidate.opportunityId, 'draft')
1610
+ .catch((err) => {
1611
+ logger.warn('[Graph:Negotiate] failed to flip opp to draft; suppressing draft-ready event', {
1612
+ opportunityId: candidate.opportunityId,
1613
+ error: err,
1614
+ });
1615
+ return null;
1616
+ });
1617
+ if (!updated || abortSignal?.aborted)
1618
+ return;
1619
+ traceEmitter?.({
1620
+ type: 'opportunity_draft_ready',
1621
+ opportunityId: candidate.opportunityId,
1622
+ opportunity: updated,
1623
+ });
1624
+ }
1625
+ : undefined;
1578
1626
  const acceptedResults = await negotiateCandidates(this.negotiationGraph, sourceUser, candidates, { networkId: '', prompt: '' }, // base context, overridden per-candidate below
1579
1627
  { maxTurns, traceEmitter: traceEmitter ?? undefined,
1580
1628
  indexContextOverrides: indexContextMap,
1581
- timeoutMs });
1629
+ timeoutMs,
1630
+ ...(onCandidateResolved && { onCandidateResolved }) });
1582
1631
  // No filtering: every candidate's outcome (accept/reject/stalled) was applied to its
1583
1632
  // opportunity row by the negotiation graph's finalize node via the opportunityId we
1584
1633
  // passed. state.opportunities stays as it was at persist time; DB has the new statuses.
@@ -1874,9 +1923,11 @@ export class OpportunityGraphFactory {
1874
1923
  const persistNode = withNodeTrace("opportunity-persist", async (state) => {
1875
1924
  return timed("OpportunityGraph.persist", async () => {
1876
1925
  const startTime = Date.now();
1926
+ const initialStatus = resolveInitialStatus(state.trigger, state.options.initialStatus);
1877
1927
  logger.verbose('[Graph:Persist] Starting persistence (dedup-v2)', {
1878
1928
  opportunitiesToCreate: state.evaluatedOpportunities.length,
1879
- initialStatus: state.options.initialStatus ?? 'pending',
1929
+ trigger: state.trigger,
1930
+ initialStatus,
1880
1931
  });
1881
1932
  if (state.evaluatedOpportunities.length === 0) {
1882
1933
  logger.verbose('[Graph:Persist] No opportunities to persist');
@@ -1887,7 +1938,6 @@ export class OpportunityGraphFactory {
1887
1938
  const reactivatedOpportunities = [];
1888
1939
  const existingBetweenActors = [];
1889
1940
  const now = new Date().toISOString();
1890
- const initialStatus = state.options.initialStatus ?? 'pending';
1891
1941
  // Only skip 'draft' (chat-only) opportunities during dedup.
1892
1942
  // 'latent' must NOT be skipped — background discovery creates latent opportunities,
1893
1943
  // and excluding them causes the same user pair to get duplicate opportunities
@@ -1896,6 +1946,41 @@ export class OpportunityGraphFactory {
1896
1946
  const introducerUserForOnBehalf = state.onBehalfOfUserId
1897
1947
  ? await this.database.getUser(state.userId)
1898
1948
  : null;
1949
+ // Orchestrator-only: collect already-accepted pairs so Task 7's
1950
+ // create_opportunities tool can tell the LLM "these pairs are
1951
+ // already connected, surface the existing chat rather than
1952
+ // creating a new draft". Runs in parallel across unique
1953
+ // counterparties (a single evaluator pass can return multiple
1954
+ // opps per counterparty; we only hit the DB once per pair).
1955
+ // Failures are swallowed — the per-pair query is best-effort.
1956
+ const dedupAlreadyAccepted = [];
1957
+ if (state.trigger === 'orchestrator') {
1958
+ // Use the same viewer-resolution as evaluation/negotiate/persist
1959
+ // on-behalf branches so an introducer-driven orchestrator run
1960
+ // queries accepted opps between the *target* user and the
1961
+ // counterparty, not between the introducer and the counterparty.
1962
+ const dedupUserId = (state.onBehalfOfUserId ?? state.userId);
1963
+ const uniqueCounterparts = new Set();
1964
+ for (const evaluated of state.evaluatedOpportunities) {
1965
+ const candidateUserId = evaluated.actors.find(a => a.userId !== dedupUserId)?.userId;
1966
+ if (candidateUserId)
1967
+ uniqueCounterparts.add(candidateUserId);
1968
+ }
1969
+ const lookups = await Promise.all([...uniqueCounterparts].map(async (counterpartyUserId) => {
1970
+ const accepted = await this.database
1971
+ .getAcceptedOpportunitiesBetweenActors(dedupUserId, counterpartyUserId)
1972
+ .catch((err) => {
1973
+ logger.warn('[Graph:Persist] getAcceptedOpportunitiesBetweenActors failed', {
1974
+ userId: dedupUserId,
1975
+ counterpartyUserId,
1976
+ error: err,
1977
+ });
1978
+ return [];
1979
+ });
1980
+ return accepted.map((opp) => ({ opportunityId: opp.id, counterpartyUserId }));
1981
+ }));
1982
+ dedupAlreadyAccepted.push(...lookups.flat());
1983
+ }
1899
1984
  for (const evaluated of state.evaluatedOpportunities) {
1900
1985
  const indexIdForActors = state.networkId ?? evaluated.actors[0]?.networkId;
1901
1986
  let actors;
@@ -2176,13 +2261,15 @@ export class OpportunityGraphFactory {
2176
2261
  return {
2177
2262
  opportunities: allOpportunities,
2178
2263
  existingBetweenActors,
2264
+ dedupAlreadyAccepted,
2179
2265
  trace: [{
2180
2266
  node: "persist",
2181
- detail: `Created ${createdList.length}, reactivated ${reactivatedOpportunities.length}, ${existingBetweenActors.length} existing skipped`,
2267
+ detail: `Created ${createdList.length}, reactivated ${reactivatedOpportunities.length}, ${existingBetweenActors.length} existing skipped, ${dedupAlreadyAccepted.length} already-accepted pair(s)`,
2182
2268
  data: {
2183
2269
  created: createdList.length,
2184
2270
  reactivated: reactivatedOpportunities.length,
2185
2271
  existingSkipped: existingBetweenActors.length,
2272
+ alreadyAccepted: dedupAlreadyAccepted.length,
2186
2273
  totalOutput: allOpportunities.length,
2187
2274
  durationMs: Date.now() - startTime,
2188
2275
  },