@indexnetwork/protocol 0.15.0-rc.38.1 → 0.17.0-rc.40.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.
- package/dist/chat/chat.agent.d.ts +7 -0
- package/dist/chat/chat.agent.d.ts.map +1 -1
- package/dist/chat/chat.agent.js +16 -2
- package/dist/chat/chat.agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/negotiation/negotiation.graph.d.ts +12 -0
- package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
- package/dist/negotiation/negotiation.graph.js +27 -5
- package/dist/negotiation/negotiation.graph.js.map +1 -1
- package/dist/negotiation/negotiation.state.d.ts +7 -1
- package/dist/negotiation/negotiation.state.d.ts.map +1 -1
- package/dist/negotiation/negotiation.state.js +8 -2
- package/dist/negotiation/negotiation.state.js.map +1 -1
- package/dist/negotiation/negotiation.tools.d.ts +10 -0
- package/dist/negotiation/negotiation.tools.d.ts.map +1 -1
- package/dist/negotiation/negotiation.tools.js +11 -1
- package/dist/negotiation/negotiation.tools.js.map +1 -1
- package/dist/opportunity/opportunity.discover.d.ts +19 -0
- package/dist/opportunity/opportunity.discover.d.ts.map +1 -1
- package/dist/opportunity/opportunity.discover.js +25 -2
- package/dist/opportunity/opportunity.discover.js.map +1 -1
- package/dist/opportunity/opportunity.enricher.d.ts +20 -1
- package/dist/opportunity/opportunity.enricher.d.ts.map +1 -1
- package/dist/opportunity/opportunity.enricher.js +15 -1
- package/dist/opportunity/opportunity.enricher.js.map +1 -1
- package/dist/opportunity/opportunity.graph.d.ts +58 -1
- package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +103 -11
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.state.d.ts +65 -0
- package/dist/opportunity/opportunity.state.d.ts.map +1 -1
- package/dist/opportunity/opportunity.state.js +50 -0
- package/dist/opportunity/opportunity.state.js.map +1 -1
- package/dist/opportunity/opportunity.tools.d.ts.map +1 -1
- package/dist/opportunity/opportunity.tools.js +15 -0
- package/dist/opportunity/opportunity.tools.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +13 -3
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/observability/request-context.d.ts +30 -1
- package/dist/shared/observability/request-context.d.ts.map +1 -1
- package/dist/shared/observability/request-context.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity.graph.d.ts","sourceRoot":"","sources":["../../src/opportunity/opportunity.graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,4CAA4C,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC/B,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"opportunity.graph.d.ts","sourceRoot":"","sources":["../../src/opportunity/opportunity.graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,12 +13,14 @@
|
|
|
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';
|
|
19
20
|
import { validateOpportunityActors } from './opportunity.utils.js';
|
|
20
21
|
import { persistOpportunities } from './opportunity.persist.js';
|
|
21
22
|
import { negotiateCandidates } from "../negotiation/negotiation.graph.js";
|
|
23
|
+
import { AMBIENT_PARK_WINDOW_MS } from "../negotiation/negotiation.tools.js";
|
|
22
24
|
import { protocolLogger, withCallLogging } from '../shared/observability/protocol.logger.js';
|
|
23
25
|
import { timed } from '../shared/observability/performance.js';
|
|
24
26
|
import { requestContext } from "../shared/observability/request-context.js";
|
|
@@ -1542,12 +1544,16 @@ export class OpportunityGraphFactory {
|
|
|
1542
1544
|
indexContextMap.set(networkId, prompt);
|
|
1543
1545
|
}));
|
|
1544
1546
|
// Decide turn timeout.
|
|
1545
|
-
// - Background/queue path (no conversationId): always
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
//
|
|
1547
|
+
// - Background/queue path (no conversationId): always the park-window
|
|
1548
|
+
// budget (AMBIENT_PARK_WINDOW_MS, 5 min). Turns park in
|
|
1549
|
+
// `waiting_for_agent` and are picked up via polling; the dispatcher
|
|
1550
|
+
// additionally short-circuits to the system agent when no personal
|
|
1551
|
+
// agent has a fresh heartbeat (see AgentDispatcherImpl).
|
|
1552
|
+
// - Chat path with a personal agent authorized: use the same park-window
|
|
1553
|
+
// so the dispatcher parks the turn and the user's personal agent can
|
|
1554
|
+
// pick it up via polling.
|
|
1555
|
+
// - Chat path with no personal agent: use a short timeout (30s) so the
|
|
1556
|
+
// system `Index Negotiator` kicks in without stalling the chat.
|
|
1551
1557
|
// Check the personal agent per unique candidate network so cross-network
|
|
1552
1558
|
// chat runs don't get a single authorized agent deciding the timeout for
|
|
1553
1559
|
// every candidate. Only use the long (polling) timeout when a personal
|
|
@@ -1559,21 +1565,69 @@ export class OpportunityGraphFactory {
|
|
|
1559
1565
|
? (await Promise.all(uniqueIndexIds.map((networkId) => this.agentDispatcher.hasPersonalAgent(discoveryUserId, { action: 'manage:negotiations', scopeType: 'network', scopeId: networkId }).catch(() => false)))).every(Boolean)
|
|
1560
1566
|
: false)
|
|
1561
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';
|
|
1562
1573
|
const useLongTimeout = !isChatPath || hasPersonalAgent;
|
|
1563
|
-
const timeoutMs =
|
|
1574
|
+
const timeoutMs = isOrchestrator
|
|
1575
|
+
? ORCHESTRATOR_PARK_WINDOW_MS
|
|
1576
|
+
: useLongTimeout ? AMBIENT_PARK_WINDOW_MS : 30000;
|
|
1564
1577
|
logger.info('negotiateNode timeout decision', {
|
|
1565
1578
|
discoveryUserId,
|
|
1579
|
+
trigger: state.trigger,
|
|
1566
1580
|
isChatPath,
|
|
1581
|
+
isOrchestrator,
|
|
1567
1582
|
hasDispatcher: !!this.agentDispatcher,
|
|
1568
1583
|
hasPersonalAgent,
|
|
1569
1584
|
useLongTimeout,
|
|
1570
1585
|
timeoutMs,
|
|
1571
1586
|
candidateCount: candidates.length,
|
|
1572
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;
|
|
1573
1626
|
const acceptedResults = await negotiateCandidates(this.negotiationGraph, sourceUser, candidates, { networkId: '', prompt: '' }, // base context, overridden per-candidate below
|
|
1574
1627
|
{ maxTurns, traceEmitter: traceEmitter ?? undefined,
|
|
1575
1628
|
indexContextOverrides: indexContextMap,
|
|
1576
|
-
timeoutMs
|
|
1629
|
+
timeoutMs,
|
|
1630
|
+
...(onCandidateResolved && { onCandidateResolved }) });
|
|
1577
1631
|
// No filtering: every candidate's outcome (accept/reject/stalled) was applied to its
|
|
1578
1632
|
// opportunity row by the negotiation graph's finalize node via the opportunityId we
|
|
1579
1633
|
// passed. state.opportunities stays as it was at persist time; DB has the new statuses.
|
|
@@ -1869,9 +1923,11 @@ export class OpportunityGraphFactory {
|
|
|
1869
1923
|
const persistNode = withNodeTrace("opportunity-persist", async (state) => {
|
|
1870
1924
|
return timed("OpportunityGraph.persist", async () => {
|
|
1871
1925
|
const startTime = Date.now();
|
|
1926
|
+
const initialStatus = resolveInitialStatus(state.trigger, state.options.initialStatus);
|
|
1872
1927
|
logger.verbose('[Graph:Persist] Starting persistence (dedup-v2)', {
|
|
1873
1928
|
opportunitiesToCreate: state.evaluatedOpportunities.length,
|
|
1874
|
-
|
|
1929
|
+
trigger: state.trigger,
|
|
1930
|
+
initialStatus,
|
|
1875
1931
|
});
|
|
1876
1932
|
if (state.evaluatedOpportunities.length === 0) {
|
|
1877
1933
|
logger.verbose('[Graph:Persist] No opportunities to persist');
|
|
@@ -1882,7 +1938,6 @@ export class OpportunityGraphFactory {
|
|
|
1882
1938
|
const reactivatedOpportunities = [];
|
|
1883
1939
|
const existingBetweenActors = [];
|
|
1884
1940
|
const now = new Date().toISOString();
|
|
1885
|
-
const initialStatus = state.options.initialStatus ?? 'pending';
|
|
1886
1941
|
// Only skip 'draft' (chat-only) opportunities during dedup.
|
|
1887
1942
|
// 'latent' must NOT be skipped — background discovery creates latent opportunities,
|
|
1888
1943
|
// and excluding them causes the same user pair to get duplicate opportunities
|
|
@@ -1891,6 +1946,41 @@ export class OpportunityGraphFactory {
|
|
|
1891
1946
|
const introducerUserForOnBehalf = state.onBehalfOfUserId
|
|
1892
1947
|
? await this.database.getUser(state.userId)
|
|
1893
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
|
+
}
|
|
1894
1984
|
for (const evaluated of state.evaluatedOpportunities) {
|
|
1895
1985
|
const indexIdForActors = state.networkId ?? evaluated.actors[0]?.networkId;
|
|
1896
1986
|
let actors;
|
|
@@ -2171,13 +2261,15 @@ export class OpportunityGraphFactory {
|
|
|
2171
2261
|
return {
|
|
2172
2262
|
opportunities: allOpportunities,
|
|
2173
2263
|
existingBetweenActors,
|
|
2264
|
+
dedupAlreadyAccepted,
|
|
2174
2265
|
trace: [{
|
|
2175
2266
|
node: "persist",
|
|
2176
|
-
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)`,
|
|
2177
2268
|
data: {
|
|
2178
2269
|
created: createdList.length,
|
|
2179
2270
|
reactivated: reactivatedOpportunities.length,
|
|
2180
2271
|
existingSkipped: existingBetweenActors.length,
|
|
2272
|
+
alreadyAccepted: dedupAlreadyAccepted.length,
|
|
2181
2273
|
totalOutput: allOpportunities.length,
|
|
2182
2274
|
durationMs: Date.now() - startTime,
|
|
2183
2275
|
},
|