@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
@@ -6,13 +6,20 @@
6
6
  * @module src/serve/server
7
7
  */
8
8
 
9
+ import type { HttpGatewayOverrides } from "../mcp/http-security";
10
+ import type { ResidentRuntime } from "./resident-runtime";
9
11
  import type { ContextHolder } from "./routes/api";
10
12
 
13
+ import {
14
+ isHttpGatewayLoopbackBind,
15
+ resolveHttpGatewayConfig,
16
+ } from "../mcp/http-security";
11
17
  import { startBackgroundRuntime } from "./background-runtime";
12
18
  import { handleContextBuild, handleContextVerify } from "./context-capsule";
13
19
  import { DocumentEventBus } from "./doc-events";
14
20
  // HTML import - Bun handles bundling TSX/CSS automatically via routes
15
21
  import homepage from "./public/index.html";
22
+ import { handleResidentRead } from "./resident-request";
16
23
  import {
17
24
  handleActiveJob,
18
25
  handleAsk,
@@ -49,6 +56,7 @@ import {
49
56
  handleQuery,
50
57
  handleQueryDiagnose,
51
58
  handleRefactorPlan,
59
+ handleResidentStatus,
52
60
  handleRenameDoc,
53
61
  handleRevealDoc,
54
62
  handleSearch,
@@ -67,9 +75,18 @@ import {
67
75
  handleDocLinks,
68
76
  handleDocSimilar,
69
77
  } from "./routes/links";
78
+ import { createMcpHttpGateway } from "./routes/mcp";
79
+ import {
80
+ handleTraceDelete,
81
+ handleTraceExport,
82
+ handleTraceLabel,
83
+ handleTraceList,
84
+ handleTracePurge,
85
+ handleTraceShow,
86
+ } from "./routes/traces";
70
87
  import { forbiddenResponse, isRequestAllowed } from "./security";
71
88
 
72
- export interface ServeOptions {
89
+ export interface ServeOptions extends HttpGatewayOverrides {
73
90
  /** Port to listen on (default: 3000) */
74
91
  port?: number;
75
92
  /** Config path override */
@@ -85,8 +102,14 @@ export interface ServeResult {
85
102
 
86
103
  interface StartServerDependencies {
87
104
  startBackgroundRuntime?: typeof startBackgroundRuntime;
105
+ createMcpHttpGateway?: typeof createMcpHttpGateway;
88
106
  serve?: typeof Bun.serve;
89
107
  handleInstallConnector?: typeof handleInstallConnector;
108
+ handleDocs?: typeof handleDocs;
109
+ handleVerifyConnector?: typeof handleVerifyConnector;
110
+ handleImportPreview?: typeof handleImportPreview;
111
+ handlePublishExport?: typeof handlePublishExport;
112
+ handleRefactorPlan?: typeof handleRefactorPlan;
90
113
  waitForShutdown?: (signal: AbortSignal) => Promise<void>;
91
114
  }
92
115
 
@@ -152,6 +175,7 @@ export async function startServer(
152
175
  const runtimeResult = await (
153
176
  dependencies.startBackgroundRuntime ?? startBackgroundRuntime
154
177
  )({
178
+ mode: "serve",
155
179
  configPath: options.configPath,
156
180
  index: options.index,
157
181
  requireCollections: false,
@@ -163,6 +187,35 @@ export async function startServer(
163
187
  const runtime = runtimeResult.runtime;
164
188
  const store = runtime.store;
165
189
  const ctxHolder: ContextHolder = runtime.ctxHolder;
190
+ const gatewayConfig = resolveHttpGatewayConfig(runtime.config.gateway, {
191
+ host: options.host,
192
+ port,
193
+ tokenFile: options.tokenFile,
194
+ allowedHosts: options.allowedHosts,
195
+ allowedOrigins: options.allowedOrigins,
196
+ enableWrite: options.enableWrite,
197
+ });
198
+ if (!isHttpGatewayLoopbackBind(gatewayConfig.host)) {
199
+ await runtime.dispose();
200
+ return {
201
+ success: false,
202
+ error:
203
+ "gno serve remains loopback-only because Web and REST share its listener; use gno daemon for authenticated non-loopback MCP",
204
+ };
205
+ }
206
+ let gateway: Awaited<ReturnType<typeof createMcpHttpGateway>>;
207
+ try {
208
+ gateway = await (dependencies.createMcpHttpGateway ?? createMcpHttpGateway)(
209
+ runtime as ResidentRuntime,
210
+ gatewayConfig
211
+ );
212
+ } catch (error) {
213
+ await runtime.dispose();
214
+ return {
215
+ success: false,
216
+ error: error instanceof Error ? error.message : String(error),
217
+ };
218
+ }
166
219
 
167
220
  // Shutdown controller for clean lifecycle
168
221
  const shutdownController = new AbortController();
@@ -185,13 +238,14 @@ export async function startServer(
185
238
  try {
186
239
  server = (dependencies.serve ?? Bun.serve)({
187
240
  port,
188
- hostname: "127.0.0.1", // Loopback only - no LAN exposure
241
+ hostname: gatewayConfig.host,
189
242
 
190
243
  // Enable development mode for HMR and console logging
191
244
  development: isDev,
192
245
 
193
246
  // Static routes - Bun handles HTML bundling and /_bun/* assets automatically
194
247
  routes: {
248
+ "/mcp": gateway.route,
195
249
  // SPA routes - all serve the same React app
196
250
  "/": homepage,
197
251
  "/search": homepage,
@@ -200,6 +254,7 @@ export async function startServer(
200
254
  "/edit": homepage,
201
255
  "/collections": homepage,
202
256
  "/connectors": homepage,
257
+ "/traces": homepage,
203
258
  "/ask": homepage,
204
259
  "/graph": homepage,
205
260
 
@@ -208,8 +263,101 @@ export async function startServer(
208
263
  GET: () => withSecurityHeaders(handleHealth(), isDev),
209
264
  },
210
265
  "/api/status": {
211
- GET: async () =>
212
- withSecurityHeaders(await handleStatus(ctxHolder.current), isDev),
266
+ GET: async (req: Request) =>
267
+ withSecurityHeaders(
268
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
269
+ handleStatus(ctxHolder.current, {
270
+ getResidentStatus: () =>
271
+ (runtime as ResidentRuntime).getStatus(),
272
+ })
273
+ ),
274
+ isDev
275
+ ),
276
+ },
277
+ "/api/traces/export": {
278
+ POST: async (req: Request) => {
279
+ if (!isRequestAllowed(req, port)) {
280
+ return withSecurityHeaders(forbiddenResponse(), isDev);
281
+ }
282
+ return withSecurityHeaders(
283
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
284
+ handleTraceExport(store, req)
285
+ ),
286
+ isDev
287
+ );
288
+ },
289
+ },
290
+ "/api/traces/:traceId/judgments": {
291
+ POST: async (req: Request) => {
292
+ if (!isRequestAllowed(req, port)) {
293
+ return withSecurityHeaders(forbiddenResponse(), isDev);
294
+ }
295
+ const traceId = decodeURIComponent(
296
+ new URL(req.url).pathname.split("/")[3] ?? ""
297
+ );
298
+ return withSecurityHeaders(
299
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
300
+ handleTraceLabel(store, traceId, req)
301
+ ),
302
+ isDev
303
+ );
304
+ },
305
+ },
306
+ "/api/traces/:traceId": {
307
+ GET: async (req: Request) => {
308
+ const traceId = decodeURIComponent(
309
+ new URL(req.url).pathname.split("/")[3] ?? ""
310
+ );
311
+ return withSecurityHeaders(
312
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
313
+ handleTraceShow(store, traceId, req)
314
+ ),
315
+ isDev
316
+ );
317
+ },
318
+ DELETE: async (req: Request) => {
319
+ if (!isRequestAllowed(req, port)) {
320
+ return withSecurityHeaders(forbiddenResponse(), isDev);
321
+ }
322
+ const traceId = decodeURIComponent(
323
+ new URL(req.url).pathname.split("/")[3] ?? ""
324
+ );
325
+ return withSecurityHeaders(
326
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
327
+ handleTraceDelete(store, traceId)
328
+ ),
329
+ isDev
330
+ );
331
+ },
332
+ },
333
+ "/api/traces": {
334
+ GET: async (req: Request) =>
335
+ withSecurityHeaders(
336
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
337
+ handleTraceList(store, req)
338
+ ),
339
+ isDev
340
+ ),
341
+ DELETE: async (req: Request) => {
342
+ if (!isRequestAllowed(req, port)) {
343
+ return withSecurityHeaders(forbiddenResponse(), isDev);
344
+ }
345
+ return withSecurityHeaders(
346
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
347
+ handleTracePurge(store)
348
+ ),
349
+ isDev
350
+ );
351
+ },
352
+ },
353
+ "/api/resident/status": {
354
+ GET: () =>
355
+ withSecurityHeaders(
356
+ handleResidentStatus(() =>
357
+ (runtime as ResidentRuntime).getStatus()
358
+ ),
359
+ isDev
360
+ ),
213
361
  },
214
362
  "/api/collections": {
215
363
  GET: async () =>
@@ -228,9 +376,11 @@ export async function startServer(
228
376
  },
229
377
  },
230
378
  "/api/connectors": {
231
- GET: async () =>
379
+ GET: async (req: Request) =>
232
380
  withSecurityHeaders(
233
- await handleConnectors(ctxHolder.config),
381
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
382
+ handleConnectors(ctxHolder.config)
383
+ ),
234
384
  isDev
235
385
  ),
236
386
  },
@@ -256,7 +406,13 @@ export async function startServer(
256
406
  return withSecurityHeaders(forbiddenResponse(), isDev);
257
407
  }
258
408
  return withSecurityHeaders(
259
- await handleVerifyConnector(ctxHolder.config, store, req),
409
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
410
+ (dependencies.handleVerifyConnector ?? handleVerifyConnector)(
411
+ ctxHolder.config,
412
+ store,
413
+ req
414
+ )
415
+ ),
260
416
  isDev
261
417
  );
262
418
  },
@@ -267,7 +423,12 @@ export async function startServer(
267
423
  return withSecurityHeaders(forbiddenResponse(), isDev);
268
424
  }
269
425
  return withSecurityHeaders(
270
- await handleImportPreview(ctxHolder, req),
426
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
427
+ (dependencies.handleImportPreview ?? handleImportPreview)(
428
+ ctxHolder,
429
+ req
430
+ )
431
+ ),
271
432
  isDev
272
433
  );
273
434
  },
@@ -278,7 +439,13 @@ export async function startServer(
278
439
  return withSecurityHeaders(forbiddenResponse(), isDev);
279
440
  }
280
441
  return withSecurityHeaders(
281
- await handlePublishExport(ctxHolder.config, store, req),
442
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
443
+ (dependencies.handlePublishExport ?? handlePublishExport)(
444
+ ctxHolder.config,
445
+ store,
446
+ req
447
+ )
448
+ ),
282
449
  isDev
283
450
  );
284
451
  },
@@ -308,7 +475,12 @@ export async function startServer(
308
475
  "/api/docs": {
309
476
  GET: async (req: Request) => {
310
477
  const url = new URL(req.url);
311
- return withSecurityHeaders(await handleDocs(store, url), isDev);
478
+ return withSecurityHeaders(
479
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
480
+ (dependencies.handleDocs ?? handleDocs)(store, url)
481
+ ),
482
+ isDev
483
+ );
312
484
  },
313
485
  POST: async (req: Request) => {
314
486
  if (!isRequestAllowed(req, port)) {
@@ -324,7 +496,9 @@ export async function startServer(
324
496
  GET: async (req: Request) => {
325
497
  const url = new URL(req.url);
326
498
  return withSecurityHeaders(
327
- await handleDocsAutocomplete(store, url),
499
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
500
+ handleDocsAutocomplete(store, url)
501
+ ),
328
502
  isDev
329
503
  );
330
504
  },
@@ -345,8 +519,13 @@ export async function startServer(
345
519
  },
346
520
  },
347
521
  "/api/browse/tree": {
348
- GET: async () =>
349
- withSecurityHeaders(await handleBrowseTree(store), isDev),
522
+ GET: async (req: Request) =>
523
+ withSecurityHeaders(
524
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
525
+ handleBrowseTree(store)
526
+ ),
527
+ isDev
528
+ ),
350
529
  },
351
530
  "/api/docs/:id/deactivate": {
352
531
  POST: async (req: Request) => {
@@ -358,7 +537,7 @@ export async function startServer(
358
537
  const parts = url.pathname.split("/");
359
538
  const id = decodeURIComponent(parts[3] || "");
360
539
  return withSecurityHeaders(
361
- await handleDeactivateDoc(store, id, req),
540
+ await handleDeactivateDoc(ctxHolder, store, id, req),
362
541
  isDev
363
542
  );
364
543
  },
@@ -414,7 +593,14 @@ export async function startServer(
414
593
  const parts = url.pathname.split("/");
415
594
  const id = decodeURIComponent(parts[3] || "");
416
595
  return withSecurityHeaders(
417
- await handleRefactorPlan(ctxHolder, store, id, req),
596
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
597
+ (dependencies.handleRefactorPlan ?? handleRefactorPlan)(
598
+ ctxHolder,
599
+ store,
600
+ id,
601
+ req
602
+ )
603
+ ),
418
604
  isDev
419
605
  );
420
606
  },
@@ -479,7 +665,9 @@ export async function startServer(
479
665
  GET: async (req: Request) => {
480
666
  const url = new URL(req.url);
481
667
  return withSecurityHeaders(
482
- await handleDoc(store, ctxHolder.config, url),
668
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
669
+ handleDoc(store, ctxHolder.config, url, req)
670
+ ),
483
671
  isDev
484
672
  );
485
673
  },
@@ -488,7 +676,9 @@ export async function startServer(
488
676
  GET: async (req: Request) => {
489
677
  const url = new URL(req.url);
490
678
  return withSecurityHeaders(
491
- await handleDocAsset(store, ctxHolder.config, url),
679
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
680
+ handleDocAsset(store, ctxHolder.config, url)
681
+ ),
492
682
  isDev
493
683
  );
494
684
  },
@@ -504,7 +694,12 @@ export async function startServer(
504
694
  "/api/tags": {
505
695
  GET: async (req: Request) => {
506
696
  const url = new URL(req.url);
507
- return withSecurityHeaders(await handleTags(store, url), isDev);
697
+ return withSecurityHeaders(
698
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
699
+ handleTags(store, url)
700
+ ),
701
+ isDev
702
+ );
508
703
  },
509
704
  },
510
705
  "/api/search": {
@@ -512,7 +707,12 @@ export async function startServer(
512
707
  if (!isRequestAllowed(req, port)) {
513
708
  return withSecurityHeaders(forbiddenResponse(), isDev);
514
709
  }
515
- return withSecurityHeaders(await handleSearch(store, req), isDev);
710
+ return withSecurityHeaders(
711
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
712
+ handleSearch(ctxHolder.current, req)
713
+ ),
714
+ isDev
715
+ );
516
716
  },
517
717
  },
518
718
  "/api/query": {
@@ -521,7 +721,9 @@ export async function startServer(
521
721
  return withSecurityHeaders(forbiddenResponse(), isDev);
522
722
  }
523
723
  return withSecurityHeaders(
524
- await handleQuery(ctxHolder.current, req),
724
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
725
+ handleQuery(ctxHolder.current, req)
726
+ ),
525
727
  isDev
526
728
  );
527
729
  },
@@ -532,7 +734,9 @@ export async function startServer(
532
734
  return withSecurityHeaders(forbiddenResponse(), isDev);
533
735
  }
534
736
  return withSecurityHeaders(
535
- await handleContextBuild(ctxHolder.current, req),
737
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
738
+ handleContextBuild(ctxHolder.current, req)
739
+ ),
536
740
  isDev
537
741
  );
