@indexnetwork/protocol 21.0.0-rc.489.1 → 21.1.0-rc.491.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 (31) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/STABILITY.md +10 -2
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +1 -0
  5. package/dist/negotiations/negotiation.agent.d.ts +13 -0
  6. package/dist/negotiations/negotiation.agent.js +53 -3
  7. package/dist/negotiations/negotiation.client-dm.d.ts +50 -0
  8. package/dist/negotiations/negotiation.client-dm.js +66 -0
  9. package/dist/negotiations/negotiation.graph.d.ts +110 -1
  10. package/dist/negotiations/negotiation.graph.js +2 -1
  11. package/dist/negotiations/negotiation.graph.shared.d.ts +17 -0
  12. package/dist/negotiations/negotiation.graph.shared.js +23 -0
  13. package/dist/negotiations/negotiation.graph.turn.d.ts +27 -0
  14. package/dist/negotiations/negotiation.graph.turn.js +64 -2
  15. package/dist/negotiations/negotiation.module.d.ts +2 -1
  16. package/dist/negotiations/negotiation.module.js +1 -1
  17. package/dist/negotiations/negotiation.protocol.d.ts +498 -0
  18. package/dist/negotiations/negotiation.question-safety.d.ts +28 -0
  19. package/dist/negotiations/negotiation.question-safety.js +65 -0
  20. package/dist/negotiations/negotiation.state.d.ts +110 -0
  21. package/dist/questions/question.schema.d.ts +13 -31
  22. package/dist/questions/question.schema.js +9 -15
  23. package/dist/shared/schemas/negotiation-state.schema.d.ts +182 -3
  24. package/dist/shared/schemas/negotiation-state.schema.js +23 -3
  25. package/dist/shared/schemas/question-block.fixture.d.ts +12 -0
  26. package/dist/shared/schemas/question-block.fixture.js +39 -0
  27. package/dist/shared/schemas/question-block.schema.d.ts +111 -0
  28. package/dist/shared/schemas/question-block.schema.js +124 -0
  29. package/dist/shared/schemas/structured-question.schema.d.ts +66 -0
  30. package/dist/shared/schemas/structured-question.schema.js +31 -0
  31. package/package.json +9 -1
