@indexnetwork/protocol 22.0.0-rc.494.1 → 22.0.0-rc.495.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/index.d.ts CHANGED
@@ -127,6 +127,6 @@ export { isNegotiationTurnCapReached, expectedNegotiationSpeaker, negotiationSco
127
127
  export type { NegotiationSpeakerParticipants, NegotiationSpeakerMessage, NegotiationScopeMetadata } from "./negotiations/negotiation.module.js";
128
128
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "./negotiations/negotiation.module.js";
129
129
  export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode } from "./negotiations/negotiation.module.js";
130
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, negotiationQuestionSettlementId, } from "./negotiations/negotiation.module.js";
130
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, negotiationQuestionSettlementId, } from "./negotiations/negotiation.module.js";
131
131
  export { classifyParkedNegotiation, consumeQuestionBlockAnswers, negotiationParkAnswerId, resumeParkedNegotiation, routeAnswerRef, } from "./negotiations/negotiation.module.js";
132
132
  export type { AnswerRoute, InflightAnswerSettlementInput, InflightAnswerSettlementResult, NegotiationAnswerConsumptionPorts, NegotiationAnswerInput, NegotiationAnswerResumeOutcome, ParkClassification, QuestionBlockAnswerConsumptionInput, QuestionBlockAnswerConsumptionResult, RoutedAnswer, } from "./negotiations/negotiation.module.js";
package/dist/index.js CHANGED
@@ -113,6 +113,6 @@ export { HERMES_OWNER_DIRECTIVE, HermesNegotiationResponseSchema, allowedHermesA
113
113
  // ─── Negotiation seat rules (v2 client-advocate protocol) ───────────────────
114
114
  export { isNegotiationTurnCapReached, expectedNegotiationSpeaker, negotiationScopeKey, readNegotiationMessages, allowedActionsFor, askUserAnswerWindowMs, configuredAskUserEnabled, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "./negotiations/negotiation.module.js";
115
115
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "./negotiations/negotiation.module.js";
116
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, negotiationQuestionSettlementId, } from "./negotiations/negotiation.module.js";
116
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, negotiationQuestionSettlementId, } from "./negotiations/negotiation.module.js";
117
117
  // ─── Negotiation answer consumption (conversational questions) ──────────────
118
118
  export { classifyParkedNegotiation, consumeQuestionBlockAnswers, negotiationParkAnswerId, resumeParkedNegotiation, routeAnswerRef, } from "./negotiations/negotiation.module.js";
@@ -238,9 +238,18 @@ ${stanceQuerySatisfiedRule(stance, otherName, userName)}`
238
238
  // worth having. What it may not do is stand in for this negotiation's own
239
239
  // exchange, so the policy line differs by whether this one has opened.
240
240
  const hasPriorDialogue = hasAttributedDialogue || input.history.length > 0;
241
+ // Three states, not two. `isContinuation` only says whether this
242
+ // negotiation spoke in an EARLIER session; on any turn after the opening
243
+ // it is still false while this negotiation is visibly mid-exchange. Under
244
+ // the old two-way split such a turn was told the signal was new and to
245
+ // "make your own case for it" — which, for the initiator seat, reads as
246
+ // an instruction to re-open, and produced a fresh outreach on every one
247
+ // of its turns instead of a reply.
241
248
  const priorDialoguePolicy = input.isContinuation
242
249
  ? 'Policy: You are continuing a prior dialogue. If this signal is materially the same as one you previously evaluated, you may resolve quickly. If materially different, evaluate on its own merits.'
243
- : 'Policy: This signal is NEW — you have not negotiated it before. The dialogue above concluded on other signals and is background only. Evaluate this one on its own merits and make your own case for it.';
250
+ : input.history.length > 0
251
+ ? 'Policy: This negotiation is already under way — the turns above under the current opportunity are THIS exchange. Respond to the counterparty\'s latest turn; do not restate or re-pitch your opening.'
252
+ : 'Policy: This signal is NEW — you have not negotiated it before. The dialogue above concluded on other signals and is background only. Evaluate this one on its own merits and make your own case for it.';
244
253
  const priorDialogueContext = hasPriorDialogue
245
254
  ? `\n\n--- Prior dialogue with this counterparty ---\n${attributionPreamble}${priorDialogueBody}\n\n--- New signal under evaluation ---\n${input.discoveryQuery
246
255
  ? `Discovery query: "${input.discoveryQuery}"`
@@ -45,6 +45,7 @@ export declare function turnNode(state: NegotiationState, deps: NegotiationGraph
45
45
  role: "agent";
46
46
  parts: unknown;
47
47
  createdAt: Date;
48
+ taskId: string;
48
49
  }[];
49
50
  turnCount: number;
50
51
  lastTurn: {
@@ -82,6 +83,7 @@ export declare function turnNode(state: NegotiationState, deps: NegotiationGraph
82
83
  role: "agent";
83
84
  parts: unknown;
84
85
  createdAt: Date;
86
+ taskId: string;
85
87
  }[];
86
88
  turnCount: number;
87
89
  currentSpeaker: "source" | "candidate";
@@ -495,6 +495,7 @@ export async function turnNode(state, deps) {
495
495
  role: "agent",
496
496
  parts: message.parts,
497
497
  createdAt: message.createdAt,
498
+ taskId: state.taskId,
498
499
  }],
499
500
  turnCount: state.turnCount + 1,
500
501
  lastTurn: turn,
@@ -518,12 +519,20 @@ export async function turnNode(state, deps) {
518
519
  });
