recallx 1.0.4 → 1.0.7
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/README.md +14 -0
- package/app/cli/src/format.js +1 -1
- package/app/mcp/api-client.js +5 -1
- package/app/mcp/index.js +1 -1
- package/app/mcp/server.js +24 -6
- package/app/server/app.js +34 -6
- package/app/server/governance.js +1 -1
- package/app/server/observability.js +45 -7
- package/app/server/repositories.js +11 -0
- package/app/shared/contracts.js +50 -2
- package/app/shared/version.js +1 -1
- package/dist/renderer/assets/{ProjectGraphCanvas-CQV7FYSd.js → ProjectGraphCanvas-YhLhWpYR.js} +2 -2
- package/dist/renderer/assets/{index-BM5Zf6Wz.js → index-Bwm9VwHm.js} +16 -16
- package/dist/renderer/index.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,11 +69,25 @@ npm start
|
|
|
69
69
|
Checks:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
+
npm run branch:check
|
|
73
|
+
npm run version:check
|
|
72
74
|
npm run check
|
|
73
75
|
npm test
|
|
74
76
|
npm run build
|
|
75
77
|
```
|
|
76
78
|
|
|
79
|
+
Start unrelated work in a separate branch/worktree instead of stacking it on the current checkout:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run branch:new -- fix-short-name
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
When you are preparing a release, bump from the highest known version instead of whatever the current branch happens to say:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run version:bump -- patch
|
|
89
|
+
```
|
|
90
|
+
|
|
77
91
|
If you want an installable runtime instead of source-run workflows, use one of the npm distribution paths below.
|
|
78
92
|
|
|
79
93
|
## 2. npm Full Runtime (`recallx`)
|
package/app/cli/src/format.js
CHANGED
|
@@ -304,7 +304,7 @@ export function renderTelemetryErrors(data) {
|
|
|
304
304
|
return `${items
|
|
305
305
|
.map(
|
|
306
306
|
(item, index) =>
|
|
307
|
-
`${index + 1}. [${item.surface}] ${item.operation}\n ts: ${item.ts}\n trace: ${item.traceId}\n error: ${item.errorKind || ""}/${item.errorCode || ""}\n status: ${item.statusCode ?? ""}\n durationMs: ${item.durationMs ?? ""}`
|
|
307
|
+
`${index + 1}. [${item.surface}] ${item.operation}\n ts: ${item.ts}\n trace: ${item.traceId}\n span: ${item.spanId ?? ""}\n parent: ${item.parentSpanId ?? ""}\n error: ${item.errorKind || ""}/${item.errorCode || ""}\n status: ${item.statusCode ?? ""}\n durationMs: ${item.durationMs ?? ""}`
|
|
308
308
|
)
|
|
309
309
|
.join("\n\n")}\n`;
|
|
310
310
|
}
|
package/app/mcp/api-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildApiRequestInit, buildApiUrl, parseApiJsonBody } from "../shared/request-runtime.js";
|
|
2
|
-
import { currentTelemetryContext } from "../server/observability.js";
|
|
2
|
+
import { currentTelemetryContext, currentTelemetrySpanId } from "../server/observability.js";
|
|
3
3
|
export class RecallXApiError extends Error {
|
|
4
4
|
status;
|
|
5
5
|
code;
|
|
@@ -43,6 +43,10 @@ export class RecallXApiClient {
|
|
|
43
43
|
if (telemetryContext?.traceId) {
|
|
44
44
|
headers.set("x-recallx-trace-id", telemetryContext.traceId);
|
|
45
45
|
}
|
|
46
|
+
const telemetrySpanId = currentTelemetrySpanId();
|
|
47
|
+
if (telemetrySpanId) {
|
|
48
|
+
headers.set("x-recallx-parent-span-id", telemetrySpanId);
|
|
49
|
+
}
|
|
46
50
|
if (telemetryContext?.toolName) {
|
|
47
51
|
headers.set("x-recallx-mcp-tool", telemetryContext.toolName);
|
|
48
52
|
}
|
package/app/mcp/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function createObservabilityStateReader() {
|
|
|
10
10
|
let cachedState = null;
|
|
11
11
|
let cachedAt = 0;
|
|
12
12
|
let inFlight = null;
|
|
13
|
-
const cacheTtlMs =
|
|
13
|
+
const cacheTtlMs = 60_000;
|
|
14
14
|
return async function readObservabilityState() {
|
|
15
15
|
const now = Date.now();
|
|
16
16
|
if (cachedState && now - cachedAt < cacheTtlMs) {
|
package/app/mcp/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
|
-
import { activityTypes, bundleModes, bundlePresets, canonicalities, captureModes, governanceStates, inferredRelationStatuses, nodeStatuses, nodeTypes, relationSources, relationStatuses, relationTypes, relationUsageEventTypes, searchFeedbackResultTypes, searchFeedbackVerdicts, sourceTypes } from "../shared/contracts.js";
|
|
3
|
+
import { activityTypes, bundleModes, bundlePresets, canonicalities, captureModes, governanceStates, inferredRelationStatuses, normalizeBundleMode, normalizeBundlePreset, nodeStatuses, nodeTypes, relationSources, relationStatuses, relationTypes, relationUsageEventTypes, searchFeedbackResultTypes, searchFeedbackVerdicts, sourceTypes } from "../shared/contracts.js";
|
|
4
4
|
import { RECALLX_VERSION } from "../shared/version.js";
|
|
5
5
|
import { createObservabilityWriter, summarizePayloadShape } from "../server/observability.js";
|
|
6
6
|
import { RecallXApiClient, RecallXApiError } from "./api-client.js";
|
|
@@ -67,6 +67,24 @@ function coerceBooleanSchema(defaultValue) {
|
|
|
67
67
|
function formatStructuredContent(content) {
|
|
68
68
|
return JSON.stringify(content, null, 2);
|
|
69
69
|
}
|
|
70
|
+
function formatInvalidBundleModeMessage(input) {
|
|
71
|
+
const quotedInput = typeof input === "string" && input.trim() ? `'${input}'` : "that value";
|
|
72
|
+
return `Unsupported mode ${quotedInput}. Use one of ${bundleModes.join(", ")}. Common aliases also work: small -> micro, concise -> compact, normal -> standard, full -> deep.`;
|
|
73
|
+
}
|
|
74
|
+
function formatInvalidBundlePresetMessage(input) {
|
|
75
|
+
const quotedInput = typeof input === "string" && input.trim() ? `'${input}'` : "that value";
|
|
76
|
+
return `Unsupported preset ${quotedInput}. Use one of ${bundlePresets.join(", ")}. Common aliases also work: coding -> for-coding, assistant/default -> for-assistant.`;
|
|
77
|
+
}
|
|
78
|
+
function bundleModeSchema(defaultValue) {
|
|
79
|
+
return z.preprocess(normalizeBundleMode, z.enum(bundleModes, {
|
|
80
|
+
error: (issue) => formatInvalidBundleModeMessage(issue.input)
|
|
81
|
+
})).default(defaultValue);
|
|
82
|
+
}
|
|
83
|
+
function bundlePresetSchema(defaultValue) {
|
|
84
|
+
return z.preprocess(normalizeBundlePreset, z.enum(bundlePresets, {
|
|
85
|
+
error: (issue) => formatInvalidBundlePresetMessage(issue.input)
|
|
86
|
+
})).default(defaultValue);
|
|
87
|
+
}
|
|
70
88
|
function toolResult(structuredContent) {
|
|
71
89
|
return {
|
|
72
90
|
content: [
|
|
@@ -317,7 +335,7 @@ export function createRecallXMcpServer(params) {
|
|
|
317
335
|
name: "recallx-mcp",
|
|
318
336
|
version: params?.serverVersion ?? RECALLX_VERSION
|
|
319
337
|
}, {
|
|
320
|
-
instructions: "Use RecallX as a local knowledge backend. Treat the current workspace as the default scope, and do not create or open another workspace unless the user explicitly asks. When the work is clearly project-shaped, search for an existing project inside the current workspace first: prefer recallx_search_nodes with type=project, broaden with recallx_search_workspace when needed, create a project node only if no suitable one exists, and then anchor follow-up context with recallx_context_bundle targetId. If the conversation is not project-specific, keep memory at workspace scope. Prefer read tools first, and include source details on durable writes when you want caller-specific provenance.",
|
|
338
|
+
instructions: "Use RecallX as a local knowledge backend. Treat the current workspace as the default scope, and do not create or open another workspace unless the user explicitly asks. When the work is clearly project-shaped, search for an existing project inside the current workspace first: prefer recallx_search_nodes with type=project, broaden with recallx_search_workspace when needed, create a project node only if no suitable one exists, and then anchor follow-up context with recallx_context_bundle targetId. Once a project is known, do not keep writing untargeted workspace captures for routine work logs: append activity to that project or pass targetNodeId on capture writes. Reserve workspace-scope inbox activity for genuinely untargeted, cross-project, or not-yet-classified short logs. If the conversation is not project-specific, keep memory at workspace scope. Prefer read tools first, and include source details on durable writes when you want caller-specific provenance.",
|
|
321
339
|
capabilities: {
|
|
322
340
|
logging: {}
|
|
323
341
|
}
|
|
@@ -617,7 +635,7 @@ export function createRecallXMcpServer(params) {
|
|
|
617
635
|
}, createPostToolHandler(apiClient, "/activities"));
|
|
618
636
|
registerTool(server, "recallx_capture_memory", {
|
|
619
637
|
title: "Capture Memory",
|
|
620
|
-
description: "Safely capture a memory item without choosing low-level storage first. Prefer this as the default write when the conversation is not yet tied to a specific project or node. General short logs can stay at workspace scope and be auto-routed into activities, while reusable content can still land as durable memory.",
|
|
638
|
+
description: "Safely capture a memory item without choosing low-level storage first. Prefer this as the default write only when the conversation is not yet tied to a specific project or node. Once a project or target node is known, include targetNodeId or switch to recallx_append_activity for routine work logs. General short logs can stay at workspace scope and be auto-routed into activities, while reusable content can still land as durable memory.",
|
|
621
639
|
inputSchema: {
|
|
622
640
|
mode: z.enum(captureModes).default("auto"),
|
|
623
641
|
body: z.string().min(1),
|
|
@@ -726,8 +744,8 @@ export function createRecallXMcpServer(params) {
|
|
|
726
744
|
description: "Build a compact RecallX context bundle for coding, research, writing, or decision support. Omit targetId to get a workspace-entry bundle when the work is not yet tied to a specific project or node, and add targetId only after you know which project or node should anchor the context.",
|
|
727
745
|
inputSchema: {
|
|
728
746
|
targetId: z.string().min(1).optional(),
|
|
729
|
-
mode:
|
|
730
|
-
preset:
|
|
747
|
+
mode: bundleModeSchema("compact"),
|
|
748
|
+
preset: bundlePresetSchema("for-assistant"),
|
|
731
749
|
options: z
|
|
732
750
|
.object({
|
|
733
751
|
includeRelated: coerceBooleanSchema(true),
|
|
@@ -778,7 +796,7 @@ export function createRecallXMcpServer(params) {
|
|
|
778
796
|
inputSchema: {
|
|
779
797
|
query: z.string().default(""),
|
|
780
798
|
candidateNodeIds: z.array(z.string().min(1)).min(1).max(100),
|
|
781
|
-
preset:
|
|
799
|
+
preset: bundlePresetSchema("for-assistant"),
|
|
782
800
|
targetNodeId: z.string().optional()
|
|
783
801
|
}
|
|
784
802
|
}, createPostToolHandler(apiClient, "/retrieval/rank-candidates"));
|
package/app/server/app.js
CHANGED
|
@@ -1160,6 +1160,25 @@ export function createRecallXApp(params) {
|
|
|
1160
1160
|
reason: input.reason
|
|
1161
1161
|
};
|
|
1162
1162
|
}
|
|
1163
|
+
function resolveCaptureActivityTarget(repository, input) {
|
|
1164
|
+
if (input.targetNodeId) {
|
|
1165
|
+
return {
|
|
1166
|
+
targetNode: repository.getNode(input.targetNodeId),
|
|
1167
|
+
route: "explicit"
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
const activeProjects = repository.listActiveNodesByType("project", 2);
|
|
1171
|
+
if (activeProjects.length === 1) {
|
|
1172
|
+
return {
|
|
1173
|
+
targetNode: activeProjects[0],
|
|
1174
|
+
route: "sole_active_project"
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
return {
|
|
1178
|
+
targetNode: repository.ensureWorkspaceInboxNode(),
|
|
1179
|
+
route: "workspace_inbox"
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1163
1182
|
function createDurableNodeResponse(repository, input) {
|
|
1164
1183
|
const governance = resolveNodeGovernance(input, resolveGovernancePolicy(repository.getSettings(["review.autoApproveLowRisk", "review.trustedSourceToolNames"])));
|
|
1165
1184
|
const node = repository.createNode({
|
|
@@ -1204,6 +1223,7 @@ export function createRecallXApp(params) {
|
|
|
1204
1223
|
app.use((request, response, next) => {
|
|
1205
1224
|
const requestId = createId("req");
|
|
1206
1225
|
const traceId = request.header("x-recallx-trace-id")?.trim() || createId("trace");
|
|
1226
|
+
const parentSpanId = request.header("x-recallx-parent-span-id")?.trim() || null;
|
|
1207
1227
|
const operation = `${request.method.toUpperCase()} ${normalizeApiRequestPath(request.path)}`;
|
|
1208
1228
|
const observabilityState = currentObservabilityConfig();
|
|
1209
1229
|
const requestSpan = observability.startSpan({
|
|
@@ -1211,6 +1231,7 @@ export function createRecallXApp(params) {
|
|
|
1211
1231
|
operation,
|
|
1212
1232
|
requestId,
|
|
1213
1233
|
traceId,
|
|
1234
|
+
parentSpanId,
|
|
1214
1235
|
details: {
|
|
1215
1236
|
...(observabilityState.capturePayloadShape ? summarizePayloadShape(request.body) : {}),
|
|
1216
1237
|
mcpTool: request.header("x-recallx-mcp-tool") ?? null
|
|
@@ -1221,6 +1242,7 @@ export function createRecallXApp(params) {
|
|
|
1221
1242
|
response.locals.telemetryRequestSpan = requestSpan;
|
|
1222
1243
|
response.setHeader("x-recallx-request-id", requestId);
|
|
1223
1244
|
response.setHeader("x-recallx-trace-id", traceId);
|
|
1245
|
+
response.setHeader("x-recallx-span-id", requestSpan.spanId);
|
|
1224
1246
|
response.on("finish", () => {
|
|
1225
1247
|
void requestSpan.finish({
|
|
1226
1248
|
outcome: response.statusCode >= 400 ? "error" : "success",
|
|
@@ -1229,14 +1251,14 @@ export function createRecallXApp(params) {
|
|
|
1229
1251
|
errorKind: response.locals.telemetryErrorKind ?? null
|
|
1230
1252
|
});
|
|
1231
1253
|
});
|
|
1232
|
-
observability.withContext({
|
|
1254
|
+
requestSpan.run(() => observability.withContext({
|
|
1233
1255
|
traceId,
|
|
1234
1256
|
requestId,
|
|
1235
1257
|
workspaceRoot: currentWorkspaceRoot(),
|
|
1236
1258
|
workspaceName: currentWorkspaceInfo().workspaceName,
|
|
1237
1259
|
toolName: request.header("x-recallx-mcp-tool") ?? null,
|
|
1238
1260
|
surface: "api"
|
|
1239
|
-
}, next);
|
|
1261
|
+
}, next));
|
|
1240
1262
|
});
|
|
1241
1263
|
app.use(express.json({ limit: "2mb" }));
|
|
1242
1264
|
app.use("/api/v1", (request, response, next) => {
|
|
@@ -1597,7 +1619,8 @@ export function createRecallXApp(params) {
|
|
|
1597
1619
|
})()));
|
|
1598
1620
|
return;
|
|
1599
1621
|
}
|
|
1600
|
-
const
|
|
1622
|
+
const captureTarget = resolveCaptureActivityTarget(repository, input);
|
|
1623
|
+
const targetNode = captureTarget.targetNode;
|
|
1601
1624
|
const activity = repository.appendActivity({
|
|
1602
1625
|
targetNodeId: targetNode.id,
|
|
1603
1626
|
activityType: "agent_run_summary",
|
|
@@ -1605,6 +1628,7 @@ export function createRecallXApp(params) {
|
|
|
1605
1628
|
source,
|
|
1606
1629
|
metadata: {
|
|
1607
1630
|
...input.metadata,
|
|
1631
|
+
autoTargetRoute: captureTarget.route,
|
|
1608
1632
|
captureMode: input.mode,
|
|
1609
1633
|
capturedTitle: title
|
|
1610
1634
|
}
|
|
@@ -1635,9 +1659,13 @@ export function createRecallXApp(params) {
|
|
|
1635
1659
|
storedAs: "activity",
|
|
1636
1660
|
status: "recorded",
|
|
1637
1661
|
governanceState: null,
|
|
1638
|
-
reason:
|
|
1639
|
-
?
|
|
1640
|
-
|
|
1662
|
+
reason: captureTarget.route === "sole_active_project"
|
|
1663
|
+
? input.mode === "activity"
|
|
1664
|
+
? "Capture was routed to the sole active project timeline."
|
|
1665
|
+
: "Short log-like capture was routed to the sole active project timeline."
|
|
1666
|
+
: input.mode === "activity"
|
|
1667
|
+
? "Capture was explicitly routed to the activity timeline."
|
|
1668
|
+
: "Short log-like capture was routed to the activity timeline."
|
|
1641
1669
|
})
|
|
1642
1670
|
}));
|
|
1643
1671
|
});
|
package/app/server/governance.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppError } from "./errors.js";
|
|
2
2
|
import { countTokensApprox, nowIso } from "./utils.js";
|
|
3
|
-
const relaxedShortFormNodeTypes = new Set(["reference", "question", "conversation"]);
|
|
3
|
+
const relaxedShortFormNodeTypes = new Set(["project", "reference", "question", "conversation"]);
|
|
4
4
|
function clampConfidence(value) {
|
|
5
5
|
return Math.min(Math.max(value, 0), 1);
|
|
6
6
|
}
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { appendFile, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { createId } from "./utils.js";
|
|
4
5
|
const telemetryStorage = new AsyncLocalStorage();
|
|
5
6
|
function nowIso() {
|
|
6
7
|
return new Date().toISOString();
|
|
7
8
|
}
|
|
9
|
+
function createSpanId() {
|
|
10
|
+
return createId("span");
|
|
11
|
+
}
|
|
8
12
|
function parseJsonLine(line) {
|
|
9
13
|
if (!line.trim()) {
|
|
10
14
|
return null;
|
|
11
15
|
}
|
|
12
16
|
try {
|
|
13
|
-
|
|
17
|
+
const parsed = JSON.parse(line);
|
|
18
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.ts !== "string" || typeof parsed.operation !== "string") {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
ts: parsed.ts,
|
|
23
|
+
traceId: typeof parsed.traceId === "string" ? parsed.traceId : "trace_unknown",
|
|
24
|
+
spanId: typeof parsed.spanId === "string" ? parsed.spanId : null,
|
|
25
|
+
parentSpanId: typeof parsed.parentSpanId === "string" ? parsed.parentSpanId : null,
|
|
26
|
+
requestId: typeof parsed.requestId === "string" ? parsed.requestId : null,
|
|
27
|
+
surface: parsed.surface === "mcp" ? "mcp" : "api",
|
|
28
|
+
operation: parsed.operation,
|
|
29
|
+
outcome: parsed.outcome === "error" ? "error" : "success",
|
|
30
|
+
durationMs: typeof parsed.durationMs === "number" ? parsed.durationMs : null,
|
|
31
|
+
statusCode: typeof parsed.statusCode === "number" ? parsed.statusCode : null,
|
|
32
|
+
errorCode: typeof parsed.errorCode === "string" ? parsed.errorCode : null,
|
|
33
|
+
errorKind: typeof parsed.errorKind === "string" ? parsed.errorKind : null,
|
|
34
|
+
workspaceName: typeof parsed.workspaceName === "string" ? parsed.workspaceName : null,
|
|
35
|
+
details: parsed.details && typeof parsed.details === "object" && !Array.isArray(parsed.details) ? parsed.details : {}
|
|
36
|
+
};
|
|
14
37
|
}
|
|
15
38
|
catch {
|
|
16
39
|
return null;
|
|
@@ -142,14 +165,18 @@ export class TelemetrySpan {
|
|
|
142
165
|
writer;
|
|
143
166
|
state;
|
|
144
167
|
context;
|
|
168
|
+
spanId;
|
|
169
|
+
parentSpanId;
|
|
145
170
|
operation;
|
|
146
171
|
startedAt = process.hrtime.bigint();
|
|
147
172
|
details;
|
|
148
173
|
finished = false;
|
|
149
|
-
constructor(writer, state, context, operation, details) {
|
|
174
|
+
constructor(writer, state, context, spanId, parentSpanId, operation, details) {
|
|
150
175
|
this.writer = writer;
|
|
151
176
|
this.state = state;
|
|
152
177
|
this.context = context;
|
|
178
|
+
this.spanId = spanId;
|
|
179
|
+
this.parentSpanId = parentSpanId;
|
|
153
180
|
this.operation = operation;
|
|
154
181
|
this.details = sanitizeDetails(details);
|
|
155
182
|
}
|
|
@@ -165,6 +192,8 @@ export class TelemetrySpan {
|
|
|
165
192
|
await this.writer.enqueue({
|
|
166
193
|
ts: nowIso(),
|
|
167
194
|
traceId: this.context.traceId,
|
|
195
|
+
spanId: this.spanId,
|
|
196
|
+
parentSpanId: this.parentSpanId,
|
|
168
197
|
requestId: input.requestId ?? this.context.requestId,
|
|
169
198
|
surface: this.context.surface,
|
|
170
199
|
operation: this.operation,
|
|
@@ -198,14 +227,16 @@ export class ObservabilityWriter {
|
|
|
198
227
|
return telemetryStorage.getStore() ?? null;
|
|
199
228
|
}
|
|
200
229
|
withContext(input, callback) {
|
|
230
|
+
const current = telemetryStorage.getStore();
|
|
201
231
|
return telemetryStorage.run({
|
|
202
232
|
...input,
|
|
203
|
-
spans: []
|
|
233
|
+
spans: current?.spans ?? []
|
|
204
234
|
}, callback);
|
|
205
235
|
}
|
|
206
236
|
startSpan(input) {
|
|
207
237
|
const state = this.options.getState();
|
|
208
238
|
const current = telemetryStorage.getStore();
|
|
239
|
+
const parentSpan = current?.spans[current.spans.length - 1] ?? null;
|
|
209
240
|
const context = {
|
|
210
241
|
traceId: input.traceId ?? current?.traceId ?? "trace_unknown",
|
|
211
242
|
requestId: input.requestId ?? current?.requestId ?? null,
|
|
@@ -215,7 +246,7 @@ export class ObservabilityWriter {
|
|
|
215
246
|
toolName: current?.toolName ?? null,
|
|
216
247
|
spans: current?.spans ?? []
|
|
217
248
|
};
|
|
218
|
-
return new TelemetrySpan(this, state, context, input.operation, input.details);
|
|
249
|
+
return new TelemetrySpan(this, state, context, createSpanId(), input.parentSpanId ?? parentSpan?.spanId ?? null, input.operation, input.details);
|
|
219
250
|
}
|
|
220
251
|
addCurrentSpanDetails(details) {
|
|
221
252
|
const current = telemetryStorage.getStore();
|
|
@@ -230,6 +261,8 @@ export class ObservabilityWriter {
|
|
|
230
261
|
await this.enqueue({
|
|
231
262
|
ts: nowIso(),
|
|
232
263
|
traceId: input.traceId ?? current?.traceId ?? "trace_unknown",
|
|
264
|
+
spanId: createSpanId(),
|
|
265
|
+
parentSpanId: current?.spans[current.spans.length - 1]?.spanId ?? null,
|
|
233
266
|
requestId: input.requestId ?? current?.requestId ?? null,
|
|
234
267
|
surface: input.surface ?? current?.surface ?? "api",
|
|
235
268
|
operation: input.operation,
|
|
@@ -497,9 +530,10 @@ export class ObservabilityWriter {
|
|
|
497
530
|
}
|
|
498
531
|
workspaceFallbackModeBuckets.set(bucketKey, current);
|
|
499
532
|
}
|
|
500
|
-
if (event.
|
|
501
|
-
event.details.feedbackVerdict === "
|
|
502
|
-
|
|
533
|
+
if (event.operation === "search.feedback" &&
|
|
534
|
+
(event.details.feedbackVerdict === "useful" ||
|
|
535
|
+
event.details.feedbackVerdict === "not_useful" ||
|
|
536
|
+
event.details.feedbackVerdict === "uncertain")) {
|
|
503
537
|
feedbackSampleCount += 1;
|
|
504
538
|
if (event.details.feedbackVerdict === "useful") {
|
|
505
539
|
feedbackUsefulCount += 1;
|
|
@@ -863,6 +897,10 @@ export function createObservabilityWriter(options) {
|
|
|
863
897
|
export function currentTelemetryContext() {
|
|
864
898
|
return telemetryStorage.getStore() ?? null;
|
|
865
899
|
}
|
|
900
|
+
export function currentTelemetrySpanId() {
|
|
901
|
+
const current = telemetryStorage.getStore();
|
|
902
|
+
return current?.spans[current.spans.length - 1]?.spanId ?? null;
|
|
903
|
+
}
|
|
866
904
|
export function appendCurrentTelemetryDetails(details) {
|
|
867
905
|
const current = telemetryStorage.getStore();
|
|
868
906
|
current?.spans[current.spans.length - 1]?.addDetails(details);
|
|
@@ -1282,6 +1282,17 @@ export class RecallXRepository {
|
|
|
1282
1282
|
tags: parseJson(row.tags_json, [])
|
|
1283
1283
|
}));
|
|
1284
1284
|
}
|
|
1285
|
+
listActiveNodesByType(type, limit = 20) {
|
|
1286
|
+
const rows = this.db
|
|
1287
|
+
.prepare(`SELECT *
|
|
1288
|
+
FROM nodes
|
|
1289
|
+
WHERE type = ?
|
|
1290
|
+
AND status = 'active'
|
|
1291
|
+
ORDER BY updated_at DESC, id DESC
|
|
1292
|
+
LIMIT ?`)
|
|
1293
|
+
.all(type, limit);
|
|
1294
|
+
return rows.map(mapNode);
|
|
1295
|
+
}
|
|
1285
1296
|
listInferenceCandidateNodes(targetNodeId, limit = 200) {
|
|
1286
1297
|
const rows = this.db
|
|
1287
1298
|
.prepare(`SELECT * FROM nodes
|
package/app/shared/contracts.js
CHANGED
|
@@ -69,6 +69,54 @@ export const bundlePresets = [
|
|
|
69
69
|
"for-writing",
|
|
70
70
|
"for-assistant"
|
|
71
71
|
];
|
|
72
|
+
export function normalizeBundlePreset(value) {
|
|
73
|
+
if (typeof value !== "string") {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
const normalized = value.trim().toLowerCase().replace(/[_\s]+/g, "-");
|
|
77
|
+
const aliasMap = {
|
|
78
|
+
coding: "for-coding",
|
|
79
|
+
code: "for-coding",
|
|
80
|
+
"for-code": "for-coding",
|
|
81
|
+
"for-coding": "for-coding",
|
|
82
|
+
research: "for-research",
|
|
83
|
+
"for-research": "for-research",
|
|
84
|
+
decision: "for-decision",
|
|
85
|
+
decisions: "for-decision",
|
|
86
|
+
"for-decision": "for-decision",
|
|
87
|
+
writing: "for-writing",
|
|
88
|
+
write: "for-writing",
|
|
89
|
+
writer: "for-writing",
|
|
90
|
+
"for-writing": "for-writing",
|
|
91
|
+
assistant: "for-assistant",
|
|
92
|
+
default: "for-assistant",
|
|
93
|
+
general: "for-assistant",
|
|
94
|
+
"for-assistant": "for-assistant"
|
|
95
|
+
};
|
|
96
|
+
return aliasMap[normalized] ?? normalized;
|
|
97
|
+
}
|
|
98
|
+
export function normalizeBundleMode(value) {
|
|
99
|
+
if (typeof value !== "string") {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
const normalized = value.trim().toLowerCase().replace(/[_\s]+/g, "-");
|
|
103
|
+
const aliasMap = {
|
|
104
|
+
micro: "micro",
|
|
105
|
+
tiny: "micro",
|
|
106
|
+
small: "micro",
|
|
107
|
+
minimal: "micro",
|
|
108
|
+
compact: "compact",
|
|
109
|
+
concise: "compact",
|
|
110
|
+
medium: "standard",
|
|
111
|
+
normal: "standard",
|
|
112
|
+
standard: "standard",
|
|
113
|
+
full: "deep",
|
|
114
|
+
detailed: "deep",
|
|
115
|
+
detail: "deep",
|
|
116
|
+
deep: "deep"
|
|
117
|
+
};
|
|
118
|
+
return aliasMap[normalized] ?? normalized;
|
|
119
|
+
}
|
|
72
120
|
export const sourceSchema = z.object({
|
|
73
121
|
actorType: z.enum(sourceTypes),
|
|
74
122
|
actorLabel: z.string().min(1),
|
|
@@ -230,8 +278,8 @@ export const buildContextBundleSchema = z.object({
|
|
|
230
278
|
id: z.string().min(1)
|
|
231
279
|
})
|
|
232
280
|
.optional(),
|
|
233
|
-
mode: z.enum(bundleModes).default("compact"),
|
|
234
|
-
preset: z.enum(bundlePresets).default("for-assistant"),
|
|
281
|
+
mode: z.preprocess(normalizeBundleMode, z.enum(bundleModes)).default("compact"),
|
|
282
|
+
preset: z.preprocess(normalizeBundlePreset, z.enum(bundlePresets)).default("for-assistant"),
|
|
235
283
|
options: z
|
|
236
284
|
.object({
|
|
237
285
|
includeRelated: z.boolean().default(true),
|
package/app/shared/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RECALLX_VERSION = "1.0.
|
|
1
|
+
export const RECALLX_VERSION = "1.0.7";
|