@indexnetwork/protocol 11.0.1-rc.455.1 → 11.1.0-rc.457.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 (39) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/capabilities/negotiation.facade.d.ts +4 -1
  3. package/dist/capabilities/negotiation.facade.js +2 -1
  4. package/dist/capabilities/negotiation.questions.facade.d.ts +1 -1
  5. package/dist/capabilities/negotiation.questions.facade.js +1 -1
  6. package/dist/index.d.ts +8 -4
  7. package/dist/index.js +5 -1
  8. package/dist/negotiation/application/negotiation.agent.d.ts +7 -1
  9. package/dist/negotiation/application/negotiation.agent.js +6 -4
  10. package/dist/negotiation/application/negotiation.detail-reader.js +3 -6
  11. package/dist/negotiation/application/negotiation.graph.d.ts +17 -29
  12. package/dist/negotiation/application/negotiation.graph.js +35 -49
  13. package/dist/negotiation/application/negotiation.tools.js +40 -43
  14. package/dist/negotiation/domain/index.d.ts +5 -0
  15. package/dist/negotiation/domain/index.js +4 -0
  16. package/dist/negotiation/domain/negotiation.consultation-policy.d.ts +2 -3
  17. package/dist/negotiation/domain/negotiation.expected-speaker.d.ts +19 -0
  18. package/dist/negotiation/domain/negotiation.expected-speaker.js +47 -0
  19. package/dist/negotiation/domain/negotiation.hermes-contract.d.ts +31 -0
  20. package/dist/negotiation/domain/negotiation.hermes-contract.js +72 -0
  21. package/dist/negotiation/domain/negotiation.protocol.d.ts +36 -66
  22. package/dist/negotiation/domain/negotiation.state.d.ts +10 -19
  23. package/dist/negotiation/domain/negotiation.turn-cap.d.ts +9 -0
  24. package/dist/negotiation/domain/negotiation.turn-cap.js +12 -0
  25. package/dist/negotiation/public/index.d.ts +4 -1
  26. package/dist/negotiation/public/index.js +3 -1
  27. package/dist/questions/application/question.input.d.ts +4 -8
  28. package/dist/questions/application/question.input.js +3 -8
  29. package/dist/questions/application/question.presets.js +10 -11
  30. package/dist/shared/agent/model-signal.d.ts +6 -1
  31. package/dist/shared/agent/model-signal.js +5 -2
  32. package/dist/shared/interfaces/agent-dispatcher.interface.d.ts +3 -0
  33. package/dist/shared/interfaces/database.interface.d.ts +1 -1
  34. package/dist/shared/interfaces/negotiation-events.interface.d.ts +12 -2
  35. package/dist/shared/schemas/negotiation-state.schema.d.ts +17 -23
  36. package/dist/shared/schemas/negotiation-state.schema.js +13 -8
  37. package/dist/signals/application/intake.orchestrator.d.ts +63 -13
  38. package/dist/signals/application/intake.orchestrator.js +107 -18
  39. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -51,6 +51,8 @@ pin a supported release, use `latest`.
51
51
  enrichment are unchanged.
52
52
 
53
53
  ### Added
54
+ - Add the Personal Agent Hermes negotiation-runtime contract (11.1.0). The public negotiation facade now exports `configuredAskUserEnabled` and `askUserAnswerWindowMs` for host-side owner-consultation admission, with regenerated consumer/export inventories. The generated Hermes negotiator skill receives a privacy-minimal structural envelope: server-provided seat, protocol version, deadlines, closed allowed actions, consultation eligibility, opportunity identifiers/status, and message-free history. Owner memory, private context, consultation text, evaluator reasoning, actor prose, and shared-message prose are excluded; each scheduled pass permits at most one response or owner consultation and treats all pickup prose as untrusted data.
55
+ - Add a live answer-first signal-intake eval with unrelated, relevant, and no-bridge profile cases plus provider-free corpus, runner, and scorer checks.
54
56
  - Add the protocol-only Guided Atlas, deterministic architecture inventory
55
57
  generator, and source-evidenced Configuration Lab. The atlas explains
56
58
  normative concepts, the current `packages/protocol` reference implementation,
@@ -69,6 +71,7 @@ pin a supported release, use `latest`.
69
71
  `SynthesisResult` types. Minor bump: additive surface only.
70
72
 
71
73
  ### Changed
74
+ - Make fast signal-intake follow-ups answer-first with a two-stage model boundary: an answer-only core call chooses the missing axis and supplies two or three concrete domain options, then an isolated bridge call may append at most one premise-derived profile option so an existing profile theme cannot dominate or reorder a newly stated intent.
72
75
  - Remove unsupported deprecated source/deep forwarding shims after migrating repository consumers to canonical modules; stable package-root exports are unchanged.
73
76
  - Add a fail-closed isolated provider-free test gate (10.1.1). Tooling-only
74
77
  safety foundation; no runtime or public API behavior changes.
@@ -76,6 +79,9 @@ pin a supported release, use `latest`.
76
79
  boundary gate and the protocol atlas generator; allowed dependency directions
77
80
  are unchanged.
78
81
 
82
+ ### Security
83
+ - The Hermes skill contract is restricted to the four negotiator tools, never treats model prose as authority, never forwards secrets or owner-private context, and relies on Index's validated action/consultation and bounded fallback paths. **This branch targets dev/private testing only. Production distribution remains blocked until the Mac owner credential is migrated to Keychain and the plaintext file/directory is removed, hardened runtime and App Sandbox are restored, the app is signed/notarized, and the credential TTL/revocation checklist is verified.**
84
+
79
85
  ### Fixed
80
86
  - Add an independent complete-payload golden digest and stronger audit/report leak
