@gmickel/gno 1.17.0 → 1.19.0

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.
Files changed (167) hide show
  1. package/README.md +18 -4
  2. package/assets/skill/SKILL.md +44 -1
  3. package/assets/skill/mcp-reference.md +21 -0
  4. package/package.json +3 -2
  5. package/spec/AGENTS.md +83 -0
  6. package/spec/CLAUDE.md +83 -0
  7. package/spec/bench-fixture.schema.json +137 -0
  8. package/spec/cli.md +2894 -0
  9. package/spec/db/schema.sql +442 -0
  10. package/spec/evals-agentic.md +510 -0
  11. package/spec/evals.md +1106 -0
  12. package/spec/mcp.md +2229 -0
  13. package/spec/output-schemas/activation-verification.schema.json +515 -0
  14. package/spec/output-schemas/ask.schema.json +366 -0
  15. package/spec/output-schemas/backlinks.schema.json +131 -0
  16. package/spec/output-schemas/bench-result.schema.json +120 -0
  17. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  18. package/spec/output-schemas/collection-list.schema.json +45 -0
  19. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  20. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  21. package/spec/output-schemas/context-list.schema.json +21 -0
  22. package/spec/output-schemas/doctor.schema.json +313 -0
  23. package/spec/output-schemas/error.schema.json +30 -0
  24. package/spec/output-schemas/expansion.schema.json +37 -0
  25. package/spec/output-schemas/get.schema.json +140 -0
  26. package/spec/output-schemas/graph-query.schema.json +99 -0
  27. package/spec/output-schemas/graph.schema.json +371 -0
  28. package/spec/output-schemas/links-list.schema.json +186 -0
  29. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  30. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  31. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  32. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  33. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  34. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  35. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  36. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  37. package/spec/output-schemas/models-list.schema.json +93 -0
  38. package/spec/output-schemas/multi-get.schema.json +103 -0
  39. package/spec/output-schemas/process-status.schema.json +119 -0
  40. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  41. package/spec/output-schemas/resident-status.schema.json +154 -0
  42. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  43. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  44. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  45. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  46. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  47. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  48. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  49. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  50. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  51. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  52. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  53. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  54. package/spec/output-schemas/search-result.schema.json +154 -0
  55. package/spec/output-schemas/search-results.schema.json +338 -0
  56. package/spec/output-schemas/similar.schema.json +84 -0
  57. package/spec/output-schemas/status.schema.json +676 -0
  58. package/spec/output-schemas/tags-list.schema.json +48 -0
  59. package/src/app/context-runtime-types.ts +3 -0
  60. package/src/app/context-runtime.ts +15 -1
  61. package/src/cli/commands/ask.ts +106 -36
  62. package/src/cli/commands/context-build.ts +56 -9
  63. package/src/cli/commands/daemon.ts +69 -2
  64. package/src/cli/commands/get.ts +64 -3
  65. package/src/cli/commands/models/pull.ts +13 -3
  66. package/src/cli/commands/query.ts +62 -23
  67. package/src/cli/commands/replay.ts +140 -0
  68. package/src/cli/commands/search.ts +48 -3
  69. package/src/cli/commands/shared.ts +3 -1
  70. package/src/cli/commands/status.ts +2 -0
  71. package/src/cli/commands/trace.ts +200 -0
  72. package/src/cli/commands/vsearch.ts +75 -53
  73. package/src/cli/detach.ts +37 -20
  74. package/src/cli/program.ts +329 -27
  75. package/src/config/index.ts +12 -0
  76. package/src/config/retrieval-traces.ts +56 -0
  77. package/src/config/types.ts +41 -0
  78. package/src/core/context-compiler.ts +11 -4
  79. package/src/core/job-manager.ts +19 -0
  80. package/src/core/mutation-generations.ts +33 -0
  81. package/src/core/retrieval-qrels.ts +405 -0
  82. package/src/core/retrieval-replay-candidate.ts +368 -0
  83. package/src/core/retrieval-replay-types.ts +109 -0
  84. package/src/core/retrieval-replay-validation.ts +89 -0
  85. package/src/core/retrieval-replay.ts +441 -0
  86. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  87. package/src/core/retrieval-trace-export.ts +113 -0
  88. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  89. package/src/core/retrieval-trace-filters.ts +19 -0
  90. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  91. package/src/core/retrieval-trace-management-types.ts +132 -0
  92. package/src/core/retrieval-trace-management.ts +422 -0
  93. package/src/core/retrieval-trace-request.ts +141 -0
  94. package/src/core/retrieval-trace-session.ts +494 -0
  95. package/src/core/retrieval-trace.ts +472 -0
  96. package/src/llm/cache.ts +13 -3
  97. package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
  98. package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
  99. package/src/mcp/context.ts +161 -0
  100. package/src/mcp/http-security.ts +477 -0
  101. package/src/mcp/http-session.ts +272 -0
  102. package/src/mcp/http-transport.ts +370 -0
  103. package/src/mcp/resources/index.ts +141 -134
  104. package/src/mcp/server.ts +19 -79
  105. package/src/mcp/tools/add-collection.ts +3 -1
  106. package/src/mcp/tools/capture.ts +3 -0
  107. package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
  108. package/src/mcp/tools/context.ts +68 -16
  109. package/src/mcp/tools/embed.ts +62 -52
  110. package/src/mcp/tools/get.ts +35 -1
  111. package/src/mcp/tools/index-cmd.ts +88 -74
  112. package/src/mcp/tools/index.ts +96 -2
  113. package/src/mcp/tools/query.ts +95 -64
  114. package/src/mcp/tools/remove-collection.ts +2 -0
  115. package/src/mcp/tools/search.ts +36 -13
  116. package/src/mcp/tools/status.ts +11 -0
  117. package/src/mcp/tools/sync.ts +16 -14
  118. package/src/mcp/tools/trace.ts +143 -0
  119. package/src/mcp/tools/vsearch.ts +71 -38
  120. package/src/mcp/tools/workspace-write.ts +7 -3
  121. package/src/pipeline/answer.ts +167 -26
  122. package/src/pipeline/graph-retrieval.ts +15 -1
  123. package/src/pipeline/hybrid.ts +151 -43
  124. package/src/pipeline/search.ts +36 -3
  125. package/src/pipeline/trace-metadata.ts +47 -0
  126. package/src/pipeline/types.ts +43 -0
  127. package/src/pipeline/vsearch.ts +101 -38
  128. package/src/sdk/client.ts +380 -71
  129. package/src/sdk/documents.ts +48 -1
  130. package/src/sdk/index.ts +17 -0
  131. package/src/sdk/types.ts +28 -0
  132. package/src/serve/background-runtime.ts +12 -212
  133. package/src/serve/context-capsule.ts +67 -8
  134. package/src/serve/embed-scheduler.ts +74 -43
  135. package/src/serve/index.ts +9 -0
  136. package/src/serve/jobs.ts +78 -80
  137. package/src/serve/public/app.tsx +12 -1
  138. package/src/serve/public/components/HealthCenter.tsx +74 -1
  139. package/src/serve/public/globals.built.css +1 -1
  140. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  141. package/src/serve/public/pages/Dashboard.tsx +11 -0
  142. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  143. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  144. package/src/serve/resident-admission.ts +159 -0
  145. package/src/serve/resident-background-work.ts +39 -0
  146. package/src/serve/resident-request.ts +55 -0
  147. package/src/serve/resident-runtime.ts +490 -0
  148. package/src/serve/resident-status.ts +96 -0
  149. package/src/serve/retrieval-trace.ts +28 -0
  150. package/src/serve/routes/api.ts +629 -239
  151. package/src/serve/routes/mcp.ts +69 -0
  152. package/src/serve/routes/traces.ts +156 -0
  153. package/src/serve/server.ts +276 -37
  154. package/src/serve/status-model.ts +51 -0
  155. package/src/serve/status.ts +5 -0
  156. package/src/store/index.ts +31 -0
  157. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  158. package/src/store/migrations/index.ts +2 -0
  159. package/src/store/retrieval-trace-codec.ts +384 -0
  160. package/src/store/sqlite/adapter.ts +179 -10
  161. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  162. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  163. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  164. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  165. package/src/store/types.ts +297 -0
  166. package/src/store/vector/sqlite-vec.ts +76 -1
  167. package/src/store/vector/types.ts +1 -1
