@pellux/goodvibes-daemon-sdk 0.33.37 → 0.33.38
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/dist/api-router.d.ts +20 -0
- package/dist/api-router.d.ts.map +1 -1
- package/dist/api-router.js +20 -0
- package/dist/automation.js +2 -2
- package/dist/context.d.ts +51 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/error-response.d.ts +32 -0
- package/dist/error-response.d.ts.map +1 -1
- package/dist/error-response.js +28 -0
- package/dist/http-policy.d.ts +39 -0
- package/dist/http-policy.d.ts.map +1 -1
- package/dist/http-policy.js +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/integration-route-types.d.ts +1 -0
- package/dist/integration-route-types.d.ts.map +1 -1
- package/dist/integration-routes.d.ts.map +1 -1
- package/dist/integration-routes.js +27 -2
- package/dist/knowledge-routes.d.ts.map +1 -1
- package/dist/knowledge-routes.js +125 -18
- package/dist/operator.js +2 -0
- package/dist/pagination.d.ts +110 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +136 -0
- package/dist/route-helpers.d.ts +123 -0
- package/dist/route-helpers.d.ts.map +1 -1
- package/dist/route-helpers.js +112 -1
- package/dist/runtime-automation-routes.d.ts.map +1 -1
- package/dist/runtime-automation-routes.js +58 -3
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration-route-types.d.ts","sourceRoot":"","sources":["../src/integration-route-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAEnC,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,IAAI;QACV,QAAQ,CAAC,UAAU,EAAE;YACnB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SACjD,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,IAAI,OAAO,CAAC;IACvB,kBAAkB,IAAI,OAAO,CAAC;IAC9B,eAAe,IAAI,OAAO,CAAC;IAC3B,qBAAqB,IAAI,OAAO,CAAC;IACjC,wBAAwB,IAAI,OAAO,CAAC;IACpC,mBAAmB,IAAI,OAAO,CAAC;IAC/B,gBAAgB,IAAI,OAAO,CAAC;IAC5B,iBAAiB,IAAI,OAAO,CAAC;IAC7B,iBAAiB,IAAI,OAAO,CAAC;IAC7B,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,mBAAmB,IAAI,OAAO,CAAC;IAC/B,yBAAyB,IAAI,OAAO,CAAC;IACrC,qBAAqB,IAAI,OAAO,CAAC;IACjC,mBAAmB,IAAI,OAAO,CAAC;IAC/B,uBAAuB,IAAI,OAAO,CAAC;IACnC,oBAAoB,IAAI,OAAO,CAAC;IAChC,UAAU,IAAI,SAAS,OAAO,EAAE,CAAC;IACjC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5D,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtG,eAAe,IAAI,2BAA2B,GAAG,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,0BAA0B;IACzC,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,kCAAkC;IACjD,aAAa,IAAI,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IAC7C,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACzD,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,WAAW,IAAI,OAAO,CAAC;IACvB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"integration-route-types.d.ts","sourceRoot":"","sources":["../src/integration-route-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration-routes.d.ts","sourceRoot":"","sources":["../src/integration-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,KAAK,EAAE,6BAA6B,EAAoD,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"integration-routes.d.ts","sourceRoot":"","sources":["../src/integration-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,KAAK,EAAE,6BAA6B,EAAoD,MAAM,8BAA8B,CAAC;AA8DpI,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,6BAA6B,GACrC,8BAA8B,CAqJhC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsonErrorResponse } from './error-response.js';
|
|
2
|
-
import { createRouteBodySchema, createRouteBodySchemaRegistry, readOptionalStringField, readStringArrayField, } from './route-helpers.js';
|
|
2
|
+
import { createRouteBodySchema, createRouteBodySchemaRegistry, readBoundedPositiveInteger, readOptionalStringField, readStringArrayField, } from './route-helpers.js';
|
|
3
3
|
const MAX_LOCAL_AUTH_ROLES = 32;
|
|
4
4
|
const integrationBodySchemas = createRouteBodySchemaRegistry({
|
|
5
5
|
embeddingDefault: createRouteBodySchema('POST /api/memory/embedding/default', (body) => {
|
|
@@ -43,7 +43,7 @@ export function createDaemonIntegrationRouteHandlers(context) {
|
|
|
43
43
|
getIntegrationSession: () => withHelpers(context.integrationHelpers, (helpers) => Response.json(helpers.getSessionSnapshot())),
|
|
44
44
|
getIntegrationTasks: () => withHelpers(context.integrationHelpers, (helpers) => Response.json(helpers.getTaskSnapshot())),
|
|
45
45
|
getIntegrationAutomation: () => withHelpers(context.integrationHelpers, (helpers) => Response.json(helpers.getAutomationSnapshot())),
|
|
46
|
-
getIntegrationSessions: () =>
|
|
46
|
+
getIntegrationSessions: (url) => handleGetIntegrationSessions(context.integrationHelpers, url),
|
|
47
47
|
getDeliveries: () => withHelpers(context.integrationHelpers, (helpers) => Response.json(helpers.getDeliverySnapshot())),
|
|
48
48
|
getDelivery: (deliveryId) => {
|
|
49
49
|
const runtimeStore = context.integrationHelpers?.getRuntimeStore() ?? null;
|
|
@@ -84,6 +84,15 @@ export function createDaemonIntegrationRouteHandlers(context) {
|
|
|
84
84
|
getIntelligence: () => withHelpers(context.integrationHelpers, (helpers) => Response.json(helpers.getIntelligenceSnapshot())),
|
|
85
85
|
getMemoryDoctor: async () => Response.json(await context.memoryRegistry.doctor()),
|
|
86
86
|
getMemoryVectorStats: () => Response.json({ vector: context.memoryRegistry.vectorStats() }),
|
|
87
|
+
getMemoryReviewQueue: (url) => {
|
|
88
|
+
const limit = readBoundedPositiveInteger(url.searchParams.get('limit'), 10, 1_000);
|
|
89
|
+
const rawScope = url.searchParams.get('scope');
|
|
90
|
+
if (rawScope !== null && rawScope !== 'session' && rawScope !== 'project' && rawScope !== 'team') {
|
|
91
|
+
return jsonErrorResponse({ error: `Invalid scope: ${rawScope}. Allowed: session, project, team` }, { status: 400 });
|
|
92
|
+
}
|
|
93
|
+
const scope = rawScope ?? undefined;
|
|
94
|
+
return Response.json({ records: context.memoryRegistry.reviewQueue(limit, scope) });
|
|
95
|
+
},
|
|
87
96
|
postMemoryVectorRebuild: async (req) => {
|
|
88
97
|
const admin = context.requireAdmin(req);
|
|
89
98
|
if (admin)
|
|
@@ -204,3 +213,19 @@ function withHelpers(helpers, run) {
|
|
|
204
213
|
}
|
|
205
214
|
return run(helpers);
|
|
206
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Handle GET /api/sessions.
|
|
218
|
+
*
|
|
219
|
+
* Returns the session broker snapshot from the integration helper service.
|
|
220
|
+
* Pagination (`?limit=` / `?cursor=`) is not supported on this endpoint —
|
|
221
|
+
* `IntegrationHelperServiceLike` is a consumer-supplied structural interface
|
|
222
|
+
* whose concrete implementation lives outside daemon-sdk and cannot be
|
|
223
|
+
* range-queried here. Requests containing pagination params receive the
|
|
224
|
+
* same snapshot response as non-paginated requests (backward compatible).
|
|
225
|
+
*/
|
|
226
|
+
function handleGetIntegrationSessions(helpers, _url) {
|
|
227
|
+
if (!helpers) {
|
|
228
|
+
return jsonErrorResponse({ error: 'Integration helper service unavailable' }, { status: 503 });
|
|
229
|
+
}
|
|
230
|
+
return Response.json(helpers.getSessionBrokerSnapshot());
|
|
231
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-routes.d.ts","sourceRoot":"","sources":["../src/knowledge-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"knowledge-routes.d.ts","sourceRoot":"","sources":["../src/knowledge-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AAoBjE,OAAO,KAAK,EAEV,2BAA2B,EAM5B,MAAM,4BAA4B,CAAC;AAwCpC,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,2BAA2B,GACnC,4BAA4B,CA+J9B"}
|
package/dist/knowledge-routes.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildMissingScopeBody, resolvePrivateHostFetchOptions, } from './http-policy.js';
|
|
2
2
|
import { GoodVibesSdkError, DaemonErrorCategory } from '@pellux/goodvibes-errors';
|
|
3
3
|
import { jsonErrorResponse } from './error-response.js';
|
|
4
|
+
import { paginateItems, hasPaginationParams } from './pagination.js';
|
|
4
5
|
import { createArtifactFromUploadRequest, isArtifactUploadRequest } from './artifact-upload.js';
|
|
5
6
|
import { createRouteBodySchema, createRouteBodySchemaRegistry, readBoundedBodyInteger, readBoundedPositiveInteger, readOptionalBoundedInteger, readOptionalStringField, readStringArrayField, } from './route-helpers.js';
|
|
6
7
|
import { createDaemonKnowledgeRefinementRouteHandlers } from './knowledge-refinement-routes.js';
|
|
@@ -8,18 +9,8 @@ const MAX_KNOWLEDGE_INGEST_TAGS = 64;
|
|
|
8
9
|
export function createDaemonKnowledgeRouteHandlers(context) {
|
|
9
10
|
return {
|
|
10
11
|
getKnowledgeStatus: async (url) => Response.json(await context.knowledgeService.getStatus(readKnowledgeSpaceQuery(url))),
|
|
11
|
-
getKnowledgeSources: async (url) =>
|
|
12
|
-
|
|
13
|
-
limit: readLimit(url, 100),
|
|
14
|
-
...readKnowledgeSpaceQuery(url),
|
|
15
|
-
}).items,
|
|
16
|
-
}),
|
|
17
|
-
getKnowledgeNodes: async (url) => Response.json({
|
|
18
|
-
nodes: context.knowledgeService.queryNodes({
|
|
19
|
-
limit: readLimit(url, 100),
|
|
20
|
-
...readKnowledgeSpaceQuery(url),
|
|
21
|
-
}).items,
|
|
22
|
-
}),
|
|
12
|
+
getKnowledgeSources: async (url) => handleGetKnowledgeSources(context, url),
|
|
13
|
+
getKnowledgeNodes: async (url) => handleGetKnowledgeNodes(context, url),
|
|
23
14
|
getKnowledgeIssues: async (url) => Response.json({
|
|
24
15
|
issues: context.knowledgeService.queryIssues({
|
|
25
16
|
limit: readLimit(url, 100),
|
|
@@ -682,8 +673,15 @@ async function handleKnowledgeReviewIssue(context, id, request) {
|
|
|
682
673
|
}
|
|
683
674
|
catch (error) {
|
|
684
675
|
// map status from error code, not English-prefix match.
|
|
685
|
-
const isNotFound = error instanceof Error && (
|
|
686
|
-
|
|
676
|
+
const isNotFound = error instanceof Error && (
|
|
677
|
+
// Domain-specific not-found codes are always explicit (never auto-inferred).
|
|
678
|
+
error.code === 'KNOWLEDGE_ISSUE_NOT_FOUND'
|
|
679
|
+
// Bare NOT_FOUND only maps to 404 when the error carries an actual HTTP 404
|
|
680
|
+
// status, preventing auto-inferred NOT_FOUND from category-only errors
|
|
681
|
+
// (which now always have code='NOT_FOUND' via inferCodeFromCategory)
|
|
682
|
+
// from silently becoming 404 responses.
|
|
683
|
+
|| (error.code === 'NOT_FOUND'
|
|
684
|
+
&& error.status === 404));
|
|
687
685
|
return jsonErrorResponse(error, { status: isNotFound ? 404 : 400 });
|
|
688
686
|
}
|
|
689
687
|
}
|
|
@@ -710,8 +708,15 @@ async function handleKnowledgeDecideCandidate(context, id, request) {
|
|
|
710
708
|
}
|
|
711
709
|
catch (error) {
|
|
712
710
|
// map status from error code, not English-prefix match.
|
|
713
|
-
const isNotFound = error instanceof Error && (
|
|
714
|
-
|
|
711
|
+
const isNotFound = error instanceof Error && (
|
|
712
|
+
// Domain-specific not-found codes are always explicit (never auto-inferred).
|
|
713
|
+
error.code === 'KNOWLEDGE_CANDIDATE_NOT_FOUND'
|
|
714
|
+
// Bare NOT_FOUND only maps to 404 when the error carries an actual HTTP 404
|
|
715
|
+
// status, preventing auto-inferred NOT_FOUND from category-only errors
|
|
716
|
+
// (which now always have code='NOT_FOUND' via inferCodeFromCategory)
|
|
717
|
+
// from silently becoming 404 responses.
|
|
718
|
+
|| (error.code === 'NOT_FOUND'
|
|
719
|
+
&& error.status === 404));
|
|
715
720
|
return jsonErrorResponse(error, { status: isNotFound ? 404 : 400 });
|
|
716
721
|
}
|
|
717
722
|
}
|
|
@@ -732,8 +737,15 @@ async function handleKnowledgeRunJob(context, jobId, request) {
|
|
|
732
737
|
}
|
|
733
738
|
catch (error) {
|
|
734
739
|
// map status from error code, not English-prefix match.
|
|
735
|
-
const isNotFound = error instanceof Error && (
|
|
736
|
-
|
|
740
|
+
const isNotFound = error instanceof Error && (
|
|
741
|
+
// Domain-specific not-found codes are always explicit (never auto-inferred).
|
|
742
|
+
error.code === 'KNOWLEDGE_JOB_NOT_FOUND'
|
|
743
|
+
// Bare NOT_FOUND only maps to 404 when the error carries an actual HTTP 404
|
|
744
|
+
// status, preventing auto-inferred NOT_FOUND from category-only errors
|
|
745
|
+
// (which now always have code='NOT_FOUND' via inferCodeFromCategory)
|
|
746
|
+
// from silently becoming 404 responses.
|
|
747
|
+
|| (error.code === 'NOT_FOUND'
|
|
748
|
+
&& error.status === 404));
|
|
737
749
|
return jsonErrorResponse(error, { status: isNotFound ? 404 : 400 });
|
|
738
750
|
}
|
|
739
751
|
}
|
|
@@ -815,3 +827,98 @@ async function handleKnowledgeMaterializeProjection(context, request) {
|
|
|
815
827
|
return jsonErrorResponse(error, { status: 400 });
|
|
816
828
|
}
|
|
817
829
|
}
|
|
830
|
+
/** Extract the `id` from an opaque knowledge item (sources/nodes are `unknown` at the SDK boundary). */
|
|
831
|
+
function extractKnowledgeItemId(item) {
|
|
832
|
+
if (typeof item === 'object' && item !== null && 'id' in item) {
|
|
833
|
+
const id = item.id;
|
|
834
|
+
return typeof id === 'string' ? id : undefined;
|
|
835
|
+
}
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Extract the `updatedAt` timestamp from an opaque knowledge item for cursor-based
|
|
840
|
+
* pagination recovery.
|
|
841
|
+
*
|
|
842
|
+
* Knowledge source and node records carry a `readonly updatedAt: number` field
|
|
843
|
+
* (see `KnowledgeSourceRecord` / `KnowledgeNodeRecord` in
|
|
844
|
+
* `platform/knowledge/types.ts`). The production store sorts both collections
|
|
845
|
+
* by `byUpdatedAtDesc` (newest-first by updatedAt), so the cursor's position
|
|
846
|
+
* key MUST encode `updatedAt` — not `createdAt` — to land at the correct
|
|
847
|
+
* insertion point after a mid-walk deletion.
|
|
848
|
+
*
|
|
849
|
+
* Semantic note: `updatedAt` mutates when an item is modified. A mid-walk
|
|
850
|
+
* update moves the item to the front of the list (it gains a new updatedAt
|
|
851
|
+
* value) and its old position disappears — identical in effect to a deletion.
|
|
852
|
+
* The insertion-point scan (findIndex(ts < cursor.value)) handles this
|
|
853
|
+
* correctly: the old position is gone, so the scan skips past it naturally.
|
|
854
|
+
*
|
|
855
|
+
* Returns `undefined` when the field is absent or not a number (tolerant of
|
|
856
|
+
* missing values at the SDK boundary).
|
|
857
|
+
*/
|
|
858
|
+
function extractKnowledgeItemUpdatedAt(item) {
|
|
859
|
+
if (typeof item === 'object' && item !== null && 'updatedAt' in item) {
|
|
860
|
+
const ts = item.updatedAt;
|
|
861
|
+
return typeof ts === 'number' ? ts : undefined;
|
|
862
|
+
}
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Handle GET /api/knowledge/sources.
|
|
867
|
+
*
|
|
868
|
+
* Without pagination params (`?limit=` / `?cursor=`): returns `{ sources: [...] }` (backward compat).
|
|
869
|
+
* With pagination params: returns `PaginatedResponse`-shaped `{ items, nextCursor, hasMore }`.
|
|
870
|
+
*
|
|
871
|
+
* ### Fetch cap
|
|
872
|
+
* The knowledge store accepts a `limit` argument but does not support
|
|
873
|
+
* cursor/offset range queries — the full in-memory collection must be loaded to
|
|
874
|
+
* perform cursor-based slicing. We cap the load at 5 000 items. If the store
|
|
875
|
+
* grows beyond this, source-level pagination on the store API should be added
|
|
876
|
+
* and this cap raised accordingly.
|
|
877
|
+
*/
|
|
878
|
+
function handleGetKnowledgeSources(context, url) {
|
|
879
|
+
const spaceQuery = readKnowledgeSpaceQuery(url);
|
|
880
|
+
if (!hasPaginationParams(url)) {
|
|
881
|
+
const { items: sources } = context.knowledgeService.querySources({ limit: readLimit(url, 100), ...spaceQuery });
|
|
882
|
+
return Response.json({ sources });
|
|
883
|
+
}
|
|
884
|
+
const limit = readLimit(url, 100);
|
|
885
|
+
const rawCursor = url.searchParams.get('cursor');
|
|
886
|
+
// Load the full in-memory collection (capped at 5_000 — see JSDoc note above).
|
|
887
|
+
const { items: allItems } = context.knowledgeService.querySources({ limit: 5_000, ...spaceQuery });
|
|
888
|
+
const items = allItems;
|
|
889
|
+
const result = paginateItems(items, limit, rawCursor, (item) => extractKnowledgeItemId(item) ?? '',
|
|
890
|
+
// NOTE: cursor position key encodes updatedAt (matches byUpdatedAtDesc store order).
|
|
891
|
+
extractKnowledgeItemUpdatedAt, { descending: true });
|
|
892
|
+
if ('error' in result) {
|
|
893
|
+
return jsonErrorResponse({ error: result.error }, { status: 400 });
|
|
894
|
+
}
|
|
895
|
+
return Response.json(result);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Handle GET /api/knowledge/nodes.
|
|
899
|
+
*
|
|
900
|
+
* Without pagination params (`?limit=` / `?cursor=`): returns `{ nodes: [...] }` (backward compat).
|
|
901
|
+
* With pagination params: returns `PaginatedResponse`-shaped `{ items, nextCursor, hasMore }`.
|
|
902
|
+
*
|
|
903
|
+
* ### Fetch cap
|
|
904
|
+
* Same limitation as `handleGetKnowledgeSources` — capped at 5 000 items.
|
|
905
|
+
*/
|
|
906
|
+
function handleGetKnowledgeNodes(context, url) {
|
|
907
|
+
const spaceQuery = readKnowledgeSpaceQuery(url);
|
|
908
|
+
if (!hasPaginationParams(url)) {
|
|
909
|
+
const { items: nodes } = context.knowledgeService.queryNodes({ limit: readLimit(url, 100), ...spaceQuery });
|
|
910
|
+
return Response.json({ nodes });
|
|
911
|
+
}
|
|
912
|
+
const limit = readLimit(url, 100);
|
|
913
|
+
const rawCursor = url.searchParams.get('cursor');
|
|
914
|
+
// Load the full in-memory collection (capped at 5_000 — see JSDoc note above).
|
|
915
|
+
const { items: allItems } = context.knowledgeService.queryNodes({ limit: 5_000, ...spaceQuery });
|
|
916
|
+
const items = allItems;
|
|
917
|
+
const result = paginateItems(items, limit, rawCursor, (item) => extractKnowledgeItemId(item) ?? '',
|
|
918
|
+
// NOTE: cursor position key encodes updatedAt (matches byUpdatedAtDesc store order).
|
|
919
|
+
extractKnowledgeItemUpdatedAt, { descending: true });
|
|
920
|
+
if ('error' in result) {
|
|
921
|
+
return jsonErrorResponse({ error: result.error }, { status: 400 });
|
|
922
|
+
}
|
|
923
|
+
return Response.json(result);
|
|
924
|
+
}
|
package/dist/operator.js
CHANGED
|
@@ -277,6 +277,8 @@ async function dispatchOperatorRoutesInner(req, handlers) {
|
|
|
277
277
|
return handlers.postAutomationHeartbeat(req);
|
|
278
278
|
if (pathname === '/api/memory/doctor' && method === 'GET')
|
|
279
279
|
return handlers.getMemoryDoctor();
|
|
280
|
+
if (pathname === '/api/memory/review-queue' && method === 'GET')
|
|
281
|
+
return handlers.getMemoryReviewQueue(url);
|
|
280
282
|
if (pathname === '/api/memory/vector' && method === 'GET')
|
|
281
283
|
return handlers.getMemoryVectorStats();
|
|
282
284
|
if (pathname === '/api/memory/vector/rebuild' && method === 'POST')
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor-based pagination utilities for daemon route handlers.
|
|
3
|
+
*
|
|
4
|
+
* Cursor format: opaque base64url-encoded JSON `{id: string, createdAt: number}`
|
|
5
|
+
* encoding the stable sort position. Cursors remain valid across inserts
|
|
6
|
+
* (no offset-based pagination).
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Paginated response envelope. Returned by list endpoints that support
|
|
12
|
+
* cursor-based pagination.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export interface PaginatedResponse<T> {
|
|
17
|
+
/** The page of items. */
|
|
18
|
+
readonly items: readonly T[];
|
|
19
|
+
/**
|
|
20
|
+
* Opaque cursor to pass as `?cursor=` to retrieve the next page.
|
|
21
|
+
* Absent when this is the last page.
|
|
22
|
+
*/
|
|
23
|
+
readonly nextCursor?: string | undefined;
|
|
24
|
+
/** Whether there are more items beyond this page. */
|
|
25
|
+
readonly hasMore: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Internal cursor payload. @internal */
|
|
28
|
+
interface CursorPayload {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly createdAt?: number | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Encode a cursor from a stable sort position.
|
|
34
|
+
*
|
|
35
|
+
* @param id - The item id used as the tiebreak.
|
|
36
|
+
* @param createdAt - Optional creation timestamp for secondary sort key.
|
|
37
|
+
* @returns Opaque base64url cursor string.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export declare function encodeCursor(id: string, createdAt?: number): string;
|
|
41
|
+
/**
|
|
42
|
+
* Decode an opaque cursor string.
|
|
43
|
+
*
|
|
44
|
+
* @param raw - The cursor string from `?cursor=`.
|
|
45
|
+
* @returns The decoded payload, or `null` if the cursor is invalid.
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export declare function decodeCursor(raw: string): CursorPayload | null;
|
|
49
|
+
/**
|
|
50
|
+
* Options for `paginateItems`.
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
export interface PaginateItemsOptions {
|
|
54
|
+
/**
|
|
55
|
+
* Set to `true` when items are sorted **descending** by `createdAt` (newest
|
|
56
|
+
* first). This inverts the insertion-point comparison used during
|
|
57
|
+
* deleted-cursor recovery so that the correct position is found regardless
|
|
58
|
+
* of sort direction. Defaults to `false` (ascending / oldest-first).
|
|
59
|
+
*/
|
|
60
|
+
readonly descending?: boolean | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Apply cursor-based pagination to an already-sorted array of items.
|
|
64
|
+
*
|
|
65
|
+
* Items must be sorted in a stable order. The cursor encodes the last-seen
|
|
66
|
+
* item's `id` (and optionally `createdAt`). Items after the cursor position
|
|
67
|
+
* are returned.
|
|
68
|
+
*
|
|
69
|
+
* ### Deleted-cursor recovery (m3/m4)
|
|
70
|
+
* When the cursor's `id` is not found in the current page (i.e. the item was
|
|
71
|
+
* deleted mid-walk), the implementation falls back to the `createdAt`
|
|
72
|
+
* timestamp in the cursor to locate the insertion point.
|
|
73
|
+
*
|
|
74
|
+
* - **Ascending** (default, `options.descending` falsy): finds the first item
|
|
75
|
+
* whose own `createdAt` is **strictly greater** than the cursor's
|
|
76
|
+
* `createdAt`, then starts the page from that position.
|
|
77
|
+
* - **Descending** (`options.descending: true`): finds the first item whose
|
|
78
|
+
* own `createdAt` is **strictly less** than the cursor's `createdAt`, which
|
|
79
|
+
* is the correct forward position in a newest-first sequence.
|
|
80
|
+
*
|
|
81
|
+
* Without `createdAt` available in the cursor, recovery falls back to the
|
|
82
|
+
* first position (index 0) to avoid returning an empty page.
|
|
83
|
+
*
|
|
84
|
+
* Callers that need stable recovery across deletions should pass `getCreatedAt`
|
|
85
|
+
* and ensure cursors are encoded with `createdAt`. Callers whose store sorts
|
|
86
|
+
* descending must also pass `options.descending: true`.
|
|
87
|
+
*
|
|
88
|
+
* @param items - Sorted array of all items.
|
|
89
|
+
* @param limit - Maximum number of items to return (already clamped by caller).
|
|
90
|
+
* @param rawCursor - Raw cursor string from the request (`?cursor=`).
|
|
91
|
+
* @param getId - Extract the item's id.
|
|
92
|
+
* @param getCreatedAt - Optional: extract the item's creation timestamp (ms since epoch).
|
|
93
|
+
* @param options - Optional pagination options (e.g. sort direction).
|
|
94
|
+
* @returns `PaginatedResponse<T>` or an error string for invalid cursors.
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
export declare function paginateItems<T>(items: readonly T[], limit: number, rawCursor: string | null, getId: (item: T) => string, getCreatedAt?: (item: T) => number | undefined, options?: PaginateItemsOptions): PaginatedResponse<T> | {
|
|
98
|
+
readonly error: string;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Check whether the request URL contains pagination parameters.
|
|
102
|
+
*
|
|
103
|
+
* Used to distinguish backward-compatible (no params → plain array)
|
|
104
|
+
* from paginated (with params → `PaginatedResponse<T>`) calls.
|
|
105
|
+
*
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
export declare function hasPaginationParams(url: URL): boolean;
|
|
109
|
+
export {};
|
|
110
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,yBAAyB;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,yCAAyC;AACzC,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAKnE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAoB9D;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAC1B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,EAC9C,OAAO,CAAC,EAAE,oBAAoB,GAC7B,iBAAiB,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA8CnD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAErD"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor-based pagination utilities for daemon route handlers.
|
|
3
|
+
*
|
|
4
|
+
* Cursor format: opaque base64url-encoded JSON `{id: string, createdAt: number}`
|
|
5
|
+
* encoding the stable sort position. Cursors remain valid across inserts
|
|
6
|
+
* (no offset-based pagination).
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Encode a cursor from a stable sort position.
|
|
12
|
+
*
|
|
13
|
+
* @param id - The item id used as the tiebreak.
|
|
14
|
+
* @param createdAt - Optional creation timestamp for secondary sort key.
|
|
15
|
+
* @returns Opaque base64url cursor string.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export function encodeCursor(id, createdAt) {
|
|
19
|
+
const payload = createdAt !== undefined ? { id, createdAt } : { id };
|
|
20
|
+
const json = JSON.stringify(payload);
|
|
21
|
+
// base64url encode (no padding, URL-safe characters)
|
|
22
|
+
return Buffer.from(json, 'utf8').toString('base64url');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Decode an opaque cursor string.
|
|
26
|
+
*
|
|
27
|
+
* @param raw - The cursor string from `?cursor=`.
|
|
28
|
+
* @returns The decoded payload, or `null` if the cursor is invalid.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export function decodeCursor(raw) {
|
|
32
|
+
try {
|
|
33
|
+
const json = Buffer.from(raw, 'base64url').toString('utf8');
|
|
34
|
+
const parsed = JSON.parse(json);
|
|
35
|
+
if (typeof parsed === 'object' &&
|
|
36
|
+
parsed !== null &&
|
|
37
|
+
'id' in parsed &&
|
|
38
|
+
typeof parsed.id === 'string') {
|
|
39
|
+
const p = parsed;
|
|
40
|
+
return {
|
|
41
|
+
id: p.id,
|
|
42
|
+
createdAt: typeof p.createdAt === 'number' ? p.createdAt : undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Apply cursor-based pagination to an already-sorted array of items.
|
|
53
|
+
*
|
|
54
|
+
* Items must be sorted in a stable order. The cursor encodes the last-seen
|
|
55
|
+
* item's `id` (and optionally `createdAt`). Items after the cursor position
|
|
56
|
+
* are returned.
|
|
57
|
+
*
|
|
58
|
+
* ### Deleted-cursor recovery (m3/m4)
|
|
59
|
+
* When the cursor's `id` is not found in the current page (i.e. the item was
|
|
60
|
+
* deleted mid-walk), the implementation falls back to the `createdAt`
|
|
61
|
+
* timestamp in the cursor to locate the insertion point.
|
|
62
|
+
*
|
|
63
|
+
* - **Ascending** (default, `options.descending` falsy): finds the first item
|
|
64
|
+
* whose own `createdAt` is **strictly greater** than the cursor's
|
|
65
|
+
* `createdAt`, then starts the page from that position.
|
|
66
|
+
* - **Descending** (`options.descending: true`): finds the first item whose
|
|
67
|
+
* own `createdAt` is **strictly less** than the cursor's `createdAt`, which
|
|
68
|
+
* is the correct forward position in a newest-first sequence.
|
|
69
|
+
*
|
|
70
|
+
* Without `createdAt` available in the cursor, recovery falls back to the
|
|
71
|
+
* first position (index 0) to avoid returning an empty page.
|
|
72
|
+
*
|
|
73
|
+
* Callers that need stable recovery across deletions should pass `getCreatedAt`
|
|
74
|
+
* and ensure cursors are encoded with `createdAt`. Callers whose store sorts
|
|
75
|
+
* descending must also pass `options.descending: true`.
|
|
76
|
+
*
|
|
77
|
+
* @param items - Sorted array of all items.
|
|
78
|
+
* @param limit - Maximum number of items to return (already clamped by caller).
|
|
79
|
+
* @param rawCursor - Raw cursor string from the request (`?cursor=`).
|
|
80
|
+
* @param getId - Extract the item's id.
|
|
81
|
+
* @param getCreatedAt - Optional: extract the item's creation timestamp (ms since epoch).
|
|
82
|
+
* @param options - Optional pagination options (e.g. sort direction).
|
|
83
|
+
* @returns `PaginatedResponse<T>` or an error string for invalid cursors.
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export function paginateItems(items, limit, rawCursor, getId, getCreatedAt, options) {
|
|
87
|
+
let startIndex = 0;
|
|
88
|
+
if (rawCursor !== null) {
|
|
89
|
+
const decoded = decodeCursor(rawCursor);
|
|
90
|
+
if (!decoded) {
|
|
91
|
+
return { error: `Invalid cursor: ${rawCursor}` };
|
|
92
|
+
}
|
|
93
|
+
const afterIndex = items.findIndex((item) => getId(item) === decoded.id);
|
|
94
|
+
if (afterIndex === -1) {
|
|
95
|
+
// Cursor id not found — item was deleted mid-walk.
|
|
96
|
+
// Use createdAt from the cursor to locate the insertion point.
|
|
97
|
+
// For ascending sequences: first item with createdAt > cursor's createdAt.
|
|
98
|
+
// For descending sequences: first item with createdAt < cursor's createdAt.
|
|
99
|
+
// Without createdAt, restart from index 0.
|
|
100
|
+
if (decoded.createdAt !== undefined && getCreatedAt !== undefined) {
|
|
101
|
+
const descending = options?.descending === true;
|
|
102
|
+
const insertionIndex = items.findIndex((item) => {
|
|
103
|
+
const ts = getCreatedAt(item);
|
|
104
|
+
return descending
|
|
105
|
+
? ts !== undefined && ts < decoded.createdAt
|
|
106
|
+
: ts !== undefined && ts > decoded.createdAt;
|
|
107
|
+
});
|
|
108
|
+
startIndex = insertionIndex === -1 ? items.length : insertionIndex;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
startIndex = 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
startIndex = afterIndex + 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const page = items.slice(startIndex, startIndex + limit);
|
|
119
|
+
const hasMore = startIndex + limit < items.length;
|
|
120
|
+
const lastItem = page[page.length - 1];
|
|
121
|
+
const nextCursor = hasMore && lastItem !== undefined
|
|
122
|
+
? encodeCursor(getId(lastItem), getCreatedAt !== undefined ? getCreatedAt(lastItem) : undefined)
|
|
123
|
+
: undefined;
|
|
124
|
+
return { items: page, nextCursor, hasMore };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check whether the request URL contains pagination parameters.
|
|
128
|
+
*
|
|
129
|
+
* Used to distinguish backward-compatible (no params → plain array)
|
|
130
|
+
* from paginated (with params → `PaginatedResponse<T>`) calls.
|
|
131
|
+
*
|
|
132
|
+
* @public
|
|
133
|
+
*/
|
|
134
|
+
export function hasPaginationParams(url) {
|
|
135
|
+
return url.searchParams.has('limit') || url.searchParams.has('cursor');
|
|
136
|
+
}
|