538
742
  },
@@ -543,7 +747,9 @@ export async function startServer(
543
747
  return withSecurityHeaders(forbiddenResponse(), isDev);
544
748
  }
545
749
  return withSecurityHeaders(
546
- await handleContextVerify(ctxHolder.current, req),
750
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
751
+ handleContextVerify(ctxHolder.current, req)
752
+ ),
547
753
  isDev
548
754
  );
549
755
  },
@@ -554,7 +760,9 @@ export async function startServer(
554
760
  return withSecurityHeaders(forbiddenResponse(), isDev);
555
761
  }
556
762
  return withSecurityHeaders(
557
- await handleQueryDiagnose(ctxHolder.current, req),
763
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
764
+ handleQueryDiagnose(ctxHolder.current, req)
765
+ ),
558
766
  isDev
559
767
  );
560
768
  },
@@ -565,7 +773,9 @@ export async function startServer(
565
773
  return withSecurityHeaders(forbiddenResponse(), isDev);
566
774
  }
567
775
  return withSecurityHeaders(
568
- await handleAsk(ctxHolder.current, req),
776
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
777
+ handleAsk(ctxHolder.current, req)
778
+ ),
569
779
  isDev
570
780
  );
571
781
  },
@@ -591,21 +801,30 @@ export async function startServer(
591
801
  GET: () => withSecurityHeaders(handleModelStatus(), isDev),
592
802
  },
