@indexnetwork/protocol 21.0.0-rc.489.1 → 21.0.0-rc.490.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,6 +36,71 @@ export function isSafeNegotiationQuestionText(value, options) {
36
36
  }
37
37
  return true;
38
38
  }
39
+ /**
40
+ * Instruction-shaped text that must never survive into a rendered question.
41
+ *
42
+ * Applies ONLY to `isSafeAuthoredNegotiationQuestion` — deliberately not added
43
+ * to `isSafeNegotiationQuestionText`, which guards a live path whose verdicts
44
+ * must not move. The exposure is specific to the authored question: the client
45
+ * reads these strings verbatim on a card, and whatever they select comes BACK
46
+ * into the negotiator's prompt as a user answer. An option labelled "ignore
47
+ * previous instructions" is therefore a round-trip injection with a human
48
+ * clicking the button.
49
+ *
50
+ * Two families. Override phrasings ("ignore the above", "you are now a…"), and
51
+ * forged structure: role headers and the `--- section ---` delimiter this
52
+ * codebase actually uses to fence prompt sections (see
53
+ * `renderNegotiatorClientDmSection`), which is what a forged block would have
54
+ * to imitate to be read as one.
55
+ */
56
+ const PROMPT_INJECTION_PATTERN = /\b(?:ignore|disregard|forget|override|bypass)\b[^.!?\n]{0,40}\b(?:instructions?|prompts?|rules?|directives?|guidelines?|everything\s+above|the\s+above|all\s+of\s+the\s+above)\b|\byou\s+are\s+now\s+(?:an?|the)\b|\bnew\s+instructions?\s*:|^\s*(?:#{1,6}\s*|\*{2}\s*)?(?:system|assistant|developer|human)\s*:|<\|[^|>]{0,40}\|>|\[\/?(?:INST|SYS)\]|^\s*-{3,}[^\n]*-{3,}\s*$/im;
57
+ /**
58
+ * Whole-question gate for a question the NEGOTIATOR authored.
59
+ *
60
+ * The pre-A2H `disclosureSubject` never reached the client as written — the
61
+ * server templated the copy around it. An authored `StructuredQuestion` is
62
+ * rendered verbatim, so every visible field needs the same gate the subject
63
+ * got, and it needs it with the identifiers in hand.
64
+ *
65
+ * That is the capability the api-side `isSafeNegotiationQuestionPayload` lacks
66
+ * and cannot gain: at the DB boundary it holds only generic patterns, so it
67
+ * cannot tell that a perfectly well-formed question is naming THIS counterparty
68
+ * or paraphrasing THIS seed assessment. The turn node has both, which is why
69
+ * this runs here and not there.
70
+ *
71
+ * Fail-closed and non-rewriting, like every guard in this file: a caller that
72
+ * gets `false` drops the question and keeps the enum-only path, rather than
73
+ * shipping a repaired one.
74
+ *
75
+ * @param options.forbiddenIdentifiers Names that must not appear as words —
76
+ * the counterparty's, at the call site.
77
+ * @param options.forbiddenSourceText Private inputs that must not be echoed —
78
+ * the seed assessment's reasoning, at the call site.
79
+ */
80
+ export function isSafeAuthoredNegotiationQuestion(question, options) {
81
+ if (!question || typeof question !== 'object')
82
+ return false;
83
+ // Re-checked rather than trusted from the schema: this gate also runs on
84
+ // turns that arrive from an external agent, and a rendered card with one
85
+ // option (or fifteen) is a broken card regardless of how it validated.
86
+ const questionOptions = question.options;
87
+ if (!Array.isArray(questionOptions) || questionOptions.length < 2 || questionOptions.length > 4)
88
+ return false;
89
+ const fields = [
90
+ question.title,
91
+ question.prompt,
92
+ ...questionOptions.flatMap((option) => [option?.label, option?.description]),
93
+ ];
94
+ for (const field of fields) {
95
+ if (typeof field !== 'string')
96
+ return false;
97
+ if (!isSafeNegotiationQuestionText(field, options))
98
+ return false;
99
+ if (PROMPT_INJECTION_PATTERN.test(field))
100
+ return false;
101
+ }
102
+ return true;
103
+ }
39
104
  /** Validate the only structured fields allowed to enter the inflight Questioner prompt. */
40
105
  export function validateInflightAskUserFields(input) {
41
106
  const disclosureSubject = input.disclosureSubject?.trim();
@@ -40,10 +40,75 @@ export declare const NegotiationTurnSchema: z.ZodObject<{
40
40
  /** Present when action is `ask_user` (v2, P3.2). */
41
41
  askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
42
42
  reason: z.ZodEnum<["unresolved_owner_constraint", "consequential_disclosure_permission", "repeated_non_convergence", "insufficient_commitment_authority"]>;
43
+ question: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodObject<{
44
+ title: z.ZodString;
45
+ prompt: z.ZodString;
46
+ options: z.ZodArray<z.ZodObject<{
47
+ label: z.ZodString;
48
+ description: z.ZodString;
49
+ }, "strip", z.ZodTypeAny, {
50
+ label: string;
51
+ description: string;
52
+ }, {
53
+ label: string;
54
+ description: string;
55
+ }>, "many">;
56
+ multiSelect: z.ZodBoolean;
57
+ }, "strip", z.ZodTypeAny, {
58
+ prompt: string;
59
+ options: {
60
+ label: string;
61
+ description: string;
62
+ }[];
63
+ title: string;
64
+ multiSelect: boolean;
65
+ }, {
66
+ prompt: string;
67
+ options: {
68
+ label: string;
69
+ description: string;
70
+ }[];
71
+ title: string;
72
+ multiSelect: boolean;
73
+ }>>>, {
74
+ prompt: string;
75
+ options: {
76
+ label: string;
77
+ description: string;
78
+ }[];
79
+ title: string;
80
+ multiSelect: boolean;
81
+ } | undefined, {
82
+ prompt: string;
83
+ options: {
84
+ label: string;
85
+ description: string;
86
+ }[];
87
+ title: string;
88
+ multiSelect: boolean;
89
+ } | null | undefined>;
43
90
  }, "strict", z.ZodTypeAny, {
44
91
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
92
+ question?: {
93
+ prompt: string;
94
+ options: {
95
+ label: string;
96
+ description: string;
97
+ }[];
98
+ title: string;
99
+ multiSelect: boolean;
100
+ } | undefined;
45
101
  }, {
46
102
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
103
+ question?: {
104
+ prompt: string;
105
+ options: {
106
+ label: string;
107
+ description: string;
108
+ }[];
109
+ title: string;
110
+ multiSelect: boolean;
111
+ } | null | undefined;
47
112
  }>>>;
48
113
  }, "strip", z.ZodTypeAny, {
49
114
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -57,6 +122,15 @@ export declare const NegotiationTurnSchema: z.ZodObject<{
57
122
  message?: string | null | undefined;
58
123
  askUser?: {
59
124
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
125
+ question?: {
126
+ prompt: string;
127
+ options: {
128
+ label: string;
129
+ description: string;
130
+ }[];
131
+ title: string;
132
+ multiSelect: boolean;
133
+ } | undefined;
60
134
  } | null | undefined;
61
135
  }, {
62
136
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -70,6 +144,15 @@ export declare const NegotiationTurnSchema: z.ZodObject<{
70
144
  message?: string | null | undefined;
71
145
  askUser?: {
72
146
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
147
+ question?: {
148
+ prompt: string;
149
+ options: {
150
+ label: string;
151
+ description: string;
152
+ }[];
153
+ title: string;
154
+ multiSelect: boolean;
155
+ } | null | undefined;
73
156
  } | null | undefined;
74
157
  }>;
75
158
  /** Restricted v1 turn schema for the system agent (no question action). */
@@ -414,6 +497,15 @@ export declare const NegotiationGraphState: import("@langchain/langgraph").Annot
414
497
  message?: string | null | undefined;
415
498
  askUser?: {
416
499
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
500
+ question?: {
501
+ prompt: string;
502
+ options: {
503
+ label: string;
504
+ description: string;
505
+ }[];
506
+ title: string;
507
+ multiSelect: boolean;
508
+ } | undefined;
417
509
  } | null | undefined;
418
510
  } | null, {
419
511
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -427,6 +519,15 @@ export declare const NegotiationGraphState: import("@langchain/langgraph").Annot
427
519
  message?: string | null | undefined;
428
520
  askUser?: {
429
521
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
522
+ question?: {
523
+ prompt: string;
524
+ options: {
525
+ label: string;
526
+ description: string;
527
+ }[];
528
+ title: string;
529
+ multiSelect: boolean;
530
+ } | undefined;
430
531
  } | null | undefined;
431
532
  } | import("@langchain/langgraph").OverwriteValue<{
432
533
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -440,6 +541,15 @@ export declare const NegotiationGraphState: import("@langchain/langgraph").Annot
440
541
  message?: string | null | undefined;
441
542
  askUser?: {
442
543
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
544
+ question?: {
545
+ prompt: string;
546
+ options: {
547
+ label: string;
548
+ description: string;
549
+ }[];
550
+ title: string;
551
+ multiSelect: boolean;
552
+ } | undefined;
443
553
  } | null | undefined;
444
554
  } | null> | null, unknown>;
445
555
  /**
@@ -7,29 +7,21 @@
7
7
  */
8
8
  import { z } from "zod";
9
9
  import { UnderspecificationTypeSchema, type UnderspecificationType } from "../shared/schemas/underspecification.schema.js";
10
+ import { QuestionOptionSchema, StructuredQuestionSchema, type QuestionOption } from "../shared/schemas/structured-question.schema.js";
10
11
  export { UnderspecificationTypeSchema };
11
- export declare const QuestionOptionSchema: z.ZodObject<{
12
- /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
13
- label: z.ZodString;
14
- /** Explains the consequence of choosing this option, not just its definition. */
15
- description: z.ZodString;
16
- }, "strip", z.ZodTypeAny, {
17
- label: string;
18
- description: string;
19
- }, {
20
- label: string;
21
- description: string;
22
- }>;
12
+ /**
13
+ * The renderer-facing quartet (title/prompt/options/multiSelect) lives in
14
+ * `shared/schemas/structured-question.schema.ts` so the negotiator can author a
15
+ * question without `shared/` importing this capability. Re-exported here so
16
+ * `questions/question.schema.js` stays the import site every caller already uses.
17
+ */
18
+ export { QuestionOptionSchema, StructuredQuestionSchema };
19
+ export type { StructuredQuestion } from "../shared/schemas/structured-question.schema.js";
23
20
  export declare const QuestionSchema: z.ZodObject<{
24
- /** ≤12 chars. Noun of the decision domain — e.g. "Stage", "Timing", "Role". */
25
21
  title: z.ZodString;
26
- /** ≤2 sentences, ≤400 chars. Ends in a question mark. */
27
22
  prompt: z.ZodString;
28
- /** 2–4 options. No explicit "Other" — clients provide that automatically. */
29
23
  options: z.ZodArray<z.ZodObject<{
30
- /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
31
24
  label: z.ZodString;
32
- /** Explains the consequence of choosing this option, not just its definition. */
33
25
  description: z.ZodString;
34
26
  }, "strip", z.ZodTypeAny, {
35
27
  label: string;
@@ -38,8 +30,8 @@ export declare const QuestionSchema: z.ZodObject<{
38
30
  label: string;
39
31
  description: string;
40
32
  }>, "many">;
41
- /** True when options are not mutually exclusive (priorities, bundles). */
42
33
  multiSelect: z.ZodBoolean;
34
+ } & {
43
35
  /**
44
36
  * Optional provenance line rendered as a muted chip above the prompt
45
37
  * (e.g. "based on 18 people matching this intent"). Aggregate counts only —
@@ -77,15 +69,10 @@ export declare const QuestionSchema: z.ZodObject<{
77
69
  }>;
78
70
  export declare const QuestionStrategySchema: z.ZodEnum<["refine_intent", "surface_missing_detail", "open_adjacent_thread", "reflective_summary", "surface_emergent_knowledge"]>;
79
71
  export declare const QuestionWithStrategySchema: z.ZodObject<{
80
- /** ≤12 chars. Noun of the decision domain — e.g. "Stage", "Timing", "Role". */
81
72
  title: z.ZodString;
82
- /** ≤2 sentences, ≤400 chars. Ends in a question mark. */
83
73
  prompt: z.ZodString;
84
- /** 2–4 options. No explicit "Other" — clients provide that automatically. */
85
74
  options: z.ZodArray<z.ZodObject<{
86
- /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
87
75
  label: z.ZodString;
88
- /** Explains the consequence of choosing this option, not just its definition. */
89
76
  description: z.ZodString;
90
77
  }, "strip", z.ZodTypeAny, {
91
78
  label: string;
@@ -94,8 +81,8 @@ export declare const QuestionWithStrategySchema: z.ZodObject<{
94
81
  label: string;
95
82
  description: string;
96
83
  }>, "many">;
97
- /** True when options are not mutually exclusive (priorities, bundles). */
98
84
  multiSelect: z.ZodBoolean;
85
+ } & {
99
86
  /**
100
87
  * Optional provenance line rendered as a muted chip above the prompt
101
88
  * (e.g. "based on 18 people matching this intent"). Aggregate counts only —
@@ -141,15 +128,10 @@ export declare const QuestionWithStrategySchema: z.ZodObject<{
141
128
  }>;
142
129
  export declare const QuestionGeneratorResponseSchema: z.ZodObject<{
143
130
  questions: z.ZodArray<z.ZodObject<{
144
- /** ≤12 chars. Noun of the decision domain — e.g. "Stage", "Timing", "Role". */
145
131
  title: z.ZodString;
146
- /** ≤2 sentences, ≤400 chars. Ends in a question mark. */
147
132
  prompt: z.ZodString;
148
- /** 2–4 options. No explicit "Other" — clients provide that automatically. */
149
133
  options: z.ZodArray<z.ZodObject<{
150
- /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
151
134
  label: z.ZodString;
152
- /** Explains the consequence of choosing this option, not just its definition. */
153
135
  description: z.ZodString;
154
136
  }, "strip", z.ZodTypeAny, {
155
137
  label: string;
@@ -158,8 +140,8 @@ export declare const QuestionGeneratorResponseSchema: z.ZodObject<{
158
140
  label: string;
159
141
  description: string;
160
142
  }>, "many">;
161
- /** True when options are not mutually exclusive (priorities, bundles). */
162
143
  multiSelect: z.ZodBoolean;
144
+ } & {
163
145
  /**
164
146
  * Optional provenance line rendered as a muted chip above the prompt
165
147
  * (e.g. "based on 18 people matching this intent"). Aggregate counts only —
@@ -230,7 +212,7 @@ export declare const QuestionGeneratorResponseSchema: z.ZodObject<{
230
212
  evidence?: string | null | undefined;
231
213
  }[];
232
214
  }>;
233
- export type QuestionOption = z.infer<typeof QuestionOptionSchema>;
215
+ export type { QuestionOption };
234
216
  export type Question = z.infer<typeof QuestionSchema>;
235
217
  export type { UnderspecificationType };
236
218
  export type QuestionStrategy = z.infer<typeof QuestionStrategySchema>;
@@ -7,22 +7,16 @@
7
7
  */
8
8
  import { z } from "zod";
9
9
  import { UnderspecificationTypeSchema } from "../shared/schemas/underspecification.schema.js";
10
+ import { QuestionOptionSchema, StructuredQuestionSchema } from "../shared/schemas/structured-question.schema.js";
10
11
  export { UnderspecificationTypeSchema };
11
- export const QuestionOptionSchema = z.object({
12
- /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
13
- label: z.string().min(1).max(120),
14
- /** Explains the consequence of choosing this option, not just its definition. */
15
- description: z.string().min(1).max(280),
16
- });
17
- export const QuestionSchema = z.object({
18
- /** ≤12 chars. Noun of the decision domain — e.g. "Stage", "Timing", "Role". */
19
- title: z.string().min(1).max(12),
20
- /** ≤2 sentences, ≤400 chars. Ends in a question mark. */
21
- prompt: z.string().min(1).max(400),
22
- /** 2–4 options. No explicit "Other" — clients provide that automatically. */
23
- options: z.array(QuestionOptionSchema).min(2).max(4),
24
- /** True when options are not mutually exclusive (priorities, bundles). */
25
- multiSelect: z.boolean(),
12
+ /**
13
+ * The renderer-facing quartet (title/prompt/options/multiSelect) lives in
14
+ * `shared/schemas/structured-question.schema.ts` so the negotiator can author a
15
+ * question without `shared/` importing this capability. Re-exported here so
16
+ * `questions/question.schema.js` stays the import site every caller already uses.
17
+ */
18
+ export { QuestionOptionSchema, StructuredQuestionSchema };
19
+ export const QuestionSchema = StructuredQuestionSchema.extend({
26
20
  /**
27
21
  * Optional provenance line rendered as a muted chip above the prompt
28
22
  * (e.g. "based on 18 people matching this intent"). Aggregate counts only —
@@ -25,16 +25,99 @@ export declare const NEGOTIATION_CONSULTATION_REASONS: readonly ["unresolved_own
25
25
  export declare const NegotiationConsultationReasonSchema: z.ZodEnum<["unresolved_owner_constraint", "consequential_disclosure_permission", "repeated_non_convergence", "insufficient_commitment_authority"]>;
26
26
  export type NegotiationConsultationReason = z.infer<typeof NegotiationConsultationReasonSchema>;
27
27
  /**
28
- * Payload for a v2 `ask_user` action. Agents may choose only a closed reason;
29
- * disclosure subjects and question wording are always rendered by fixed
30
- * server templates and can never carry model-authored instructions.
28
+ * Payload for a v2 `ask_user` action.
29
+ *
30
+ * `reason` stays the closed enum it has always been: it is admission metadata
31
+ * for the deterministic consultation policy, not copy. `question` is the
32
+ * channel through which the user's own personal agent — the only thing that
33
+ * should be writing questions — hands over the question it authored from the
34
+ * negotiation it is actually having. Free-form keys beyond these two are still
35
+ * rejected by `.strict()`.
31
36
  */
32
37
  export declare const AskUserPayloadSchema: z.ZodObject<{
33
38
  reason: z.ZodEnum<["unresolved_owner_constraint", "consequential_disclosure_permission", "repeated_non_convergence", "insufficient_commitment_authority"]>;
39
+ /**
40
+ * The question the negotiating agent wrote, in the structured shape the UI
41
+ * already renders. Optional: v1 turns, external agents, and the existing
42
+ * enum-only path must all keep validating, so a payload of `{ reason }`
43
+ * alone stays byte-identical in and out.
44
+ *
45
+ * Declared `.nullable().optional()` (not bare `.optional()`) so the enclosing
46
+ * turn schemas survive OpenAI/OpenRouter strict structured-output conversion,
47
+ * which rejects optional-without-nullable fields — the same constraint
48
+ * documented on `QuestionSchema.evidence`. The `.transform()` normalizes an
49
+ * LLM-returned `null` back to `undefined`, so a null is never persisted and
50
+ * `null` and omitted read as absent identically downstream.
51
+ */
52
+ question: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodObject<{
53
+ title: z.ZodString;
54
+ prompt: z.ZodString;
55
+ options: z.ZodArray<z.ZodObject<{
56
+ label: z.ZodString;
57
+ description: z.ZodString;
58
+ }, "strip", z.ZodTypeAny, {
59
+ label: string;
60
+ description: string;
61
+ }, {
62
+ label: string;
63
+ description: string;
64
+ }>, "many">;
65
+ multiSelect: z.ZodBoolean;
66
+ }, "strip", z.ZodTypeAny, {
67
+ prompt: string;
68
+ options: {
69
+ label: string;
70
+ description: string;
71
+ }[];
72
+ title: string;
73
+ multiSelect: boolean;
74
+ }, {
75
+ prompt: string;
76
+ options: {
77
+ label: string;
78
+ description: string;
79
+ }[];
80
+ title: string;
81
+ multiSelect: boolean;
82
+ }>>>, {
83
+ prompt: string;
84
+ options: {
85
+ label: string;
86
+ description: string;
87
+ }[];
88
+ title: string;
89
+ multiSelect: boolean;
90
+ } | undefined, {
91
+ prompt: string;
92
+ options: {
93
+ label: string;
94
+ description: string;
95
+ }[];
96
+ title: string;
97
+ multiSelect: boolean;
98
+ } | null | undefined>;
34
99
  }, "strict", z.ZodTypeAny, {
35
100
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
101
+ question?: {
102
+ prompt: string;
103
+ options: {
104
+ label: string;
105
+ description: string;
106
+ }[];
107
+ title: string;
108
+ multiSelect: boolean;
109
+ } | undefined;
36
110
  }, {
37
111
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
112
+ question?: {
113
+ prompt: string;
114
+ options: {
115
+ label: string;
116
+ description: string;
117
+ }[];
118
+ title: string;
119
+ multiSelect: boolean;
120
+ } | null | undefined;
38
121
  }>;
39
122
  export type AskUserPayload = z.infer<typeof AskUserPayloadSchema>;
40
123
  export declare const NegotiationTurnSchema: z.ZodObject<{
@@ -68,10 +151,88 @@ export declare const NegotiationTurnSchema: z.ZodObject<{
68
151
  /** Present when action is `ask_user` (v2, P3.2). */
69
152
  askUser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
70
153
  reason: z.ZodEnum<["unresolved_owner_constraint", "consequential_disclosure_permission", "repeated_non_convergence", "insufficient_commitment_authority"]>;
154
+ /**
155
+ * The question the negotiating agent wrote, in the structured shape the UI
156
+ * already renders. Optional: v1 turns, external agents, and the existing
157
+ * enum-only path must all keep validating, so a payload of `{ reason }`
158
+ * alone stays byte-identical in and out.
159
+ *
160
+ * Declared `.nullable().optional()` (not bare `.optional()`) so the enclosing
161
+ * turn schemas survive OpenAI/OpenRouter strict structured-output conversion,
162
+ * which rejects optional-without-nullable fields — the same constraint
163
+ * documented on `QuestionSchema.evidence`. The `.transform()` normalizes an
164
+ * LLM-returned `null` back to `undefined`, so a null is never persisted and
165
+ * `null` and omitted read as absent identically downstream.
166
+ */
167
+ question: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodObject<{
168
+ title: z.ZodString;
169
+ prompt: z.ZodString;
170
+ options: z.ZodArray<z.ZodObject<{
171
+ label: z.ZodString;
172
+ description: z.ZodString;
173
+ }, "strip", z.ZodTypeAny, {
174
+ label: string;
175
+ description: string;
176
+ }, {
177
+ label: string;
178
+ description: string;
179
+ }>, "many">;
180
+ multiSelect: z.ZodBoolean;
181
+ }, "strip", z.ZodTypeAny, {
182
+ prompt: string;
183
+ options: {
184
+ label: string;
185
+ description: string;
186
+ }[];
187
+ title: string;
188
+ multiSelect: boolean;
189
+ }, {
190
+ prompt: string;
191
+ options: {
192
+ label: string;
193
+ description: string;
194
+ }[];
195
+ title: string;
196
+ multiSelect: boolean;
197
+ }>>>, {
198
+ prompt: string;
199
+ options: {
200
+ label: string;
201
+ description: string;
202
+ }[];
203
+ title: string;
204
+ multiSelect: boolean;
205
+ } | undefined, {
206
+ prompt: string;
207
+ options: {
208
+ label: string;
209
+ description: string;
210
+ }[];
211
+ title: string;
212
+ multiSelect: boolean;
213
+ } | null | undefined>;
71
214
  }, "strict", z.ZodTypeAny, {
72
215
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
216
+ question?: {
217
+ prompt: string;
218
+ options: {
219
+ label: string;
220
+ description: string;
221
+ }[];
222
+ title: string;
223
+ multiSelect: boolean;
224
+ } | undefined;
73
225
  }, {
74
226
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
227
+ question?: {
228
+ prompt: string;
229
+ options: {
230
+ label: string;
231
+ description: string;
232
+ }[];
233
+ title: string;
234
+ multiSelect: boolean;
235
+ } | null | undefined;
75
236
  }>>>;
76
237
  }, "strip", z.ZodTypeAny, {
77
238
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -85,6 +246,15 @@ export declare const NegotiationTurnSchema: z.ZodObject<{
85
246
  message?: string | null | undefined;
86
247
  askUser?: {
87
248
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
249
+ question?: {
250
+ prompt: string;
251
+ options: {
252
+ label: string;
253
+ description: string;
254
+ }[];
255
+ title: string;
256
+ multiSelect: boolean;
257
+ } | undefined;
88
258
  } | null | undefined;
89
259
  }, {
90
260
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -98,6 +268,15 @@ export declare const NegotiationTurnSchema: z.ZodObject<{
98
268
  message?: string | null | undefined;
99
269
  askUser?: {
100
270
  reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority";
271
+ question?: {
272
+ prompt: string;
273
+ options: {
274
+ label: string;
275
+ description: string;
276
+ }[];
277
+ title: string;
278
+ multiSelect: boolean;
279
+ } | null | undefined;
101
280
  } | null | undefined;
102
281
  }>;
103
282
  export type NegotiationTurn = z.infer<typeof NegotiationTurnSchema>;
@@ -4,6 +4,7 @@
4
4
  * LangGraph Annotation.Root stays in the domain file.
5
5
  */
6
6
  import { z } from "zod";
7
+ import { StructuredQuestionSchema } from "./structured-question.schema.js";
7
8
  // ─── Zod schemas (available for runtime validation) ───────────────────────────
8
9
  /**
9
10
  * Union of every negotiation turn action across protocol versions.
@@ -29,12 +30,31 @@ export const NEGOTIATION_CONSULTATION_REASONS = [
29
30
  ];
30
31
  export const NegotiationConsultationReasonSchema = z.enum(NEGOTIATION_CONSULTATION_REASONS);
31
32
  /**
32
- * Payload for a v2 `ask_user` action. Agents may choose only a closed reason;
33
- * disclosure subjects and question wording are always rendered by fixed
34
- * server templates and can never carry model-authored instructions.
33
+ * Payload for a v2 `ask_user` action.
34
+ *
35
+ * `reason` stays the closed enum it has always been: it is admission metadata
36
+ * for the deterministic consultation policy, not copy. `question` is the
37
+ * channel through which the user's own personal agent — the only thing that
38
+ * should be writing questions — hands over the question it authored from the
39
+ * negotiation it is actually having. Free-form keys beyond these two are still
40
+ * rejected by `.strict()`.
35
41
  */
36
42
  export const AskUserPayloadSchema = z.object({
37
43
  reason: NegotiationConsultationReasonSchema,
44
+ /**
45
+ * The question the negotiating agent wrote, in the structured shape the UI
46
+ * already renders. Optional: v1 turns, external agents, and the existing
47
+ * enum-only path must all keep validating, so a payload of `{ reason }`
48
+ * alone stays byte-identical in and out.
49
+ *
50
+ * Declared `.nullable().optional()` (not bare `.optional()`) so the enclosing
51
+ * turn schemas survive OpenAI/OpenRouter strict structured-output conversion,
52
+ * which rejects optional-without-nullable fields — the same constraint
53
+ * documented on `QuestionSchema.evidence`. The `.transform()` normalizes an
54
+ * LLM-returned `null` back to `undefined`, so a null is never persisted and
55
+ * `null` and omitted read as absent identically downstream.
56
+ */
57
+ question: StructuredQuestionSchema.nullable().optional().transform((value) => value ?? undefined),
38
58
  }).strict();
39
59
  export const NegotiationTurnSchema = z.object({
40
60
  action: z.enum(NEGOTIATION_ACTIONS),