@indexnetwork/protocol 6.14.0-rc.406.1 → 6.15.0-rc.407.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 +5 -0
- package/dist/capabilities/signals.tools.port.d.ts +1 -1
- package/dist/runtime/foreground/composition/tool.factory.js +1 -0
- package/dist/shared/agent/tool.helpers.d.ts +4 -0
- package/dist/signals/application/intent.graph.js +4 -15
- package/dist/signals/application/intent.tools.js +27 -6
- package/dist/signals/domain/intent.proposal.d.ts +35 -0
- package/dist/signals/domain/intent.proposal.js +12 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,11 @@ See [STABILITY.md](./STABILITY.md) for the public-contract and tier definitions.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
### Added
|
|
16
|
+
- Add the injected `IntentProposalStore` host boundary so web proposal cards are
|
|
17
|
+
emitted only after the normalized description, optional network scope, and
|
|
18
|
+
complete verifier output have been durably bound to their owner.
|
|
19
|
+
|
|
15
20
|
## [6.14.0] — 2026-07-25
|
|
16
21
|
|
|
17
22
|
### Added
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ToolRegistryCompositionDeps } from "../shared/agent/tool.helpers.js";
|
|
2
2
|
/** Host capabilities consumed by signal and intent tools. */
|
|
3
|
-
export type IntentToolDeps = Pick<ToolRegistryCompositionDeps, "userDb" | "systemDb"> & {
|
|
3
|
+
export type IntentToolDeps = Pick<ToolRegistryCompositionDeps, "userDb" | "systemDb"> & Pick<ToolRegistryCompositionDeps, "intentProposalStore"> & {
|
|
4
4
|
graphs: Pick<ToolRegistryCompositionDeps["graphs"], "intent" | "intentIndex" | "profile">;
|
|
5
5
|
};
|
|
@@ -172,6 +172,7 @@ export async function createChatTools(deps, preResolvedContext) {
|
|
|
172
172
|
...(deps.chatQuestions && { chatQuestions: deps.chatQuestions }),
|
|
173
173
|
...(deps.chatSession && { chatSession: deps.chatSession }),
|
|
174
174
|
...(deps.getUserContextText && { getUserContextText: deps.getUserContextText }),
|
|
175
|
+
...(deps.intentProposalStore && { intentProposalStore: deps.intentProposalStore }),
|
|
175
176
|
graphs: {
|
|
176
177
|
profile: profileGraph,
|
|
177
178
|
intent: intentGraph,
|
|
@@ -207,6 +207,8 @@ interface ToolContextBindings {
|
|
|
207
207
|
chatQuestions?: ChatQuestionsHost;
|
|
208
208
|
/** Optional durable persistence for reporter cleanup-action proposals. */
|
|
209
209
|
actionProposalStore?: import('../../chat/reporter.action.contracts.js').AgentActionProposalStore;
|
|
210
|
+
/** Durable host persistence for verified intent proposals shown in chat. */
|
|
211
|
+
intentProposalStore?: import('../../signals/domain/intent.proposal.js').IntentProposalStore;
|
|
210
212
|
/**
|
|
211
213
|
* Host bridge for the negotiator persona's `remember`/`forget` memory
|
|
212
214
|
* tools (P5.4). Injected by the composition root only when negotiator
|
|
@@ -384,6 +386,8 @@ interface ToolDepsBindings {
|
|
|
384
386
|
userDb: UserDatabase;
|
|
385
387
|
/** Context-bound database for LLM/system operations on cross-user resources within shared networks. */
|
|
386
388
|
systemDb: SystemDatabase;
|
|
389
|
+
/** Durable host persistence for verified intent proposals shown in chat. */
|
|
390
|
+
intentProposalStore?: import('../../signals/domain/intent.proposal.js').IntentProposalStore;
|
|
387
391
|
scraper: Scraper;
|
|
388
392
|
embedder: import('../interfaces/embedder.interface.js').Embedder;
|
|
389
393
|
cache: Cache;
|
|
@@ -3,6 +3,7 @@ import { IntentGraphState } from "../domain/intent.state.js";
|
|
|
3
3
|
import { ExplicitIntentInferrer } from "./intent.inferrer.js";
|
|
4
4
|
import { SemanticVerifier } from "./intent.verifier.js";
|
|
5
5
|
import { DEFAULT_SPECIFICITY_WARNING } from "../domain/signal.specificity.js";
|
|
6
|
+
import { normalizeIntentDescription } from "../domain/intent.proposal.js";
|
|
6
7
|
import { IntentReconciler } from "./intent.reconciler.js";
|
|
7
8
|
import { getAbortSignalConfig } from "../../shared/agent/model-signal.js";
|
|
8
9
|
import { protocolLogger } from "../../shared/observability/protocol.logger.js";
|
|
@@ -497,18 +498,6 @@ export class IntentGraphFactory {
|
|
|
497
498
|
};
|
|
498
499
|
});
|
|
499
500
|
};
|
|
500
|
-
/** Strip URLs and "More details at [url]" from intent payloads before persisting. */
|
|
501
|
-
const sanitizePayload = (payload) => {
|
|
502
|
-
if (!payload || typeof payload !== "string")
|
|
503
|
-
return payload;
|
|
504
|
-
const out = payload
|
|
505
|
-
.replace(/\s*More details at\s*:?\s*https?:\/\/[^\s"'<>)\]]+/gi, "")
|
|
506
|
-
.replace(/\s*See\s+https?:\/\/[^\s"'<>)\]]+\s+for\s+more[^.]*\.?/gi, "")
|
|
507
|
-
.replace(/https?:\/\/[^\s"'<>)\]]+/g, "")
|
|
508
|
-
.replace(/\s{2,}/g, " ")
|
|
509
|
-
.trim();
|
|
510
|
-
return out.replace(/[.,;]\s*$/, "").trim() || payload;
|
|
511
|
-
};
|
|
512
501
|
/**
|
|
513
502
|
* Generate a flat embedding for an intent payload, swallowing failures so
|
|
514
503
|
* persistence can continue without an embedding. `intentId` is logging-only
|
|
@@ -554,14 +543,14 @@ export class IntentGraphFactory {
|
|
|
554
543
|
const verifiedIntentByPayload = new Map();
|
|
555
544
|
for (const verifiedIntent of state.verifiedIntents) {
|
|
556
545
|
verifiedIntentByPayload.set(verifiedIntent.description, verifiedIntent);
|
|
557
|
-
verifiedIntentByPayload.set(
|
|
546
|
+
verifiedIntentByPayload.set(normalizeIntentDescription(verifiedIntent.description), verifiedIntent);
|
|
558
547
|
}
|
|
559
548
|
for (const action of actions) {
|
|
560
549
|
const actionType = action.type.toLowerCase();
|
|
561
550
|
try {
|
|
562
551
|
if (actionType === 'create') {
|
|
563
552
|
const createAction = action;
|
|
564
|
-
const sanitizedPayload =
|
|
553
|
+
const sanitizedPayload = normalizeIntentDescription(createAction.payload);
|
|
565
554
|
const matchedVerifiedIntent = verifiedIntentByPayload.get(createAction.payload) ||
|
|
566
555
|
verifiedIntentByPayload.get(sanitizedPayload);
|
|
567
556
|
// Generate embedding for the intent payload
|
|
@@ -610,7 +599,7 @@ export class IntentGraphFactory {
|
|
|
610
599
|
}
|
|
611
600
|
else if (actionType === 'update') {
|
|
612
601
|
const updateAction = action;
|
|
613
|
-
const sanitizedPayload =
|
|
602
|
+
const sanitizedPayload = normalizeIntentDescription(updateAction.payload);
|
|
614
603
|
const matchedVerifiedIntent = verifiedIntentByPayload.get(updateAction.payload) ||
|
|
615
604
|
verifiedIntentByPayload.get(sanitizedPayload);
|
|
616
605
|
// Regenerate embedding for the updated payload
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { IntentClarifier } from "./intent.clarifier.js";
|
|
3
3
|
import { DEFAULT_SPECIFICITY_WARNING } from "../domain/signal.specificity.js";
|
|
4
|
+
import { normalizeIntentDescription } from "../domain/intent.proposal.js";
|
|
4
5
|
import { protocolLogger } from "../../shared/observability/protocol.logger.js";
|
|
5
6
|
import { traceGraph } from "../../shared/observability/trace.js";
|
|
6
7
|
import { success, error, UUID_REGEX } from "../../shared/agent/tool.helpers.js";
|
|
@@ -265,7 +266,7 @@ export function createIntentTools(defineTool, deps) {
|
|
|
265
266
|
"**What to pass:** A clear, concept-based description of what the user is looking for (e.g. 'Looking for an AI/ML co-founder in Berlin', " +
|
|
266
267
|
"'Need a designer for a mobile app project'). If the user provided a URL, scrape it with scrape_url first and synthesize the content into a description.\n\n" +
|
|
267
268
|
"**What happens:** The system runs inference (extracting structured intents), verification (checking specificity and speech-act type), " +
|
|
268
|
-
"and
|
|
269
|
+
"and durably stores an owner-scoped proposal containing the exact normalized description, optional network scope, and complete verifier output before returning a proposal widget. The intent itself is NOT yet persisted — the user must approve the proposal first.\n\n" +
|
|
269
270
|
"**Returns:** An intent_proposal code block that MUST be included verbatim in the response. The frontend renders it as an interactive " +
|
|
270
271
|
"card the user can approve or skip. On approval, the intent is persisted, indexed, and discovery begins.\n\n" +
|
|
271
272
|
"**Next steps after approval:** The intent is automatically linked to relevant indexes. Call discover_opportunities(searchQuery) to explicitly trigger discovery, " +
|
|
@@ -419,13 +420,32 @@ export function createIntentTools(defineTool, deps) {
|
|
|
419
420
|
});
|
|
420
421
|
}
|
|
421
422
|
// ── Proposal path (for web chat with interactive cards) ──
|
|
422
|
-
//
|
|
423
|
-
|
|
423
|
+
// Persist complete verifier output before emitting any usable card.
|
|
424
|
+
if (!deps.intentProposalStore) {
|
|
425
|
+
return error("Verified intent proposals are unavailable because durable proposal storage is not configured.");
|
|
426
|
+
}
|
|
427
|
+
const proposals = [];
|
|
428
|
+
const proposalBlocks = [];
|
|
429
|
+
for (const v of verified) {
|
|
430
|
+
if (!v.verification) {
|
|
431
|
+
return error("Intent verification produced no authoritative analysis; no proposal was created.", debugSteps);
|
|
432
|
+
}
|
|
424
433
|
const proposalId = crypto.randomUUID();
|
|
434
|
+
const normalizedDescription = normalizeIntentDescription(v.description);
|
|
425
435
|
const isBroad = isBroadAttributiveIntent(v);
|
|
436
|
+
proposals.push({
|
|
437
|
+
proposalId,
|
|
438
|
+
userId: context.userId,
|
|
439
|
+
description: normalizedDescription,
|
|
440
|
+
...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
|
|
441
|
+
analysis: {
|
|
442
|
+
verifierOutput: v.verification,
|
|
443
|
+
combinedScore: v.score ?? null,
|
|
444
|
+
},
|
|
445
|
+
});
|
|
426
446
|
const data = {
|
|
427
447
|
proposalId,
|
|
428
|
-
description:
|
|
448
|
+
description: normalizedDescription,
|
|
429
449
|
...(effectiveIndexId ? { networkId: effectiveIndexId } : {}),
|
|
430
450
|
confidence: v.score != null ? Math.round(v.score * 100) / 100 : null,
|
|
431
451
|
speechActType: v.verification?.classification ?? null,
|
|
@@ -434,10 +454,11 @@ export function createIntentTools(defineTool, deps) {
|
|
|
434
454
|
missingSelectionalConstraints: v.verification?.missing_selectional_constraints ?? [],
|
|
435
455
|
specificityWarning: isBroad ? specificityWarningFor(v) : normalizeSpecificityWarning(v.verification?.specificity_warning),
|
|
436
456
|
};
|
|
437
|
-
|
|
457
|
+
proposalBlocks.push("```intent_proposal\n" +
|
|
438
458
|
sanitizeJsonForCodeFence(JSON.stringify(data)) +
|
|
439
459
|
"\n```");
|
|
440
|
-
}
|
|
460
|
+
}
|
|
461
|
+
await deps.intentProposalStore.createProposals(proposals);
|
|
441
462
|
const blocksText = proposalBlocks.join("\n\n");
|
|
442
463
|
return success({
|
|
443
464
|
proposed: true,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Verifier output retained verbatim across the host persistence boundary. */
|
|
2
|
+
export interface IntentProposalVerifierOutput {
|
|
3
|
+
reasoning: string;
|
|
4
|
+
classification: "COMMISSIVE" | "DIRECTIVE" | "ASSERTIVE" | "EXPRESSIVE" | "DECLARATION" | "UNKNOWN";
|
|
5
|
+
felicity_scores: {
|
|
6
|
+
clarity: number;
|
|
7
|
+
authority: number;
|
|
8
|
+
sincerity: number;
|
|
9
|
+
};
|
|
10
|
+
semantic_entropy: number;
|
|
11
|
+
referential_anchor: string | null;
|
|
12
|
+
referential_breadth: "narrow" | "moderate" | "broad";
|
|
13
|
+
missing_selectional_constraints: Array<"role" | "outcome" | "location" | "timeframe" | "domain" | "concrete_need">;
|
|
14
|
+
specificity_warning: string | null;
|
|
15
|
+
flags: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Complete server-authoritative analysis captured for a verified intent proposal. */
|
|
18
|
+
export interface IntentProposalAnalysis {
|
|
19
|
+
verifierOutput: IntentProposalVerifierOutput;
|
|
20
|
+
combinedScore: number | null;
|
|
21
|
+
}
|
|
22
|
+
/** One proposal persisted by the host before its display card is emitted. */
|
|
23
|
+
export interface PersistableIntentProposal {
|
|
24
|
+
proposalId: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
description: string;
|
|
27
|
+
networkId?: string;
|
|
28
|
+
analysis: IntentProposalAnalysis;
|
|
29
|
+
}
|
|
30
|
+
/** Host persistence boundary for durable, owner-scoped intent proposals. */
|
|
31
|
+
export interface IntentProposalStore {
|
|
32
|
+
createProposals(proposals: PersistableIntentProposal[]): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/** Normalize intent text exactly as the direct graph write path does. */
|
|
35
|
+
export declare function normalizeIntentDescription(description: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Normalize intent text exactly as the direct graph write path does. */
|
|
2
|
+
export function normalizeIntentDescription(description) {
|
|
3
|
+
if (!description || typeof description !== "string")
|
|
4
|
+
return description;
|
|
5
|
+
const normalized = description
|
|
6
|
+
.replace(/\s*More details at\s*:?\s*https?:\/\/[^\s"'<>)\]]+/gi, "")
|
|
7
|
+
.replace(/\s*See\s+https?:\/\/[^\s"'<>)\]]+\s+for\s+more[^.]*\.?/gi, "")
|
|
8
|
+
.replace(/https?:\/\/[^\s"'<>)\]]+/g, "")
|
|
9
|
+
.replace(/\s{2,}/g, " ")
|
|
10
|
+
.trim();
|
|
11
|
+
return normalized.replace(/[.,;]\s*$/, "").trim() || description;
|
|
12
|
+
}
|