@indexnetwork/protocol 5.0.0 → 5.2.0-rc.368.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 (71) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/index.d.ts +8 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/negotiation/negotiation.summarizer.d.ts +1 -1
  7. package/dist/negotiation/negotiation.summarizer.d.ts.map +1 -1
  8. package/dist/negotiation/negotiation.summarizer.js.map +1 -1
  9. package/dist/opportunity/discovery-question.helper.d.ts +1 -1
  10. package/dist/opportunity/discovery-question.helper.d.ts.map +1 -1
  11. package/dist/opportunity/discovery-question.helper.js.map +1 -1
  12. package/dist/opportunity/discriminator/discriminator.miner.d.ts +3 -1
  13. package/dist/opportunity/discriminator/discriminator.miner.d.ts.map +1 -1
  14. package/dist/opportunity/discriminator/discriminator.miner.js +48 -18
  15. package/dist/opportunity/discriminator/discriminator.miner.js.map +1 -1
  16. package/dist/opportunity/negotiation-summary.builder.d.ts +1 -1
  17. package/dist/opportunity/negotiation-summary.builder.d.ts.map +1 -1
  18. package/dist/opportunity/negotiation-summary.builder.js.map +1 -1
  19. package/dist/opportunity/opportunity.discover.js.map +1 -1
  20. package/dist/opportunity/opportunity.graph.d.ts +323 -15
  21. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  22. package/dist/opportunity/opportunity.state.d.ts +68 -2
  23. package/dist/opportunity/opportunity.state.d.ts.map +1 -1
  24. package/dist/opportunity/opportunity.state.js.map +1 -1
  25. package/dist/opportunity/outcome/outcome.env.d.ts +61 -0
  26. package/dist/opportunity/outcome/outcome.env.d.ts.map +1 -0
  27. package/dist/opportunity/outcome/outcome.env.js +64 -0
  28. package/dist/opportunity/outcome/outcome.env.js.map +1 -0
  29. package/dist/opportunity/outcome/outcome.hypotheses.d.ts +30 -0
  30. package/dist/opportunity/outcome/outcome.hypotheses.d.ts.map +1 -0
  31. package/dist/opportunity/outcome/outcome.hypotheses.js +118 -0
  32. package/dist/opportunity/outcome/outcome.hypotheses.js.map +1 -0
  33. package/dist/opportunity/outcome/outcome.shadow.d.ts +57 -0
  34. package/dist/opportunity/outcome/outcome.shadow.d.ts.map +1 -0
  35. package/dist/opportunity/outcome/outcome.shadow.js +88 -0
  36. package/dist/opportunity/outcome/outcome.shadow.js.map +1 -0
  37. package/dist/opportunity/outcome/outcome.types.d.ts +111 -0
  38. package/dist/opportunity/outcome/outcome.types.d.ts.map +1 -0
  39. package/dist/opportunity/outcome/outcome.types.js +27 -0
  40. package/dist/opportunity/outcome/outcome.types.js.map +1 -0
  41. package/dist/questioner/questioner.discovery.prompt.d.ts +9 -0
  42. package/dist/questioner/questioner.discovery.prompt.d.ts.map +1 -0
  43. package/dist/{opportunity/question.prompt.js → questioner/questioner.discovery.prompt.js} +6 -7
  44. package/dist/questioner/questioner.discovery.prompt.js.map +1 -0
  45. package/dist/questioner/questioner.presets.js +2 -2
  46. package/dist/questioner/questioner.presets.js.map +1 -1
  47. package/dist/questioner/questioner.types.d.ts +2 -2
  48. package/dist/questioner/questioner.types.d.ts.map +1 -1
  49. package/dist/questioner/questioner.types.js.map +1 -1
  50. package/dist/shared/agent/model.config.d.ts +0 -5
  51. package/dist/shared/agent/model.config.d.ts.map +1 -1
  52. package/dist/shared/agent/model.config.js +0 -1
  53. package/dist/shared/agent/model.config.js.map +1 -1
  54. package/dist/shared/interfaces/database.interface.d.ts +28 -2
  55. package/dist/shared/interfaces/database.interface.d.ts.map +1 -1
  56. package/dist/shared/interfaces/database.interface.js.map +1 -1
  57. package/dist/shared/interfaces/negotiation-summary.interface.d.ts +1 -1
  58. package/dist/shared/interfaces/negotiation-summary.interface.d.ts.map +1 -1
  59. package/dist/shared/interfaces/negotiation-summary.interface.js.map +1 -1
  60. package/dist/shared/interfaces/question-generator.interface.d.ts +7 -5
  61. package/dist/shared/interfaces/question-generator.interface.d.ts.map +1 -1
  62. package/dist/shared/interfaces/question-generator.interface.js.map +1 -1
  63. package/dist/shared/schemas/discovery-question.schema.d.ts +2 -2
  64. package/package.json +1 -1
  65. package/dist/opportunity/question.generator.d.ts +0 -22
  66. package/dist/opportunity/question.generator.d.ts.map +0 -1
  67. package/dist/opportunity/question.generator.js +0 -147
  68. package/dist/opportunity/question.generator.js.map +0 -1
  69. package/dist/opportunity/question.prompt.d.ts +0 -102
  70. package/dist/opportunity/question.prompt.d.ts.map +0 -1
  71. package/dist/opportunity/question.prompt.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"question-generator.interface.d.ts","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,QAAQ,CACN,KAAK,EAAE,sBAAsB,EAC7B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;CAC7C"}
