@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.
- package/README.md +18 -4
- package/assets/skill/SKILL.md +44 -1
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +3 -2
- package/spec/AGENTS.md +83 -0
- package/spec/CLAUDE.md +83 -0
- package/spec/bench-fixture.schema.json +137 -0
- package/spec/cli.md +2894 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +510 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2229 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +366 -0
- package/spec/output-schemas/backlinks.schema.json +131 -0
- package/spec/output-schemas/bench-result.schema.json +120 -0
- package/spec/output-schemas/capture-receipt.schema.json +143 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
- package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
- package/spec/output-schemas/context-list.schema.json +21 -0
- package/spec/output-schemas/doctor.schema.json +313 -0
- package/spec/output-schemas/error.schema.json +30 -0
- package/spec/output-schemas/expansion.schema.json +37 -0
- package/spec/output-schemas/get.schema.json +140 -0
- package/spec/output-schemas/graph-query.schema.json +99 -0
- package/spec/output-schemas/graph.schema.json +371 -0
- package/spec/output-schemas/links-list.schema.json +186 -0
- package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
- package/spec/output-schemas/mcp-http-error.schema.json +30 -0
- package/spec/output-schemas/mcp-job-list.schema.json +58 -0
- package/spec/output-schemas/mcp-job-status.schema.json +224 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
- package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
- package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
- package/spec/output-schemas/models-list.schema.json +93 -0
- package/spec/output-schemas/multi-get.schema.json +103 -0
- package/spec/output-schemas/process-status.schema.json +119 -0
- package/spec/output-schemas/query-diagnose.schema.json +123 -0
- package/spec/output-schemas/resident-status.schema.json +154 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
- package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
- package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
- package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
- package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
- package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
- package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
- package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
- package/spec/output-schemas/search-result.schema.json +154 -0
- package/spec/output-schemas/search-results.schema.json +338 -0
- package/spec/output-schemas/similar.schema.json +84 -0
- package/spec/output-schemas/status.schema.json +676 -0
- package/spec/output-schemas/tags-list.schema.json +48 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +15 -1
- package/src/cli/commands/ask.ts +106 -36
- package/src/cli/commands/context-build.ts +56 -9
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/get.ts +64 -3
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/query.ts +62 -23
- package/src/cli/commands/replay.ts +140 -0
- package/src/cli/commands/search.ts +48 -3
- package/src/cli/commands/shared.ts +3 -1
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/commands/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/detach.ts +37 -20
- package/src/cli/program.ts +329 -27
- package/src/config/index.ts +12 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +41 -0
- package/src/core/context-compiler.ts +11 -4
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/core/retrieval-qrels.ts +405 -0
- package/src/core/retrieval-replay-candidate.ts +368 -0
- package/src/core/retrieval-replay-types.ts +109 -0
- package/src/core/retrieval-replay-validation.ts +89 -0
- package/src/core/retrieval-replay.ts +441 -0
- package/src/core/retrieval-trace-evidence-origin.ts +175 -0
- package/src/core/retrieval-trace-export.ts +113 -0
- package/src/core/retrieval-trace-filter-normalization.ts +27 -0
- package/src/core/retrieval-trace-filters.ts +19 -0
- package/src/core/retrieval-trace-management-helpers.ts +247 -0
- package/src/core/retrieval-trace-management-types.ts +132 -0
- package/src/core/retrieval-trace-management.ts +422 -0
- package/src/core/retrieval-trace-request.ts +141 -0
- package/src/core/retrieval-trace-session.ts +494 -0
- package/src/core/retrieval-trace.ts +472 -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 +68 -16
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +96 -2
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/pipeline/answer.ts +167 -26
- package/src/pipeline/graph-retrieval.ts +15 -1
- package/src/pipeline/hybrid.ts +151 -43
- package/src/pipeline/search.ts +36 -3
- package/src/pipeline/trace-metadata.ts +47 -0
- package/src/pipeline/types.ts +43 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +380 -71
- package/src/sdk/documents.ts +48 -1
- package/src/sdk/index.ts +17 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/background-runtime.ts +12 -212
- package/src/serve/context-capsule.ts +67 -8
- 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/app.tsx +12 -1
- package/src/serve/public/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Dashboard.tsx +11 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -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/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +629 -239
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +276 -37
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/index.ts +31 -0
- package/src/store/migrations/014-retrieval-traces.ts +303 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/retrieval-trace-codec.ts +384 -0
- package/src/store/sqlite/adapter.ts +179 -10
- package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
- package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
- package/src/store/sqlite/retrieval-trace-store.ts +515 -0
- package/src/store/types.ts +297 -0
- package/src/store/vector/sqlite-vec.ts +76 -1
- package/src/store/vector/types.ts +1 -1
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,
|
|
@@ -87,6 +94,14 @@ import {
|
|
|
87
94
|
resolveNotePreset,
|
|
88
95
|
type NotePresetId,
|
|
89
96
|
} from "../../core/note-presets";
|
|
97
|
+
import {
|
|
98
|
+
retrievalTraceFilters,
|
|
99
|
+
startRetrievalTraceRequest,
|
|
100
|
+
} from "../../core/retrieval-trace-request";
|
|
101
|
+
import {
|
|
102
|
+
evidenceFromExactDocument,
|
|
103
|
+
RetrievalTraceSession,
|
|
104
|
+
} from "../../core/retrieval-trace-session";
|
|
90
105
|
import { extractSections } from "../../core/sections";
|
|
91
106
|
import { normalizeStructuredQueryInput } from "../../core/structured-query";
|
|
92
107
|
import {
|
|
@@ -111,7 +126,7 @@ import {
|
|
|
111
126
|
} from "../../llm/registry";
|
|
112
127
|
import {
|
|
113
128
|
generateGroundedAnswer,
|
|
114
|
-
|
|
129
|
+
processAnswerResultWithTrace,
|
|
115
130
|
} from "../../pipeline/answer";
|
|
116
131
|
import { diagnoseQueryTarget } from "../../pipeline/diagnose";
|
|
117
132
|
import { searchHybrid } from "../../pipeline/hybrid";
|
|
@@ -138,6 +153,10 @@ import {
|
|
|
138
153
|
} from "../context";
|
|
139
154
|
import { analyzeImportPath } from "../import-preview";
|
|
140
155
|
import { getActiveJob, getJobStatus, startJob } from "../jobs";
|
|
156
|
+
import {
|
|
157
|
+
requestRetrievalTraceId,
|
|
158
|
+
withRetrievalTraceHeader,
|
|
159
|
+
} from "../retrieval-trace";
|
|
141
160
|
import { buildAppStatus, type StatusBuildDeps } from "../status";
|
|
142
161
|
|
|
143
162
|
/** Mutable context holder for hot-reloading presets */
|
|
@@ -147,6 +166,26 @@ export interface ContextHolder {
|
|
|
147
166
|
scheduler: EmbedScheduler | null;
|
|
148
167
|
eventBus: DocumentEventBus | null;
|
|
149
168
|
watchService: CollectionWatchService | null;
|
|
169
|
+
jobManager?: JobManager;
|
|
170
|
+
markContentMutation?: () => void;
|
|
171
|
+
markIndexMutation?: () => void;
|
|
172
|
+
startBackgroundWork?: (
|
|
173
|
+
operation: (signal: AbortSignal) => Promise<void>
|
|
174
|
+
) => boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function syncResidentCollection(
|
|
178
|
+
ctxHolder: ContextHolder,
|
|
179
|
+
collection: Collection,
|
|
180
|
+
store: SqliteAdapter,
|
|
181
|
+
options: Parameters<typeof defaultSyncService.syncCollection>[2],
|
|
182
|
+
syncCollection: typeof defaultSyncService.syncCollection = defaultSyncService.syncCollection.bind(
|
|
183
|
+
defaultSyncService
|
|
184
|
+
)
|
|
185
|
+
): ReturnType<typeof defaultSyncService.syncCollection> {
|
|
186
|
+
const result = await syncCollection(collection, store, options);
|
|
187
|
+
recordContentMutation(result, ctxHolder.markContentMutation);
|
|
188
|
+
return result;
|
|
150
189
|
}
|
|
151
190
|
|
|
152
191
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -433,6 +472,65 @@ function errorResponse(
|
|
|
433
472
|
);
|
|
434
473
|
}
|
|
435
474
|
|
|
475
|
+
interface RestTraceStart {
|
|
476
|
+
session: RetrievalTraceSession | null;
|
|
477
|
+
error: Response | null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function startRestTrace(
|
|
481
|
+
ctx: ServerContext,
|
|
482
|
+
input: {
|
|
483
|
+
query: string;
|
|
484
|
+
goal?: string;
|
|
485
|
+
filters?: Record<string, unknown>;
|
|
486
|
+
pipeline: string;
|
|
487
|
+
modelUris?: string[];
|
|
488
|
+
}
|
|
489
|
+
): Promise<RestTraceStart> {
|
|
490
|
+
const started = await startRetrievalTraceRequest({
|
|
491
|
+
store: ctx.store,
|
|
492
|
+
config: ctx.config,
|
|
493
|
+
query: input.query,
|
|
494
|
+
goal: input.goal,
|
|
495
|
+
filters: input.filters,
|
|
496
|
+
pipeline: input.pipeline,
|
|
497
|
+
indexName: ctx.indexName,
|
|
498
|
+
modelUris: input.modelUris,
|
|
499
|
+
});
|
|
500
|
+
return started.ok
|
|
501
|
+
? { session: started.value, error: null }
|
|
502
|
+
: {
|
|
503
|
+
session: null,
|
|
504
|
+
error: errorResponse(
|
|
505
|
+
"RUNTIME",
|
|
506
|
+
`Retrieval trace start failed: ${started.error.message}`,
|
|
507
|
+
500
|
|
508
|
+
),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function finishRestTrace(
|
|
513
|
+
request: Request,
|
|
514
|
+
session: RetrievalTraceSession | null,
|
|
515
|
+
status: "completed" | "partial" | "failed",
|
|
516
|
+
response: Response
|
|
517
|
+
): Promise<Response> {
|
|
518
|
+
if (!session) return response;
|
|
519
|
+
const terminalStatus = request.signal.aborted ? "cancelled" : status;
|
|
520
|
+
const finished = await session.finish(terminalStatus);
|
|
521
|
+
if (!finished.ok) {
|
|
522
|
+
return withRetrievalTraceHeader(
|
|
523
|
+
errorResponse(
|
|
524
|
+
"RUNTIME",
|
|
525
|
+
`Retrieval trace finalization failed: ${finished.error.message}`,
|
|
526
|
+
500
|
|
527
|
+
),
|
|
528
|
+
session
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return withRetrievalTraceHeader(response, session);
|
|
532
|
+
}
|
|
533
|
+
|
|
436
534
|
function jobConflictResponse(jobResult: StartJobError): Response {
|
|
437
535
|
return errorResponse("CONFLICT", jobResult.error, 409, {
|
|
438
536
|
activeJobId: jobResult.activeJobId,
|
|
@@ -815,6 +913,12 @@ export async function handleStatus(
|
|
|
815
913
|
}
|
|
816
914
|
}
|
|
817
915
|
|
|
916
|
+
export function handleResidentStatus(
|
|
917
|
+
getStatus: () => ResidentStatus
|
|
918
|
+
): Response {
|
|
919
|
+
return jsonResponse(getStatus());
|
|
920
|
+
}
|
|
921
|
+
|
|
818
922
|
/**
|
|
819
923
|
* GET /api/collections
|
|
820
924
|
* Returns list of collections.
|
|
@@ -990,33 +1094,38 @@ export async function handleCreateCollection(
|
|
|
990
1094
|
if (!collection) {
|
|
991
1095
|
return errorResponse("RUNTIME", "Collection not found after add", 500);
|
|
992
1096
|
}
|
|
993
|
-
const jobResult = startJob(
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1097
|
+
const jobResult = await startJob(
|
|
1098
|
+
"add",
|
|
1099
|
+
async (): Promise<SyncResult> => {
|
|
1100
|
+
const result = await syncResidentCollection(
|
|
1101
|
+
ctxHolder,
|
|
1102
|
+
collection,
|
|
1103
|
+
store,
|
|
1104
|
+
withContentTypeRules(
|
|
1105
|
+
{
|
|
1106
|
+
gitPull: body.gitPull,
|
|
1107
|
+
runUpdateCmd: true,
|
|
1108
|
+
},
|
|
1109
|
+
syncResult.config
|
|
1110
|
+
)
|
|
1111
|
+
);
|
|
1112
|
+
if (result.filesAdded > 0 || result.filesUpdated > 0) {
|
|
1113
|
+
if (ctxHolder.scheduler) {
|
|
1114
|
+
await ctxHolder.scheduler.triggerNow();
|
|
1115
|
+
}
|
|
1008
1116
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1117
|
+
return {
|
|
1118
|
+
collections: [result],
|
|
1119
|
+
totalDurationMs: result.durationMs,
|
|
1120
|
+
totalFilesProcessed: result.filesProcessed,
|
|
1121
|
+
totalFilesAdded: result.filesAdded,
|
|
1122
|
+
totalFilesUpdated: result.filesUpdated,
|
|
1123
|
+
totalFilesErrored: result.filesErrored,
|
|
1124
|
+
totalFilesSkipped: result.filesSkipped,
|
|
1125
|
+
};
|
|
1126
|
+
},
|
|
1127
|
+
ctxHolder.jobManager
|
|
1128
|
+
);
|
|
1020
1129
|
|
|
1021
1130
|
if (!jobResult.ok) {
|
|
1022
1131
|
return jobConflictResponse(jobResult);
|
|
@@ -1100,7 +1209,8 @@ export async function handleDeleteCollection(
|
|
|
1100
1209
|
const status = statusMap[syncResult.code] ?? 500;
|
|
1101
1210
|
return errorResponse(syncResult.code, syncResult.error, status);
|
|
1102
1211
|
}
|
|
1103
|
-
|
|
1212
|
+
ctxHolder.markContentMutation?.();
|
|
1213
|
+
ctxHolder.markIndexMutation?.();
|
|
1104
1214
|
return jsonResponse({
|
|
1105
1215
|
success: true,
|
|
1106
1216
|
collection: name,
|
|
@@ -1231,6 +1341,7 @@ export async function handleClearCollectionEmbeddings(
|
|
|
1231
1341
|
const status = result.error.code === "INVALID_INPUT" ? 400 : 500;
|
|
1232
1342
|
return errorResponse(result.error.code, result.error.message, status);
|
|
1233
1343
|
}
|
|
1344
|
+
recordIndexMutation(result.value.deletedVectors, ctxHolder.markIndexMutation);
|
|
1234
1345
|
|
|
1235
1346
|
return jsonResponse({
|
|
1236
1347
|
success: true,
|
|
@@ -1292,25 +1403,30 @@ export async function handleSync(
|
|
|
1292
1403
|
}
|
|
1293
1404
|
|
|
1294
1405
|
// Start background sync job
|
|
1295
|
-
const jobResult = startJob(
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1406
|
+
const jobResult = await startJob(
|
|
1407
|
+
"sync",
|
|
1408
|
+
async (): Promise<SyncResult> => {
|
|
1409
|
+
const result = await defaultSyncService.syncAll(
|
|
1410
|
+
collections,
|
|
1411
|
+
store,
|
|
1412
|
+
withContentTypeRules(
|
|
1413
|
+
{
|
|
1414
|
+
gitPull: body.gitPull,
|
|
1415
|
+
runUpdateCmd: true,
|
|
1416
|
+
},
|
|
1417
|
+
ctxHolder.config
|
|
1418
|
+
)
|
|
1419
|
+
);
|
|
1420
|
+
recordContentMutation(result, ctxHolder.markContentMutation);
|
|
1421
|
+
if (hasContentMutation(result)) {
|
|
1422
|
+
if (ctxHolder.scheduler) {
|
|
1423
|
+
await ctxHolder.scheduler.triggerNow();
|
|
1424
|
+
}
|
|
1310
1425
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1426
|
+
return result;
|
|
1427
|
+
},
|
|
1428
|
+
ctxHolder.jobManager
|
|
1429
|
+
);
|
|
1314
1430
|
|
|
1315
1431
|
if (!jobResult.ok) {
|
|
1316
1432
|
return jobConflictResponse(jobResult);
|
|
@@ -1594,7 +1710,8 @@ export async function handleDocsAutocomplete(
|
|
|
1594
1710
|
export async function handleDoc(
|
|
1595
1711
|
store: SqliteAdapter,
|
|
1596
1712
|
config: Config,
|
|
1597
|
-
url: URL
|
|
1713
|
+
url: URL,
|
|
1714
|
+
request?: Request
|
|
1598
1715
|
): Promise<Response> {
|
|
1599
1716
|
const uri = url.searchParams.get("uri");
|
|
1600
1717
|
if (!uri) {
|
|
@@ -1634,7 +1751,7 @@ export async function handleDoc(
|
|
|
1634
1751
|
});
|
|
1635
1752
|
const source = await buildSourceMeta(config.collections, doc);
|
|
1636
1753
|
|
|
1637
|
-
|
|
1754
|
+
const responseData = {
|
|
1638
1755
|
docid: doc.docid,
|
|
1639
1756
|
uri: doc.uri,
|
|
1640
1757
|
title: doc.title,
|
|
@@ -1645,7 +1762,52 @@ export async function handleDoc(
|
|
|
1645
1762
|
tags,
|
|
1646
1763
|
source,
|
|
1647
1764
|
capabilities,
|
|
1765
|
+
};
|
|
1766
|
+
const continuationTraceId = request
|
|
1767
|
+
? requestRetrievalTraceId(request)
|
|
1768
|
+
: undefined;
|
|
1769
|
+
if (!continuationTraceId) return jsonResponse(responseData);
|
|
1770
|
+
|
|
1771
|
+
const resumed = await RetrievalTraceSession.resume({
|
|
1772
|
+
store,
|
|
1773
|
+
config: config.retrievalTraces,
|
|
1774
|
+
traceId: continuationTraceId,
|
|
1648
1775
|
});
|
|
1776
|
+
if (!resumed.ok) {
|
|
1777
|
+
return errorResponse("RUNTIME", resumed.error.message, 500);
|
|
1778
|
+
}
|
|
1779
|
+
const traceSession = resumed.value;
|
|
1780
|
+
if (!traceSession) return jsonResponse(responseData);
|
|
1781
|
+
|
|
1782
|
+
if (content !== null) {
|
|
1783
|
+
const endLine = content.split("\n").length;
|
|
1784
|
+
const evidence = evidenceFromExactDocument({
|
|
1785
|
+
docid: doc.docid,
|
|
1786
|
+
uri: doc.uri,
|
|
1787
|
+
sourceHash: doc.sourceHash,
|
|
1788
|
+
mirrorHash: doc.mirrorHash ?? undefined,
|
|
1789
|
+
content,
|
|
1790
|
+
startLine: 1,
|
|
1791
|
+
endLine,
|
|
1792
|
+
});
|
|
1793
|
+
if (evidence) {
|
|
1794
|
+
const got = await traceSession.recordEvidence("get", [evidence]);
|
|
1795
|
+
if (!got.ok) {
|
|
1796
|
+
return withRetrievalTraceHeader(
|
|
1797
|
+
errorResponse("RUNTIME", got.error.message, 500),
|
|
1798
|
+
traceSession
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
const opened = await traceSession.recordEvidence("open", [evidence]);
|
|
1802
|
+
if (!opened.ok) {
|
|
1803
|
+
return withRetrievalTraceHeader(
|
|
1804
|
+
errorResponse("RUNTIME", opened.error.message, 500),
|
|
1805
|
+
traceSession
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return withRetrievalTraceHeader(jsonResponse(responseData), traceSession);
|
|
1649
1811
|
}
|
|
1650
1812
|
|
|
1651
1813
|
/**
|
|
@@ -1797,6 +1959,7 @@ export async function handleTags(
|
|
|
1797
1959
|
* Deactivate a document (soft delete - does not remove file from disk).
|
|
1798
1960
|
*/
|
|
1799
1961
|
export async function handleDeactivateDoc(
|
|
1962
|
+
ctxHolder: ContextHolder,
|
|
1800
1963
|
store: SqliteAdapter,
|
|
1801
1964
|
docId: string,
|
|
1802
1965
|
req?: Request
|
|
@@ -1821,6 +1984,10 @@ export async function handleDeactivateDoc(
|
|
|
1821
1984
|
if (!result.ok) {
|
|
1822
1985
|
return errorResponse("RUNTIME", result.error.message, 500);
|
|
1823
1986
|
}
|
|
1987
|
+
if (result.value > 0) {
|
|
1988
|
+
ctxHolder.markContentMutation?.();
|
|
1989
|
+
ctxHolder.markIndexMutation?.();
|
|
1990
|
+
}
|
|
1824
1991
|
|
|
1825
1992
|
return jsonResponse({
|
|
1826
1993
|
success: true,
|
|
@@ -1929,10 +2096,12 @@ export async function handleRenameDoc(
|
|
|
1929
2096
|
const nextUri = `gno://${collection.name}/${nextRelPath}`;
|
|
1930
2097
|
let warning: string | undefined;
|
|
1931
2098
|
try {
|
|
1932
|
-
await
|
|
2099
|
+
await syncResidentCollection(
|
|
2100
|
+
ctxHolder,
|
|
1933
2101
|
collection,
|
|
1934
2102
|
store,
|
|
1935
|
-
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
2103
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2104
|
+
syncCollection
|
|
1936
2105
|
);
|
|
1937
2106
|
} catch {
|
|
1938
2107
|
warning =
|
|
@@ -2151,10 +2320,12 @@ export async function handleTrashDoc(
|
|
|
2151
2320
|
}
|
|
2152
2321
|
let warning: string | undefined;
|
|
2153
2322
|
try {
|
|
2154
|
-
await
|
|
2323
|
+
await syncResidentCollection(
|
|
2324
|
+
ctxHolder,
|
|
2155
2325
|
collection,
|
|
2156
2326
|
store,
|
|
2157
|
-
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
2327
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2328
|
+
syncCollection
|
|
2158
2329
|
);
|
|
2159
2330
|
} catch {
|
|
2160
2331
|
warning =
|
|
@@ -2274,7 +2445,8 @@ export async function handleMoveDoc(
|
|
|
2274
2445
|
await renameFilePath(fullPath, nextFullPath);
|
|
2275
2446
|
let warning: string | undefined;
|
|
2276
2447
|
try {
|
|
2277
|
-
await
|
|
2448
|
+
await syncResidentCollection(
|
|
2449
|
+
ctxHolder,
|
|
2278
2450
|
collection,
|
|
2279
2451
|
store,
|
|
2280
2452
|
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
@@ -2397,7 +2569,8 @@ export async function handleDuplicateDoc(
|
|
|
2397
2569
|
await copyFilePath(fullPath, nextFullPath);
|
|
2398
2570
|
let warning: string | undefined;
|
|
2399
2571
|
try {
|
|
2400
|
-
await
|
|
2572
|
+
await syncResidentCollection(
|
|
2573
|
+
ctxHolder,
|
|
2401
2574
|
collection,
|
|
2402
2575
|
store,
|
|
2403
2576
|
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config)
|
|
@@ -2546,7 +2719,10 @@ export async function handleUpdateDoc(
|
|
|
2546
2719
|
ctxHolder: ContextHolder,
|
|
2547
2720
|
store: SqliteAdapter,
|
|
2548
2721
|
docId: string,
|
|
2549
|
-
req: Request
|
|
2722
|
+
req: Request,
|
|
2723
|
+
deps?: {
|
|
2724
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2725
|
+
}
|
|
2550
2726
|
): Promise<Response> {
|
|
2551
2727
|
let body: UpdateDocRequestBody;
|
|
2552
2728
|
try {
|
|
@@ -2726,32 +2902,38 @@ export async function handleUpdateDoc(
|
|
|
2726
2902
|
// Note: embedding handled separately by embed-scheduler (not inline)
|
|
2727
2903
|
let jobId: string | null = null;
|
|
2728
2904
|
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
|
-
|
|
2905
|
+
const jobResult = await startJob(
|
|
2906
|
+
"sync",
|
|
2907
|
+
async (): Promise<SyncResult> => {
|
|
2908
|
+
const result = await syncResidentCollection(
|
|
2909
|
+
ctxHolder,
|
|
2910
|
+
collection,
|
|
2911
|
+
store,
|
|
2912
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2913
|
+
deps?.syncCollection
|
|
2914
|
+
);
|
|
2915
|
+
// Notify scheduler after sync completes
|
|
2916
|
+
ctxHolder.scheduler?.notifySyncComplete([doc.docid]);
|
|
2917
|
+
ctxHolder.eventBus?.emit({
|
|
2918
|
+
type: "document-changed",
|
|
2919
|
+
uri: doc.uri,
|
|
2920
|
+
collection: doc.collection,
|
|
2921
|
+
relPath: doc.relPath,
|
|
2922
|
+
origin: "save",
|
|
2923
|
+
changedAt: new Date().toISOString(),
|
|
2924
|
+
});
|
|
2925
|
+
return {
|
|
2926
|
+
collections: [result],
|
|
2927
|
+
totalDurationMs: result.durationMs,
|
|
2928
|
+
totalFilesProcessed: result.filesProcessed,
|
|
2929
|
+
totalFilesAdded: result.filesAdded,
|
|
2930
|
+
totalFilesUpdated: result.filesUpdated,
|
|
2931
|
+
totalFilesErrored: result.filesErrored,
|
|
2932
|
+
totalFilesSkipped: result.filesSkipped,
|
|
2933
|
+
};
|
|
2934
|
+
},
|
|
2935
|
+
ctxHolder.jobManager
|
|
2936
|
+
);
|
|
2755
2937
|
jobId = jobResult.ok ? jobResult.jobId : null;
|
|
2756
2938
|
}
|
|
2757
2939
|
|
|
@@ -2906,7 +3088,10 @@ export async function handleCreateEditableCopy(
|
|
|
2906
3088
|
export async function handleCreateCapture(
|
|
2907
3089
|
ctxHolder: ContextHolder,
|
|
2908
3090
|
store: SqliteAdapter,
|
|
2909
|
-
req: Request
|
|
3091
|
+
req: Request,
|
|
3092
|
+
deps?: {
|
|
3093
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
3094
|
+
}
|
|
2910
3095
|
): Promise<Response> {
|
|
2911
3096
|
let body: CreateCaptureRequestBody;
|
|
2912
3097
|
try {
|
|
@@ -2994,31 +3179,37 @@ export async function handleCreateCapture(
|
|
|
2994
3179
|
await writeCapturePlanFile(plan, fullPath);
|
|
2995
3180
|
|
|
2996
3181
|
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
|
-
|
|
3182
|
+
const jobResult = await startJob(
|
|
3183
|
+
"sync",
|
|
3184
|
+
async (): Promise<SyncResult> => {
|
|
3185
|
+
const result = await syncResidentCollection(
|
|
3186
|
+
ctxHolder,
|
|
3187
|
+
collection,
|
|
3188
|
+
store,
|
|
3189
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
3190
|
+
deps?.syncCollection
|
|
3191
|
+
);
|
|
3192
|
+
ctxHolder.scheduler?.notifySyncComplete([plan.relPath]);
|
|
3193
|
+
ctxHolder.eventBus?.emit({
|
|
3194
|
+
type: "document-changed",
|
|
3195
|
+
uri: gnoUri,
|
|
3196
|
+
collection: collection.name,
|
|
3197
|
+
relPath: plan.relPath,
|
|
3198
|
+
origin: "create",
|
|
3199
|
+
changedAt: new Date().toISOString(),
|
|
3200
|
+
});
|
|
3201
|
+
return {
|
|
3202
|
+
collections: [result],
|
|
3203
|
+
totalDurationMs: result.durationMs,
|
|
3204
|
+
totalFilesProcessed: result.filesProcessed,
|
|
3205
|
+
totalFilesAdded: result.filesAdded,
|
|
3206
|
+
totalFilesUpdated: result.filesUpdated,
|
|
3207
|
+
totalFilesErrored: result.filesErrored,
|
|
3208
|
+
totalFilesSkipped: result.filesSkipped,
|
|
3209
|
+
};
|
|
3210
|
+
},
|
|
3211
|
+
ctxHolder.jobManager
|
|
3212
|
+
);
|
|
3022
3213
|
|
|
3023
3214
|
return jsonResponse(
|
|
3024
3215
|
buildCaptureReceipt({
|
|
@@ -3058,7 +3249,10 @@ export async function handleCreateCapture(
|
|
|
3058
3249
|
export async function handleCreateDoc(
|
|
3059
3250
|
ctxHolder: ContextHolder,
|
|
3060
3251
|
store: SqliteAdapter,
|
|
3061
|
-
req: Request
|
|
3252
|
+
req: Request,
|
|
3253
|
+
deps?: {
|
|
3254
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
3255
|
+
}
|
|
3062
3256
|
): Promise<Response> {
|
|
3063
3257
|
let body: CreateDocRequestBody;
|
|
3064
3258
|
try {
|
|
@@ -3230,34 +3424,40 @@ export async function handleCreateDoc(
|
|
|
3230
3424
|
|
|
3231
3425
|
// Run sync via job system (non-blocking)
|
|
3232
3426
|
// 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
|
-
|
|
3427
|
+
const jobResult = await startJob(
|
|
3428
|
+
"sync",
|
|
3429
|
+
async (): Promise<SyncResult> => {
|
|
3430
|
+
const result = await syncResidentCollection(
|
|
3431
|
+
ctxHolder,
|
|
3432
|
+
collection,
|
|
3433
|
+
store,
|
|
3434
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
3435
|
+
deps?.syncCollection
|
|
3436
|
+
);
|
|
3437
|
+
// Notify scheduler after sync completes (use gnoUri as docid placeholder)
|
|
3438
|
+
// The sync will create a proper docid, but we don't have it here yet
|
|
3439
|
+
// Using normalizedRelPath as identifier since docid is generated during sync
|
|
3440
|
+
ctxHolder.scheduler?.notifySyncComplete([normalizedRelPath]);
|
|
3441
|
+
ctxHolder.eventBus?.emit({
|
|
3442
|
+
type: "document-changed",
|
|
3443
|
+
uri: gnoUri,
|
|
3444
|
+
collection: collection.name,
|
|
3445
|
+
relPath: normalizedRelPath,
|
|
3446
|
+
origin: "create",
|
|
3447
|
+
changedAt: new Date().toISOString(),
|
|
3448
|
+
});
|
|
3449
|
+
return {
|
|
3450
|
+
collections: [result],
|
|
3451
|
+
totalDurationMs: result.durationMs,
|
|
3452
|
+
totalFilesProcessed: result.filesProcessed,
|
|
3453
|
+
totalFilesAdded: result.filesAdded,
|
|
3454
|
+
totalFilesUpdated: result.filesUpdated,
|
|
3455
|
+
totalFilesErrored: result.filesErrored,
|
|
3456
|
+
totalFilesSkipped: result.filesSkipped,
|
|
3457
|
+
};
|
|
3458
|
+
},
|
|
3459
|
+
ctxHolder.jobManager
|
|
3460
|
+
);
|
|
3261
3461
|
|
|
3262
3462
|
return jsonResponse(
|
|
3263
3463
|
{
|
|
@@ -3289,9 +3489,13 @@ export async function handleCreateDoc(
|
|
|
3289
3489
|
* Returns search results.
|
|
3290
3490
|
*/
|
|
3291
3491
|
export async function handleSearch(
|
|
3292
|
-
|
|
3492
|
+
contextOrStore: ServerContext | SqliteAdapter,
|
|
3293
3493
|
req: Request
|
|
3294
3494
|
): Promise<Response> {
|
|
3495
|
+
const context =
|
|
3496
|
+
"store" in contextOrStore ? contextOrStore : (null as ServerContext | null);
|
|
3497
|
+
const store =
|
|
3498
|
+
"store" in contextOrStore ? contextOrStore.store : contextOrStore;
|
|
3295
3499
|
let body: SearchRequestBody;
|
|
3296
3500
|
try {
|
|
3297
3501
|
body = (await req.json()) as SearchRequestBody;
|
|
@@ -3403,13 +3607,30 @@ export async function handleSearch(
|
|
|
3403
3607
|
author,
|
|
3404
3608
|
};
|
|
3405
3609
|
|
|
3406
|
-
const
|
|
3610
|
+
const trace = context
|
|
3611
|
+
? await startRestTrace(context, {
|
|
3612
|
+
query: rawQuery,
|
|
3613
|
+
filters: retrievalTraceFilters(options),
|
|
3614
|
+
pipeline: "bm25",
|
|
3615
|
+
})
|
|
3616
|
+
: { session: null, error: null };
|
|
3617
|
+
if (trace.error) return trace.error;
|
|
3618
|
+
|
|
3619
|
+
const result = await searchBm25(store, rawQuery, {
|
|
3620
|
+
...options,
|
|
3621
|
+
traceSession: trace.session ?? undefined,
|
|
3622
|
+
});
|
|
3407
3623
|
|
|
3408
3624
|
if (!result.ok) {
|
|
3409
|
-
return
|
|
3625
|
+
return finishRestTrace(
|
|
3626
|
+
req,
|
|
3627
|
+
trace.session,
|
|
3628
|
+
"failed",
|
|
3629
|
+
errorResponse("RUNTIME", result.error.message, 500)
|
|
3630
|
+
);
|
|
3410
3631
|
}
|
|
3411
3632
|
|
|
3412
|
-
return jsonResponse(result.value);
|
|
3633
|
+
return withRetrievalTraceHeader(jsonResponse(result.value), trace.session);
|
|
3413
3634
|
}
|
|
3414
3635
|
|
|
3415
3636
|
/**
|
|
@@ -3543,6 +3764,41 @@ export async function handleQuery(
|
|
|
3543
3764
|
: undefined;
|
|
3544
3765
|
const author = body.author?.trim() || undefined;
|
|
3545
3766
|
|
|
3767
|
+
const queryOptions = {
|
|
3768
|
+
limit: Math.min(body.limit ?? 20, 50),
|
|
3769
|
+
minScore: body.minScore,
|
|
3770
|
+
collection: body.collection,
|
|
3771
|
+
lang: body.lang,
|
|
3772
|
+
intent: body.intent?.trim() || undefined,
|
|
3773
|
+
candidateLimit:
|
|
3774
|
+
body.candidateLimit !== undefined
|
|
3775
|
+
? Math.min(body.candidateLimit, 100)
|
|
3776
|
+
: undefined,
|
|
3777
|
+
exclude,
|
|
3778
|
+
queryModes: normalizedQueryModes,
|
|
3779
|
+
noExpand: body.noExpand,
|
|
3780
|
+
noRerank: body.noRerank,
|
|
3781
|
+
graph: body.graph === true,
|
|
3782
|
+
noGraph: body.noGraph,
|
|
3783
|
+
tagsAll,
|
|
3784
|
+
tagsAny,
|
|
3785
|
+
since: body.since,
|
|
3786
|
+
until: body.until,
|
|
3787
|
+
categories,
|
|
3788
|
+
author,
|
|
3789
|
+
};
|
|
3790
|
+
const trace = await startRestTrace(ctx, {
|
|
3791
|
+
query: normalizedQuery,
|
|
3792
|
+
filters: retrievalTraceFilters(queryOptions),
|
|
3793
|
+
pipeline: "hybrid",
|
|
3794
|
+
modelUris: [
|
|
3795
|
+
ctx.embedPort?.modelUri,
|
|
3796
|
+
ctx.expandPort?.modelUri,
|
|
3797
|
+
ctx.rerankPort?.modelUri,
|
|
3798
|
+
].filter((value): value is string => Boolean(value)),
|
|
3799
|
+
});
|
|
3800
|
+
if (trace.error) return trace.error;
|
|
3801
|
+
|
|
3546
3802
|
const result = await searchHybrid(
|
|
3547
3803
|
{
|
|
3548
3804
|
store: ctx.store,
|
|
@@ -3554,35 +3810,21 @@ export async function handleQuery(
|
|
|
3554
3810
|
},
|
|
3555
3811
|
normalizedQuery,
|
|
3556
3812
|
{
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
collection: body.collection,
|
|
3560
|
-
lang: body.lang,
|
|
3561
|
-
intent: body.intent?.trim() || undefined,
|
|
3562
|
-
candidateLimit:
|
|
3563
|
-
body.candidateLimit !== undefined
|
|
3564
|
-
? Math.min(body.candidateLimit, 100)
|
|
3565
|
-
: undefined,
|
|
3566
|
-
exclude,
|
|
3567
|
-
queryModes: normalizedQueryModes,
|
|
3568
|
-
noExpand: body.noExpand,
|
|
3569
|
-
noRerank: body.noRerank,
|
|
3570
|
-
graph: body.graph === true,
|
|
3571
|
-
noGraph: body.noGraph,
|
|
3572
|
-
tagsAll,
|
|
3573
|
-
tagsAny,
|
|
3574
|
-
since: body.since,
|
|
3575
|
-
until: body.until,
|
|
3576
|
-
categories,
|
|
3577
|
-
author,
|
|
3813
|
+
...queryOptions,
|
|
3814
|
+
traceSession: trace.session ?? undefined,
|
|
3578
3815
|
}
|
|
3579
3816
|
);
|
|
3580
3817
|
|
|
3581
3818
|
if (!result.ok) {
|
|
3582
|
-
return
|
|
3819
|
+
return finishRestTrace(
|
|
3820
|
+
req,
|
|
3821
|
+
trace.session,
|
|
3822
|
+
"failed",
|
|
3823
|
+
errorResponse("RUNTIME", result.error.message, 500)
|
|
3824
|
+
);
|
|
3583
3825
|
}
|
|
3584
3826
|
|
|
3585
|
-
return jsonResponse(result.value);
|
|
3827
|
+
return withRetrievalTraceHeader(jsonResponse(result.value), trace.session);
|
|
3586
3828
|
}
|
|
3587
3829
|
|
|
3588
3830
|
/**
|
|
@@ -3795,15 +4037,6 @@ export async function handleAsk(
|
|
|
3795
4037
|
return errorResponse("VALIDATION", "Query cannot be empty");
|
|
3796
4038
|
}
|
|
3797
4039
|
|
|
3798
|
-
// Check if answer generation is available
|
|
3799
|
-
if (!ctx.capabilities.answer) {
|
|
3800
|
-
return errorResponse(
|
|
3801
|
-
"UNAVAILABLE",
|
|
3802
|
-
"Answer generation not available. No answer model loaded.",
|
|
3803
|
-
503
|
|
3804
|
-
);
|
|
3805
|
-
}
|
|
3806
|
-
|
|
3807
4040
|
// Parse tag filters
|
|
3808
4041
|
let tagsAll: string[] | undefined;
|
|
3809
4042
|
let tagsAny: string[] | undefined;
|
|
@@ -3890,6 +4123,64 @@ export async function handleAsk(
|
|
|
3890
4123
|
const author = body.author?.trim() || undefined;
|
|
3891
4124
|
|
|
3892
4125
|
const limit = Math.min(body.limit ?? 5, 20);
|
|
4126
|
+
const askOptions = {
|
|
4127
|
+
limit,
|
|
4128
|
+
collection: body.collection,
|
|
4129
|
+
lang: body.lang,
|
|
4130
|
+
intent: body.intent?.trim() || undefined,
|
|
4131
|
+
noExpand: body.noExpand,
|
|
4132
|
+
noRerank: body.noRerank,
|
|
4133
|
+
candidateLimit:
|
|
4134
|
+
body.candidateLimit !== undefined
|
|
4135
|
+
? Math.min(body.candidateLimit, 100)
|
|
4136
|
+
: undefined,
|
|
4137
|
+
exclude,
|
|
4138
|
+
queryModes: normalizedQueryModes,
|
|
4139
|
+
tagsAll,
|
|
4140
|
+
tagsAny,
|
|
4141
|
+
since: body.since,
|
|
4142
|
+
until: body.until,
|
|
4143
|
+
categories,
|
|
4144
|
+
author,
|
|
4145
|
+
};
|
|
4146
|
+
const trace = await startRestTrace(ctx, {
|
|
4147
|
+
query: normalizedQuery,
|
|
4148
|
+
filters: retrievalTraceFilters(askOptions),
|
|
4149
|
+
pipeline: "ask",
|
|
4150
|
+
modelUris: [
|
|
4151
|
+
ctx.embedPort?.modelUri,
|
|
4152
|
+
ctx.expandPort?.modelUri,
|
|
4153
|
+
ctx.answerPort?.modelUri,
|
|
4154
|
+
ctx.rerankPort?.modelUri,
|
|
4155
|
+
].filter((value): value is string => Boolean(value)),
|
|
4156
|
+
});
|
|
4157
|
+
if (trace.error) return trace.error;
|
|
4158
|
+
|
|
4159
|
+
if (!ctx.capabilities.answer) {
|
|
4160
|
+
const unavailable = await trace.session?.recordCapability(
|
|
4161
|
+
"answer_generation",
|
|
4162
|
+
"unavailable",
|
|
4163
|
+
"model_unavailable"
|
|
4164
|
+
);
|
|
4165
|
+
if (unavailable && !unavailable.ok) {
|
|
4166
|
+
return finishRestTrace(
|
|
4167
|
+
req,
|
|
4168
|
+
trace.session,
|
|
4169
|
+
"failed",
|
|
4170
|
+
errorResponse("RUNTIME", unavailable.error.message, 500)
|
|
4171
|
+
);
|
|
4172
|
+
}
|
|
4173
|
+
return finishRestTrace(
|
|
4174
|
+
req,
|
|
4175
|
+
trace.session,
|
|
4176
|
+
"failed",
|
|
4177
|
+
errorResponse(
|
|
4178
|
+
"UNAVAILABLE",
|
|
4179
|
+
"Answer generation not available. No answer model loaded.",
|
|
4180
|
+
503
|
|
4181
|
+
)
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
3893
4184
|
|
|
3894
4185
|
// Run hybrid search first
|
|
3895
4186
|
const searchResult = await searchHybrid(
|
|
@@ -3903,29 +4194,18 @@ export async function handleAsk(
|
|
|
3903
4194
|
},
|
|
3904
4195
|
normalizedQuery,
|
|
3905
4196
|
{
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
lang: body.lang,
|
|
3909
|
-
intent: body.intent?.trim() || undefined,
|
|
3910
|
-
noExpand: body.noExpand,
|
|
3911
|
-
noRerank: body.noRerank,
|
|
3912
|
-
candidateLimit:
|
|
3913
|
-
body.candidateLimit !== undefined
|
|
3914
|
-
? Math.min(body.candidateLimit, 100)
|
|
3915
|
-
: undefined,
|
|
3916
|
-
exclude,
|
|
3917
|
-
queryModes: normalizedQueryModes,
|
|
3918
|
-
tagsAll,
|
|
3919
|
-
tagsAny,
|
|
3920
|
-
since: body.since,
|
|
3921
|
-
until: body.until,
|
|
3922
|
-
categories,
|
|
3923
|
-
author,
|
|
4197
|
+
...askOptions,
|
|
4198
|
+
traceSession: trace.session ?? undefined,
|
|
3924
4199
|
}
|
|
3925
4200
|
);
|
|
3926
4201
|
|
|
3927
4202
|
if (!searchResult.ok) {
|
|
3928
|
-
return
|
|
4203
|
+
return finishRestTrace(
|
|
4204
|
+
req,
|
|
4205
|
+
trace.session,
|
|
4206
|
+
"failed",
|
|
4207
|
+
errorResponse("RUNTIME", searchResult.error.message, 500)
|
|
4208
|
+
);
|
|
3929
4209
|
}
|
|
3930
4210
|
|
|
3931
4211
|
const results = searchResult.value.results;
|
|
@@ -3937,20 +4217,93 @@ export async function handleAsk(
|
|
|
3937
4217
|
let answerGenerated = false;
|
|
3938
4218
|
|
|
3939
4219
|
if (ctx.answerPort) {
|
|
3940
|
-
const
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
normalizedQuery,
|
|
3944
|
-
results,
|
|
3945
|
-
maxTokens
|
|
4220
|
+
const attempted = await trace.session?.recordCapability(
|
|
4221
|
+
"answer_generation",
|
|
4222
|
+
"attempted"
|
|
3946
4223
|
);
|
|
4224
|
+
if (attempted && !attempted.ok) {
|
|
4225
|
+
return finishRestTrace(
|
|
4226
|
+
req,
|
|
4227
|
+
trace.session,
|
|
4228
|
+
"failed",
|
|
4229
|
+
errorResponse("RUNTIME", attempted.error.message, 500)
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
const maxTokens = body.maxAnswerTokens ?? 512;
|
|
4233
|
+
let rawResult: Awaited<ReturnType<typeof generateGroundedAnswer>>;
|
|
4234
|
+
try {
|
|
4235
|
+
rawResult = await generateGroundedAnswer(
|
|
4236
|
+
{ genPort: ctx.answerPort, store: ctx.store },
|
|
4237
|
+
normalizedQuery,
|
|
4238
|
+
results,
|
|
4239
|
+
maxTokens
|
|
4240
|
+
);
|
|
4241
|
+
} catch (error) {
|
|
4242
|
+
await trace.session?.recordCapability(
|
|
4243
|
+
"answer_generation",
|
|
4244
|
+
"failed",
|
|
4245
|
+
"generation_failed"
|
|
4246
|
+
);
|
|
4247
|
+
return finishRestTrace(
|
|
4248
|
+
req,
|
|
4249
|
+
trace.session,
|
|
4250
|
+
"failed",
|
|
4251
|
+
errorResponse(
|
|
4252
|
+
"RUNTIME",
|
|
4253
|
+
error instanceof Error ? error.message : String(error),
|
|
4254
|
+
500
|
|
4255
|
+
)
|
|
4256
|
+
);
|
|
4257
|
+
}
|
|
3947
4258
|
|
|
3948
4259
|
if (rawResult) {
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4260
|
+
try {
|
|
4261
|
+
const processed = await processAnswerResultWithTrace(
|
|
4262
|
+
rawResult,
|
|
4263
|
+
trace.session ?? undefined
|
|
4264
|
+
);
|
|
4265
|
+
answer = processed.answer;
|
|
4266
|
+
citations = processed.citations;
|
|
4267
|
+
answerContext = processed.answerContext;
|
|
4268
|
+
answerGenerated = true;
|
|
4269
|
+
const used = await trace.session?.recordCapability(
|
|
4270
|
+
"answer_generation",
|
|
4271
|
+
"used"
|
|
4272
|
+
);
|
|
4273
|
+
if (used && !used.ok) {
|
|
4274
|
+
throw new Error(`Trace recording failed: ${used.error.message}`);
|
|
4275
|
+
}
|
|
4276
|
+
} catch (error) {
|
|
4277
|
+
await trace.session?.recordCapability(
|
|
4278
|
+
"answer_generation",
|
|
4279
|
+
"failed",
|
|
4280
|
+
"generation_failed"
|
|
4281
|
+
);
|
|
4282
|
+
return finishRestTrace(
|
|
4283
|
+
req,
|
|
4284
|
+
trace.session,
|
|
4285
|
+
"failed",
|
|
4286
|
+
errorResponse(
|
|
4287
|
+
"RUNTIME",
|
|
4288
|
+
error instanceof Error ? error.message : String(error),
|
|
4289
|
+
500
|
|
4290
|
+
)
|
|
4291
|
+
);
|
|
4292
|
+
}
|
|
4293
|
+
} else {
|
|
4294
|
+
const failed = await trace.session?.recordCapability(
|
|
4295
|
+
"answer_generation",
|
|
4296
|
+
"failed",
|
|
4297
|
+
"generation_failed"
|
|
4298
|
+
);
|
|
4299
|
+
if (failed && !failed.ok) {
|
|
4300
|
+
return finishRestTrace(
|
|
4301
|
+
req,
|
|
4302
|
+
trace.session,
|
|
4303
|
+
"failed",
|
|
4304
|
+
errorResponse("RUNTIME", failed.error.message, 500)
|
|
4305
|
+
);
|
|
4306
|
+
}
|
|
3954
4307
|
}
|
|
3955
4308
|
}
|
|
3956
4309
|
|
|
@@ -3975,7 +4328,13 @@ export async function handleAsk(
|
|
|
3975
4328
|
},
|
|
3976
4329
|
};
|
|
3977
4330
|
|
|
3978
|
-
|
|
4331
|
+
const complete = answerGenerated && (citations?.length ?? 0) > 0;
|
|
4332
|
+
return finishRestTrace(
|
|
4333
|
+
req,
|
|
4334
|
+
trace.session,
|
|
4335
|
+
complete ? "completed" : "partial",
|
|
4336
|
+
jsonResponse(askResult)
|
|
4337
|
+
);
|
|
3979
4338
|
}
|
|
3980
4339
|
|
|
3981
4340
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -4141,7 +4500,13 @@ export function handleModelStatus(): Response {
|
|
|
4141
4500
|
* Start downloading models for current preset.
|
|
4142
4501
|
* Returns immediately; poll /api/models/status for progress.
|
|
4143
4502
|
*/
|
|
4144
|
-
export function handleModelPull(
|
|
4503
|
+
export function handleModelPull(
|
|
4504
|
+
ctxHolder: ContextHolder,
|
|
4505
|
+
deps?: {
|
|
4506
|
+
modelsPullFn?: typeof modelsPull;
|
|
4507
|
+
reloadServerContextFn?: typeof reloadServerContext;
|
|
4508
|
+
}
|
|
4509
|
+
): Response {
|
|
4145
4510
|
// Don't start if already downloading
|
|
4146
4511
|
if (downloadState.active) {
|
|
4147
4512
|
return errorResponse("CONFLICT", "Download already in progress", 409);
|
|
@@ -4152,17 +4517,20 @@ export function handleModelPull(ctxHolder: ContextHolder): Response {
|
|
|
4152
4517
|
downloadState.active = true;
|
|
4153
4518
|
downloadState.startedAt = Date.now();
|
|
4154
4519
|
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4520
|
+
const operation = async (signal: AbortSignal): Promise<void> => {
|
|
4521
|
+
try {
|
|
4522
|
+
const result = await (deps?.modelsPullFn ?? modelsPull)({
|
|
4523
|
+
config: ctxHolder.config,
|
|
4524
|
+
all: true,
|
|
4525
|
+
signal,
|
|
4526
|
+
onProgress: (type, progress) => {
|
|
4527
|
+
if (signal.aborted) return;
|
|
4528
|
+
downloadState.currentType = type;
|
|
4529
|
+
downloadState.progress = progress;
|
|
4530
|
+
},
|
|
4531
|
+
});
|
|
4532
|
+
if (signal.aborted) return;
|
|
4533
|
+
|
|
4166
4534
|
// Track results
|
|
4167
4535
|
for (const r of result.results) {
|
|
4168
4536
|
if (r.ok) {
|
|
@@ -4180,35 +4548,39 @@ export function handleModelPull(ctxHolder: ContextHolder): Response {
|
|
|
4180
4548
|
// Reload context to pick up new models
|
|
4181
4549
|
console.log("Models downloaded, reloading context...");
|
|
4182
4550
|
try {
|
|
4183
|
-
ctxHolder.current = await
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
);
|
|
4551
|
+
ctxHolder.current = await (
|
|
4552
|
+
deps?.reloadServerContextFn ?? reloadServerContext
|
|
4553
|
+
)(ctxHolder.current, ctxHolder.config);
|
|
4554
|
+
if (signal.aborted) return;
|
|
4187
4555
|
console.log("Context reloaded");
|
|
4188
4556
|
if (ctxHolder.scheduler) {
|
|
4189
|
-
|
|
4190
|
-
console.error(
|
|
4191
|
-
"Failed to trigger embedding after model download:",
|
|
4192
|
-
error
|
|
4193
|
-
);
|
|
4194
|
-
});
|
|
4557
|
+
await ctxHolder.scheduler.triggerNow();
|
|
4195
4558
|
}
|
|
4196
4559
|
} catch (e) {
|
|
4197
4560
|
console.error("Failed to reload context:", e);
|
|
4198
4561
|
}
|
|
4199
|
-
|
|
4200
|
-
downloadState.active = false;
|
|
4201
|
-
downloadState.currentType = null;
|
|
4202
|
-
downloadState.progress = null;
|
|
4203
|
-
})
|
|
4204
|
-
.catch((e) => {
|
|
4562
|
+
} catch (e) {
|
|
4205
4563
|
console.error("Model download failed:", e);
|
|
4206
|
-
downloadState.active = false;
|
|
4207
4564
|
downloadState.failed.push({
|
|
4208
4565
|
type: downloadState.currentType ?? "embed",
|
|
4209
4566
|
error: e instanceof Error ? e.message : String(e),
|
|
4210
4567
|
});
|
|
4211
|
-
}
|
|
4568
|
+
} finally {
|
|
4569
|
+
downloadState.active = false;
|
|
4570
|
+
downloadState.currentType = null;
|
|
4571
|
+
downloadState.progress = null;
|
|
4572
|
+
}
|
|
4573
|
+
};
|
|
4574
|
+
|
|
4575
|
+
const started = ctxHolder.startBackgroundWork?.(operation) ?? false;
|
|
4576
|
+
if (!started) {
|
|
4577
|
+
resetDownloadState();
|
|
4578
|
+
return errorResponse(
|
|
4579
|
+
"UNAVAILABLE",
|
|
4580
|
+
"Resident runtime is shutting down",
|
|
4581
|
+
503
|
|
4582
|
+
);
|
|
4583
|
+
}
|
|
4212
4584
|
|
|
4213
4585
|
return jsonResponse({
|
|
4214
4586
|
started: true,
|
|
@@ -4224,8 +4596,8 @@ export function handleModelPull(ctxHolder: ContextHolder): Response {
|
|
|
4224
4596
|
* GET /api/jobs/:id
|
|
4225
4597
|
* Poll job status for async operations.
|
|
4226
4598
|
*/
|
|
4227
|
-
export function handleJob(jobId: string): Response {
|
|
4228
|
-
const status = getJobStatus(jobId);
|
|
4599
|
+
export function handleJob(jobId: string, jobManager?: JobManager): Response {
|
|
4600
|
+
const status = getJobStatus(jobId, jobManager);
|
|
4229
4601
|
if (!status) {
|
|
4230
4602
|
return errorResponse("NOT_FOUND", "Job not found or expired", 404);
|
|
4231
4603
|
}
|
|
@@ -4236,9 +4608,9 @@ export function handleJob(jobId: string): Response {
|
|
|
4236
4608
|
* GET /api/jobs/active
|
|
4237
4609
|
* Returns the current active job, or null when idle.
|
|
4238
4610
|
*/
|
|
4239
|
-
export function handleActiveJob(): Response {
|
|
4611
|
+
export function handleActiveJob(jobManager?: JobManager): Response {
|
|
4240
4612
|
return jsonResponse({
|
|
4241
|
-
activeJob: getActiveJob(),
|
|
4613
|
+
activeJob: getActiveJob(jobManager),
|
|
4242
4614
|
});
|
|
4243
4615
|
}
|
|
4244
4616
|
|
|
@@ -4575,7 +4947,7 @@ export async function routeApi(
|
|
|
4575
4947
|
}
|
|
4576
4948
|
|
|
4577
4949
|
if (path === "/api/doc") {
|
|
4578
|
-
return handleDoc(store, config, url);
|
|
4950
|
+
return handleDoc(store, config, url, req);
|
|
4579
4951
|
}
|
|
4580
4952
|
|
|
4581
4953
|
if (path === "/api/doc-asset") {
|
|
@@ -4583,7 +4955,25 @@ export async function routeApi(
|
|
|
4583
4955
|
}
|
|
4584
4956
|
|
|
4585
4957
|
if (path === "/api/search" && req.method === "POST") {
|
|
4586
|
-
return handleSearch(
|
|
4958
|
+
return handleSearch(
|
|
4959
|
+
{
|
|
4960
|
+
store,
|
|
4961
|
+
config,
|
|
4962
|
+
indexName: "default",
|
|
4963
|
+
vectorIndex: null,
|
|
4964
|
+
embedPort: null,
|
|
4965
|
+
expandPort: null,
|
|
4966
|
+
answerPort: null,
|
|
4967
|
+
rerankPort: null,
|
|
4968
|
+
capabilities: {
|
|
4969
|
+
bm25: true,
|
|
4970
|
+
vector: false,
|
|
4971
|
+
hybrid: false,
|
|
4972
|
+
answer: false,
|
|
4973
|
+
},
|
|
4974
|
+
},
|
|
4975
|
+
req
|
|
4976
|
+
);
|
|
4587
4977
|
}
|
|
4588
4978
|
|
|
4589
4979
|
// Unknown API route
|