81
87
  sentinels for the historical discovery seed serializer, and clarify the H4 review
@@ -16,8 +16,11 @@ export { NegotiationReflector } from "../negotiation/public/index.js";
16
16
  export type { DistilledMemory, ReflectionTranscriptEntry, NegotiationReflectionInput, ChatReflectionInput, NegotiationReflectJobData, ReflectEnqueueFn } from "../negotiation/public/index.js";
17
17
  export type { NegotiatorMemoryEntry } from "../negotiation/public/index.js";
18
18
  export type { NegotiationGraphLike } from "../negotiation/public/index.js";
19
+ export { HERMES_OWNER_DIRECTIVE, HERMES_SHARED_MESSAGE_TEMPLATES, HermesNegotiationActionSchema, HermesNegotiationResponseSchema, HermesOwnerDirectiveSchema, HermesRoleAlignmentSchema, allowedHermesActionsFor, buildHermesNegotiationTurn, } from "../negotiation/public/index.js";
20
+ export type { HermesNegotiationAction, HermesNegotiationResponse, HermesOwnerDirective, HermesRoleAlignment, } from "../negotiation/public/index.js";
19
21
  export { AMBIENT_PARK_WINDOW_MS } from "../negotiation/public/index.js";
20
- export { allowedActionsFor, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "../negotiation/public/index.js";
22
+ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached, expectedNegotiationSpeaker, allowedActionsFor, askUserAnswerWindowMs, configuredAskUserEnabled, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "../negotiation/public/index.js";
23
+ export type { NegotiationSpeakerParticipants, NegotiationSpeakerMessage } from "../negotiation/public/index.js";
21
24
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "../negotiation/public/index.js";
22
25
  export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode, NegotiationConsultationReason } from "../negotiation/public/index.js";
23
26
  export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, validateInflightAskUserFields, negotiationQuestionSettlementId, } from "../negotiation/public/index.js";
@@ -11,7 +11,8 @@ export { NegotiationInsightsGenerator } from "../negotiation/public/index.js";
11
11
  export { IndexNegotiator } from "../negotiation/public/index.js";
12
12
  export { NegotiationScreener } from "../negotiation/public/index.js";
13
13
  export { NegotiationReflector } from "../negotiation/public/index.js";
14
+ export { HERMES_OWNER_DIRECTIVE, HERMES_SHARED_MESSAGE_TEMPLATES, HermesNegotiationActionSchema, HermesNegotiationResponseSchema, HermesOwnerDirectiveSchema, HermesRoleAlignmentSchema, allowedHermesActionsFor, buildHermesNegotiationTurn, } from "../negotiation/public/index.js";
14
15
  export { AMBIENT_PARK_WINDOW_MS } from "../negotiation/public/index.js";
15
- export { allowedActionsFor, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "../negotiation/public/index.js";
16
+ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached, expectedNegotiationSpeaker, allowedActionsFor, askUserAnswerWindowMs, configuredAskUserEnabled, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "../negotiation/public/index.js";
16
17
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "../negotiation/public/index.js";
17
18
  export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, validateInflightAskUserFields, negotiationQuestionSettlementId, } from "../negotiation/public/index.js";
@@ -2,5 +2,5 @@
2
2
  * Narrow question-policy port consumed by the Questions capability — IND-550.
3
3
  * Sources now route through the canonical negotiation module public surface.
4
4
  */
5
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, } from "../negotiation/public/index.js";
5
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, consultationPromptFor, isSafeNegotiationQuestionText, } from "../negotiation/public/index.js";
6
6
  export type { NegotiationConsultationReason } from "../negotiation/public/index.js";
@@ -2,4 +2,4 @@
2
2
  * Narrow question-policy port consumed by the Questions capability — IND-550.
3
3
  * Sources now route through the canonical negotiation module public surface.
4
4
  */
