@indexnetwork/protocol 21.1.0-rc.493.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/chat/chat-streaming.types.d.ts +2 -23
  3. package/dist/chat/chat-streaming.types.js +0 -3
  4. package/dist/chat/chat.agent.d.ts +0 -5
  5. package/dist/chat/chat.streamer.js +3 -6
  6. package/dist/chat/negotiator.persona.d.ts +1 -1
  7. package/dist/chat/negotiator.persona.js +0 -7
  8. package/dist/chat/negotiator.prompt.js +10 -12
  9. package/dist/chat/onboarding.persona.d.ts +1 -1
  10. package/dist/chat/onboarding.persona.js +0 -1
  11. package/dist/chat/onboarding.prompt.js +2 -2
  12. package/dist/chat/signal.persona.d.ts +1 -1
  13. package/dist/chat/signal.persona.js +1 -2
  14. package/dist/chat/signal.prompt.d.ts +6 -5
  15. package/dist/chat/signal.prompt.js +17 -37
  16. package/dist/index.d.ts +3 -7
  17. package/dist/index.js +2 -4
  18. package/dist/intents/graph/intent.graph.d.ts +1 -2
  19. package/dist/intents/graph/intent.graph.execute.js +0 -15
  20. package/dist/intents/graph/intent.graph.js +1 -2
  21. package/dist/intents/graph/intent.graph.shared.d.ts +0 -2
  22. package/dist/intents/intent.module.d.ts +0 -3
  23. package/dist/intents/intent.module.js +2 -2
  24. package/dist/mcp/mcp.authorization-policy.d.ts +2 -2
  25. package/dist/mcp/mcp.authorization-policy.js +0 -24
  26. package/dist/negotiations/negotiation.agent.js +10 -1
  27. package/dist/negotiations/negotiation.graph.turn.d.ts +2 -0
  28. package/dist/negotiations/negotiation.graph.turn.js +9 -0
  29. package/dist/negotiations/negotiation.module.d.ts +1 -1
  30. package/dist/negotiations/negotiation.module.js +1 -1
  31. package/dist/negotiations/negotiation.question-safety.d.ts +0 -1
  32. package/dist/negotiations/negotiation.question-safety.js +0 -1
  33. package/dist/negotiations/negotiation.tools.js +83 -31
  34. package/dist/opportunities/opportunity.graph.d.ts +1 -1
  35. package/dist/opportunities/opportunity.tools.cards.d.ts +0 -13
  36. package/dist/opportunities/opportunity.tools.cards.js +0 -22
  37. package/dist/opportunities/opportunity.tools.js +4 -65
  38. package/dist/opportunities/opportunity.tools.port.d.ts +1 -1
  39. package/dist/questions/question.env.d.ts +5 -35
  40. package/dist/questions/question.env.js +5 -70
  41. package/dist/questions/question.input.d.ts +36 -145
  42. package/dist/questions/question.input.js +1 -56
  43. package/dist/questions/question.module.d.ts +10 -22
  44. package/dist/questions/question.module.js +9 -19
  45. package/dist/shared/agent/activity-projection.d.ts +2 -2
  46. package/dist/shared/agent/tool.factory.js +3 -15
  47. package/dist/shared/agent/tool.helpers.d.ts +0 -73
  48. package/dist/shared/agent/tool.registry.js +0 -2
  49. package/dist/shared/agent/tool.runtime.d.ts +1 -1
  50. package/dist/shared/agent/tool.runtime.js +3 -14
  51. package/dist/shared/observability/request-context.d.ts +0 -5
  52. package/package.json +1 -1
  53. package/dist/opportunities/opportunity.pending-questions.d.ts +0 -33
  54. package/dist/opportunities/opportunity.pending-questions.js +0 -42
  55. package/dist/questions/question.agent.d.ts +0 -28
  56. package/dist/questions/question.agent.js +0 -136
  57. package/dist/questions/question.ask.tool.d.ts +0 -12
  58. package/dist/questions/question.ask.tool.js +0 -297
  59. package/dist/questions/question.persistence.port.d.ts +0 -84
  60. package/dist/questions/question.persistence.port.js +0 -1
  61. package/dist/questions/question.presets.d.ts +0 -21
  62. package/dist/questions/question.presets.js +0 -388
  63. package/dist/questions/question.tools.d.ts +0 -15
  64. package/dist/questions/question.tools.js +0 -216
  65. package/dist/questions/question.tools.port.d.ts +0 -12
  66. package/dist/questions/question.tools.port.js +0 -1
  67. package/dist/shared/schemas/pending-question.schema.d.ts +0 -28
  68. package/dist/shared/schemas/pending-question.schema.js +0 -1