@@ -164,6 +164,15 @@ async function writeOutput(
164
164
  }
165
165
  }
166
166
 
167
+ /** Emit opt-in trace identity without changing command stdout payloads. */
168
+ export function writeRetrievalTraceReceipt(
169
+ metadata: { traceId: string } | undefined
170
+ ): void {
171
+ if (metadata) {
172
+ process.stderr.write(`Trace: ${metadata.traceId}\n`);
173
+ }
174
+ }
175
+
167
176
  async function resolveTerminalLinkPolicy(
168
177
  format: "terminal" | "json" | "files" | "csv" | "md" | "xml"
169
178
  ): Promise<
@@ -292,6 +301,7 @@ export function createProgram(): Command {
292
301
  wireOnboardingCommands(program);
293
302
  wireCaptureCommand(program);
294
303
  wireManagementCommands(program);
304
+ wireTraceCommands(program);
295
305
  wirePublishCommand(program);
296
306
  wireVecCommands(program);
297
307
  wireRetrievalCommands(program);
@@ -315,6 +325,244 @@ Report issues: ${ISSUES_URL}`
315
325
  return program;
316
326
  }
317
327
 
328
+ function wireTraceCommands(program: Command): void {
329
+ const traceCmd = program
330
+ .command("trace")
331
+ .description("Inspect and manage private local retrieval traces");
332
+
333
+ const outputFormat = (options: Record<string, unknown>): "json" | "md" =>
334
+ getFormat(options) === "json" ? "json" : "md";
335
+
336
+ traceCmd
337
+ .command("list")
338
+ .description("List bounded, redacted retrieval trace summaries")
339
+ .option("-n, --limit <num>", "maximum traces", "50")
340
+ .option("--cursor <cursor>", "continue from a previous list receipt")
341
+ .option("--json", "JSON output")
342
+ .option("--md", "Markdown output")
343
+ .action(async (cmdOpts: Record<string, unknown>) => {
344
+ const globals = getGlobals();
345
+ const { traceList } = await import("./commands/trace");
346
+ const output = await traceList(
347
+ {
348
+ limit: parsePositiveInt("limit", cmdOpts.limit),
349
+ cursor: cmdOpts.cursor as string | undefined,
350
+ },
351
+ {
352
+ configPath: globals.config,
353
+ indexName: globals.index,
354
+ format: outputFormat(cmdOpts),
355
+ }
356
+ );
357
+ process.stdout.write(output);
358
+ });
359
+
360
+ traceCmd
361
+ .command("show <trace-id>")
362
+ .description("Inspect one bounded retrieval trace receipt")
363
+ .option("--detail-limit <num>", "maximum records per detail section", "500")
364
+ .option("--json", "JSON output")
365
+ .option("--md", "Markdown output")
366
+ .action(async (traceId: string, cmdOpts: Record<string, unknown>) => {
367
+ const globals = getGlobals();
368
+ const { traceShow } = await import("./commands/trace");
369
+ const output = await traceShow(
370
+ traceId,
371
+ {
372
+ detailLimit: parsePositiveInt("detail-limit", cmdOpts.detailLimit),
373
+ },
374
+ {
375
+ configPath: globals.config,
376
+ indexName: globals.index,
377
+ format: outputFormat(cmdOpts),
378
+ }
379
+ );
380
+ process.stdout.write(output);
381
+ });
382
+
383
+ traceCmd
384
+ .command("label <trace-id>")
385
+ .description("Append an explicit retrieval relevance judgment")
386
+ .requiredOption(
387
+ "--label <label>",
388
+ "relevant, irrelevant, or missing-expected"
389
+ )
390
+ .requiredOption("--target <ref>", "evidence or expected-document reference")
391
+ .option("--target-kind <kind>", "document, chunk, or span")
392
+ .option("--from-line <num>", "exact evidence start line")
393
+ .option("--to-line <num>", "exact evidence end line")
394
+ .option("--source-hash <sha256>", "expected immutable source hash")
395
+ .option("--docid <docid>", "expected document ID")
396
+ .option("--idempotency-key <key>", "caller retry key")
397
+ .option("--json", "JSON output")
398
+ .option("--md", "Markdown output")
399
+ .action(async (traceId: string, cmdOpts: Record<string, unknown>) => {
400
+ const globals = getGlobals();
401
+ const rawLabel = String(cmdOpts.label).replace("-", "_");
402
+ if (!["relevant", "irrelevant", "missing_expected"].includes(rawLabel)) {
403
+ throw new CliError(
404
+ "VALIDATION",
405
+ "--label must be relevant, irrelevant, or missing-expected"
406
+ );
407
+ }
408
+ const rawTargetKind = cmdOpts.targetKind;
409
+ if (
410
+ rawTargetKind !== undefined &&
411
+ (typeof rawTargetKind !== "string" ||
412
+ !["document", "chunk", "span"].includes(rawTargetKind))
413
+ ) {
414
+ throw new CliError(
415
+ "VALIDATION",
416
+ "--target-kind must be document, chunk, or span"
417
+ );
418
+ }
419
+ const { traceLabel } = await import("./commands/trace");
420
+ const output = await traceLabel(
421
+ {
422
+ traceId,
423
+ label: rawLabel as "relevant" | "irrelevant" | "missing_expected",
424
+ targetRef: String(cmdOpts.target),
425
+ targetKind: rawTargetKind as
426
+ | "document"
427
+ | "chunk"
428
+ | "span"
429
+ | undefined,
430
+ startLine:
431
+ cmdOpts.fromLine === undefined
432
+ ? undefined
433
+ : parsePositiveInt("from-line", cmdOpts.fromLine),
434
+ endLine:
435
+ cmdOpts.toLine === undefined
436
+ ? undefined
437
+ : parsePositiveInt("to-line", cmdOpts.toLine),
438
+ sourceHash: cmdOpts.sourceHash as string | undefined,
439
+ docid: cmdOpts.docid as string | undefined,
440
+ idempotencyKey: cmdOpts.idempotencyKey as string | undefined,
441
+ },
442
+ {
443
+ configPath: globals.config,
444
+ indexName: globals.index,
445
+ format: outputFormat(cmdOpts),
446
+ }
447
+ );
448
+ process.stdout.write(output);
449
+ });
450
+
451
+ traceCmd
452
+ .command("export <trace-ids...>")
453
+ .description("Export immutable terminal traces as one local receipt")
454
+ .option("--format <format>", "agentic-receipt or qrels", "agentic-receipt")
455
+ .option("--output <path>", "write canonical artifact atomically")
456
+ .option("--json", "JSON output")
457
+ .action(async (traceIds: string[], cmdOpts: Record<string, unknown>) => {
458
+ const globals = getGlobals();
459
+ const exportFormat = String(cmdOpts.format);
460
+ if (!["agentic-receipt", "qrels"].includes(exportFormat)) {
461
+ throw new CliError(
462
+ "VALIDATION",
463
+ "--format must be agentic-receipt or qrels"
464
+ );
465
+ }
466
+ const { traceExport } = await import("./commands/trace");
467
+ process.stdout.write(
468
+ await traceExport(traceIds, {
469
+ configPath: globals.config,
470
+ indexName: globals.index,
471
+ format: "json",
472
+ output: cmdOpts.output as string | undefined,
473
+ exportFormat: exportFormat as "agentic-receipt" | "qrels",
474
+ })
475
+ );
476
+ });
477
+
478
+ traceCmd
479
+ .command("replay <export-id>")
480
+ .description("Compare one immutable qrels baseline with a candidate")
481
+ .requiredOption("--candidate <type>", "bm25, vector, or hybrid")
482
+ .option("-n, --limit <num>", "result cutoff")
483
+ .option("--candidate-limit <num>", "candidate pool size")
484
+ .option("--no-expand", "disable query expansion")
485
+ .option("--no-rerank", "disable reranking")
486
+ .option("--json", "JSON output")
487
+ .option("--md", "Markdown output")
488
+ .action(async (exportId: string, cmdOpts: Record<string, unknown>) => {
489
+ const globals = getGlobals();
490
+ const candidateType = String(cmdOpts.candidate);
491
+ if (!["bm25", "vector", "hybrid"].includes(candidateType)) {
492
+ throw new CliError(
493
+ "VALIDATION",
494
+ "--candidate must be bm25, vector, or hybrid"
495
+ );
496
+ }
497
+ const { traceReplay } = await import("./commands/replay");
498
+ process.stdout.write(
499
+ await traceReplay(
500
+ exportId,
501
+ {
502
+ id: `cli-${candidateType}`,
503
+ type: candidateType as "bm25" | "vector" | "hybrid",
504
+ limit:
505
+ cmdOpts.limit === undefined
506
+ ? undefined
507
+ : parsePositiveInt("limit", cmdOpts.limit),
508
+ candidateLimit:
509
+ cmdOpts.candidateLimit === undefined
510
+ ? undefined
511
+ : parsePositiveInt("candidate-limit", cmdOpts.candidateLimit),
512
+ noExpand: cmdOpts.expand === false,
513
+ noRerank: cmdOpts.rerank === false,
514
+ },
515
+ {
516
+ configPath: globals.config,
517
+ indexName: globals.index,
518
+ format: outputFormat(cmdOpts),
519
+ offline: globals.offline,
520
+ }
521
+ )
522
+ );
523
+ });
524
+
525
+ traceCmd
526
+ .command("delete <trace-id>")
527
+ .description("Delete one trace and every owned local record")
528
+ .option("--json", "JSON output")
529
+ .option("--md", "Markdown output")
530
+ .action(async (traceId: string, cmdOpts: Record<string, unknown>) => {
531
+ const globals = getGlobals();
532
+ const { traceDelete } = await import("./commands/trace");
533
+ process.stdout.write(
534
+ await traceDelete(traceId, {
535
+ configPath: globals.config,
536
+ indexName: globals.index,
537
+ format: outputFormat(cmdOpts),
538
+ })
539
+ );
540
+ });
541
+
542
+ traceCmd
543
+ .command("purge")
544
+ .description("Delete every local retrieval trace receipt")
545
+ .option("--json", "JSON output")
546
+ .option("--md", "Markdown output")
547
+ .action(async (cmdOpts: Record<string, unknown>) => {
548
+ const globals = getGlobals();
549
+ if (!globals.yes) {
550
+ throw new CliError(
551
+ "VALIDATION",
552
+ "Trace purge requires the global --yes confirmation"
553
+ );
554
+ }
555
+ const { tracePurge } = await import("./commands/trace");
556
+ process.stdout.write(
557
+ await tracePurge({
558
+ configPath: globals.config,
559
+ indexName: globals.index,
560
+ format: outputFormat(cmdOpts),
561
+ })
562
+ );
563
+ });
564
+ }
565
+
318
566
  // ─────────────────────────────────────────────────────────────────────────────
319
567
  // Search Commands (search, vsearch, query, ask)
320
568
  // ─────────────────────────────────────────────────────────────────────────────
@@ -435,6 +683,7 @@ function wireSearchCommands(program: Command): void {
435
683
  terminalLinks: await resolveTerminalLinkPolicy(format),
436
684
  });
437
685
  await writeOutput(output, format);
686
+ writeRetrievalTraceReceipt(result.metadata);
438
687
  });
439
688
 
440
689
  // vsearch - Vector similarity search
@@ -547,6 +796,7 @@ function wireSearchCommands(program: Command): void {
547
796
  terminalLinks: await resolveTerminalLinkPolicy(format),
548
797
  });
549
798
  await writeOutput(output, format);
799
+ writeRetrievalTraceReceipt(result.metadata);
550
800
  });
551
801
 
552
802
  // query - Hybrid search with expansion and reranking
@@ -768,6 +1018,7 @@ function wireSearchCommands(program: Command): void {
768
1018
  terminalLinks: await resolveTerminalLinkPolicy(format),
769
1019
  });
770
1020
  await writeOutput(output, format);
1021
+ writeRetrievalTraceReceipt(result.metadata);
771
1022
  });
772
1023
 
773
1024
  // bench - Retrieval benchmark fixture runner
@@ -960,6 +1211,7 @@ function wireSearchCommands(program: Command): void {
960
1211
  showSources,
961
1212
  });
962
1213
  await writeOutput(output, format);
1214
+ writeRetrievalTraceReceipt(result.metadata);
963
1215
  });
964
1216
  }
965
1217
 
@@ -1191,6 +1443,7 @@ function wireRetrievalCommands(program: Command): void {
1191
1443
  parsePositiveInt.bind(null, "limit")
1192
1444
  )
1193
1445
  .option("--line-numbers", "Prefix lines with numbers")
1446
+ .option("--trace-id <id>", "Continue an open retrieval trace")
1194
1447
  .option("--source", "Include source metadata")
1195
1448
  .option("--json", "JSON output")
1196
1449
  .option("--md", "Markdown output")
@@ -1209,6 +1462,7 @@ function wireRetrievalCommands(program: Command): void {
1209
1462
  source: Boolean(cmdOpts.source),
1210
1463
  json: format === "json",
1211
1464
  md: format === "md",
1465
+ traceId: cmdOpts.traceId as string | undefined,
1212
1466
  });
1213
1467
 
1214
1468
  if (!result.success) {
@@ -1225,6 +1479,7 @@ function wireRetrievalCommands(program: Command): void {
1225
1479
  md: format === "md",
1226
1480
  })}\n`
1227
1481
  );