@@ -0,0 +1,39 @@
1
+ export const questionProseFixture = [
2
+ "I moved three conversations forward on your search for a technical co-founder.",
3
+ "Two of them are waiting on details only you can give — answer here and I will",
4
+ "pick the negotiations back up.",
5
+ ].join("\n");
6
+ export const questionBlockFixture = {
7
+ version: 1,
8
+ questions: [
9
+ {
10
+ prompt: "Both counterparts asked about equity: what range are you prepared to offer a founding engineer?",
11
+ opportunityId: "0b0e8a9c-6d3f-4d6a-9f2e-1c5b7a4d8e01",
12
+ alsoUnblocks: ["7f3d2c1b-8a90-4e5f-b6c7-d8e9f0a1b2c3"],
13
+ },
14
+ {
15
+ prompt: "The Berlin robotics lab wants to know whether you can be on-site one week per month.",
16
+ opportunityId: "4a5b6c7d-8e9f-4a1b-8c2d-3e4f5a6b7c8d",
17
+ },
18
+ ],
19
+ };
20
+ export const questionMessageFixture = `${questionProseFixture}
21
+
22
+ \`\`\`index-questions
23
+ {
24
+ "version": 1,
25
+ "questions": [
26
+ {
27
+ "prompt": "Both counterparts asked about equity: what range are you prepared to offer a founding engineer?",
28
+ "opportunityId": "0b0e8a9c-6d3f-4d6a-9f2e-1c5b7a4d8e01",
29
+ "alsoUnblocks": [
30
+ "7f3d2c1b-8a90-4e5f-b6c7-d8e9f0a1b2c3"
31
+ ]
32
+ },
33
+ {
34
+ "prompt": "The Berlin robotics lab wants to know whether you can be on-site one week per month.",
35
+ "opportunityId": "4a5b6c7d-8e9f-4a1b-8c2d-3e4f5a6b7c8d"
36
+ }
37
+ ]
38
+ }
39
+ \`\`\``;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The question block: the rendering contract between the negotiator's
3
+ * question-message and the web client's steps UI, and the routing contract
4
+ * for answers (docs/plans/2026-08-18-conversational-questions.md).
5
+ *
6
+ * The block is a *view* of the currently-parked negotiations for one intent
7
+ * scope, regenerated fresh on every change — it is not a persistence schema
8
+ * and must never grow state (answered flags, timestamps, question ids).
9
+ * A question's identity is its primary negotiation reference: the id of the
10
+ * opportunity row the negotiation runs on. Task re-resolution from that row
11
+ * is server-side and never encoded here — deliberately, because a snapshotted
12
+ * task id would go stale: answer routing branches on what it re-resolves
13
+ * (an `input_required` task → mid-flight consult, answered via the exact
14
+ * continuation's successor task; a completed task on a stalled opportunity
15
+ * with a trailing ask_user gap → post-stall park, answered via a retry).
16
+ *
17
+ * This module must stay browser-safe: it may import zod and nothing else.
18
+ * It is exposed to the web client via the `@indexnetwork/protocol/question-block`
19
+ * subpath export (see STABILITY.md).
20
+ */
21
+ import { z } from "zod";
22
+ /** Info string of the fenced section that carries the block in a message body. */
23
+ export declare const QUESTION_BLOCK_MARKER = "index-questions";
24
+ /** Bump when the block shape changes; parsers fail closed on unknown versions. */
25
+ export declare const QUESTION_BLOCK_VERSION = 1;
26
+ export declare const QuestionBlockQuestionSchema: z.ZodObject<{
27
+ /** Agent-authored question text, rendered as one step. */
28
+ prompt: z.ZodString;
29
+ /** Primary negotiation this question unparks; doubles as the question's identity. */
30
+ opportunityId: z.ZodString;
31
+ /** Further negotiations parked on the same gap that this answer also unparks. */
32
+ alsoUnblocks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
33
+ }, "strict", z.ZodTypeAny, {
34
+ prompt: string;
35
+ opportunityId: string;
36
+ alsoUnblocks?: string[] | undefined;
37
+ }, {
38
+ prompt: string;
39
+ opportunityId: string;
40
+ alsoUnblocks?: string[] | undefined;
41
+ }>;
42
+ export type QuestionBlockQuestion = z.infer<typeof QuestionBlockQuestionSchema>;
43
+ export declare const QuestionBlockSchema: z.ZodEffects<z.ZodObject<{
44
+ version: z.ZodLiteral<1>;
45
+ questions: z.ZodArray<z.ZodObject<{
46
+ /** Agent-authored question text, rendered as one step. */
47
+ prompt: z.ZodString;
48
+ /** Primary negotiation this question unparks; doubles as the question's identity. */
49
+ opportunityId: z.ZodString;
50
+ /** Further negotiations parked on the same gap that this answer also unparks. */
51
+ alsoUnblocks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
52
+ }, "strict", z.ZodTypeAny, {
53
+ prompt: string;
54
+ opportunityId: string;
55
+ alsoUnblocks?: string[] | undefined;
56
+ }, {
57
+ prompt: string;
58
+ opportunityId: string;
59
+ alsoUnblocks?: string[] | undefined;
60
+ }>, "many">;
61
+ }, "strict", z.ZodTypeAny, {
62
+ questions: {
63
+ prompt: string;
64
+ opportunityId: string;
65
+ alsoUnblocks?: string[] | undefined;
66
+ }[];
67
+ version: 1;
68
+ }, {
69
+ questions: {
70
+ prompt: string;
71
+ opportunityId: string;
72
+ alsoUnblocks?: string[] | undefined;
73
+ }[];
74
+ version: 1;
75
+ }>, {
76
+ questions: {
77
+ prompt: string;
78
+ opportunityId: string;
79
+ alsoUnblocks?: string[] | undefined;
80
+ }[];
81
+ version: 1;
82
+ }, {
83
+ questions: {
84
+ prompt: string;
85
+ opportunityId: string;
86
+ alsoUnblocks?: string[] | undefined;
87
+ }[];
88
+ version: 1;
89
+ }>;
90
+ export type QuestionBlock = z.infer<typeof QuestionBlockSchema>;
91
+ /** A parsed question-message: the agent's prose and the block that followed it. */
92
+ export interface ParsedQuestionMessage {
93
+ prose: string;
94
+ block: QuestionBlock;
95
+ }
96
+ /**
97
+ * Embed a block after the agent's prose. The block is always the terminal
98
+ * section of the body: prose, a blank line, then a fenced section whose info
99
+ * string is the marker. The fence is lengthened past any backtick run in the
100
+ * payload so the payload can never close it early.
101
+ *
102
+ * Throws on a block that fails the schema — serializing an invalid block is a
103
+ * producer bug, not a rendering condition.
104
+ */
105
+ export declare function serializeQuestionMessage(prose: string, block: QuestionBlock): string;
106
+ /**
107
+ * Extract the block from a message body. Fails closed: any body that does not
108
+ * end in exactly one well-formed, schema-valid block returns null, and the
109
+ * caller renders the whole body as plain text — never a broken steps UI.
110
+ */
111
+ export declare function parseQuestionMessage(body: string): ParsedQuestionMessage | null;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The question block: the rendering contract between the negotiator's
3
+ * question-message and the web client's steps UI, and the routing contract
4
+ * for answers (docs/plans/2026-08-18-conversational-questions.md).
5
+ *
6
+ * The block is a *view* of the currently-parked negotiations for one intent
7
+ * scope, regenerated fresh on every change — it is not a persistence schema
8
+ * and must never grow state (answered flags, timestamps, question ids).
9
+ * A question's identity is its primary negotiation reference: the id of the
10
+ * opportunity row the negotiation runs on. Task re-resolution from that row
11
+ * is server-side and never encoded here — deliberately, because a snapshotted
12
+ * task id would go stale: answer routing branches on what it re-resolves
13
+ * (an `input_required` task → mid-flight consult, answered via the exact
14
+ * continuation's successor task; a completed task on a stalled opportunity
15
+ * with a trailing ask_user gap → post-stall park, answered via a retry).
16
+ *
17
+ * This module must stay browser-safe: it may import zod and nothing else.
18
+ * It is exposed to the web client via the `@indexnetwork/protocol/question-block`
19
+ * subpath export (see STABILITY.md).
20
+ */
21
+ import { z } from "zod";
22
+ /** Info string of the fenced section that carries the block in a message body. */
23
+ export const QUESTION_BLOCK_MARKER = "index-questions";
24
+ /** Bump when the block shape changes; parsers fail closed on unknown versions. */
25
+ export const QUESTION_BLOCK_VERSION = 1;
26
+ /**
27
+ * A negotiation reference: the id of the opportunity row the negotiation runs
28
+ * on. There is no negotiations table — opportunity id is the durable identity
29
+ * used by every negotiation binding (see NegotiationQuestionProvenanceSchema).
30
+ */
31
+ const NegotiationRefSchema = z.string().uuid();
32
+ export const QuestionBlockQuestionSchema = z.object({
33
+ /** Agent-authored question text, rendered as one step. */
34
+ prompt: z.string().min(1).max(2000),
35
+ /** Primary negotiation this question unparks; doubles as the question's identity. */
36
+ opportunityId: NegotiationRefSchema,
37
+ /** Further negotiations parked on the same gap that this answer also unparks. */
38
+ alsoUnblocks: z.array(NegotiationRefSchema).max(8).optional(),
39
+ }).strict();
40
+ export const QuestionBlockSchema = z.object({
41
+ version: z.literal(QUESTION_BLOCK_VERSION),
42
+ questions: z.array(QuestionBlockQuestionSchema).min(1).max(20),
43
+ }).strict().superRefine((block, ctx) => {
44
+ // Identity is the negotiation ref, so a ref may appear exactly once in the
45
+ // whole block — a duplicate would make answer routing ambiguous.
46
+ const seen = new Set();
47
+ block.questions.forEach((question, questionIndex) => {
48
+ const refs = [question.opportunityId, ...(question.alsoUnblocks ?? [])];
49
+ refs.forEach((ref, refIndex) => {
50
+ if (seen.has(ref)) {
51
+ ctx.addIssue({
52
+ code: z.ZodIssueCode.custom,
53
+ path: refIndex === 0
54
+ ? ["questions", questionIndex, "opportunityId"]
55
+ : ["questions", questionIndex, "alsoUnblocks", refIndex - 1],
56
+ message: `negotiation ref ${ref} appears more than once in the block`,
57
+ });
58
+ }
59
+ seen.add(ref);
60
+ });
61
+ });
62
+ });
63
+ const openFencePattern = new RegExp(`^(\`{3,})${QUESTION_BLOCK_MARKER}[ \\t]*\\r?$`, "m");
64
+ function longestBacktickRun(text) {
65
+ let longest = 0;
66
+ for (const run of text.match(/`+/g) ?? [])
67
+ longest = Math.max(longest, run.length);
68
+ return longest;
69
+ }
70
+ /**
71
+ * Embed a block after the agent's prose. The block is always the terminal
72
+ * section of the body: prose, a blank line, then a fenced section whose info
73
+ * string is the marker. The fence is lengthened past any backtick run in the
74
+ * payload so the payload can never close it early.
75
+ *
76
+ * Throws on a block that fails the schema — serializing an invalid block is a
77
+ * producer bug, not a rendering condition.
78
+ */
79
+ export function serializeQuestionMessage(prose, block) {
80
+ const payload = JSON.stringify(QuestionBlockSchema.parse(block), null, 2);
81
+ const fence = "`".repeat(Math.max(3, longestBacktickRun(payload) + 1));
82
+ const trimmedProse = prose.trimEnd();
83
+ const fenced = `${fence}${QUESTION_BLOCK_MARKER}\n${payload}\n${fence}`;
84
+ return trimmedProse.length > 0 ? `${trimmedProse}\n\n${fenced}` : fenced;
85
+ }
86
+ /**
87
+ * Extract the block from a message body. Fails closed: any body that does not
88
+ * end in exactly one well-formed, schema-valid block returns null, and the
89
+ * caller renders the whole body as plain text — never a broken steps UI.
90
+ */
91
+ export function parseQuestionMessage(body) {
92
+ // Anchor on the last marker fence so prose that merely mentions the marker
93
+ // earlier in the body cannot shadow the real block.
94
+ let open = null;
95
+ const searchPattern = new RegExp(openFencePattern.source, "gm");
96
+ for (let match = searchPattern.exec(body); match; match = searchPattern.exec(body)) {
97
+ open = match;
98
+ }
99
+ if (!open)
100
+ return null;
101
+ const fenceLength = open[1].length;
102
+ const payloadStart = open.index + open[0].length + 1; // past the fence line's newline
103
+ if (payloadStart > body.length)
104
+ return null;
105
+ const closePattern = new RegExp(`^\`{${fenceLength}}[ \\t]*\\r?$`, "m");
106
+ const close = closePattern.exec(body.slice(payloadStart));
107
+ if (!close)
108
+ return null;
109
+ // The block must terminate the message: nothing but whitespace may follow.
110
+ const afterClose = body.slice(payloadStart + close.index + close[0].length);
111
+ if (afterClose.trim().length > 0)
112
+ return null;
113
+ let parsedPayload;
114
+ try {
115
+ parsedPayload = JSON.parse(body.slice(payloadStart, payloadStart + close.index));
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ const block = QuestionBlockSchema.safeParse(parsedPayload);
121
+ if (!block.success)
122
+ return null;
123
+ return { prose: body.slice(0, open.index).trimEnd(), block: block.data };
124
+ }
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Canonical structured question shape: the title/prompt/options/multiSelect
4
+ * quartet every question renderer (frontend cards, MCP elicitation) consumes.
5
+ *
6
+ * It lives in `shared/` rather than inside `questions/` for the same reason as
7
+ * `underspecification.schema.ts`: more than one capability needs the shape.
8
+ * The questions capability owns question *generation* and extends this with its
9
+ * own provenance field; the negotiator only needs to hand over a question it
10
+ * authored (`AskUserPayloadSchema` in `negotiation-state.schema.ts`), and
11
+ * `shared/schemas` must not value-import a capability module to reach a shape.
12
+ *
13
+ * Field constraints are tuned for the renderer and are the single source of
14
+ * truth — `questions/question.schema.ts` re-exports rather than redeclares.
15
+ */
16
+ export declare const QuestionOptionSchema: z.ZodObject<{
17
+ /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
18
+ label: z.ZodString;
19
+ /** Explains the consequence of choosing this option, not just its definition. */
20
+ description: z.ZodString;
21
+ }, "strip", z.ZodTypeAny, {
22
+ label: string;
23
+ description: string;
24
+ }, {
25
+ label: string;
26
+ description: string;
27
+ }>;
28
+ export declare const StructuredQuestionSchema: z.ZodObject<{
29
+ /** ≤12 chars. Noun of the decision domain — e.g. "Stage", "Timing", "Role". */
30
+ title: z.ZodString;
31
+ /** ≤2 sentences, ≤400 chars. Ends in a question mark. */
32
+ prompt: z.ZodString;
33
+ /** 2–4 options. No explicit "Other" — clients provide that automatically. */
34
+ options: z.ZodArray<z.ZodObject<{
35
+ /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
36
+ label: z.ZodString;
37
+ /** Explains the consequence of choosing this option, not just its definition. */
38
+ description: z.ZodString;
39
+ }, "strip", z.ZodTypeAny, {
40
+ label: string;
41
+ description: string;
42
+ }, {
43
+ label: string;
44
+ description: string;
45
+ }>, "many">;
46
+ /** True when options are not mutually exclusive (priorities, bundles). */
47
+ multiSelect: z.ZodBoolean;
48
+ }, "strip", z.ZodTypeAny, {
49
+ prompt: string;
50
+ options: {
51
+ label: string;
52
+ description: string;
53
+ }[];
54
+ title: string;
55
+ multiSelect: boolean;
56
+ }, {
57
+ prompt: string;
58
+ options: {
59
+ label: string;
60
+ description: string;
61
+ }[];
62
+ title: string;
63
+ multiSelect: boolean;
64
+ }>;
65
+ export type QuestionOption = z.infer<typeof QuestionOptionSchema>;
66
+ export type StructuredQuestion = z.infer<typeof StructuredQuestionSchema>;
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Canonical structured question shape: the title/prompt/options/multiSelect
4
+ * quartet every question renderer (frontend cards, MCP elicitation) consumes.
5
+ *
6
+ * It lives in `shared/` rather than inside `questions/` for the same reason as
7
+ * `underspecification.schema.ts`: more than one capability needs the shape.
8
+ * The questions capability owns question *generation* and extends this with its
9
+ * own provenance field; the negotiator only needs to hand over a question it
10
+ * authored (`AskUserPayloadSchema` in `negotiation-state.schema.ts`), and
11
+ * `shared/schemas` must not value-import a capability module to reach a shape.
12
+ *
13
+ * Field constraints are tuned for the renderer and are the single source of
14
+ * truth — `questions/question.schema.ts` re-exports rather than redeclares.
15
+ */
16
+ export const QuestionOptionSchema = z.object({
17
+ /** Display text. Suffix " (Recommended)" on the safest path; list it first. */
18
+ label: z.string().min(1).max(120),
19
+ /** Explains the consequence of choosing this option, not just its definition. */
20
+ description: z.string().min(1).max(280),
21
+ });
22
+ export const StructuredQuestionSchema = z.object({
23
+ /** ≤12 chars. Noun of the decision domain — e.g. "Stage", "Timing", "Role". */
24
+ title: z.string().min(1).max(12),
25
+ /** ≤2 sentences, ≤400 chars. Ends in a question mark. */
26
+ prompt: z.string().min(1).max(400),
27
+ /** 2–4 options. No explicit "Other" — clients provide that automatically. */
28
+ options: z.array(QuestionOptionSchema).min(2).max(4),
29
+ /** True when options are not mutually exclusive (priorities, bundles). */
30
+ multiSelect: z.boolean(),
31
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "21.0.0-rc.489.1",
3
+ "version": "21.1.0-rc.491.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -8,6 +8,14 @@
8
8
  ".": {
9
9
  "import": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts"
11
+ },
12
+ "./question-block": {
13
+ "types": "./dist/shared/schemas/question-block.schema.d.ts",
14
+ "import": "./dist/shared/schemas/question-block.schema.js"
15
+ },
16
+ "./question-block/fixture": {
17
+ "types": "./dist/shared/schemas/question-block.fixture.d.ts",
18
+ "import": "./dist/shared/schemas/question-block.fixture.js"
11
19
  }
12
20
  },
13
21
  "files": [