@indexnetwork/protocol 66.0.0-rc.608.1 → 66.0.0-rc.610.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 +3 -1
- package/IMPLEMENTATION.md +11 -12
- package/STABILITY.md +0 -1
- package/dist/capabilities/intents.d.ts +7 -3
- package/dist/capabilities/intents.js +10 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +3 -3
- package/dist/internal/intents/graph/intent.graph.reconcile.js +1 -1
- package/dist/internal/intents/intent.admission.d.ts +22 -1
- package/dist/internal/intents/intent.admission.js +29 -8
- package/dist/internal/intents/intent.preparer.d.ts +29 -4
- package/dist/internal/intents/intent.preparer.js +125 -18
- package/dist/internal/opportunities/opportunity.cards.d.ts +78 -0
- package/dist/internal/opportunities/opportunity.cards.js +165 -0
- package/dist/internal/opportunities/opportunity.presentation.d.ts +2 -2
- package/dist/internal/opportunities/opportunity.presentation.js +3 -3
- package/dist/internal/opportunities/opportunity.utils.d.ts +0 -42
- package/dist/internal/opportunities/opportunity.utils.js +0 -102
- package/dist/internal/shared/agent/decision.client.d.ts +42 -0
- package/dist/internal/shared/agent/decision.client.js +61 -0
- package/dist/platform/database/capabilities.d.ts +2 -2
- package/dist/platform/database/entities.d.ts +1 -1
- package/package.json +1 -1
- package/dist/internal/opportunities/opportunity.feed-selection.d.ts +0 -24
- package/dist/internal/opportunities/opportunity.feed-selection.js +0 -40
- package/dist/internal/opportunities/radar/radar.graph.d.ts +0 -230
- package/dist/internal/opportunities/radar/radar.graph.js +0 -506
- package/dist/internal/opportunities/radar/radar.state.d.ts +0 -86
- package/dist/internal/opportunities/radar/radar.state.js +0 -87
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
- Opportunity copy comes only from the LLM presenter. `OpportunityPresenter.present()` and `presentCard()` throw on LLM failure, timeout, or invalid output instead of returning fallback copy; `isFallback` and `fallbackReason` are gone from their results.
|
|
8
8
|
- Remove `presentOpportunity`, `stripUuids`, `truncateAtBoundary`, `safeFallbackSummary`, `DEFAULT_FALLBACK_HEADLINE`, `hasUnsupportedOpportunityClaim`, `stripUnsupportedOpportunityClaims`, and `stripUnsupportedOpportunityClaimsText` from the public entry point. The regex claim-safety guard is deleted; the presenter prompts carry the rule.
|
|
9
|
-
- `readOpportunities` items no longer carry `reasoning`.
|
|
9
|
+
- `readOpportunities` items no longer carry `reasoning`.
|
|
10
|
+
- Remove `RadarGraphFactory` and the radar graph. Use `listOpportunityCards(deps, { viewerId, statuses, networkId?, intentId?, limit, noCache?, skeleton? })` for a presented card list and `presentOpportunityCard(deps, opportunity, viewerId, { intentId?, skeleton? })` for one card; both return `OpportunityCard`. `statuses` is required, and the list is newest first with one card per counterpart. A card whose presenter call fails is dropped from the list; `presentOpportunityCard` throws.
|
|
11
|
+
- Rename `RadarGraphDatabase` to `OpportunityCardsDatabase` and `buildRadarCardPresentationCacheKey` to `buildOpportunityCardCacheKey` (keys now start with `card:`). Remove `RADAR_SOFT_TARGETS` and `selectByComposition`.
|
|
10
12
|
|
|
11
13
|
## 65.0.0
|
|
12
14
|
|
package/IMPLEMENTATION.md
CHANGED
|
@@ -128,26 +128,25 @@ negotiation state and presentation contracts.
|
|
|
128
128
|
Optional capabilities default to a
|
|
129
129
|
degraded-but-functional mode when omitted.
|
|
130
130
|
|
|
131
|
-
##
|
|
131
|
+
## Opportunity cards
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
Presented opportunity cards are plain async functions over
|
|
134
|
+
`{ database: OpportunityCardsDatabase, cache: OpportunityCache, presenter: OpportunityPresenter }`:
|
|
134
135
|
|
|
135
136
|
```typescript
|
|
136
137
|
import {
|
|
137
|
-
|
|
138
|
+
listOpportunityCards,
|
|
139
|
+
presentOpportunityCard,
|
|
138
140
|
} from "@indexnetwork/protocol";
|
|
139
141
|
```
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
`listOpportunityCards` returns the viewer's opportunities in the requested
|
|
144
|
+
statuses, newest first with one card per counterpart, serving cached cards and
|
|
145
|
+
dropping any whose presenter call fails. `presentOpportunityCard` presents one
|
|
146
|
+
opportunity and throws when the presenter fails.
|
|
143
147
|
|
|
144
|
-
The intent and community graphs are
|
|
145
|
-
|
|
146
|
-
[Intents](#intents) and [Networks](#networks) below).
|
|
147
|
-
|
|
148
|
-
| Factory | Workflow |
|
|
149
|
-
|---|---|
|
|
150
|
-
| `RadarGraphFactory` | Build the radar view: flat presenter-card list, optionally intent-scoped |
|
|
148
|
+
The intent and community graphs are reached through the `Intents` and `Networks`
|
|
149
|
+
module classes (see [Intents](#intents) and [Networks](#networks) below).
|
|
151
150
|
|
|
152
151
|
## On-demand discovery
|
|
153
152
|
|
package/STABILITY.md
CHANGED
|
@@ -46,7 +46,6 @@ Covered by SemVer below. Breaking changes require a **major** bump.
|
|
|
46
46
|
| **Public API** | Model config helpers, request scope helpers (`deriveAllowedNetworkIds`, `deriveDiscoveryNetworkIds`), `requestContext`. |
|
|
47
47
|
| **Interfaces** | Every port you implement to inject infrastructure (databases, embedder, cache, scraper, integration, …). |
|
|
48
48
|
| **Shared schemas** | Zod schemas + inferred types that cross the boundary (underspecification, identity, network-assignment, chat-context, …). |
|
|
49
|
-
| **Graph factories** | `*GraphFactory` classes (for example, `RadarGraphFactory`). |
|
|
50
49
|
| **Intents** | `Intents` — the whole signal capability as one class (lifecycle graph, verification, preparation) plus `IntentsDeps` and the `Prepare*` / `RecoveryField*` types. Replaced the six separate intent exports in 18.0.0. |
|
|
51
50
|
| **Agents** | Structured LLM agents (`OpportunityEvaluator`, …). |
|
|
52
51
|
|
|
@@ -18,10 +18,10 @@ import type { EmbeddingGenerator } from "../platform/discovery/embedder.js";
|
|
|
18
18
|
import type { IntentFollowUp } from "../platform/runtime/follow-up.js";
|
|
19
19
|
import { ExplicitIntentInferrer } from "../internal/intents/intent.inferrer.js";
|
|
20
20
|
import { SemanticVerifier } from "../internal/intents/intent.verifier.js";
|
|
21
|
-
import type { PrepareInput, PrepareResult } from "../internal/intents/intent.preparer.js";
|
|
21
|
+
import type { AdmissionCheck, PrepareInput, PrepareResult, RecoveryField } from "../internal/intents/intent.preparer.js";
|
|
22
22
|
export type { IntentSemanticMetadata } from "../internal/intents/intent.admission.js";
|
|
23
23
|
export type { PreparedIntent } from "../internal/intents/intent.preparer.js";
|
|
24
|
-
export type { PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, } from "../internal/intents/intent.preparer.js";
|
|
24
|
+
export type { AdmissionCheck, PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, } from "../internal/intents/intent.preparer.js";
|
|
25
25
|
/**
|
|
26
26
|
* Host capabilities the intent lifecycle needs.
|
|
27
27
|
*
|
|
@@ -421,7 +421,7 @@ export declare class Intents {
|
|
|
421
421
|
status: "needs_revision";
|
|
422
422
|
payload: string;
|
|
423
423
|
feedback: string;
|
|
424
|
-
recovery:
|
|
424
|
+
recovery: RecoveryField[];
|
|
425
425
|
};
|
|
426
426
|
actions: never[];
|
|
427
427
|
validationFailures: {
|
|
@@ -607,6 +607,10 @@ export declare class Intents {
|
|
|
607
607
|
* @param input - The payload and any recovery answers gathered so far.
|
|
608
608
|
*/
|
|
609
609
|
prepare(input: PrepareInput): Promise<PrepareResult>;
|
|
610
|
+
/** Jev's pass or the constraints still missing. Does not write questions. */
|
|
611
|
+
check(input: PrepareInput): Promise<AdmissionCheck>;
|
|
612
|
+
/** One recovery field per missing constraint. Does not run Jev. */
|
|
613
|
+
questions(input: PrepareInput, missing: readonly string[]): Promise<RecoveryField[]>;
|
|
610
614
|
/**
|
|
611
615
|
* Measure text without applying any admission filters.
|
|
612
616
|
* @param content - The exact saved description.
|
|
@@ -70,6 +70,16 @@ export class Intents {
|
|
|
70
70
|
this.preparer ?? (this.preparer = new IntentPreparer(this.deps.agents?.verifier));
|
|
71
71
|
return this.preparer.invoke(input);
|
|
72
72
|
}
|
|
73
|
+
/** Jev's pass or the constraints still missing. Does not write questions. */
|
|
74
|
+
async check(input) {
|
|
75
|
+
this.preparer ?? (this.preparer = new IntentPreparer(this.deps.agents?.verifier));
|
|
76
|
+
return this.preparer.check(input);
|
|
77
|
+
}
|
|
78
|
+
/** One recovery field per missing constraint. Does not run Jev. */
|
|
79
|
+
async questions(input, missing) {
|
|
80
|
+
this.preparer ?? (this.preparer = new IntentPreparer(this.deps.agents?.verifier));
|
|
81
|
+
return this.preparer.questions(input, missing);
|
|
82
|
+
}
|
|
73
83
|
/**
|
|
74
84
|
* Measure text without applying any admission filters.
|
|
75
85
|
* @param content - The exact saved description.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { requestContext, setRequestContextStore } from "./internal/shared/observ
|
|
|
6
6
|
export { setLoggerFactory } from "./internal/shared/observability/log.js";
|
|
7
7
|
export { setTimingWrapper } from "./internal/shared/observability/performance.js";
|
|
8
8
|
export type { Cache, CacheOptions, OpportunityCache } from "./platform/discovery/cache.js";
|
|
9
|
-
export type { CompositeDatabase, UserDatabase, SystemDatabase, OpportunityDatabase, OpportunityControllerDatabase,
|
|
9
|
+
export type { CompositeDatabase, UserDatabase, SystemDatabase, OpportunityDatabase, OpportunityControllerDatabase, OpportunityCardsDatabase, IntentGraphDatabase, Opportunity, OpportunityActor, OpportunityStatus, AssignmentNetworkMembership, IntentNetworkFinalAssignmentResult, CreateOpportunityData, IntentLifecycleStatus, TransitionLifecycleResult, NegotiationContextDatabase, NegotiationContextRecord, NegotiationContextOutcome, NegotiationContextTurn, } from "./platform/database.js";
|
|
10
10
|
export type { Embedder, VectorStoreOption, VectorSearchResult } from "./platform/discovery/embedder.js";
|
|
11
11
|
export type { IntentFollowUp } from "./platform/runtime/follow-up.js";
|
|
12
12
|
export type { Scraper } from "./platform/discovery/scraper.js";
|
|
@@ -29,7 +29,7 @@ export type { NegotiationDatabase } from './platform/database/negotiation.js';
|
|
|
29
29
|
export { Networks } from "./capabilities/networks.js";
|
|
30
30
|
export type { NetworksDeps } from "./capabilities/networks.js";
|
|
31
31
|
export { Intents } from "./capabilities/intents.js";
|
|
32
|
-
export type { PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, IntentsDeps, IntentSemanticMetadata, PreparedIntent, } from "./capabilities/intents.js";
|
|
32
|
+
export type { AdmissionCheck, PrepareAnswer, PrepareInput, PrepareResult, RecoveryField, RecoveryFieldKind, RecoveryFieldOption, IntentsDeps, IntentSemanticMetadata, PreparedIntent, } from "./capabilities/intents.js";
|
|
33
33
|
export { normalizeTelegramHandle } from './internal/shared/utils/telegram-handle.js';
|
|
34
34
|
/**
|
|
35
35
|
* opportunity — the capability's sole cross-capability surface.
|
|
@@ -44,10 +44,11 @@ export type { CreateIntentCounterpartyData, OpenedNegotiation, } from "./interna
|
|
|
44
44
|
export { gatherPresenterContext, OpportunityPresenter, } from "./internal/opportunities/opportunity.presentation.js";
|
|
45
45
|
export type { PresenterDatabase, } from "./internal/opportunities/opportunity.presentation.js";
|
|
46
46
|
export { getPrimaryActionLabel, } from "./internal/opportunities/opportunity.labels.js";
|
|
47
|
-
export { buildApiChatCardPresentationCacheKey,
|
|
47
|
+
export { buildApiChatCardPresentationCacheKey, buildOpportunityCardCacheKey, } from "./internal/opportunities/opportunity.presentation.js";
|
|
48
48
|
export type { UserInfo, } from "./internal/opportunities/opportunity.presentation.js";
|
|
49
|
-
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer,
|
|
50
|
-
export {
|
|
49
|
+
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer, validateOpportunityActors, } from "./internal/opportunities/opportunity.utils.js";
|
|
50
|
+
export { listOpportunityCards, presentOpportunityCard, } from "./internal/opportunities/opportunity.cards.js";
|
|
51
|
+
export type { OpportunityCard, } from "./internal/opportunities/opportunity.cards.js";
|
|
51
52
|
export { readOpportunities } from './internal/opportunities/opportunity.graph.modes.js';
|
|
52
53
|
export { admitOpportunityEvent, projectOpportunity } from './internal/opportunities/opportunity.events.js';
|
|
53
54
|
export type { OpportunityAdmission, OpportunityEventType, OpportunityLogEvent, OpportunityProjection, OpportunityProjectionStatus } from './internal/opportunities/opportunity.events.js';
|
package/dist/index.js
CHANGED
|
@@ -48,9 +48,9 @@ export { normalizeTelegramHandle } from './internal/shared/utils/telegram-handle
|
|
|
48
48
|
export { pairKeyOf, } from "./internal/opportunities/opportunity.counterparties.js";
|
|
49
49
|
export { gatherPresenterContext, OpportunityPresenter, } from "./internal/opportunities/opportunity.presentation.js";
|
|
50
50
|
export { getPrimaryActionLabel, } from "./internal/opportunities/opportunity.labels.js";
|
|
51
|
-
export { buildApiChatCardPresentationCacheKey,
|
|
52
|
-
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer,
|
|
53
|
-
export {
|
|
51
|
+
export { buildApiChatCardPresentationCacheKey, buildOpportunityCardCacheKey, } from "./internal/opportunities/opportunity.presentation.js";
|
|
52
|
+
export { canUserSeeOpportunity, classifyOpportunity, isActionableForViewer, validateOpportunityActors, } from "./internal/opportunities/opportunity.utils.js";
|
|
53
|
+
export { listOpportunityCards, presentOpportunityCard, } from "./internal/opportunities/opportunity.cards.js";
|
|
54
54
|
export { readOpportunities } from './internal/opportunities/opportunity.graph.modes.js';
|
|
55
55
|
export { admitOpportunityEvent, projectOpportunity } from './internal/opportunities/opportunity.events.js';
|
|
56
56
|
export { resolveDiscoveryNetworkScope, renderDiscoveryNetworkContext } from './protocol/discovery.rules.js';
|
|
@@ -34,7 +34,7 @@ export async function verificationNode(state, deps) {
|
|
|
34
34
|
const verdict = await deps.verifier.invoke(description, state.userProfile);
|
|
35
35
|
agentTimingsAccum.push({ name: 'intent.verifier', durationMs: Date.now() - verifierStart });
|
|
36
36
|
_traceEmitterVerifier?.({ type: "agent_end", name: "intent-verifier", durationMs: Date.now() - verifierStart, summary: `Verified: ${verdict.classification}` });
|
|
37
|
-
const failure = admissionFailure(
|
|
37
|
+
const failure = admissionFailure(verdict, isExplicitUpdate);
|
|
38
38
|
if (failure)
|
|
39
39
|
return { failure };
|
|
40
40
|
// Calculate Score
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import type { SemanticVerifierOutput } from "./intent.verifier.js";
|
|
2
2
|
import type { IntentValidationFailure } from "./graph/intent.graph.state.js";
|
|
3
|
+
/** A noul at or above this is a yes the admission check acts on. */
|
|
4
|
+
export declare const ADMISSION_NOUL = 0.8;
|
|
5
|
+
export declare const ADMISSION_CONSTRAINTS: readonly ["role", "outcome", "location", "timeframe", "domain", "concrete_need"];
|
|
6
|
+
export type AdmissionConstraint = (typeof ADMISSION_CONSTRAINTS)[number];
|
|
7
|
+
type SpeechAct = "commissive" | "directive" | "declaration" | "other";
|
|
8
|
+
/** One Jev admission call, already read into numbers. */
|
|
9
|
+
export interface AdmissionDecision {
|
|
10
|
+
speechAct: SpeechAct;
|
|
11
|
+
tooVague: number;
|
|
12
|
+
tooBroad: number;
|
|
13
|
+
constraints: Record<AdmissionConstraint, number>;
|
|
14
|
+
}
|
|
3
15
|
/** The admission policy shared by preparation and explicit updates. */
|
|
4
|
-
export declare function admissionFailure(
|
|
16
|
+
export declare function admissionFailure(verdict: SemanticVerifierOutput, isExplicitUpdate?: boolean): IntentValidationFailure | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The prepare-path admission check. Constraint nouls at or above {@link ADMISSION_NOUL}
|
|
19
|
+
* are still missing; they do not fail admission on their own.
|
|
20
|
+
*/
|
|
21
|
+
export declare function admissionFromDecision(decision: AdmissionDecision): {
|
|
22
|
+
failure?: IntentValidationFailure;
|
|
23
|
+
missing: AdmissionConstraint[];
|
|
24
|
+
};
|
|
5
25
|
/** Measurements of exactly one description, independent of admission. */
|
|
6
26
|
export interface IntentSemanticMetadata {
|
|
7
27
|
semanticEntropy: number;
|
|
@@ -14,3 +34,4 @@ export interface IntentSemanticMetadata {
|
|
|
14
34
|
}
|
|
15
35
|
/** Map even a negative verdict to measurements without applying admission. */
|
|
16
36
|
export declare function semanticMetadata(verdict: SemanticVerifierOutput): IntentSemanticMetadata;
|
|
37
|
+
export {};
|
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
const MAX_PERMISSIBLE_ENTROPY = 0.75;
|
|
2
2
|
const MIN_CLEAR_INTENT_SCORE = 40;
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const NON_ACTIONABLE_MESSAGE = "Describe who you want to reach and what you want to do together.";
|
|
4
|
+
const VAGUE_MESSAGE = "Make the goal more concrete: specify the role, outcome, or particular help you need.";
|
|
5
|
+
const BROAD_MESSAGE = "This signal is broad and may produce many weak matches. Add a more concrete role, outcome, location, timeframe, domain, or specific need to get better recommendations.";
|
|
6
|
+
/** A noul at or above this is a yes the admission check acts on. */
|
|
7
|
+
export const ADMISSION_NOUL = 0.8;
|
|
8
|
+
export const ADMISSION_CONSTRAINTS = ["role", "outcome", "location", "timeframe", "domain", "concrete_need"];
|
|
6
9
|
/** The admission policy shared by preparation and explicit updates. */
|
|
7
|
-
export function admissionFailure(
|
|
10
|
+
export function admissionFailure(verdict, isExplicitUpdate = false) {
|
|
8
11
|
const details = { classification: verdict.classification, referentialBreadth: verdict.referential_breadth };
|
|
9
12
|
if (!["COMMISSIVE", "DIRECTIVE", "DECLARATION"].includes(verdict.classification)) {
|
|
10
|
-
return { ...details, category: "non_actionable", message:
|
|
13
|
+
return { ...details, category: "non_actionable", message: NON_ACTIONABLE_MESSAGE };
|
|
11
14
|
}
|
|
12
|
-
if (
|
|
13
|
-
return { ...details, category: "vague_or_invalid", message:
|
|
15
|
+
if (verdict.semantic_entropy > MAX_PERMISSIBLE_ENTROPY || verdict.felicity_scores.clarity < MIN_CLEAR_INTENT_SCORE) {
|
|
16
|
+
return { ...details, category: "vague_or_invalid", message: VAGUE_MESSAGE };
|
|
14
17
|
}
|
|
15
18
|
if (!isExplicitUpdate && verdict.referential_breadth === "broad") {
|
|
16
|
-
return { ...details, category: "vague_or_invalid", message: verdict.specificity_warning?.trim() ||
|
|
19
|
+
return { ...details, category: "vague_or_invalid", message: verdict.specificity_warning?.trim() || BROAD_MESSAGE };
|
|
17
20
|
}
|
|
18
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The prepare-path admission check. Constraint nouls at or above {@link ADMISSION_NOUL}
|
|
24
|
+
* are still missing; they do not fail admission on their own.
|
|
25
|
+
*/
|
|
26
|
+
export function admissionFromDecision(decision) {
|
|
27
|
+
const missing = ADMISSION_CONSTRAINTS.filter((name) => decision.constraints[name] >= ADMISSION_NOUL);
|
|
28
|
+
const classification = decision.speechAct === "other" ? "UNKNOWN" : decision.speechAct.toUpperCase();
|
|
29
|
+
if (decision.speechAct === "other") {
|
|
30
|
+
return { missing, failure: { category: "non_actionable", classification, message: NON_ACTIONABLE_MESSAGE } };
|
|
31
|
+
}
|
|
32
|
+
if (decision.tooVague >= ADMISSION_NOUL) {
|
|
33
|
+
return { missing, failure: { category: "vague_or_invalid", classification, message: VAGUE_MESSAGE } };
|
|
34
|
+
}
|
|
35
|
+
if (decision.tooBroad >= ADMISSION_NOUL) {
|
|
36
|
+
return { missing, failure: { category: "vague_or_invalid", classification, referentialBreadth: "broad", message: BROAD_MESSAGE } };
|
|
37
|
+
}
|
|
38
|
+
return { missing };
|
|
39
|
+
}
|
|
19
40
|
/** Map even a negative verdict to measurements without applying admission. */
|
|
20
41
|
export function semanticMetadata(verdict) {
|
|
21
42
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DecisionClient } from "../shared/agent/decision.client.js";
|
|
2
|
+
import { type AdmissionConstraint, type IntentSemanticMetadata } from "./intent.admission.js";
|
|
2
3
|
import { SemanticVerifier } from "./intent.verifier.js";
|
|
3
4
|
/** One answer paired with the recovery field it fills. */
|
|
4
5
|
export interface PrepareAnswer {
|
|
@@ -37,16 +38,40 @@ export type PrepareResult = {
|
|
|
37
38
|
feedback: string;
|
|
38
39
|
recovery: RecoveryField[];
|
|
39
40
|
};
|
|
41
|
+
/** Jev's read of the current rows. `passed` is the only state that may mint a receipt. */
|
|
42
|
+
export interface AdmissionCheck {
|
|
43
|
+
passed: boolean;
|
|
44
|
+
missing: AdmissionConstraint[];
|
|
45
|
+
message: string;
|
|
46
|
+
}
|
|
40
47
|
/** Folds recovery answers into a draft, admits its final form, and returns a dynamic recovery form on failure. Model failures propagate for retry. */
|
|
41
48
|
export declare class IntentPreparer {
|
|
42
49
|
private readonly verifier;
|
|
43
|
-
|
|
50
|
+
private readonly decider;
|
|
51
|
+
constructor(verifier?: Pick<SemanticVerifier, "invoke">, decider?: Pick<DecisionClient, "decide">);
|
|
52
|
+
/**
|
|
53
|
+
* Ask Jev whether the current rows would pass. Does not fold text or write questions.
|
|
54
|
+
* @param input - Opening draft and the answers already on the page.
|
|
55
|
+
* @param profileContext - The speaker's profile, if supplied by the host.
|
|
56
|
+
* @throws When Jev fails; the caller keeps the rows and retries.
|
|
57
|
+
*/
|
|
58
|
+
check(input: PrepareInput, profileContext?: string): Promise<AdmissionCheck>;
|
|
59
|
+
/**
|
|
60
|
+
* Write one recovery field per constraint id. Does not run Jev.
|
|
61
|
+
* @param input - Opening draft and the answers already on the page.
|
|
62
|
+
* @param missing - Constraint ids that still need a row.
|
|
63
|
+
*/
|
|
64
|
+
questions(input: PrepareInput, missing: readonly string[]): Promise<RecoveryField[]>;
|
|
44
65
|
/**
|
|
45
|
-
*
|
|
66
|
+
* Fold answers, admit the draft, and return a recovery form when it still fails.
|
|
46
67
|
* @param input - Current payload and pending recovery answers.
|
|
47
68
|
* @param profileContext - The speaker's profile, if supplied by the host.
|
|
48
|
-
* @returns An admitted draft or admission feedback plus a recovery form.
|
|
49
69
|
* @throws When rewriting, verification, or recovery generation fails; callers must retain the input for retry.
|
|
50
70
|
*/
|
|
51
71
|
invoke(input: PrepareInput, profileContext?: string): Promise<PrepareResult>;
|
|
72
|
+
/** Today's recovery form, used only when the Jev call itself fails. */
|
|
73
|
+
private legacy;
|
|
74
|
+
/** One field per constraint still missing. None named means one text field from the admission message. */
|
|
75
|
+
private revise;
|
|
76
|
+
private fieldsFor;
|
|
52
77
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { DecisionClient } from "../shared/agent/decision.client.js";
|
|
3
4
|
import { createStructuredModel } from "../shared/agent/model.config.js";
|
|
4
5
|
import { invokeWithAbortSignal } from "../shared/agent/model-signal.js";
|
|
5
|
-
import { admissionFailure, semanticMetadata } from "./intent.admission.js";
|
|
6
|
+
import { ADMISSION_CONSTRAINTS, admissionFailure, admissionFromDecision, semanticMetadata } from "./intent.admission.js";
|
|
6
7
|
import { SemanticVerifier } from "./intent.verifier.js";
|
|
7
8
|
const payloadSchema = z.object({ payload: z.string().trim().min(1).max(65536) });
|
|
8
9
|
const recoveryOptionSchema = z.object({
|
|
@@ -17,34 +18,107 @@ const recoveryFieldSchema = z.object({
|
|
|
17
18
|
placeholder: z.string().max(200).nullable().optional(),
|
|
18
19
|
});
|
|
19
20
|
const recoverySchema = z.object({
|
|
21
|
+
recovery: z.array(recoveryFieldSchema).min(1).max(6),
|
|
22
|
+
});
|
|
23
|
+
const legacyRecoverySchema = z.object({
|
|
20
24
|
recovery: z.array(recoveryFieldSchema).min(2).max(6),
|
|
21
25
|
});
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
const noul = (instructions, yes, no) => ({
|
|
27
|
+
type: "noul",
|
|
28
|
+
instructions: `Read \`draft\` and \`answers\` together. ${instructions}`,
|
|
29
|
+
criteria: { true: yes, false: no },
|
|
30
|
+
});
|
|
31
|
+
/** The admission process: the same questions judge the opening draft and a draft after answers. */
|
|
32
|
+
const ADMISSION_QUESTIONS = {
|
|
33
|
+
speech_act: {
|
|
34
|
+
type: "choice",
|
|
35
|
+
instructions: "Read `draft` and `answers` together. What speech act is `draft`?",
|
|
36
|
+
criteria: {
|
|
37
|
+
commissive: "The speaker commits to a future action.",
|
|
38
|
+
directive: "The speaker is looking for someone, or asking for something to happen with another person.",
|
|
39
|
+
declaration: "The speaker cancels, closes, or declares a state change.",
|
|
40
|
+
other: "A fact, opinion, feeling, or anything that is not a search, a commitment, or a declaration.",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
too_vague: noul("Is `draft` still too unconstrained to match someone on? A bare request, such as wanting a job with no role or outcome, is too vague.", "The goal is too unconstrained to match on.", "The goal names a concrete role, outcome, or particular help."),
|
|
44
|
+
too_broad: noul("Could many people still satisfy `draft`, even if it names a topic?", "Many people could plausibly match.", "A concrete role, outcome, location, timeframe, or specific need narrows who fits."),
|
|
45
|
+
role: noul("Is a specific role still missing from `draft`?", "No specific role is stated.", "A specific role is stated."),
|
|
46
|
+
outcome: noul("Is a concrete outcome still missing from `draft`?", "No concrete outcome is stated.", "A concrete outcome is stated."),
|
|
47
|
+
location: noul("Is a location still missing from `draft` where one would change who fits?", "No location is stated and one would change who fits.", "A location is stated, or none is needed."),
|
|
48
|
+
timeframe: noul("Is a timeframe still missing from `draft` where one would change who fits?", "No timeframe is stated and one would change who fits.", "A timeframe is stated, or none is needed."),
|
|
49
|
+
domain: noul("Is a domain still missing from `draft`?", "No domain is stated.", "A domain is stated."),
|
|
50
|
+
concrete_need: noul("Is the particular help still missing from `draft`?", "The particular help is not stated.", "The particular help is stated."),
|
|
51
|
+
};
|
|
52
|
+
function parseRecoveryFields(fields) {
|
|
53
|
+
return fields.map((field) => {
|
|
24
54
|
const placeholder = field.placeholder?.trim() || undefined;
|
|
25
55
|
const options = field.options?.length ? field.options : undefined;
|
|
26
56
|
if (field.kind === "text") {
|
|
27
|
-
if (!placeholder)
|
|
57
|
+
if (!placeholder)
|
|
28
58
|
throw new Error("Text recovery fields require a placeholder.");
|
|
29
|
-
}
|
|
30
59
|
return { id: field.id, label: field.label, kind: field.kind, placeholder };
|
|
31
60
|
}
|
|
32
|
-
if (!options?.length)
|
|
61
|
+
if (!options?.length)
|
|
33
62
|
throw new Error("Single and multi recovery fields require options.");
|
|
34
|
-
}
|
|
35
63
|
return { id: field.id, label: field.label, kind: field.kind, options };
|
|
36
64
|
});
|
|
37
65
|
}
|
|
66
|
+
function readDecision(answers) {
|
|
67
|
+
const speech = answers.speech_act;
|
|
68
|
+
const speechAct = speech?.type === "choice" ? speech.choice : "";
|
|
69
|
+
if (speechAct !== "commissive" && speechAct !== "directive" && speechAct !== "declaration" && speechAct !== "other") {
|
|
70
|
+
throw new Error("Jev speech_act answer was unusable");
|
|
71
|
+
}
|
|
72
|
+
const probability = (key) => {
|
|
73
|
+
const answer = answers[key];
|
|
74
|
+
if (!answer || answer.type !== "noul")
|
|
75
|
+
throw new Error(`Jev ${key} answer was unusable`);
|
|
76
|
+
return answer.noul;
|
|
77
|
+
};
|
|
78
|
+
const constraints = Object.fromEntries(ADMISSION_CONSTRAINTS.map((name) => [name, probability(name)]));
|
|
79
|
+
return { speechAct, tooVague: probability("too_vague"), tooBroad: probability("too_broad"), constraints };
|
|
80
|
+
}
|
|
81
|
+
function textField(message) {
|
|
82
|
+
return { id: "detail", label: message, kind: "text", placeholder: "Add the missing detail" };
|
|
83
|
+
}
|
|
84
|
+
function isAbort(error) {
|
|
85
|
+
return error instanceof Error && error.name === "AbortError";
|
|
86
|
+
}
|
|
38
87
|
/** Folds recovery answers into a draft, admits its final form, and returns a dynamic recovery form on failure. Model failures propagate for retry. */
|
|
39
88
|
export class IntentPreparer {
|
|
40
|
-
constructor(verifier = new SemanticVerifier()) {
|
|
89
|
+
constructor(verifier = new SemanticVerifier(), decider = new DecisionClient()) {
|
|
41
90
|
this.verifier = verifier;
|
|
91
|
+
this.decider = decider;
|
|
42
92
|
}
|
|
43
93
|
/**
|
|
44
|
-
*
|
|
94
|
+
* Ask Jev whether the current rows would pass. Does not fold text or write questions.
|
|
95
|
+
* @param input - Opening draft and the answers already on the page.
|
|
96
|
+
* @param profileContext - The speaker's profile, if supplied by the host.
|
|
97
|
+
* @throws When Jev fails; the caller keeps the rows and retries.
|
|
98
|
+
*/
|
|
99
|
+
async check(input, profileContext = "") {
|
|
100
|
+
const answers = (input.answers ?? []).filter((answer) => answer.answer.trim());
|
|
101
|
+
const judged = admissionFromDecision(readDecision(await this.decider.decide({ draft: input.payload, answers, profile: profileContext }, ADMISSION_QUESTIONS)));
|
|
102
|
+
if (!judged.failure)
|
|
103
|
+
return { passed: true, missing: [], message: "" };
|
|
104
|
+
return { passed: false, missing: judged.missing, message: judged.failure.message };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Write one recovery field per constraint id. Does not run Jev.
|
|
108
|
+
* @param input - Opening draft and the answers already on the page.
|
|
109
|
+
* @param missing - Constraint ids that still need a row.
|
|
110
|
+
*/
|
|
111
|
+
async questions(input, missing) {
|
|
112
|
+
const answers = (input.answers ?? []).filter((answer) => answer.answer.trim());
|
|
113
|
+
const constraints = [...new Set(missing)].filter((id) => ADMISSION_CONSTRAINTS.includes(id));
|
|
114
|
+
if (!constraints.length)
|
|
115
|
+
return [];
|
|
116
|
+
return this.fieldsFor(input.payload, answers, "", constraints);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Fold answers, admit the draft, and return a recovery form when it still fails.
|
|
45
120
|
* @param input - Current payload and pending recovery answers.
|
|
46
121
|
* @param profileContext - The speaker's profile, if supplied by the host.
|
|
47
|
-
* @returns An admitted draft or admission feedback plus a recovery form.
|
|
48
122
|
* @throws When rewriting, verification, or recovery generation fails; callers must retain the input for retry.
|
|
49
123
|
*/
|
|
50
124
|
async invoke(input, profileContext = "") {
|
|
@@ -58,11 +132,28 @@ export class IntentPreparer {
|
|
|
58
132
|
]);
|
|
59
133
|
payload = payloadSchema.parse(result).payload;
|
|
60
134
|
}
|
|
135
|
+
let jevFailed = false;
|
|
136
|
+
try {
|
|
137
|
+
const judged = admissionFromDecision(readDecision(await this.decider.decide({ draft: payload, profile: profileContext }, ADMISSION_QUESTIONS)));
|
|
138
|
+
if (judged.failure)
|
|
139
|
+
return this.revise(payload, answers, judged.failure.message, judged.missing);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (isAbort(error))
|
|
143
|
+
throw error;
|
|
144
|
+
jevFailed = true;
|
|
145
|
+
}
|
|
61
146
|
const verdict = await this.verifier.invoke(payload, profileContext);
|
|
62
|
-
const failure = admissionFailure(
|
|
147
|
+
const failure = admissionFailure(verdict);
|
|
63
148
|
if (!failure)
|
|
64
149
|
return { status: "ready", payload, metadata: semanticMetadata(verdict) };
|
|
65
|
-
|
|
150
|
+
if (jevFailed)
|
|
151
|
+
return this.legacy(payload, answers, failure, verdict);
|
|
152
|
+
return this.revise(payload, answers, failure.message, verdict.missing_selectional_constraints);
|
|
153
|
+
}
|
|
154
|
+
/** Today's recovery form, used only when the Jev call itself fails. */
|
|
155
|
+
async legacy(payload, answers, failure, verdict) {
|
|
156
|
+
const model = createStructuredModel("intentClarifier", legacyRecoverySchema, { name: "intent_recovery" });
|
|
66
157
|
const result = await invokeWithAbortSignal(model, [
|
|
67
158
|
new SystemMessage("Build one recovery form that helps the person concretize their signal. Return 2-6 fields. Each field needs a draft-specific label, a kind (single, multi, or text), and for single/multi 2-5 concrete options anchored to the draft. Text fields need a placeholder. Never re-ask what the draft or answers already settle. Options must be matchable and specific. Do not expose scores, classifications, JSON, or internal vocabulary."),
|
|
68
159
|
new HumanMessage(JSON.stringify({
|
|
@@ -77,11 +168,27 @@ export class IntentPreparer {
|
|
|
77
168
|
missingConstraints: verdict.missing_selectional_constraints,
|
|
78
169
|
})),
|
|
79
170
|
]);
|
|
80
|
-
return {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
171
|
+
return { status: "needs_revision", payload, feedback: failure.message, recovery: parseRecoveryFields(legacyRecoverySchema.parse(result).recovery) };
|
|
172
|
+
}
|
|
173
|
+
/** One field per constraint still missing. None named means one text field from the admission message. */
|
|
174
|
+
async revise(payload, answers, feedback, missing) {
|
|
175
|
+
const constraints = [...new Set(missing)];
|
|
176
|
+
const recovery = constraints.length === 0
|
|
177
|
+
? [textField(feedback)]
|
|
178
|
+
: await this.fieldsFor(payload, answers, feedback, constraints);
|
|
179
|
+
return { status: "needs_revision", payload, feedback, recovery };
|
|
180
|
+
}
|
|
181
|
+
async fieldsFor(payload, answers, feedback, constraints) {
|
|
182
|
+
const model = createStructuredModel("intentClarifier", recoverySchema, { name: "intent_recovery" });
|
|
183
|
+
const result = await invokeWithAbortSignal(model, [
|
|
184
|
+
new SystemMessage(`Write one recovery field for each missing constraint, in this order: ${constraints.join(", ")}. Set each field's id to that constraint name. Each field needs a draft-specific label, a kind (single, multi, or text), and for single/multi 2-5 concrete options anchored to the draft. Text fields need a placeholder. Do not add any other field. Never re-ask what the draft or answers already settle. Do not expose scores, classifications, JSON, or internal vocabulary.`),
|
|
185
|
+
new HumanMessage(JSON.stringify({ payload, answers, feedback, missingConstraints: constraints })),
|
|
186
|
+
]);
|
|
187
|
+
const fields = parseRecoveryFields(recoverySchema.parse(result).recovery);
|
|
188
|
+
const byId = new Map(fields.map((field) => [field.id, field]));
|
|
189
|
+
const matched = constraints.map((id) => byId.get(id));
|
|
190
|
+
if (matched.some((field) => !field))
|
|
191
|
+
throw new Error("Recovery fields did not cover the missing constraints.");
|
|
192
|
+
return matched;
|
|
86
193
|
}
|
|
87
194
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opportunity cards: presenter-texted cards for a viewer, as a list or one at
|
|
3
|
+
* a time. The list loads, filters, and dedupes the viewer's opportunities,
|
|
4
|
+
* serves cached cards, and presents the misses through OpportunityPresenter.
|
|
5
|
+
*/
|
|
6
|
+
import type { Opportunity, OpportunityCardsDatabase, OpportunityStatus } from '../../platform/database.js';
|
|
7
|
+
import type { OpportunityCache } from '../../platform/discovery/cache.js';
|
|
8
|
+
import { OpportunityPresenter } from './opportunity.presentation.js';
|
|
9
|
+
/** One opportunity with its presenter-driven display contract. */
|
|
10
|
+
export interface OpportunityCard {
|
|
11
|
+
opportunityId: string;
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
/** Lifecycle status of the underlying opportunity at render time. */
|
|
14
|
+
status?: OpportunityStatus;
|
|
15
|
+
userId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
avatar: string | null;
|
|
18
|
+
mainText: string;
|
|
19
|
+
cta: string;
|
|
20
|
+
headline?: string;
|
|
21
|
+
primaryActionLabel: string;
|
|
22
|
+
secondaryActionLabel: string;
|
|
23
|
+
mutualIntentsLabel: string;
|
|
24
|
+
narratorChip?: {
|
|
25
|
+
name: string;
|
|
26
|
+
text: string;
|
|
27
|
+
avatar?: string | null;
|
|
28
|
+
userId?: string;
|
|
29
|
+
};
|
|
30
|
+
/** Viewer's role in this opportunity (e.g. 'party', 'agent', 'patient', 'peer'). */
|
|
31
|
+
viewerRole?: string;
|
|
32
|
+
/**
|
|
33
|
+
* True for a skeleton card: identity fields are real but mainText/cta are
|
|
34
|
+
* empty because the presenter was skipped. Never cached.
|
|
35
|
+
*/
|
|
36
|
+
presentationPending?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** Everything card presentation reaches for. */
|
|
39
|
+
export interface OpportunityCardsDeps {
|
|
40
|
+
database: OpportunityCardsDatabase;
|
|
41
|
+
cache: OpportunityCache;
|
|
42
|
+
presenter: OpportunityPresenter;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Present one opportunity as a card for the viewer.
|
|
46
|
+
*
|
|
47
|
+
* @param deps - Database, cache, and presenter.
|
|
48
|
+
* @param opportunity - The opportunity to present.
|
|
49
|
+
* @param viewerId - The viewing user.
|
|
50
|
+
* @param options - `intentId` focuses viewer context on one intent; `skeleton` skips the presenter.
|
|
51
|
+
* @returns The card, or null when the counterpart's name cannot be resolved.
|
|
52
|
+
* @throws When the presenter fails.
|
|
53
|
+
*/
|
|
54
|
+
export declare function presentOpportunityCard(deps: OpportunityCardsDeps, opportunity: Opportunity, viewerId: string, options?: {
|
|
55
|
+
intentId?: string;
|
|
56
|
+
skeleton?: boolean;
|
|
57
|
+
}): Promise<OpportunityCard | null>;
|
|
58
|
+
/**
|
|
59
|
+
* List the viewer's opportunities in the given statuses as presented cards,
|
|
60
|
+
* newest first, one card per counterpart. Cards whose presentation fails are
|
|
61
|
+
* dropped.
|
|
62
|
+
*
|
|
63
|
+
* @param deps - Database, cache, and presenter.
|
|
64
|
+
* @param input - Viewer, statuses, optional network/intent scope, limit, cache bypass, skeleton mode.
|
|
65
|
+
* @returns The cards and the number of opportunities selected.
|
|
66
|
+
*/
|
|
67
|
+
export declare function listOpportunityCards(deps: OpportunityCardsDeps, input: {
|
|
68
|
+
viewerId: string;
|
|
69
|
+
statuses: OpportunityStatus[];
|
|
70
|
+
networkId?: string;
|
|
71
|
+
intentId?: string;
|
|
72
|
+
limit: number;
|
|
73
|
+
noCache?: boolean;
|
|
74
|
+
skeleton?: boolean;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
cards: OpportunityCard[];
|
|
77
|
+
totalOpportunities: number;
|
|
78
|
+
}>;
|