@gmickel/gno 1.17.0 → 1.18.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.
- package/README.md +14 -2
- package/assets/skill/SKILL.md +17 -1
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +2 -2
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/detach.ts +37 -20
- package/src/cli/program.ts +74 -27
- package/src/config/index.ts +3 -0
- package/src/config/types.ts +37 -0
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/llm/cache.ts +13 -3
- package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
- package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
- package/src/mcp/context.ts +161 -0
- package/src/mcp/http-security.ts +477 -0
- package/src/mcp/http-session.ts +272 -0
- package/src/mcp/http-transport.ts +370 -0
- package/src/mcp/resources/index.ts +141 -134
- package/src/mcp/server.ts +19 -79
- package/src/mcp/tools/add-collection.ts +3 -1
- package/src/mcp/tools/capture.ts +3 -0
- package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
- package/src/mcp/tools/context.ts +9 -8
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +22 -2
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/serve/background-runtime.ts +12 -212
- package/src/serve/embed-scheduler.ts +74 -43
- package/src/serve/index.ts +9 -0
- package/src/serve/jobs.ts +78 -80
- package/src/serve/public/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Dashboard.tsx +1 -0
- package/src/serve/resident-admission.ts +159 -0
- package/src/serve/resident-background-work.ts +39 -0
- package/src/serve/resident-request.ts +55 -0
- package/src/serve/resident-runtime.ts +490 -0
- package/src/serve/resident-status.ts +96 -0
- package/src/serve/routes/api.ts +263 -167
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/server.ts +191 -37
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/sqlite/adapter.ts +26 -9
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Production route adapter for the secured resident MCP endpoint. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
HttpMcpPeerServer,
|
|
5
|
+
ResolvedHttpGatewayConfig,
|
|
6
|
+
} from "../../mcp/http-security";
|
|
7
|
+
import type { ResidentRuntime } from "../resident-runtime";
|
|
8
|
+
|
|
9
|
+
import { HttpMcpSecurity } from "../../mcp/http-security";
|
|
10
|
+
import { HttpMcpTransport } from "../../mcp/http-transport";
|
|
11
|
+
|
|
12
|
+
export type McpHttpRoute = (
|
|
13
|
+
request: Request,
|
|
14
|
+
server: HttpMcpPeerServer
|
|
15
|
+
) => Promise<Response>;
|
|
16
|
+
|
|
17
|
+
export interface McpHttpGateway {
|
|
18
|
+
readonly route: McpHttpRoute;
|
|
19
|
+
readonly security: HttpMcpSecurity;
|
|
20
|
+
readonly transport: HttpMcpTransport;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the route only after startup policy and token-file checks succeed.
|
|
26
|
+
* Every method passes through the external boundary before transport dispatch.
|
|
27
|
+
*/
|
|
28
|
+
export async function createMcpHttpGateway(
|
|
29
|
+
runtime: ResidentRuntime,
|
|
30
|
+
config: ResolvedHttpGatewayConfig
|
|
31
|
+
): Promise<McpHttpGateway> {
|
|
32
|
+
runtime.mcpContext.enableWrite = config.enableWrite;
|
|
33
|
+
const transport = new HttpMcpTransport(runtime, {
|
|
34
|
+
enableWrite: config.enableWrite,
|
|
35
|
+
idleTimeoutMs: config.limits.sessionIdleTimeoutMs,
|
|
36
|
+
maxConcurrentRequests: config.limits.maxConcurrentRequests,
|
|
37
|
+
maxQueuedRequests: config.limits.maxQueuedRequests,
|
|
38
|
+
maxSessions: config.limits.maxSessions,
|
|
39
|
+
});
|
|
40
|
+
const security = new HttpMcpSecurity(config, {
|
|
41
|
+
onCredentialsChanged: () => transport.invalidateAuthenticatedSessions(),
|
|
42
|
+
});
|
|
43
|
+
await security.initialize();
|
|
44
|
+
(runtime as Partial<ResidentRuntime>).setTransportStatusProvider?.(() =>
|
|
45
|
+
transport.getStatus()
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const route: McpHttpRoute = async (request, server) => {
|
|
49
|
+
const authorization = await security.authorize(request, server);
|
|
50
|
+
if (!authorization.ok) return authorization.response;
|
|
51
|
+
|
|
52
|
+
// POST responses and GET streams may both be long-lived SSE responses.
|
|
53
|
+
server.timeout(request, 0);
|
|
54
|
+
return transport.handleRequest(authorization.value.request, {
|
|
55
|
+
identity: authorization.value.identity,
|
|
56
|
+
parsedBody: authorization.value.parsedBody,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
route,
|
|
62
|
+
security,
|
|
63
|
+
transport,
|
|
64
|
+
close: async () => {
|
|
65
|
+
await transport.close();
|
|
66
|
+
(runtime as Partial<ResidentRuntime>).setTransportStatusProvider?.(null);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
package/src/serve/server.ts
CHANGED
|
@@ -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,10 @@ import {
|
|
|
67
75
|
handleDocLinks,
|
|
68
76
|
handleDocSimilar,
|
|
69
77
|
} from "./routes/links";
|
|
78
|
+
import { createMcpHttpGateway } from "./routes/mcp";
|
|
70
79
|
import { forbiddenResponse, isRequestAllowed } from "./security";
|
|
71
80
|
|
|
72
|
-
export interface ServeOptions {
|
|
81
|
+
export interface ServeOptions extends HttpGatewayOverrides {
|
|
73
82
|
/** Port to listen on (default: 3000) */
|
|
74
83
|
port?: number;
|
|
75
84
|
/** Config path override */
|
|
@@ -85,8 +94,14 @@ export interface ServeResult {
|
|
|
85
94
|
|
|
86
95
|
interface StartServerDependencies {
|
|
87
96
|
startBackgroundRuntime?: typeof startBackgroundRuntime;
|
|
97
|
+
createMcpHttpGateway?: typeof createMcpHttpGateway;
|
|
88
98
|
serve?: typeof Bun.serve;
|
|
89
99
|
handleInstallConnector?: typeof handleInstallConnector;
|
|
100
|
+
handleDocs?: typeof handleDocs;
|
|
101
|
+
handleVerifyConnector?: typeof handleVerifyConnector;
|
|
102
|
+
handleImportPreview?: typeof handleImportPreview;
|
|
103
|
+
handlePublishExport?: typeof handlePublishExport;
|
|
104
|
+
handleRefactorPlan?: typeof handleRefactorPlan;
|
|
90
105
|
waitForShutdown?: (signal: AbortSignal) => Promise<void>;
|
|
91
106
|
}
|
|
92
107
|
|
|
@@ -152,6 +167,7 @@ export async function startServer(
|
|
|
152
167
|
const runtimeResult = await (
|
|
153
168
|
dependencies.startBackgroundRuntime ?? startBackgroundRuntime
|
|
154
169
|
)({
|
|
170
|
+
mode: "serve",
|
|
155
171
|
configPath: options.configPath,
|
|
156
172
|
index: options.index,
|
|
157
173
|
requireCollections: false,
|
|
@@ -163,6 +179,35 @@ export async function startServer(
|
|
|
163
179
|
const runtime = runtimeResult.runtime;
|
|
164
180
|
const store = runtime.store;
|
|
165
181
|
const ctxHolder: ContextHolder = runtime.ctxHolder;
|
|
182
|
+
const gatewayConfig = resolveHttpGatewayConfig(runtime.config.gateway, {
|
|
183
|
+
host: options.host,
|
|
184
|
+
port,
|
|
185
|
+
tokenFile: options.tokenFile,
|
|
186
|
+
allowedHosts: options.allowedHosts,
|
|
187
|
+
allowedOrigins: options.allowedOrigins,
|
|
188
|
+
enableWrite: options.enableWrite,
|
|
189
|
+
});
|
|
190
|
+
if (!isHttpGatewayLoopbackBind(gatewayConfig.host)) {
|
|
191
|
+
await runtime.dispose();
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error:
|
|
195
|
+
"gno serve remains loopback-only because Web and REST share its listener; use gno daemon for authenticated non-loopback MCP",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
let gateway: Awaited<ReturnType<typeof createMcpHttpGateway>>;
|
|
199
|
+
try {
|
|
200
|
+
gateway = await (dependencies.createMcpHttpGateway ?? createMcpHttpGateway)(
|
|
201
|
+
runtime as ResidentRuntime,
|
|
202
|
+
gatewayConfig
|
|
203
|
+
);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
await runtime.dispose();
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
error: error instanceof Error ? error.message : String(error),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
166
211
|
|
|
167
212
|
// Shutdown controller for clean lifecycle
|
|
168
213
|
const shutdownController = new AbortController();
|
|
@@ -185,13 +230,14 @@ export async function startServer(
|
|
|
185
230
|
try {
|
|
186
231
|
server = (dependencies.serve ?? Bun.serve)({
|
|
187
232
|
port,
|
|
188
|
-
hostname:
|
|
233
|
+
hostname: gatewayConfig.host,
|
|
189
234
|
|
|
190
235
|
// Enable development mode for HMR and console logging
|
|
191
236
|
development: isDev,
|
|
192
237
|
|
|
193
238
|
// Static routes - Bun handles HTML bundling and /_bun/* assets automatically
|
|
194
239
|
routes: {
|
|
240
|
+
"/mcp": gateway.route,
|
|
195
241
|
// SPA routes - all serve the same React app
|
|
196
242
|
"/": homepage,
|
|
197
243
|
"/search": homepage,
|
|
@@ -208,8 +254,25 @@ export async function startServer(
|
|
|
208
254
|
GET: () => withSecurityHeaders(handleHealth(), isDev),
|
|
209
255
|
},
|
|
210
256
|
"/api/status": {
|
|
211
|
-
GET: async () =>
|
|
212
|
-
withSecurityHeaders(
|
|
257
|
+
GET: async (req: Request) =>
|
|
258
|
+
withSecurityHeaders(
|
|
259
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
260
|
+
handleStatus(ctxHolder.current, {
|
|
261
|
+
getResidentStatus: () =>
|
|
262
|
+
(runtime as ResidentRuntime).getStatus(),
|
|
263
|
+
})
|
|
264
|
+
),
|
|
265
|
+
isDev
|
|
266
|
+
),
|
|
267
|
+
},
|
|
268
|
+
"/api/resident/status": {
|
|
269
|
+
GET: () =>
|
|
270
|
+
withSecurityHeaders(
|
|
271
|
+
handleResidentStatus(() =>
|
|
272
|
+
(runtime as ResidentRuntime).getStatus()
|
|
273
|
+
),
|
|
274
|
+
isDev
|
|
275
|
+
),
|
|
213
276
|
},
|
|
214
277
|
"/api/collections": {
|
|
215
278
|
GET: async () =>
|
|
@@ -228,9 +291,11 @@ export async function startServer(
|
|
|
228
291
|
},
|
|
229
292
|
},
|
|
230
293
|
"/api/connectors": {
|
|
231
|
-
GET: async () =>
|
|
294
|
+
GET: async (req: Request) =>
|
|
232
295
|
withSecurityHeaders(
|
|
233
|
-
await
|
|
296
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
297
|
+
handleConnectors(ctxHolder.config)
|
|
298
|
+
),
|
|
234
299
|
isDev
|
|
235
300
|
),
|
|
236
301
|
},
|
|
@@ -256,7 +321,13 @@ export async function startServer(
|
|
|
256
321
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
257
322
|
}
|
|
258
323
|
return withSecurityHeaders(
|
|
259
|
-
await
|
|
324
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
325
|
+
(dependencies.handleVerifyConnector ?? handleVerifyConnector)(
|
|
326
|
+
ctxHolder.config,
|
|
327
|
+
store,
|
|
328
|
+
req
|
|
329
|
+
)
|
|
330
|
+
),
|
|
260
331
|
isDev
|
|
261
332
|
);
|
|
262
333
|
},
|
|
@@ -267,7 +338,12 @@ export async function startServer(
|
|
|
267
338
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
268
339
|
}
|
|
269
340
|
return withSecurityHeaders(
|
|
270
|
-
await
|
|
341
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
342
|
+
(dependencies.handleImportPreview ?? handleImportPreview)(
|
|
343
|
+
ctxHolder,
|
|
344
|
+
req
|
|
345
|
+
)
|
|
346
|
+
),
|
|
271
347
|
isDev
|
|
272
348
|
);
|
|
273
349
|
},
|
|
@@ -278,7 +354,13 @@ export async function startServer(
|
|
|
278
354
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
279
355
|
}
|
|
280
356
|
return withSecurityHeaders(
|
|
281
|
-
await
|
|
357
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
358
|
+
(dependencies.handlePublishExport ?? handlePublishExport)(
|
|
359
|
+
ctxHolder.config,
|
|
360
|
+
store,
|
|
361
|
+
req
|
|
362
|
+
)
|
|
363
|
+
),
|
|
282
364
|
isDev
|
|
283
365
|
);
|
|
284
366
|
},
|
|
@@ -308,7 +390,12 @@ export async function startServer(
|
|
|
308
390
|
"/api/docs": {
|
|
309
391
|
GET: async (req: Request) => {
|
|
310
392
|
const url = new URL(req.url);
|
|
311
|
-
return withSecurityHeaders(
|
|
393
|
+
return withSecurityHeaders(
|
|
394
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
395
|
+
(dependencies.handleDocs ?? handleDocs)(store, url)
|
|
396
|
+
),
|
|
397
|
+
isDev
|
|
398
|
+
);
|
|
312
399
|
},
|
|
313
400
|
POST: async (req: Request) => {
|
|
314
401
|
if (!isRequestAllowed(req, port)) {
|
|
@@ -324,7 +411,9 @@ export async function startServer(
|
|
|
324
411
|
GET: async (req: Request) => {
|
|
325
412
|
const url = new URL(req.url);
|
|
326
413
|
return withSecurityHeaders(
|
|
327
|
-
await
|
|
414
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
415
|
+
handleDocsAutocomplete(store, url)
|
|
416
|
+
),
|
|
328
417
|
isDev
|
|
329
418
|
);
|
|
330
419
|
},
|
|
@@ -345,8 +434,13 @@ export async function startServer(
|
|
|
345
434
|
},
|
|
346
435
|
},
|
|
347
436
|
"/api/browse/tree": {
|
|
348
|
-
GET: async () =>
|
|
349
|
-
withSecurityHeaders(
|
|
437
|
+
GET: async (req: Request) =>
|
|
438
|
+
withSecurityHeaders(
|
|
439
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
440
|
+
handleBrowseTree(store)
|
|
441
|
+
),
|
|
442
|
+
isDev
|
|
443
|
+
),
|
|
350
444
|
},
|
|
351
445
|
"/api/docs/:id/deactivate": {
|
|
352
446
|
POST: async (req: Request) => {
|
|
@@ -358,7 +452,7 @@ export async function startServer(
|
|
|
358
452
|
const parts = url.pathname.split("/");
|
|
359
453
|
const id = decodeURIComponent(parts[3] || "");
|
|
360
454
|
return withSecurityHeaders(
|
|
361
|
-
await handleDeactivateDoc(store, id, req),
|
|
455
|
+
await handleDeactivateDoc(ctxHolder, store, id, req),
|
|
362
456
|
isDev
|
|
363
457
|
);
|
|
364
458
|
},
|
|
@@ -414,7 +508,14 @@ export async function startServer(
|
|
|
414
508
|
const parts = url.pathname.split("/");
|
|
415
509
|
const id = decodeURIComponent(parts[3] || "");
|
|
416
510
|
return withSecurityHeaders(
|
|
417
|
-
await
|
|
511
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
512
|
+
(dependencies.handleRefactorPlan ?? handleRefactorPlan)(
|
|
513
|
+
ctxHolder,
|
|
514
|
+
store,
|
|
515
|
+
id,
|
|
516
|
+
req
|
|
517
|
+
)
|
|
518
|
+
),
|
|
418
519
|
isDev
|
|
419
520
|
);
|
|
420
521
|
},
|
|
@@ -479,7 +580,9 @@ export async function startServer(
|
|
|
479
580
|
GET: async (req: Request) => {
|
|
480
581
|
const url = new URL(req.url);
|
|
481
582
|
return withSecurityHeaders(
|
|
482
|
-
await
|
|
583
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
584
|
+
handleDoc(store, ctxHolder.config, url)
|
|
585
|
+
),
|
|
483
586
|
isDev
|
|
484
587
|
);
|
|
485
588
|
},
|
|
@@ -488,7 +591,9 @@ export async function startServer(
|
|
|
488
591
|
GET: async (req: Request) => {
|
|
489
592
|
const url = new URL(req.url);
|
|
490
593
|
return withSecurityHeaders(
|
|
491
|
-
await
|
|
594
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
595
|
+
handleDocAsset(store, ctxHolder.config, url)
|
|
596
|
+
),
|
|
492
597
|
isDev
|
|
493
598
|
);
|
|
494
599
|
},
|
|
@@ -504,7 +609,12 @@ export async function startServer(
|
|
|
504
609
|
"/api/tags": {
|
|
505
610
|
GET: async (req: Request) => {
|
|
506
611
|
const url = new URL(req.url);
|
|
507
|
-
return withSecurityHeaders(
|
|
612
|
+
return withSecurityHeaders(
|
|
613
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
614
|
+
handleTags(store, url)
|
|
615
|
+
),
|
|
616
|
+
isDev
|
|
617
|
+
);
|
|
508
618
|
},
|
|
509
619
|
},
|
|
510
620
|
"/api/search": {
|
|
@@ -512,7 +622,12 @@ export async function startServer(
|
|
|
512
622
|
if (!isRequestAllowed(req, port)) {
|
|
513
623
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
514
624
|
}
|
|
515
|
-
return withSecurityHeaders(
|
|
625
|
+
return withSecurityHeaders(
|
|
626
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
627
|
+
handleSearch(store, req)
|
|
628
|
+
),
|
|
629
|
+
isDev
|
|
630
|
+
);
|
|
516
631
|
},
|
|
517
632
|
},
|
|
518
633
|
"/api/query": {
|
|
@@ -521,7 +636,9 @@ export async function startServer(
|
|
|
521
636
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
522
637
|
}
|
|
523
638
|
return withSecurityHeaders(
|
|
524
|
-
await
|
|
639
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
640
|
+
handleQuery(ctxHolder.current, req)
|
|
641
|
+
),
|
|
525
642
|
isDev
|
|
526
643
|
);
|
|
527
644
|
},
|
|
@@ -532,7 +649,9 @@ export async function startServer(
|
|
|
532
649
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
533
650
|
}
|
|
534
651
|
return withSecurityHeaders(
|
|
535
|
-
await
|
|
652
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
653
|
+
handleContextBuild(ctxHolder.current, req)
|
|
654
|
+
),
|
|
536
655
|
isDev
|
|
537
656
|
);
|
|
538
657
|
},
|
|
@@ -543,7 +662,9 @@ export async function startServer(
|
|
|
543
662
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
544
663
|
}
|
|
545
664
|
return withSecurityHeaders(
|
|
546
|
-
await
|
|
665
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
666
|
+
handleContextVerify(ctxHolder.current, req)
|
|
667
|
+
),
|
|
547
668
|
isDev
|
|
548
669
|
);
|
|
549
670
|
},
|
|
@@ -554,7 +675,9 @@ export async function startServer(
|
|
|
554
675
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
555
676
|
}
|
|
556
677
|
return withSecurityHeaders(
|
|
557
|
-
await
|
|
678
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
679
|
+
handleQueryDiagnose(ctxHolder.current, req)
|
|
680
|
+
),
|
|
558
681
|
isDev
|
|
559
682
|
);
|
|
560
683
|
},
|
|
@@ -565,7 +688,9 @@ export async function startServer(
|
|
|
565
688
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
566
689
|
}
|
|
567
690
|
return withSecurityHeaders(
|
|
568
|
-
await
|
|
691
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
692
|
+
handleAsk(ctxHolder.current, req)
|
|
693
|
+
),
|
|
569
694
|
isDev
|
|
570
695
|
);
|
|
571
696
|
},
|
|
@@ -591,21 +716,30 @@ export async function startServer(
|
|
|
591
716
|
GET: () => withSecurityHeaders(handleModelStatus(), isDev),
|
|
592
717
|
},
|
|
593
718
|
"/api/models/pull": {
|
|
594
|
-
POST: (req: Request) => {
|
|
719
|
+
POST: async (req: Request) => {
|
|
595
720
|
if (!isRequestAllowed(req, port)) {
|
|
596
721
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
597
722
|
}
|
|
598
|
-
return withSecurityHeaders(
|
|
723
|
+
return withSecurityHeaders(
|
|
724
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
725
|
+
handleModelPull(ctxHolder)
|
|
726
|
+
),
|
|
727
|
+
isDev
|
|
728
|
+
);
|
|
599
729
|
},
|
|
600
730
|
},
|
|
601
731
|
"/api/jobs/active": {
|
|
602
|
-
GET: () =>
|
|
732
|
+
GET: () =>
|
|
733
|
+
withSecurityHeaders(handleActiveJob(ctxHolder.jobManager), isDev),
|
|
603
734
|
},
|
|
604
735
|
"/api/jobs/:id": {
|
|
605
736
|
GET: (req: Request) => {
|
|
606
737
|
const url = new URL(req.url);
|
|
607
738
|
const id = decodeURIComponent(url.pathname.split("/").pop() || "");
|
|
608
|
-
return withSecurityHeaders(
|
|
739
|
+
return withSecurityHeaders(
|
|
740
|
+
handleJob(id, ctxHolder.jobManager),
|
|
741
|
+
isDev
|
|
742
|
+
);
|
|
609
743
|
},
|
|
610
744
|
},
|
|
611
745
|
"/api/embed": {
|
|
@@ -677,7 +811,9 @@ export async function startServer(
|
|
|
677
811
|
const parts = url.pathname.split("/");
|
|
678
812
|
const id = decodeURIComponent(parts[3] || "");
|
|
679
813
|
return withSecurityHeaders(
|
|
680
|
-
await
|
|
814
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
815
|
+
handleDocLinks(store, id, url)
|
|
816
|
+
),
|
|
681
817
|
isDev
|
|
682
818
|
);
|
|
683
819
|
},
|
|
@@ -688,7 +824,9 @@ export async function startServer(
|
|
|
688
824
|
const parts = url.pathname.split("/");
|
|
689
825
|
const id = decodeURIComponent(parts[3] || "");
|
|
690
826
|
return withSecurityHeaders(
|
|
691
|
-
await
|
|
827
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
828
|
+
handleDocSections(store, id, req)
|
|
829
|
+
),
|
|
692
830
|
isDev
|
|
693
831
|
);
|
|
694
832
|
},
|
|
@@ -700,7 +838,9 @@ export async function startServer(
|
|
|
700
838
|
const parts = url.pathname.split("/");
|
|
701
839
|
const id = decodeURIComponent(parts[3] || "");
|
|
702
840
|
return withSecurityHeaders(
|
|
703
|
-
await
|
|
841
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
842
|
+
handleDocBacklinks(store, id)
|
|
843
|
+
),
|
|
704
844
|
isDev
|
|
705
845
|
);
|
|
706
846
|
},
|
|
@@ -712,7 +852,9 @@ export async function startServer(
|
|
|
712
852
|
const parts = url.pathname.split("/");
|
|
713
853
|
const id = decodeURIComponent(parts[3] || "");
|
|
714
854
|
return withSecurityHeaders(
|
|
715
|
-
await
|
|
855
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
856
|
+
handleDocSimilar(ctxHolder.current, id, url)
|
|
857
|
+
),
|
|
716
858
|
isDev
|
|
717
859
|
);
|
|
718
860
|
},
|
|
@@ -720,7 +862,12 @@ export async function startServer(
|
|
|
720
862
|
"/api/graph": {
|
|
721
863
|
GET: async (req: Request) => {
|
|
722
864
|
const url = new URL(req.url);
|
|
723
|
-
return withSecurityHeaders(
|
|
865
|
+
return withSecurityHeaders(
|
|
866
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
867
|
+
handleGraph(store, url)
|
|
868
|
+
),
|
|
869
|
+
isDev
|
|
870
|
+
);
|
|
724
871
|
},
|
|
725
872
|
},
|
|
726
873
|
"/api/graph/query": {
|
|
@@ -729,7 +876,9 @@ export async function startServer(
|
|
|
729
876
|
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
730
877
|
}
|
|
731
878
|
return withSecurityHeaders(
|
|
732
|
-
await
|
|
879
|
+
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
880
|
+
handleGraphQuery(store, ctxHolder.config, req)
|
|
881
|
+
),
|
|
733
882
|
isDev
|
|
734
883
|
);
|
|
735
884
|
},
|
|
@@ -738,14 +887,18 @@ export async function startServer(
|
|
|
738
887
|
});
|
|
739
888
|
} catch (e) {
|
|
740
889
|
removeShutdownHandlers();
|
|
741
|
-
await
|
|
890
|
+
await Promise.allSettled([gateway.close()]);
|
|
891
|
+
await Promise.allSettled([runtime.dispose()]);
|
|
742
892
|
return {
|
|
743
893
|
success: false,
|
|
744
894
|
error: e instanceof Error ? e.message : String(e),
|
|
745
895
|
};
|
|
746
896
|
}
|
|
897
|
+
(runtime as Partial<ResidentRuntime>).setListenerPort?.(server.port ?? port);
|
|
747
898
|
|
|
748
|
-
console.log(
|
|
899
|
+
console.log(
|
|
900
|
+
`GNO server running at http://${gatewayConfig.host}:${server.port}`
|
|
901
|
+
);
|
|
749
902
|
console.log("Press Ctrl+C to stop");
|
|
750
903
|
|
|
751
904
|
// Block until shutdown signal
|
|
@@ -763,7 +916,8 @@ export async function startServer(
|
|
|
763
916
|
try {
|
|
764
917
|
await server.stop(true);
|
|
765
918
|
} finally {
|
|
766
|
-
await
|
|
919
|
+
await Promise.allSettled([gateway.close()]);
|
|
920
|
+
await Promise.allSettled([runtime.dispose()]);
|
|
767
921
|
}
|
|
768
922
|
return { success: true };
|
|
769
923
|
}
|
|
@@ -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;
|
package/src/serve/status.ts
CHANGED
|
@@ -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,
|