519
520
  }
520
521
  return {
522
+ // `taskId` is what makes this turn part of THIS session's block in the
523
+ // attributed prior dialogue (IND-569). Persisted with the current task
524
+ // id above; carried onto the state message so `buildAttributedDialogue`
525
+ // can match it. Dropping it silently emptied the "[Current opportunity —
526
+ // under negotiation now]" block, which — because the attributed
527
+ // rendering replaces the flat history in the prompt — left every turn
528
+ // after the opening blind to the exchange it was answering.
521
529
  messages: [{
522
530
  id: message.id,
523
531
  senderId: message.senderId,
524
532
  role: "agent",
525
533
  parts: message.parts,
526
534
  createdAt: message.createdAt,
535
+ taskId: state.taskId,
527
536
  }],
528
537
  turnCount: state.turnCount + 1,
529
538
  currentSpeaker: (isSource ? "candidate" : "source"),
@@ -24,7 +24,7 @@ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached } from "./ne
24
24
  export { expectedNegotiationSpeaker } from "./negotiation.expected-speaker.js";
25
25
  export { negotiationScopeKey, readNegotiationMessages } from "./negotiation.scope.js";
26
26
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode, } from "./negotiation.consultation-policy.js";
27
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeAuthoredNegotiationQuestion, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.js";
27
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, isSafeAuthoredNegotiationQuestion, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.js";
28
28
  export { renderNegotiatorChatMemorySection } from "./negotiation.memory.js";
29
29
  export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode, NegotiationConsultationReason, } from "./negotiation.consultation-policy.js";
30
30
  export type { HermesNegotiationAction, HermesNegotiationResponse, HermesOwnerDirective, HermesRoleAlignment, } from "./negotiation.hermes-contract.js";
@@ -19,5 +19,5 @@ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached } from "./ne
19
19
  export { expectedNegotiationSpeaker } from "./negotiation.expected-speaker.js";
20
20
  export { negotiationScopeKey, readNegotiationMessages } from "./negotiation.scope.js";
21
21
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode, } from "./negotiation.consultation-policy.js";
22
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeAuthoredNegotiationQuestion, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.js";
22
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, isSafeAuthoredNegotiationQuestion, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.js";
23
23
  export { renderNegotiatorChatMemorySection } from "./negotiation.memory.js";
@@ -2,7 +2,6 @@ import type { StructuredQuestion } from '../shared/schemas/structured-question.s
2
2
  /** Fixed prompt-safe labels; producers must not replace them with raw network/counterparty text. */
