@indexnetwork/protocol 21.0.0-rc.488.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.
Files changed (37) hide show
  1. package/dist/index.d.ts +3 -2
  2. package/dist/index.js +1 -1
  3. package/dist/negotiations/negotiation.agent.d.ts +13 -0
  4. package/dist/negotiations/negotiation.agent.js +64 -6
  5. package/dist/negotiations/negotiation.client-dm.d.ts +50 -0
  6. package/dist/negotiations/negotiation.client-dm.js +66 -0
  7. package/dist/negotiations/negotiation.detail-reader.js +4 -1
  8. package/dist/negotiations/negotiation.expected-speaker.d.ts +16 -2
  9. package/dist/negotiations/negotiation.expected-speaker.js +15 -3
  10. package/dist/negotiations/negotiation.graph.d.ts +110 -1
  11. package/dist/negotiations/negotiation.graph.init.js +51 -22
  12. package/dist/negotiations/negotiation.graph.js +2 -1
  13. package/dist/negotiations/negotiation.graph.shared.d.ts +22 -3
  14. package/dist/negotiations/negotiation.graph.shared.js +31 -4
  15. package/dist/negotiations/negotiation.graph.turn.d.ts +27 -0
  16. package/dist/negotiations/negotiation.graph.turn.js +64 -2
  17. package/dist/negotiations/negotiation.module.d.ts +4 -1
  18. package/dist/negotiations/negotiation.module.js +2 -1
  19. package/dist/negotiations/negotiation.protocol.d.ts +498 -0
  20. package/dist/negotiations/negotiation.question-safety.d.ts +28 -0
  21. package/dist/negotiations/negotiation.question-safety.js +65 -0
  22. package/dist/negotiations/negotiation.scope.d.ts +41 -0
  23. package/dist/negotiations/negotiation.scope.js +39 -0
  24. package/dist/negotiations/negotiation.screen.js +7 -3
  25. package/dist/negotiations/negotiation.state.d.ts +110 -0
  26. package/dist/negotiations/negotiation.tools.js +15 -4
  27. package/dist/opportunities/negotiation-context.loader.d.ts +1 -1
  28. package/dist/opportunities/negotiation-context.loader.js +3 -1
  29. package/dist/questions/question.schema.d.ts +13 -31
  30. package/dist/questions/question.schema.js +9 -15
  31. package/dist/shared/interfaces/database.capabilities.d.ts +1 -1
  32. package/dist/shared/interfaces/database.negotiation.d.ts +24 -0
  33. package/dist/shared/schemas/negotiation-state.schema.d.ts +182 -3
  34. package/dist/shared/schemas/negotiation-state.schema.js +23 -3
  35. package/dist/shared/schemas/structured-question.schema.d.ts +66 -0
  36. package/dist/shared/schemas/structured-question.schema.js +31 -0
  37. package/package.json +1 -1
@@ -5,6 +5,7 @@ import { configuredProtocolVersion, readProtocolVersion } from "./negotiation.pr
5
5
  import { buildIntentSnapshots } from "./negotiation.intent-snapshot-provenance.js";
6
6
  import { holdsNegotiationConversationLock } from "./negotiation.task-lock-policy.js";
7
7
  import { expectedNegotiationSpeaker } from "./negotiation.expected-speaker.js";
8
+ import { readNegotiationMessages } from "./negotiation.scope.js";
8
9
  import { buildSeededAttribution } from './negotiation.attribution.js';
9
10
  import { initLog, resolveTaskAttribution, turnsFromMessages } from "./negotiation.graph.shared.js";
10
11
  export async function initNode(state, deps) {
@@ -18,7 +19,12 @@ export async function initNode(state, deps) {
18
19
  ? { id: execution.conversationId }
19
20
  : await deps.database.getOrCreateDM(agentIdA, agentIdB, 'agent');
20
21
  // --- Lock gate: check for an active task on this conversation ---
21
- const priorMessages = await deps.database.getMessagesForConversation(conversation.id);
22
+ // Two reads with two jobs. `conversationMessages` is the pair's whole shared
23
+ // DM — CONTEXT, including negotiations for other matches, which reaches the
24
+ // agent only as labelled prior dialogue. `negotiationMessages` is THIS
25
+ // match's own turns, and is the sole input to this negotiation's state:
26
+ // whether it has opened, whose turn it is, how far it has run.
27
+ const conversationMessages = await deps.database.getMessagesForConversation(conversation.id);
22
28
  if (Boolean(state.resumeFromTaskId) !== Boolean(state.continuationSettlementId)
23
29
  || Boolean(state.resumeFromTaskId) !== Boolean(execution))
24
30
  return { error: 'invalid continuation correlation' };
@@ -57,18 +63,24 @@ export async function initNode(state, deps) {
57
63
  });
58
64
  return { error: 'busy' };
59
65
  }
