@indexnetwork/protocol 21.0.0-rc.489.1 → 21.0.0-rc.490.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.
@@ -6,9 +6,9 @@ import { allowedActionsFor, askUserAnswerWindowMs, configuredAskUserEnabled, fal
6
6
  import { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "./negotiation.consultation-policy.js";
7
7
  import { blocksNegotiationBeforeFirstTurn } from "./negotiation.screen.js";
8
8
  import { assessDeadlock, configuredDeadlockShiftEnabled, configuredDeadlockThreshold } from "./negotiation.deadlock.js";
9
- import { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, negotiationQuestionSettlementId } from './negotiation.question-safety.js';
9
+ import { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, isSafeAuthoredNegotiationQuestion, negotiationQuestionSettlementId } from './negotiation.question-safety.js';
10
10
  import { isNegotiationTurnCapReached } from "./negotiation.turn-cap.js";
11
- import { buildAttributedDialogue, hasPriorAskUser, memoryQueryText, retrieveMemory, turnLog, turnsFromMessages } from "./negotiation.graph.shared.js";
11
+ import { buildAttributedDialogue, hasPriorAskUser, memoryQueryText, retrieveClientDm, retrieveMemory, turnLog, turnsFromMessages } from "./negotiation.graph.shared.js";
12
12
  export async function turnNode(state, deps) {
13
13
  const traceEmitter = requestContext.getStore()?.traceEmitter;
14
14
  // Local helper to emit events whose shape is wider than the declared
@@ -150,6 +150,27 @@ export async function turnNode(state, deps) {
150
150
  else {
151
151
  // No personal agent or timeout — run system agent
152
152
  const agentPriorDialogue = buildAttributedDialogue(state);
153
+ // ─── A2H: the acting user's own negotiator DM for this signal ──────
154
+ // Retrieved HERE, inside the system-agent branch, rather than beside
155
+ // `ownMemory` above. Two reasons, and the first is the constraint:
156
+ //
157
+ // 1. `payload` is built and dispatched before this point, so the
158
+ // excerpt cannot reach an external agent by a later edit — the
159
+ // value does not exist in that scope. Memory is safe to forward
160
+ // (distilled standing rules); a verbatim excerpt of the client's
161
+ // private thread with their own negotiator is not, and an external
162
+ // registered agent can hold the personal-agent seat.
163
+ // 2. A dispatched turn never reads it, so it never pays for the query.
164
+ //
165
+ // Gated on `askUserAvailable`: the grant is settled before the model
166
+ // runs, so the DM is present on exactly the turns where the agent may
167
+ // consult its client — the turns where knowing what they already said
168
+ // changes what it asks. Fetching it on every turn would move the
169
+ // prompt for every negotiation, not just the consulting ones.
170
+ // `askUserAvailable` already requires a non-empty `ownIntentId`.
171
+ const clientDm = askUserAvailable
172
+ ? await retrieveClientDm(deps, ownUser.id, ownIntentId)
173
+ : [];
153
174
  turn = await deps.systemAgent.invoke({
154
175
  ownUser,
155
176
  otherUser,
@@ -167,6 +188,7 @@ export async function turnNode(state, deps) {
167
188
  ...(askUserAvailable && { canAskUser: true }),
168
189
  ...(bargainingMode && { bargaining: { consecutiveNonConvergent: deadlock.consecutiveNonConvergent } }),
169
190
  ...(ownMemory.length > 0 && { memory: ownMemory }),
191
+ ...(clientDm.length > 0 && { clientDm }),
170
192
  ...(state.privateConsultation?.recipientUserId === ownUser.id
171
193
  ? { privateConsultation: state.privateConsultation }
172
194
  : {}),
@@ -307,6 +329,46 @@ export async function turnNode(state, deps) {
307
329
  });
308
330
  }
309
331
  }
332
+ // ─── Authored ask_user question: identifier-aware safety gate ─────────
333
+ // The agent now writes the question its client reads verbatim (the
334
+ // pre-A2H `disclosureSubject` was only an input to server-templated copy),
335
+ // and an external registered agent can hold the personal-agent seat — so
336
+ // this runs on every turn, dispatched or system, not just our own.
337
+ //
338
+ // Placed BEFORE persistence deliberately. The turn is about to become a
339
+ // message in the shared conversation, so a rejected question must not
340
+ // survive there either; and issue 6 reads the field back off the persisted
341
+ // turn, which means dropping it here is what makes that read safe by
342
+ // construction rather than by remembering to re-check.
343
+ //
344
+ // Rejection is a DOWNGRADE, never a failure: the turn, its action, and
345
+ // `askUser.reason` all stand, so the consultation proceeds on today's
346
+ // enum-only path — the same shape a v1 turn or an older agent produces.
347
+ // A guard that could fail a turn would let malformed model output stall a
348
+ // negotiation, which is strictly worse than asking a generic question.
349
+ //
350
+ // The two inputs are exactly what the api-side payload guard can never
351
+ // have: it sees the question at the DB boundary with no idea who the
352
+ // counterparty is or what the evaluator wrote, so it cannot tell that a
353
+ // well-formed question is naming them or paraphrasing it.
354
+ if (turn.askUser?.question) {
355
+ const counterpartyName = otherUser.profile?.name?.trim();
356
+ const seedReasoning = state.seedAssessment?.reasoning?.trim();
357
+ const safeAuthoredQuestion = isSafeAuthoredNegotiationQuestion(turn.askUser.question, {
358
+ ...(counterpartyName ? { forbiddenIdentifiers: [counterpartyName] } : {}),
359
+ ...(seedReasoning ? { forbiddenSourceText: [seedReasoning] } : {}),
360
+ });
361
+ if (!safeAuthoredQuestion) {
362
+ turnLog.warn('Dropping unsafe authored ask_user question; consultation continues without it', {
363
+ taskId: state.taskId,
364
+ opportunityId: state.opportunityId || undefined,
365
+ seat,
366
+ handledExternally: dispatchResult.handled,
367
+ });
368
+ const { question: _rejected, ...askUserWithoutQuestion } = turn.askUser;
369
+ turn = { ...turn, askUser: askUserWithoutQuestion };
370
+ }
371
+ }
310
372
  const parts = [{ kind: "data", data: turn }];
311
373
  const message = await deps.database.createMessage({
312
374
  conversationId: state.conversationId,
@@ -19,7 +19,7 @@ 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, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.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";
23
23
  export { renderNegotiatorChatMemorySection } from "./negotiation.memory.js";
24
24
  export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode, NegotiationConsultationReason, } from "./negotiation.consultation-policy.js";
25
25
  export type { HermesNegotiationAction, HermesNegotiationResponse, HermesOwnerDirective, HermesRoleAlignment, } from "./negotiation.hermes-contract.js";
@@ -27,4 +27,5 @@ export type { NegotiationGraphLike, NegotiationOutcome, NegotiationTurn, UserNeg
27
27
  export type { NegotiationSpeakerMessage, NegotiationSpeakerParticipants } from "./negotiation.expected-speaker.js";
28
28
  export type { NegotiationScopeMetadata } from "./negotiation.scope.js";
29
29
  export type { NegotiatorMemoryEntry } from "./negotiation.memory.js";
30
+ export type { NegotiatorClientDmMessage, NegotiatorClientDmQuery, NegotiatorClientDmRetrieveFn, } from "./negotiation.client-dm.js";
30
31
  export type { NegotiationToolDeps } from "./negotiation.tools.port.js";
@@ -16,5 +16,5 @@ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached } from "./ne
16
16
  export { expectedNegotiationSpeaker } from "./negotiation.expected-speaker.js";
17
17
  export { negotiationScopeKey, readNegotiationMessages } from "./negotiation.scope.js";
18
18
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode, } from "./negotiation.consultation-policy.js";
19
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.js";
19
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeAuthoredNegotiationQuestion, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./negotiation.question-safety.js";
20
20
  export { renderNegotiatorChatMemorySection } from "./negotiation.memory.js";