@indexnetwork/protocol 4.5.0-rc.335.1 → 4.5.0-rc.337.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 (48) hide show
  1. package/dist/chat/chat-streaming.types.d.ts +1 -1
  2. package/dist/chat/chat-streaming.types.d.ts.map +1 -1
  3. package/dist/chat/chat-streaming.types.js.map +1 -1
  4. package/dist/chat/chat.agent.d.ts +1 -1
  5. package/dist/chat/chat.agent.d.ts.map +1 -1
  6. package/dist/chat/chat.agent.js.map +1 -1
  7. package/dist/index.d.ts +6 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +3 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/negotiation/negotiation.agent.d.ts +9 -0
  12. package/dist/negotiation/negotiation.agent.d.ts.map +1 -1
  13. package/dist/negotiation/negotiation.agent.js +11 -3
  14. package/dist/negotiation/negotiation.agent.js.map +1 -1
  15. package/dist/negotiation/negotiation.graph.d.ts +133 -35
  16. package/dist/negotiation/negotiation.graph.d.ts.map +1 -1
  17. package/dist/negotiation/negotiation.graph.js +197 -9
  18. package/dist/negotiation/negotiation.graph.js.map +1 -1
  19. package/dist/negotiation/negotiation.protocol.d.ts +250 -2
  20. package/dist/negotiation/negotiation.protocol.d.ts.map +1 -1
  21. package/dist/negotiation/negotiation.protocol.js +57 -8
  22. package/dist/negotiation/negotiation.protocol.js.map +1 -1
  23. package/dist/negotiation/negotiation.reflect.d.ts +199 -0
  24. package/dist/negotiation/negotiation.reflect.d.ts.map +1 -0
  25. package/dist/negotiation/negotiation.reflect.js +153 -0
  26. package/dist/negotiation/negotiation.reflect.js.map +1 -0
  27. package/dist/negotiation/negotiation.state.d.ts +40 -7
  28. package/dist/negotiation/negotiation.state.d.ts.map +1 -1
  29. package/dist/negotiation/negotiation.state.js +5 -1
  30. package/dist/negotiation/negotiation.state.js.map +1 -1
  31. package/dist/opportunity/question.prompt.d.ts +1 -1
  32. package/dist/opportunity/question.prompt.d.ts.map +1 -1
  33. package/dist/opportunity/question.prompt.js.map +1 -1
  34. package/dist/shared/agent/model.config.d.ts +5 -0
  35. package/dist/shared/agent/model.config.d.ts.map +1 -1
  36. package/dist/shared/agent/model.config.js +1 -0
  37. package/dist/shared/agent/model.config.js.map +1 -1
  38. package/dist/shared/interfaces/negotiation-events.interface.d.ts +30 -0
  39. package/dist/shared/interfaces/negotiation-events.interface.d.ts.map +1 -1
  40. package/dist/shared/interfaces/negotiation-events.interface.js.map +1 -1
  41. package/dist/shared/schemas/discovery-question.schema.d.ts +8 -8
  42. package/dist/shared/schemas/discovery-question.schema.js +1 -1
  43. package/dist/shared/schemas/discovery-question.schema.js.map +1 -1
  44. package/dist/shared/schemas/negotiation-state.schema.d.ts +41 -4
  45. package/dist/shared/schemas/negotiation-state.schema.d.ts.map +1 -1
  46. package/dist/shared/schemas/negotiation-state.schema.js +14 -0
  47. package/dist/shared/schemas/negotiation-state.schema.js.map +1 -1
  48. package/package.json +1 -1
@@ -48,6 +48,17 @@ export declare const InitiatorTurnSchema: z.ZodObject<{
48
48
  };
49
49
  }>;
50
50
  message: z.ZodOptional<z.ZodNullable<z.ZodString>>;
51
+ /** Present when action is `ask_user` (v2, P3.2). */
52
+ askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
53
+ disclosureSubject: z.ZodString;
54
+ draftQuestion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
55
+ }, "strip", z.ZodTypeAny, {
56
+ disclosureSubject: string;
57
+ draftQuestion?: string | null | undefined;
58
+ }, {
59
+ disclosureSubject: string;
60
+ draftQuestion?: string | null | undefined;
61
+ }>>>;
51
62
  }, "strip", z.ZodTypeAny, {
52
63
  action: "counter" | "question" | "outreach" | "withdraw";
53
64
  assessment: {
@@ -58,6 +69,10 @@ export declare const InitiatorTurnSchema: z.ZodObject<{
58
69
  };
59
70
  };
60
71
  message?: string | null | undefined;
72
+ askUser?: {
73
+ disclosureSubject: string;
74
+ draftQuestion?: string | null | undefined;
75
+ } | null | undefined;
61
76
  }, {
62
77
  action: "counter" | "question" | "outreach" | "withdraw";
63
78
  assessment: {
@@ -68,6 +83,10 @@ export declare const InitiatorTurnSchema: z.ZodObject<{
68
83
  };
69
84
  };
70
85
  message?: string | null | undefined;
86
+ askUser?: {
87
+ disclosureSubject: string;
88
+ draftQuestion?: string | null | undefined;
89
+ } | null | undefined;
71
90
  }>;
72
91
  /** Counterparty seat, non-final turn: the only seat that can accept. */
73
92
  export declare const CounterpartyTurnSchema: z.ZodObject<{
@@ -98,6 +117,17 @@ export declare const CounterpartyTurnSchema: z.ZodObject<{
98
117
  };
99
118
  }>;
100
119
  message: z.ZodOptional<z.ZodNullable<z.ZodString>>;
120
+ /** Present when action is `ask_user` (v2, P3.2). */
121
+ askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
122
+ disclosureSubject: z.ZodString;
123
+ draftQuestion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
124
+ }, "strip", z.ZodTypeAny, {
125
+ disclosureSubject: string;
126
+ draftQuestion?: string | null | undefined;
127
+ }, {
128
+ disclosureSubject: string;
129
+ draftQuestion?: string | null | undefined;
130
+ }>>>;
101
131
  }, "strip", z.ZodTypeAny, {
102
132
  action: "accept" | "counter" | "question" | "decline";
103
133
  assessment: {
@@ -108,6 +138,10 @@ export declare const CounterpartyTurnSchema: z.ZodObject<{
108
138
  };
109
139
  };
110
140
  message?: string | null | undefined;
141
+ askUser?: {
142
+ disclosureSubject: string;
143
+ draftQuestion?: string | null | undefined;
144
+ } | null | undefined;
111
145
  }, {
112
146
  action: "accept" | "counter" | "question" | "decline";
113
147
  assessment: {
@@ -118,6 +152,10 @@ export declare const CounterpartyTurnSchema: z.ZodObject<{
118
152
  };
119
153
  };
120
154
  message?: string | null | undefined;
155
+ askUser?: {
156
+ disclosureSubject: string;
157
+ draftQuestion?: string | null | undefined;
158
+ } | null | undefined;
121
159
  }>;
