@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.
- package/CHANGELOG.md +26 -0
- package/dist/chat/chat-streaming.types.d.ts +2 -23
- package/dist/chat/chat-streaming.types.js +0 -3
- package/dist/chat/chat.agent.d.ts +0 -5
- package/dist/chat/chat.streamer.js +3 -6
- package/dist/chat/negotiator.persona.d.ts +1 -1
- package/dist/chat/negotiator.persona.js +0 -7
- package/dist/chat/negotiator.prompt.js +10 -12
- package/dist/chat/onboarding.persona.d.ts +1 -1
- package/dist/chat/onboarding.persona.js +0 -1
- package/dist/chat/onboarding.prompt.js +2 -2
- package/dist/chat/signal.persona.d.ts +1 -1
- package/dist/chat/signal.persona.js +1 -2
- package/dist/chat/signal.prompt.d.ts +6 -5
- package/dist/chat/signal.prompt.js +17 -37
- package/dist/index.d.ts +3 -7
- package/dist/index.js +2 -4
- package/dist/intents/graph/intent.graph.d.ts +1 -2
- package/dist/intents/graph/intent.graph.execute.js +0 -15
- package/dist/intents/graph/intent.graph.js +1 -2
- package/dist/intents/graph/intent.graph.shared.d.ts +0 -2
- package/dist/intents/intent.module.d.ts +0 -3
- package/dist/intents/intent.module.js +2 -2
- package/dist/mcp/mcp.authorization-policy.d.ts +2 -2
- package/dist/mcp/mcp.authorization-policy.js +0 -24
- package/dist/negotiations/negotiation.agent.js +10 -1
- package/dist/negotiations/negotiation.graph.turn.d.ts +2 -0
- package/dist/negotiations/negotiation.graph.turn.js +9 -0
- package/dist/negotiations/negotiation.module.d.ts +1 -1
- package/dist/negotiations/negotiation.module.js +1 -1
- package/dist/negotiations/negotiation.question-safety.d.ts +0 -1
- package/dist/negotiations/negotiation.question-safety.js +0 -1
- package/dist/negotiations/negotiation.tools.js +83 -31
- package/dist/opportunities/opportunity.graph.d.ts +1 -1
- package/dist/opportunities/opportunity.tools.cards.d.ts +0 -13
- package/dist/opportunities/opportunity.tools.cards.js +0 -22
- package/dist/opportunities/opportunity.tools.js +4 -65
- package/dist/opportunities/opportunity.tools.port.d.ts +1 -1
- package/dist/questions/question.env.d.ts +5 -35
- package/dist/questions/question.env.js +5 -70
- package/dist/questions/question.input.d.ts +36 -145
- package/dist/questions/question.input.js +1 -56
- package/dist/questions/question.module.d.ts +10 -22
- package/dist/questions/question.module.js +9 -19
- package/dist/shared/agent/activity-projection.d.ts +2 -2
- package/dist/shared/agent/tool.factory.js +3 -15
- package/dist/shared/agent/tool.helpers.d.ts +0 -73
- package/dist/shared/agent/tool.registry.js +0 -2
- package/dist/shared/agent/tool.runtime.d.ts +1 -1
- package/dist/shared/agent/tool.runtime.js +3 -14
- package/dist/shared/observability/request-context.d.ts +0 -5
- package/package.json +1 -1
- package/dist/opportunities/opportunity.pending-questions.d.ts +0 -33
- package/dist/opportunities/opportunity.pending-questions.js +0 -42
- package/dist/questions/question.agent.d.ts +0 -28
- package/dist/questions/question.agent.js +0 -136
- package/dist/questions/question.ask.tool.d.ts +0 -12
- package/dist/questions/question.ask.tool.js +0 -297
- package/dist/questions/question.persistence.port.d.ts +0 -84
- package/dist/questions/question.persistence.port.js +0 -1
- package/dist/questions/question.presets.d.ts +0 -21
- package/dist/questions/question.presets.js +0 -388
- package/dist/questions/question.tools.d.ts +0 -15
- package/dist/questions/question.tools.js +0 -216
- package/dist/questions/question.tools.port.d.ts +0 -12
- package/dist/questions/question.tools.port.js +0 -1
- package/dist/shared/schemas/pending-question.schema.d.ts +0 -28
- 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
|
-
:
|
|
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,
|
|
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,
|
|
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
|
|
441
|
-
await negotiationDatabase.createArtifact({
|
|
492
|
+
await concludeNegotiation(negotiationDatabase, {
|
|
442
493
|
taskId: task.id,
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
469
|
-
await negotiationDatabase.createArtifact({
|
|
520
|
+
await concludeNegotiation(negotiationDatabase, {
|
|
470
521
|
taskId: task.id,
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
590
|
-
await negotiationDatabase.createArtifact({
|
|
641
|
+
await concludeNegotiation(negotiationDatabase, {
|
|
591
642
|
taskId: task.id,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
610
|
-
await negotiationDatabase.createArtifact({
|
|
661
|
+
await concludeNegotiation(negotiationDatabase, {
|
|
611
662
|
taskId: task.id,
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
|
687
|
-
await negotiationDatabase.createArtifact({
|
|
738
|
+
await concludeNegotiation(negotiationDatabase, {
|
|
688
739
|
taskId: task.id,
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
|
706
|
-
await negotiationDatabase.createArtifact({
|
|
757
|
+
await concludeNegotiation(negotiationDatabase, {
|
|
707
758
|
taskId: task.id,
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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__" | "
|
|
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
|
|
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;
|
|
45
|
-
"**Returns:** Confirmation with the new status and notification details (who was notified)
|
|
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" | "
|
|
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 —
|
|
2
|
+
* questions/question.env — leftover question env constants.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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;
|