5
- export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, } from "../negotiation/public/index.js";
5
+ export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, consultationPromptFor, isSafeNegotiationQuestionText, } from "../negotiation/public/index.js";
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@ export type { IntegrationAdapter, IntegrationConnection, IntegrationSession, Int
21
21
  export type { IntentGraphQueue } from "./shared/interfaces/queue.interface.js";
22
22
  export type { Scraper } from "./shared/interfaces/scraper.interface.js";
23
23
  export type { EnrichmentRunInput, EnrichmentRunRecord } from "./shared/interfaces/enrichment-run.interface.js";
24
- export type { NegotiationTimeoutQueue, AskUserExpiryPayload } from "./shared/interfaces/negotiation-events.interface.js";
24
+ export type { NegotiationTimeoutQueue, AskUserExpiryPayload, NegotiationContinuationTimeoutIdentity, } from "./shared/interfaces/negotiation-events.interface.js";
25
25
  export type { AgentDispatcher, AgentDispatchResult, NegotiationTurnPayload } from "./shared/interfaces/agent-dispatcher.interface.js";
26
26
  export { SYSTEM_AGENT_IDS } from './capabilities/participant-agents.facade.js';
27
27
  export { ChatContextDigestSchema, type ChatContextDigest } from "./shared/schemas/chat-context.schema.js";
@@ -151,10 +151,14 @@ export { MCP_AGENT_ADMIN_TOOLS, CANONICAL_MCP_CAPABILITY_POLICY_OPTIONS, CANONIC
151
151
  export { ActivityQuestionCountsSchema, ActivityQuestionDomainSchema, ActivitySummaryDomainSchema, ActivitySummaryResponseSchema, McpActivityCallerSchema, QUESTION_MODE_TO_DOMAIN, READ_ACTIVITY_SUMMARY_TOOL_NAME, activitySummaryNetworkId, projectActivitySummary, resolveActivitySummaryDomains, } from "./shared/agent/activity-projection.js";
152
152
  export type { ActivityQuestionCounts, ActivityQuestionDomain, ActivitySummaryDomain, McpActivityCaller, ProjectedActivitySummary, } from "./shared/agent/activity-projection.js";
153
153
  export type { McpAuthorizationDenialEvent, McpAuthorizationObserver, McpCapabilityDecision, McpCapabilityDecisionReason, McpCapabilityPolicyOptions, McpCapabilitySubject, McpPermissionAction, McpPolicyAgentSnapshot, McpPrincipalProfile, McpToolPermissionMap, McpToolPermissionRequirement, McpToolAccessRule, McpToolAccessRuleMap, ResolveMcpCapabilitySubjectInput, } from "./mcp/mcp.authorization-policy.js";
154
+ export { AskUserPayloadSchema, NEGOTIATION_CONSULTATION_REASONS, NegotiationConsultationReasonSchema, } from "./shared/schemas/negotiation-state.schema.js";
154
155
  export type { UserNegotiationContext, NegotiationTurn, NegotiationOutcome, SeedAssessment } from "./shared/schemas/negotiation-state.schema.js";
155
- export type { NegotiationAction, NegotiationSeat, NegotiationProtocolVersion } from "./shared/schemas/negotiation-state.schema.js";
156
+ export type { NegotiationAction, NegotiationConsultationReason, NegotiationSeat, NegotiationProtocolVersion } from "./shared/schemas/negotiation-state.schema.js";
156
157
  export type { NegotiationGraphLike } from "./capabilities/negotiation.facade.js";
157
- export { allowedActionsFor, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "./capabilities/negotiation.facade.js";
158
+ export { HERMES_OWNER_DIRECTIVE, HERMES_SHARED_MESSAGE_TEMPLATES, HermesNegotiationActionSchema, HermesNegotiationResponseSchema, HermesOwnerDirectiveSchema, HermesRoleAlignmentSchema, allowedHermesActionsFor, buildHermesNegotiationTurn, } from "./capabilities/negotiation.facade.js";
159
+ export type { HermesNegotiationAction, HermesNegotiationResponse, HermesOwnerDirective, HermesRoleAlignment, } from "./capabilities/negotiation.facade.js";
160
+ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached, expectedNegotiationSpeaker, allowedActionsFor, askUserAnswerWindowMs, configuredAskUserEnabled, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "./capabilities/negotiation.facade.js";
161
+ export type { NegotiationSpeakerParticipants, NegotiationSpeakerMessage } from "./capabilities/negotiation.facade.js";
158
162
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "./capabilities/negotiation.facade.js";
159
- export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode, NegotiationConsultationReason } from "./capabilities/negotiation.facade.js";
163
+ export type { ConsultationEligibility, ConsultationEligibilityInput, NegotiationConsultationPolicyMode } from "./capabilities/negotiation.facade.js";
160
164
  export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./capabilities/negotiation.facade.js";
package/dist/index.js CHANGED
@@ -121,7 +121,11 @@ export { normalizeTelegramHandle } from './shared/utils/telegram-handle.js';
121
121
  export { createMcpServer, buildMcpOnboardingMessage, ONBOARDING_ALLOWED } from "./mcp/mcp.server.js";
122
122
  export { MCP_AGENT_ADMIN_TOOLS, CANONICAL_MCP_CAPABILITY_POLICY_OPTIONS, CANONICAL_MCP_TOOL_ACCESS_RULES, MCP_INFORMATIONAL_TOOLS, MCP_PERMISSION_ACTIONS, McpCapabilityPolicy, McpCapabilitySubjectSchema, McpPermissionActionSchema, McpPolicyAgentSnapshotSchema, McpPrincipalProfileSchema, McpToolPermissionRequirementSchema, McpToolAccessRuleSchema, buildMcpAuthorizationDenialEvent, defineMcpToolAccessRules, defineMcpToolPermissionMap, resolveMcpActivityCaller, resolveMcpCapabilitySubject, } from "./mcp/mcp.authorization-policy.js";
123
123
  export { ActivityQuestionCountsSchema, ActivityQuestionDomainSchema, ActivitySummaryDomainSchema, ActivitySummaryResponseSchema, McpActivityCallerSchema, QUESTION_MODE_TO_DOMAIN, READ_ACTIVITY_SUMMARY_TOOL_NAME, activitySummaryNetworkId, projectActivitySummary, resolveActivitySummaryDomains, } from "./shared/agent/activity-projection.js";
124
+ // ─── States (for advanced graph consumers) ────────────────────────────────────
125
+ // @experimental — internal graph-state shapes; may change in a minor release.
126
+ export { AskUserPayloadSchema, NEGOTIATION_CONSULTATION_REASONS, NegotiationConsultationReasonSchema, } from "./shared/schemas/negotiation-state.schema.js";
127
+ export { HERMES_OWNER_DIRECTIVE, HERMES_SHARED_MESSAGE_TEMPLATES, HermesNegotiationActionSchema, HermesNegotiationResponseSchema, HermesOwnerDirectiveSchema, HermesRoleAlignmentSchema, allowedHermesActionsFor, buildHermesNegotiationTurn, } from "./capabilities/negotiation.facade.js";
124
128
  // ─── Negotiation seat rules (v2 client-advocate protocol) ───────────────────
125
- export { allowedActionsFor, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "./capabilities/negotiation.facade.js";
129
+ export { DEFAULT_NEGOTIATION_MAX_TURNS, isNegotiationTurnCapReached, expectedNegotiationSpeaker, allowedActionsFor, askUserAnswerWindowMs, configuredAskUserEnabled, isTerminalAction, isRejectLikeAction, readProtocolVersion, resolveSeat, seatViolationMessage } from "./capabilities/negotiation.facade.js";
126
130
  export { assessConsultationEligibility, consultationPromptFor, negotiationConsultationPolicyMode } from "./capabilities/negotiation.facade.js";
127
131
  export { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, NEGOTIATION_QUESTION_GENERIC_UPTAKE_ACTIVITY, isSafeNegotiationQuestionText, negotiationQuestionSettlementId, validateInflightAskUserFields, } from "./capabilities/negotiation.facade.js";
@@ -69,6 +69,12 @@ export interface NegotiationAgentInput {
69
69
  * falls back to the flat continuation history (byte-identical to before).
70
70
  */
71
71
  priorDialogue?: AttributedPriorDialogue;
72
+ /**
73
+ * Durable caller-owned execution identity. Timeout workers reuse this exact
74
+ * value across delivery retries; it is forwarded as model-run metadata for
75
+ * deterministic provider tracing/idempotency without entering the prompt.
76
+ */
77
+ executionId?: string;
72
78
  }
73
79
  export interface IndexNegotiatorConfig {
74
80
  /**
@@ -106,5 +112,5 @@ export declare class IndexNegotiator {
106
112
  protected callModel(model: ReturnType<typeof createStructuredModel>, chatMessages: Array<{
107
113
  role: string;
108
114
  content: string;
109
- }>): Promise<unknown>;
115
+ }>, executionId?: string): Promise<unknown>;
110
116
  }
@@ -56,7 +56,7 @@ const V2_INITIATOR_RULES = `- You hold the INITIATING seat: your user's side sur
56
56
  * prompt (or the schema) otherwise.
57
57
  */
58
58
  const ASK_USER_RULE = `
59
- - "ask_user" if you need {userName}'s OWN input before you can proceed — typically permission to disclose something sensitive (budget, availability, private details) or a fact only they know. This PAUSES the negotiation until they answer (up to 24h), so use it only when proceeding without their input would risk over-disclosure or a wrong call. You get AT MOST ONE client consultation per negotiation spend it well. Set askUser: { disclosureSubject: what you need permission for or need to know, draftQuestion: the question in your words }. Use "question" (not "ask_user") when the clarification should come from the other side.`;
59
+ - "ask_user" if you need {userName}'s OWN input before you can proceed. This PAUSES the negotiation until they answer (up to 24h), so use it only when proceeding without their input would risk over-disclosure or a wrong call. You get AT MOST ONE client consultation per negotiation. Set askUser to exactly one closed server category: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" }. Never write question text or instructions in askUser; the server owns all owner-facing copy. Use "question" (not "ask_user") when clarification should come from the other side.`;
60
60
  /** v2 counterparty seat: receiving stance — acceptance is this seat's decision alone. */
61
61
  const V2_COUNTERPARTY_RULES = `- You hold the RECEIVING seat: the other side reached out to {userName}. Whether to accept is YOUR seat's decision alone.
62
62
  - Evaluate the initiator's arguments. Either:
@@ -233,7 +233,7 @@ ${input.history.length === 0 && !input.isContinuation ? (version === "v2" && sea
233
233
  // out-of-vocabulary actions. Validate; retry once; then fall back to the
234
234
  // conservative seat-valid action instead of poisoning the turn history.
235
235
  for (let attempt = 0; attempt < 2; attempt++) {
236
- const result = await this.callModel(model, chatMessages);
236
+ const result = await this.callModel(model, chatMessages, input.executionId);
237
237
  const parsed = schema.safeParse(result);
238
238
  if (parsed.success)
239
239
  return parsed.data;
@@ -262,7 +262,9 @@ ${input.history.length === 0 && !input.isContinuation ? (version === "v2" && sea
262
262
  * Raw structured-model round trip. Split out as a seam so tests can drive
263
263
  * the validate→retry→fallback loop without a live provider.
264
264
  */
265
- async callModel(model, chatMessages) {
266
- return invokeWithAbortSignal(model, chatMessages, AbortSignal.timeout(this.turnTimeoutMs));
265
+ async callModel(model, chatMessages, executionId) {
266
+ return invokeWithAbortSignal(model, chatMessages, AbortSignal.timeout(this.turnTimeoutMs), executionId
267
+ ? { metadata: { timeoutExecutionId: executionId }, tags: ['negotiation-timeout'] }
268
+ : undefined);
267
269
  }
268
270
  }
@@ -1,3 +1,4 @@
1
+ import { expectedNegotiationSpeaker } from '../domain/negotiation.expected-speaker.js';
1
2
  import { buildLifecycleNarration } from '../domain/negotiation.lifecycle-narration.js';
2
3
  import { allowedActionsFor, readProtocolVersion, resolveSeat } from '../domain/negotiation.protocol.js';
3
4
  /**
@@ -53,16 +54,12 @@ export async function readAuthorizedNegotiationDetail(input) {
53
54
  ? outcomeArtifact.parts?.find((part) => part.kind === 'data')?.data
54
55
  : null;
55
56
  const turnCount = messages.length;
56
- const lastSenderId = turnCount > 0 ? messages[turnCount - 1].senderId : null;
57
- const currentSpeaker = lastSenderId
58
- ? (lastSenderId === `agent:${metadata.sourceUserId}` ? 'candidate' : 'source')
59
- : (turnCount % 2 === 0 ? 'source' : 'candidate');
57
+ const expectedSpeaker = expectedNegotiationSpeaker(metadata, messages);
60
58
  const status = task.state === 'working' ? 'active'
61
59
  : task.state === 'waiting_for_agent' ? 'waiting_for_agent'
62
60
  : task.state === 'completed' ? 'completed'
63
61
  : task.state;
64
- const isUsersTurn = status !== 'completed' &&
65
- ((isSource && currentSpeaker === 'source') || (!isSource && currentSpeaker === 'candidate'));
62
+ const isUsersTurn = status !== 'completed' && expectedSpeaker === callerUserId;
66
63
  const protocolVersion = readProtocolVersion(metadata) ?? 'v1';
67
64
  const seat = resolveSeat(callerUserId, metadata);
68
65
  const priorTurnCount = metadata.priorTurnCount ?? 0;
@@ -50,7 +50,7 @@ export declare class NegotiationGraphFactory {
50
50
  continuationExecution: import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined;
51
51
  continuationReceipt: NegotiationContinuationReceipt | undefined;
52
52
  privateConsultation: import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined;
53
- consultationPolicyReason: NegotiationConsultationReason | undefined;
53
+ consultationPolicyReason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined;
54
54
  conversationId: string;
55
55
  taskId: string;
56
56
  messages: import("../domain/negotiation.state.js").NegotiationMessage[];
@@ -69,8 +69,7 @@ export declare class NegotiationGraphFactory {
69
69
  };
70
70
  message?: string | null | undefined;
71
71
  askUser?: {
72
- disclosureSubject: string;
73
- draftQuestion?: string | null | undefined;
72
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
74
73
  } | null | undefined;
75
74
  } | null;
76
75
  status: "waiting_for_agent" | "input_required" | "completed" | "active";
@@ -118,7 +117,7 @@ export declare class NegotiationGraphFactory {
118
117
  continuationExecution?: import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined> | undefined;
119
118
  continuationReceipt?: NegotiationContinuationReceipt | import("@langchain/langgraph").OverwriteValue<NegotiationContinuationReceipt | undefined> | undefined;
120
119
  privateConsultation?: import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined> | undefined;
121
- consultationPolicyReason?: NegotiationConsultationReason | import("@langchain/langgraph").OverwriteValue<NegotiationConsultationReason | undefined> | undefined;
120
+ consultationPolicyReason?: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | import("@langchain/langgraph").OverwriteValue<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined> | undefined;
122
121
  conversationId?: string | import("@langchain/langgraph").OverwriteValue<string> | undefined;
123
122
  taskId?: string | import("@langchain/langgraph").OverwriteValue<string> | undefined;
124
123
  messages?: import("../domain/negotiation.state.js").NegotiationMessage[] | import("@langchain/langgraph").OverwriteValue<import("../domain/negotiation.state.js").NegotiationMessage[]> | undefined;
@@ -137,8 +136,7 @@ export declare class NegotiationGraphFactory {
137
136
  };
138
137
  message?: string | null | undefined;
139
138
  askUser?: {
140
- disclosureSubject: string;
141
- draftQuestion?: string | null | undefined;
139
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
142
140
  } | null | undefined;
143
141
  } | import("@langchain/langgraph").OverwriteValue<{
144
142
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -151,8 +149,7 @@ export declare class NegotiationGraphFactory {
151
149
  };
152
150
  message?: string | null | undefined;
153
151
  askUser?: {
154
- disclosureSubject: string;
155
- draftQuestion?: string | null | undefined;
152
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
156
153
  } | null | undefined;
157
154
  } | null> | null | undefined;
158
155
  status?: "waiting_for_agent" | "input_required" | "completed" | "active" | import("@langchain/langgraph").OverwriteValue<"waiting_for_agent" | "input_required" | "completed" | "active"> | undefined;
@@ -212,7 +209,7 @@ export declare class NegotiationGraphFactory {
212
209
  continuationExecution: import("@langchain/langgraph").BaseChannel<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined, import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined> | undefined, unknown>;
213
210
  continuationReceipt: import("@langchain/langgraph").BaseChannel<NegotiationContinuationReceipt | undefined, NegotiationContinuationReceipt | import("@langchain/langgraph").OverwriteValue<NegotiationContinuationReceipt | undefined> | undefined, unknown>;
214
211
  privateConsultation: import("@langchain/langgraph").BaseChannel<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined, import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined> | undefined, unknown>;
215
- consultationPolicyReason: import("@langchain/langgraph").BaseChannel<NegotiationConsultationReason | undefined, NegotiationConsultationReason | import("@langchain/langgraph").OverwriteValue<NegotiationConsultationReason | undefined> | undefined, unknown>;
212
+ consultationPolicyReason: import("@langchain/langgraph").BaseChannel<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined, "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | import("@langchain/langgraph").OverwriteValue<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined> | undefined, unknown>;
216
213
  conversationId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
217
214
  taskId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
218
215
  messages: import("@langchain/langgraph").BaseChannel<import("../domain/negotiation.state.js").NegotiationMessage[], import("../domain/negotiation.state.js").NegotiationMessage[] | import("@langchain/langgraph").OverwriteValue<import("../domain/negotiation.state.js").NegotiationMessage[]>, unknown>;
@@ -231,8 +228,7 @@ export declare class NegotiationGraphFactory {
231
228
  };
232
229
  message?: string | null | undefined;
233
230
  askUser?: {
234
- disclosureSubject: string;
235
- draftQuestion?: string | null | undefined;
231
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
236
232
  } | null | undefined;
237
233
  } | null, {
238
234
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -245,8 +241,7 @@ export declare class NegotiationGraphFactory {
245
241
  };
246
242
  message?: string | null | undefined;
247
243
  askUser?: {
248
- disclosureSubject: string;
249
- draftQuestion?: string | null | undefined;
244
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
250
245
  } | null | undefined;
251
246
  } | import("@langchain/langgraph").OverwriteValue<{
252
247
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -259,8 +254,7 @@ export declare class NegotiationGraphFactory {
259
254
  };
260
255
  message?: string | null | undefined;
261
256
  askUser?: {
262
- disclosureSubject: string;
263
- draftQuestion?: string | null | undefined;
257
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
264
258
  } | null | undefined;
265
259
  } | null> | null, unknown>;
266
260
  status: import("@langchain/langgraph").BaseChannel<"waiting_for_agent" | "input_required" | "completed" | "active", "waiting_for_agent" | "input_required" | "completed" | "active" | import("@langchain/langgraph").OverwriteValue<"waiting_for_agent" | "input_required" | "completed" | "active">, unknown>;
@@ -329,7 +323,7 @@ export declare class NegotiationGraphFactory {
329
323
  continuationExecution: import("@langchain/langgraph").BaseChannel<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined, import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined> | undefined, unknown>;
330
324
  continuationReceipt: import("@langchain/langgraph").BaseChannel<NegotiationContinuationReceipt | undefined, NegotiationContinuationReceipt | import("@langchain/langgraph").OverwriteValue<NegotiationContinuationReceipt | undefined> | undefined, unknown>;
331
325
  privateConsultation: import("@langchain/langgraph").BaseChannel<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined, import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined> | undefined, unknown>;
332
- consultationPolicyReason: import("@langchain/langgraph").BaseChannel<NegotiationConsultationReason | undefined, NegotiationConsultationReason | import("@langchain/langgraph").OverwriteValue<NegotiationConsultationReason | undefined> | undefined, unknown>;
326
+ consultationPolicyReason: import("@langchain/langgraph").BaseChannel<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined, "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | import("@langchain/langgraph").OverwriteValue<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined> | undefined, unknown>;
333
327
  conversationId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
334
328
  taskId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
335
329
  messages: import("@langchain/langgraph").BaseChannel<import("../domain/negotiation.state.js").NegotiationMessage[], import("../domain/negotiation.state.js").NegotiationMessage[] | import("@langchain/langgraph").OverwriteValue<import("../domain/negotiation.state.js").NegotiationMessage[]>, unknown>;
@@ -348,8 +342,7 @@ export declare class NegotiationGraphFactory {
348
342
  };
349
343
  message?: string | null | undefined;
350
344
  askUser?: {
351
- disclosureSubject: string;
352
- draftQuestion?: string | null | undefined;
345
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
353
346
  } | null | undefined;
354
347
  } | null, {
355
348
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -362,8 +355,7 @@ export declare class NegotiationGraphFactory {
362
355
  };
363
356
  message?: string | null | undefined;
364
357
  askUser?: {
365
- disclosureSubject: string;
366
- draftQuestion?: string | null | undefined;
358
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
367
359
  } | null | undefined;
368
360
  } | import("@langchain/langgraph").OverwriteValue<{
369
361
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -376,8 +368,7 @@ export declare class NegotiationGraphFactory {
376
368
  };
377
369
  message?: string | null | undefined;
378
370
  askUser?: {
379
- disclosureSubject: string;
380
- draftQuestion?: string | null | undefined;
371
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
381
372
  } | null | undefined;
382
373
  } | null> | null, unknown>;
383
374
  status: import("@langchain/langgraph").BaseChannel<"waiting_for_agent" | "input_required" | "completed" | "active", "waiting_for_agent" | "input_required" | "completed" | "active" | import("@langchain/langgraph").OverwriteValue<"waiting_for_agent" | "input_required" | "completed" | "active">, unknown>;
@@ -501,7 +492,7 @@ export declare class NegotiationGraphFactory {
501
492
  continuationExecution: import("@langchain/langgraph").BaseChannel<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined, import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationContinuationExecution | undefined> | undefined, unknown>;
502
493
  continuationReceipt: import("@langchain/langgraph").BaseChannel<NegotiationContinuationReceipt | undefined, NegotiationContinuationReceipt | import("@langchain/langgraph").OverwriteValue<NegotiationContinuationReceipt | undefined> | undefined, unknown>;
503
494
  privateConsultation: import("@langchain/langgraph").BaseChannel<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined, import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | import("@langchain/langgraph").OverwriteValue<import("../../shared/interfaces/database.interface.js").NegotiationPrivateConsultation | undefined> | undefined, unknown>;
504
- consultationPolicyReason: import("@langchain/langgraph").BaseChannel<NegotiationConsultationReason | undefined, NegotiationConsultationReason | import("@langchain/langgraph").OverwriteValue<NegotiationConsultationReason | undefined> | undefined, unknown>;
495
+ consultationPolicyReason: import("@langchain/langgraph").BaseChannel<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined, "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | import("@langchain/langgraph").OverwriteValue<"unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority" | undefined> | undefined, unknown>;
505
496
  conversationId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
506
497
  taskId: import("@langchain/langgraph").BaseChannel<string, string | import("@langchain/langgraph").OverwriteValue<string>, unknown>;
507
498
  messages: import("@langchain/langgraph").BaseChannel<import("../domain/negotiation.state.js").NegotiationMessage[], import("../domain/negotiation.state.js").NegotiationMessage[] | import("@langchain/langgraph").OverwriteValue<import("../domain/negotiation.state.js").NegotiationMessage[]>, unknown>;
@@ -520,8 +511,7 @@ export declare class NegotiationGraphFactory {
520
511
  };
521
512
  message?: string | null | undefined;
522
513
  askUser?: {
523
- disclosureSubject: string;
524
- draftQuestion?: string | null | undefined;
514
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
525
515
  } | null | undefined;
526
516
  } | null, {
527
517
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -534,8 +524,7 @@ export declare class NegotiationGraphFactory {
534
524
  };
535
525
  message?: string | null | undefined;
536
526
  askUser?: {
537
- disclosureSubject: string;
538
- draftQuestion?: string | null | undefined;
527
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
539
528
  } | null | undefined;
540
529
  } | import("@langchain/langgraph").OverwriteValue<{
541
530
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -548,8 +537,7 @@ export declare class NegotiationGraphFactory {
548
537
  };
549
538
  message?: string | null | undefined;
550
539
  askUser?: {
551
- disclosureSubject: string;
552
- draftQuestion?: string | null | undefined;
540
+ reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
553
541
  } | null | undefined;
554
542
  } | null> | null, unknown>;
555
543
  status: import("@langchain/langgraph").BaseChannel<"waiting_for_agent" | "input_required" | "completed" | "active", "waiting_for_agent" | "input_required" | "completed" | "active" | import("@langchain/langgraph").OverwriteValue<"waiting_for_agent" | "input_required" | "completed" | "active">, unknown>;
@@ -9,9 +9,11 @@ import { blocksNegotiationBeforeFirstTurn, NegotiationScreener } from "./negotia
9
9
  import { configuredScreenMode } from "../domain/negotiation.screen.contracts.js";
10
10
  import { assessDeadlock, configuredDeadlockShiftEnabled, configuredDeadlockThreshold } from "../domain/negotiation.deadlock.js";
11
11
  import { protocolLogger } from "../../shared/observability/protocol.logger.js";
12
- import { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, negotiationQuestionSettlementId, validateInflightAskUserFields } from '../domain/negotiation.question-safety.js';
12
+ import { NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY, NEGOTIATION_QUESTION_GENERIC_NETWORK, negotiationQuestionSettlementId } from '../domain/negotiation.question-safety.js';
13
13
  import { buildIntentSnapshots } from "../domain/negotiation.intent-snapshot-provenance.js";
14
14
  import { holdsNegotiationConversationLock } from "../domain/negotiation.task-lock-policy.js";
15
+ import { isNegotiationTurnCapReached } from "../domain/negotiation.turn-cap.js";
16
+ import { expectedNegotiationSpeaker } from "../domain/negotiation.expected-speaker.js";
15
17
  import { attributedDialogueIsEmpty, buildSeededAttribution, combineAttributedDialogue } from '../negotiation.attribution.js';
16
18
  const logger = protocolLogger("NegotiationGraph");
17
19
  const initLog = protocolLogger("NegotiationGraph:Init");
@@ -196,22 +198,15 @@ export class NegotiationGraphFactory {
196
198
  // --- Load prior messages and determine continuation ---
197
199
  const priorTurns = turnsFromMessages(priorMessages);
198
200
  const isContinuation = priorTurns.length > 0;
199
- // Determine currentSpeaker from last prior message. An `ask_user` last
200
- // turn does NOT pass the floor: the sender paused to consult its own
201
- // client, so on resume the same side speaks again — now armed with the
202
- // client's answer (or its recorded absence). Flipping here would hand
203
- // the turn to the counterparty, who has nothing to respond to.
204
- let currentSpeaker = 'source';
205
- if (isContinuation && priorMessages.length > 0) {
206
- const lastMessage = priorMessages[priorMessages.length - 1];
207
- const lastAction = turnsFromMessages([lastMessage])[0]?.action;
208
- if (lastAction === 'ask_user') {
209
- currentSpeaker = lastMessage.senderId === agentIdA ? 'source' : 'candidate';
210
- }
211
- else {
212
- currentSpeaker = lastMessage.senderId === agentIdA ? 'candidate' : 'source';
213
- }
214
- }
201
+ const expectedSpeaker = expectedNegotiationSpeaker({
202
+ sourceUserId: state.sourceUser.id,
203
+ candidateUserId: state.candidateUser.id,
204
+ }, priorMessages);
205
+ if (!expectedSpeaker)
206
+ return { error: 'invalid negotiation participants' };
207
+ const currentSpeaker = expectedSpeaker === state.sourceUser.id
208
+ ? 'source'
209
+ : 'candidate';
215
210
  // Determine scenario-based maxTurns
216
211
  const scope = { action: 'manage:negotiations', scopeType: 'network', scopeId: state.indexContext.networkId };
217
212
  const [sourceHasAgent, candidateHasAgent] = await Promise.all([
@@ -485,9 +480,8 @@ export class NegotiationGraphFactory {
485
480
  const ownUser = isSource ? state.sourceUser : state.candidateUser;
486
481
  const otherUser = isSource ? state.candidateUser : state.sourceUser;
487
482
  const ownIntentId = isSource ? state.sourceIntentId : state.candidateIntentId;
488
- // Determine if this is the system agent's final allowed turn
489
- const maxTurns = state.maxTurns ?? 0;
490
- const isFinalTurn = maxTurns > 0 && (state.turnCount + 1) >= maxTurns;
483
+ // Determine if this is the system agent's final allowed turn.
484
+ const isFinalTurn = isNegotiationTurnCapReached(state.turnCount + 1, state.maxTurns);
491
485
  // Seat attribution keys on initiatorUserId (rigid v2 stamp), never on
492
486
  // parity or source/candidate position — under the conversation-scoped
493
487
  // tie-break this run's source may hold the counterparty seat.
@@ -558,6 +552,17 @@ export class NegotiationGraphFactory {
558
552
  ...(state.privateConsultation?.recipientUserId === ownUser.id
559
553
  ? { privateConsultation: state.privateConsultation }
560
554
  : {}),
555
+ ...(state.continuationExecution
556
+ ? {
557
+ timeoutContinuation: {
558
+ priorTaskId: state.continuationExecution.taskId,
559
+ settlementId: state.continuationExecution.settlementId,
560
+ successorTaskId: state.continuationExecution.successorTaskId,
561
+ token: state.continuationExecution.token,
562
+ fence: state.continuationExecution.fence,
563
+ },
564
+ }
565
+ : {}),
561
566
  };
562
567
  const scope = { action: 'manage:negotiations', scopeType: 'network', scopeId: state.indexContext.networkId };
563
568
  const dispatchResult = await dispatcher.dispatch(ownUser.id, scope, payload, { timeoutMs: state.timeoutMs });
@@ -595,7 +600,7 @@ export class NegotiationGraphFactory {
595
600
  ? { privateConsultation: state.privateConsultation }
596
601
  : {}),
597
602
  }, state.continuationExecution);
598
- await database.updateTaskState(state.taskId, "waiting_for_agent", undefined, state.continuationExecution);
603
+ await database.updateTaskState(state.taskId, "waiting_for_agent", undefined, state.continuationExecution, dispatchResult.resumeToken);
599
604
  return { status: 'waiting_for_agent' };
600
605
  }
601
606
  else {
@@ -703,7 +708,7 @@ export class NegotiationGraphFactory {
703
708
  reasoning: 'Client consultation required.',
704
709
  suggestedRoles: turn.assessment.suggestedRoles,
705
710
  },
706
- askUser: consultationPromptFor(consultationPolicyReason),
711
+ askUser: { reason: consultationPolicyReason },
707
712
  };
708
713
  emitConsultationTelemetry('asked', consultationPolicyReason);
709
714
  }
@@ -775,27 +780,10 @@ export class NegotiationGraphFactory {
775
780
  // like the waiting_for_agent suspend; the answer (or window expiry)
776
781
  // resumes via the run-existing continuation path.
777
782
  if (turn.action === 'ask_user') {
778
- const counterparty = isSource ? state.candidateUser : state.sourceUser;
779
- const safeAskUser = validateInflightAskUserFields({
780
- disclosureSubject: turn.askUser?.disclosureSubject,
781
- draftQuestion: turn.askUser?.draftQuestion,
782
- forbiddenIdentifiers: [
783
- counterparty.id,
784
- counterparty.profile.name ?? '',
785
- state.opportunityId,
786
- state.taskId,
787
- state.indexContext.networkId,
788
- isSource ? state.candidateIntentId ?? '' : state.sourceIntentId ?? '',
789
- ],
790
- forbiddenSourceText: [
791
- counterparty.profile.bio ?? '',
792
- counterparty.profile.location ?? '',
793
- ...counterparty.intents.flatMap((intent) => [intent.title, intent.description]),
794
- state.seedAssessment.reasoning,
795
- state.indexContext.prompt,
796
- state.discoveryQuery ?? '',
797
- ],
798
- });
783
+ const consultationReason = turn.askUser?.reason;
784
+ const safeAskUser = consultationReason
785
+ ? consultationPromptFor(consultationReason)
786
+ : null;
799
787
  const settlementId = negotiationQuestionSettlementId(state.taskId);
800
788
  const askUserBinding = await database.captureNegotiationAskUserBinding({
801
789
  taskId: state.taskId,
@@ -810,7 +798,7 @@ export class NegotiationGraphFactory {
810
798
  indexContext: state.indexContext,
811
799
  seedAssessment: state.seedAssessment,
812
800
  ...(isSource && state.discoveryQuery && { discoveryQuery: state.discoveryQuery }),
813
- ...(consultationPolicyReason && { consultationPolicyReason }),
801
+ ...(consultationReason && { consultationPolicyReason: consultationReason }),
814
802
  },
815
803
  ...(state.continuationExecution ? { continuationExecution: state.continuationExecution } : {}),
816
804
  });
@@ -852,10 +840,8 @@ export class NegotiationGraphFactory {
852
840
  context: {
853
841
  negotiationId: state.taskId,
854
842
  counterpartyHint: NEGOTIATION_QUESTION_GENERIC_COUNTERPARTY,
855
- disclosureSubject: safeAskUser.disclosureSubject,
856
- ...(safeAskUser.draftQuestion && { draftQuestion: safeAskUser.draftQuestion }),
857
843
  indexContext: NEGOTIATION_QUESTION_GENERIC_NETWORK,
858
- ...(consultationPolicyReason && { consultationPolicyReason }),
844
+ consultationPolicyReason: consultationReason,
859
845
  ...(userContext && { userContext }),
860
846
  },
861
847
  }).catch((error) => {
@@ -965,7 +951,7 @@ export class NegotiationGraphFactory {
965
951
  if (isTerminalAction(state.lastTurn.action))
966
952
  return "finalize";
967
953
  // question routes same as counter — next turn
968
- if ((state.maxTurns ?? 0) > 0 && state.turnCount >= state.maxTurns)
954
+ if (isNegotiationTurnCapReached(state.turnCount, state.maxTurns))
969
955
  return "finalize";
970
956
  return "turn";
971
957
  };
@@ -1038,7 +1024,7 @@ export class NegotiationGraphFactory {
1038
1024
  const blockedByScreenNode = blocksNegotiationBeforeFirstTurn(state.screenDecision, state.turnCount);
1039
1025
  const refusedAtOpeningTurn = state.firstTurnScreenedOut === true;
1040
1026
  const screenedOut = blockedByScreenNode || refusedAtOpeningTurn;
1041
- const atCap = !screenedOut && (state.maxTurns ?? 0) > 0 && state.turnCount >= state.maxTurns && !isTerminalAction(lastTurn?.action);
1027
+ const atCap = !screenedOut && isNegotiationTurnCapReached(state.turnCount, state.maxTurns) && !isTerminalAction(lastTurn?.action);
1042
1028
  let agreedRoles = [];
1043
1029
  if (hasOpportunity && history.length >= 2) {
1044
1030
  const acceptTurn = history[history.length - 1];