1
+ {"version":3,"file":"question-generator.interface.d.ts","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,QAAQ,CACN,KAAK,EAAE,sBAAsB,EAC7B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;CAC7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"question-generator.interface.js","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @deprecated Use QuestionerAgent instead. Will be removed in a future version.\n *\n * Protocol-level read contract for decision-question generation. Implementations\n * live in the backend (see `QuestionGeneratorService`) and are injected into the\n * protocol via `ProtocolDeps`/`ToolContext`. The protocol module never constructs\n * its own LLM-bound `QuestionGenerator` — callers inject one (or `undefined` to\n * opt out).\n */\nimport type { DiscoveryQuestionInput } from \"../schemas/discovery-question.schema.js\";\nimport type { QuestionGenerationResult } from \"../schemas/question.schema.js\";\n\nexport interface QuestionGeneratorReader {\n /**\n * Run the question generator over a single discovery turn.\n *\n * @param input Discovery turn payload (query + negotiation digests + chat context).\n * @param options.signal Optional AbortSignal. When aborted (deadline reached or\n * upstream cancel) the in-flight LLM call is cancelled and `null` is returned —\n * discovery still emits its response, just without questions.\n * @returns The structured result, or `null` when generation failed,\n * guardrails dropped all candidates, the underlying LLM threw, or\n * the call was aborted.\n */\n generate(\n input: DiscoveryQuestionInput,\n options?: { signal?: AbortSignal },\n ): Promise<QuestionGenerationResult | null>;\n}\n"]}
1
+ {"version":3,"file":"question-generator.interface.js","sourceRoot":"/","sources":["shared/interfaces/question-generator.interface.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @deprecated Inline question generation is superseded by the async\n * QuestionerQueue path (`questionerEnqueue`). Will be removed once the inline\n * fallback in `opportunity.discover.ts` is retired.\n *\n * Protocol-level read contract for decision-question generation. Implementations\n * live in the backend (see `QuestionGeneratorService`, which delegates to the\n * QuestionerAgent's `discovery` mode) and are injected into the protocol via\n * `ProtocolDeps`/`ToolContext`. The protocol module never constructs its own\n * LLM-bound generator — callers inject one (or `undefined` to opt out).\n */\nimport type { DiscoveryQuestionInput } from \"../schemas/discovery-question.schema.js\";\nimport type { QuestionGenerationResult } from \"../schemas/question.schema.js\";\n\nexport interface QuestionGeneratorReader {\n /**\n * Run the question generator over a single discovery turn.\n *\n * @param input Discovery turn payload (query + negotiation digests + chat context).\n * @param options.signal Optional AbortSignal. When aborted (deadline reached or\n * upstream cancel) the in-flight LLM call is cancelled and `null` is returned —\n * discovery still emits its response, just without questions.\n * @returns The structured result, or `null` when generation failed,\n * guardrails dropped all candidates, the underlying LLM threw, or\n * the call was aborted.\n */\n generate(\n input: DiscoveryQuestionInput,\n options?: { signal?: AbortSignal },\n ): Promise<QuestionGenerationResult | null>;\n}\n"]}
@@ -146,6 +146,7 @@ export declare const DiscoveryNegotiationSchema: z.ZodObject<{
146
146
  };
147
147
  counterpartyHint: string;
148
148
  indexContext: string;
149
+ counterpartyId: string;
149
150
  turns: {
150
151
  reasoning: string;
151
152
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -154,7 +155,6 @@ export declare const DiscoveryNegotiationSchema: z.ZodObject<{
154
155
  otherUser: "agent" | "patient" | "peer";
155
156
  };
156
157
  }[];
157
- counterpartyId: string;
158
158
  seedAssessmentScore?: number | undefined;