122
160
  /** Initiator seat, final allowed turn: commit to walking away or leave the door open. */
123
161
  export declare const FinalInitiatorTurnSchema: z.ZodObject<{
@@ -148,6 +186,17 @@ export declare const FinalInitiatorTurnSchema: z.ZodObject<{
148
186
  };
149
187
  }>;
150
188
  message: z.ZodOptional<z.ZodNullable<z.ZodString>>;
189
+ /** Present when action is `ask_user` (v2, P3.2). */
190
+ askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
191
+ disclosureSubject: z.ZodString;
192
+ draftQuestion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
193
+ }, "strip", z.ZodTypeAny, {
194
+ disclosureSubject: string;
195
+ draftQuestion?: string | null | undefined;
196
+ }, {
197
+ disclosureSubject: string;
198
+ draftQuestion?: string | null | undefined;
199
+ }>>>;
151
200
  }, "strip", z.ZodTypeAny, {
152
201
  action: "counter" | "withdraw";
153
202
  assessment: {
@@ -158,6 +207,10 @@ export declare const FinalInitiatorTurnSchema: z.ZodObject<{
158
207
  };
159
208
  };
160
209
  message?: string | null | undefined;
210
+ askUser?: {
211
+ disclosureSubject: string;
212
+ draftQuestion?: string | null | undefined;
213
+ } | null | undefined;
161
214
  }, {
162
215
  action: "counter" | "withdraw";
163
216
  assessment: {
@@ -168,6 +221,10 @@ export declare const FinalInitiatorTurnSchema: z.ZodObject<{
168
221
  };
169
222
  };
170
223
  message?: string | null | undefined;
224
+ askUser?: {
225
+ disclosureSubject: string;
226
+ draftQuestion?: string | null | undefined;
227
+ } | null | undefined;
171
228
  }>;
172
229
  /** Counterparty seat, final allowed turn: must decide. */
173
230
  export declare const FinalCounterpartyTurnSchema: z.ZodObject<{
@@ -198,6 +255,17 @@ export declare const FinalCounterpartyTurnSchema: z.ZodObject<{
198
255
  };
199
256
  }>;
200
257
  message: z.ZodOptional<z.ZodNullable<z.ZodString>>;
258
+ /** Present when action is `ask_user` (v2, P3.2). */
259
+ askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
260
+ disclosureSubject: z.ZodString;
261
+ draftQuestion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
262
+ }, "strip", z.ZodTypeAny, {
263
+ disclosureSubject: string;
264
+ draftQuestion?: string | null | undefined;
265
+ }, {
266
+ disclosureSubject: string;
267
+ draftQuestion?: string | null | undefined;
268
+ }>>>;
201
269
  }, "strip", z.ZodTypeAny, {
202
270
  action: "accept" | "decline";
203
271
  assessment: {
@@ -208,6 +276,10 @@ export declare const FinalCounterpartyTurnSchema: z.ZodObject<{
208
276
  };
209
277
  };
210
278
  message?: string | null | undefined;
279
+ askUser?: {
280
+ disclosureSubject: string;
281
+ draftQuestion?: string | null | undefined;
282
+ } | null | undefined;
211
283
  }, {
212
284
  action: "accept" | "decline";
213
285
  assessment: {
@@ -218,6 +290,148 @@ export declare const FinalCounterpartyTurnSchema: z.ZodObject<{
218
290
  };
219
291
  };
220
292
  message?: string | null | undefined;
293
+ askUser?: {
294
+ disclosureSubject: string;
295
+ draftQuestion?: string | null | undefined;
296
+ } | null | undefined;
297
+ }>;
298
+ /** Initiator seat, non-final turn, with the client-consult pause available. */
299
+ export declare const InitiatorAskUserTurnSchema: z.ZodObject<{
300
+ action: z.ZodEnum<["outreach", "counter", "question", "withdraw", "ask_user"]>;
301
+ assessment: z.ZodObject<{
302
+ reasoning: z.ZodString;
303
+ suggestedRoles: z.ZodObject<{
304
+ ownUser: z.ZodEnum<["agent", "patient", "peer"]>;
305
+ otherUser: z.ZodEnum<["agent", "patient", "peer"]>;
306
+ }, "strip", z.ZodTypeAny, {
307
+ ownUser: "agent" | "patient" | "peer";
308
+ otherUser: "agent" | "patient" | "peer";
309
+ }, {
310
+ ownUser: "agent" | "patient" | "peer";
311
+ otherUser: "agent" | "patient" | "peer";
312
+ }>;
313
+ }, "strip", z.ZodTypeAny, {
314
+ reasoning: string;
315
+ suggestedRoles: {
316
+ ownUser: "agent" | "patient" | "peer";
317
+ otherUser: "agent" | "patient" | "peer";
318
+ };
319
+ }, {
320
+ reasoning: string;
321
+ suggestedRoles: {
322
+ ownUser: "agent" | "patient" | "peer";
323
+ otherUser: "agent" | "patient" | "peer";
324
+ };
325
+ }>;
326
+ message: z.ZodOptional<z.ZodNullable<z.ZodString>>;
327
+ /** Present when action is `ask_user` (v2, P3.2). */
328
+ askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
329
+ disclosureSubject: z.ZodString;
330
+ draftQuestion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
331
+ }, "strip", z.ZodTypeAny, {
332
+ disclosureSubject: string;
333
+ draftQuestion?: string | null | undefined;
334
+ }, {
335
+ disclosureSubject: string;
336
+ draftQuestion?: string | null | undefined;
337
+ }>>>;
338
+ }, "strip", z.ZodTypeAny, {
339
+ action: "counter" | "question" | "outreach" | "withdraw" | "ask_user";
340
+ assessment: {
341
+ reasoning: string;
342
+ suggestedRoles: {
343
+ ownUser: "agent" | "patient" | "peer";
344
+ otherUser: "agent" | "patient" | "peer";
345
+ };
346
+ };
347
+ message?: string | null | undefined;
348
+ askUser?: {
349
+ disclosureSubject: string;
350
+ draftQuestion?: string | null | undefined;
351
+ } | null | undefined;
352
+ }, {
353
+ action: "counter" | "question" | "outreach" | "withdraw" | "ask_user";
354
+ assessment: {
355
+ reasoning: string;
356
+ suggestedRoles: {
357
+ ownUser: "agent" | "patient" | "peer";
358
+ otherUser: "agent" | "patient" | "peer";
359
+ };
360
+ };
361
+ message?: string | null | undefined;
362
+ askUser?: {
363
+ disclosureSubject: string;
364
+ draftQuestion?: string | null | undefined;
365
+ } | null | undefined;
366
+ }>;
367
+ /** Counterparty seat, non-final turn, with the client-consult pause available. */
368
+ export declare const CounterpartyAskUserTurnSchema: z.ZodObject<{
369
+ action: z.ZodEnum<["accept", "decline", "counter", "question", "ask_user"]>;
370
+ assessment: z.ZodObject<{
371
+ reasoning: z.ZodString;
372
+ suggestedRoles: z.ZodObject<{
373
+ ownUser: z.ZodEnum<["agent", "patient", "peer"]>;
374
+ otherUser: z.ZodEnum<["agent", "patient", "peer"]>;
375
+ }, "strip", z.ZodTypeAny, {
376
+ ownUser: "agent" | "patient" | "peer";
377
+ otherUser: "agent" | "patient" | "peer";
378
+ }, {
379
+ ownUser: "agent" | "patient" | "peer";
380
+ otherUser: "agent" | "patient" | "peer";
381
+ }>;
382
+ }, "strip", z.ZodTypeAny, {
383
+ reasoning: string;
384
+ suggestedRoles: {
385
+ ownUser: "agent" | "patient" | "peer";
386
+ otherUser: "agent" | "patient" | "peer";
387
+ };
388
+ }, {
389
+ reasoning: string;
390
+ suggestedRoles: {
391
+ ownUser: "agent" | "patient" | "peer";
392
+ otherUser: "agent" | "patient" | "peer";
393
+ };
394
+ }>;
395
+ message: z.ZodOptional<z.ZodNullable<z.ZodString>>;
396
+ /** Present when action is `ask_user` (v2, P3.2). */
397
+ askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
398
+ disclosureSubject: z.ZodString;
399
+ draftQuestion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
400
+ }, "strip", z.ZodTypeAny, {
401
+ disclosureSubject: string;
402
+ draftQuestion?: string | null | undefined;
403
+ }, {
404
+ disclosureSubject: string;
405
+ draftQuestion?: string | null | undefined;
406
+ }>>>;
407
+ }, "strip", z.ZodTypeAny, {
408
+ action: "accept" | "counter" | "question" | "decline" | "ask_user";
409
+ assessment: {
410
+ reasoning: string;
411
+ suggestedRoles: {
412
+ ownUser: "agent" | "patient" | "peer";
413
+ otherUser: "agent" | "patient" | "peer";
414
+ };
415
+ };
416
+ message?: string | null | undefined;
417
+ askUser?: {
418
+ disclosureSubject: string;
419
+ draftQuestion?: string | null | undefined;
420
+ } | null | undefined;
421
+ }, {
422
+ action: "accept" | "counter" | "question" | "decline" | "ask_user";
423
+ assessment: {
424
+ reasoning: string;
425
+ suggestedRoles: {
426
+ ownUser: "agent" | "patient" | "peer";
427
+ otherUser: "agent" | "patient" | "peer";
428
+ };
429
+ };
430
+ message?: string | null | undefined;
431
+ askUser?: {
432
+ disclosureSubject: string;
433
+ draftQuestion?: string | null | undefined;
434
+ } | null | undefined;
221
435
  }>;
