@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
package/src/serve/routes/api.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
Config,
|
|
17
17
|
ModelPreset,
|
|
18
18
|
} from "../../config/types";
|
|
19
|
+
import type { JobManager } from "../../core/job-manager";
|
|
19
20
|
import type {
|
|
20
21
|
AskResult,
|
|
21
22
|
Citation,
|
|
@@ -32,6 +33,7 @@ import type {
|
|
|
32
33
|
import type { DocumentEventBus } from "../doc-events";
|
|
33
34
|
import type { EmbedScheduler } from "../embed-scheduler";
|
|
34
35
|
import type { StartJobError } from "../jobs";
|
|
36
|
+
import type { ResidentStatus } from "../status-model";
|
|
35
37
|
import type { CollectionWatchService } from "../watch-service";
|
|
36
38
|
|
|
37
39
|
import { modelsPull } from "../../cli/commands/models/pull";
|
|
@@ -76,6 +78,11 @@ import {
|
|
|
76
78
|
planMoveRefactor,
|
|
77
79
|
planRenameRefactor,
|
|
78
80
|
} from "../../core/file-refactors";
|
|
81
|
+
import {
|
|
82
|
+
hasContentMutation,
|
|
83
|
+
recordContentMutation,
|
|
84
|
+
recordIndexMutation,
|
|
85
|
+
} from "../../core/mutation-generations";
|
|
79
86
|
import {
|
|
80
87
|
resolveNoteCreatePlan,
|
|
81
88
|
sanitizeNoteFilename,
|
|
@@ -147,6 +154,26 @@ export interface ContextHolder {
|
|
|
147
154
|
scheduler: EmbedScheduler | null;
|
|
148
155
|
eventBus: DocumentEventBus | null;
|
|
149
156
|
watchService: CollectionWatchService | null;
|
|
157
|
+
jobManager?: JobManager;
|
|
158
|
+
markContentMutation?: () => void;
|
|
159
|
+
markIndexMutation?: () => void;
|
|
160
|
+
startBackgroundWork?: (
|
|
161
|
+
operation: (signal: AbortSignal) => Promise<void>
|
|
162
|
+
) => boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function syncResidentCollection(
|
|
166
|
+
ctxHolder: ContextHolder,
|
|
167
|
+
collection: Collection,
|
|
168
|
+
store: SqliteAdapter,
|
|
169
|
+
options: Parameters<typeof defaultSyncService.syncCollection>[2],
|
|
170
|
+
syncCollection: typeof defaultSyncService.syncCollection = defaultSyncService.syncCollection.bind(
|
|
171
|
+
defaultSyncService
|
|
172
|
+
)
|
|
173
|
+
): ReturnType<typeof defaultSyncService.syncCollection> {
|
|
174
|
+
const result = await syncCollection(collection, store, options);
|
|
175
|
+
recordContentMutation(result, ctxHolder.markContentMutation);
|
|
176
|
+
return result;
|
|
150
177
|
}
|
|
151
178
|
|
|
152
179
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -815,6 +842,12 @@ export async function handleStatus(
|
|
|
815
842
|
}
|
|
816
843
|
}
|
|
817
844
|
|
|
845
|
+
export function handleResidentStatus(
|
|
846
|
+
getStatus: () => ResidentStatus
|
|
847
|
+
): Response {
|
|
848
|
+
return jsonResponse(getStatus());
|
|
849
|
+
}
|
|
850
|
+
|
|
818
851
|
/**
|
|
819
852
|
* GET /api/collections
|
|
820
853
|
* Returns list of collections.
|
|
@@ -990,33 +1023,38 @@ export async function handleCreateCollection(
|
|
|
990
1023
|
if (!collection) {
|
|
991
1024
|
return errorResponse("RUNTIME", "Collection not found after add", 500);
|
|
992
1025
|
}
|
|
993
|
-
const jobResult = startJob(
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1026
|
+
const jobResult = await startJob(
|
|
1027
|
+
"add",
|
|
1028
|
+
async (): Promise<SyncResult> => {
|
|
1029
|
+
const result = await syncResidentCollection(
|
|
1030
|
+
ctxHolder,
|
|
1031
|
+
collection,
|
|
1032
|
+
store,
|
|
1033
|
+
withContentTypeRules(
|
|
1034
|
+
{
|
|
1035
|
+
gitPull: body.gitPull,
|
|
1036
|
+
runUpdateCmd: true,
|
|
1037
|
+
},
|
|
1038
|
+
syncResult.config
|
|
1039
|
+
)
|
|
1040
|
+
);
|
|
1041
|
+
if (result.filesAdded > 0 || result.filesUpdated > 0) {
|
|
1042
|
+
if (ctxHolder.scheduler) {
|
|
1043
|
+
await ctxHolder.scheduler.triggerNow();
|
|
1044
|
+
}
|
|
1008
1045
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1046
|
+
return {
|
|
1047
|
+
collections: [result],
|
|
1048
|
+
totalDurationMs: result.durationMs,
|
|
1049
|
+
totalFilesProcessed: result.filesProcessed,
|
|
1050
|
+
totalFilesAdded: result.filesAdded,
|
|
1051
|
+
totalFilesUpdated: result.filesUpdated,
|
|
1052
|
+
totalFilesErrored: result.filesErrored,
|
|
1053
|
+
totalFilesSkipped: result.filesSkipped,
|
|
1054
|
+
};
|
|
1055
|
+
},
|
|
1056
|
+
ctxHolder.jobManager
|
|
1057
|
+
);
|
|
1020
1058
|
|
|
1021
1059
|
if (!jobResult.ok) {
|
|
1022
1060
|
return jobConflictResponse(jobResult);
|
|
@@ -1100,7 +1138,8 @@ export async function handleDeleteCollection(
|
|
|
1100
1138
|
const status = statusMap[syncResult.code] ?? 500;
|
|
1101
1139
|
return errorResponse(syncResult.code, syncResult.error, status);
|
|
1102
1140
|
}
|
|
1103
|
-
|
|
1141
|
+
ctxHolder.markContentMutation?.();
|
|
1142
|
+
ctxHolder.markIndexMutation?.();
|
|
1104
1143
|
return jsonResponse({
|
|
1105
1144
|
success: true,
|
|
1106
1145
|
collection: name,
|
|
@@ -1231,6 +1270,7 @@ export async function handleClearCollectionEmbeddings(
|
|
|
1231
1270
|
const status = result.error.code === "INVALID_INPUT" ? 400 : 500;
|
|
1232
1271
|
return errorResponse(result.error.code, result.error.message, status);
|
|
1233
1272
|
}
|
|
1273
|
+
recordIndexMutation(result.value.deletedVectors, ctxHolder.markIndexMutation);
|
|
1234
1274
|
|
|
1235
1275
|
return jsonResponse({
|
|
1236
1276
|
success: true,
|
|
@@ -1292,25 +1332,30 @@ export async function handleSync(
|
|
|
1292
1332
|
}
|
|
1293
1333
|
|
|
1294
1334
|
// Start background sync job
|
|
1295
|
-
const jobResult = startJob(
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1335
|
+
const jobResult = await startJob(
|
|
1336
|
+
"sync",
|
|
1337
|
+
async (): Promise<SyncResult> => {
|
|
1338
|
+
const result = await defaultSyncService.syncAll(
|
|
1339
|
+
collections,
|
|
1340
|
+
store,
|
|
1341
|
+
withContentTypeRules(
|
|
1342
|
+
{
|
|
1343
|
+
gitPull: body.gitPull,
|
|
1344
|
+
runUpdateCmd: true,
|
|
1345
|
+
},
|
|
1346
|
+
ctxHolder.config
|
|
1347
|
+
)
|
|
1348
|
+
);
|
|
1349
|
+
recordContentMutation(result, ctxHolder.markContentMutation);
|
|
1350
|
+
if (hasContentMutation(result)) {
|
|
1351
|
+
if (ctxHolder.scheduler) {
|
|
1352
|
+
await ctxHolder.scheduler.triggerNow();
|
|
1353
|
+
}
|
|
1310
1354
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1355
|
+
return result;
|
|
1356
|
+
},
|
|
1357
|
+
ctxHolder.jobManager
|
|
1358
|
+
);
|
|
1314
1359
|
|
|
1315
1360
|
if (!jobResult.ok) {
|
|
1316
1361
|
return jobConflictResponse(jobResult);
|
|
@@ -1797,6 +1842,7 @@ export async function handleTags(
|
|
|
1797
1842
|
* Deactivate a document (soft delete - does not remove file from disk).
|
|
1798
1843
|
*/
|
|
1799
1844
|
export async function handleDeactivateDoc(
|
|
1845
|
+
ctxHolder: ContextHolder,
|
|
1800
1846
|
store: SqliteAdapter,
|
|
1801
1847
|
docId: string,
|
|
1802
1848
|
req?: Request
|
|
@@ -1821,6 +1867,10 @@ export async function handleDeactivateDoc(
|
|
|
1821
1867
|
if (!result.ok) {
|
|
1822
1868
|
return errorResponse("RUNTIME", result.error.message, 500);
|
|
1823
1869
|
}
|
|
1870
|
+
if (result.value > 0) {
|
|
1871
|
+
ctxHolder.markContentMutation?.();
|
|
1872
|
+
ctxHolder.markIndexMutation?.();
|
|
1873
|
+
}
|
|
1824
1874
|
|
|
1825
1875
|
return jsonResponse({
|
|
1826
1876
|
success: true,
|
|
@@ -1929,10 +1979,12 @@ export async function handleRenameDoc(
|
|
|
1929
1979
|
const nextUri = `gno://${collection.name}/${nextRelPath}`;
|
|
1930
1980
|
let warning: string | undefined;
|
|
1931
1981
|
try {
|
|
1932
|
-
await
|
|
1982
|
+
await syncResidentCollection(
|
|
1983
|
+
ctxHolder,
|
|
1933
1984
|
collection,
|
|
1934
1985
|
store,
|
|
1935
|
-
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
1986
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
1987
|
+
syncCollection
|
|
1936
1988
|
);
|
|
1937
1989
|
} catch {
|
|
1938
1990
|
warning =
|
|
@@ -2151,10 +2203,12 @@ export async function handleTrashDoc(
|
|
|
2151
2203
|
}
|
|
2152
2204
|
let warning: string | undefined;
|
|
2153
2205
|
try {
|
|
2154
|
-
await
|
|
2206
|
+
await syncResidentCollection(
|
|
2207
|
+
ctxHolder,
|
|
2155
2208
|
collection,
|
|
2156
2209
|
store,
|
|
2157
|
-
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
2210
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2211
|
+
syncCollection
|
|
2158
2212
|
);
|
|
2159
2213
|
} catch {
|
|
2160
2214
|
warning =
|
|
@@ -2274,7 +2328,8 @@ export async function handleMoveDoc(
|
|
|
2274
2328
|
await renameFilePath(fullPath, nextFullPath);
|
|
2275
2329
|
let warning: string | undefined;
|
|
2276
2330
|
try {
|
|
2277
|
-
await
|
|
2331
|
+
await syncResidentCollection(
|
|
2332
|
+
ctxHolder,
|
|
2278
2333
|
collection,
|
|
2279
2334
|
store,
|
|
2280
2335
|
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
@@ -2397,7 +2452,8 @@ export async function handleDuplicateDoc(
|
|
|
2397
2452
|
await copyFilePath(fullPath, nextFullPath);
|
|
2398
2453
|
let warning: string | undefined;
|
|
2399
2454
|
try {
|
|
2400
|
-
await
|
|
2455
|
+
await syncResidentCollection(
|
|
2456
|
+
ctxHolder,
|
|
2401
2457
|
collection,
|
|
2402
2458
|
store,
|
|
2403
2459
|
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
@@ -2546,7 +2602,10 @@ export async function handleUpdateDoc(
|
|
|
2546
2602
|
ctxHolder: ContextHolder,
|
|
2547
2603
|
store: SqliteAdapter,
|
|
2548
2604
|
docId: string,
|
|
2549
|
-
req: Request
|
|
2605
|
+
req: Request,
|
|
2606
|
+
deps?: {
|
|
2607
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2608
|
+
}
|
|
2550
2609
|
): Promise<Response> {
|
|
2551
2610
|
let body: UpdateDocRequestBody;
|
|
2552
2611
|
try {
|
|
@@ -2726,32 +2785,38 @@ export async function handleUpdateDoc(
|
|
|
2726
2785
|
// Note: embedding handled separately by embed-scheduler (not inline)
|
|
2727
2786
|
let jobId: string | null = null;
|
|
2728
2787
|
if (contentToWrite !== undefined) {
|
|
2729
|
-
const jobResult = startJob(
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2788
|
+
const jobResult = await startJob(
|
|
2789
|
+
"sync",
|
|
2790
|
+
async (): Promise<SyncResult> => {
|
|
2791
|
+
const result = await syncResidentCollection(
|
|
2792
|
+
ctxHolder,
|
|
2793
|
+
collection,
|
|
2794
|
+
store,
|
|
2795
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2796
|
+
deps?.syncCollection
|
|
2797
|
+
);
|
|
2798
|
+
// Notify scheduler after sync completes
|
|
2799
|
+
ctxHolder.scheduler?.notifySyncComplete([doc.docid]);
|
|
2800
|
+
ctxHolder.eventBus?.emit({
|
|
2801
|
+
type: "document-changed",
|
|
2802
|
+
uri: doc.uri,
|
|
2803
|
+
collection: doc.collection,
|
|
2804
|
+
relPath: doc.relPath,
|
|
2805
|
+
origin: "save",
|
|
2806
|
+
changedAt: new Date().toISOString(),
|
|
2807
|
+
});
|
|
2808
|
+
return {
|
|
2809
|
+
collections: [result],
|
|
2810
|
+
totalDurationMs: result.durationMs,
|
|
2811
|
+
totalFilesProcessed: result.filesProcessed,
|
|
2812
|
+
totalFilesAdded: result.filesAdded,
|
|
2813
|
+
totalFilesUpdated: result.filesUpdated,
|
|
2814
|
+
totalFilesErrored: result.filesErrored,
|
|
2815
|
+
totalFilesSkipped: result.filesSkipped,
|
|
2816
|
+
};
|
|
2817
|
+
},
|
|
2818
|
+
ctxHolder.jobManager
|
|
2819
|
+
);
|
|
2755
2820
|
jobId = jobResult.ok ? jobResult.jobId : null;
|
|
2756
2821
|
}
|
|
2757
2822
|
|
|
@@ -2906,7 +2971,10 @@ export async function handleCreateEditableCopy(
|
|
|
2906
2971
|
export async function handleCreateCapture(
|
|
2907
2972
|
ctxHolder: ContextHolder,
|
|
2908
2973
|
store: SqliteAdapter,
|
|
2909
|
-
req: Request
|
|
2974
|
+
req: Request,
|
|
2975
|
+
deps?: {
|
|
2976
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2977
|
+
}
|
|
2910
2978
|
): Promise<Response> {
|
|
2911
2979
|
let body: CreateCaptureRequestBody;
|
|
2912
2980
|
try {
|
|
@@ -2994,31 +3062,37 @@ export async function handleCreateCapture(
|
|
|
2994
3062
|
await writeCapturePlanFile(plan, fullPath);
|
|
2995
3063
|
|
|
2996
3064
|
const gnoUri = `gno://${collection.name}/${plan.relPath}`;
|
|
2997
|
-
const jobResult = startJob(
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3065
|
+
const jobResult = await startJob(
|
|
3066
|
+
"sync",
|
|
3067
|
+
async (): Promise<SyncResult> => {
|
|
3068
|
+
const result = await syncResidentCollection(
|
|
3069
|
+
ctxHolder,
|
|
3070
|
+
collection,
|
|
3071
|
+
store,
|
|
3072
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
3073
|
+
deps?.syncCollection
|
|
3074
|
+
);
|
|
3075
|
+
ctxHolder.scheduler?.notifySyncComplete([plan.relPath]);
|
|
3076
|
+
ctxHolder.eventBus?.emit({
|
|
3077
|
+
type: "document-changed",
|
|
3078
|
+
uri: gnoUri,
|
|
3079
|
+
collection: collection.name,
|
|
3080
|
+
relPath: plan.relPath,
|
|
3081
|
+
origin: "create",
|
|
3082
|
+
changedAt: new Date().toISOString(),
|
|
3083
|
+
});
|
|
3084
|
+
return {
|
|
3085
|
+
collections: [result],
|
|
3086
|
+
totalDurationMs: result.durationMs,
|
|
3087
|
+
totalFilesProcessed: result.filesProcessed,
|
|
3088
|
+
totalFilesAdded: result.filesAdded,
|
|
3089
|
+
totalFilesUpdated: result.filesUpdated,
|
|
3090
|
+
totalFilesErrored: result.filesErrored,
|
|
3091
|
+
totalFilesSkipped: result.filesSkipped,
|
|
3092
|
+
};
|
|
3093
|
+
},
|
|
3094
|
+
ctxHolder.jobManager
|
|
3095
|
+
);
|
|
3022
3096
|
|
|
3023
3097
|
return jsonResponse(
|
|
3024
3098
|
buildCaptureReceipt({
|
|
@@ -3058,7 +3132,10 @@ export async function handleCreateCapture(
|
|
|
3058
3132
|
export async function handleCreateDoc(
|
|
3059
3133
|
ctxHolder: ContextHolder,
|
|
3060
3134
|
store: SqliteAdapter,
|
|
3061
|
-
req: Request
|
|
3135
|
+
req: Request,
|
|
3136
|
+
deps?: {
|
|
3137
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
3138
|
+
}
|
|
3062
3139
|
): Promise<Response> {
|
|
3063
3140
|
let body: CreateDocRequestBody;
|
|
3064
3141
|
try {
|
|
@@ -3230,34 +3307,40 @@ export async function handleCreateDoc(
|
|
|
3230
3307
|
|
|
3231
3308
|
// Run sync via job system (non-blocking)
|
|
3232
3309
|
// Note: embedding handled separately by embed-scheduler (not inline)
|
|
3233
|
-
const jobResult = startJob(
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3310
|
+
const jobResult = await startJob(
|
|
3311
|
+
"sync",
|
|
3312
|
+
async (): Promise<SyncResult> => {
|
|
3313
|
+
const result = await syncResidentCollection(
|
|
3314
|
+
ctxHolder,
|
|
3315
|
+
collection,
|
|
3316
|
+
store,
|
|
3317
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
3318
|
+
deps?.syncCollection
|
|
3319
|
+
);
|
|
3320
|
+
// Notify scheduler after sync completes (use gnoUri as docid placeholder)
|
|
3321
|
+
// The sync will create a proper docid, but we don't have it here yet
|
|
3322
|
+
// Using normalizedRelPath as identifier since docid is generated during sync
|
|
3323
|
+
ctxHolder.scheduler?.notifySyncComplete([normalizedRelPath]);
|
|
3324
|
+
ctxHolder.eventBus?.emit({
|
|
3325
|
+
type: "document-changed",
|
|
3326
|
+
uri: gnoUri,
|
|
3327
|
+
collection: collection.name,
|
|
3328
|
+
relPath: normalizedRelPath,
|
|
3329
|
+
origin: "create",
|
|
3330
|
+
changedAt: new Date().toISOString(),
|
|
3331
|
+
});
|
|
3332
|
+
return {
|
|
3333
|
+
collections: [result],
|
|
3334
|
+
totalDurationMs: result.durationMs,
|
|
3335
|
+
totalFilesProcessed: result.filesProcessed,
|
|
3336
|
+
totalFilesAdded: result.filesAdded,
|
|
3337
|
+
totalFilesUpdated: result.filesUpdated,
|
|
3338
|
+
totalFilesErrored: result.filesErrored,
|
|
3339
|
+
totalFilesSkipped: result.filesSkipped,
|
|
3340
|
+
};
|
|
3341
|
+
},
|
|
3342
|
+
ctxHolder.jobManager
|
|
3343
|
+
);
|
|
3261
3344
|
|
|
3262
3345
|
return jsonResponse(
|
|
3263
3346
|
{
|
|
@@ -4141,7 +4224,13 @@ export function handleModelStatus(): Response {
|
|
|
4141
4224
|
* Start downloading models for current preset.
|
|
4142
4225
|
* Returns immediately; poll /api/models/status for progress.
|
|
4143
4226
|
*/
|
|
4144
|
-
export function handleModelPull(
|
|
4227
|
+
export function handleModelPull(
|
|
4228
|
+
ctxHolder: ContextHolder,
|
|
4229
|
+
deps?: {
|
|
4230
|
+
modelsPullFn?: typeof modelsPull;
|
|
4231
|
+
reloadServerContextFn?: typeof reloadServerContext;
|
|
4232
|
+
}
|
|
4233
|
+
): Response {
|
|
4145
4234
|
// Don't start if already downloading
|
|
4146
4235
|
if (downloadState.active) {
|
|
4147
4236
|
return errorResponse("CONFLICT", "Download already in progress", 409);
|
|
@@ -4152,17 +4241,20 @@ export function handleModelPull(ctxHolder: ContextHolder): Response {
|
|
|
4152
4241
|
downloadState.active = true;
|
|
4153
4242
|
downloadState.startedAt = Date.now();
|
|
4154
4243
|
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4244
|
+
const operation = async (signal: AbortSignal): Promise<void> => {
|
|
4245
|
+
try {
|
|
4246
|
+
const result = await (deps?.modelsPullFn ?? modelsPull)({
|
|
4247
|
+
config: ctxHolder.config,
|
|
4248
|
+
all: true,
|
|
4249
|
+
signal,
|
|
4250
|
+
onProgress: (type, progress) => {
|
|
4251
|
+
if (signal.aborted) return;
|
|
4252
|
+
downloadState.currentType = type;
|
|
4253
|
+
downloadState.progress = progress;
|
|
4254
|
+
},
|
|
4255
|
+
});
|
|
4256
|
+
if (signal.aborted) return;
|
|
4257
|
+
|
|
4166
4258
|
// Track results
|
|
4167
4259
|
for (const r of result.results) {
|
|
4168
4260
|
if (r.ok) {
|
|
@@ -4180,35 +4272,39 @@ export function handleModelPull(ctxHolder: ContextHolder): Response {
|
|
|
4180
4272
|
// Reload context to pick up new models
|
|
4181
4273
|
console.log("Models downloaded, reloading context...");
|
|
4182
4274
|
try {
|
|
4183
|
-
ctxHolder.current = await
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
);
|
|
4275
|
+
ctxHolder.current = await (
|
|
4276
|
+
deps?.reloadServerContextFn ?? reloadServerContext
|
|
4277
|
+
)(ctxHolder.current, ctxHolder.config);
|
|
4278
|
+
if (signal.aborted) return;
|
|
4187
4279
|
console.log("Context reloaded");
|
|
4188
4280
|
if (ctxHolder.scheduler) {
|
|
4189
|
-
|
|
4190
|
-
console.error(
|
|
4191
|
-
"Failed to trigger embedding after model download:",
|
|
4192
|
-
error
|
|
4193
|
-
);
|
|
4194
|
-
});
|
|
4281
|
+
await ctxHolder.scheduler.triggerNow();
|
|
4195
4282
|
}
|
|
4196
4283
|
} catch (e) {
|
|
4197
4284
|
console.error("Failed to reload context:", e);
|
|
4198
4285
|
}
|
|
4199
|
-
|
|
4200
|
-
downloadState.active = false;
|
|
4201
|
-
downloadState.currentType = null;
|
|
4202
|
-
downloadState.progress = null;
|
|
4203
|
-
})
|
|
4204
|
-
.catch((e) => {
|
|
4286
|
+
} catch (e) {
|
|
4205
4287
|
console.error("Model download failed:", e);
|
|
4206
|
-
downloadState.active = false;
|
|
4207
4288
|
downloadState.failed.push({
|
|
4208
4289
|
type: downloadState.currentType ?? "embed",
|
|
4209
4290
|
error: e instanceof Error ? e.message : String(e),
|
|
4210
4291
|
});
|
|
4211
|
-
}
|
|
4292
|
+
} finally {
|
|
4293
|
+
downloadState.active = false;
|
|
4294
|
+
downloadState.currentType = null;
|
|
4295
|
+
downloadState.progress = null;
|
|
4296
|
+
}
|
|
4297
|
+
};
|
|
4298
|
+
|
|
4299
|
+
const started = ctxHolder.startBackgroundWork?.(operation) ?? false;
|
|
4300
|
+
if (!started) {
|
|
4301
|
+
resetDownloadState();
|
|
4302
|
+
return errorResponse(
|
|
4303
|
+
"UNAVAILABLE",
|
|
4304
|
+
"Resident runtime is shutting down",
|
|
4305
|
+
503
|
|
4306
|
+
);
|
|
4307
|
+
}
|
|
4212
4308
|
|
|
4213
4309
|
return jsonResponse({
|
|
4214
4310
|
started: true,
|
|
@@ -4224,8 +4320,8 @@ export function handleModelPull(ctxHolder: ContextHolder): Response {
|
|
|
4224
4320
|
* GET /api/jobs/:id
|
|
4225
4321
|
* Poll job status for async operations.
|
|
4226
4322
|
*/
|
|
4227
|
-
export function handleJob(jobId: string): Response {
|
|
4228
|
-
const status = getJobStatus(jobId);
|
|
4323
|
+
export function handleJob(jobId: string, jobManager?: JobManager): Response {
|
|
4324
|
+
const status = getJobStatus(jobId, jobManager);
|
|
4229
4325
|
if (!status) {
|
|
4230
4326
|
return errorResponse("NOT_FOUND", "Job not found or expired", 404);
|
|
4231
4327
|
}
|
|
@@ -4236,9 +4332,9 @@ export function handleJob(jobId: string): Response {
|
|
|
4236
4332
|
* GET /api/jobs/active
|
|
4237
4333
|
* Returns the current active job, or null when idle.
|
|
4238
4334
|
*/
|
|
4239
|
-
export function handleActiveJob(): Response {
|
|
4335
|
+
export function handleActiveJob(jobManager?: JobManager): Response {
|
|
4240
4336
|
return jsonResponse({
|
|
4241
|
-
activeJob: getActiveJob(),
|
|
4337
|
+
activeJob: getActiveJob(jobManager),
|
|
4242
4338
|
});
|
|
4243
4339
|
}
|
|
4244
4340
|
|