3
3
  export declare const NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY = "the other participant";
4
4
  export declare const NEGOTIATION_QUESTION_GENERIC_NETWORK = "the selected network";
5
- export declare const NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY = "a potential collaboration that may require clarification before you decide";
6
5
  /**
7
6
  * Deterministically reject question context copied from private negotiation inputs.
8
7
  * This guard never rewrites content: unsafe or ambiguous text yields no card while
@@ -2,7 +2,6 @@ import { hasUnsupportedOpportunityClaim } from '../shared/utils/claim-safety.js'
2
2
  /** Fixed prompt-safe labels; producers must not replace them with raw network/counterparty text. */
3
3
  export const NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY = 'the other participant';
4
4
  export const NEGOTIATION_QUESTION_GENERIC_NETWORK = 'the selected network';
5
- export const NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY = 'a potential collaboration that may require clarification before you decide';
6
5
  const INTERNAL_ID_PATTERN = /\b(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|(?:task|intent|network|opportunity|user|match)[_-]?id)\b/i;
7
6
  const PRIVATE_SOURCE_PATTERN = /\b(?:private transcript|raw transcript|assessment(?:\.reasoning)?|seed assessment|evaluator reasoning|match reason|matchReason|internal metadata|counterparty profile)\b/i;
8
7
  function normalize(value) {
@@ -1,7 +1,7 @@
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';
4
+ import { allowedActionsFor, isRejectLikeAction, isTerminalAction, readProtocolVersion, rejectActionFor, resolveSeat, seatViolationMessage } from './negotiation.protocol.js';
5
5
  import { NEGOTIATION_ACTIONS } from '../shared/schemas/negotiation-state.schema.js';
6
6
  import { protocolLogger } from '../shared/observability/protocol.logger.js';
7
7
  import { focusedIntentId, focusedNetworkId } from '../shared/agent/tool.scope.js';
@@ -53,6 +53,58 @@ async function readOpportunityLifecycles(database, opportunityIds, ownerUserId)
53
53
  return {};
54
54
  }
55
55
  }
56
+ /**
57
+ * Terminal opportunity status for a negotiation concluded through this tool.
58
+ * Version-independent mapping, identical to the graph's finalize node:
59
+ * `accept` → `pending` (the owner gate is still ahead), reject-like →
60
+ * `rejected`, anything else (the turn cap) → `stalled`.
61
+ */
62
+ function terminalOpportunityStatus(lastAction) {
63
+ if (lastAction === 'accept')
64
+ return 'pending';
65
+ return isRejectLikeAction(lastAction) ? 'rejected' : 'stalled';
66
+ }
67
+ /**
68
+ * Conclude a negotiation reached through `respond_to_negotiation`: complete
69
+ * the task, write the outcome artifact, and advance the opportunity out of
70
+ * `negotiating`.
71
+ *
72
+ * The status write goes through `updateOpportunityStatus` — the same waist the
73
+ * graph's finalize node uses — because that method is what carries the
74
+ * post-commit opportunity-transition emit. Writing the column any other way
75
+ * (or, as this path did before, not at all) leaves the transition hook blind:
76
+ * neither side's question-message is regenerated or pruned, radar buckets keep
77
+ * counting a finished negotiation, and the expiry sweep still sees a phantom
78
+ * active opportunity.
79
+ *
80
+ * The status write is best-effort, mirroring finalize: the task is already
81
+ * completed and the artifact written, so a failed status flip is logged rather
82
+ * than turned into a tool error the caller would read as "the conclude did not
83
+ * happen".
84
+ */
85
+ async function concludeNegotiation(database, input) {
86
+ await database.updateTaskState(input.taskId, 'completed');
87
+ await database.createArtifact({
88
+ taskId: input.taskId,
89
+ name: 'negotiation-outcome',
90
+ parts: [{ kind: 'data', data: input.outcome }],
91
+ metadata: { hasOpportunity: input.outcome.hasOpportunity, turnCount: input.turnCount },
92
+ });
93
+ if (!input.opportunityId)
94
+ return;
95
+ const status = terminalOpportunityStatus(input.lastAction);
96
+ try {
97
+ await database.updateOpportunityStatus(input.opportunityId, status);
98
+ }
99
+ catch (err) {
100
+ logger.error('Failed to update opportunity status on MCP conclude', {
101
+ taskId: input.taskId,
102
+ opportunityId: input.opportunityId,
103
+ status,
104
+ err,
105
+ });
106
+ }
107
+ }
56
108
  /** Extracts the ordered NegotiationTurn list from A2A message data parts. */