@@ -60,7 +60,6 @@ export const HERMES_AGENT_MCP_TOOL_PERMISSIONS = defineMcpToolPermissionMap({
60
60
  create_premise: { action: 'manage:premises', reach: 'principal' },
61
61
  update_premise: { action: 'manage:premises', reach: 'principal' },
62
62
  retract_premise: { action: 'manage:premises', reach: 'principal' },
63
- read_pending_questions: { action: 'manage:identity', reach: 'principal' },
64
63
  read_activity_summary: { action: 'manage:identity', reach: 'principal' },
65
64
  read_docs: { action: 'manage:identity', reach: 'principal' },
66
65
  });
@@ -127,29 +126,6 @@ export const CANONICAL_MCP_TOOL_ACCESS_RULES = defineMcpToolAccessRules({
127
126
  read_intent_indexes: { access: 'authenticated', reach: 'network' },
128
127
  create_intent_index: { access: 'permission', actions: ['manage:intents'], reach: 'network' },
129
128
  delete_intent_index: { access: 'permission', actions: ['manage:intents'], reach: 'network' },
130
- // Question tools inherit the exact affected object's permission in-handler.
131
- read_pending_questions: {
132
- access: 'permission',
133
- actions: [
134
- 'manage:identity',
135
- 'manage:premises',
136
- 'manage:intents',
137
- 'manage:opportunities',
138
- 'manage:negotiations',
139
- ],
140
- reach: 'principal',
141
- },
142
- answer_pending_question: {
143
- access: 'permission',
144
- actions: [
145
- 'manage:identity',
146
- 'manage:premises',
147
- 'manage:intents',
148
- 'manage:opportunities',
149
- 'manage:negotiations',
150
- ],
151
- reach: 'principal',
152
- },
153
129
  // Communities.
154
130
  read_networks: { access: 'authenticated', reach: 'network' },
155
131
  read_network_memberships: { access: 'authenticated', reach: 'network' },
@@ -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.',
@@ -733,7 +733,7 @@ export declare class OpportunityGraphFactory {
733
733
  seedAssessmentScore?: number | undefined;
734
734
  }[]> | undefined;
735
735
  discoverySummary?: import("../shared/schemas/discovery-question.schema.js").DiscoverySummary | import("@langchain/langgraph").OverwriteValue<import("../shared/schemas/discovery-question.schema.js").DiscoverySummary | null> | null | undefined;
736
- }, "scope" | "__start__" | "discovery" | "prep" | "evaluation" | "persist" | "negotiate" | "resolve" | "ranking", {
736
+ }, "scope" | "__start__" | "prep" | "discovery" | "evaluation" | "persist" | "negotiate" | "resolve" | "ranking", {
737
737
  userId: import("@langchain/langgraph").BaseChannel<import("./opportunity.graph.shared.js").Id<"users">, import("./opportunity.graph.shared.js").Id<"users"> | import("@langchain/langgraph").OverwriteValue<import("./opportunity.graph.shared.js").Id<"users">>, unknown>;
738
738
  searchQuery: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
739
739
  networkId: import("@langchain/langgraph").BaseChannel<import("./opportunity.graph.shared.js").Id<"networks"> | undefined, import("./opportunity.graph.shared.js").Id<"networks"> | import("@langchain/langgraph").OverwriteValue<import("./opportunity.graph.shared.js").Id<"networks"> | undefined> | undefined, unknown>;
@@ -8,7 +8,6 @@
8
8
  import { type OpportunityOwnerAction, type OpportunityOwnerApprovalVerdict } from "./opportunity.owner-approval.js";
9
9
  export declare function stripLeadingNarratorName(remark: string, narratorName: string): string;
10
10
  import type { Opportunity } from "../shared/interfaces/database.interface.js";
11
- import type { PendingQuestionSummary } from "../shared/schemas/pending-question.schema.js";
12
11
  export declare const logger: import("../shared/observability/log.js").LoggerWithSource;
13
12
  /**
14
13
  * Build the agent-facing profile link for a counterpart — always the Index web
@@ -72,18 +71,6 @@ export declare function attachOpportunityAppLink(card: Record<string, unknown> &
72
71
  }, opts: {
73
72
  frontendUrl: string | undefined;
74
73
  }): void;
75
- export interface PublicUptakeQuestion {
76
- id: string;
77
- title: string;
78
- prompt: string;
79
- options: Array<{
80
- label: string;
81
- description: string;
82
- }>;
83
- multiSelect: boolean;
84
- }
85
- export declare function publicUptakeQuestion(question: PendingQuestionSummary): PublicUptakeQuestion;
86
- export declare function uptakeAdvisory(opportunityId: string, questions: PublicUptakeQuestion[]): string;
87
74
  /**
88
75
  * IND-593: stable fail-closed denial for the owner-approval boundary. The
89
76
  * `missing` reason carries the fresh, server-derived interaction challenge the
@@ -111,28 +111,6 @@ export function attachOpportunityAppLink(card, opts) {
111
111
  if (appUrl)
112
112
  card.appUrl = appUrl;
113
113
  }
114
- export function publicUptakeQuestion(question) {
115
- return {
116
- id: question.id,
117
- title: question.title,
118
- prompt: question.prompt,
119
- options: question.options,
120
- multiSelect: question.multiSelect,
121
- };
122
- }
123
- export function uptakeAdvisory(opportunityId, questions) {
124
- return JSON.stringify({
125
- success: false,
126
- error: "Resolve the pending uptake questions or explicitly continue anyway.",
127
- advisory: {
128
- code: "unresolved_uptake_questions",
129
- advisoryOnly: true,
130
- opportunityId,
131
- questions,
132
- acknowledgedUptakeQuestionIds: questions.map((question) => question.id),
133
- },
134
- });
135
- }
136
114
  /**
137
115
  * IND-593: stable fail-closed denial for the owner-approval boundary. The
138
116
  * `missing` reason carries the fresh, server-derived interaction challenge the
@@ -10,14 +10,13 @@ import { requestContext } from "../shared/observability/request-context.js";
10
10
  import { success, error, UUID_REGEX } from "../shared/agent/tool.helpers.js";
11
11
  import { focusedIntentId, focusedNetworkId } from "../shared/agent/tool.scope.js";
12
12
  import { OpportunityPresenter, gatherPresenterContext } from "./opportunity.presentation.js";
13
- import { isUptakeGuardEnabled } from "../questions/question.module.js";
14
13
  import { admitOpportunityUpdate } from "./opportunity.update-admission.js";
15
14
  import { opportunityOwnerActionForStatus } from "./opportunity.owner-approval.js";
16
15
  import { ownerApprovalProvenanceFor } from "./opportunity.owner-provenance.js";
17
16
  export { buildOpportunityPresentation } from "./opportunity.presentation.js";
18
17
  import { sendOpportunity, updateOpportunityStatus } from "./opportunity.graph.modes.js";
19
18
  import { createListOpportunitiesTool } from "./opportunity.tools.list.js";
20
- import { confirmDeliveryError, logger, ownerApprovalDenial, publicUptakeQuestion, uptakeAdvisory } from "./opportunity.tools.cards.js";
19
+ import { confirmDeliveryError, logger, ownerApprovalDenial } from "./opportunity.tools.cards.js";
21
20
  export { attachOpportunityAppLink, attachProfileLink, buildMinimalOpportunityCard, buildNegotiationUrl, buildOpportunityAppUrl, buildProfileUrl } from "./opportunity.tools.cards.js";
22
21
  export function createOpportunityTools(defineTool, deps) {
23
22
  const { database, userDb, systemDb, graphs, cache } = deps;
@@ -36,13 +35,12 @@ export function createOpportunityTools(defineTool, deps) {
36
35
  "- `rejected`: Decline a received opportunity.\n" +
37
36
  "- `expired`: Mark as expired (typically done by the system after timeout).\n\n" +
38
37
  "**When to use:** After list_opportunities returns persisted opportunity cards. " +
39
- "The user clicks 'Send' (pending), 'Accept', or 'Reject' on the card, and the agent calls this tool. " +
40
- "An accepted transition may first return a non-success uptake advisory with preparatory questions. Surface those questions, then retry with all returned question ids in acknowledgedUptakeQuestionIds; acknowledgement confirms presentation, not an answer.\n\n" +
38
+ "The user clicks 'Send' (pending), 'Accept', or 'Reject' on the card, and the agent calls this tool.\n\n" +
41
39
  "**Owner approval (agents):** Agent-driven send/accept/reject transitions require an explicit owner-issued approval proof. " +
42
40
  "Call without ownerApprovalProof first: the denial returns an approval challenge (interactionId, expiresAt) bound to the exact opportunity, action, owner, and agent. " +
43
41
  "Relay that challenge to the owner for explicit approval, then retry once with the issued ownerApprovalProof. " +
44
- "Proofs are single-use and expire; acknowledgedUptakeQuestionIds, negotiation approvals, and advisory values are never substitutes.\n\n" +
45
- "**Returns:** Confirmation with the new status and notification details (who was notified), or a structured uptake advisory without mutation.",
42
+ "Proofs are single-use and expire; negotiation approvals and advisory values are never substitutes.\n\n" +
43
+ "**Returns:** Confirmation with the new status and notification details (who was notified).",
46
44
  querySchema: z.object({
47
45
  opportunityId: z
48
46
  .string()
@@ -58,10 +56,6 @@ export function createOpportunityTools(defineTool, deps) {
58
56
  .describe("Opaque owner-issued approval proof for this exact transition (agents only). Obtained after the owner " +
59
57
  "explicitly approves the interaction challenge returned by a proof-less call. The opportunity, action, " +
60
58
  "owner, agent, and interaction binding is always derived server-side; only this token is presented."),
61
- acknowledgedUptakeQuestionIds: z
62
- .array(z.string().min(1))
63
- .optional()
64
- .describe("On an acknowledged retry after an uptake advisory, include every question id returned by that advisory."),
65
59
  scopeType: z
66
60
  .enum(['intent'])
67
61
  .optional()
@@ -137,61 +131,6 @@ export function createOpportunityTools(defineTool, deps) {
137
131
  if (verdict.kind === 'denied')
138
132
  return ownerApprovalDenial(opportunityId, ownerAction, verdict);
139
133
  }
140
- // The caller actor's own network is the exact question lookup boundary,
141
- // even for an otherwise unscoped request. A focused network may only be
142
- // equal to this after the guard above.
143
- // Unscoped callers query all of their exact opportunity questions; a
144
- // network-scoped caller is clamped to the bound network. Selecting the
145
- // first duplicate actor row would miss a valid question on another
146
- // shared network.
147
- const uptakeNetworkId = scopedNetworkId;
148
- // Soft uptake interlock: only acceptance is advisory-gated. All existing
149
- // actor/scope/privacy guards run first so the question lookup cannot be
150
- // used to probe opportunities or networks the caller cannot access.
151
- if (query.status === "accepted" && isUptakeGuardEnabled() && deps.findPendingQuestions) {
152
- try {
153
- const pending = await deps.findPendingQuestions(context.userId, {
154
- sourceType: "opportunity",
155
- sourceId: opportunityId,
156
- modes: ["negotiation"],
157
- purpose: "uptake",
158
- ...(uptakeNetworkId ? { networkId: uptakeNetworkId } : {}),
159
- });
160
- // Defense in depth if a host overlooks one or more filters. Actor
161
- // internals are checked here and never serialized into the advisory.
162
- const exactPending = pending.filter((question) => {
163
- if (question.sourceType !== "opportunity" ||
164
- question.sourceId !== opportunityId ||
165
- question.mode !== "negotiation" ||
166
- question.purpose !== "uptake") {
167
- return false;
168
- }
169
- if (!question.actors?.some((actor) => actor.userId === context.userId))
170
- return false;
171
- if (uptakeNetworkId && !question.actors.some((actor) => actor.userId === context.userId && actor.networkId === uptakeNetworkId)) {
172
- return false;
173
- }
174
- return true;
175
- });
176
- const acknowledged = new Set(query.acknowledgedUptakeQuestionIds ?? []);
177
- if (exactPending.some((question) => !acknowledged.has(question.id))) {
178
- return uptakeAdvisory(opportunityId, exactPending.map(publicUptakeQuestion));
179
- }
180
- }
181
- catch (err) {
182
- logger.warn("update_opportunity: uptake question lookup failed open", {
183
- opportunityId,
184
- userId: context.userId,
185
- error: err instanceof Error ? err.message : String(err),
186
- });
187
- deps.reportToolError?.(err, {
188
- subsystem: "opportunity",
189
- operation: "opportunity.uptake_lookup",
190
- toolName: "update_opportunity",
191
- userId: context.userId,
192
- });
193
- }
194
- }
195
134
  const isSend = query.status === "pending";
196
135
  const _updateGraphStart = Date.now();
197
136
  const _updateTraceEmitter = requestContext.getStore()?.traceEmitter;
@@ -11,6 +11,6 @@ export interface OpportunityOwnerApprovalDeps {
11
11
  opportunityOwnerApproval?: OpportunityOwnerApprovalAuthority;
12
12
  }
13
13
  /** Host capabilities consumed by opportunity discovery, delivery, and presentation tools. */
14
- export type OpportunityToolDeps = Pick<ToolRegistryCompositionDeps, "database" | "userDb" | "systemDb" | "cache" | "chatSummary" | "opportunityPresentation" | "questionerEnqueue" | "findPendingQuestions" | "negotiationSummary" | "negotiationDatabase" | "deliveryLedger" | "frontendUrl" | "stampNewbornOpportunities" | "reportToolError" | "opportunityOperations"> & OpportunityOwnerApprovalDeps & {
14
+ export type OpportunityToolDeps = Pick<ToolRegistryCompositionDeps, "database" | "userDb" | "systemDb" | "cache" | "chatSummary" | "opportunityPresentation" | "questionerEnqueue" | "negotiationSummary" | "negotiationDatabase" | "deliveryLedger" | "frontendUrl" | "stampNewbornOpportunities" | "reportToolError" | "opportunityOperations"> & OpportunityOwnerApprovalDeps & {
15
15
  graphs: Pick<ToolRegistryCompositionDeps["graphs"], "index" | "networkMembership" | "opportunity">;
16
16
  };
@@ -1,26 +1,11 @@
1
1
  /**
2
- * questions/question.env — centralized question-generation env accessors.
2
+ * questions/question.env — leftover question env constants.
3
3
  *
4
- * Naming scheme (one prefix, hierarchical):
5
- *
6
- * QUESTIONER_ENABLED master switch — QuestionerQueue worker +
7
- * enqueue closures at every composition site.
8
- * QUESTIONER_UPTAKE_ENABLED per-surface switch — advisory pre-accept uptake
9
- * interlock. Requires the master switch.
10
- * QUESTIONER_UPTAKE_AUTHORITY_THRESHOLD
11
- * authority threshold (0-100, default 70).
12
- * QUESTIONER_CHAT_WAIT_TIMEOUT_MS how long the blocking ask_user_question chat
13
- * tool waits for an inline answer (default 4 min).
14
- * QUESTIONER_INTENT_DAILY_CAP refinement questions one intent may generate
15
- * per rolling 24h, recovery + pool combined
16
- * (default 2; 0 disables refinement).
17
- *
18
- * All reads go through this module — do not read these variables via
19
- * `process.env` elsewhere. Values are read on every call (no caching) so tests
20
- * and long-lived processes observe changes.
4
+ * The QUESTIONER_* runtime flags are retired with the card generators
5
+ * (conversational-questions plan); park-path routing is always on. What
6
+ * remains are the historical budget constants still referenced by the
7
+ * questions-table adapter until its surface drops.
21
8
  */
22
- export declare const CHAT_QUESTION_WAIT_TIMEOUT_MS_DEFAULT = 240000;
23
- export declare const UPTAKE_AUTHORITY_THRESHOLD_DEFAULT = 70;
24
9
  /**
25
10
  * Refinement questions a single intent may generate per rolling window.
26
11
  *
@@ -40,18 +25,3 @@ export declare const INTENT_QUESTION_DAILY_CAP_DEFAULT = 2;
40
25
  * have granted the second burst a full fresh allowance.
41
26
  */
42
27
  export declare const INTENT_QUESTION_DAILY_WINDOW_HOURS = 24;
43
- /** Master switch: is any background question generation enabled? */
44
- export declare function isQuestionerEnabled(): boolean;
45
- /** Advisory uptake interlock. Flag-off by default and subordinate to the master switch. */
46
- export declare function isUptakeGuardEnabled(): boolean;
47
- /** Authority threshold below which hosts may generate uptake questions. */
48
- export declare function uptakeAuthorityThreshold(): number;
49
- /** Wait budget for the blocking ask_user_question chat tool. */
50
- export declare function chatQuestionWaitTimeoutMs(): number;
51
- /**
52
- * Per-intent refinement budget over {@link INTENT_QUESTION_DAILY_WINDOW_HOURS}.
53
- *
54
- * Zero is a meaningful setting — it disables background refinement entirely
55
- * without touching the master switch — so this does not reuse `positiveIntEnv`.
56
- */
57
- export declare function intentQuestionDailyCap(): number;