593
803
  "/api/models/pull": {
594
- POST: (req: Request) => {
804
+ POST: async (req: Request) => {
595
805
  if (!isRequestAllowed(req, port)) {
596
806
  return withSecurityHeaders(forbiddenResponse(), isDev);
597
807
  }
598
- return withSecurityHeaders(handleModelPull(ctxHolder), isDev);
808
+ return withSecurityHeaders(
809
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
810
+ handleModelPull(ctxHolder)
811
+ ),
812
+ isDev
813
+ );
599
814
  },
600
815
  },
601
816
  "/api/jobs/active": {
602
- GET: () => withSecurityHeaders(handleActiveJob(), isDev),
817
+ GET: () =>
818
+ withSecurityHeaders(handleActiveJob(ctxHolder.jobManager), isDev),
603
819
  },
604
820
  "/api/jobs/:id": {
605
821
  GET: (req: Request) => {
606
822
  const url = new URL(req.url);
607
823
  const id = decodeURIComponent(url.pathname.split("/").pop() || "");
608
- return withSecurityHeaders(handleJob(id), isDev);
824
+ return withSecurityHeaders(
825
+ handleJob(id, ctxHolder.jobManager),
826
+ isDev
827
+ );
609
828
  },
610
829
  },
611
830
  "/api/embed": {
@@ -677,7 +896,9 @@ export async function startServer(
677
896
  const parts = url.pathname.split("/");
678
897
  const id = decodeURIComponent(parts[3] || "");
679
898
  return withSecurityHeaders(
680
- await handleDocLinks(store, id, url),
899
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
900
+ handleDocLinks(store, id, url)
901
+ ),
681
902
  isDev
682
903
  );
683
904
  },