222
436
  /**
223
437
  * The set of actions a given seat may submit under a given protocol version.
@@ -225,7 +439,7 @@ export declare const FinalCounterpartyTurnSchema: z.ZodObject<{
225
439
  * v1 ignores the seat entirely (legacy symmetric vocabulary) so pre-v2
226
440
  * negotiations behave exactly as before.
227
441
  */
228
- export declare function allowedActionsFor(version: NegotiationProtocolVersion, seat: NegotiationSeat, isFinalTurn?: boolean): readonly NegotiationAction[];
442
+ export declare function allowedActionsFor(version: NegotiationProtocolVersion, seat: NegotiationSeat, isFinalTurn?: boolean, opts?: AskUserOpts): readonly NegotiationAction[];
229
443
  /**
230
444
  * Zod turn schema for a system-agent turn, selected by version + seat +
231
445
  * final-turn flag. v1 returns the legacy schemas (seat-agnostic); v2 returns
@@ -238,7 +452,19 @@ export declare function allowedActionsFor(version: NegotiationProtocolVersion, s
238
452
  export declare function turnSchemaFor(version: NegotiationProtocolVersion, seat: NegotiationSeat, isFinalTurn: boolean, v1Schemas: {
239
453
  system: z.ZodTypeAny;
240
454
  final: z.ZodTypeAny;
241
- }): z.ZodTypeAny;
455
+ }, opts?: AskUserOpts): z.ZodTypeAny;
456
+ /**
457
+ * Opt-in extension of the seat vocabulary with the `ask_user` client-consult
458
+ * pause (P3.2). Never granted on final-cap turns (the final turn must decide)
459
+ * and never under v1. Callers pass `{ askUser: true }` only when the full
460
+ * pause loop is available on their surface: the ask-user feature flag is on,
461
+ * a questioner enqueue and an answer-window timer are wired, the negotiation
462
+ * has an opportunity to resume against, and the acting side has not already
463
+ * consumed its one client question for this negotiation (rationing).
464
+ */
465
+ export interface AskUserOpts {
466
+ askUser?: boolean;
467
+ }
242
468
  /** Terminal actions end the negotiation immediately. */
243
469
  export declare function isTerminalAction(action: string | undefined | null): boolean;
244
470
  /** Reject-like actions map the opportunity to `rejected` (v1 reject, v2 withdraw/decline). */