60
- // --- Load prior messages and determine continuation ---
61
- const priorTurns = turnsFromMessages(priorMessages);
66
+ // --- Load this negotiation's own prior turns ---
67
+ // Resolved through the shared scope rule, not a local copy of it: the graph
68
+ // and the respond/polling surfaces must agree on what "this negotiation's
69
+ // messages" means, or an external agent can be told it is not its turn
70
+ // forever. That rule also owns the unkeyed case — a run with no opportunity
71
+ // has no identity apart from its conversation.
72
+ const negotiationMessages = await readNegotiationMessages({
73
+ byNegotiation: (id) => deps.database.getNegotiationMessages(id),
74
+ byConversation: async () => conversationMessages,
75
+ }, {
76
+ conversationId: conversation.id,
77
+ metadata: { opportunityId: state.opportunityId },
78
+ });
79
+ const priorTurns = turnsFromMessages(negotiationMessages);
80
+ // `isContinuation` means THIS negotiation has already spoken — not that the
81
+ // pair has history. A fresh match in a long-running DM is not a
82
+ // continuation, and must still open.
62
83
  const isContinuation = priorTurns.length > 0;
63
- const expectedSpeaker = expectedNegotiationSpeaker({
64
- sourceUserId: state.sourceUser.id,
65
- candidateUserId: state.candidateUser.id,
66
- }, priorMessages);
67
- if (!expectedSpeaker)
68
- return { error: 'invalid negotiation participants' };
69
- const currentSpeaker = expectedSpeaker === state.sourceUser.id
70
- ? 'source'
71
- : 'candidate';
72
84
  // Determine scenario-based maxTurns
73
85
  const scope = { action: 'manage:negotiations', scopeType: 'network', scopeId: state.indexContext.networkId };
74
86
  const [sourceHasAgent, candidateHasAgent] = await Promise.all([
@@ -112,6 +124,19 @@ export async function initNode(state, deps) {
112
124
  }
113
125
  }
114
126
  }
127
+ // --- Floor: derived from this negotiation's turns, after the seat is known ---
128
+ // Resolved here rather than above because an unopened negotiation starts
129
+ // with its initiator, which the tie-break may only just have settled.
130
+ const expectedSpeaker = expectedNegotiationSpeaker({
131
+ sourceUserId: state.sourceUser.id,
132
+ candidateUserId: state.candidateUser.id,
133
+ initiatorUserId,
134
+ }, negotiationMessages);
135
+ if (!expectedSpeaker)
136
+ return { error: 'invalid negotiation participants' };
137
+ const currentSpeaker = expectedSpeaker === state.sourceUser.id
138
+ ? 'source'
139
+ : 'candidate';
115
140
  // --- Protocol version: pinned per negotiation, re-stamped per match ---
116
141
  // A prior task for this same negotiation (exact continuation resume or
117
142
  // a re-run of the same opportunity) pins the version, so one
@@ -179,22 +204,26 @@ export async function initNode(state, deps) {
179
204
  return [];
180
205
  })
181
206
  : [];
