@indexnetwork/protocol 4.5.0-rc.331.1 → 4.5.0-rc.333.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-streaming.types.d.ts +3 -1
- package/dist/chat/chat-streaming.types.d.ts.map +1 -1
- package/dist/chat/chat-streaming.types.js.map +1 -1
- package/dist/chat/chat.agent.d.ts +1 -1
- package/dist/chat/chat.agent.d.ts.map +1 -1
- package/dist/chat/chat.agent.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/negotiation/negotiation.agent.d.ts +21 -0
- package/dist/negotiation/negotiation.agent.d.ts.map +1 -1
- package/dist/negotiation/negotiation.agent.js +79 -11
- package/dist/negotiation/negotiation.agent.js.map +1 -1
- package/dist/negotiation/negotiation.graph.d.ts +55 -24
- package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
- package/dist/negotiation/negotiation.graph.js +106 -27
- package/dist/negotiation/negotiation.graph.js.map +1 -1
- package/dist/negotiation/negotiation.protocol.d.ts +287 -0
- package/dist/negotiation/negotiation.protocol.d.ts.map +1 -0
- package/dist/negotiation/negotiation.protocol.js +152 -0
- package/dist/negotiation/negotiation.protocol.js.map +1 -0
- package/dist/negotiation/negotiation.state.d.ts +34 -9
- package/dist/negotiation/negotiation.state.d.ts.map +1 -1
- package/dist/negotiation/negotiation.state.js +28 -4
- package/dist/negotiation/negotiation.state.js.map +1 -1
- package/dist/negotiation/negotiation.tools.d.ts.map +1 -1
- package/dist/negotiation/negotiation.tools.js +86 -31
- package/dist/negotiation/negotiation.tools.js.map +1 -1
- package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +9 -0
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/question.prompt.d.ts +1 -1
- package/dist/opportunity/question.prompt.d.ts.map +1 -1
- package/dist/opportunity/question.prompt.js.map +1 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.d.ts +6 -0
- package/dist/shared/interfaces/agent-dispatcher.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/agent-dispatcher.interface.js.map +1 -1
- package/dist/shared/interfaces/database.interface.d.ts +17 -0
- package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
- package/dist/shared/interfaces/database.interface.js.map +1 -1
- package/dist/shared/schemas/discovery-question.schema.d.ts +8 -8
- package/dist/shared/schemas/discovery-question.schema.js +1 -1
- package/dist/shared/schemas/discovery-question.schema.js.map +1 -1
- package/dist/shared/schemas/negotiation-state.schema.d.ts +19 -3
- package/dist/shared/schemas/negotiation-state.schema.d.ts.map +1 -1
- package/dist/shared/schemas/negotiation-state.schema.js +15 -1
- package/dist/shared/schemas/negotiation-state.schema.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { success, error } from '../shared/agent/tool.helpers.js';
|
|
3
3
|
import { IndexNegotiator } from './negotiation.agent.js';
|
|
4
|
+
import { allowedActionsFor, isTerminalAction, readProtocolVersion, rejectActionFor, resolveSeat, seatViolationMessage } from './negotiation.protocol.js';
|
|
5
|
+
import { NEGOTIATION_ACTIONS } from '../shared/schemas/negotiation-state.schema.js';
|
|
4
6
|
import { protocolLogger } from '../shared/observability/protocol.logger.js';
|
|
5
7
|
import { focusedNetworkId } from '../shared/agent/tool.scope.js';
|
|
6
8
|
const logger = protocolLogger('ChatTools:Negotiation');
|
|
@@ -107,9 +109,14 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
107
109
|
const lastTurnData = lastMessage
|
|
108
110
|
? lastMessage.parts?.find(p => p.kind === 'data')?.data
|
|
109
111
|
: undefined;
|
|
110
|
-
// Determine whose turn it is
|
|
112
|
+
// Determine whose turn it is from the last message's sender — not
|
|
113
|
+
// parity, which misattributes across continuation sessions. Rows
|
|
114
|
+
// without senderId (legacy) fall back to parity.
|
|
111
115
|
const turnCount = messages.length;
|
|
112
|
-
const
|
|
116
|
+
const lastSenderId = turnCount > 0 ? messages[turnCount - 1].senderId : null;
|
|
117
|
+
const currentSpeaker = lastSenderId
|
|
118
|
+
? (lastSenderId === `agent:${meta.sourceUserId}` ? 'candidate' : 'source')
|
|
119
|
+
: (turnCount % 2 === 0 ? 'source' : 'candidate');
|
|
113
120
|
// Map task state to tool status
|
|
114
121
|
const status = task.state === 'working' ? 'active'
|
|
115
122
|
: task.state === 'waiting_for_agent' ? 'waiting_for_agent'
|
|
@@ -138,7 +145,9 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
138
145
|
const recentMessages = messages.slice(-RECENT_TURNS_LIMIT);
|
|
139
146
|
const recentTurns = recentMessages.map((m, sliceIdx) => {
|
|
140
147
|
const absoluteIdx = messages.length - recentMessages.length + sliceIdx;
|
|
141
|
-
const speaker =
|
|
148
|
+
const speaker = m.senderId
|
|
149
|
+
? (m.senderId === `agent:${meta.sourceUserId}` ? 'source' : 'candidate')
|
|
150
|
+
: (absoluteIdx % 2 === 0 ? 'source' : 'candidate');
|
|
142
151
|
const td = m.parts?.find(p => p.kind === 'data')?.data;
|
|
143
152
|
return {
|
|
144
153
|
turnNumber: absoluteIdx + 1,
|
|
@@ -256,12 +265,15 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
256
265
|
negotiationDatabase.getMessagesForConversation(task.conversationId),
|
|
257
266
|
negotiationDatabase.getArtifactsForTask(task.id),
|
|
258
267
|
]);
|
|
259
|
-
// Parse turns from messages
|
|
268
|
+
// Parse turns from messages (speaker from senderId, not parity —
|
|
269
|
+
// continuations can start with either side speaking first)
|
|
260
270
|
const turns = messages.map((m, idx) => {
|
|
261
271
|
const dataPart = m.parts?.find(p => p.kind === 'data');
|
|
262
272
|
const turnData = dataPart?.data;
|
|
263
273
|
const turnNumber = idx + 1;
|
|
264
|
-
const speaker =
|
|
274
|
+
const speaker = m.senderId
|
|
275
|
+
? (m.senderId === `agent:${meta.sourceUserId}` ? 'source' : 'candidate')
|
|
276
|
+
: (turnNumber % 2 === 1 ? 'source' : 'candidate');
|
|
265
277
|
return {
|
|
266
278
|
turnNumber,
|
|
267
279
|
speaker,
|
|
@@ -278,9 +290,13 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
278
290
|
const outcome = outcomeArtifact
|
|
279
291
|
? outcomeArtifact.parts?.find(p => p.kind === 'data')?.data
|
|
280
292
|
: null;
|
|
281
|
-
// Determine whose turn it is
|
|
293
|
+
// Determine whose turn it is (last sender's counterpart, not parity;
|
|
294
|
+
// rows without senderId fall back to parity)
|
|
282
295
|
const turnCount = messages.length;
|
|
283
|
-
const
|
|
296
|
+
const lastSenderId = turnCount > 0 ? messages[turnCount - 1].senderId : null;
|
|
297
|
+
const currentSpeaker = lastSenderId
|
|
298
|
+
? (lastSenderId === `agent:${meta.sourceUserId}` ? 'candidate' : 'source')
|
|
299
|
+
: (turnCount % 2 === 0 ? 'source' : 'candidate');
|
|
284
300
|
const status = task.state === 'working' ? 'active'
|
|
285
301
|
: task.state === 'waiting_for_agent' ? 'waiting_for_agent'
|
|
286
302
|
: task.state === 'completed' ? 'completed'
|
|
@@ -289,11 +305,18 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
289
305
|
((isSource && currentSpeaker === 'source') || (!isSource && currentSpeaker === 'candidate'));
|
|
290
306
|
const isContinuation = meta.isContinuation ?? false;
|
|
291
307
|
const priorTurnCount = meta.priorTurnCount ?? 0;
|
|
308
|
+
// Seat + protocol version (v2 client-advocate): announce the caller's
|
|
309
|
+
// seat and the actions it may submit so agents don't guess.
|
|
310
|
+
const protocolVersion = readProtocolVersion(meta) ?? 'v1';
|
|
311
|
+
const seat = resolveSeat(context.userId, meta);
|
|
292
312
|
return success({
|
|
293
313
|
id: task.id,
|
|
294
314
|
conversationId: task.conversationId,
|
|
295
315
|
status,
|
|
296
316
|
role: isSource ? 'source' : 'candidate',
|
|
317
|
+
seat,
|
|
318
|
+
protocolVersion,
|
|
319
|
+
allowedActions: allowedActionsFor(protocolVersion, seat),
|
|
297
320
|
counterpartyId: counterpartyId ?? 'unknown',
|
|
298
321
|
turnCount,
|
|
299
322
|
isUsersTurn,
|
|
@@ -319,22 +342,25 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
319
342
|
'by accepting, rejecting, countering, or asking a clarifying question.\n\n' +
|
|
320
343
|
'**Turn-based model:** Negotiations alternate between source and candidate agents. When the graph yields with ' +
|
|
321
344
|
'`waiting_for_agent` status, the user whose turn it is can respond.\n\n' +
|
|
322
|
-
'**Valid actions
|
|
323
|
-
'
|
|
324
|
-
'
|
|
325
|
-
'
|
|
326
|
-
'- `
|
|
327
|
-
'
|
|
328
|
-
'
|
|
345
|
+
'**Valid actions depend on the negotiation protocol version and your seat** — call get_negotiation first: ' +
|
|
346
|
+
'its `seat`, `protocolVersion`, and `allowedActions` fields tell you exactly what you may submit.\n\n' +
|
|
347
|
+
'**v1 negotiations (legacy):** `propose | accept | reject | counter | question` — on the first turn the action MUST be `propose`.\n\n' +
|
|
348
|
+
'**v2 negotiations (client-advocate seat rules):**\n' +
|
|
349
|
+
'- Initiator seat (`outreach | counter | question | withdraw`): you reached out — you can NEVER accept. ' +
|
|
350
|
+
'`outreach` opens the negotiation; `withdraw` ends it without an opportunity.\n' +
|
|
351
|
+
'- Counterparty seat (`accept | decline | counter | question`): only your seat can `accept` (finalizes an opportunity); ' +
|
|
352
|
+
'`decline` ends the negotiation without one.\n\n' +
|
|
353
|
+
'- `counter` — Counter with a message (message is required). The negotiation continues.\n' +
|
|
354
|
+
'- `question` — Ask the other side a clarifying question (message is required). The negotiation continues.\n\n' +
|
|
355
|
+
'**What happens after:** Terminal actions (accept/reject/withdraw/decline) finalize the negotiation immediately. ' +
|
|
356
|
+
'Counter/question continues — if the counterparty has an agent, the negotiation yields again; otherwise the AI agent responds inline.\n\n' +
|
|
329
357
|
'**Silent-subagent response contract.** In negotiation-turn mode, submit exactly ONE call to this tool ' +
|
|
330
|
-
'per dispatch with
|
|
331
|
-
'
|
|
332
|
-
'
|
|
333
|
-
'(turnCount === 0) the action MUST be `propose`. Do not ask the user clarifying questions; you are ' +
|
|
334
|
-
'authorized to act on their behalf within the scope granted to your agent.',
|
|
358
|
+
'per dispatch with an action from your seat\'s allowed set and the assessment (reasoning + suggestedRoles). ' +
|
|
359
|
+
'If the decision is ambiguous, pick the most conservative action — usually `counter` with specific objections. ' +
|
|
360
|
+
'Do not ask the user clarifying questions; you are authorized to act on their behalf within the scope granted to your agent.',
|
|
335
361
|
querySchema: z.object({
|
|
336
362
|
negotiationId: z.string().describe('The negotiation task ID to respond to.'),
|
|
337
|
-
action: z.enum(
|
|
363
|
+
action: z.enum(NEGOTIATION_ACTIONS).describe('The response action. Must be within your seat\'s allowedActions (see get_negotiation). v1 first turn MUST be "propose"; v2 initiator first turn MUST be "outreach".'),
|
|
338
364
|
reasoning: z.string().describe('Why you are taking this action — your assessment of the opportunity.'),
|
|
339
365
|
suggestedRoles: z.object({
|
|
340
366
|
ownUser: z.enum(['agent', 'patient', 'peer']).describe('Suggested role for your user in this opportunity.'),
|
|
@@ -371,14 +397,29 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
371
397
|
if (!isSource && !isCandidate) {
|
|
372
398
|
return error('Access denied: you are not a party to this negotiation.');
|
|
373
399
|
}
|
|
374
|
-
//
|
|
400
|
+
// Seat + version validation (v2 client-advocate): the submitted action
|
|
401
|
+
// must be within the caller's seat vocabulary. v1 tasks accept the
|
|
402
|
+
// legacy vocabulary unchanged (grandfathered).
|
|
403
|
+
const protocolVersion = readProtocolVersion(meta) ?? 'v1';
|
|
404
|
+
const seat = resolveSeat(context.userId, meta);
|
|
405
|
+
if (!allowedActionsFor(protocolVersion, seat).includes(query.action)) {
|
|
406
|
+
return error(seatViolationMessage(query.action, seat, protocolVersion));
|
|
407
|
+
}
|
|
408
|
+
// Determine whose turn it is from the last message's sender — not
|
|
409
|
+
// parity, which misattributes across continuation sessions. Rows
|
|
410
|
+
// without senderId (legacy) fall back to the parity heuristic.
|
|
375
411
|
const messages = await negotiationDatabase.getMessagesForConversation(task.conversationId);
|
|
376
412
|
const turnCount = messages.length;
|
|
377
|
-
const
|
|
378
|
-
const
|
|
413
|
+
const lastSenderId = turnCount > 0 ? messages[turnCount - 1].senderId : null;
|
|
414
|
+
const paritySpeaker = turnCount % 2 === 0 ? 'source' : 'candidate';
|
|
415
|
+
const isUsersTurn = lastSenderId
|
|
416
|
+
? lastSenderId !== `agent:${context.userId}`
|
|
417
|
+
: ((isSource && paritySpeaker === 'source') || (!isSource && paritySpeaker === 'candidate'));
|
|
379
418
|
if (!isUsersTurn) {
|
|
380
419
|
return error('It is not your turn to respond in this negotiation.');
|
|
381
420
|
}
|
|
421
|
+
// The caller is the current speaker (verified above).
|
|
422
|
+
const currentSpeaker = isSource ? 'source' : 'candidate';
|
|
382
423
|
// Validate counter/question has a message
|
|
383
424
|
if ((query.action === 'counter' || query.action === 'question') && !query.message?.trim()) {
|
|
384
425
|
return error(`A message is required when using "${query.action}". Explain what you want to change or clarify.`);
|
|
@@ -405,8 +446,8 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
405
446
|
taskId: task.id,
|
|
406
447
|
});
|
|
407
448
|
const newTurnCount = turnCount + 1;
|
|
408
|
-
// ── Handle accept/reject: finalize immediately ──
|
|
409
|
-
if (query.action
|
|
449
|
+
// ── Handle terminal actions (accept / reject / withdraw / decline): finalize immediately ──
|
|
450
|
+
if (isTerminalAction(query.action)) {
|
|
410
451
|
const allMessages = [...messages, { id: turnMessage.id, senderId: turnMessage.senderId, role: turnMessage.role, parts: turnMessage.parts, createdAt: turnMessage.createdAt }];
|
|
411
452
|
const history = turnsFromMessages(allMessages);
|
|
412
453
|
const nextSpeaker = currentSpeaker === 'source' ? 'candidate' : 'source';
|
|
@@ -421,7 +462,11 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
421
462
|
return success({
|
|
422
463
|
message: query.action === 'accept'
|
|
423
464
|
? 'Negotiation accepted. An opportunity has been created.'
|
|
424
|
-
:
|
|
465
|
+
: query.action === 'withdraw'
|
|
466
|
+
? 'Negotiation withdrawn.'
|
|
467
|
+
: query.action === 'decline'
|
|
468
|
+
? 'Negotiation declined.'
|
|
469
|
+
: 'Negotiation rejected.',
|
|
425
470
|
negotiationId: task.id,
|
|
426
471
|
action: query.action,
|
|
427
472
|
turnNumber: newTurnCount,
|
|
@@ -454,6 +499,7 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
454
499
|
// ── Counter/question under max turns: dispatch to counterparty's agent ──
|
|
455
500
|
const counterpartyUserId = isSource ? meta.candidateUserId : meta.sourceUserId;
|
|
456
501
|
const counterpartySpeaker = isSource ? 'candidate' : 'source';
|
|
502
|
+
const counterpartySeat = resolveSeat(counterpartyUserId, meta);
|
|
457
503
|
// Build the current turn history for dispatcher payload
|
|
458
504
|
const allMessagesWithTurn = [...messages, { id: turnMessage.id, senderId: turnMessage.senderId, role: turnMessage.role, parts: turnMessage.parts, createdAt: turnMessage.createdAt }];
|
|
459
505
|
const historyForDispatch = turnsFromMessages(allMessagesWithTurn);
|
|
@@ -470,6 +516,9 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
470
516
|
history: historyForDispatch,
|
|
471
517
|
isFinalTurn,
|
|
472
518
|
isDiscoverer: false,
|
|
519
|
+
seat: counterpartySeat,
|
|
520
|
+
protocolVersion,
|
|
521
|
+
allowedActions: [...allowedActionsFor(protocolVersion, counterpartySeat, isFinalTurn)],
|
|
473
522
|
};
|
|
474
523
|
const scope = { action: 'negotiation.respond', scopeType: 'negotiation', scopeId: task.id };
|
|
475
524
|
const timeoutMs = AMBIENT_PARK_WINDOW_MS;
|
|
@@ -481,7 +530,7 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
481
530
|
await deps.negotiationTimeoutQueue.enqueueTimeout(task.id, newTurnCount, timeoutMs);
|
|
482
531
|
}
|
|
483
532
|
return success({
|
|
484
|
-
message: `${query.action === 'question' ? 'Question' : query.action === 'propose' ? 'Proposal' : 'Counter-proposal'} submitted. Waiting for counterparty response.`,
|
|
533
|
+
message: `${query.action === 'question' ? 'Question' : query.action === 'propose' ? 'Proposal' : query.action === 'outreach' ? 'Outreach' : 'Counter-proposal'} submitted. Waiting for counterparty response.`,
|
|
485
534
|
negotiationId: task.id,
|
|
486
535
|
action: query.action,
|
|
487
536
|
turnNumber: newTurnCount,
|
|
@@ -510,6 +559,8 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
510
559
|
seedAssessment,
|
|
511
560
|
history: historyForDispatch,
|
|
512
561
|
isFinalTurn,
|
|
562
|
+
seat: counterpartySeat,
|
|
563
|
+
protocolVersion,
|
|
513
564
|
});
|
|
514
565
|
}
|
|
515
566
|
catch (err) {
|
|
@@ -528,7 +579,7 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
528
579
|
error: errMsg,
|
|
529
580
|
});
|
|
530
581
|
aiTurn = {
|
|
531
|
-
action:
|
|
582
|
+
action: rejectActionFor(protocolVersion, counterpartySeat),
|
|
532
583
|
assessment: {
|
|
533
584
|
reasoning: isTimeout
|
|
534
585
|
? 'Negotiator response timed out.'
|
|
@@ -549,7 +600,7 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
549
600
|
});
|
|
550
601
|
const finalTurnCount = newTurnCount + 1;
|
|
551
602
|
// Evaluate response
|
|
552
|
-
if (aiTurn.action
|
|
603
|
+
if (isTerminalAction(aiTurn.action)) {
|
|
553
604
|
const fullHistory = [...historyForDispatch, aiTurn];
|
|
554
605
|
const outcome = buildNegotiationOutcome(fullHistory, finalTurnCount, aiTurn.action, meta.sourceUserId, meta.candidateUserId, counterpartySpeaker === 'source' ? 'candidate' : 'source');
|
|
555
606
|
await negotiationDatabase.updateTaskState(task.id, 'completed');
|
|
@@ -598,6 +649,9 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
598
649
|
history: [...historyForDispatch, aiTurn],
|
|
599
650
|
isFinalTurn: finalTurnCount + 1 >= maxTurns,
|
|
600
651
|
isDiscoverer: true,
|
|
652
|
+
seat,
|
|
653
|
+
protocolVersion,
|
|
654
|
+
allowedActions: [...allowedActionsFor(protocolVersion, seat, finalTurnCount + 1 >= maxTurns)],
|
|
601
655
|
};
|
|
602
656
|
const userDispatchResult = await deps.agentDispatcher?.dispatch(context.userId, scope, userDispatchPayload, { timeoutMs });
|
|
603
657
|
if (!userDispatchResult || (userDispatchResult.handled === false && userDispatchResult.reason === 'no_agent')) {
|
|
@@ -642,7 +696,7 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
642
696
|
taskId: task.id,
|
|
643
697
|
});
|
|
644
698
|
const userTurnCount = finalTurnCount + 1;
|
|
645
|
-
if (userAgentTurn.action
|
|
699
|
+
if (isTerminalAction(userAgentTurn.action)) {
|
|
646
700
|
const fullHistory = [...historyForDispatch, aiTurn, userAgentTurn];
|
|
647
701
|
const userSpeaker = isSource ? 'source' : 'candidate';
|
|
648
702
|
const outcome = buildNegotiationOutcome(fullHistory, userTurnCount, userAgentTurn.action, meta.sourceUserId, meta.candidateUserId, userSpeaker === 'source' ? 'candidate' : 'source');
|
|
@@ -726,7 +780,8 @@ export function createNegotiationTools(defineTool, deps) {
|
|
|
726
780
|
*/
|
|
727
781
|
function buildNegotiationOutcome(history, turnCount, lastAction, sourceUserId, candidateUserId, currentSpeaker) {
|
|
728
782
|
const hasOpportunity = lastAction === 'accept';
|
|
729
|
-
|
|
783
|
+
// Non-terminal last action at finalization means the turn cap was hit.
|
|
784
|
+
const atCap = !isTerminalAction(lastAction);
|
|
730
785
|
let agreedRoles = [];
|
|
731
786
|
if (hasOpportunity && history.length >= 2) {
|
|
732
787
|
const acceptTurn = history[history.length - 1];
|