57
109
  function turnsFromMessages(messages) {
58
110
  return messages
@@ -437,12 +489,12 @@ export function createNegotiationTools(defineTool, deps) {
437
489
  const history = turnsFromMessages(allMessages);
438
490
  const nextSpeaker = currentSpeaker === 'source' ? 'candidate' : 'source';
439
491
  const outcome = buildNegotiationOutcome(history, newTurnCount, query.action, meta.sourceUserId, meta.candidateUserId, nextSpeaker);
440
- await negotiationDatabase.updateTaskState(task.id, 'completed');
441
- await negotiationDatabase.createArtifact({
492
+ await concludeNegotiation(negotiationDatabase, {
442
493
  taskId: task.id,
443
- name: 'negotiation-outcome',
444
- parts: [{ kind: 'data', data: outcome }],
445
- metadata: { hasOpportunity: outcome.hasOpportunity, turnCount: newTurnCount },
494
+ ...(meta.opportunityId ? { opportunityId: meta.opportunityId } : {}),
495
+ lastAction: query.action,
496
+ outcome,
497
+ turnCount: newTurnCount,
446
498
  });
447
499
  return success({
448
500
  message: query.action === 'accept'
@@ -465,12 +517,12 @@ export function createNegotiationTools(defineTool, deps) {
465
517
  const history = turnsFromMessages(allMessages);
466
518
  const nextSpeakerForCap = currentSpeaker === 'source' ? 'candidate' : 'source';
467
519
  const outcome = buildNegotiationOutcome(history, newTurnCount, 'counter', meta.sourceUserId, meta.candidateUserId, nextSpeakerForCap);
468
- await negotiationDatabase.updateTaskState(task.id, 'completed');
469
- await negotiationDatabase.createArtifact({
520
+ await concludeNegotiation(negotiationDatabase, {
470
521
  taskId: task.id,
471
- name: 'negotiation-outcome',
472
- parts: [{ kind: 'data', data: outcome }],
473
- metadata: { hasOpportunity: false, turnCount: newTurnCount },
522
+ ...(meta.opportunityId ? { opportunityId: meta.opportunityId } : {}),
523
+ lastAction: 'counter',
524
+ outcome,
525
+ turnCount: newTurnCount,
474
526
  });
475
527
  return success({
476
528
  message: 'Maximum turns reached. Negotiation finalized without opportunity.',
@@ -586,12 +638,12 @@ export function createNegotiationTools(defineTool, deps) {
586
638
  if (isTerminalAction(aiTurn.action)) {
587
639
  const fullHistory = [...historyForDispatch, aiTurn];
588
640
  const outcome = buildNegotiationOutcome(fullHistory, finalTurnCount, aiTurn.action, meta.sourceUserId, meta.candidateUserId, counterpartySpeaker === 'source' ? 'candidate' : 'source');
589
- await negotiationDatabase.updateTaskState(task.id, 'completed');
590
- await negotiationDatabase.createArtifact({
641
+ await concludeNegotiation(negotiationDatabase, {
591
642
  taskId: task.id,
592
- name: 'negotiation-outcome',
593
- parts: [{ kind: 'data', data: outcome }],
594
- metadata: { hasOpportunity: outcome.hasOpportunity, turnCount: finalTurnCount },
643
+ ...(meta.opportunityId ? { opportunityId: meta.opportunityId } : {}),
644
+ lastAction: aiTurn.action,
645
+ outcome,
646
+ turnCount: finalTurnCount,
595
647
  });
596
648
  return success({
597
649
  message: `${query.action === 'question' ? 'Question' : query.action === 'propose' ? 'Proposal' : 'Counter'} submitted. Counterparty responded with ${aiTurn.action}.`,
@@ -606,12 +658,12 @@ export function createNegotiationTools(defineTool, deps) {
606
658
  if (isNegotiationTurnCapReached(finalTurnCount, meta.maxTurns)) {
607
659
  const fullHistory = [...historyForDispatch, aiTurn];
608
660
  const outcome = buildNegotiationOutcome(fullHistory, finalTurnCount, 'counter', meta.sourceUserId, meta.candidateUserId, counterpartySpeaker === 'source' ? 'candidate' : 'source');
609
- await negotiationDatabase.updateTaskState(task.id, 'completed');
610
- await negotiationDatabase.createArtifact({
661
+ await concludeNegotiation(negotiationDatabase, {
611
662
  taskId: task.id,
612
- name: 'negotiation-outcome',
613
- parts: [{ kind: 'data', data: outcome }],
614
- metadata: { hasOpportunity: false, turnCount: finalTurnCount },
663
+ ...(meta.opportunityId ? { opportunityId: meta.opportunityId } : {}),
664
+ lastAction: 'counter',
665
+ outcome,
666
+ turnCount: finalTurnCount,
615
667
  });
616
668
  return success({
617
669
  message: 'Counterparty responded but max turns reached. Negotiation finalized.',
@@ -683,12 +735,12 @@ export function createNegotiationTools(defineTool, deps) {
683
735
  const fullHistory = [...historyForDispatch, aiTurn, userAgentTurn];
684
736
  const userSpeaker = isSource ? 'source' : 'candidate';
685
737
  const outcome = buildNegotiationOutcome(fullHistory, userTurnCount, userAgentTurn.action, meta.sourceUserId, meta.candidateUserId, userSpeaker === 'source' ? 'candidate' : 'source');
686
- await negotiationDatabase.updateTaskState(task.id, 'completed');
687
- await negotiationDatabase.createArtifact({
738
+ await concludeNegotiation(negotiationDatabase, {
688
739
  taskId: task.id,
689
- name: 'negotiation-outcome',
690
- parts: [{ kind: 'data', data: outcome }],
691
- metadata: { hasOpportunity: outcome.hasOpportunity, turnCount: userTurnCount },
740
+ ...(meta.opportunityId ? { opportunityId: meta.opportunityId } : {}),
741
+ lastAction: userAgentTurn.action,
742
+ outcome,
743
+ turnCount: userTurnCount,
692
744
  });
693
745
  return success({
694
746
  message: `Your agent ${userAgentTurn.action}ed the counterparty's response.`,
@@ -702,12 +754,12 @@ export function createNegotiationTools(defineTool, deps) {
702
754
  if (isNegotiationTurnCapReached(userTurnCount, meta.maxTurns)) {
703
755
  const fullHistory = [...historyForDispatch, aiTurn, userAgentTurn];
704
756
  const outcome = buildNegotiationOutcome(fullHistory, userTurnCount, 'counter', meta.sourceUserId, meta.candidateUserId, isSource ? 'candidate' : 'source');
705
- await negotiationDatabase.updateTaskState(task.id, 'completed');
706
- await negotiationDatabase.createArtifact({
757
+ await concludeNegotiation(negotiationDatabase, {
707
758
  taskId: task.id,
708
- name: 'negotiation-outcome',
709
- parts: [{ kind: 'data', data: outcome }],
710
- metadata: { hasOpportunity: false, turnCount: userTurnCount },
759
+ ...(meta.opportunityId ? { opportunityId: meta.opportunityId } : {}),
760
+ lastAction: 'counter',
761
+ outcome,
762
+ turnCount: userTurnCount,
711
763
  });
712
764
  return success({
713
765
  message: 'Your agent responded but max turns reached. Negotiation finalized.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "22.0.0-rc.494.1",
3
+ "version": "22.0.0-rc.495.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",