182
- // Seed messages with prior turns (additive reducer appends new turns on top).
183
- // taskId is preserved so the turn/screen nodes can separate this
184
- // session's turns from seeded prior-task turns (IND-569).
185
- const seedMessages = isContinuation ? priorMessages.map((m) => ({
207
+ // Seed messages with THIS negotiation's prior turns (additive reducer
208
+ // appends new turns on top). taskId is preserved so the turn/screen nodes
209
+ // can separate this session's turns from earlier sessions of the same
210
+ // negotiation (IND-569).
211
+ const seedMessages = negotiationMessages.map((m) => ({
186
212
  id: m.id,
187
213
  senderId: m.senderId,
188
214
  role: 'agent',
189
215
  parts: m.parts,
190
216
  createdAt: m.createdAt,
191
217
  taskId: m.taskId ?? null,
192
- })) : [];
193
- // IND-569: attribute seeded prior turns to their originating opportunity
194
- // once, up front. Earlier-opportunity and legacy unattributed blocks are
195
- // immutable for the session; the current block is composed per turn.
196
- const priorAttribution = isContinuation
197
- ? await buildSeededAttribution(priorMessages
218
+ }));
219
+ // IND-569: attribute the pair's whole shared DM to its originating
220
+ // negotiations, once, up front. Earlier-match and legacy unattributed
221
+ // blocks are immutable for the session; the current block is composed per
222
+ // turn. Keyed on the conversation, not on `isContinuation`: a fresh match
223
+ // has no turns of its own but the pair's history is exactly the context
224
+ // worth carrying into it.
225
+ const priorAttribution = conversationMessages.length > 0
226
+ ? await buildSeededAttribution(conversationMessages
198
227
  .map((m) => ({ taskId: m.taskId ?? null, turn: turnsFromMessages([m])[0] }))
199
228
  .filter((e) => Boolean(e.turn)), state.opportunityId, (taskId) => resolveTaskAttribution(deps, taskId))
200
229
  : null;
@@ -22,7 +22,7 @@ export { negotiateCandidates } from "./negotiation.candidates.js";
22
22
  * @remarks Accepts an AgentDispatcher for per-turn agent resolution.
23
23
  */
24
24
  export class NegotiationGraphFactory {
25
- constructor(database, dispatcher, timeoutQueue, questionerEnqueue, reflectEnqueue, memoryRetrieve) {
25
+ constructor(database, dispatcher, timeoutQueue, questionerEnqueue, reflectEnqueue, memoryRetrieve, clientDmRetrieve) {
26
26
  this.deps = {
27
27
  database,
28
28
  dispatcher,
@@ -30,6 +30,7 @@ export class NegotiationGraphFactory {
30
30
  questionerEnqueue,
31
31
  reflectEnqueue,
32
32
  memoryRetrieve,
33
+ clientDmRetrieve,
33
34
  systemAgent: new IndexNegotiator(),
34
35
  screener: new NegotiationScreener(),
35
36
  };
@@ -15,6 +15,7 @@ import { NegotiationScreener } from "./negotiation.screen.js";
15
15
  import type { QuestionerEnqueueFn } from "../questions/question.module.js";
16
16
  import type { ReflectEnqueueFn } from "./negotiation.reflect.js";
17
17
  import type { NegotiatorMemoryEntry, NegotiatorMemoryRetrieveFn, NegotiatorMemoryScope } from "./negotiation.memory.js";
18
+ import type { NegotiatorClientDmMessage, NegotiatorClientDmRetrieveFn } from "./negotiation.client-dm.js";
18
19
  import { type AttributedPriorDialogue, type TaskAttribution } from './negotiation.attribution.js';
19
20
  /** The graph's channel state, as every node sees it. */
20
21
  export type NegotiationState = typeof NegotiationGraphState.State;
@@ -26,6 +27,12 @@ export interface NegotiationGraphDeps {
26
27
  questionerEnqueue?: QuestionerEnqueueFn;
27
28
  reflectEnqueue?: ReflectEnqueueFn;
28
29
  memoryRetrieve?: NegotiatorMemoryRetrieveFn;
30
+ /**
31
+ * A2H read path: the acting user's own negotiator DM for this signal.
32
+ * System-agent grounding only — see `negotiation.client-dm.ts`; this must
33
+ * never be forwarded to an external seat via `NegotiationTurnPayload`.
34
+ */
35
+ clientDmRetrieve?: NegotiatorClientDmRetrieveFn;
29
36
  /** In-process negotiator used when no personal agent answers. */
30
37
  systemAgent: IndexNegotiator;
31
38
  /** Outreach gate for fresh negotiations. */
@@ -43,9 +50,11 @@ export declare function turnsFromMessages(messages: Array<{
43
50
  }>): NegotiationTurn[];
44
51
  /**
45
52
  * Whether `userId`'s side has already spent its one `ask_user` client
46
- * consultation in this conversation (P3.2 rationing: max one per negotiation
47
- * per side, checked against the full message history so continuations count
48
- * prior sessions' consultations too).
53
+ * consultation in THIS negotiation (P3.2 rationing: max one per negotiation per
54
+ * side). Callers pass `state.messages`, which carries this negotiation's turns
55
+ * across all of its sessions so an earlier session of the same negotiation
56
+ * counts, while a consultation spent on a different match with the same
57
+ * counterparty does not.
49
58
  */
50
59
  export declare function hasPriorAskUser(messages: Array<{
51
60
  senderId: string;
@@ -57,6 +66,16 @@ export declare function hasPriorAskUser(messages: Array<{
57
66
  * this wrapper adds the graph-side failure guard.
58
67
  */
59
68
  export declare function retrieveMemory(deps: NegotiationGraphDeps, userId: string, counterpartyUserId: string, queryText: string, scope: NegotiatorMemoryScope): Promise<NegotiatorMemoryEntry[]>;
69
+ /**
70
+ * A2H client-DM retrieval — never throws, never blocks a negotiation. The
71
+ * injected fn already resolves [] when the flag is off or the user has no
72
+ * negotiator DM for this signal; this wrapper adds the graph-side failure
73
+ * guard, exactly as `retrieveMemory` does for the memory seam.
74
+ *
75
+ * `userId` is always the ACTING user's — the seam has no counterparty field,
76
+ * so the counterparty's DM cannot be requested from here.
77
+ */
78
+ export declare function retrieveClientDm(deps: NegotiationGraphDeps, userId: string, intentId: string): Promise<NegotiatorClientDmMessage[]>;
60
79
  /** Similarity query text: seed reasoning + counterparty context. */
61
80
  export declare function memoryQueryText(state: NegotiationState, counterparty: UserNegotiationContext): string;
62
81
  /**
@@ -25,9 +25,11 @@ export function turnsFromMessages(messages) {
25
25
  }
26
26
  /**
27
27
  * Whether `userId`'s side has already spent its one `ask_user` client
28
- * consultation in this conversation (P3.2 rationing: max one per negotiation
29
- * per side, checked against the full message history so continuations count
30
- * prior sessions' consultations too).
28
+ * consultation in THIS negotiation (P3.2 rationing: max one per negotiation per
29
+ * side). Callers pass `state.messages`, which carries this negotiation's turns
30
+ * across all of its sessions so an earlier session of the same negotiation
31
+ * counts, while a consultation spent on a different match with the same
32
+ * counterparty does not.
31
33
  */
32
34
  export function hasPriorAskUser(messages, userId) {
33
35
  const sender = `agent:${userId}`;
@@ -58,6 +60,29 @@ export async function retrieveMemory(deps, userId, counterpartyUserId, queryText
58
60
  return [];
59
61
  }
60
62
  }
63
+ /**
64
+ * A2H client-DM retrieval — never throws, never blocks a negotiation. The
65
+ * injected fn already resolves [] when the flag is off or the user has no
66
+ * negotiator DM for this signal; this wrapper adds the graph-side failure
67
+ * guard, exactly as `retrieveMemory` does for the memory seam.
68
+ *
69
+ * `userId` is always the ACTING user's — the seam has no counterparty field,
70
+ * so the counterparty's DM cannot be requested from here.
71
+ */
72
+ export async function retrieveClientDm(deps, userId, intentId) {
73
+ if (!deps.clientDmRetrieve)
74
+ return [];
75
+ try {
76
+ return await deps.clientDmRetrieve({ userId, intentId });
77
+ }
78
+ catch (err) {
79
+ logger.warn("Negotiator client DM retrieval failed; proceeding without it", {
80
+ userId,
81
+ error: err instanceof Error ? err.message : String(err),
82
+ });
83
+ return [];
84
+ }
85
+ }
61
86
  /** Similarity query text: seed reasoning + counterparty context. */
62
87
  export function memoryQueryText(state, counterparty) {
63
88
  return [
@@ -117,7 +142,9 @@ export async function resolveTaskAttribution(deps, taskId) {
117
142
  * (task-id-matched). Null when there is no seeded attribution.
118
143
  */
119
144
  export function buildAttributedDialogue(state) {
120
- if (!state.isContinuation || !state.priorAttribution)
145
+ // Not gated on `isContinuation`: that now means "this negotiation has spoken",
146
+ // and the pair's earlier matches are context worth carrying into a fresh one.
147
+ if (!state.priorAttribution)
121
148
  return null;
122
149
  const currentSessionTurns = turnsFromMessages(state.messages.filter((m) => m.taskId === state.taskId));
123
150
  const dialogue = combineAttributedDialogue(state.priorAttribution, currentSessionTurns);
@@ -22,6 +22,15 @@ export declare function turnNode(state: NegotiationState, deps: NegotiationGraph
22
22
  message?: string | null | undefined;
23
23
  askUser?: {
24
24
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
25
+ question?: {
26
+ prompt: string;
27
+ options: {
28
+ label: string;
29
+ description: string;
30
+ }[];
31
+ title: string;
32
+ multiSelect: boolean;
33
+ } | undefined;
25
34
  } | null | undefined;
26
35
  };
27
36
  firstTurnScreenedOut: boolean;
@@ -50,6 +59,15 @@ export declare function turnNode(state: NegotiationState, deps: NegotiationGraph
50
59
  message?: string | null | undefined;
51
60
  askUser?: {
52
61
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
62
+ question?: {
63
+ prompt: string;
64
+ options: {
65
+ label: string;
66
+ description: string;
67
+ }[];
68
+ title: string;
69
+ multiSelect: boolean;
70
+ } | undefined;
53
71
  } | null | undefined;
54
72
  };
55
73
  status: "input_required";
@@ -79,6 +97,15 @@ export declare function turnNode(state: NegotiationState, deps: NegotiationGraph
79
97
  message?: string | null | undefined;
80
98
  askUser?: {
81
99
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
100
+ question?: {
101
+ prompt: string;
102
+ options: {
103
+ label: string;
104
+ description: string;
105
+ }[];
106
+ title: string;
107
+ multiSelect: boolean;
108
+ } | undefined;
82
109
  } | null | undefined;
83
110
  };
84
111
  memoryBySide: {
@@ -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,
@@ -17,12 +17,15 @@ export { ASK_USER_LOCK_SLACK_MS, allowedActionsFor, askUserAnswerWindowMs, confi
17
17
  export { HERMES_OWNER_DIRECTIVE, HERMES_SHARED_MESSAGE_TEMPLATES, HermesNegotiationActionSchema, HermesNegotiationResponseSchema, HermesOwnerDirectiveSchema, HermesRoleAlignmentSchema, allowedHermesActionsFor, buildHermesNegotiationTurn, } from "./negotiation.hermes-contract.js";
18
18
  export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached } from "./negotiation.turn-cap.js";
19
19
  export { expectedNegotiationSpeaker } from "./negotiation.expected-speaker.js";
20
+ export { negotiationScopeKey, readNegotiationMessages } from "./negotiation.scope.js";
20
21
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode, } from "./negotiation.consultation-policy.js";
21
- 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";
22
23
  export { renderNegotiatorChatMemorySection } from "./negotiation.memory.js";
23
24
  export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode, NegotiationConsultationReason, } from "./negotiation.consultation-policy.js";
24
25
  export type { HermesNegotiationAction, HermesNegotiationResponse, HermesOwnerDirective, HermesRoleAlignment, } from "./negotiation.hermes-contract.js";
25
26
  export type { NegotiationGraphLike, NegotiationOutcome, NegotiationTurn, UserNegotiationContext, } from "./negotiation.state.js";
26
27
  export type { NegotiationSpeakerMessage, NegotiationSpeakerParticipants } from "./negotiation.expected-speaker.js";
28
+ export type { NegotiationScopeMetadata } from "./negotiation.scope.js";
27
29
  export type { NegotiatorMemoryEntry } from "./negotiation.memory.js";
30
+ export type { NegotiatorClientDmMessage, NegotiatorClientDmQuery, NegotiatorClientDmRetrieveFn, } from "./negotiation.client-dm.js";
28
31
  export type { NegotiationToolDeps } from "./negotiation.tools.port.js";
@@ -14,6 +14,7 @@ export { ASK_USER_LOCK_SLACK_MS, allowedActionsFor, askUserAnswerWindowMs, confi
14
14
  export { HERMES_OWNER_DIRECTIVE, HERMES_SHARED_MESSAGE_TEMPLATES, HermesNegotiationActionSchema, HermesNegotiationResponseSchema, HermesOwnerDirectiveSchema, HermesRoleAlignmentSchema, allowedHermesActionsFor, buildHermesNegotiationTurn, } from "./negotiation.hermes-contract.js";
15
15
  export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached } from "./negotiation.turn-cap.js";
16
16
  export { expectedNegotiationSpeaker } from "./negotiation.expected-speaker.js";
17
+ export { negotiationScopeKey, readNegotiationMessages } from "./negotiation.scope.js";
17
18
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode, } from "./negotiation.consultation-policy.js";
18
- 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";
19
20
  export { renderNegotiatorChatMemorySection } from "./negotiation.memory.js";