@@ -688,7 +909,9 @@ export async function startServer(
688
909
  const parts = url.pathname.split("/");
689
910
  const id = decodeURIComponent(parts[3] || "");
690
911
  return withSecurityHeaders(
691
- await handleDocSections(store, id, req),
912
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
913
+ handleDocSections(store, id, req)
914
+ ),
692
915
  isDev
693
916
  );
694
917
  },
@@ -700,7 +923,9 @@ export async function startServer(
700
923
  const parts = url.pathname.split("/");
701
924
  const id = decodeURIComponent(parts[3] || "");
702
925
  return withSecurityHeaders(
703
- await handleDocBacklinks(store, id),
926
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
927
+ handleDocBacklinks(store, id)
928
+ ),
704
929
  isDev
705
930
  );
706
931
  },
@@ -712,7 +937,9 @@ export async function startServer(
712
937
  const parts = url.pathname.split("/");
713
938
  const id = decodeURIComponent(parts[3] || "");
714
939
  return withSecurityHeaders(
715
- await handleDocSimilar(ctxHolder.current, id, url),
940
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
941
+ handleDocSimilar(ctxHolder.current, id, url)
942
+ ),
716
943
  isDev
717
944
  );
718
945
  },
@@ -720,7 +947,12 @@ export async function startServer(
720
947
  "/api/graph": {
721
948
  GET: async (req: Request) => {
722
949
  const url = new URL(req.url);
723
- return withSecurityHeaders(await handleGraph(store, url), isDev);
950
+ return withSecurityHeaders(
951
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
952
+ handleGraph(store, url)
953
+ ),
954
+ isDev
955
+ );
724
956
  },
725
957
  },