1482
+ writeRetrievalTraceReceipt(result.metadata);
1228
1483
  });
1229
1484
 
1230
1485
  // multi-get - Retrieve multiple documents
@@ -2806,20 +3061,46 @@ function wireGraphCommand(program: Command): void {
2806
3061
  // Serve Command (web UI)
2807
3062
  // ─────────────────────────────────────────────────────────────────────────────
2808
3063
 
2809
- function wireDaemonCommand(program: Command): void {
2810
- const daemonCmd = program
2811
- .command("daemon")
2812
- .description("Start headless continuous indexing")
3064
+ function addGatewayOptions(
3065
+ command: Command,
3066
+ hostDescription = "HTTP listen address (default: 127.0.0.1)"
3067
+ ): Command {
3068
+ return command
3069
+ .option("--host <address>", hostDescription)
3070
+ .option("--mcp-token-file <path>", "restrictive bearer token file")
2813
3071
  .option(
2814
- "--no-sync-on-start",
2815
- "skip initial sync and only watch future file changes"
3072
+ "--mcp-allowed-host <host>",
3073
+ "exact allowed Host header (repeatable)",
3074
+ (value: string, previous: string[] = []) => [...previous, value]
2816
3075
  )
2817
- // Local `--json` so `gno daemon --status --json` parses cleanly in any
2818
- // argv position. Mirrors the serve wiring: gated to --status only.
2819
3076
  .option(
2820
- "--json",
2821
- "JSON output (applies to --status; see process-status schema)"
3077
+ "--mcp-allowed-origin <origin>",
3078
+ "exact allowed Origin (repeatable)",
3079
+ (value: string, previous: string[] = []) => [...previous, value]
3080
+ )
3081
+ .option(
3082
+ "--mcp-enable-write",
3083
+ "authorize HTTP MCP mutation tools (independent of authentication)"
2822
3084
  );
3085
+ }
3086
+
3087
+ function wireDaemonCommand(program: Command): void {
3088
+ const daemonCmd = addGatewayOptions(
3089
+ program
3090
+ .command("daemon")
3091
+ .description("Start headless continuous indexing")
3092
+ .option("-p, --port <num>", "MCP gateway port", "3000")
3093
+ .option(
3094
+ "--no-sync-on-start",
3095
+ "skip initial sync and only watch future file changes"
3096
+ )
3097
+ // Local `--json` so `gno daemon --status --json` parses cleanly in any
3098
+ // argv position. Mirrors the serve wiring: gated to --status only.
3099
+ .option(
3100
+ "--json",
3101
+ "JSON output (applies to --status; see process-status schema)"
3102
+ )
3103
+ );
2823
3104
 
2824
3105
  // --detach / --status / --stop are mutually exclusive. Use Commander's
2825
3106
  // Option API so the conflict error is the native "option '--status'
@@ -2873,8 +3154,8 @@ function wireDaemonCommand(program: Command): void {
2873
3154
  * the foreground + detached-child paths boot the runtime.
2874
3155
  *
2875
3156
  * Mirrors `handleServeAction` exactly — same helper imports, same JSON
2876
- * gating, same detached-child verification + pid-file cleanup. Daemon has no
2877
- * `--port`, so the foreground path skips port validation entirely.
3157
+ * gating, same detached-child verification + pid-file cleanup. Daemon's port
3158
+ * identifies its headless MCP gateway.
2878
3159
  */
2879
3160
  async function handleDaemonAction(
2880
3161
  cmdOpts: Record<string, unknown>,
@@ -2927,7 +3208,9 @@ async function handleDaemonAction(
2927
3208
  }
2928
3209
 
2929
3210
  if (cmdOpts.detach) {
3211
+ const port = parsePositiveInt("port", cmdOpts.port);
2930
3212
  await runDaemonDetach({
3213
+ port,
2931
3214
  paths,
2932
3215
  spawnDetached,
2933
3216
  argv: resolveCliArgv(cmd),
@@ -2954,6 +3237,8 @@ async function handleDaemonAction(
2954
3237
  installPidFileCleanup(paths.pidFile);
2955
3238
  }
2956
3239
 
3240
+ const port = parsePositiveInt("port", cmdOpts.port);
3241
+
2957
3242
  const { daemon } = await import("./commands/daemon.js");
2958
3243
  const result = await daemon({
2959
3244
  configPath: globals.config,
@@ -2962,6 +3247,12 @@ async function handleDaemonAction(
2962
3247
  verbose: globals.verbose,
2963
3248
  quiet: globals.quiet,
2964
3249
  noSyncOnStart: cmdOpts.syncOnStart === false,
3250
+ port,
3251
+ host: cmdOpts.host as string | undefined,
3252
+ tokenFile: cmdOpts.mcpTokenFile as string | undefined,
3253
+ allowedHosts: cmdOpts.mcpAllowedHost as string[] | undefined,
3254
+ allowedOrigins: cmdOpts.mcpAllowedOrigin as string[] | undefined,
3255
+ enableWrite: cmdOpts.mcpEnableWrite === true ? true : undefined,
2965
3256
  });
2966
3257
  if (!result.success) {
2967
3258
  throw new CliError("RUNTIME", result.error);
@@ -2995,8 +3286,9 @@ async function runDaemonStatus(deps: DaemonStatusDeps): Promise<void> {
2995
3286
  process.stdout.write("gno daemon status\n");
2996
3287
  process.stdout.write(`${"─".repeat(50)}\n`);
2997
3288
  if (status.running) {
2998
- // Daemon is headless — no port to print.
2999
- process.stdout.write(` running yes (pid ${status.pid})\n`);
3289
+ process.stdout.write(
3290
+ ` running yes (pid ${status.pid}, port ${status.port})\n`
3291
+ );
3000
3292
  if (status.version) {
3001
3293
  process.stdout.write(` version ${status.version}\n`);
3002
3294
  }
@@ -3091,6 +3383,7 @@ async function runDaemonStop(deps: DaemonStopDeps): Promise<void> {
3091
3383
  }
3092
3384
 
3093
3385
  interface DaemonDetachDeps {
3386
+ port: number;
3094
3387
  paths: { pidFile: string; logFile: string };
3095
3388
  spawnDetached: typeof import("./detach.js").spawnDetached;
3096
3389
  /**
@@ -3111,24 +3404,28 @@ async function runDaemonDetach(deps: DaemonDetachDeps): Promise<void> {
3111
3404
  argv: childArgv,
3112
3405
  pidFile: deps.paths.pidFile,
3113
3406
  logFile: deps.paths.logFile,
3407
+ port: deps.port,
3114
3408
  });
3115
- // Daemon is headless print pid only (no URL).
3409
+ // Daemon has no Web UI URL; its listener exists solely for MCP.
3116
3410
  process.stdout.write(`PID ${result.pid}\n`);
3117
3411
  }
3118
3412
 
3119
3413
  function wireServeCommand(program: Command): void {
3120
- const serveCmd = program
3121
- .command("serve")
3122
- .description("Start web UI server")
3123
- .option("-p, --port <num>", "port to listen on", "3000")
3124
- // Local `--json` so `gno serve --status --json` parses cleanly in any
3125
- // argv position. Global `--json` already exists on the root program
3126
- // (see line 208); resolution goes through `getFormat()` /
3127
- // `getGlobals()` which precedence-merges local over global.
3128
- .option(
3129
- "--json",
3130
- "JSON output (applies to --status; see process-status schema)"
3131
- );
3414
+ const serveCmd = addGatewayOptions(
3415
+ program
3416
+ .command("serve")
3417
+ .description("Start web UI server")
3418
+ .option("-p, --port <num>", "port to listen on", "3000")
3419
+ // Local `--json` so `gno serve --status --json` parses cleanly in any
3420
+ // argv position. Global `--json` already exists on the root program
3421
+ // (see line 208); resolution goes through `getFormat()` /
3422
+ // `getGlobals()` which precedence-merges local over global.
3423
+ .option(
3424
+ "--json",
3425
+ "JSON output (applies to --status; see process-status schema)"
3426
+ ),
3427
+ "loopback HTTP listen address (default: 127.0.0.1)"
3428
+ );
3132
3429
 
3133
3430
  // --detach / --status / --stop are mutually exclusive. Use Commander's
3134
3431
  // Option API so the conflict error is the native "option '--status'
@@ -3275,6 +3572,11 @@ async function handleServeAction(
3275
3572
  port,
3276
3573
  configPath: globals.config,
3277
3574
  index: globals.index,
3575
+ host: cmdOpts.host as string | undefined,
3576
+ tokenFile: cmdOpts.mcpTokenFile as string | undefined,
3577
+ allowedHosts: cmdOpts.mcpAllowedHost as string[] | undefined,
3578
+ allowedOrigins: cmdOpts.mcpAllowedOrigin as string[] | undefined,
3579
+ enableWrite: cmdOpts.mcpEnableWrite === true ? true : undefined,
3278
3580
  });
3279
3581
 
3280
3582
  if (!result.success) {
@@ -50,6 +50,8 @@ export {
50
50
  CollectionSchema,
51
51
  type Config,
52
52
  ConfigSchema,
53
+ HttpGatewayConfigSchema,
54
+ HttpGatewayLimitsSchema,
53
55
  CONTENT_TYPE_GRAPH_HINTS,
54
56
  type ContentTypeConfig,
55
57
  type ContentTypeGraphHint,
@@ -67,3 +69,13 @@ export {
67
69
  type ScopeType,
68
70
  ScopeTypeSchema,
69
71
  } from "./types";
72
+ export type { HttpGatewayConfig } from "./types";
73
+ export {
74
+ RETRIEVAL_TRACE_DEFAULT_RETENTION,
75
+ type RetrievalTraceConfig,
76
+ RetrievalTraceConfigSchema,
77
+ type RetrievalTraceRedactionMode,
78
+ RetrievalTraceRedactionModeSchema,
79
+ type RetrievalTraceRetention,
80
+ RetrievalTraceRetentionSchema,
81
+ } from "./retrieval-traces";
@@ -0,0 +1,56 @@
1
+ /** Opt-in retrieval trace configuration and bounded-retention defaults. */
2
+
3
+ import { z } from "zod";
4
+
5
+ export const RETRIEVAL_TRACE_DEFAULT_RETENTION = {
6
+ maxAgeDays: 30,
7
+ maxTraces: 1_000,
8
+ maxRecordsPerTrace: 10_000,
9
+ maxBytes: 16 * 1024 * 1024,
10
+ } as const;
11
+
12
+ export const RetrievalTraceRetentionSchema = z
13
+ .object({
14
+ maxAgeDays: z.number().int().min(1).max(3_650),
15
+ maxTraces: z.number().int().min(1).max(1_000_000),
16
+ maxRecordsPerTrace: z.number().int().min(1).max(100_000),
17
+ maxBytes: z
18
+ .number()
19
+ .int()
20
+ .min(64 * 1024)
21
+ .max(1024 * 1024 * 1024),
22
+ })
23
+ .strict();
24
+
25
+ export const RetrievalTraceRedactionModeSchema = z.enum(["metadata", "replay"]);
26
+
27
+ const DisabledRetrievalTraceConfigSchema = z
28
+ .object({
29
+ enabled: z.literal(false),
30
+ })
31
+ .strict();
32
+
33
+ const EnabledRetrievalTraceConfigSchema = z
34
+ .object({
35
+ enabled: z.literal(true),
36
+ /**
37
+ * `replay` stores replay inputs such as the raw query and filters. Choosing
38
+ * it is explicit consent; the safer metadata-only projection is recommended.
39
+ */
40
+ redactionMode: RetrievalTraceRedactionModeSchema,
41
+ retention: RetrievalTraceRetentionSchema,
42
+ })
43
+ .strict();
44
+
45
+ export const RetrievalTraceConfigSchema = z.discriminatedUnion("enabled", [
46
+ DisabledRetrievalTraceConfigSchema,
47
+ EnabledRetrievalTraceConfigSchema,
48
+ ]);
49
+
50
+ export type RetrievalTraceConfig = z.infer<typeof RetrievalTraceConfigSchema>;
51
+ export type RetrievalTraceRetention = z.infer<
52
+ typeof RetrievalTraceRetentionSchema
53
+ >;
54
+ export type RetrievalTraceRedactionMode = z.infer<
55
+ typeof RetrievalTraceRedactionModeSchema
56
+ >;
@@ -8,6 +8,7 @@
8
8
  import { z } from "zod";
9
9
 
10
10
  import { URI_PREFIX } from "../app/constants";
11
+ import { RetrievalTraceConfigSchema } from "./retrieval-traces";
11
12
 
12
13
  // ─────────────────────────────────────────────────────────────────────────────
13
14
  // Constants
@@ -245,6 +246,40 @@ export const ModelConfigSchema = z.object({
245
246
 
246
247
  export type ModelConfig = z.infer<typeof ModelConfigSchema>;
247
248
 
249
+ // ────────────────────────────────────────────────────────────────────────────
250
+ // Resident HTTP gateway schema
251
+ // ────────────────────────────────────────────────────────────────────────────
252
+
253
+ export const HttpGatewayLimitsSchema = z.object({
254
+ maxBodyBytes: z
255
+ .number()
256
+ .int()
257
+ .min(1)
258
+ .max(16 * 1024 * 1024)
259
+ .optional(),
260
+ maxRequestsPerMinute: z.number().int().min(1).max(100_000).optional(),
261
+ maxConcurrentRequests: z.number().int().min(1).max(10_000).optional(),
262
+ maxQueuedRequests: z.number().int().min(0).max(10_000).optional(),
263
+ maxSessions: z.number().int().min(1).max(10_000).optional(),
264
+ sessionIdleTimeoutMs: z.number().int().min(1_000).optional(),
265
+ });
266
+
267
+ export const HttpGatewayConfigSchema = z.object({
268
+ /** Literal listen address. Defaults to IPv4 loopback. */
269
+ host: z.string().min(1).optional(),
270
+ /** Bearer token file. Required for wildcard/non-loopback binding. */
271
+ tokenFile: z.string().min(1).optional(),
272
+ /** Exact Host header allowlist. */
273
+ allowedHosts: z.array(z.string().min(1)).optional(),
274
+ /** Exact Origin allowlist. Origin-less non-browser clients remain supported. */
275
+ allowedOrigins: z.array(z.string().min(1)).optional(),
276
+ /** Separate mutation authorization; authentication alone never enables it. */
277
+ enableWrite: z.boolean().optional(),
278
+ limits: HttpGatewayLimitsSchema.optional(),
279
+ });
280
+
281
+ export type HttpGatewayConfig = z.infer<typeof HttpGatewayConfigSchema>;
282
+
248
283
  // ─────────────────────────────────────────────────────────────────────────────
249
284
  // Content Type Schema
250
285
  // ─────────────────────────────────────────────────────────────────────────────
@@ -301,6 +336,12 @@ export const ConfigSchema = z.object({
301
336
 
302
337
  /** Model configuration */
303
338
  models: ModelConfigSchema.optional(),
339
+
340
+ /** Resident Streamable HTTP MCP gateway configuration */
341
+ gateway: HttpGatewayConfigSchema.optional(),
342
+
343
+ /** Private local retrieval trace recording. Absent means recording off. */
344
+ retrievalTraces: RetrievalTraceConfigSchema.optional(),
304
345
  });
305
346
 
306
347
  export type Config = Omit<z.infer<typeof ConfigSchema>, "contentTypes"> & {
@@ -29,6 +29,7 @@ import type { ContextConfiguredGuidance } from "./context-guidance";
29
29
  import { decorateUriForIndex } from "../app/constants";
30
30
  import { canonicalizeIndexName } from "../app/index-name";
31
31
  import { resolveTemporalRange } from "../pipeline/temporal";
32
+ import { attachSearchResultPlannerMetadata } from "../pipeline/trace-metadata";
32
33
  import {
33
34
  SEARCH_RESULT_PLANNER_METADATA,
34
35
  type SearchResultPlannerMetadata,
@@ -363,10 +364,16 @@ export const planContextEvidence = async <T, P>(
363
364
  }
364
365
  const decoratedResults = responses
365
366
  .flatMap((response) => response.results)
366
- .map((result) => ({
367
- ...result,
368
- uri: decorateUriForIndex(result.uri, indexName),
369
- }));
367
+ .map((result) => {
368
+ const decorated = {
369
+ ...result,
370
+ uri: decorateUriForIndex(result.uri, indexName),
371
+ };
372
+ const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
373
+ return metadata
374
+ ? attachSearchResultPlannerMetadata(decorated, metadata)
375
+ : decorated;
376
+ });
370
377
  const results = decoratedResults
371
378
  .map((result, index) => ({
372
379
  result,