@indexnetwork/protocol 21.1.0-rc.493.1 → 22.0.0-rc.495.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.
- package/CHANGELOG.md +26 -0
- package/dist/chat/chat-streaming.types.d.ts +2 -23
- package/dist/chat/chat-streaming.types.js +0 -3
- package/dist/chat/chat.agent.d.ts +0 -5
- package/dist/chat/chat.streamer.js +3 -6
- package/dist/chat/negotiator.persona.d.ts +1 -1
- package/dist/chat/negotiator.persona.js +0 -7
- package/dist/chat/negotiator.prompt.js +10 -12
- package/dist/chat/onboarding.persona.d.ts +1 -1
- package/dist/chat/onboarding.persona.js +0 -1
- package/dist/chat/onboarding.prompt.js +2 -2
- package/dist/chat/signal.persona.d.ts +1 -1
- package/dist/chat/signal.persona.js +1 -2
- package/dist/chat/signal.prompt.d.ts +6 -5
- package/dist/chat/signal.prompt.js +17 -37
- package/dist/index.d.ts +3 -7
- package/dist/index.js +2 -4
- package/dist/intents/graph/intent.graph.d.ts +1 -2
- package/dist/intents/graph/intent.graph.execute.js +0 -15
- package/dist/intents/graph/intent.graph.js +1 -2
- package/dist/intents/graph/intent.graph.shared.d.ts +0 -2
- package/dist/intents/intent.module.d.ts +0 -3
- package/dist/intents/intent.module.js +2 -2
- package/dist/mcp/mcp.authorization-policy.d.ts +2 -2
- package/dist/mcp/mcp.authorization-policy.js +0 -24
- package/dist/negotiations/negotiation.agent.js +10 -1
- package/dist/negotiations/negotiation.graph.turn.d.ts +2 -0
- package/dist/negotiations/negotiation.graph.turn.js +9 -0
- package/dist/negotiations/negotiation.module.d.ts +1 -1
- package/dist/negotiations/negotiation.module.js +1 -1
- package/dist/negotiations/negotiation.question-safety.d.ts +0 -1
- package/dist/negotiations/negotiation.question-safety.js +0 -1
- package/dist/negotiations/negotiation.tools.js +83 -31
- package/dist/opportunities/opportunity.graph.d.ts +1 -1
- package/dist/opportunities/opportunity.tools.cards.d.ts +0 -13
- package/dist/opportunities/opportunity.tools.cards.js +0 -22
- package/dist/opportunities/opportunity.tools.js +4 -65
- package/dist/opportunities/opportunity.tools.port.d.ts +1 -1
- package/dist/questions/question.env.d.ts +5 -35
- package/dist/questions/question.env.js +5 -70
- package/dist/questions/question.input.d.ts +36 -145
- package/dist/questions/question.input.js +1 -56
- package/dist/questions/question.module.d.ts +10 -22
- package/dist/questions/question.module.js +9 -19
- package/dist/shared/agent/activity-projection.d.ts +2 -2
- package/dist/shared/agent/tool.factory.js +3 -15
- package/dist/shared/agent/tool.helpers.d.ts +0 -73
- package/dist/shared/agent/tool.registry.js +0 -2
- package/dist/shared/agent/tool.runtime.d.ts +1 -1
- package/dist/shared/agent/tool.runtime.js +3 -14
- package/dist/shared/observability/request-context.d.ts +0 -5
- package/package.json +1 -1
- package/dist/opportunities/opportunity.pending-questions.d.ts +0 -33
- package/dist/opportunities/opportunity.pending-questions.js +0 -42
- package/dist/questions/question.agent.d.ts +0 -28
- package/dist/questions/question.agent.js +0 -136
- package/dist/questions/question.ask.tool.d.ts +0 -12
- package/dist/questions/question.ask.tool.js +0 -297
- package/dist/questions/question.persistence.port.d.ts +0 -84
- package/dist/questions/question.persistence.port.js +0 -1
- package/dist/questions/question.presets.d.ts +0 -21
- package/dist/questions/question.presets.js +0 -388
- package/dist/questions/question.tools.d.ts +0 -15
- package/dist/questions/question.tools.js +0 -216
- package/dist/questions/question.tools.port.d.ts +0 -12
- package/dist/questions/question.tools.port.js +0 -1
- package/dist/shared/schemas/pending-question.schema.d.ts +0 -28
- package/dist/shared/schemas/pending-question.schema.js +0 -1
|
@@ -20,11 +20,8 @@ import type { AgentDatabase } from "../../agents/agent.repository.port.js";
|
|
|
20
20
|
import type { NegotiationTimeoutQueue } from "../interfaces/negotiation-events.interface.js";
|
|
21
21
|
import type { AgentDispatcher } from "../interfaces/agent-dispatcher.interface.js";
|
|
22
22
|
import type { DeliveryLedger } from "../interfaces/delivery-ledger.interface.js";
|
|
23
|
-
import type { ChatQuestionsHost, QuestionerDatabase } from "../../questions/question.persistence.port.js";
|
|
24
23
|
import type { NegotiatorMemoryToolsHost } from "../interfaces/negotiator-memory.interface.js";
|
|
25
24
|
import type { QuestionerEnqueueFn } from "../../questions/question.input.js";
|
|
26
|
-
import type { PendingQuestionSummary } from "../schemas/pending-question.schema.js";
|
|
27
|
-
import type { QuestionMode, QuestionPurpose } from "../../questions/question.schema.js";
|
|
28
25
|
import type { EnrichmentRunQueue, EnrichmentRunStore } from "../interfaces/enrichment-run.interface.js";
|
|
29
26
|
import type { McpActivityCaller } from "./activity-projection.js";
|
|
30
27
|
export type IdentityContext = UserIdentity | null;
|
|
@@ -147,39 +144,8 @@ interface ToolContextBindings {
|
|
|
147
144
|
* Injected by the composition root when QUESTIONER_ENABLED=true.
|
|
148
145
|
*/
|
|
149
146
|
questionerEnqueue?: QuestionerEnqueueFn;
|
|
150
|
-
/**
|
|
151
|
-
* Lookup pending questions for a user, optionally filtered by source,
|
|
152
|
-
* detection mode, selected intent scope, or capped by count.
|
|
153
|
-
*/
|
|
154
|
-
findPendingQuestions?: (userId: string, filters?: {
|
|
155
|
-
sourceType?: string;
|
|
156
|
-
sourceId?: string;
|
|
157
|
-
scopeType?: 'intent';
|
|
158
|
-
scopeId?: string;
|
|
159
|
-
networkId?: string;
|
|
160
|
-
modes?: QuestionMode[];
|
|
161
|
-
purpose?: QuestionPurpose;
|
|
162
|
-
limit?: number;
|
|
163
|
-
}) => Promise<PendingQuestionSummary[]>;
|
|
164
|
-
/**
|
|
165
|
-
* Record the client's explicit answer to a pending question through the
|
|
166
|
-
* host's question-answer pipeline (atomic pending→answered flip + answered
|
|
167
|
-
* events). Returns false when the question is not pending for this user
|
|
168
|
-
* (already answered/dismissed, expired, or not theirs). Injected by the
|
|
169
|
-
* composition root — absent when question delivery is disabled.
|
|
170
|
-
*/
|
|
171
|
-
answerPendingQuestion?: (userId: string, questionId: string, answer: {
|
|
172
|
-
selectedOptions: string[];
|
|
173
|
-
freeText?: string;
|
|
174
|
-
}) => Promise<boolean>;
|
|
175
147
|
/** Negotiation-digest summarizer. Optional; consumers fall back to deterministic digests. */
|
|
176
148
|
negotiationSummary?: NegotiationSummaryReader;
|
|
177
|
-
/**
|
|
178
|
-
* Host bridge for the orchestrator's blocking `ask_user_question` tool
|
|
179
|
-
* (synchronous chat-question persist + in-stream answer wait). Injected by
|
|
180
|
-
* the backend composition root; when absent the tool is not registered.
|
|
181
|
-
*/
|
|
182
|
-
chatQuestions?: ChatQuestionsHost;
|
|
183
149
|
/** Durable host persistence for verified intent proposals shown in chat. */
|
|
184
150
|
intentProposalStore?: import('../../intents/intent.proposal.js').IntentProposalStore;
|
|
185
151
|
/**
|
|
@@ -228,8 +194,6 @@ interface ToolContextBindings {
|
|
|
228
194
|
frontendUrl?: string;
|
|
229
195
|
/** API base URL for building opportunity accept links (e.g. https://protocol.index.network, optional). */
|
|
230
196
|
apiBaseUrl?: string;
|
|
231
|
-
/** Persistence for structured questions generated by the QuestionerAgent (optional). */
|
|
232
|
-
questionerDatabase?: QuestionerDatabase;
|
|
233
197
|
/** Optional host-side error reporter for swallowed protocol/tool errors. */
|
|
234
198
|
reportToolError?: (error: unknown, report: ToolErrorReport) => void;
|
|
235
199
|
/**
|
|
@@ -366,45 +330,8 @@ interface ToolDepsBindings {
|
|
|
366
330
|
* composition root when QUESTIONER_ENABLED=true.
|
|
367
331
|
*/
|
|
368
332
|
questionerEnqueue?: QuestionerEnqueueFn;
|
|
369
|
-
/**
|
|
370
|
-
* Lookup pending questions for a user, optionally filtered by source,
|
|
371
|
-
* detection mode, or capped by count (hosts apply `limit` SQL-side).
|
|
372
|
-
* Used by tools to attach contextually relevant questions to their results.
|
|
373
|
-
* Injected by the composition root — absent when question delivery is disabled.
|
|
374
|
-
*/
|
|
375
|
-
findPendingQuestions?: (userId: string, filters?: {
|
|
376
|
-
sourceType?: string;
|
|
377
|
-
sourceId?: string;
|
|
378
|
-
/** Optional selected-intent scope. When `scopeType === 'intent'`, `scopeId` is the selected intent id. */
|
|
379
|
-
scopeType?: 'intent';
|
|
380
|
-
scopeId?: string;
|
|
381
|
-
/** Restrict to questions whose actor carries this network id. */
|
|
382
|
-
networkId?: string;
|
|
383
|
-
/** Restrict to questions whose detection mode is in this set. */
|
|
384
|
-
modes?: QuestionMode[];
|
|
385
|
-
/** Restrict to an internal generation purpose. */
|
|
386
|
-
purpose?: QuestionPurpose;
|
|
387
|
-
/** Maximum rows to return; hosts should apply this in the query. */
|
|
388
|
-
limit?: number;
|
|
389
|
-
}) => Promise<PendingQuestionSummary[]>;
|
|
390
|
-
/**
|
|
391
|
-
* Record the client's explicit answer to a pending question through the
|
|
392
|
-
* host's question-answer pipeline (atomic pending→answered flip + answered
|
|
393
|
-
* events). Returns false when the question is not pending for this user.
|
|
394
|
-
* Injected by the composition root — absent when question delivery is disabled.
|
|
395
|
-
*/
|
|
396
|
-
answerPendingQuestion?: (userId: string, questionId: string, answer: {
|
|
397
|
-
selectedOptions: string[];
|
|
398
|
-
freeText?: string;
|
|
399
|
-
}) => Promise<boolean>;
|
|
400
333
|
/** Negotiation-digest summarizer. Optional; consumers fall back to deterministic digests. */
|
|
401
334
|
negotiationSummary?: NegotiationSummaryReader;
|
|
402
|
-
/**
|
|
403
|
-
* Host bridge for the orchestrator's blocking `ask_user_question` tool
|
|
404
|
-
* (synchronous chat-question persist + in-stream answer wait). Injected by
|
|
405
|
-
* the backend composition root; when absent the tool is not registered.
|
|
406
|
-
*/
|
|
407
|
-
chatQuestions?: ChatQuestionsHost;
|
|
408
335
|
/** Manages negotiation timeout jobs (optional — enables AI fallback on external agent timeout). */
|
|
409
336
|
negotiationTimeoutQueue?: NegotiationTimeoutQueue;
|
|
410
337
|
/** Agent registry database adapter (optional — absent when host does not support agents). */
|
|
@@ -9,7 +9,6 @@ import { createAgentTools } from '../../agents/agent.module.js';
|
|
|
9
9
|
import { createNegotiationTools } from '../../negotiations/negotiation.module.js';
|
|
10
10
|
import { createChatTools } from '../../chat/chat.tools.js';
|
|
11
11
|
import { createPremiseTools } from '../../premises/premise.tools.js';
|
|
12
|
-
import { createQuestionerTools } from '../../questions/question.module.js';
|
|
13
12
|
import { protocolLogger } from '../observability/protocol.logger.js';
|
|
14
13
|
import { requestContext } from '../observability/request-context.js';
|
|
15
14
|
const logger = protocolLogger('ToolRegistry');
|
|
@@ -69,7 +68,6 @@ export function createToolRegistry(deps, options = {}) {
|
|
|
69
68
|
createAgentTools(dt, deps);
|
|
70
69
|
createNegotiationTools(dt, deps);
|
|
71
70
|
createPremiseTools(dt, deps);
|
|
72
|
-
createQuestionerTools(dt, deps);
|
|
73
71
|
if (deps.chatSession) {
|
|
74
72
|
createChatTools(dt, deps);
|
|
75
73
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TraceEmitter } from "../observability/request-context.js";
|
|
2
2
|
import type { RawToolDefinition, ResolvedToolContext } from "./tool.helpers.js";
|
|
3
|
-
export type ToolTimeoutClass = "fast" | "bounded_slow" | "async_candidate"
|
|
3
|
+
export type ToolTimeoutClass = "fast" | "bounded_slow" | "async_candidate";
|
|
4
4
|
export type ToolRuntimeErrorCode = "TOOL_TIMEOUT" | "TOOL_CANCELLED" | "TOOL_OUTPUT_TOO_LARGE";
|
|
5
5
|
export interface ToolTimeoutPolicy {
|
|
6
6
|
class: ToolTimeoutClass;
|
|
@@ -3,11 +3,6 @@ import { requestContext } from "../observability/request-context.js";
|
|
|
3
3
|
const FAST_TIMEOUT_MS = 10000;
|
|
4
4
|
const BOUNDED_SLOW_TIMEOUT_MS = 45000;
|
|
5
5
|
const ASYNC_CANDIDATE_TIMEOUT_MS = 50000;
|
|
6
|
-
// Interactive tools deliberately block on human input (ask_user_question).
|
|
7
|
-
// Must exceed the tool's own internal wait budget (QUESTIONER_CHAT_WAIT_TIMEOUT_MS,
|
|
8
|
-
// default 4 min) so the tool returns a graceful timeout result instead of
|
|
9
|
-
// being killed by the runtime.
|
|
10
|
-
const INTERACTIVE_TIMEOUT_MS = 300000;
|
|
11
6
|
const DEFAULT_MAX_OUTPUT_BYTES = 1000000;
|
|
12
7
|
const FAST_TOOLS = new Set([
|
|
13
8
|
"create_intent_index",
|
|
@@ -33,7 +28,6 @@ const FAST_TOOLS = new Set([
|
|
|
33
28
|
"revoke_agent_permission",
|
|
34
29
|
"retract_premise",
|
|
35
30
|
]);
|
|
36
|
-
const INTERACTIVE_TOOLS = new Set(["ask_user_question"]);
|
|
37
31
|
const ASYNC_CANDIDATE_TOOLS = new Set([
|
|
38
32
|
"read_user_contexts",
|
|
39
33
|
"preview_user_context",
|
|
@@ -64,19 +58,14 @@ function toolNameOutputEnv(toolName) {
|
|
|
64
58
|
export function getToolTimeoutPolicy(toolName) {
|
|
65
59
|
const classification = FAST_TOOLS.has(toolName)
|
|
66
60
|
? "fast"
|
|
67
|
-
:
|
|
68
|
-
? "
|
|
69
|
-
:
|
|
70
|
-
? "async_candidate"
|
|
71
|
-
: "bounded_slow";
|
|
61
|
+
: ASYNC_CANDIDATE_TOOLS.has(toolName)
|
|
62
|
+
? "async_candidate"
|
|
63
|
+
: "bounded_slow";
|
|
72
64
|
let classDefault;
|
|
73
65
|
switch (classification) {
|
|
74
66
|
case "fast":
|
|
75
67
|
classDefault = parsePositiveIntEnv("MCP_TOOL_TIMEOUT_FAST_MS", FAST_TIMEOUT_MS);
|
|
76
68
|
break;
|
|
77
|
-
case "interactive":
|
|
78
|
-
classDefault = parsePositiveIntEnv("MCP_TOOL_TIMEOUT_INTERACTIVE_MS", INTERACTIVE_TIMEOUT_MS);
|
|
79
|
-
break;
|
|
80
69
|
case "async_candidate":
|
|
81
70
|
classDefault = parsePositiveIntEnv("MCP_TOOL_TIMEOUT_ASYNC_CANDIDATE_MS", ASYNC_CANDIDATE_TIMEOUT_MS);
|
|
82
71
|
break;
|
package/package.json
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { PendingQuestionSummary } from "../shared/schemas/pending-question.schema.js";
|
|
2
|
-
/** Maximum pending questions attached to a single tool result. */
|
|
3
|
-
export declare const MAX_PENDING_QUESTIONS = 3;
|
|
4
|
-
export interface MergePendingQuestionsInput {
|
|
5
|
-
findPendingQuestions?: (userId: string, filters?: {
|
|
6
|
-
sourceType?: string;
|
|
7
|
-
sourceId?: string;
|
|
8
|
-
networkId?: string;
|
|
9
|
-
scopeType?: 'intent';
|
|
10
|
-
scopeId?: string;
|
|
11
|
-
}) => Promise<PendingQuestionSummary[]>;
|
|
12
|
-
userId: string;
|
|
13
|
-
sourceType?: string;
|
|
14
|
-
sourceId?: string;
|
|
15
|
-
/** Restrict to questions whose actor carries this network id. */
|
|
16
|
-
networkId?: string;
|
|
17
|
-
/** Optional selected-intent scope. */
|
|
18
|
-
scopeType?: 'intent';
|
|
19
|
-
scopeId?: string;
|
|
20
|
-
/** IDs already shown in this chat session — skip them. */
|
|
21
|
-
surfacedQuestionIds: Set<string>;
|
|
22
|
-
}
|
|
23
|
-
export interface MergePendingQuestionsResult {
|
|
24
|
-
/** Questions to attach to the tool result (under the `questions` key). */
|
|
25
|
-
questions: PendingQuestionSummary[];
|
|
26
|
-
/** IDs of the questions that were included (caller should add to surfacedQuestionIds). */
|
|
27
|
-
surfacedIds: string[];
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Query pending questions and filter out already-surfaced ones.
|
|
31
|
-
* Returns at most MAX_PENDING_QUESTIONS entries.
|
|
32
|
-
*/
|
|
33
|
-
export declare function mergePendingQuestions(input: MergePendingQuestionsInput): Promise<MergePendingQuestionsResult>;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helper for merging pending questions from the DB into tool results.
|
|
3
|
-
* Extracted so it can be unit-tested independently of the tool handler.
|
|
4
|
-
*/
|
|
5
|
-
import { protocolLogger } from "../shared/observability/protocol.logger.js";
|
|
6
|
-
const logger = protocolLogger("PendingQuestions");
|
|
7
|
-
/** Maximum pending questions attached to a single tool result. */
|
|
8
|
-
export const MAX_PENDING_QUESTIONS = 3;
|
|
9
|
-
/**
|
|
10
|
-
* Query pending questions and filter out already-surfaced ones.
|
|
11
|
-
* Returns at most MAX_PENDING_QUESTIONS entries.
|
|
12
|
-
*/
|
|
13
|
-
export async function mergePendingQuestions(input) {
|
|
14
|
-
if (!input.findPendingQuestions) {
|
|
15
|
-
return { questions: [], surfacedIds: [] };
|
|
16
|
-
}
|
|
17
|
-
const hasFilters = Boolean(input.sourceType || input.networkId || input.scopeType);
|
|
18
|
-
const filters = hasFilters
|
|
19
|
-
? {
|
|
20
|
-
...(input.sourceType ? { sourceType: input.sourceType } : {}),
|
|
21
|
-
...(input.sourceId ? { sourceId: input.sourceId } : {}),
|
|
22
|
-
...(input.networkId ? { networkId: input.networkId } : {}),
|
|
23
|
-
...(input.scopeType && input.scopeId ? { scopeType: input.scopeType, scopeId: input.scopeId } : {}),
|
|
24
|
-
}
|
|
25
|
-
: undefined;
|
|
26
|
-
let pending;
|
|
27
|
-
try {
|
|
28
|
-
pending = await input.findPendingQuestions(input.userId, filters);
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
logger.warn('Failed to fetch pending questions, returning empty', { error: err });
|
|
32
|
-
return { questions: [], surfacedIds: [] };
|
|
33
|
-
}
|
|
34
|
-
// Deduplicate against already-surfaced IDs in this session
|
|
35
|
-
const fresh = pending.filter((q) => !input.surfacedQuestionIds.has(q.id));
|
|
36
|
-
const capped = fresh.slice(0, MAX_PENDING_QUESTIONS);
|
|
37
|
-
const publicQuestions = capped.map(({ actors: _actors, purpose: _purpose, ...question }) => question);
|
|
38
|
-
return {
|
|
39
|
-
questions: publicQuestions,
|
|
40
|
-
surfacedIds: capped.map((q) => q.id),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type QuestionGenerationResult } from "./question.schema.js";
|
|
2
|
-
import { createStructuredModel } from "../shared/agent/model.config.js";
|
|
3
|
-
import { type QuestionerInput } from "./question.input.js";
|
|
4
|
-
export interface QuestionerAgentConfig {
|
|
5
|
-
/** Optional model config override. */
|
|
6
|
-
modelConfig?: Parameters<typeof createStructuredModel>[3];
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Stateless question-generation agent. Accepts a `QuestionerInput` envelope,
|
|
10
|
-
* selects the preset for the given mode, invokes the LLM, and applies
|
|
11
|
-
* guardrails (dedup + strategy diversity).
|
|
12
|
-
*/
|
|
13
|
-
export declare class QuestionerAgent {
|
|
14
|
-
private model;
|
|
15
|
-
constructor(config?: QuestionerAgentConfig);
|
|
16
|
-
/**
|
|
17
|
-
* Generate up to 3 decision questions from the given input.
|
|
18
|
-
*
|
|
19
|
-
* @param input Envelope with mode, userId, source info, and mode-specific context.
|
|
20
|
-
* @param options.signal Optional AbortSignal to cancel the in-flight LLM call.
|
|
21
|
-
* @returns A result with parallel questions[] and strategies[] arrays,
|
|
22
|
-
* or null when generation failed, guardrails dropped all candidates,
|
|
23
|
-
* the LLM threw, or the call was aborted.
|
|
24
|
-
*/
|
|
25
|
-
invoke(input: QuestionerInput, options?: {
|
|
26
|
-
signal?: AbortSignal;
|
|
27
|
-
}): Promise<QuestionGenerationResult | null>;
|
|
28
|
-
}
|
|
@@ -1,136 +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
|
-
* questions/question.agent — QuestionerAgent.
|
|
12
|
-
*
|
|
13
|
-
* Stateless, mode-driven agent that generates structured decision questions
|
|
14
|
-
* from arbitrary protocol contexts.
|
|
15
|
-
*
|
|
16
|
-
* Follows the IndexNegotiator pattern: constructor takes optional config,
|
|
17
|
-
* single public `invoke()` method receives the full context per call.
|
|
18
|
-
* The LLM model is bound once at construction; the preset (system prompt +
|
|
19
|
-
* builder) is selected per invocation based on `input.mode`.
|
|
20
|
-
*/
|
|
21
|
-
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
22
|
-
import { QuestionGeneratorResponseSchema } from "./question.schema.js";
|
|
23
|
-
import { createStructuredModel } from "../shared/agent/model.config.js";
|
|
24
|
-
import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
|
|
25
|
-
import { protocolLogger } from "../shared/observability/protocol.logger.js";
|
|
26
|
-
import { Timed } from "../shared/observability/performance.js";
|
|
27
|
-
import { getPreset } from "./question.presets.js";
|
|
28
|
-
import { isValidQuestionerInputContract } from "./question.input.js";
|
|
29
|
-
const logger = protocolLogger("QuestionerAgent");
|
|
30
|
-
/** Maximum same-strategy questions allowed in a single emission. */
|
|
31
|
-
const MAX_SAME_STRATEGY = 2;
|
|
32
|
-
/**
|
|
33
|
-
* Stateless question-generation agent. Accepts a `QuestionerInput` envelope,
|
|
34
|
-
* selects the preset for the given mode, invokes the LLM, and applies
|
|
35
|
-
* guardrails (dedup + strategy diversity).
|
|
36
|
-
*/
|
|
37
|
-
export class QuestionerAgent {
|
|
38
|
-
constructor(config) {
|
|
39
|
-
this.model = createStructuredModel("questioner", QuestionGeneratorResponseSchema, {
|
|
40
|
-
name: "clarifying_questions",
|
|
41
|
-
}, config?.modelConfig);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Generate up to 3 decision questions from the given input.
|
|
45
|
-
*
|
|
46
|
-
* @param input Envelope with mode, userId, source info, and mode-specific context.
|
|
47
|
-
* @param options.signal Optional AbortSignal to cancel the in-flight LLM call.
|
|
48
|
-
* @returns A result with parallel questions[] and strategies[] arrays,
|
|
49
|
-
* or null when generation failed, guardrails dropped all candidates,
|
|
50
|
-
* the LLM threw, or the call was aborted.
|
|
51
|
-
*/
|
|
52
|
-
async invoke(input, options) {
|
|
53
|
-
if (!isValidQuestionerInputContract(input)) {
|
|
54
|
-
logger.warn('QuestionerAgent rejected invalid mode/purpose/context contract', { mode: input.mode });
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
const preset = getPreset(input.mode, input.purpose);
|
|
58
|
-
const userMessage = preset.buildPrompt(input.context);
|
|
59
|
-
let raw;
|
|
60
|
-
try {
|
|
61
|
-
raw = await invokeWithAbortSignal(this.model, [new SystemMessage(preset.systemPrompt), new HumanMessage(userMessage)], options?.signal);
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
const aborted = options?.signal?.aborted ?? false;
|
|
65
|
-
if (aborted) {
|
|
66
|
-
logger.info("QuestionerAgent aborted by signal", {
|
|
67
|
-
mode: input.mode,
|
|
68
|
-
reason: options?.signal?.reason instanceof Error
|
|
69
|
-
? options.signal.reason.message
|
|
70
|
-
: String(options?.signal?.reason ?? "unknown"),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
logger.warn("QuestionerAgent LLM call failed", {
|
|
75
|
-
mode: input.mode,
|
|
76
|
-
error: err instanceof Error ? err.message : String(err),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
const parsed = QuestionGeneratorResponseSchema.safeParse(raw);
|
|
82
|
-
if (!parsed.success) {
|
|
83
|
-
logger.warn("QuestionerAgent parse failed", {
|
|
84
|
-
mode: input.mode,
|
|
85
|
-
error: parsed.error.message,
|
|
86
|
-
});
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
const filtered = applyGuardrails(parsed.data.questions);
|
|
90
|
-
if (filtered.length === 0)
|
|
91
|
-
return null;
|
|
92
|
-
return {
|
|
93
|
-
questions: filtered.map(stripInternalMetadata),
|
|
94
|
-
strategies: filtered.map((q) => q.strategy),
|
|
95
|
-
underspecificationTypes: filtered.map((q) => q.underspecificationType),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
__decorate([
|
|
100
|
-
Timed(),
|
|
101
|
-
__metadata("design:type", Function),
|
|
102
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
103
|
-
__metadata("design:returntype", Promise)
|
|
104
|
-
], QuestionerAgent.prototype, "invoke", null);
|
|
105
|
-
// --- Guardrails ---------------------------------------------------------------
|
|
106
|
-
function applyGuardrails(questions) {
|
|
107
|
-
const dedupedByTitle = dedupByTitle(questions);
|
|
108
|
-
return enforceStrategyDiversity(dedupedByTitle);
|
|
109
|
-
}
|
|
110
|
-
function dedupByTitle(questions) {
|
|
111
|
-
const seen = new Set();
|
|
112
|
-
const out = [];
|
|
113
|
-
for (const q of questions) {
|
|
114
|
-
if (seen.has(q.title))
|
|
115
|
-
continue;
|
|
116
|
-
seen.add(q.title);
|
|
117
|
-
out.push(q);
|
|
118
|
-
}
|
|
119
|
-
return out;
|
|
120
|
-
}
|
|
121
|
-
function enforceStrategyDiversity(questions) {
|
|
122
|
-
const counts = new Map();
|
|
123
|
-
const out = [];
|
|
124
|
-
for (const q of questions) {
|
|
125
|
-
const n = counts.get(q.strategy) ?? 0;
|
|
126
|
-
if (n >= MAX_SAME_STRATEGY)
|
|
127
|
-
continue;
|
|
128
|
-
counts.set(q.strategy, n + 1);
|
|
129
|
-
out.push(q);
|
|
130
|
-
}
|
|
131
|
-
return out;
|
|
132
|
-
}
|
|
133
|
-
function stripInternalMetadata(q) {
|
|
134
|
-
const { strategy: _strategy, underspecificationType: _underspecificationType, ...publicShape } = q;
|
|
135
|
-
return publicShape;
|
|
136
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { DefineTool } from "../shared/agent/tool.helpers.js";
|
|
2
|
-
import type { AskUserQuestionToolDeps } from "./question.tools.port.js";
|
|
3
|
-
import { QuestionerAgent } from "./question.agent.js";
|
|
4
|
-
/** Test seam: replace or reset the module-level QuestionerAgent singleton. */
|
|
5
|
-
export declare function setQuestionerAgentForTesting(agent: QuestionerAgent | null): void;
|
|
6
|
-
/**
|
|
7
|
-
* Creates the chat-only `ask_user_question` tool.
|
|
8
|
-
*
|
|
9
|
-
* @param defineTool - Tool factory provided by the composition root.
|
|
10
|
-
* @param deps - Shared tool dependencies; requires `chatQuestions`.
|
|
11
|
-
*/
|
|
12
|
-
export declare function createAskUserQuestionTools(defineTool: DefineTool, deps: AskUserQuestionToolDeps): readonly [any];
|