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 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`)
@@ -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
  }
@@ -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 = 5_000;
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: z.enum(bundleModes).default("compact"),
730
- preset: z.enum(bundlePresets).default("for-assistant"),
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: z.enum(bundlePresets).default("for-assistant"),
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 targetNode = input.targetNodeId ? repository.getNode(input.targetNodeId) : repository.ensureWorkspaceInboxNode();
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: input.mode === "activity"
1639
- ? "Capture was explicitly routed to the activity timeline."
1640
- : "Short log-like capture was routed to the activity timeline."
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
  });
@@ -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
- return JSON.parse(line);
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.details.feedbackVerdict === "useful" ||
501
- event.details.feedbackVerdict === "not_useful" ||
502
- event.details.feedbackVerdict === "uncertain") {
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
@@ -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),
@@ -1 +1 @@
1
- export const RECALLX_VERSION = "1.0.4";
1
+ export const RECALLX_VERSION = "1.0.7";