726
958
  "/api/graph/query": {
@@ -729,7 +961,9 @@ export async function startServer(
729
961
  return withSecurityHeaders(forbiddenResponse(), isDev);
730
962
  }
731
963
  return withSecurityHeaders(
732
- await handleGraphQuery(store, ctxHolder.config, req),
964
+ await handleResidentRead(runtime as ResidentRuntime, req, () =>
965
+ handleGraphQuery(store, ctxHolder.config, req)
966
+ ),
733
967
  isDev
734
968
  );
735
969
  },
@@ -738,14 +972,18 @@ export async function startServer(
738
972
  });
739
973
  } catch (e) {
740
974
  removeShutdownHandlers();
741
- await runtime.dispose();
975
+ await Promise.allSettled([gateway.close()]);
976
+ await Promise.allSettled([runtime.dispose()]);
742
977
  return {
743
978
  success: false,
744
979
  error: e instanceof Error ? e.message : String(e),
745
980
  };
746
981
  }
982
+ (runtime as Partial<ResidentRuntime>).setListenerPort?.(server.port ?? port);
747
983
 
748
- console.log(`GNO server running at http://localhost:${server.port}`);
984
+ console.log(
985
+ `GNO server running at http://${gatewayConfig.host}:${server.port}`
986
+ );
749
987
  console.log("Press Ctrl+C to stop");