@@ -269,6 +495,28 @@ export declare function readProtocolVersion(metadata: {
269
495
  * v2 is opt-in per environment, and rolling back is the same single switch.
270
496
  */
271
497
  export declare function configuredProtocolVersion(): NegotiationProtocolVersion;
498
+ /**
499
+ * Whether the `ask_user` client-consult pause (P3.2) is enabled, from the
500
+ * `NEGOTIATION_ASK_USER_ENABLED` env switch. Defaults to off — the deployment
501
+ * is byte-for-byte unchanged until the flag is flipped, and rolling back is
502
+ * the same single switch.
503
+ */
504
+ export declare function configuredAskUserEnabled(): boolean;
505
+ /** Default answer window for a paused `ask_user` negotiation: 24 hours. */
506
+ export declare const DEFAULT_ASK_USER_WINDOW_MS: number;
507
+ /**
508
+ * Answer window for a paused `ask_user` negotiation, in ms. Overridable via
509
+ * `NEGOTIATION_ASK_USER_WINDOW_MS` (dev/e2e use shorter windows to exercise
510
+ * the expiry path); invalid or non-positive values fall back to 24 h.
511
+ */
512
+ export declare function askUserAnswerWindowMs(): number;
513
+ /**
514
+ * Slack added on top of the answer window when deciding whether a paused
515
+ * (`input_required`) task still holds the conversation lock. Covers expiry
516
+ * worker delay: the lock must outlive the timer slightly, so ambient
517
+ * rediscovery cannot slip in between window expiry and the worker's resume.
518
+ */
519
+ export declare const ASK_USER_LOCK_SLACK_MS: number;
272
520
  /**
273
521
  * Resolve the seat of `userId` on a negotiation task.
274
522
  *
@@ -1 +1 @@
1
- {"version":3,"file":"negotiation.protocol.d.ts","sourceRoot":"/","sources":["negotiation/negotiation.protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAsBpI,kGAAkG;AAClG,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA8D,CAAC;AAE/F,wEAAwE;AACxE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA2D,CAAC;AAE/F,yFAAyF;AACzF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAsC,CAAC;AAE5E,0DAA0D;AAC1D,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAoC,CAAC;AAW7E;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,0BAA0B,EACnC,IAAI,EAAE,eAAe,EACrB,WAAW,UAAQ,GAClB,SAAS,iBAAiB,EAAE,CAI9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,0BAA0B,EACnC,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE;IAAE,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,UAAU,CAAA;CAAE,GACvD,CAAC,CAAC,UAAU,CAId;AAID,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAE3E;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAE7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,0BAA0B,EACnC,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,OAAO,GACnB,iBAAiB,CAInB;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAC7B,OAAO,EAAE,0BAA0B,EACnC,IAAI,EAAE,eAAe,GACpB,iBAAiB,CAGnB;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACzD,0BAA0B,GAAG,IAAI,CAGnC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,0BAA0B,CAEtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACjF,eAAe,CAQjB;AAED,wEAAwE;AACxE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,0BAA0B,GAClC,MAAM,CAGR"}
1
+ {"version":3,"file":"negotiation.protocol.d.ts","sourceRoot":"/","sources":["negotiation/negotiation.protocol.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@
18
18
  * `reject`/`withdraw`/`decline` → `rejected`, turn-cap → `stalled`.
19
19
  */
20
20
  import { z } from "zod";
21
+ import { AskUserPayloadSchema } from "../shared/schemas/negotiation-state.schema.js";
21
22
  // ─── Shared assessment fragment ──────────────────────────────────────────────
22
23
  const AssessmentSchema = z.object({
23
24
  reasoning: z.string(),
@@ -31,6 +32,8 @@ function turnSchema(actions) {
31
32
  action: z.enum(actions),
32
33
  assessment: AssessmentSchema,
33
34
  message: z.string().nullable().optional(),
35
+ /** Present when action is `ask_user` (v2, P3.2). */
36
+ askUser: AskUserPayloadSchema.nullable().optional(),
34
37
  });
35
38
  }
36
39
  // ─── v2 seat-scoped turn schemas ─────────────────────────────────────────────
@@ -42,6 +45,14 @@ export const CounterpartyTurnSchema = turnSchema(["accept", "decline", "counter"
42
45
  export const FinalInitiatorTurnSchema = turnSchema(["withdraw", "counter"]);
43
46
  /** Counterparty seat, final allowed turn: must decide. */
44
47
  export const FinalCounterpartyTurnSchema = turnSchema(["accept", "decline"]);
48
+ // ─── v2 ask_user variants (P3.2, flag-gated) ────────────────────────────────
49
+ // Non-final turns only: the final-cap turn must decide, never pause. Selected
50
+ // via the `opts.askUser` parameter on allowedActionsFor/turnSchemaFor — the
51
+ // base schemas above stay byte-identical for every existing caller.
52
+ /** Initiator seat, non-final turn, with the client-consult pause available. */
53
+ export const InitiatorAskUserTurnSchema = turnSchema(["outreach", "counter", "question", "withdraw", "ask_user"]);
54
+ /** Counterparty seat, non-final turn, with the client-consult pause available. */
55
+ export const CounterpartyAskUserTurnSchema = turnSchema(["accept", "decline", "counter", "question", "ask_user"]);
45
56
  // ─── Action vocabulary per version + seat ────────────────────────────────────
46
57
  const V1_ACTIONS = ["propose", "accept", "reject", "counter", "question"];
47
58
  const V1_FINAL_ACTIONS = ["accept", "reject"];
@@ -49,18 +60,22 @@ const V2_INITIATOR_ACTIONS = ["outreach", "counter", "question", "withdraw"];
49
60
  const V2_COUNTERPARTY_ACTIONS = ["accept", "decline", "counter", "question"];
50
61
  const V2_FINAL_INITIATOR_ACTIONS = ["withdraw", "counter"];
51
62
  const V2_FINAL_COUNTERPARTY_ACTIONS = ["accept", "decline"];
63
+ const V2_INITIATOR_ASK_USER_ACTIONS = [...V2_INITIATOR_ACTIONS, "ask_user"];
64
+ const V2_COUNTERPARTY_ASK_USER_ACTIONS = [...V2_COUNTERPARTY_ACTIONS, "ask_user"];
52
65
  /**
53
66
  * The set of actions a given seat may submit under a given protocol version.
54
67
  *
55
68
  * v1 ignores the seat entirely (legacy symmetric vocabulary) so pre-v2
56
69
  * negotiations behave exactly as before.
57
70
  */
58
- export function allowedActionsFor(version, seat, isFinalTurn = false) {
71
+ export function allowedActionsFor(version, seat, isFinalTurn = false, opts) {
59
72
  if (version !== "v2")
60
73
  return isFinalTurn ? V1_FINAL_ACTIONS : V1_ACTIONS;
61
- if (seat === "initiator")
62
- return isFinalTurn ? V2_FINAL_INITIATOR_ACTIONS : V2_INITIATOR_ACTIONS;
63
- return isFinalTurn ? V2_FINAL_COUNTERPARTY_ACTIONS : V2_COUNTERPARTY_ACTIONS;
74
+ const askUser = opts?.askUser === true && !isFinalTurn;
75
+ if (seat === "initiator") {
76
+ return isFinalTurn ? V2_FINAL_INITIATOR_ACTIONS : (askUser ? V2_INITIATOR_ASK_USER_ACTIONS : V2_INITIATOR_ACTIONS);
77
+ }
78
+ return isFinalTurn ? V2_FINAL_COUNTERPARTY_ACTIONS : (askUser ? V2_COUNTERPARTY_ASK_USER_ACTIONS : V2_COUNTERPARTY_ACTIONS);
64
79
  }
65
80
  /**
66
81
  * Zod turn schema for a system-agent turn, selected by version + seat +
@@ -71,12 +86,14 @@ export function allowedActionsFor(version, seat, isFinalTurn = false) {
71
86
  * The v1 legacy schemas are passed in by the caller (they live in
72
87
  * `negotiation.state.ts`) to keep this module free of a state-module import.
73
88
  */
74
- export function turnSchemaFor(version, seat, isFinalTurn, v1Schemas) {
89
+ export function turnSchemaFor(version, seat, isFinalTurn, v1Schemas, opts) {
75
90
  if (version !== "v2")
76
91
  return isFinalTurn ? v1Schemas.final : v1Schemas.system;
77
- if (seat === "initiator")
78
- return isFinalTurn ? FinalInitiatorTurnSchema : InitiatorTurnSchema;
79
- return isFinalTurn ? FinalCounterpartyTurnSchema : CounterpartyTurnSchema;
92
+ const askUser = opts?.askUser === true && !isFinalTurn;
93
+ if (seat === "initiator") {
94
+ return isFinalTurn ? FinalInitiatorTurnSchema : (askUser ? InitiatorAskUserTurnSchema : InitiatorTurnSchema);
95
+ }
96
+ return isFinalTurn ? FinalCounterpartyTurnSchema : (askUser ? CounterpartyAskUserTurnSchema : CounterpartyTurnSchema);
80
97
  }
81
98
  // ─── Action semantics (version-independent) ──────────────────────────────────
82
99
  /** Terminal actions end the negotiation immediately. */
@@ -127,6 +144,38 @@ export function readProtocolVersion(metadata) {
127
144
  export function configuredProtocolVersion() {
128
145
  return process.env.NEGOTIATION_PROTOCOL_VERSION === "v2" ? "v2" : "v1";
129
146
  }
147
+ /**
148
+ * Whether the `ask_user` client-consult pause (P3.2) is enabled, from the
149
+ * `NEGOTIATION_ASK_USER_ENABLED` env switch. Defaults to off — the deployment
150
+ * is byte-for-byte unchanged until the flag is flipped, and rolling back is
151
+ * the same single switch.
152
+ */
153
+ export function configuredAskUserEnabled() {
154
+ return process.env.NEGOTIATION_ASK_USER_ENABLED === "true";
155
+ }
156
+ /** Default answer window for a paused `ask_user` negotiation: 24 hours. */
157
+ export const DEFAULT_ASK_USER_WINDOW_MS = 24 * 60 * 60 * 1000;
158
+ /**
159
+ * Answer window for a paused `ask_user` negotiation, in ms. Overridable via
160
+ * `NEGOTIATION_ASK_USER_WINDOW_MS` (dev/e2e use shorter windows to exercise
161
+ * the expiry path); invalid or non-positive values fall back to 24 h.
162
+ */
163
+ export function askUserAnswerWindowMs() {
164
+ const raw = process.env.NEGOTIATION_ASK_USER_WINDOW_MS;
165
+ if (raw) {
166
+ const parsed = Number(raw);
167
+ if (Number.isFinite(parsed) && parsed > 0)
168
+ return parsed;
169
+ }
170
+ return DEFAULT_ASK_USER_WINDOW_MS;
171
+ }
172
+ /**
173
+ * Slack added on top of the answer window when deciding whether a paused
174
+ * (`input_required`) task still holds the conversation lock. Covers expiry
175
+ * worker delay: the lock must outlive the timer slightly, so ambient
176
+ * rediscovery cannot slip in between window expiry and the worker's resume.
177
+ */
178
+ export const ASK_USER_LOCK_SLACK_MS = 60 * 60 * 1000;
130
179
  /**
131
180
  * Resolve the seat of `userId` on a negotiation task.
132
181
  *
@@ -1 +1 @@
1
- {"version":3,"file":"negotiation.protocol.js","sourceRoot":"/","sources":["negotiation/negotiation.protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Seat-scoped negotiation protocol rules (v2 client-advocate protocol).\n *\n * v2 fixes exactly one initiating seat per match (`metadata.initiatorUserId`,\n * stamped at discovery time — IND-396) and makes consent asymmetric:\n * **accept can only come from the counterparty seat**, schema-enforced.\n *\n * Vocabulary per seat (v2):\n * - initiator: `outreach | counter | question | withdraw` (no accept)\n * - counterparty: `accept | decline | counter | question`\n * - final turn: initiator `withdraw | counter`; counterparty `accept | decline`\n *\n * v1 tasks keep the legacy vocabulary (`propose | accept | reject | counter |\n * question`) — the version is inherited per conversation, never re-stamped, so\n * in-flight v1 negotiations are grandfathered untouched.\n *\n * Outcome mapping is version-independent: `accept` → opportunity `pending`,\n * `reject`/`withdraw`/`decline` → `rejected`, turn-cap → `stalled`.\n */\nimport { z } from \"zod\";\n\nimport type { NegotiationAction, NegotiationSeat, NegotiationProtocolVersion } from \"../shared/schemas/negotiation-state.schema.js\";\n\n// ─── Shared assessment fragment ──────────────────────────────────────────────\n\nconst AssessmentSchema = z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n});\n\nfunction turnSchema<T extends [NegotiationAction, ...NegotiationAction[]]>(actions: T) {\n return z.object({\n action: z.enum(actions),\n assessment: AssessmentSchema,\n message: z.string().nullable().optional(),\n });\n}\n\n// ─── v2 seat-scoped turn schemas ─────────────────────────────────────────────\n\n/** Initiator seat, non-final turn: may reach out, push back, ask, or walk away — never accept. */\nexport const InitiatorTurnSchema = turnSchema([\"outreach\", \"counter\", \"question\", \"withdraw\"]);\n\n/** Counterparty seat, non-final turn: the only seat that can accept. */\nexport const CounterpartyTurnSchema = turnSchema([\"accept\", \"decline\", \"counter\", \"question\"]);\n\n/** Initiator seat, final allowed turn: commit to walking away or leave the door open. */\nexport const FinalInitiatorTurnSchema = turnSchema([\"withdraw\", \"counter\"]);\n\n/** Counterparty seat, final allowed turn: must decide. */\nexport const FinalCounterpartyTurnSchema = turnSchema([\"accept\", \"decline\"]);\n\n// ─── Action vocabulary per version + seat ────────────────────────────────────\n\nconst V1_ACTIONS: readonly NegotiationAction[] = [\"propose\", \"accept\", \"reject\", \"counter\", \"question\"];\nconst V1_FINAL_ACTIONS: readonly NegotiationAction[] = [\"accept\", \"reject\"];\nconst V2_INITIATOR_ACTIONS: readonly NegotiationAction[] = [\"outreach\", \"counter\", \"question\", \"withdraw\"];\nconst V2_COUNTERPARTY_ACTIONS: readonly NegotiationAction[] = [\"accept\", \"decline\", \"counter\", \"question\"];\nconst V2_FINAL_INITIATOR_ACTIONS: readonly NegotiationAction[] = [\"withdraw\", \"counter\"];\nconst V2_FINAL_COUNTERPARTY_ACTIONS: readonly NegotiationAction[] = [\"accept\", \"decline\"];\n\n/**\n * The set of actions a given seat may submit under a given protocol version.\n *\n * v1 ignores the seat entirely (legacy symmetric vocabulary) so pre-v2\n * negotiations behave exactly as before.\n */\nexport function allowedActionsFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n isFinalTurn = false,\n): readonly NegotiationAction[] {\n if (version !== \"v2\") return isFinalTurn ? V1_FINAL_ACTIONS : V1_ACTIONS;\n if (seat === \"initiator\") return isFinalTurn ? V2_FINAL_INITIATOR_ACTIONS : V2_INITIATOR_ACTIONS;\n return isFinalTurn ? V2_FINAL_COUNTERPARTY_ACTIONS : V2_COUNTERPARTY_ACTIONS;\n}\n\n/**\n * Zod turn schema for a system-agent turn, selected by version + seat +\n * final-turn flag. v1 returns the legacy schemas (seat-agnostic); v2 returns\n * the seat-scoped schemas above, making an initiator `accept` structurally\n * impossible rather than prompt-discouraged.\n *\n * The v1 legacy schemas are passed in by the caller (they live in\n * `negotiation.state.ts`) to keep this module free of a state-module import.\n */\nexport function turnSchemaFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n isFinalTurn: boolean,\n v1Schemas: { system: z.ZodTypeAny; final: z.ZodTypeAny },\n): z.ZodTypeAny {\n if (version !== \"v2\") return isFinalTurn ? v1Schemas.final : v1Schemas.system;\n if (seat === \"initiator\") return isFinalTurn ? FinalInitiatorTurnSchema : InitiatorTurnSchema;\n return isFinalTurn ? FinalCounterpartyTurnSchema : CounterpartyTurnSchema;\n}\n\n// ─── Action semantics (version-independent) ──────────────────────────────────\n\n/** Terminal actions end the negotiation immediately. */\nexport function isTerminalAction(action: string | undefined | null): boolean {\n return action === \"accept\" || action === \"reject\" || action === \"withdraw\" || action === \"decline\";\n}\n\n/** Reject-like actions map the opportunity to `rejected` (v1 reject, v2 withdraw/decline). */\nexport function isRejectLikeAction(action: string | undefined | null): boolean {\n return action === \"reject\" || action === \"withdraw\" || action === \"decline\";\n}\n\n/**\n * Conservative action when an agent produced schema-invalid output (after the\n * retry) or an internal error needs a seat-valid terminal placeholder.\n *\n * Non-final turns fall back to `counter` (keeps the dialogue open — the AC's\n * \"conservative counter\"). Final turns must decide: v1 → `reject`, v2\n * counterparty → `decline`, v2 initiator → `counter` is still legal on the\n * final turn so it stays `counter` (finalizes as turn-cap/stalled).\n */\nexport function fallbackActionFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n isFinalTurn: boolean,\n): NegotiationAction {\n if (!isFinalTurn) return \"counter\";\n if (version !== \"v2\") return \"reject\";\n return seat === \"counterparty\" ? \"decline\" : \"counter\";\n}\n\n/** Seat-appropriate reject-like action for error paths. */\nexport function rejectActionFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n): NegotiationAction {\n if (version !== \"v2\") return \"reject\";\n return seat === \"initiator\" ? \"withdraw\" : \"decline\";\n}\n\n// ─── Metadata readers ────────────────────────────────────────────────────────\n\n/**\n * Read the protocol version off task metadata. Returns null when the task\n * predates version stamping (treat as v1 at the call site when the task is a\n * genuine prior; fresh tasks stamp from {@link configuredProtocolVersion}).\n */\nexport function readProtocolVersion(\n metadata: { protocolVersion?: unknown } | null | undefined,\n): NegotiationProtocolVersion | null {\n const v = metadata?.protocolVersion;\n return v === \"v2\" ? \"v2\" : v === \"v1\" ? \"v1\" : null;\n}\n\n/**\n * Protocol version for genuinely fresh negotiations, from the\n * `NEGOTIATION_PROTOCOL_VERSION` env switch. Defaults to `v1` when unset —\n * v2 is opt-in per environment, and rolling back is the same single switch.\n */\nexport function configuredProtocolVersion(): NegotiationProtocolVersion {\n return process.env.NEGOTIATION_PROTOCOL_VERSION === \"v2\" ? \"v2\" : \"v1\";\n}\n\n/**\n * Resolve the seat of `userId` on a negotiation task.\n *\n * Keys on `metadata.initiatorUserId` (the rigid v2 stamp), **never on turn\n * parity** — continuations can start with either side speaking first, so\n * parity misattributes seats across sessions. Pre-stamp tasks fall back to\n * `sourceUserId` (the discovery-session opener), which is what the stamp\n * defaults to anyway.\n */\nexport function resolveSeat(\n userId: string,\n metadata: { initiatorUserId?: unknown; sourceUserId?: unknown } | null | undefined,\n): NegotiationSeat {\n const initiator =\n typeof metadata?.initiatorUserId === \"string\" && metadata.initiatorUserId.length > 0\n ? metadata.initiatorUserId\n : typeof metadata?.sourceUserId === \"string\"\n ? metadata.sourceUserId\n : undefined;\n return initiator === userId ? \"initiator\" : \"counterparty\";\n}\n\n/** Human-readable seat-violation message shared by respond surfaces. */\nexport function seatViolationMessage(\n action: string,\n seat: NegotiationSeat,\n version: NegotiationProtocolVersion,\n): string {\n const allowed = allowedActionsFor(version, seat).join(\", \");\n return `Action \"${action}\" is not allowed for your seat (${seat}) under negotiation protocol ${version}. Allowed actions: ${allowed}.`;\n}\n"]}
1
+ {"version":3,"file":"negotiation.protocol.js","sourceRoot":"/","sources":["negotiation/negotiation.protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Seat-scoped negotiation protocol rules (v2 client-advocate protocol).\n *\n * v2 fixes exactly one initiating seat per match (`metadata.initiatorUserId`,\n * stamped at discovery time — IND-396) and makes consent asymmetric:\n * **accept can only come from the counterparty seat**, schema-enforced.\n *\n * Vocabulary per seat (v2):\n * - initiator: `outreach | counter | question | withdraw` (no accept)\n * - counterparty: `accept | decline | counter | question`\n * - final turn: initiator `withdraw | counter`; counterparty `accept | decline`\n *\n * v1 tasks keep the legacy vocabulary (`propose | accept | reject | counter |\n * question`) — the version is inherited per conversation, never re-stamped, so\n * in-flight v1 negotiations are grandfathered untouched.\n *\n * Outcome mapping is version-independent: `accept` → opportunity `pending`,\n * `reject`/`withdraw`/`decline` → `rejected`, turn-cap → `stalled`.\n */\nimport { z } from \"zod\";\n\nimport { AskUserPayloadSchema } from \"../shared/schemas/negotiation-state.schema.js\";\nimport type { NegotiationAction, NegotiationSeat, NegotiationProtocolVersion } from \"../shared/schemas/negotiation-state.schema.js\";\n\n// ─── Shared assessment fragment ──────────────────────────────────────────────\n\nconst AssessmentSchema = z.object({\n reasoning: z.string(),\n suggestedRoles: z.object({\n ownUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n otherUser: z.enum([\"agent\", \"patient\", \"peer\"]),\n }),\n});\n\nfunction turnSchema<T extends [NegotiationAction, ...NegotiationAction[]]>(actions: T) {\n return z.object({\n action: z.enum(actions),\n assessment: AssessmentSchema,\n message: z.string().nullable().optional(),\n /** Present when action is `ask_user` (v2, P3.2). */\n askUser: AskUserPayloadSchema.nullable().optional(),\n });\n}\n\n// ─── v2 seat-scoped turn schemas ─────────────────────────────────────────────\n\n/** Initiator seat, non-final turn: may reach out, push back, ask, or walk away — never accept. */\nexport const InitiatorTurnSchema = turnSchema([\"outreach\", \"counter\", \"question\", \"withdraw\"]);\n\n/** Counterparty seat, non-final turn: the only seat that can accept. */\nexport const CounterpartyTurnSchema = turnSchema([\"accept\", \"decline\", \"counter\", \"question\"]);\n\n/** Initiator seat, final allowed turn: commit to walking away or leave the door open. */\nexport const FinalInitiatorTurnSchema = turnSchema([\"withdraw\", \"counter\"]);\n\n/** Counterparty seat, final allowed turn: must decide. */\nexport const FinalCounterpartyTurnSchema = turnSchema([\"accept\", \"decline\"]);\n\n// ─── v2 ask_user variants (P3.2, flag-gated) ────────────────────────────────\n// Non-final turns only: the final-cap turn must decide, never pause. Selected\n// via the `opts.askUser` parameter on allowedActionsFor/turnSchemaFor — the\n// base schemas above stay byte-identical for every existing caller.\n\n/** Initiator seat, non-final turn, with the client-consult pause available. */\nexport const InitiatorAskUserTurnSchema = turnSchema([\"outreach\", \"counter\", \"question\", \"withdraw\", \"ask_user\"]);\n\n/** Counterparty seat, non-final turn, with the client-consult pause available. */\nexport const CounterpartyAskUserTurnSchema = turnSchema([\"accept\", \"decline\", \"counter\", \"question\", \"ask_user\"]);\n\n// ─── Action vocabulary per version + seat ────────────────────────────────────\n\nconst V1_ACTIONS: readonly NegotiationAction[] = [\"propose\", \"accept\", \"reject\", \"counter\", \"question\"];\nconst V1_FINAL_ACTIONS: readonly NegotiationAction[] = [\"accept\", \"reject\"];\nconst V2_INITIATOR_ACTIONS: readonly NegotiationAction[] = [\"outreach\", \"counter\", \"question\", \"withdraw\"];\nconst V2_COUNTERPARTY_ACTIONS: readonly NegotiationAction[] = [\"accept\", \"decline\", \"counter\", \"question\"];\nconst V2_FINAL_INITIATOR_ACTIONS: readonly NegotiationAction[] = [\"withdraw\", \"counter\"];\nconst V2_FINAL_COUNTERPARTY_ACTIONS: readonly NegotiationAction[] = [\"accept\", \"decline\"];\nconst V2_INITIATOR_ASK_USER_ACTIONS: readonly NegotiationAction[] = [...V2_INITIATOR_ACTIONS, \"ask_user\"];\nconst V2_COUNTERPARTY_ASK_USER_ACTIONS: readonly NegotiationAction[] = [...V2_COUNTERPARTY_ACTIONS, \"ask_user\"];\n\n/**\n * The set of actions a given seat may submit under a given protocol version.\n *\n * v1 ignores the seat entirely (legacy symmetric vocabulary) so pre-v2\n * negotiations behave exactly as before.\n */\nexport function allowedActionsFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n isFinalTurn = false,\n opts?: AskUserOpts,\n): readonly NegotiationAction[] {\n if (version !== \"v2\") return isFinalTurn ? V1_FINAL_ACTIONS : V1_ACTIONS;\n const askUser = opts?.askUser === true && !isFinalTurn;\n if (seat === \"initiator\") {\n return isFinalTurn ? V2_FINAL_INITIATOR_ACTIONS : (askUser ? V2_INITIATOR_ASK_USER_ACTIONS : V2_INITIATOR_ACTIONS);\n }\n return isFinalTurn ? V2_FINAL_COUNTERPARTY_ACTIONS : (askUser ? V2_COUNTERPARTY_ASK_USER_ACTIONS : V2_COUNTERPARTY_ACTIONS);\n}\n\n/**\n * Zod turn schema for a system-agent turn, selected by version + seat +\n * final-turn flag. v1 returns the legacy schemas (seat-agnostic); v2 returns\n * the seat-scoped schemas above, making an initiator `accept` structurally\n * impossible rather than prompt-discouraged.\n *\n * The v1 legacy schemas are passed in by the caller (they live in\n * `negotiation.state.ts`) to keep this module free of a state-module import.\n */\nexport function turnSchemaFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n isFinalTurn: boolean,\n v1Schemas: { system: z.ZodTypeAny; final: z.ZodTypeAny },\n opts?: AskUserOpts,\n): z.ZodTypeAny {\n if (version !== \"v2\") return isFinalTurn ? v1Schemas.final : v1Schemas.system;\n const askUser = opts?.askUser === true && !isFinalTurn;\n if (seat === \"initiator\") {\n return isFinalTurn ? FinalInitiatorTurnSchema : (askUser ? InitiatorAskUserTurnSchema : InitiatorTurnSchema);\n }\n return isFinalTurn ? FinalCounterpartyTurnSchema : (askUser ? CounterpartyAskUserTurnSchema : CounterpartyTurnSchema);\n}\n\n/**\n * Opt-in extension of the seat vocabulary with the `ask_user` client-consult\n * pause (P3.2). Never granted on final-cap turns (the final turn must decide)\n * and never under v1. Callers pass `{ askUser: true }` only when the full\n * pause loop is available on their surface: the ask-user feature flag is on,\n * a questioner enqueue and an answer-window timer are wired, the negotiation\n * has an opportunity to resume against, and the acting side has not already\n * consumed its one client question for this negotiation (rationing).\n */\nexport interface AskUserOpts {\n askUser?: boolean;\n}\n\n// ─── Action semantics (version-independent) ──────────────────────────────────\n\n/** Terminal actions end the negotiation immediately. */\nexport function isTerminalAction(action: string | undefined | null): boolean {\n return action === \"accept\" || action === \"reject\" || action === \"withdraw\" || action === \"decline\";\n}\n\n/** Reject-like actions map the opportunity to `rejected` (v1 reject, v2 withdraw/decline). */\nexport function isRejectLikeAction(action: string | undefined | null): boolean {\n return action === \"reject\" || action === \"withdraw\" || action === \"decline\";\n}\n\n/**\n * Conservative action when an agent produced schema-invalid output (after the\n * retry) or an internal error needs a seat-valid terminal placeholder.\n *\n * Non-final turns fall back to `counter` (keeps the dialogue open — the AC's\n * \"conservative counter\"). Final turns must decide: v1 → `reject`, v2\n * counterparty → `decline`, v2 initiator → `counter` is still legal on the\n * final turn so it stays `counter` (finalizes as turn-cap/stalled).\n */\nexport function fallbackActionFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n isFinalTurn: boolean,\n): NegotiationAction {\n if (!isFinalTurn) return \"counter\";\n if (version !== \"v2\") return \"reject\";\n return seat === \"counterparty\" ? \"decline\" : \"counter\";\n}\n\n/** Seat-appropriate reject-like action for error paths. */\nexport function rejectActionFor(\n version: NegotiationProtocolVersion,\n seat: NegotiationSeat,\n): NegotiationAction {\n if (version !== \"v2\") return \"reject\";\n return seat === \"initiator\" ? \"withdraw\" : \"decline\";\n}\n\n// ─── Metadata readers ────────────────────────────────────────────────────────\n\n/**\n * Read the protocol version off task metadata. Returns null when the task\n * predates version stamping (treat as v1 at the call site when the task is a\n * genuine prior; fresh tasks stamp from {@link configuredProtocolVersion}).\n */\nexport function readProtocolVersion(\n metadata: { protocolVersion?: unknown } | null | undefined,\n): NegotiationProtocolVersion | null {\n const v = metadata?.protocolVersion;\n return v === \"v2\" ? \"v2\" : v === \"v1\" ? \"v1\" : null;\n}\n\n/**\n * Protocol version for genuinely fresh negotiations, from the\n * `NEGOTIATION_PROTOCOL_VERSION` env switch. Defaults to `v1` when unset —\n * v2 is opt-in per environment, and rolling back is the same single switch.\n */\nexport function configuredProtocolVersion(): NegotiationProtocolVersion {\n return process.env.NEGOTIATION_PROTOCOL_VERSION === \"v2\" ? \"v2\" : \"v1\";\n}\n\n/**\n * Whether the `ask_user` client-consult pause (P3.2) is enabled, from the\n * `NEGOTIATION_ASK_USER_ENABLED` env switch. Defaults to off — the deployment\n * is byte-for-byte unchanged until the flag is flipped, and rolling back is\n * the same single switch.\n */\nexport function configuredAskUserEnabled(): boolean {\n return process.env.NEGOTIATION_ASK_USER_ENABLED === \"true\";\n}\n\n/** Default answer window for a paused `ask_user` negotiation: 24 hours. */\nexport const DEFAULT_ASK_USER_WINDOW_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Answer window for a paused `ask_user` negotiation, in ms. Overridable via\n * `NEGOTIATION_ASK_USER_WINDOW_MS` (dev/e2e use shorter windows to exercise\n * the expiry path); invalid or non-positive values fall back to 24 h.\n */\nexport function askUserAnswerWindowMs(): number {\n const raw = process.env.NEGOTIATION_ASK_USER_WINDOW_MS;\n if (raw) {\n const parsed = Number(raw);\n if (Number.isFinite(parsed) && parsed > 0) return parsed;\n }\n return DEFAULT_ASK_USER_WINDOW_MS;\n}\n\n/**\n * Slack added on top of the answer window when deciding whether a paused\n * (`input_required`) task still holds the conversation lock. Covers expiry\n * worker delay: the lock must outlive the timer slightly, so ambient\n * rediscovery cannot slip in between window expiry and the worker's resume.\n */\nexport const ASK_USER_LOCK_SLACK_MS = 60 * 60 * 1000;\n\n/**\n * Resolve the seat of `userId` on a negotiation task.\n *\n * Keys on `metadata.initiatorUserId` (the rigid v2 stamp), **never on turn\n * parity** — continuations can start with either side speaking first, so\n * parity misattributes seats across sessions. Pre-stamp tasks fall back to\n * `sourceUserId` (the discovery-session opener), which is what the stamp\n * defaults to anyway.\n */\nexport function resolveSeat(\n userId: string,\n metadata: { initiatorUserId?: unknown; sourceUserId?: unknown } | null | undefined,\n): NegotiationSeat {\n const initiator =\n typeof metadata?.initiatorUserId === \"string\" && metadata.initiatorUserId.length > 0\n ? metadata.initiatorUserId\n : typeof metadata?.sourceUserId === \"string\"\n ? metadata.sourceUserId\n : undefined;\n return initiator === userId ? \"initiator\" : \"counterparty\";\n}\n\n/** Human-readable seat-violation message shared by respond surfaces. */\nexport function seatViolationMessage(\n action: string,\n seat: NegotiationSeat,\n version: NegotiationProtocolVersion,\n): string {\n const allowed = allowedActionsFor(version, seat).join(\", \");\n return `Action \"${action}\" is not allowed for your seat (${seat}) under negotiation protocol ${version}. Allowed actions: ${allowed}.`;\n}\n"]}