@indexnetwork/protocol 14.3.2-rc.477.1 → 16.1.1-rc.478.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 +107 -0
- package/IMPLEMENTATION.md +50 -52
- package/STABILITY.md +3 -3
- package/dist/chat/chat.agent.js +2 -2
- package/dist/chat/chat.graph.d.ts +41 -1
- package/dist/chat/chat.graph.js +108 -127
- package/dist/discovery/hyde.frame.d.ts +4 -4
- package/dist/discovery/hyde.graph.d.ts +71 -12
- package/dist/discovery/hyde.graph.js +337 -335
- package/dist/discovery/lens.inferrer.d.ts +6 -6
- package/dist/enrichment/enrichment.graph.d.ts +127 -9
- package/dist/enrichment/enrichment.graph.js +633 -655
- package/dist/enrichment/enrichment.state.d.ts +1 -1
- package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
- package/dist/enrichment/enrichment.tools.context-read.js +353 -0
- package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
- package/dist/enrichment/enrichment.tools.context-write.js +417 -0
- package/dist/enrichment/enrichment.tools.d.ts +11 -1
- package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
- package/dist/enrichment/enrichment.tools.helpers.js +234 -0
- package/dist/enrichment/enrichment.tools.js +16 -975
- package/dist/index.d.ts +27 -48
- package/dist/index.js +17 -27
- package/dist/intents/application/intent.graph.d.ts +71 -67
- package/dist/intents/application/intent.graph.execute.d.ts +59 -0
- package/dist/intents/application/intent.graph.execute.js +301 -0
- package/dist/intents/application/intent.graph.infer.d.ts +44 -0
- package/dist/intents/application/intent.graph.infer.js +97 -0
- package/dist/intents/application/intent.graph.js +96 -888
- package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
- package/dist/intents/application/intent.graph.reconcile.js +274 -0
- package/dist/intents/application/intent.graph.shared.d.ts +70 -0
- package/dist/intents/application/intent.graph.shared.js +153 -0
- package/dist/intents/domain/intent.state.d.ts +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +66 -3
- package/dist/maintenance/maintenance.graph.js +155 -156
- package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
- package/dist/mcp/mcp.authorization-policy.js +14 -34
- package/dist/mcp/mcp.server.d.ts +3 -3
- package/dist/mcp/mcp.server.js +12 -16
- package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
- package/dist/negotiations/application/negotiation.candidates.js +162 -0
- package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
- package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
- package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
- package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
- package/dist/negotiations/application/negotiation.graph.init.js +227 -0
- package/dist/negotiations/application/negotiation.graph.js +69 -1388
- package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
- package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
- package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
- package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
- package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
- package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
- package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
- package/dist/negotiations/domain/negotiation.state.js +0 -1
- package/dist/networks/application/indexer.graph.d.ts +165 -7
- package/dist/networks/application/indexer.graph.js +339 -388
- package/dist/networks/application/indexer.state.d.ts +1 -1
- package/dist/networks/application/membership.graph.d.ts +83 -5
- package/dist/networks/application/membership.graph.js +177 -207
- package/dist/networks/application/network.graph.d.ts +148 -5
- package/dist/networks/application/network.graph.js +249 -278
- package/dist/networks/domain/membership.state.d.ts +1 -1
- package/dist/networks/domain/network.state.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.js +2 -2
- package/dist/opportunities/application/index.d.ts +2 -2
- package/dist/opportunities/application/index.js +2 -2
- package/dist/opportunities/application/opportunity.evaluator.js +1 -1
- package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
- package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
- package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
- package/dist/opportunities/application/opportunity.graph.js +120 -3690
- package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
- package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
- package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
- package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
- package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
- package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
- package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
- package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
- package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
- package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
- package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
- package/dist/opportunities/application/opportunity.tools.js +28 -711
- package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
- package/dist/opportunities/application/opportunity.tools.list.js +493 -0
- package/dist/opportunities/domain/index.d.ts +3 -3
- package/dist/opportunities/domain/index.js +3 -3
- package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
- package/dist/opportunities/index.d.ts +6 -6
- package/dist/opportunities/index.js +4 -4
- package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
- package/dist/opportunities/radar/radar.graph.d.ts +57 -13
- package/dist/opportunities/radar/radar.graph.js +470 -471
- package/dist/premises/premise.graph.d.ts +88 -20
- package/dist/premises/premise.graph.js +207 -218
- package/dist/questions/domain/question.schema.d.ts +40 -40
- package/dist/shared/agent/tool.helpers.d.ts +21 -0
- package/dist/shared/agent/tool.registry.d.ts +4 -3
- package/dist/shared/agent/tool.registry.js +1 -1
- package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
- package/dist/shared/interfaces/database.capabilities.js +7 -0
- package/dist/shared/interfaces/database.entities.d.ts +533 -0
- package/dist/shared/interfaces/database.entities.js +10 -0
- package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
- package/dist/shared/interfaces/database.identity-queries.js +4 -0
- package/dist/shared/interfaces/database.interface.d.ts +15 -2277
- package/dist/shared/interfaces/database.interface.js +8 -0
- package/dist/shared/interfaces/database.member-queries.d.ts +228 -0
- package/dist/shared/interfaces/database.member-queries.js +4 -0
- package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
- package/dist/shared/interfaces/database.negotiation.js +26 -0
- package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
- package/dist/shared/interfaces/database.network-queries.js +4 -0
- package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
- package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
- package/dist/shared/interfaces/database.port.d.ts +322 -0
- package/dist/shared/interfaces/database.port.js +29 -0
- package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
- package/package.json +2 -15
- package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
- package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
- package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
- package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
- package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
- package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
- package/dist/opportunities/domain/opportunity.presentation.js +0 -516
- package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
- package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
|
@@ -6,413 +6,364 @@ import { requestContext } from "../../shared/observability/request-context.js";
|
|
|
6
6
|
import { renderNetworkContext } from "../../shared/network/metadata.renderer.js";
|
|
7
7
|
import { IntentNetworkGraphState } from "./indexer.state.js";
|
|
8
8
|
const logger = protocolLogger("IntentNetworkGraphFactory");
|
|
9
|
-
/**
|
|
10
|
-
* Factory class to build and compile the Intent–Network (indexer) Graph.
|
|
11
|
-
*
|
|
12
|
-
* Handles CRUD for the intent_indexes junction table:
|
|
13
|
-
* - create: Assign an intent to a network (direct or LLM-evaluated)
|
|
14
|
-
* - read: List intent–network links (by intentId or by networkId)
|
|
15
|
-
* - delete: Unassign an intent from a network
|
|
16
|
-
*
|
|
17
|
-
* ## Signal assignment policy
|
|
18
|
-
*
|
|
19
|
-
* The `IntentIndexer` is injected at construction time from the signals public
|
|
20
|
-
* facade via the communities ports layer — this factory never imports signals
|
|
21
|
-
* application internals directly.
|
|
22
|
-
*
|
|
23
|
-
* Two assignment paths:
|
|
24
|
-
* 1. Direct (`skipEvaluation: true`):
|
|
25
|
-
* - Writes the link immediately with `mode: manual_override`, `score: 1`.
|
|
26
|
-
* 2. Evaluated (`skipEvaluation: false`):
|
|
27
|
-
* - Loads intent + network context (indexPrompt, memberPrompt).
|
|
28
|
-
* - No-prompt fast path: if both prompts are absent, assigns with
|
|
29
|
-
* `mode: automatic, promptPresence: 'none'` without calling the LLM.
|
|
30
|
-
* - Otherwise invokes IntentIndexer to get indexScore + memberScore + reasoning.
|
|
31
|
-
* - Applies `buildNetworkAssignmentDecision` to produce the threshold/metadata.
|
|
32
|
-
* - Only writes the link when the decision's `assigned` flag is true.
|
|
33
|
-
*
|
|
34
|
-
* ## Membership authority for assignment
|
|
35
|
-
*
|
|
36
|
-
* A user may only assign their own intents.
|
|
37
|
-
* The user must be a member (or owner) of the target network.
|
|
38
|
-
* Assignment is rejected if membership has been revoked between intent creation
|
|
39
|
-
* and assignment time (`assignIntentToNetworkIfMember` returns `membership_required`).
|
|
40
|
-
*
|
|
41
|
-
* Flow:
|
|
42
|
-
* START → router → {
|
|
43
|
-
* create: assignNode (direct or evaluated) → END
|
|
44
|
-
* read: readNode → END
|
|
45
|
-
* delete: unassignNode → END
|
|
46
|
-
* }
|
|
47
|
-
*
|
|
48
|
-
* IND-546: canonical home — previously network/indexer/indexer.graph.ts.
|
|
49
|
-
* Signals dependency consumed via capabilities/signals.facade.ts (through ports).
|
|
50
|
-
*/
|
|
51
9
|
export class IntentNetworkGraphFactory {
|
|
52
10
|
constructor(database, intentNetworker) {
|
|
53
|
-
this.
|
|
54
|
-
this.intentNetworker = intentNetworker;
|
|
11
|
+
this.deps = { database, intentNetworker };
|
|
55
12
|
}
|
|
56
13
|
createGraph() {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
14
|
+
const deps = this.deps;
|
|
15
|
+
// --- GRAPH ASSEMBLY ---
|
|
16
|
+
const workflow = new StateGraph(IntentNetworkGraphState)
|
|
17
|
+
.addNode("assign", (state) => assignNode(state, deps))
|
|
18
|
+
.addNode("read", (state) => readNode(state, deps))
|
|
19
|
+
.addNode("unassign", (state) => unassignNode(state, deps))
|
|
20
|
+
.addConditionalEdges(START, routeByMode, {
|
|
21
|
+
assign: "assign",
|
|
22
|
+
read: "read",
|
|
23
|
+
unassign: "unassign",
|
|
24
|
+
})
|
|
25
|
+
.addEdge("assign", END)
|
|
26
|
+
.addEdge("read", END)
|
|
27
|
+
.addEdge("unassign", END);
|
|
28
|
+
return workflow.compile();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Persist an intent→network assignment, honouring membership, and shape the tool result.
|
|
33
|
+
*/
|
|
34
|
+
export async function finalizeAssignment(deps, userId, intentId, networkId, score, metadata, successMessage) {
|
|
35
|
+
const outcome = await deps.database.assignIntentToNetworkIfMember(userId, intentId, networkId, score, metadata);
|
|
36
|
+
if (outcome.kind === 'assigned') {
|
|
37
|
+
return {
|
|
38
|
+
assignmentResult: { networkId, assigned: true, success: true },
|
|
39
|
+
mutationResult: { success: true, message: successMessage },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (outcome.kind === 'already_assigned') {
|
|
43
|
+
return {
|
|
44
|
+
assignmentResult: { networkId, assigned: true, success: true },
|
|
45
|
+
mutationResult: { success: true, message: 'That intent is already in this network.' },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (outcome.kind === 'membership_required') {
|
|
49
|
+
return {
|
|
50
|
+
assignmentResult: { networkId, assigned: false, success: false },
|
|
51
|
+
mutationResult: { success: false, error: 'Current network membership is required to assign this intent.' },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
assignmentResult: { networkId, assigned: false, success: false },
|
|
56
|
+
mutationResult: { success: false, error: 'Intent is no longer available for assignment.' },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function assignNode(state, deps) {
|
|
60
|
+
return timed("IntentNetworkGraph.assign", async () => {
|
|
61
|
+
const intentId = state.intentId;
|
|
62
|
+
const networkId = state.networkId;
|
|
63
|
+
logger.verbose("Assign intent to network", { userId: state.userId, intentId, networkId, skipEvaluation: state.skipEvaluation });
|
|
64
|
+
const agentTimingsAccum = [];
|
|
65
|
+
if (!intentId || !networkId) {
|
|
66
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "Both intentId and networkId are required." } };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// Validate ownership and membership
|
|
70
|
+
const intent = await deps.database.getIntent(intentId);
|
|
71
|
+
if (!intent) {
|
|
72
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "Intent not found." } };
|
|
73
|
+
}
|
|
74
|
+
if (intent.userId !== state.userId) {
|
|
75
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "You can only add your own intents to a network." } };
|
|
76
|
+
}
|
|
77
|
+
const [isMember, isOwner] = await Promise.all([
|
|
78
|
+
deps.database.isNetworkMember(networkId, state.userId),
|
|
79
|
+
deps.database.isIndexOwner(networkId, state.userId),
|
|
80
|
+
]);
|
|
81
|
+
if (!isMember && !isOwner) {
|
|
82
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "You are not a member of that network." } };
|
|
83
|
+
}
|
|
84
|
+
// Check if already assigned
|
|
85
|
+
const alreadyAssigned = await deps.database.isIntentAssignedToIndex(intentId, networkId);
|
|
86
|
+
if (alreadyAssigned) {
|
|
87
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: true, message: "That intent is already in this network." } };
|
|
88
|
+
}
|
|
89
|
+
// Direct assignment (skip LLM evaluation)
|
|
90
|
+
if (state.skipEvaluation) {
|
|
91
|
+
const decision = buildNetworkAssignmentDecision({
|
|
92
|
+
resourceType: "intent",
|
|
93
|
+
mode: "manual_override",
|
|
94
|
+
scope: "network",
|
|
95
|
+
evaluator: "intent-network-graph",
|
|
96
|
+
source: "manual-index-assignment",
|
|
97
|
+
createdAt: new Date().toISOString(),
|
|
98
|
+
});
|
|
99
|
+
const finalized = await finalizeAssignment(deps, state.userId, intentId, networkId, decision.finalScore, decision.metadata, 'Intent saved to the network.');
|
|
100
|
+
return { agentTimings: agentTimingsAccum, ...finalized };
|
|
101
|
+
}
|
|
102
|
+
// Evaluated assignment path
|
|
103
|
+
const intentForIndexing = await deps.database.getIntentForIndexing(intentId);
|
|
104
|
+
if (!intentForIndexing) {
|
|
105
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "Intent not found for evaluation." } };
|
|
106
|
+
}
|
|
107
|
+
const indexContext = await deps.database.getNetworkAssignmentContext(networkId, intentForIndexing.userId);
|
|
108
|
+
if (!indexContext) {
|
|
61
109
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
110
|
+
agentTimings: agentTimingsAccum,
|
|
111
|
+
assignmentResult: { networkId, assigned: false, success: false },
|
|
112
|
+
mutationResult: { success: false, error: "Network assignment context not found." },
|
|
64
113
|
};
|
|
65
114
|
}
|
|
66
|
-
|
|
115
|
+
const indexPrompt = indexContext.indexPrompt ?? null;
|
|
116
|
+
const memberPrompt = indexContext.memberPrompt ?? null;
|
|
117
|
+
const hasNoPrompts = !indexPrompt?.trim() && !memberPrompt?.trim();
|
|
118
|
+
// No-prompt fast path: assign without LLM
|
|
119
|
+
if (hasNoPrompts) {
|
|
120
|
+
const decision = buildNetworkAssignmentDecision({
|
|
121
|
+
resourceType: "intent",
|
|
122
|
+
mode: "automatic",
|
|
123
|
+
scope: "network",
|
|
124
|
+
indexPrompt,
|
|
125
|
+
memberPrompt,
|
|
126
|
+
evaluator: "intent-networker",
|
|
127
|
+
source: "intent-network-graph",
|
|
128
|
+
createdAt: new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
const finalized = await finalizeAssignment(deps, state.userId, intentId, networkId, decision.finalScore, decision.metadata, 'Intent assigned to network (no prompts).');
|
|
131
|
+
return { agentTimings: agentTimingsAccum, ...finalized };
|
|
132
|
+
}
|
|
133
|
+
// Render network context for the evaluator
|
|
134
|
+
const network = await deps.database.getNetwork(networkId);
|
|
135
|
+
const renderedContext = network
|
|
136
|
+
? renderNetworkContext({
|
|
137
|
+
title: network.title,
|
|
138
|
+
prompt: network.prompt,
|
|
139
|
+
})
|
|
140
|
+
: null;
|
|
141
|
+
const sourceName = intentForIndexing.sourceType
|
|
142
|
+
? `${intentForIndexing.sourceType}:${intentForIndexing.sourceId ?? ""}`
|
|
143
|
+
: undefined;
|
|
144
|
+
// Run IntentIndexer evaluation (injected from signals public facade via ports)
|
|
145
|
+
const _traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
146
|
+
const _indexerStart = Date.now();
|
|
147
|
+
_traceEmitter?.({ type: "agent_start", name: "intent-networker" });
|
|
148
|
+
let result = null;
|
|
149
|
+
try {
|
|
150
|
+
result = await deps.intentNetworker.evaluate(intentForIndexing.payload, indexPrompt, memberPrompt, sourceName, renderedContext);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
const _indexerMs = Date.now() - _indexerStart;
|
|
154
|
+
agentTimingsAccum.push({ name: 'intent.indexer', durationMs: _indexerMs });
|
|
155
|
+
_traceEmitter?.({
|
|
156
|
+
type: "agent_end",
|
|
157
|
+
name: "intent-networker",
|
|
158
|
+
durationMs: _indexerMs,
|
|
159
|
+
summary: result
|
|
160
|
+
? `Scored: index=${result.indexScore.toFixed(2)}, member=${result.memberScore.toFixed(2)}`
|
|
161
|
+
: "intent-networker failed",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (!result) {
|
|
67
165
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
166
|
+
agentTimings: agentTimingsAccum,
|
|
167
|
+
evaluation: null,
|
|
168
|
+
shouldAssign: false,
|
|
169
|
+
finalScore: 0,
|
|
170
|
+
mutationResult: { success: false, error: "Evaluation returned no result." },
|
|
70
171
|
};
|
|
71
172
|
}
|
|
72
|
-
|
|
173
|
+
const decision = buildNetworkAssignmentDecision({
|
|
174
|
+
resourceType: "intent",
|
|
175
|
+
mode: "automatic",
|
|
176
|
+
scope: "network",
|
|
177
|
+
indexPrompt,
|
|
178
|
+
memberPrompt,
|
|
179
|
+
rawScores: { indexScore: result.indexScore, memberScore: result.memberScore },
|
|
180
|
+
evaluator: "intent-networker",
|
|
181
|
+
source: "intent-network-graph",
|
|
182
|
+
reason: result.reasoning,
|
|
183
|
+
createdAt: new Date().toISOString(),
|
|
184
|
+
});
|
|
185
|
+
if (decision.assigned) {
|
|
186
|
+
const finalized = await finalizeAssignment(deps, state.userId, intentId, networkId, decision.finalScore, decision.metadata, `Intent assigned to network (score: ${decision.finalScore.toFixed(2)}).`);
|
|
73
187
|
return {
|
|
74
|
-
|
|
75
|
-
|
|
188
|
+
agentTimings: agentTimingsAccum,
|
|
189
|
+
evaluation: result,
|
|
190
|
+
shouldAssign: finalized.assignmentResult.assigned,
|
|
191
|
+
finalScore: decision.finalScore,
|
|
192
|
+
...finalized,
|
|
76
193
|
};
|
|
77
194
|
}
|
|
78
195
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
196
|
+
agentTimings: agentTimingsAccum,
|
|
197
|
+
evaluation: result,
|
|
198
|
+
shouldAssign: false,
|
|
199
|
+
finalScore: decision.finalScore,
|
|
200
|
+
assignmentResult: { networkId, assigned: false, success: true },
|
|
201
|
+
mutationResult: { success: false, error: `Intent did not qualify for this network (score: ${decision.finalScore.toFixed(2)}).` },
|
|
81
202
|
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
const [isMember, isOwner] = await Promise.all([
|
|
110
|
-
this.database.isNetworkMember(networkId, state.userId),
|
|
111
|
-
this.database.isIndexOwner(networkId, state.userId),
|
|
112
|
-
]);
|
|
113
|
-
if (!isMember && !isOwner) {
|
|
114
|
-
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "You are not a member of that network." } };
|
|
115
|
-
}
|
|
116
|
-
// Check if already assigned
|
|
117
|
-
const alreadyAssigned = await this.database.isIntentAssignedToIndex(intentId, networkId);
|
|
118
|
-
if (alreadyAssigned) {
|
|
119
|
-
return { agentTimings: agentTimingsAccum, mutationResult: { success: true, message: "That intent is already in this network." } };
|
|
120
|
-
}
|
|
121
|
-
// Direct assignment (skip LLM evaluation)
|
|
122
|
-
if (state.skipEvaluation) {
|
|
123
|
-
const decision = buildNetworkAssignmentDecision({
|
|
124
|
-
resourceType: "intent",
|
|
125
|
-
mode: "manual_override",
|
|
126
|
-
scope: "network",
|
|
127
|
-
evaluator: "intent-network-graph",
|
|
128
|
-
source: "manual-index-assignment",
|
|
129
|
-
createdAt: new Date().toISOString(),
|
|
130
|
-
});
|
|
131
|
-
const finalized = await finalizeAssignment(state.userId, intentId, networkId, decision.finalScore, decision.metadata, 'Intent saved to the network.');
|
|
132
|
-
return { agentTimings: agentTimingsAccum, ...finalized };
|
|
133
|
-
}
|
|
134
|
-
// Evaluated assignment path
|
|
135
|
-
const intentForIndexing = await this.database.getIntentForIndexing(intentId);
|
|
136
|
-
if (!intentForIndexing) {
|
|
137
|
-
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "Intent not found for evaluation." } };
|
|
138
|
-
}
|
|
139
|
-
const indexContext = await this.database.getNetworkAssignmentContext(networkId, intentForIndexing.userId);
|
|
140
|
-
if (!indexContext) {
|
|
141
|
-
return {
|
|
142
|
-
agentTimings: agentTimingsAccum,
|
|
143
|
-
assignmentResult: { networkId, assigned: false, success: false },
|
|
144
|
-
mutationResult: { success: false, error: "Network assignment context not found." },
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
const indexPrompt = indexContext.indexPrompt ?? null;
|
|
148
|
-
const memberPrompt = indexContext.memberPrompt ?? null;
|
|
149
|
-
const hasNoPrompts = !indexPrompt?.trim() && !memberPrompt?.trim();
|
|
150
|
-
// No-prompt fast path: assign without LLM
|
|
151
|
-
if (hasNoPrompts) {
|
|
152
|
-
const decision = buildNetworkAssignmentDecision({
|
|
153
|
-
resourceType: "intent",
|
|
154
|
-
mode: "automatic",
|
|
155
|
-
scope: "network",
|
|
156
|
-
indexPrompt,
|
|
157
|
-
memberPrompt,
|
|
158
|
-
evaluator: "intent-networker",
|
|
159
|
-
source: "intent-network-graph",
|
|
160
|
-
createdAt: new Date().toISOString(),
|
|
161
|
-
});
|
|
162
|
-
const finalized = await finalizeAssignment(state.userId, intentId, networkId, decision.finalScore, decision.metadata, 'Intent assigned to network (no prompts).');
|
|
163
|
-
return { agentTimings: agentTimingsAccum, ...finalized };
|
|
164
|
-
}
|
|
165
|
-
// Render network context for the evaluator
|
|
166
|
-
const network = await this.database.getNetwork(networkId);
|
|
167
|
-
const renderedContext = network
|
|
168
|
-
? renderNetworkContext({
|
|
169
|
-
title: network.title,
|
|
170
|
-
prompt: network.prompt,
|
|
171
|
-
})
|
|
172
|
-
: null;
|
|
173
|
-
const sourceName = intentForIndexing.sourceType
|
|
174
|
-
? `${intentForIndexing.sourceType}:${intentForIndexing.sourceId ?? ""}`
|
|
175
|
-
: undefined;
|
|
176
|
-
// Run IntentIndexer evaluation (injected from signals public facade via ports)
|
|
177
|
-
const _traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
178
|
-
const _indexerStart = Date.now();
|
|
179
|
-
_traceEmitter?.({ type: "agent_start", name: "intent-networker" });
|
|
180
|
-
let result = null;
|
|
181
|
-
try {
|
|
182
|
-
result = await indexer.evaluate(intentForIndexing.payload, indexPrompt, memberPrompt, sourceName, renderedContext);
|
|
183
|
-
}
|
|
184
|
-
finally {
|
|
185
|
-
const _indexerMs = Date.now() - _indexerStart;
|
|
186
|
-
agentTimingsAccum.push({ name: 'intent.indexer', durationMs: _indexerMs });
|
|
187
|
-
_traceEmitter?.({
|
|
188
|
-
type: "agent_end",
|
|
189
|
-
name: "intent-networker",
|
|
190
|
-
durationMs: _indexerMs,
|
|
191
|
-
summary: result
|
|
192
|
-
? `Scored: index=${result.indexScore.toFixed(2)}, member=${result.memberScore.toFixed(2)}`
|
|
193
|
-
: "intent-networker failed",
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
if (!result) {
|
|
197
|
-
return {
|
|
198
|
-
agentTimings: agentTimingsAccum,
|
|
199
|
-
evaluation: null,
|
|
200
|
-
shouldAssign: false,
|
|
201
|
-
finalScore: 0,
|
|
202
|
-
mutationResult: { success: false, error: "Evaluation returned no result." },
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
const decision = buildNetworkAssignmentDecision({
|
|
206
|
-
resourceType: "intent",
|
|
207
|
-
mode: "automatic",
|
|
208
|
-
scope: "network",
|
|
209
|
-
indexPrompt,
|
|
210
|
-
memberPrompt,
|
|
211
|
-
rawScores: { indexScore: result.indexScore, memberScore: result.memberScore },
|
|
212
|
-
evaluator: "intent-networker",
|
|
213
|
-
source: "intent-network-graph",
|
|
214
|
-
reason: result.reasoning,
|
|
215
|
-
createdAt: new Date().toISOString(),
|
|
216
|
-
});
|
|
217
|
-
if (decision.assigned) {
|
|
218
|
-
const finalized = await finalizeAssignment(state.userId, intentId, networkId, decision.finalScore, decision.metadata, `Intent assigned to network (score: ${decision.finalScore.toFixed(2)}).`);
|
|
219
|
-
return {
|
|
220
|
-
agentTimings: agentTimingsAccum,
|
|
221
|
-
evaluation: result,
|
|
222
|
-
shouldAssign: finalized.assignmentResult.assigned,
|
|
223
|
-
finalScore: decision.finalScore,
|
|
224
|
-
...finalized,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
agentTimings: agentTimingsAccum,
|
|
229
|
-
evaluation: result,
|
|
230
|
-
shouldAssign: false,
|
|
231
|
-
finalScore: decision.finalScore,
|
|
232
|
-
assignmentResult: { networkId, assigned: false, success: true },
|
|
233
|
-
mutationResult: { success: false, error: `Intent did not qualify for this network (score: ${decision.finalScore.toFixed(2)}).` },
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
logger.error("Assign failed", { error: err });
|
|
238
|
-
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "Failed to assign intent to network." } };
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
};
|
|
242
|
-
/**
|
|
243
|
-
* Read Node: Query intent–network relationships.
|
|
244
|
-
*
|
|
245
|
-
* Three modes (selected by which inputs are provided):
|
|
246
|
-
* - Both intentId + networkId: check if the specific link exists (owner only).
|
|
247
|
-
* - intentId only: list all networks the intent is linked to (owner only).
|
|
248
|
-
* - networkId only: list intents in the network (member only, up to 50).
|
|
249
|
-
* Add `queryUserId` to filter to one member's intents.
|
|
250
|
-
*/
|
|
251
|
-
const readNode = async (state) => {
|
|
252
|
-
return timed("IntentNetworkGraph.read", async () => {
|
|
253
|
-
const intentId = state.intentId;
|
|
254
|
-
const networkId = state.networkId;
|
|
255
|
-
logger.verbose("Read intent–network links", { userId: state.userId, intentId, networkId, queryUserId: state.queryUserId });
|
|
256
|
-
try {
|
|
257
|
-
// By both: check if specific intent–network link exists
|
|
258
|
-
if (intentId && networkId) {
|
|
259
|
-
const intent = await this.database.getIntent(intentId);
|
|
260
|
-
if (!intent) {
|
|
261
|
-
return { readResult: { links: [], count: 0, mode: "check_link" }, error: "Intent not found." };
|
|
262
|
-
}
|
|
263
|
-
if (intent.userId !== state.userId) {
|
|
264
|
-
return { readResult: { links: [], count: 0, mode: "check_link" }, error: "You can only check links for your own intents." };
|
|
265
|
-
}
|
|
266
|
-
const isLinked = await this.database.isIntentAssignedToIndex(intentId, networkId);
|
|
267
|
-
return {
|
|
268
|
-
readResult: {
|
|
269
|
-
links: isLinked ? [{ intentId, networkId }] : [],
|
|
270
|
-
count: isLinked ? 1 : 0,
|
|
271
|
-
mode: "check_link",
|
|
272
|
-
note: isLinked ? "Intent is linked to this network." : "Intent is not linked to this network.",
|
|
273
|
-
},
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
// By intent only: list all networks for this intent
|
|
277
|
-
if (intentId) {
|
|
278
|
-
const intent = await this.database.getIntent(intentId);
|
|
279
|
-
if (!intent) {
|
|
280
|
-
return { readResult: { links: [], count: 0, mode: "networks_for_intent" }, error: "Intent not found." };
|
|
281
|
-
}
|
|
282
|
-
if (intent.userId !== state.userId) {
|
|
283
|
-
return { readResult: { links: [], count: 0, mode: "networks_for_intent" }, error: "You can only list networks for your own intents." };
|
|
284
|
-
}
|
|
285
|
-
const networkIds = await this.database.getNetworkIdsForIntent(intentId);
|
|
286
|
-
return {
|
|
287
|
-
readResult: {
|
|
288
|
-
links: networkIds.map((id) => ({ intentId, networkId: id })),
|
|
289
|
-
count: networkIds.length,
|
|
290
|
-
mode: "networks_for_intent",
|
|
291
|
-
note: "To show network titles, use read_networks.",
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
// By network: list intents in the network
|
|
296
|
-
if (!networkId) {
|
|
297
|
-
return {
|
|
298
|
-
readResult: { links: [], count: 0, mode: "unknown" },
|
|
299
|
-
error: "Provide networkId or intentId.",
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
const [isMember, isOwner] = await Promise.all([
|
|
303
|
-
this.database.isNetworkMember(networkId, state.userId),
|
|
304
|
-
this.database.isIndexOwner(networkId, state.userId),
|
|
305
|
-
]);
|
|
306
|
-
if (!isMember && !isOwner) {
|
|
307
|
-
return {
|
|
308
|
-
readResult: { links: [], count: 0, mode: "intents_in_network" },
|
|
309
|
-
error: "Network not found or you are not a member.",
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
if (!state.queryUserId) {
|
|
313
|
-
const intents = await this.database.getNetworkIntentsForMember(networkId, state.userId, { limit: 50, offset: 0 });
|
|
314
|
-
return {
|
|
315
|
-
readResult: {
|
|
316
|
-
links: intents.map((i) => ({
|
|
317
|
-
intentId: i.id,
|
|
318
|
-
networkId,
|
|
319
|
-
intentTitle: i.payload,
|
|
320
|
-
userId: i.userId,
|
|
321
|
-
userName: i.userName,
|
|
322
|
-
createdAt: i.createdAt,
|
|
323
|
-
relevancyScore: i.relevancyScore,
|
|
324
|
-
})),
|
|
325
|
-
count: intents.length,
|
|
326
|
-
mode: "intents_in_network",
|
|
327
|
-
note: "To show network title and full intent details, use read_networks and read_intents.",
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
// Specific user's intents in the network
|
|
332
|
-
const intents = await this.database.getIntentsInIndexForMember(state.queryUserId, networkId);
|
|
333
|
-
return {
|
|
334
|
-
readResult: {
|
|
335
|
-
links: intents.map((i) => ({
|
|
336
|
-
intentId: i.id,
|
|
337
|
-
networkId,
|
|
338
|
-
intentTitle: i.payload,
|
|
339
|
-
createdAt: i.createdAt,
|
|
340
|
-
relevancyScore: i.relevancyScore,
|
|
341
|
-
})),
|
|
342
|
-
count: intents.length,
|
|
343
|
-
mode: "intents_in_network",
|
|
344
|
-
note: "To show network title and full intent details, use read_networks and read_intents.",
|
|
345
|
-
},
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
catch (err) {
|
|
349
|
-
logger.error("Read intent–network failed", { error: err });
|
|
350
|
-
return { error: "Failed to fetch intent–network links." };
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
logger.error("Assign failed", { error: err });
|
|
206
|
+
return { agentTimings: agentTimingsAccum, mutationResult: { success: false, error: "Failed to assign intent to network." } };
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Read Node: Query intent–network relationships.
|
|
212
|
+
*
|
|
213
|
+
* Three modes (selected by which inputs are provided):
|
|
214
|
+
* - Both intentId + networkId: check if the specific link exists (owner only).
|
|
215
|
+
* - intentId only: list all networks the intent is linked to (owner only).
|
|
216
|
+
* - networkId only: list intents in the network (member only, up to 50).
|
|
217
|
+
* Add `queryUserId` to filter to one member's intents.
|
|
218
|
+
*/
|
|
219
|
+
export async function readNode(state, deps) {
|
|
220
|
+
return timed("IntentNetworkGraph.read", async () => {
|
|
221
|
+
const intentId = state.intentId;
|
|
222
|
+
const networkId = state.networkId;
|
|
223
|
+
logger.verbose("Read intent–network links", { userId: state.userId, intentId, networkId, queryUserId: state.queryUserId });
|
|
224
|
+
try {
|
|
225
|
+
// By both: check if specific intent–network link exists
|
|
226
|
+
if (intentId && networkId) {
|
|
227
|
+
const intent = await deps.database.getIntent(intentId);
|
|
228
|
+
if (!intent) {
|
|
229
|
+
return { readResult: { links: [], count: 0, mode: "check_link" }, error: "Intent not found." };
|
|
351
230
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Unassign Node: Remove an intent from a network.
|
|
356
|
-
* Only the intent owner may unassign, and only if they are a member/owner of the network.
|
|
357
|
-
*/
|
|
358
|
-
const unassignNode = async (state) => {
|
|
359
|
-
return timed("IntentNetworkGraph.unassign", async () => {
|
|
360
|
-
const intentId = state.intentId;
|
|
361
|
-
const networkId = state.networkId;
|
|
362
|
-
logger.verbose("Unassign intent from network", { userId: state.userId, intentId, networkId });
|
|
363
|
-
if (!intentId || !networkId) {
|
|
364
|
-
return { mutationResult: { success: false, error: "Both intentId and networkId are required." } };
|
|
231
|
+
if (intent.userId !== state.userId) {
|
|
232
|
+
return { readResult: { links: [], count: 0, mode: "check_link" }, error: "You can only check links for your own intents." };
|
|
365
233
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
const assigned = await this.database.isIntentAssignedToIndex(intentId, networkId);
|
|
382
|
-
if (!assigned) {
|
|
383
|
-
return { mutationResult: { success: true, message: "That intent is not in this network." } };
|
|
384
|
-
}
|
|
385
|
-
await this.database.unassignIntentFromIndex(intentId, networkId);
|
|
386
|
-
return { mutationResult: { success: true, message: "Intent removed from the network." } };
|
|
234
|
+
const isLinked = await deps.database.isIntentAssignedToIndex(intentId, networkId);
|
|
235
|
+
return {
|
|
236
|
+
readResult: {
|
|
237
|
+
links: isLinked ? [{ intentId, networkId }] : [],
|
|
238
|
+
count: isLinked ? 1 : 0,
|
|
239
|
+
mode: "check_link",
|
|
240
|
+
note: isLinked ? "Intent is linked to this network." : "Intent is not linked to this network.",
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// By intent only: list all networks for this intent
|
|
245
|
+
if (intentId) {
|
|
246
|
+
const intent = await deps.database.getIntent(intentId);
|
|
247
|
+
if (!intent) {
|
|
248
|
+
return { readResult: { links: [], count: 0, mode: "networks_for_intent" }, error: "Intent not found." };
|
|
387
249
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return { mutationResult: { success: false, error: "Failed to remove intent from network." } };
|
|
250
|
+
if (intent.userId !== state.userId) {
|
|
251
|
+
return { readResult: { links: [], count: 0, mode: "networks_for_intent" }, error: "You can only list networks for your own intents." };
|
|
391
252
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
253
|
+
const networkIds = await deps.database.getNetworkIdsForIntent(intentId);
|
|
254
|
+
return {
|
|
255
|
+
readResult: {
|
|
256
|
+
links: networkIds.map((id) => ({ intentId, networkId: id })),
|
|
257
|
+
count: networkIds.length,
|
|
258
|
+
mode: "networks_for_intent",
|
|
259
|
+
note: "To show network titles, use read_networks.",
|
|
260
|
+
},
|
|
261
|
+
};
|
|
401
262
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
263
|
+
// By network: list intents in the network
|
|
264
|
+
if (!networkId) {
|
|
265
|
+
return {
|
|
266
|
+
readResult: { links: [], count: 0, mode: "unknown" },
|
|
267
|
+
error: "Provide networkId or intentId.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const [isMember, isOwner] = await Promise.all([
|
|
271
|
+
deps.database.isNetworkMember(networkId, state.userId),
|
|
272
|
+
deps.database.isIndexOwner(networkId, state.userId),
|
|
273
|
+
]);
|
|
274
|
+
if (!isMember && !isOwner) {
|
|
275
|
+
return {
|
|
276
|
+
readResult: { links: [], count: 0, mode: "intents_in_network" },
|
|
277
|
+
error: "Network not found or you are not a member.",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (!state.queryUserId) {
|
|
281
|
+
const intents = await deps.database.getNetworkIntentsForMember(networkId, state.userId, { limit: 50, offset: 0 });
|
|
282
|
+
return {
|
|
283
|
+
readResult: {
|
|
284
|
+
links: intents.map((i) => ({
|
|
285
|
+
intentId: i.id,
|
|
286
|
+
networkId,
|
|
287
|
+
intentTitle: i.payload,
|
|
288
|
+
userId: i.userId,
|
|
289
|
+
userName: i.userName,
|
|
290
|
+
createdAt: i.createdAt,
|
|
291
|
+
relevancyScore: i.relevancyScore,
|
|
292
|
+
})),
|
|
293
|
+
count: intents.length,
|
|
294
|
+
mode: "intents_in_network",
|
|
295
|
+
note: "To show network title and full intent details, use read_networks and read_intents.",
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// Specific user's intents in the network
|
|
300
|
+
const intents = await deps.database.getIntentsInIndexForMember(state.queryUserId, networkId);
|
|
301
|
+
return {
|
|
302
|
+
readResult: {
|
|
303
|
+
links: intents.map((i) => ({
|
|
304
|
+
intentId: i.id,
|
|
305
|
+
networkId,
|
|
306
|
+
intentTitle: i.payload,
|
|
307
|
+
createdAt: i.createdAt,
|
|
308
|
+
relevancyScore: i.relevancyScore,
|
|
309
|
+
})),
|
|
310
|
+
count: intents.length,
|
|
311
|
+
mode: "intents_in_network",
|
|
312
|
+
note: "To show network title and full intent details, use read_networks and read_intents.",
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
logger.error("Read intent–network failed", { error: err });
|
|
318
|
+
return { error: "Failed to fetch intent–network links." };
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Unassign Node: Remove an intent from a network.
|
|
324
|
+
* Only the intent owner may unassign, and only if they are a member/owner of the network.
|
|
325
|
+
*/
|
|
326
|
+
export async function unassignNode(state, deps) {
|
|
327
|
+
return timed("IntentNetworkGraph.unassign", async () => {
|
|
328
|
+
const intentId = state.intentId;
|
|
329
|
+
const networkId = state.networkId;
|
|
330
|
+
logger.verbose("Unassign intent from network", { userId: state.userId, intentId, networkId });
|
|
331
|
+
if (!intentId || !networkId) {
|
|
332
|
+
return { mutationResult: { success: false, error: "Both intentId and networkId are required." } };
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const intent = await deps.database.getIntent(intentId);
|
|
336
|
+
if (!intent) {
|
|
337
|
+
return { mutationResult: { success: false, error: "Intent not found." } };
|
|
338
|
+
}
|
|
339
|
+
if (intent.userId !== state.userId) {
|
|
340
|
+
return { mutationResult: { success: false, error: "You can only remove your own intents from a network." } };
|
|
341
|
+
}
|
|
342
|
+
const [isMember, isOwner] = await Promise.all([
|
|
343
|
+
deps.database.isNetworkMember(networkId, state.userId),
|
|
344
|
+
deps.database.isIndexOwner(networkId, state.userId),
|
|
345
|
+
]);
|
|
346
|
+
if (!isMember && !isOwner) {
|
|
347
|
+
return { mutationResult: { success: false, error: "You are not a member of that network." } };
|
|
348
|
+
}
|
|
349
|
+
const assigned = await deps.database.isIntentAssignedToIndex(intentId, networkId);
|
|
350
|
+
if (!assigned) {
|
|
351
|
+
return { mutationResult: { success: true, message: "That intent is not in this network." } };
|
|
352
|
+
}
|
|
353
|
+
await deps.database.unassignIntentFromIndex(intentId, networkId);
|
|
354
|
+
return { mutationResult: { success: true, message: "Intent removed from the network." } };
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
logger.error("Unassign failed", { error: err });
|
|
358
|
+
return { mutationResult: { success: false, error: "Failed to remove intent from network." } };
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
export function routeByMode(state) {
|
|
363
|
+
switch (state.operationMode) {
|
|
364
|
+
case 'create': return 'assign';
|
|
365
|
+
case 'read': return 'read';
|
|
366
|
+
case 'delete': return 'unassign';
|
|
367
|
+
default: return 'read';
|
|
417
368
|
}
|
|
418
369
|
}
|