750
988
 
751
989
  // Block until shutdown signal
@@ -763,7 +1001,8 @@ export async function startServer(
763
1001
  try {
764
1002
  await server.stop(true);
765
1003
  } finally {
766
- await runtime.dispose();
1004
+ await Promise.allSettled([gateway.close()]);
1005
+ await Promise.allSettled([runtime.dispose()]);
767
1006
  }
768
1007
  return { success: true };
769
1008
  }
@@ -59,6 +59,56 @@ export interface HealthCenterState {
59
59
  checks: HealthCheck[];
60
60
  }
61
61
 
62
+ export type RuntimeMode = "serve" | "daemon" | "stdio" | "direct-cli";
63
+
64
+ export interface ResidentStatus {
65
+ schemaVersion: "1.0";
66
+ mode: RuntimeMode;
67
+ resident: boolean;
68
+ uptimeSeconds: number | null;
69
+ listenerPort: number | null;
70
+ admission: {
71
+ state: "accepting" | "draining" | "closed";
72
+ activeRequests: number;
73
+ };
74
+ shutdown: {
75
+ state: "none" | "graceful" | "deadline";
76
+ };
77
+ transport: {
78
+ activeRequests: number;
79
+ activeSessions: number;
80
+ queuedRequests: number;
81
+ maxConcurrentRequests: number;
82
+ maxQueuedRequests: number;
83
+ maxSessions: number;
84
+ };
85
+ readers: {
86
+ active: number;
87
+ queued: number;
88
+ limit: number;
89
+ maxQueued: number;
90
+ };
91
+ models: {
92
+ activeLeases: number;
93
+ leaseAcquisitions: number;
94
+ leaseReleases: number;
95
+ loadedModels: number;
96
+ loadAttempts: number;
97
+ loadSuccesses: number;
98
+ loadFailures: number;
99
+ inflightLoads: number;
100
+ };
101
+ jobs: {
102
+ active: number;
103
+ recent: number;
104
+ failed: number;
105
+ };
106
+ generations: {
107
+ content: number;
108
+ index: number;
109
+ };
110
+ }
111
+
62
112
  export interface BackgroundServiceState {
63
113
  watcher: {
64
114
  expectedCollections: string[];
@@ -125,6 +175,7 @@ export interface BootstrapState {
125
175
  }
126
176
 
127
177
  export interface AppStatusResponse {
178
+ resident: ResidentStatus;
128
179
  indexName: string;
129
180
  configPath: string;
130
181
  dbPath: string;
@@ -26,6 +26,7 @@ import {
26
26
  } from "./activation-health";
27
27
  import { getConnectorVerificationTargets } from "./connectors";
28
28
  import { downloadState, type ServerContext } from "./context";
29
+ import { createStandaloneResidentStatus } from "./resident-status";
29
30
 
30
31
  const GIGABYTE = 1024 * 1024 * 1024;
31
32
  const DISK_WARN_BYTES = 4 * GIGABYTE;
@@ -71,6 +72,7 @@ export interface StatusBuildDeps {
71
72
  listSuggestedCollections?: () => Promise<SuggestedCollection[]>;
72
73
  buildActivation?: typeof buildActivationStatus;
73
74
  listConnectorTargets?: typeof getConnectorVerificationTargets;
75
+ getResidentStatus?: () => AppStatusResponse["resident"];
74
76
  }
75
77
 
76
78
  function formatBytes(bytes: number): string {
@@ -714,6 +716,9 @@ export async function buildAppStatus(
714
716
  : "GNO works, but a few issues still need attention before it feels reliable.";
715
717
 
716
718
  return {
719
+ resident:
720
+ deps.getResidentStatus?.() ??
721
+ createStandaloneResidentStatus("direct-cli"),
717
722
  indexName: status.indexName,
718
723
  configPath: status.configPath,
719
724
  dbPath: status.dbPath,