159
159
  }, {
160
160
  outcome: {
@@ -168,6 +168,7 @@ export declare const DiscoveryNegotiationSchema: z.ZodObject<{
168
168
  };
169
169
  counterpartyHint: string;
170
170
  indexContext: string;
171
+ counterpartyId: string;
171
172
  turns: {
172
173
  reasoning: string;
173
174
  action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
@@ -176,7 +177,6 @@ export declare const DiscoveryNegotiationSchema: z.ZodObject<{
176
177
  otherUser: "agent" | "patient" | "peer";
177
178
  };
178
179
  }[];
179
- counterpartyId: string;
180
180
  seedAssessmentScore?: number | undefined;
181
181
  }>;
182
182
  export type DiscoveryNegotiation = z.infer<typeof DiscoveryNegotiationSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "5.0.0",
3
+ "version": "5.2.0-rc.368.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,22 +0,0 @@
1
- import { type QuestionGenerationResult } from "../shared/schemas/question.schema.js";
2
- import { type DiscoveryQuestionInput } from "./question.prompt.js";
3
- /** @deprecated Use QuestionerAgent instead. Will be removed in a future version. */
4
- export declare class QuestionGenerator {
5
- private model;
6
- constructor();
7
- /**
8
- * Generate up to 3 decision questions from the given discovery turn.
9
- *
10
- * @param input Discovery turn payload.
11
- * @param options.signal Optional AbortSignal. Passed to LangChain so an
12
- * aborted call cancels the underlying HTTP fetch instead of leaving a
13
- * floating promise burning OpenRouter capacity.
14
- * @returns A result with parallel questions[] and strategies[] arrays,
15
- * or null when the LLM fails, the output is malformed, the call was
16
- * aborted, or the guardrails leave zero questions standing.
17
- */
18
- generate(input: DiscoveryQuestionInput, options?: {
19
- signal?: AbortSignal;
20
- }): Promise<QuestionGenerationResult | null>;
21
- }
22
- //# sourceMappingURL=question.generator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"question.generator.d.ts","sourceRoot":"/","sources":["opportunity/question.generator.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAkD,KAAK,wBAAwB,EAAoD,MAAM,sCAAsC,CAAC;AAMvL,OAAO,EAAsC,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAOvG,oFAAoF;AACpF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA2C;;IAQxD;;;;;;;;;;OAUG;IAEG,QAAQ,CACZ,KAAK,EAAE,sBAAsB,EAC7B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC;CA6C5C"}
@@ -1,147 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- };
10
- /**
11
- * @deprecated Use QuestionerAgent instead. Will be removed in a future version.
12
- *
13
- * QuestionGenerator — pure LLM pass that turns a structured DiscoveryQuestionInput
14
- * into 0–3 decision questions. No DB, no events, no caller wired here; Slice 3
15
- * (opportunity.discover.ts) is the first consumer.
16
- *
17
- * Flow:
18
- * 1. buildQuestionPrompt(input) → user message string.
19
- * 2. model.invoke([system, user]) returns a structured payload.
20
- * 3. safeParse via QuestionGeneratorResponseSchema → null on failure.
21
- * 4. Guardrails: dedup by title, then strategy-diversity (never 3 same).
22
- * 5. If empty, return null. Otherwise split into public Question[] + parallel
23
- * QuestionStrategy[] (debug-only; strategy is NEVER on the public shape).
24
- */
25
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
26
- import { QuestionGeneratorResponseSchema } from "../shared/schemas/question.schema.js";
27
- import { QUD_UNDERSPECIFICATION_RULES } from "../questioner/questioner.qud.js";
28
- import { createStructuredModel } from "../shared/agent/model.config.js";
29
- import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
30
- import { protocolLogger } from "../shared/observability/protocol.logger.js";
31
- import { Timed } from "../shared/observability/performance.js";
32
- import { SYSTEM_PROMPT, buildQuestionPrompt } from "./question.prompt.js";
33
- const logger = protocolLogger("QuestionGenerator");
34
- /** Maximum same-strategy questions allowed in a single emission. */
35
- const MAX_SAME_STRATEGY = 2;
36
- /** @deprecated Use QuestionerAgent instead. Will be removed in a future version. */
37
- export class QuestionGenerator {
38
- constructor() {
39
- this.model = createStructuredModel("discoveryQuestionGenerator", QuestionGeneratorResponseSchema, {
40
- name: "clarifying_questions",
41
- });
42
- }
43
- /**
44
- * Generate up to 3 decision questions from the given discovery turn.
45
- *
46
- * @param input Discovery turn payload.
47
- * @param options.signal Optional AbortSignal. Passed to LangChain so an
48
- * aborted call cancels the underlying HTTP fetch instead of leaving a
49
- * floating promise burning OpenRouter capacity.
50
- * @returns A result with parallel questions[] and strategies[] arrays,
51
- * or null when the LLM fails, the output is malformed, the call was
52
- * aborted, or the guardrails leave zero questions standing.
53
- */
54
- async generate(input, options) {
55
- const user = buildQuestionPrompt(input);
56
- let raw;
57
- try {
58
- raw = await invokeWithAbortSignal(this.model, [
59
- new SystemMessage(`${SYSTEM_PROMPT}\n\n${QUD_UNDERSPECIFICATION_RULES}`),
60
- new HumanMessage(user),
61
- ], options?.signal);
62
- }
63
- catch (err) {
64
- // AbortError is the expected outcome under deadline pressure — caller
65
- // already wants to skip questions in that case, so log at info rather
66
- // than warn to keep dashboards clean.
67
- const aborted = options?.signal?.aborted ?? false;
68
- if (aborted) {
69
- logger.info("QuestionGenerator aborted by signal", {
70
- reason: options?.signal?.reason instanceof Error ? options.signal.reason.message : String(options?.signal?.reason ?? "unknown"),
71
- });
72
- }
73
- else {
74
- logger.warn("QuestionGenerator LLM call failed", {
75
- error: err instanceof Error ? err.message : String(err),
76
- });
77
- }
78
- return null;
79
- }
80
- const parsed = QuestionGeneratorResponseSchema.safeParse(raw);
81
- if (!parsed.success) {
82
- logger.warn("QuestionGenerator parse failed", { error: parsed.error.message });
83
- return null;
84
- }
85
- const filtered = applyGuardrails(parsed.data.questions);
86
- if (filtered.length === 0)
87
- return null;
88
- return {
89
- questions: filtered.map(stripInternalMetadata),
90
- strategies: filtered.map((q) => q.strategy),
91
- underspecificationTypes: filtered.map((q) => q.underspecificationType),
92
- };
93
- }
94
- }
95
- __decorate([
96
- Timed(),
97
- __metadata("design:type", Function),
98
- __metadata("design:paramtypes", [Object, Object]),
99
- __metadata("design:returntype", Promise)
100
- ], QuestionGenerator.prototype, "generate", null);
101
- /**
102
- * Guardrail pipeline. Order matters:
103
- * 1. Dedup by title (keep first occurrence). Title uniqueness is a hard UX
104
- * requirement for the renderer — two questions cannot share a chip label.
105
- * 2. Strategy diversity: cap same-strategy entries at MAX_SAME_STRATEGY,
106
- * dropping overflow unconditionally (does NOT depend on whether a
107
- * distinct-strategy alternative exists in the batch). The cap exists to
108
- * enforce the "never 3 of the same kind" guidance from the spec.
109
- */
110
- function applyGuardrails(questions) {
111
- const dedupedByTitle = dedupByTitle(questions);
112
- return enforceStrategyDiversity(dedupedByTitle);
113
- }
114
- function dedupByTitle(questions) {
115
- const seen = new Set();
116
- const out = [];
117
- for (const q of questions) {
118
- if (seen.has(q.title))
119
- continue;
120
- seen.add(q.title);
121
- out.push(q);
122
- }
123
- return out;
124
- }
125
- /**
126
- * Enforce the "never 3 same-strategy" rule. Walks the array in order; once a
127
- * given strategy has appeared MAX_SAME_STRATEGY times, subsequent entries with
128
- * the same strategy are dropped. Distinct-strategy entries are always kept
129
- * (subject to the schema's overall 3-question cap, which has already applied).
130
- */
131
- function enforceStrategyDiversity(questions) {
132
- const counts = new Map();
133
- const out = [];
134
- for (const q of questions) {
135
- const n = counts.get(q.strategy) ?? 0;
136
- if (n >= MAX_SAME_STRATEGY)
137
- continue;
138
- counts.set(q.strategy, n + 1);
139
- out.push(q);
140
- }
141
- return out;
142
- }
143
- function stripInternalMetadata(q) {
144
- const { strategy: _strategy, underspecificationType: _underspecificationType, ...publicShape } = q;
145
- return publicShape;
146
- }
147
- //# sourceMappingURL=question.generator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"question.generator.js","sourceRoot":"/","sources":["opportunity/question.generator.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAAE,+BAA+B,EAAkG,MAAM,sCAAsC,CAAC;AACvL,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAA+B,MAAM,sBAAsB,CAAC;AAEvG,MAAM,MAAM,GAAG,cAAc,CAAC,mBAAmB,CAAC,CAAC;AAEnD,oEAAoE;AACpE,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,oFAAoF;AACpF,MAAM,OAAO,iBAAiB;IAG5B;QACE,IAAI,CAAC,KAAK,GAAG,qBAAqB,CAAC,4BAA4B,EAAE,+BAA+B,EAAE;YAChG,IAAI,EAAE,sBAAsB;SAC7B,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;OAUG;IAEG,AAAN,KAAK,CAAC,QAAQ,CACZ,KAA6B,EAC7B,OAAkC;QAElC,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAExC,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,qBAAqB,CAC/B,IAAI,CAAC,KAAK,EACV;gBACE,IAAI,aAAa,CAAC,GAAG,aAAa,OAAO,4BAA4B,EAAE,CAAC;gBACxE,IAAI,YAAY,CAAC,IAAI,CAAC;aACvB,EACD,OAAO,EAAE,MAAM,CAChB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,sEAAsE;YACtE,sEAAsE;YACtE,sCAAsC;YACtC,MAAM,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,KAAK,CAAC;YAClD,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE;oBACjD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS,CAAC;iBAChI,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,mCAAmC,EAAE;oBAC/C,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACxD,CAAC,CAAC;YACL,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,+BAA+B,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/E,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEvC,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,CAAC;YAC9C,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC3C,uBAAuB,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC;SACvE,CAAC;IACJ,CAAC;CACF;AAhDO;IADL,KAAK,EAAE;;;;iDAgDP;AAGH;;;;;;;;GAQG;AACH,SAAS,eAAe,CAAC,SAAiC;IACxD,MAAM,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IAC/C,OAAO,wBAAwB,CAAC,cAAc,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,YAAY,CAAC,SAAiC;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;YAAE,SAAS;QAChC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,SAAS,wBAAwB,CAC/B,SAAiC;IAEjC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IACnD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,iBAAiB;YAAE,SAAS;QACrC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,qBAAqB,CAAC,CAAuB;IACpD,MAAM,EACJ,QAAQ,EAAE,SAAS,EACnB,sBAAsB,EAAE,uBAAuB,EAC/C,GAAG,WAAW,EACf,GAAG,CAAC,CAAC;IACN,OAAO,WAAW,CAAC;AACrB,CAAC","sourcesContent":["/**\n * @deprecated Use QuestionerAgent instead. Will be removed in a future version.\n *\n * QuestionGenerator — pure LLM pass that turns a structured DiscoveryQuestionInput\n * into 0–3 decision questions. No DB, no events, no caller wired here; Slice 3\n * (opportunity.discover.ts) is the first consumer.\n *\n * Flow:\n * 1. buildQuestionPrompt(input) → user message string.\n * 2. model.invoke([system, user]) returns a structured payload.\n * 3. safeParse via QuestionGeneratorResponseSchema → null on failure.\n * 4. Guardrails: dedup by title, then strategy-diversity (never 3 same).\n * 5. If empty, return null. Otherwise split into public Question[] + parallel\n * QuestionStrategy[] (debug-only; strategy is NEVER on the public shape).\n */\nimport { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\n\nimport { QuestionGeneratorResponseSchema, type Question, type QuestionGenerationResult, type QuestionStrategy, type QuestionWithStrategy } from \"../shared/schemas/question.schema.js\";\nimport { QUD_UNDERSPECIFICATION_RULES } from \"../questioner/questioner.qud.js\";\nimport { createStructuredModel } from \"../shared/agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../shared/agent/model-signal.js\";\nimport { protocolLogger } from \"../shared/observability/protocol.logger.js\";\nimport { Timed } from \"../shared/observability/performance.js\";\nimport { SYSTEM_PROMPT, buildQuestionPrompt, type DiscoveryQuestionInput } from \"./question.prompt.js\";\n\nconst logger = protocolLogger(\"QuestionGenerator\");\n\n/** Maximum same-strategy questions allowed in a single emission. */\nconst MAX_SAME_STRATEGY = 2;\n\n/** @deprecated Use QuestionerAgent instead. Will be removed in a future version. */\nexport class QuestionGenerator {\n private model: ReturnType<typeof createStructuredModel>;\n\n constructor() {\n this.model = createStructuredModel(\"discoveryQuestionGenerator\", QuestionGeneratorResponseSchema, {\n name: \"clarifying_questions\",\n });\n }\n\n /**\n * Generate up to 3 decision questions from the given discovery turn.\n *\n * @param input Discovery turn payload.\n * @param options.signal Optional AbortSignal. Passed to LangChain so an\n * aborted call cancels the underlying HTTP fetch instead of leaving a\n * floating promise burning OpenRouter capacity.\n * @returns A result with parallel questions[] and strategies[] arrays,\n * or null when the LLM fails, the output is malformed, the call was\n * aborted, or the guardrails leave zero questions standing.\n */\n @Timed()\n async generate(\n input: DiscoveryQuestionInput,\n options?: { signal?: AbortSignal },\n ): Promise<QuestionGenerationResult | null> {\n const user = buildQuestionPrompt(input);\n\n let raw: unknown;\n try {\n raw = await invokeWithAbortSignal(\n this.model,\n [\n new SystemMessage(`${SYSTEM_PROMPT}\\n\\n${QUD_UNDERSPECIFICATION_RULES}`),\n new HumanMessage(user),\n ],\n options?.signal,\n );\n } catch (err) {\n // AbortError is the expected outcome under deadline pressure — caller\n // already wants to skip questions in that case, so log at info rather\n // than warn to keep dashboards clean.\n const aborted = options?.signal?.aborted ?? false;\n if (aborted) {\n logger.info(\"QuestionGenerator aborted by signal\", {\n reason: options?.signal?.reason instanceof Error ? options.signal.reason.message : String(options?.signal?.reason ?? \"unknown\"),\n });\n } else {\n logger.warn(\"QuestionGenerator LLM call failed\", {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return null;\n }\n\n const parsed = QuestionGeneratorResponseSchema.safeParse(raw);\n if (!parsed.success) {\n logger.warn(\"QuestionGenerator parse failed\", { error: parsed.error.message });\n return null;\n }\n\n const filtered = applyGuardrails(parsed.data.questions);\n if (filtered.length === 0) return null;\n\n return {\n questions: filtered.map(stripInternalMetadata),\n strategies: filtered.map((q) => q.strategy),\n underspecificationTypes: filtered.map((q) => q.underspecificationType),\n };\n }\n}\n\n/**\n * Guardrail pipeline. Order matters:\n * 1. Dedup by title (keep first occurrence). Title uniqueness is a hard UX\n * requirement for the renderer — two questions cannot share a chip label.\n * 2. Strategy diversity: cap same-strategy entries at MAX_SAME_STRATEGY,\n * dropping overflow unconditionally (does NOT depend on whether a\n * distinct-strategy alternative exists in the batch). The cap exists to\n * enforce the \"never 3 of the same kind\" guidance from the spec.\n */\nfunction applyGuardrails(questions: QuestionWithStrategy[]): QuestionWithStrategy[] {\n const dedupedByTitle = dedupByTitle(questions);\n return enforceStrategyDiversity(dedupedByTitle);\n}\n\nfunction dedupByTitle(questions: QuestionWithStrategy[]): QuestionWithStrategy[] {\n const seen = new Set<string>();\n const out: QuestionWithStrategy[] = [];\n for (const q of questions) {\n if (seen.has(q.title)) continue;\n seen.add(q.title);\n out.push(q);\n }\n return out;\n}\n\n/**\n * Enforce the \"never 3 same-strategy\" rule. Walks the array in order; once a\n * given strategy has appeared MAX_SAME_STRATEGY times, subsequent entries with\n * the same strategy are dropped. Distinct-strategy entries are always kept\n * (subject to the schema's overall 3-question cap, which has already applied).\n */\nfunction enforceStrategyDiversity(\n questions: QuestionWithStrategy[],\n): QuestionWithStrategy[] {\n const counts = new Map<QuestionStrategy, number>();\n const out: QuestionWithStrategy[] = [];\n for (const q of questions) {\n const n = counts.get(q.strategy) ?? 0;\n if (n >= MAX_SAME_STRATEGY) continue;\n counts.set(q.strategy, n + 1);\n out.push(q);\n }\n return out;\n}\n\nfunction stripInternalMetadata(q: QuestionWithStrategy): Question {\n const {\n strategy: _strategy,\n underspecificationType: _underspecificationType,\n ...publicShape\n } = q;\n return publicShape;\n}\n"]}
@@ -1,102 +0,0 @@
1
- /**
2
- * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.
3
- *
4
- * Prompt module for the decision-question generator: the system prompt
5
- * constant, the `DiscoveryQuestionInput` contract, and a pure string-building
6
- * `buildQuestionPrompt` that assembles the user message.
7
- *
8
- * Pure: no I/O, no LLM call. The generator class (`question.generator.ts`)
9
- * orchestrates this module + an LLM client.
10
- */
11
- import type { ChatContextDigest } from "../shared/schemas/chat-context.schema.js";
12
- import type { DiscoveryNegotiationDigest } from "../shared/schemas/negotiation-digest.schema.js";
13
- /** Roles used in the existing negotiation framework. */
14
- export type NegotiationRole = "agent" | "patient" | "peer";
15
- /** One turn within a negotiation. */
16
- export interface DiscoveryTurn {
17
- action: "propose" | "accept" | "reject" | "counter" | "question" | "outreach" | "withdraw" | "decline" | "ask_user";
18
- reasoning: string;
19
- suggestedRoles: {
20
- ownUser: NegotiationRole;
21
- otherUser: NegotiationRole;
22
- };
23
- }
24
- /** Outcome of a negotiation. */
25
- export interface DiscoveryOutcome {
26
- hasOpportunity: boolean;
27
- reasoning: string;
28
- agreedRoles?: Array<{
29
- userId: string;
30
- role: NegotiationRole;
31
- }>;
32
- /** Why the negotiation stopped, when not by an explicit accept/reject. */
33
- reason?: "turn_cap" | "timeout" | "screened_out";
34
- }
35
- /** One negotiation that ran during this discovery turn. */
36
- export interface DiscoveryNegotiation {
37
- /** Opaque counterparty identifier; NEVER surfaced to the user (kept out of the prompt). */
38
- counterpartyId: string;
39
- /** Abstract profile slice for the LLM (e.g. "AI infra founder, Berlin"). */
40
- counterpartyHint: string;
41
- /** The network/community prompt this negotiation ran under. */
42
- indexContext: string;
43
- /** Last 6 turns are retained; earlier ones are dropped. */
44
- turns: DiscoveryTurn[];
45
- outcome: DiscoveryOutcome;
46
- /**
47
- * Optional pre-negotiation evaluator score (0..1). When more than
48
- * `MAX_NEGOTIATIONS` candidates exist, this is used as a tie-breaker after
49
- * `turns.length` to decide which to keep.
50
- */
51
- seedAssessmentScore?: number;
52
- }
53
- /** Aggregate counters across all negotiations in this discovery turn. */
54
- export interface DiscoverySummary {
55
- totalCandidates: number;
56
- opportunitiesFound: number;
57
- noOpportunityCount: number;
58
- /** Subset of `noOpportunityCount` where the negotiation hit a turn-cap or timeout. */
59
- timeoutCount: number;
60
- /** Map of role → count across all outcomes' `agreedRoles`. */
61
- roleDistribution: Partial<Record<NegotiationRole, number>>;
62
- }
63
- /**
64
- * The seeker's profile slice the generator used to see. Retained as an exported
65
- * type for backward compatibility; the question prompt now consumes the global
66
- * `userContext` paragraph instead of these discrete fields.
67
- */
68
- export interface DiscoverySourceProfile {
69
- name?: string;
70
- bio?: string;
71
- location?: string;
72
- skills?: string[];
73
- interests?: string[];
74
- }
75
- /** Full input to the question generator. */
76
- export interface DiscoveryQuestionInput {
77
- /** The seeker's original natural-language query / signal that triggered discovery. */
78
- query: string;
79
- /** The seeker's global user_context paragraph (profile-replacing identity text). */
80
- userContext: string;
81
- /**
82
- * Compact per-negotiation digests from THIS discovery turn. Each digest is a
83
- * fixed-size structured summary (counterparty hint, index, outcome role,
84
- * keyTake) — pre-summarized so this prompt stays small regardless of how
85
- * many candidates were negotiated. Raw negotiations are NOT passed here.
86
- */
87
- negotiationDigests: DiscoveryNegotiationDigest[];
88
- summary: DiscoverySummary;
89
- /** Distilled chat-session digest, when a session is in scope. */
90
- chatContext?: ChatContextDigest;
91
- /** ISO timestamp used as the "now" anchor in the prompt. */
92
- now: string;
93
- }
94
- /** @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version. */
95
- export declare const SYSTEM_PROMPT = "You help write user-facing follow-up questions after Index has reviewed potential connections for a human. Your job: surface the minimum set of structured decision questions the human must answer to make the next discovery turn sharper, or improve their outlook on the intent.\n\nYou may pick from five strategies. Choose contextually; mix when multiple questions genuinely complement.\n- refine_intent: ask the user to sharpen or pivot their original signal.\n- surface_missing_detail: ask for one concrete missing input (stage, location, timing, scope, \u2026).\n- open_adjacent_thread: offer a pivot suggested by recurring connection signals.\n- reflective_summary: mirror what the connection review revealed and ask the user to decide.\n- surface_emergent_knowledge: cite a fact you learned from the connection review and ask the user to decide in light of it.\n\nAsk a question only when ALL of these hold:\n1. Index cannot resolve the decision autonomously from the evidence shown.\n2. The answer would materially change which people surface next.\n3. The same fact is NOT already in chatContext.statedFacts, NOT already asked in chatContext.openQuestions, and NOT already shared in chatContext.surfacedFindings.\n\nStandalone prompt rule. Every generated `prompt` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on `title`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer \"For your AI crypto decentralized deep-tech search, which area is most critical right now?\" over \"Which area is most critical right now?\"\n\nReferential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text \u2014 never the people you reviewed, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: \"these builders\", \"those founders\", \"these researchers\", \"these conversations\", \"this lunch\", \"the speaker\". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself (\"founders working on decentralized identity\"), never \"these founders\". Never imply a list, set, or prior exchange the reader is not currently looking at.\n- Bad: \"What kind of collaboration are you looking for with these builders?\"\n- Good: \"You're meeting people building agent infrastructure \u2014 what kind of collaboration are you looking for?\"\n\nNo process narration. Never describe Index's own activity or internal state. Forbidden: \"the previous negotiation\", \"the negotiation stalled\", \"opportunities found so far\", \"my search\", \"the counterparty\", \"candidates reviewed\", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.\n- Bad: \"All opportunities found so far are related to 'Edge Esmeralda'. Would you like to broaden the search?\"\n- Good: \"Do you want to focus on people at Edge Esmeralda, or also connect beyond it?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are \u22653 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).\n\nOrdering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification \u2014 prioritize.\n\nUser-facing language. Every title, prompt, option label, and option description is shown directly to the user. Never mention raw protocol mechanics or internal labels such as \"agent\", \"patient\", \"peer\", \"suggestedRoles\", \"role distribution\", \"counterparty\", \"negotiation\", \"turn_cap\", \"timeout\", or \"candidate\". Use natural language instead: people, matches, connections, mutual collaboration, someone who can help, or someone seeking help.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2\u20134 options. Never add an \"Other\" option \u2014 clients provide a free-text fallback automatically. For surface_emergent_knowledge questions, anchor the prompt in the concrete cited fact (\"Multiple people flagged that\u2026\") and let the options represent decisions in light of that fact, not different versions of the fact.\n\nTitle rules. \u226412 chars. Noun of the decision domain. Discovery examples: \"Stage\", \"Timing\", \"Role\", \"Location\", \"Stack\", \"Budget\", \"Scope\", \"Format\".\n\nAnti-patterns \u2014 never do these.\n- Don't ask procedural confirmations (\"Should I look again?\").\n- Don't ask about hypothetical edge cases that didn't occur.\n- Don't ask about specific person identities; treat the provided person summary as the only allowed reference.\n- Don't repeat anything in chatContext.openQuestions.\n- Don't re-surface anything in chatContext.surfacedFindings.\n- Don't ask for facts in chatContext.statedFacts.\n\nOutput. Return at most 3 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the five values). If nothing is worth asking, return \"questions\": [].";
96
- /**
97
- * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.
98
- *
99
- * Pure builder: assembles the user message string from a structured input.
100
- */
101
- export declare function buildQuestionPrompt(input: DiscoveryQuestionInput): string;
102
- //# sourceMappingURL=question.prompt.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"question.prompt.d.ts","sourceRoot":"/","sources":["opportunity/question.prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAEjG,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;IACpH,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE;QAAE,OAAO,EAAE,eAAe,CAAC;QAAC,SAAS,EAAE,eAAe,CAAA;KAAE,CAAC;CAC1E;AAED,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,CAAA;KAAE,CAAC,CAAC;IAC/D,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,cAAc,CAAC;CAClD;AAED,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,2FAA2F;IAC3F,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5D;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,sBAAsB;IACrC,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,kBAAkB,EAAE,0BAA0B,EAAE,CAAC;IACjD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2GAA2G;AAC3G,eAAO,MAAM,aAAa,yyLA0C0J,CAAC;AAErL;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAmCzE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"question.prompt.js","sourceRoot":"/","sources":["opportunity/question.prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.\n *\n * Prompt module for the decision-question generator: the system prompt\n * constant, the `DiscoveryQuestionInput` contract, and a pure string-building\n * `buildQuestionPrompt` that assembles the user message.\n *\n * Pure: no I/O, no LLM call. The generator class (`question.generator.ts`)\n * orchestrates this module + an LLM client.\n */\nimport type { ChatContextDigest } from \"../shared/schemas/chat-context.schema.js\";\nimport type { DiscoveryNegotiationDigest } from \"../shared/schemas/negotiation-digest.schema.js\";\n\n/** Roles used in the existing negotiation framework. */\nexport type NegotiationRole = \"agent\" | \"patient\" | \"peer\";\n\n/** One turn within a negotiation. */\nexport interface DiscoveryTurn {\n action: \"propose\" | \"accept\" | \"reject\" | \"counter\" | \"question\" | \"outreach\" | \"withdraw\" | \"decline\" | \"ask_user\";\n reasoning: string;\n suggestedRoles: { ownUser: NegotiationRole; otherUser: NegotiationRole };\n}\n\n/** Outcome of a negotiation. */\nexport interface DiscoveryOutcome {\n hasOpportunity: boolean;\n reasoning: string;\n agreedRoles?: Array<{ userId: string; role: NegotiationRole }>;\n /** Why the negotiation stopped, when not by an explicit accept/reject. */\n reason?: \"turn_cap\" | \"timeout\" | \"screened_out\";\n}\n\n/** One negotiation that ran during this discovery turn. */\nexport interface DiscoveryNegotiation {\n /** Opaque counterparty identifier; NEVER surfaced to the user (kept out of the prompt). */\n counterpartyId: string;\n /** Abstract profile slice for the LLM (e.g. \"AI infra founder, Berlin\"). */\n counterpartyHint: string;\n /** The network/community prompt this negotiation ran under. */\n indexContext: string;\n /** Last 6 turns are retained; earlier ones are dropped. */\n turns: DiscoveryTurn[];\n outcome: DiscoveryOutcome;\n /**\n * Optional pre-negotiation evaluator score (0..1). When more than\n * `MAX_NEGOTIATIONS` candidates exist, this is used as a tie-breaker after\n * `turns.length` to decide which to keep.\n */\n seedAssessmentScore?: number;\n}\n\n/** Aggregate counters across all negotiations in this discovery turn. */\nexport interface DiscoverySummary {\n totalCandidates: number;\n opportunitiesFound: number;\n noOpportunityCount: number;\n /** Subset of `noOpportunityCount` where the negotiation hit a turn-cap or timeout. */\n timeoutCount: number;\n /** Map of role → count across all outcomes' `agreedRoles`. */\n roleDistribution: Partial<Record<NegotiationRole, number>>;\n}\n\n/**\n * The seeker's profile slice the generator used to see. Retained as an exported\n * type for backward compatibility; the question prompt now consumes the global\n * `userContext` paragraph instead of these discrete fields.\n */\nexport interface DiscoverySourceProfile {\n name?: string;\n bio?: string;\n location?: string;\n skills?: string[];\n interests?: string[];\n}\n\n/** Full input to the question generator. */\nexport interface DiscoveryQuestionInput {\n /** The seeker's original natural-language query / signal that triggered discovery. */\n query: string;\n /** The seeker's global user_context paragraph (profile-replacing identity text). */\n userContext: string;\n /**\n * Compact per-negotiation digests from THIS discovery turn. Each digest is a\n * fixed-size structured summary (counterparty hint, index, outcome role,\n * keyTake) — pre-summarized so this prompt stays small regardless of how\n * many candidates were negotiated. Raw negotiations are NOT passed here.\n */\n negotiationDigests: DiscoveryNegotiationDigest[];\n summary: DiscoverySummary;\n /** Distilled chat-session digest, when a session is in scope. */\n chatContext?: ChatContextDigest;\n /** ISO timestamp used as the \"now\" anchor in the prompt. */\n now: string;\n}\n\n/** @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version. */\nexport const SYSTEM_PROMPT = `You help write user-facing follow-up questions after Index has reviewed potential connections for a human. Your job: surface the minimum set of structured decision questions the human must answer to make the next discovery turn sharper, or improve their outlook on the intent.\n\nYou may pick from five strategies. Choose contextually; mix when multiple questions genuinely complement.\n- refine_intent: ask the user to sharpen or pivot their original signal.\n- surface_missing_detail: ask for one concrete missing input (stage, location, timing, scope, …).\n- open_adjacent_thread: offer a pivot suggested by recurring connection signals.\n- reflective_summary: mirror what the connection review revealed and ask the user to decide.\n- surface_emergent_knowledge: cite a fact you learned from the connection review and ask the user to decide in light of it.\n\nAsk a question only when ALL of these hold:\n1. Index cannot resolve the decision autonomously from the evidence shown.\n2. The answer would materially change which people surface next.\n3. The same fact is NOT already in chatContext.statedFacts, NOT already asked in chatContext.openQuestions, and NOT already shared in chatContext.surfacedFindings.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer \"For your AI crypto decentralized deep-tech search, which area is most critical right now?\" over \"Which area is most critical right now?\"\n\nReferential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text — never the people you reviewed, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: \"these builders\", \"those founders\", \"these researchers\", \"these conversations\", \"this lunch\", \"the speaker\". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself (\"founders working on decentralized identity\"), never \"these founders\". Never imply a list, set, or prior exchange the reader is not currently looking at.\n- Bad: \"What kind of collaboration are you looking for with these builders?\"\n- Good: \"You're meeting people building agent infrastructure — what kind of collaboration are you looking for?\"\n\nNo process narration. Never describe Index's own activity or internal state. Forbidden: \"the previous negotiation\", \"the negotiation stalled\", \"opportunities found so far\", \"my search\", \"the counterparty\", \"candidates reviewed\", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.\n- Bad: \"All opportunities found so far are related to 'Edge Esmeralda'. Would you like to broaden the search?\"\n- Good: \"Do you want to focus on people at Edge Esmeralda, or also connect beyond it?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are ≥3 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).\n\nOrdering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification — prioritize.\n\nUser-facing language. Every title, prompt, option label, and option description is shown directly to the user. Never mention raw protocol mechanics or internal labels such as \"agent\", \"patient\", \"peer\", \"suggestedRoles\", \"role distribution\", \"counterparty\", \"negotiation\", \"turn_cap\", \"timeout\", or \"candidate\". Use natural language instead: people, matches, connections, mutual collaboration, someone who can help, or someone seeking help.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically. For surface_emergent_knowledge questions, anchor the prompt in the concrete cited fact (\"Multiple people flagged that…\") and let the options represent decisions in light of that fact, not different versions of the fact.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Discovery examples: \"Stage\", \"Timing\", \"Role\", \"Location\", \"Stack\", \"Budget\", \"Scope\", \"Format\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I look again?\").\n- Don't ask about hypothetical edge cases that didn't occur.\n- Don't ask about specific person identities; treat the provided person summary as the only allowed reference.\n- Don't repeat anything in chatContext.openQuestions.\n- Don't re-surface anything in chatContext.surfacedFindings.\n- Don't ask for facts in chatContext.statedFacts.\n\nOutput. Return at most 3 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the five values). If nothing is worth asking, return \"questions\": [].`;\n\n/**\n * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.\n *\n * Pure builder: assembles the user message string from a structured input.\n */\nexport function buildQuestionPrompt(input: DiscoveryQuestionInput): string {\n const profileSummary = input.userContext?.trim() || \"(no profile data)\";\n const connectionReviewBlocks = renderConnectionReviewDigests(input.negotiationDigests);\n const chatContextBlock = input.chatContext\n ? renderDigest(input.chatContext)\n : \"(no chat context available)\";\n const engagementPattern = renderEngagementPattern(input.summary.roleDistribution);\n\n return [\n \"## Seeker's query\",\n input.query,\n \"\",\n \"## Seeker profile\",\n profileSummary,\n \"\",\n \"## This discovery turn\",\n `- ${input.summary.totalCandidates} people reviewed`,\n `- ${input.summary.opportunitiesFound} promising connections found`,\n `- ${input.summary.noOpportunityCount} reviews did not find enough fit (${input.summary.timeoutCount} needed more detail or time)`,\n `- Engagement pattern: ${engagementPattern}`,\n \"\",\n \"## Connection review evidence (compact digests)\",\n connectionReviewBlocks,\n \"\",\n \"## What the user has already said in this session\",\n chatContextBlock,\n \"\",\n \"## Now\",\n input.now,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of decision questions the seeker must answer to make\",\n \"the next discovery turn sharper. Apply every rule from your system prompt\",\n \"before outputting. Return an empty `questions` array if nothing is worth asking.\",\n ].join(\"\\n\");\n}\n\n/**\n * Render the negotiation-digest collection into compact one-liners. Each digest\n * is fixed-size (≤ ~400 chars after rendering), so the rendered block scales\n * linearly with candidate count: 10 candidates ≈ 4 KB, well within budget.\n */\nfunction renderConnectionReviewDigests(digests: DiscoveryNegotiationDigest[]): string {\n if (digests.length === 0) return \"(no connection reviews)\";\n return digests\n .map((d) => {\n const relationshipSignal = d.suggestedRoles\n ? [` Relationship signal: ${renderRelationshipSignal(d.suggestedRoles)}`]\n : [];\n return [\n `- Person: ${d.counterpartyHint}`,\n ` Community context: ${d.indexContext}`,\n ` Outcome: ${renderOutcome(d)}`,\n ...relationshipSignal,\n ` Take: ${d.keyTake}`,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\");\n}\n\nfunction renderEngagementPattern(dist: Partial<Record<NegotiationRole, number>>): string {\n const parts: string[] = [];\n if ((dist.peer ?? 0) > 0) {\n parts.push(`${dist.peer} mutual collaboration${dist.peer === 1 ? \"\" : \"s\"}`);\n }\n if ((dist.agent ?? 0) > 0) {\n parts.push(`${dist.agent} where the user could offer help or expertise`);\n }\n if ((dist.patient ?? 0) > 0) {\n parts.push(`${dist.patient} where the user seemed to be seeking help or expertise`);\n }\n return parts.length > 0 ? parts.join(\", \") : \"(no engagement pattern available)\";\n}\n\nfunction renderOutcome(digest: DiscoveryNegotiationDigest): string {\n const outcome = digest.outcomeRole === \"opportunity\"\n ? \"promising connection\"\n : \"not enough fit\";\n const reason = renderOutcomeReason(digest.outcomeReason);\n return reason ? `${outcome} (${reason})` : outcome;\n}\n\nfunction renderOutcomeReason(reason: DiscoveryNegotiationDigest[\"outcomeReason\"]): string {\n switch (reason) {\n case \"turn_cap\":\n return \"needed more detail\";\n case \"timeout\":\n return \"ran out of time\";\n case \"rejected\":\n return \"not enough mutual interest\";\n case \"stalled\":\n return \"stalled\";\n case \"screened_out\":\n return \"didn't look like a strong enough fit to pursue\";\n case null:\n return \"\";\n }\n}\n\nfunction renderRelationshipSignal(roles: NonNullable<DiscoveryNegotiationDigest[\"suggestedRoles\"]>): string {\n if (roles.ownUser === \"peer\" && roles.otherUser === \"peer\") return \"mutual collaboration\";\n if (roles.ownUser === \"agent\" && roles.otherUser === \"patient\") {\n return \"the user could offer help or expertise\";\n }\n if (roles.ownUser === \"patient\" && roles.otherUser === \"agent\") {\n return \"the user seemed to be seeking help or expertise\";\n }\n if (roles.ownUser === \"agent\") return \"the user could offer help or expertise\";\n if (roles.ownUser === \"patient\") return \"the user seemed to be seeking help or expertise\";\n return \"collaboration fit\";\n}\n\nfunction renderDigest(d: ChatContextDigest): string {\n const lines: string[] = [];\n if (d.statedFacts.length > 0) {\n lines.push(\"Stated facts:\");\n for (const f of d.statedFacts) lines.push(` - ${f}`);\n }\n if (d.openQuestions.length > 0) {\n lines.push(\"Open questions (assistant already asked):\");\n for (const q of d.openQuestions) lines.push(` - ${q}`);\n }\n if (d.rejectionReasons.length > 0) {\n lines.push(\"User pushback / rejections:\");\n for (const r of d.rejectionReasons) lines.push(` - ${r}`);\n }\n if (d.surfacedFindings.length > 0) {\n lines.push(\"Findings already surfaced to user:\");\n for (const f of d.surfacedFindings) lines.push(` - ${f}`);\n }\n return lines.length > 0 ? lines.join(\"\\n\") : \"(digest is empty)\";\n}\n\n"]}