@remnic/core 9.25.6 → 9.26.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/dist/access-admin-ops-surface.d.ts +2 -2
- package/dist/access-admin-ops-surface.js +41 -41
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +2 -2
- package/dist/access-authorization-probe.js +42 -42
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +41 -41
- package/dist/access-cli.js +98 -97
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http-lcm-compaction.d.ts +130 -0
- package/dist/access-http-lcm-compaction.js +10 -0
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +53 -52
- package/dist/access-identity-continuity-surface.d.ts +1 -1
- package/dist/access-identity-continuity-surface.js +41 -41
- package/dist/access-lcm-surface.d.ts +2 -2
- package/dist/access-lcm-surface.js +41 -41
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +47 -47
- package/dist/access-namespace-preflight.js +41 -41
- package/dist/access-observe-write-surface.d.ts +2 -2
- package/dist/access-observe-write-surface.js +41 -41
- package/dist/access-operations-batch.js +43 -43
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +45 -45
- package/dist/access-recall-concurrency.d.ts +2 -2
- package/dist/access-recall-concurrency.js +41 -41
- package/dist/access-recall-response.d.ts +2 -2
- package/dist/access-recall-response.js +41 -41
- package/dist/access-recall-surface.d.ts +2 -2
- package/dist/access-recall-surface.js +41 -41
- package/dist/access-schema.d.ts +16 -1
- package/dist/access-schema.js +8 -8
- package/dist/{access-service-BJQ22URi.d.ts → access-service-DL8FJnTj.d.ts} +2 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +41 -41
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/{auto-sync-5OE5QOZB.js → auto-sync-UPTUVE4X.js} +3 -3
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +5 -5
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/capsule-crypto-YO5QJ6L3.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +11 -11
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
- package/dist/{chunk-BL4JHZSO.js → chunk-2XP3PWL3.js} +40 -40
- package/dist/{chunk-AQ2ZQYDI.js → chunk-4KKV7W62.js} +6 -6
- package/dist/chunk-4Z4XNEWZ.js +86 -0
- package/dist/chunk-4Z4XNEWZ.js.map +1 -0
- package/dist/{chunk-32XTXKIA.js → chunk-67DSDRUB.js} +22 -14
- package/dist/chunk-67DSDRUB.js.map +1 -0
- package/dist/{chunk-2PHX3I5C.js → chunk-6NNNURLK.js} +8 -8
- package/dist/{chunk-BLAKS3CP.js → chunk-77CNV7DY.js} +4 -4
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
- package/dist/{chunk-WIGBTWXA.js → chunk-7XXCIO6X.js} +2 -2
- package/dist/{chunk-7CPOZH6Y.js → chunk-AJ7QCSIS.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-UMFLAACL.js → chunk-E6EAB4J6.js} +9 -9
- package/dist/{chunk-WSEA52RI.js → chunk-EXM2Q555.js} +2 -2
- package/dist/{chunk-G6MGAE36.js → chunk-F6LSNPPB.js} +5 -5
- package/dist/{chunk-7KXQ44PA.js → chunk-FE5FKTCI.js} +5 -5
- package/dist/{chunk-5J7HJ7RT.js → chunk-H2PBM562.js} +2 -2
- package/dist/{chunk-OIE242WM.js → chunk-HBVOJNV4.js} +3 -3
- package/dist/{chunk-PS5OG75R.js → chunk-IHMXPG4P.js} +2 -2
- package/dist/{chunk-3UOSVTFR.js → chunk-JCVQV6QX.js} +11 -11
- package/dist/{chunk-P25IPRXP.js → chunk-KGTD2KZJ.js} +28 -8
- package/dist/chunk-KGTD2KZJ.js.map +1 -0
- package/dist/{chunk-NY6W6CJW.js → chunk-KPWLZBXZ.js} +2 -2
- package/dist/{chunk-W33DEUKR.js → chunk-LIIQPDFN.js} +2 -2
- package/dist/{chunk-D7MAWMMK.js → chunk-M2KGXXTL.js} +2 -2
- package/dist/{chunk-UOURUMBN.js → chunk-MIFF37NN.js} +2 -2
- package/dist/{chunk-37VPMR4E.js → chunk-NKDVXEFU.js} +6 -6
- package/dist/{chunk-ZPUFQGUF.js → chunk-PQPSZICJ.js} +5 -5
- package/dist/{chunk-ES36HTSC.js → chunk-PTN6UAUA.js} +4 -4
- package/dist/{chunk-FR75B4UU.js → chunk-PX7VDW7R.js} +408 -721
- package/dist/chunk-PX7VDW7R.js.map +1 -0
- package/dist/{chunk-S2BBISHU.js → chunk-RYHWZ52A.js} +4 -4
- package/dist/{chunk-ZN4UZWMT.js → chunk-SK3EYPKC.js} +2 -2
- package/dist/{chunk-TPYZ727G.js → chunk-SN3ZESWQ.js} +6 -6
- package/dist/{chunk-BUK2FXOL.js → chunk-TKBDJTXG.js} +4 -4
- package/dist/{chunk-4B77XSG5.js → chunk-UV72DEZD.js} +5 -5
- package/dist/{chunk-K4DP5KZA.js → chunk-UZK7VVPD.js} +14 -14
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-SSYBI43M.js → chunk-VGEH3STZ.js} +6 -6
- package/dist/{chunk-NRV4Y3PM.js → chunk-W4FBGYGK.js} +1 -1
- package/dist/{chunk-IR4LGIH6.js → chunk-WMDRLTNA.js} +2 -2
- package/dist/{chunk-MU7ZHSZE.js → chunk-WZFAPHIK.js} +2 -2
- package/dist/{chunk-JA4HEXV7.js → chunk-XAAIUXJO.js} +2 -2
- package/dist/{chunk-VPSAVXHM.js → chunk-XNUBF6RF.js} +7 -7
- package/dist/{chunk-3CHQZVAL.js → chunk-Y6E37WRG.js} +5 -5
- package/dist/{chunk-E5A6C4A5.js → chunk-YN4AUGNF.js} +48 -48
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-7IGWPXVC.js → chunk-ZHP4SUPB.js} +2 -2
- package/dist/{cli-1W7cNjJJ.d.ts → cli-BoouX6LX.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +75 -74
- package/dist/compounding/engine.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +16 -16
- package/dist/dashboard-runtime.js +2 -2
- package/dist/entity-retrieval.js +5 -5
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/{first-start-migration-BKNGOFED.js → first-start-migration-LEXL3XYL.js} +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +177 -176
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +9 -9
- package/dist/maintenance/memory-governance.js +6 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.js +7 -7
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/namespaces/migrate.js +15 -15
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.js +5 -5
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-toolkit.js +28 -28
- package/dist/orchestration/maintenance.js +11 -11
- package/dist/{orchestrator-BtA1wC5K.d.ts → orchestrator-CFJ_TG6D.d.ts} +15 -10
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +98 -97
- package/dist/resume-bundles.js +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +6 -6
- package/dist/session-namespace-bindings.d.ts +27 -0
- package/dist/session-namespace-bindings.js +243 -0
- package/dist/session-namespace-bindings.js.map +1 -0
- package/dist/storage.js +4 -4
- package/dist/testing/lifecycle-matrix.d.ts +100 -0
- package/dist/testing/lifecycle-matrix.js +149 -0
- package/dist/testing/lifecycle-matrix.js.map +1 -0
- package/dist/transfer/backup.js +4 -4
- package/dist/transfer/capsule-export.js +4 -4
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/verified-recall.js +6 -6
- package/package.json +22 -2
- package/src/access-http-lcm-compaction.ts +116 -0
- package/src/access-http.test.ts +218 -0
- package/src/access-http.ts +8 -8
- package/src/access-schema-pi.test.ts +9 -0
- package/src/access-schema.ts +29 -4
- package/src/access-surface-catalog.test.ts +1 -0
- package/src/meetings/recall-exclusion.test.ts +4 -2
- package/src/orchestration/generic-recall-paths.ts +119 -0
- package/src/orchestration/namespace-read-fanout.ts +0 -12
- package/src/orchestration/orchestrator-helpers.ts +0 -35
- package/src/orchestration/recall-internal.ts +6 -6
- package/src/orchestration/recall-search-pipeline.test.ts +194 -28
- package/src/orchestration/recall-search-pipeline.ts +39 -89
- package/src/orchestrator.ts +5 -17
- package/src/recall-cold-deadline.test.ts +11 -13
- package/src/session-namespace-bindings.test.ts +358 -0
- package/src/session-namespace-bindings.ts +301 -0
- package/src/testing/lifecycle-matrix.ts +17 -7
- package/src/testing/subjects/generic-recall-paths.test.ts +157 -0
- package/dist/chunk-32XTXKIA.js.map +0 -1
- package/dist/chunk-FR75B4UU.js.map +0 -1
- package/dist/chunk-P25IPRXP.js.map +0 -1
- package/src/recall/archive-scoring.ts +0 -544
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-http-lcm-compaction.js.map} +0 -0
- /package/dist/{auto-sync-5OE5QOZB.js.map → auto-sync-UPTUVE4X.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-BL4JHZSO.js.map → chunk-2XP3PWL3.js.map} +0 -0
- /package/dist/{chunk-AQ2ZQYDI.js.map → chunk-4KKV7W62.js.map} +0 -0
- /package/dist/{chunk-2PHX3I5C.js.map → chunk-6NNNURLK.js.map} +0 -0
- /package/dist/{chunk-BLAKS3CP.js.map → chunk-77CNV7DY.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
- /package/dist/{chunk-WIGBTWXA.js.map → chunk-7XXCIO6X.js.map} +0 -0
- /package/dist/{chunk-7CPOZH6Y.js.map → chunk-AJ7QCSIS.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-UMFLAACL.js.map → chunk-E6EAB4J6.js.map} +0 -0
- /package/dist/{chunk-WSEA52RI.js.map → chunk-EXM2Q555.js.map} +0 -0
- /package/dist/{chunk-G6MGAE36.js.map → chunk-F6LSNPPB.js.map} +0 -0
- /package/dist/{chunk-7KXQ44PA.js.map → chunk-FE5FKTCI.js.map} +0 -0
- /package/dist/{chunk-5J7HJ7RT.js.map → chunk-H2PBM562.js.map} +0 -0
- /package/dist/{chunk-OIE242WM.js.map → chunk-HBVOJNV4.js.map} +0 -0
- /package/dist/{chunk-PS5OG75R.js.map → chunk-IHMXPG4P.js.map} +0 -0
- /package/dist/{chunk-3UOSVTFR.js.map → chunk-JCVQV6QX.js.map} +0 -0
- /package/dist/{chunk-NY6W6CJW.js.map → chunk-KPWLZBXZ.js.map} +0 -0
- /package/dist/{chunk-W33DEUKR.js.map → chunk-LIIQPDFN.js.map} +0 -0
- /package/dist/{chunk-D7MAWMMK.js.map → chunk-M2KGXXTL.js.map} +0 -0
- /package/dist/{chunk-UOURUMBN.js.map → chunk-MIFF37NN.js.map} +0 -0
- /package/dist/{chunk-37VPMR4E.js.map → chunk-NKDVXEFU.js.map} +0 -0
- /package/dist/{chunk-ZPUFQGUF.js.map → chunk-PQPSZICJ.js.map} +0 -0
- /package/dist/{chunk-ES36HTSC.js.map → chunk-PTN6UAUA.js.map} +0 -0
- /package/dist/{chunk-S2BBISHU.js.map → chunk-RYHWZ52A.js.map} +0 -0
- /package/dist/{chunk-ZN4UZWMT.js.map → chunk-SK3EYPKC.js.map} +0 -0
- /package/dist/{chunk-TPYZ727G.js.map → chunk-SN3ZESWQ.js.map} +0 -0
- /package/dist/{chunk-BUK2FXOL.js.map → chunk-TKBDJTXG.js.map} +0 -0
- /package/dist/{chunk-4B77XSG5.js.map → chunk-UV72DEZD.js.map} +0 -0
- /package/dist/{chunk-K4DP5KZA.js.map → chunk-UZK7VVPD.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-SSYBI43M.js.map → chunk-VGEH3STZ.js.map} +0 -0
- /package/dist/{chunk-NRV4Y3PM.js.map → chunk-W4FBGYGK.js.map} +0 -0
- /package/dist/{chunk-IR4LGIH6.js.map → chunk-WMDRLTNA.js.map} +0 -0
- /package/dist/{chunk-MU7ZHSZE.js.map → chunk-WZFAPHIK.js.map} +0 -0
- /package/dist/{chunk-JA4HEXV7.js.map → chunk-XAAIUXJO.js.map} +0 -0
- /package/dist/{chunk-VPSAVXHM.js.map → chunk-XNUBF6RF.js.map} +0 -0
- /package/dist/{chunk-3CHQZVAL.js.map → chunk-Y6E37WRG.js.map} +0 -0
- /package/dist/{chunk-E5A6C4A5.js.map → chunk-YN4AUGNF.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-7IGWPXVC.js.map → chunk-ZHP4SUPB.js.map} +0 -0
- /package/dist/{first-start-migration-BKNGOFED.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/access-schema.ts"],"sourcesContent":["// Request/response schema validation for the Remnic HTTP API.\n// Uses zod for runtime validation — returns structured 400 errors with\n// field-level detail so consumers get clear feedback on malformed requests.\n\nimport { z } from \"zod\";\nimport {\n ACTION_CONFIDENCE_CONTEXT_READINESS,\n ACTION_CONFIDENCE_RISK_CATEGORIES,\n ACTION_CONFIDENCE_RULE_KINDS,\n} from \"./action-confidence.js\";\nimport { isValidCapsuleSince } from \"./transfer/capsule-export.js\";\nimport { validateArchiveRelativePath } from \"./transfer/fs-utils.js\";\nimport { CAPSULE_ID_PATTERN } from \"./transfer/types.js\";\nimport {\n OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,\n OFFLINE_SYNC_MAX_MTIME_MS,\n} from \"./offline-sync.js\";\n\n// ---------------------------------------------------------------------------\n// Error formatting\n// ---------------------------------------------------------------------------\n\nexport interface SchemaValidationError {\n error: string;\n code: \"validation_error\";\n details: Array<{ field: string; message: string }>;\n}\n\nexport function formatZodError(error: z.ZodError): SchemaValidationError {\n return {\n error: \"request validation failed\",\n code: \"validation_error\",\n details: error.issues.map((issue) => ({\n field: issue.path.join(\".\") || \"(root)\",\n message: issue.message,\n })),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared fields\n// ---------------------------------------------------------------------------\n\nconst namespaceSchema = z.string().trim().max(256).optional();\nconst sessionKeySchema = z.string().trim().min(1).max(512).optional();\nconst idempotencyKeySchema = z.string().trim().min(1).max(256).optional();\nconst dryRunSchema = z.boolean().optional();\nconst schemaVersionSchema = z.number().int().optional();\nconst timeZoneSchema = z\n .string()\n .trim()\n .min(1)\n .max(128)\n .refine((value) => {\n try {\n Intl.DateTimeFormat(undefined, { timeZone: value });\n return true;\n } catch {\n return false;\n }\n }, \"must be a valid IANA timezone\");\n\n// ---------------------------------------------------------------------------\n// Recall\n// ---------------------------------------------------------------------------\n\n/**\n * Coding-agent context (issue #569). Optional payload that connectors may\n * ship with a recall request so the project/branch namespace overlay\n * applies to that recall. All fields are validated per CLAUDE.md #51 —\n * empty-string projectId / rootPath is rejected, not silently accepted.\n */\nexport const codingContextSchema = z\n .object({\n projectId: z.string().trim().min(1, \"codingContext.projectId is required\").max(128),\n branch: z.string().trim().max(256).nullable(),\n rootPath: z.string().trim().min(1, \"codingContext.rootPath is required\").max(1024),\n defaultBranch: z.string().trim().max(256).nullable(),\n })\n .nullable();\n\n/**\n * Recall disclosure depth (issue #677). Mirrors the `RecallDisclosure`\n * type in `types.ts` — keep these in sync. Default-application happens\n * inside `EngramAccessService.recall()`; the schema only accepts/rejects.\n * Invalid values throw a structured 400 instead of silently defaulting,\n * per CLAUDE.md rule 51.\n */\nexport const recallDisclosureSchema = z.enum([\"chunk\", \"section\", \"raw\"]);\n\n/**\n * Tag-match semantics (issue #689). `any` (default when `tags` is provided\n * and `tagMatch` is omitted) admits a result when it carries at least one\n * of the filter tags. `all` requires every filter tag to be present.\n * Schema rejects unknown values up front — never silently defaults\n * (CLAUDE.md rule 51).\n */\nexport const tagMatchSchema = z.enum([\"any\", \"all\"]);\n\nexport const recallRequestSchema = z.object({\n query: z.string().min(1, \"query is required\"),\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n topK: z.number().int().min(0).max(200).optional(),\n mode: z.enum([\"auto\", \"no_recall\", \"minimal\", \"full\", \"graph_mode\"]).optional(),\n includeDebug: z.boolean().optional(),\n idempotencyKey: idempotencyKeySchema,\n disclosure: recallDisclosureSchema.optional(),\n codingContext: codingContextSchema.optional(),\n /** Working directory for auto git-context resolution (issue #569). */\n cwd: z.string().trim().min(1, \"cwd must be non-empty when provided\").max(2048).optional(),\n /**\n * Arbitrary project tag for non-git-based project scoping (issue #569).\n * Creates a coding context with `projectId: \"tag:<projectTag>\"`.\n */\n projectTag: z.string().trim().min(1, \"projectTag must be non-empty when provided\").max(256).optional(),\n /**\n * Historical recall pin (issue #680). ISO 8601 timestamp. The\n * schema only enforces the basic shape; the access service runs\n * `Date.parse` and emits a structured 400 on malformed input\n * (CLAUDE.md rule 51).\n */\n asOf: z.string().trim().min(1, \"asOf must be a non-empty ISO 8601 timestamp\").max(64).optional(),\n /**\n * Free-form recall tag filter (issue #689). When provided, recall results\n * whose frontmatter `tags` do not match the filter are removed before the\n * response is returned. Comparison is case-sensitive exact match.\n */\n tags: z.array(z.string().trim().min(1).max(256)).max(50).optional(),\n /**\n * Match mode for `tags` (issue #689). Defaults to `\"any\"` when `tags` is\n * provided and `tagMatch` is omitted. Ignored when `tags` is absent.\n */\n tagMatch: tagMatchSchema.optional(),\n /**\n * Include graph edges below `graphTraversalConfidenceFloor` for diagnostic\n * recall traversal (issue #681). Defaults to false.\n */\n includeLowConfidence: z.boolean().optional(),\n});\n\nexport const recallExplainRequestSchema = z.object({\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n});\n\n/**\n * Standalone \"set coding context\" request. Used by the HTTP endpoint\n * `POST /engram/v1/coding-context` and the MCP `remnic.set_coding_context`\n * tool (PR 7). `codingContext: null` clears the attached context.\n */\nexport const setCodingContextRequestSchema = z\n .object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n codingContext: codingContextSchema.optional(),\n /**\n * Project tag shorthand for non-git-based project scoping. When\n * `codingContext` is omitted, this becomes\n * `{ projectId: \"tag:<projectTag>\", branch: null, rootPath: \"tag:<projectTag>\", defaultBranch: null }`.\n */\n projectTag: z.string().trim().min(1, \"projectTag must be non-empty when provided\").max(256).optional(),\n })\n .superRefine((value, ctx) => {\n if (value.codingContext === undefined && value.projectTag === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: \"codingContext or projectTag is required\",\n path: [\"codingContext\"],\n });\n }\n });\n\n// ---------------------------------------------------------------------------\n// Observe\n// ---------------------------------------------------------------------------\n\nconst messageSchema = z.object({\n role: z.enum([\"user\", \"assistant\"]),\n content: z.string().min(1, \"message content must be non-empty\"),\n sourceFormat: z\n .enum([\"openai\", \"anthropic\", \"openclaw\", \"pi\", \"lossless-claw\", \"remnic\"])\n .nullable()\n .optional(),\n rawContent: z.unknown().nullable().optional(),\n parts: z\n .array(\n z.object({\n ordinal: z.number().int().min(0).nullable().optional(),\n kind: z.enum([\n \"text\",\n \"tool_call\",\n \"tool_result\",\n \"patch\",\n \"file_read\",\n \"file_write\",\n \"step_start\",\n \"step_finish\",\n \"snapshot\",\n \"retry\",\n ]),\n payload: z.record(z.string(), z.unknown()),\n toolName: z.string().nullable().optional(),\n tool_name: z.string().nullable().optional(),\n filePath: z.string().nullable().optional(),\n file_path: z.string().nullable().optional(),\n createdAt: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n }),\n )\n .nullable()\n .optional(),\n});\n\nexport const observeRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n messages: z.array(messageSchema).min(1, \"messages must be a non-empty array\"),\n namespace: namespaceSchema,\n skipExtraction: z.boolean().optional(),\n /**\n * Optional idempotency key for server-side dedup of retried observe POSTs\n * (issue #1649). A retry that reaches the daemon after the first attempt\n * already processed is replayed from cache instead of re-ingested. When the\n * key is reused with a DIFFERENT payload the request is rejected as a\n * conflict (same contract as memory_store/suggestion_submit).\n */\n idempotencyKey: idempotencyKeySchema,\n /** Working directory for auto git-context resolution (issue #569). */\n cwd: z.string().trim().min(1, \"cwd must be non-empty when provided\").max(2048).optional(),\n /**\n * Arbitrary project tag for non-git-based project scoping (issue #569).\n * Creates a coding context with `projectId: \"tag:<projectTag>\"`.\n */\n projectTag: z.string().trim().min(1, \"projectTag must be non-empty when provided\").max(256).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Memory store / suggestion submit\n// ---------------------------------------------------------------------------\n\nconst writeContentSchema = z.string().min(1, \"content is required\").max(50000);\nconst categorySchema = z\n .enum([\n \"fact\", \"preference\", \"correction\", \"entity\", \"decision\",\n \"relationship\", \"principle\", \"commitment\", \"moment\", \"skill\", \"rule\", \"procedure\",\n \"reasoning_trace\",\n ])\n .optional();\nconst confidenceSchema = z.number().min(0).max(1).optional();\nconst tagsSchema = z.array(z.string().max(256)).max(50).optional();\nconst entityRefSchema = z.string().trim().max(512).optional();\nconst ttlSchema = z.string().trim().max(128).optional();\nconst sourceReasonSchema = z.string().trim().max(2000).optional();\n\nexport const memoryStoreRequestSchema = z.object({\n schemaVersion: schemaVersionSchema,\n idempotencyKey: idempotencyKeySchema,\n dryRun: dryRunSchema,\n sessionKey: sessionKeySchema,\n content: writeContentSchema,\n category: categorySchema,\n confidence: confidenceSchema,\n namespace: namespaceSchema,\n tags: tagsSchema,\n entityRef: entityRefSchema,\n ttl: ttlSchema,\n sourceReason: sourceReasonSchema,\n // Git/project context for project-scoped writes (#1434). When no explicit\n // `namespace` is given, these route the write to the same project namespace\n // recall/observe resolve from `cwd`/`projectTag` (issue #569, rule 42). Also\n // lets MCP clients that auto-inject `cwd` (e.g. Pi MCPorter) call write tools.\n cwd: z.string().trim().min(1, \"cwd must be non-empty when provided\").max(2048).optional(),\n projectTag: z\n .string()\n .trim()\n .min(1, \"projectTag must be non-empty when provided\")\n .max(256)\n .optional(),\n});\n\nexport const suggestionSubmitRequestSchema = memoryStoreRequestSchema;\n\n// ---------------------------------------------------------------------------\n// Review disposition\n// ---------------------------------------------------------------------------\n\nexport const reviewDispositionRequestSchema = z.object({\n memoryId: z.string().trim().min(1, \"memoryId is required\"),\n status: z.enum([\n \"active\", \"pending_review\", \"quarantined\", \"rejected\", \"superseded\", \"archived\",\n ]),\n reasonCode: z.string().trim().min(1, \"reasonCode is required\"),\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Trust-zone promote\n// ---------------------------------------------------------------------------\n\nexport const trustZonePromoteRequestSchema = z.object({\n recordId: z.string().trim().min(1, \"recordId is required\"),\n targetZone: z.enum([\"working\", \"trusted\"], {\n errorMap: () => ({ message: \"targetZone must be 'working' or 'trusted'\" }),\n }),\n promotionReason: z.string().trim().min(1, \"promotionReason is required\"),\n recordedAt: z.string().trim().optional(),\n summary: z.string().trim().max(5000).optional(),\n dryRun: dryRunSchema,\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Trust-zone demo-seed\n// ---------------------------------------------------------------------------\n\nexport const trustZoneDemoSeedRequestSchema = z.object({\n scenario: z.string().trim().max(256).optional(),\n recordedAt: z.string().trim().optional(),\n dryRun: dryRunSchema,\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// LCM search\n// ---------------------------------------------------------------------------\n\nexport const lcmSearchRequestSchema = z.object({\n query: z.string().min(1, \"query is required\"),\n sessionKey: sessionKeySchema,\n sessionPrefix: z.string().trim().min(1).max(512).optional(),\n namespace: namespaceSchema,\n limit: z.number().int().min(1).max(100).optional(),\n});\n\nexport const lcmCompactionFlushRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n namespace: namespaceSchema,\n});\n\nexport const lcmCompactionRecordRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n namespace: namespaceSchema,\n tokensBefore: z.number().int().min(0, \"tokensBefore must be a non-negative integer\"),\n tokensAfter: z.number().int().min(0, \"tokensAfter must be a non-negative integer\"),\n});\n\n// ---------------------------------------------------------------------------\n// Day summary\n// ---------------------------------------------------------------------------\n\nexport const daySummaryRequestSchema = z.object({\n memories: z.string().max(100000).optional(),\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n timeZone: timeZoneSchema.optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Capsule export\n// ---------------------------------------------------------------------------\n\nconst capsuleTopLevelSegmentSchema = z\n .string()\n .trim()\n .min(1)\n .max(128)\n .refine(\n (value) => !value.includes(\"/\") && !value.includes(\"\\\\\"),\n \"must be a top-level directory name without path separators\",\n );\n\nconst capsulePeerIdSchema = z\n .string()\n .trim()\n .min(1)\n .max(256)\n .refine(\n (value) => value !== \".\" && value !== \"..\" && !value.includes(\"/\") && !value.includes(\"\\\\\"),\n \"must be a plain peer id without path separators\",\n );\n\nconst capsuleIsoSinceSchema = z\n .string()\n .trim()\n .min(1, \"since must be a non-empty ISO 8601 timestamp\")\n .max(128)\n .refine(\n isValidCapsuleSince,\n \"since must be a valid ISO 8601 timestamp with no calendar overflow\",\n );\n\nexport const capsuleExportRequestSchema = z\n .object({\n name: z\n .string()\n .trim()\n .min(1, \"name is required\")\n .max(64, \"name must be 64 characters or fewer\")\n .regex(\n CAPSULE_ID_PATTERN,\n \"name must be alphanumeric with single dashes (no spaces, no leading/trailing dashes)\",\n ),\n namespace: namespaceSchema,\n since: capsuleIsoSinceSchema.optional(),\n includeKinds: z.array(capsuleTopLevelSegmentSchema).max(50).optional(),\n peerIds: z.array(capsulePeerIdSchema).max(100).optional(),\n includeTranscripts: z.boolean().optional(),\n encrypt: z.boolean().optional(),\n });\n\nexport const capsuleImportRequestSchema = z\n .object({\n archivePath: z.string().trim().min(1, \"archivePath is required\").max(4096),\n namespace: namespaceSchema,\n mode: z.enum([\"skip\", \"overwrite\", \"fork\"]).optional(),\n passphrase: z.string().min(1, \"passphrase must not be empty\").max(4096).optional(),\n });\n\nexport const capsuleListRequestSchema = z\n .object({\n namespace: namespaceSchema,\n sessionKey: sessionKeySchema,\n });\n\n// ---------------------------------------------------------------------------\n// Offline sync\n// ---------------------------------------------------------------------------\n\nfunction isValidOfflineSyncPath(value: string): boolean {\n try {\n validateArchiveRelativePath(value, \"path\");\n return true;\n } catch {\n return false;\n }\n}\n\nconst offlineSyncPathSchema = z\n .string()\n .trim()\n .min(1, \"path must be non-empty\")\n .max(4096)\n .refine(\n isValidOfflineSyncPath,\n \"path must be a POSIX relative path without unsafe segments\",\n );\n\nconst offlineSyncFileStateSchema = z.object({\n path: offlineSyncPathSchema,\n sha256: z.string().regex(/^[a-f0-9]{64}$/i, \"sha256 must be a 64-character hex digest\"),\n bytes: z.number().int().min(0),\n mtimeMs: z.number().finite().min(0).max(OFFLINE_SYNC_MAX_MTIME_MS),\n});\n\nconst offlineSyncBaseCapturedAtSchema = z\n .string()\n .trim()\n .min(1, \"baseCapturedAt must be non-empty when provided\")\n .max(64)\n .refine((value) => Number.isFinite(Date.parse(value)), {\n message: \"baseCapturedAt must be a valid ISO 8601 timestamp\",\n });\n\nexport const offlineSyncSnapshotRequestSchema = z.object({\n namespace: namespaceSchema,\n includeTranscripts: z.boolean().optional(),\n includeContent: z.boolean().optional(),\n baseCapturedAt: offlineSyncBaseCapturedAtSchema.optional(),\n baseFiles: z\n .array(offlineSyncFileStateSchema)\n .max(300_000, \"baseFiles must contain 300000 or fewer entries\")\n .optional(),\n});\n\nexport const offlineSyncApplyRequestSchema = z\n .object({\n namespace: namespaceSchema,\n changeset: z.unknown(),\n returnCurrentFiles: z.boolean().optional(),\n })\n .refine((value) => value.changeset !== undefined && value.changeset !== null, {\n message: \"changeset is required\",\n path: [\"changeset\"],\n });\n\nexport const offlineSyncFilesRequestSchema = z.object({\n namespace: namespaceSchema,\n includeTranscripts: z.boolean().optional(),\n paths: z\n .array(offlineSyncPathSchema)\n .max(5000, \"paths must contain 5000 or fewer entries\"),\n});\n\nexport const offlineSyncFileContentRequestSchema = z.object({\n namespace: namespaceSchema,\n includeTranscripts: z.boolean().optional(),\n path: offlineSyncPathSchema,\n offset: z.number().int().min(0).optional(),\n length: z.number().int().min(1).max(OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Action confidence\n// ---------------------------------------------------------------------------\n\nconst nullableOptional = <T extends z.ZodTypeAny>(schema: T) =>\n schema.optional().nullable().transform((value) => value ?? undefined);\n\nconst actionConfidenceRuleSchema = z\n .object({\n kind: z.enum(ACTION_CONFIDENCE_RULE_KINDS),\n description: nullableOptional(z.string().trim().min(1).max(2000)),\n matched: nullableOptional(z.boolean()),\n })\n .strict();\n\nconst actionConfidenceMemorySchema = z\n .object({\n source: nullableOptional(z.string().trim().min(1).max(256)),\n created: nullableOptional(z.string().trim().min(1).max(128)),\n updated: nullableOptional(z.string().trim().min(1).max(128)),\n scope: nullableOptional(z.string().trim().min(1).max(512)),\n userContextScopes: nullableOptional(z.array(z.string().trim().min(1).max(128)).max(50)),\n retrievalReason: nullableOptional(z.string().trim().min(1).max(2000)),\n confidence: nullableOptional(z.number().min(0).max(1)),\n stale: nullableOptional(z.boolean()),\n corrected: nullableOptional(z.boolean()),\n correctionState: nullableOptional(z.enum([\"none\", \"correction\", \"superseded\", \"disputed\", \"forgotten\"])),\n safeToUse: nullableOptional(z.boolean()),\n safety: nullableOptional(z.enum([\"safe\", \"requires-review\", \"blocked\"])),\n safetyReasons: nullableOptional(z.array(z.string().trim().min(1).max(1000)).max(50)),\n })\n .strict();\n\nexport const actionConfidenceRequestSchema = z\n .object({\n intendedAction: nullableOptional(z.string().trim().min(1).max(1000)),\n confidence: nullableOptional(z.number().min(0).max(1)),\n risk: nullableOptional(z.enum(ACTION_CONFIDENCE_RISK_CATEGORIES)),\n contextReadiness: nullableOptional(z.enum(ACTION_CONFIDENCE_CONTEXT_READINESS)),\n currentContextScopes: nullableOptional(z.array(z.string().trim().min(1).max(128)).max(50)),\n userRules: nullableOptional(z.array(actionConfidenceRuleSchema).max(100)),\n retrievedMemories: nullableOptional(z.array(actionConfidenceMemorySchema).max(200)),\n })\n .strict();\n\n// ---------------------------------------------------------------------------\n// Inferred types\n// ---------------------------------------------------------------------------\n\nexport type RecallRequest = z.infer<typeof recallRequestSchema>;\nexport type RecallExplainRequest = z.infer<typeof recallExplainRequestSchema>;\nexport type SetCodingContextRequest = z.infer<typeof setCodingContextRequestSchema>;\nexport type ObserveRequest = z.infer<typeof observeRequestSchema>;\nexport type MemoryStoreRequest = z.infer<typeof memoryStoreRequestSchema>;\nexport type SuggestionSubmitRequest = z.infer<typeof suggestionSubmitRequestSchema>;\nexport type ReviewDispositionRequest = z.infer<typeof reviewDispositionRequestSchema>;\nexport type TrustZonePromoteRequest = z.infer<typeof trustZonePromoteRequestSchema>;\nexport type TrustZoneDemoSeedRequest = z.infer<typeof trustZoneDemoSeedRequestSchema>;\nexport type LcmSearchRequest = z.infer<typeof lcmSearchRequestSchema>;\nexport type LcmCompactionFlushRequest = z.infer<typeof lcmCompactionFlushRequestSchema>;\nexport type LcmCompactionRecordRequest = z.infer<typeof lcmCompactionRecordRequestSchema>;\nexport type DaySummaryRequest = z.infer<typeof daySummaryRequestSchema>;\nexport type CapsuleExportRequest = z.infer<typeof capsuleExportRequestSchema>;\nexport type CapsuleImportRequest = z.infer<typeof capsuleImportRequestSchema>;\nexport type CapsuleListRequest = z.infer<typeof capsuleListRequestSchema>;\nexport type OfflineSyncApplyRequest = z.infer<typeof offlineSyncApplyRequestSchema>;\nexport type OfflineSyncSnapshotRequest = z.infer<typeof offlineSyncSnapshotRequestSchema>;\nexport type OfflineSyncFilesRequest = z.infer<typeof offlineSyncFilesRequestSchema>;\nexport type OfflineSyncFileContentRequest = z.infer<typeof offlineSyncFileContentRequestSchema>;\nexport type ActionConfidenceRequest = z.infer<typeof actionConfidenceRequestSchema>;\n\n// ---------------------------------------------------------------------------\n// Validation helper\n// ---------------------------------------------------------------------------\n\nexport type SchemaName =\n | \"recall\"\n | \"recallExplain\"\n | \"setCodingContext\"\n | \"observe\"\n | \"memoryStore\"\n | \"suggestionSubmit\"\n | \"reviewDisposition\"\n | \"trustZonePromote\"\n | \"trustZoneDemoSeed\"\n | \"lcmSearch\"\n | \"lcmCompactionFlush\"\n | \"lcmCompactionRecord\"\n | \"daySummary\"\n | \"capsuleExport\"\n | \"capsuleImport\"\n | \"capsuleList\"\n | \"offlineSyncSnapshot\"\n | \"offlineSyncFiles\"\n | \"offlineSyncFileContent\"\n | \"offlineSyncApply\"\n | \"actionConfidence\";\n\nexport type SchemaTypeFor<N extends SchemaName> =\n N extends \"recall\" ? RecallRequest\n : N extends \"recallExplain\" ? RecallExplainRequest\n : N extends \"setCodingContext\" ? SetCodingContextRequest\n : N extends \"observe\" ? ObserveRequest\n : N extends \"memoryStore\" ? MemoryStoreRequest\n : N extends \"suggestionSubmit\" ? SuggestionSubmitRequest\n : N extends \"reviewDisposition\" ? ReviewDispositionRequest\n : N extends \"trustZonePromote\" ? TrustZonePromoteRequest\n : N extends \"trustZoneDemoSeed\" ? TrustZoneDemoSeedRequest\n : N extends \"lcmSearch\" ? LcmSearchRequest\n : N extends \"lcmCompactionFlush\" ? LcmCompactionFlushRequest\n : N extends \"lcmCompactionRecord\" ? LcmCompactionRecordRequest\n : N extends \"daySummary\" ? DaySummaryRequest\n : N extends \"capsuleExport\" ? CapsuleExportRequest\n : N extends \"capsuleImport\" ? CapsuleImportRequest\n : N extends \"capsuleList\" ? CapsuleListRequest\n : N extends \"offlineSyncSnapshot\" ? OfflineSyncSnapshotRequest\n : N extends \"offlineSyncFiles\" ? OfflineSyncFilesRequest\n : N extends \"offlineSyncFileContent\" ? OfflineSyncFileContentRequest\n : N extends \"offlineSyncApply\" ? OfflineSyncApplyRequest\n : N extends \"actionConfidence\" ? ActionConfidenceRequest\n : never;\n\nconst schemas: Record<SchemaName, z.ZodTypeAny> = {\n recall: recallRequestSchema,\n recallExplain: recallExplainRequestSchema,\n setCodingContext: setCodingContextRequestSchema,\n observe: observeRequestSchema,\n memoryStore: memoryStoreRequestSchema,\n suggestionSubmit: suggestionSubmitRequestSchema,\n reviewDisposition: reviewDispositionRequestSchema,\n trustZonePromote: trustZonePromoteRequestSchema,\n trustZoneDemoSeed: trustZoneDemoSeedRequestSchema,\n lcmSearch: lcmSearchRequestSchema,\n lcmCompactionFlush: lcmCompactionFlushRequestSchema,\n lcmCompactionRecord: lcmCompactionRecordRequestSchema,\n daySummary: daySummaryRequestSchema,\n capsuleExport: capsuleExportRequestSchema,\n capsuleImport: capsuleImportRequestSchema,\n capsuleList: capsuleListRequestSchema,\n offlineSyncSnapshot: offlineSyncSnapshotRequestSchema,\n offlineSyncFiles: offlineSyncFilesRequestSchema,\n offlineSyncFileContent: offlineSyncFileContentRequestSchema,\n offlineSyncApply: offlineSyncApplyRequestSchema,\n actionConfidence: actionConfidenceRequestSchema,\n};\n\n/**\n * Validate a request body against the named schema.\n * Returns `{ success: true, data }` on pass or\n * `{ success: false, error }` on failure with field-level detail.\n */\nexport function validateRequest<T = unknown>(\n schemaName: SchemaName,\n body: unknown,\n): { success: true; data: T } | { success: false; error: SchemaValidationError } {\n const schema = schemas[schemaName];\n if (!schema) {\n return {\n success: false,\n error: {\n error: `unknown schema: ${schemaName}`,\n code: \"validation_error\",\n details: [],\n },\n };\n }\n const result = schema.safeParse(body);\n if (result.success) {\n return { success: true, data: result.data as T };\n }\n return { success: false, error: formatZodError(result.error) };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Off-thread archive scoring for the cold-fallback recall path (issue #1674).
|
|
3
|
-
//
|
|
4
|
-
// `searchLongTermArchiveFallback` (orchestrator.ts) falls back to scanning
|
|
5
|
-
// EVERY archived memory file when hybrid/vector search returns zero hits.
|
|
6
|
-
// The scoring loop — for each memory × for each token, `haystack.includes(token)`
|
|
7
|
-
// — is fully synchronous, unbounded, and CPU-bound. Under concurrent recall
|
|
8
|
-
// load this monopolized the JS main thread: N concurrent recalls serialized
|
|
9
|
-
// on one core and each blew past the client-side timeout even though total
|
|
10
|
-
// CPU work would have finished comfortably if parallelized.
|
|
11
|
-
//
|
|
12
|
-
// This module extracts the scoring loop into a pure function and provides two
|
|
13
|
-
// strategies:
|
|
14
|
-
//
|
|
15
|
-
// 1. `SyncArchiveScoring` — runs the pure function on the calling thread
|
|
16
|
-
// (the OLD behavior; preserved for prove-fail
|
|
17
|
-
// tests and as the graceful fallback).
|
|
18
|
-
// 2. `OffThreadArchiveScoring` — dispatches the pure function to a
|
|
19
|
-
// `worker_threads` pool so concurrent recalls
|
|
20
|
-
// run on separate cores instead of serializing
|
|
21
|
-
// on the main thread. Falls back to sync if
|
|
22
|
-
// workers cannot be created.
|
|
23
|
-
//
|
|
24
|
-
// Both strategies share the identical `scoreArchiveMemories` pure function,
|
|
25
|
-
// so the scoring semantics are byte-identical regardless of which path runs.
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
import os from "node:os";
|
|
29
|
-
import { Worker } from "node:worker_threads";
|
|
30
|
-
import { log } from "../logger.js";
|
|
31
|
-
import type { MemoryFile } from "../types.js";
|
|
32
|
-
|
|
33
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
-
// Wire types — plain-serializable shapes that cross the worker boundary via
|
|
35
|
-
// structured clone. Only the fields the scoring loop reads are included.
|
|
36
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
/** Minimal serializable projection of {@link MemoryFile} for scoring. */
|
|
39
|
-
export interface ArchiveScoreItem {
|
|
40
|
-
id: string;
|
|
41
|
-
path: string;
|
|
42
|
-
content: string;
|
|
43
|
-
category: string;
|
|
44
|
-
tags: string[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Scoring output — maps 1:1 to the relevant QmdSearchResult fields. */
|
|
48
|
-
export interface ArchiveScoreResult {
|
|
49
|
-
docid: string;
|
|
50
|
-
path: string;
|
|
51
|
-
score: number;
|
|
52
|
-
snippet: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Worker request envelope. */
|
|
56
|
-
interface ScoreTask {
|
|
57
|
-
items: ArchiveScoreItem[];
|
|
58
|
-
tokens: string[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Worker reply envelope. */
|
|
62
|
-
type ScoreReply = { ok: true; results: ArchiveScoreResult[] } | { ok: false; error: string };
|
|
63
|
-
|
|
64
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
-
// Pure scoring function — shared by both strategies and by the inline worker.
|
|
66
|
-
// Extracted verbatim from the original inline loop in
|
|
67
|
-
// `searchLongTermArchiveFallback` so behavior is identical.
|
|
68
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Score archived memories against query tokens using substring overlap.
|
|
72
|
-
*
|
|
73
|
-
* For each memory, builds a lowercase haystack from `[content, category, ...tags]`,
|
|
74
|
-
* counts how many distinct tokens appear in it, and scores by `hits / tokens.length`.
|
|
75
|
-
* Memories with zero hits are dropped. Snippets are the first 400 chars of content
|
|
76
|
-
* with newlines collapsed to spaces — matching the original orchestrator behavior.
|
|
77
|
-
*
|
|
78
|
-
* This function is intentionally synchronous and CPU-bound; that is precisely
|
|
79
|
-
* why the off-thread strategy exists.
|
|
80
|
-
*/
|
|
81
|
-
export function scoreArchiveMemories(
|
|
82
|
-
items: ReadonlyArray<ArchiveScoreItem>,
|
|
83
|
-
tokens: ReadonlyArray<string>
|
|
84
|
-
): ArchiveScoreResult[] {
|
|
85
|
-
if (items.length === 0 || tokens.length === 0) return [];
|
|
86
|
-
|
|
87
|
-
const scored: ArchiveScoreResult[] = [];
|
|
88
|
-
for (const item of items) {
|
|
89
|
-
const haystack = [item.content, item.category, ...item.tags].join(" ").toLowerCase();
|
|
90
|
-
let hits = 0;
|
|
91
|
-
for (const token of tokens) {
|
|
92
|
-
if (haystack.includes(token)) hits += 1;
|
|
93
|
-
}
|
|
94
|
-
if (hits === 0) continue;
|
|
95
|
-
scored.push({
|
|
96
|
-
docid: item.id,
|
|
97
|
-
path: item.path,
|
|
98
|
-
score: hits / tokens.length,
|
|
99
|
-
snippet: item.content.slice(0, 400).replace(/\n/g, " "),
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return scored;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Project a {@link MemoryFile} into the minimal serializable shape the scoring
|
|
107
|
-
* function consumes. Called on the main thread BEFORE dispatching to a worker
|
|
108
|
-
* so the heavy `MemoryFrontmatter` (dozens of optional fields, nested objects)
|
|
109
|
-
* never crosses the worker boundary.
|
|
110
|
-
*/
|
|
111
|
-
export function memoryFileToScoreItem(memory: MemoryFile): ArchiveScoreItem {
|
|
112
|
-
return {
|
|
113
|
-
id: memory.frontmatter.id,
|
|
114
|
-
path: memory.path,
|
|
115
|
-
content: memory.content,
|
|
116
|
-
category: memory.frontmatter.category,
|
|
117
|
-
tags: memory.frontmatter.tags ?? [],
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
-
// Strategy interface
|
|
123
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Pluggable scoring backend. The orchestrator holds one instance and calls
|
|
127
|
-
* `score()` from the cold-fallback path. The default is off-thread; tests and
|
|
128
|
-
* restricted environments can swap in the sync strategy.
|
|
129
|
-
*/
|
|
130
|
-
export interface ArchiveScoringStrategy {
|
|
131
|
-
score(
|
|
132
|
-
items: ReadonlyArray<ArchiveScoreItem>,
|
|
133
|
-
tokens: ReadonlyArray<string>,
|
|
134
|
-
abortSignal?: AbortSignal
|
|
135
|
-
): Promise<ArchiveScoreResult[]>;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
|
-
// 1. SyncArchiveScoring — the OLD serialized behavior, preserved for fallback
|
|
140
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Runs the scoring loop synchronously on the calling thread.
|
|
144
|
-
*
|
|
145
|
-
* This is the exact behavior that caused issue #1674: the synchronous loop
|
|
146
|
-
* blocks the event loop for its entire duration, so concurrent recall
|
|
147
|
-
* requests serialize behind each other. It is retained as the graceful
|
|
148
|
-
* fallback when worker_threads are unavailable, and as the prove-fail
|
|
149
|
-
* baseline in regression tests.
|
|
150
|
-
*/
|
|
151
|
-
export class SyncArchiveScoring implements ArchiveScoringStrategy {
|
|
152
|
-
async score(
|
|
153
|
-
items: ReadonlyArray<ArchiveScoreItem>,
|
|
154
|
-
tokens: ReadonlyArray<string>,
|
|
155
|
-
abortSignal?: AbortSignal
|
|
156
|
-
): Promise<ArchiveScoreResult[]> {
|
|
157
|
-
if (items.length === 0 || tokens.length === 0) return [];
|
|
158
|
-
if (abortSignal?.aborted) return [];
|
|
159
|
-
// Process in chunks so an abort during a large archive scan is observed
|
|
160
|
-
// without burning the full synchronous CPU pass (#1674 review: sync
|
|
161
|
-
// fallback should check mid-scoring abort, like the old inline loop).
|
|
162
|
-
const CHUNK = 500;
|
|
163
|
-
if (items.length <= CHUNK) return scoreArchiveMemories(items, tokens);
|
|
164
|
-
const results: ArchiveScoreResult[] = [];
|
|
165
|
-
for (let i = 0; i < items.length; i += CHUNK) {
|
|
166
|
-
if (abortSignal?.aborted) return [];
|
|
167
|
-
const scored = scoreArchiveMemories(items.slice(i, i + CHUNK), tokens);
|
|
168
|
-
for (const r of scored) results.push(r);
|
|
169
|
-
}
|
|
170
|
-
return results;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
-
// Inline worker code (eval mode) — eliminates file-resolution / build-config
|
|
176
|
-
// issues entirely. The worker is self-contained CJS (no imports), so it runs
|
|
177
|
-
// identically under tsx, compiled dist, and published npm packages.
|
|
178
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Inline worker source. Runs via `new Worker(code, { eval: true })`.
|
|
182
|
-
*
|
|
183
|
-
* MUST stay byte-identical to {@link scoreArchiveMemories} above. The
|
|
184
|
-
* regression test "worker scoring matches canonical scoreArchiveMemories"
|
|
185
|
-
* asserts this equivalence at runtime.
|
|
186
|
-
*/
|
|
187
|
-
const WORKER_SOURCE = String.raw`
|
|
188
|
-
const { parentPort } = require('node:worker_threads');
|
|
189
|
-
|
|
190
|
-
function scoreArchiveMemories(items, tokens) {
|
|
191
|
-
if (items.length === 0 || tokens.length === 0) return [];
|
|
192
|
-
var scored = [];
|
|
193
|
-
for (var i = 0; i < items.length; i++) {
|
|
194
|
-
var item = items[i];
|
|
195
|
-
var haystack = [item.content, item.category].concat(item.tags || []).join(' ').toLowerCase();
|
|
196
|
-
var hits = 0;
|
|
197
|
-
for (var j = 0; j < tokens.length; j++) {
|
|
198
|
-
if (haystack.indexOf(tokens[j]) !== -1) hits++;
|
|
199
|
-
}
|
|
200
|
-
if (hits === 0) continue;
|
|
201
|
-
scored.push({
|
|
202
|
-
docid: item.id,
|
|
203
|
-
path: item.path,
|
|
204
|
-
score: hits / tokens.length,
|
|
205
|
-
snippet: item.content.slice(0, 400).replace(/\n/g, ' '),
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
return scored;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (parentPort) {
|
|
212
|
-
parentPort.on('message', function(task) {
|
|
213
|
-
try {
|
|
214
|
-
var results = scoreArchiveMemories(task.items, task.tokens);
|
|
215
|
-
parentPort.postMessage({ ok: true, results: results });
|
|
216
|
-
} catch (err) {
|
|
217
|
-
parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
`;
|
|
222
|
-
|
|
223
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
224
|
-
// 2. OffThreadArchiveScoring — worker pool for genuine multi-core parallelism
|
|
225
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Default worker pool size. Uses `availableParallelism()` (Node 19.4+) minus
|
|
229
|
-
* one so the main thread always has a dedicated core for I/O dispatch. Capped
|
|
230
|
-
* at 8 to bound memory overhead (each worker has its own V8 heap).
|
|
231
|
-
*/
|
|
232
|
-
function defaultPoolSize(): number {
|
|
233
|
-
const cpus = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length;
|
|
234
|
-
return Math.max(1, Math.min(Math.max(1, cpus - 1), 8));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Safety-net timeout for a single dispatch. The cold-fallback pipeline already
|
|
239
|
-
* has its own deadline mechanism (runColdStepWithinDeadline); this is a
|
|
240
|
-
* last-resort guard so a hung worker never blocks recall indefinitely.
|
|
241
|
-
* Generously large to avoid interfering with large corpora (issue #1674
|
|
242
|
-
* reported scans up to ~70s on large archives).
|
|
243
|
-
*/
|
|
244
|
-
const DISPATCH_TIMEOUT_MS = 120_000;
|
|
245
|
-
|
|
246
|
-
/** Lazy worker pool. Workers are created on first use and recycled. */
|
|
247
|
-
class ArchiveScoringWorkerPool {
|
|
248
|
-
private readonly targetSize: number;
|
|
249
|
-
private workers: Worker[] = [];
|
|
250
|
-
private idle: Worker[] = [];
|
|
251
|
-
private waiters: Array<{ resolve: (worker: Worker) => void; reject: (err: Error) => void }> = [];
|
|
252
|
-
private terminated = false;
|
|
253
|
-
private busy = 0;
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Workers currently checked out and scoring — NOT callers waiting in line.
|
|
257
|
-
* A caller parked in `acquire()` is queued, not running, so this is the only
|
|
258
|
-
* honest measure of real task overlap: with a size-1 pool it never exceeds 1
|
|
259
|
-
* no matter how many callers are queued behind it.
|
|
260
|
-
*/
|
|
261
|
-
get busyWorkers(): number {
|
|
262
|
-
return this.busy;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
constructor(size: number = defaultPoolSize()) {
|
|
266
|
-
this.targetSize = Math.max(1, size);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async run(task: ScoreTask, abortSignal?: AbortSignal): Promise<ArchiveScoreResult[]> {
|
|
270
|
-
if (this.terminated) throw new Error("archive-scoring pool terminated");
|
|
271
|
-
const worker = await this.acquire(abortSignal);
|
|
272
|
-
// If the caller already aborted before dispatch, return the worker to idle
|
|
273
|
-
// instead of retiring it — it was never posted to (#1674).
|
|
274
|
-
if (abortSignal?.aborted) {
|
|
275
|
-
this.release(worker);
|
|
276
|
-
return [];
|
|
277
|
-
}
|
|
278
|
-
let abandoned = false;
|
|
279
|
-
this.busy += 1;
|
|
280
|
-
try {
|
|
281
|
-
return await this.dispatch(worker, task, abortSignal, () => {
|
|
282
|
-
abandoned = true;
|
|
283
|
-
});
|
|
284
|
-
} finally {
|
|
285
|
-
this.busy -= 1;
|
|
286
|
-
if (abandoned) this.retireWorker(worker);
|
|
287
|
-
else this.release(worker);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async terminate(): Promise<void> {
|
|
292
|
-
if (this.terminated) return;
|
|
293
|
-
this.terminated = true;
|
|
294
|
-
// Reject all queued waiters so they don't hang indefinitely (#1674).
|
|
295
|
-
const queued = this.waiters;
|
|
296
|
-
this.waiters = [];
|
|
297
|
-
for (const w of queued) w.reject(new Error("archive-scoring pool terminated"));
|
|
298
|
-
const all = [...this.workers];
|
|
299
|
-
this.workers = [];
|
|
300
|
-
this.idle = [];
|
|
301
|
-
await Promise.allSettled(all.map((w) => w.terminate()));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async acquire(abortSignal?: AbortSignal): Promise<Worker> {
|
|
305
|
-
const idle = this.idle.pop();
|
|
306
|
-
if (idle) return idle;
|
|
307
|
-
if (this.workers.length < this.targetSize) return this.spawn();
|
|
308
|
-
// Park until a worker is released. If the caller aborts (or the pool
|
|
309
|
-
// terminates) while queued, reject so the recall falls back to sync
|
|
310
|
-
// instead of consuming a worker for a request that already timed out.
|
|
311
|
-
return new Promise<Worker>((resolve, reject) => {
|
|
312
|
-
const entry = { resolve, reject };
|
|
313
|
-
this.waiters.push(entry);
|
|
314
|
-
if (!abortSignal) return;
|
|
315
|
-
const onAbort = () => {
|
|
316
|
-
const idx = this.waiters.indexOf(entry);
|
|
317
|
-
if (idx !== -1) this.waiters.splice(idx, 1);
|
|
318
|
-
reject(new Error("archive-scoring acquire aborted"));
|
|
319
|
-
};
|
|
320
|
-
if (abortSignal.aborted) { onAbort(); return; }
|
|
321
|
-
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
private release(worker: Worker): void {
|
|
326
|
-
const next = this.waiters.shift();
|
|
327
|
-
if (next) {
|
|
328
|
-
next.resolve(worker);
|
|
329
|
-
} else if (!this.terminated) {
|
|
330
|
-
this.idle.push(worker);
|
|
331
|
-
} else {
|
|
332
|
-
void worker.terminate();
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/** Terminate a worker that may still be busy, then spawn a replacement
|
|
337
|
-
* if a waiter is queued. */
|
|
338
|
-
private retireWorker(worker: Worker): void {
|
|
339
|
-
const idx = this.workers.indexOf(worker);
|
|
340
|
-
if (idx !== -1) this.workers.splice(idx, 1);
|
|
341
|
-
void worker.terminate();
|
|
342
|
-
const next = this.waiters.shift();
|
|
343
|
-
if (next) next.resolve(this.spawn());
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
private spawn(): Worker {
|
|
347
|
-
const worker = new Worker(WORKER_SOURCE, { eval: true });
|
|
348
|
-
// Unref so idle workers never keep the event loop alive (#1674).
|
|
349
|
-
worker.unref();
|
|
350
|
-
this.workers.push(worker);
|
|
351
|
-
return worker;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
private dispatch(
|
|
355
|
-
worker: Worker,
|
|
356
|
-
task: ScoreTask,
|
|
357
|
-
abortSignal: AbortSignal | undefined,
|
|
358
|
-
onAbandon: () => void
|
|
359
|
-
): Promise<ArchiveScoreResult[]> {
|
|
360
|
-
return new Promise<ArchiveScoreResult[]>((resolve, reject) => {
|
|
361
|
-
let settled = false;
|
|
362
|
-
const timer = setTimeout(() => {
|
|
363
|
-
if (settled) return;
|
|
364
|
-
settled = true;
|
|
365
|
-
cleanup();
|
|
366
|
-
onAbandon();
|
|
367
|
-
log.debug(`archive-scoring dispatch timed out after ${DISPATCH_TIMEOUT_MS}ms — falling back to sync`);
|
|
368
|
-
reject(new Error(`archive-scoring dispatch timed out after ${DISPATCH_TIMEOUT_MS}ms`));
|
|
369
|
-
}, DISPATCH_TIMEOUT_MS);
|
|
370
|
-
|
|
371
|
-
const onMessage = (reply: ScoreReply) => {
|
|
372
|
-
if (settled) return;
|
|
373
|
-
settled = true;
|
|
374
|
-
cleanup();
|
|
375
|
-
if (reply.ok) resolve(reply.results);
|
|
376
|
-
else reject(new Error(reply.error));
|
|
377
|
-
};
|
|
378
|
-
const onError = (err: Error) => {
|
|
379
|
-
if (settled) return;
|
|
380
|
-
settled = true;
|
|
381
|
-
cleanup();
|
|
382
|
-
onAbandon();
|
|
383
|
-
reject(err);
|
|
384
|
-
};
|
|
385
|
-
// worker.terminate() ends via 'exit', not 'error' — listen so in-flight
|
|
386
|
-
// dispatches during pool shutdown reject immediately instead of hanging
|
|
387
|
-
// until the 120s timeout (#1674).
|
|
388
|
-
const onExit = (code: number) => {
|
|
389
|
-
if (settled) return;
|
|
390
|
-
settled = true;
|
|
391
|
-
cleanup();
|
|
392
|
-
onAbandon();
|
|
393
|
-
reject(new Error(`archive-scoring worker exited with code ${code}`));
|
|
394
|
-
};
|
|
395
|
-
const onAbort = () => {
|
|
396
|
-
if (settled) return;
|
|
397
|
-
settled = true;
|
|
398
|
-
cleanup();
|
|
399
|
-
onAbandon();
|
|
400
|
-
resolve([]);
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
const cleanup = () => {
|
|
404
|
-
clearTimeout(timer);
|
|
405
|
-
worker.off("message", onMessage);
|
|
406
|
-
worker.off("error", onError);
|
|
407
|
-
worker.off("exit", onExit);
|
|
408
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
worker.on("message", onMessage);
|
|
412
|
-
worker.on("error", onError);
|
|
413
|
-
worker.on("exit", onExit);
|
|
414
|
-
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
415
|
-
worker.postMessage(task);
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Off-thread scoring via a `worker_threads` pool.
|
|
422
|
-
*
|
|
423
|
-
* Concurrent `score()` calls are dispatched to separate workers, giving
|
|
424
|
-
* genuine multi-core parallelism: K concurrent recalls run on K cores
|
|
425
|
-
* instead of serializing on the main JS thread. If the pool cannot be
|
|
426
|
-
* created (e.g. restricted runtime), it transparently falls back to
|
|
427
|
-
* {@link SyncArchiveScoring} so recall never breaks.
|
|
428
|
-
*/
|
|
429
|
-
export class OffThreadArchiveScoring implements ArchiveScoringStrategy {
|
|
430
|
-
private pool: ArchiveScoringWorkerPool | null = null;
|
|
431
|
-
private poolFailed = false;
|
|
432
|
-
private readonly syncFallback = new SyncArchiveScoring();
|
|
433
|
-
|
|
434
|
-
constructor(poolSize?: number) {
|
|
435
|
-
if (poolSize !== undefined) {
|
|
436
|
-
this.pool = new ArchiveScoringWorkerPool(poolSize);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/** Workers currently scoring; 0 when no pool has been created yet. */
|
|
441
|
-
get busyWorkers(): number {
|
|
442
|
-
return this.pool?.busyWorkers ?? 0;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async score(
|
|
446
|
-
items: ReadonlyArray<ArchiveScoreItem>,
|
|
447
|
-
tokens: ReadonlyArray<string>,
|
|
448
|
-
abortSignal?: AbortSignal
|
|
449
|
-
): Promise<ArchiveScoreResult[]> {
|
|
450
|
-
if (items.length === 0 || tokens.length === 0) return [];
|
|
451
|
-
if (abortSignal?.aborted) return [];
|
|
452
|
-
|
|
453
|
-
// Lazy pool init — workers are only created when the cold-fallback path
|
|
454
|
-
// is first hit, so hot-path recall pays zero overhead.
|
|
455
|
-
if (this.pool === null && !this.poolFailed) {
|
|
456
|
-
try {
|
|
457
|
-
this.pool = new ArchiveScoringWorkerPool();
|
|
458
|
-
} catch (err) {
|
|
459
|
-
this.poolFailed = true;
|
|
460
|
-
log.debug(`archive-scoring: worker pool unavailable, using sync fallback — ${(err as Error).message}`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (this.pool !== null) {
|
|
465
|
-
const dispatchStart = Date.now();
|
|
466
|
-
try {
|
|
467
|
-
const task: ScoreTask = {
|
|
468
|
-
items: items as ArchiveScoreItem[],
|
|
469
|
-
tokens: tokens as string[],
|
|
470
|
-
};
|
|
471
|
-
const results = await this.pool.run(task, abortSignal);
|
|
472
|
-
if (abortSignal?.aborted) return [];
|
|
473
|
-
return results;
|
|
474
|
-
} catch (err) {
|
|
475
|
-
// Timeout or worker error — fall back to sync scoring so recall
|
|
476
|
-
// quality is never silently dropped. An already-aborted signal
|
|
477
|
-
// short-circuits first. If the dispatch consumed most of the timeout
|
|
478
|
-
// budget (genuine timeout, not a fast error), skip the sync rescore —
|
|
479
|
-
// the recall deadline has very likely expired by then (#1674).
|
|
480
|
-
if (abortSignal?.aborted) return [];
|
|
481
|
-
if (Date.now() - dispatchStart > DISPATCH_TIMEOUT_MS * 0.5) return [];
|
|
482
|
-
log.debug(`archive-scoring: worker dispatch failed, using sync fallback — ${(err as Error).message}`);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
return this.syncFallback.score(items, tokens, abortSignal);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/** @internal — terminate the underlying pool (tests / shutdown). */
|
|
490
|
-
async terminate(): Promise<void> {
|
|
491
|
-
if (this.pool !== null) {
|
|
492
|
-
await this.pool.terminate();
|
|
493
|
-
this.pool = null;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
499
|
-
// Factory + process-wide default
|
|
500
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
501
|
-
|
|
502
|
-
let defaultStrategy: ArchiveScoringStrategy | null = null;
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* Process-wide default archive-scoring strategy. Lazily creates an
|
|
506
|
-
* {@link OffThreadArchiveScoring} on first use. All orchestrator instances
|
|
507
|
-
* share one pool — a single daemon serves all concurrent sessions, so one
|
|
508
|
-
* shared pool is the correct sizing unit.
|
|
509
|
-
*/
|
|
510
|
-
export function getDefaultArchiveScoring(): ArchiveScoringStrategy {
|
|
511
|
-
if (defaultStrategy === null) {
|
|
512
|
-
defaultStrategy = new OffThreadArchiveScoring();
|
|
513
|
-
}
|
|
514
|
-
return defaultStrategy;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Dispose the process-wide default archive-scoring strategy, terminating any
|
|
519
|
-
* worker threads. Called from `Orchestrator.destroy()` so worker threads don't
|
|
520
|
-
* outlive the orchestrator (#1674). The strategy is lazily recreated on the
|
|
521
|
-
* next cold-fallback recall, so this is safe to call from tests that create
|
|
522
|
-
* and destroy orchestrator instances.
|
|
523
|
-
*/
|
|
524
|
-
export async function disposeDefaultArchiveScoring(): Promise<void> {
|
|
525
|
-
if (defaultStrategy !== null) {
|
|
526
|
-
const strategy = defaultStrategy;
|
|
527
|
-
defaultStrategy = null;
|
|
528
|
-
if (strategy instanceof OffThreadArchiveScoring) {
|
|
529
|
-
await strategy.terminate();
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Create a fresh strategy instance (for tests that need isolation or
|
|
536
|
-
* explicit control over pool size / sync vs off-thread).
|
|
537
|
-
*/
|
|
538
|
-
export function createArchiveScoring(opts?: {
|
|
539
|
-
poolSize?: number;
|
|
540
|
-
sync?: boolean;
|
|
541
|
-
}): ArchiveScoringStrategy {
|
|
542
|
-
if (opts?.sync) return new SyncArchiveScoring();
|
|
543
|
-
return new OffThreadArchiveScoring(opts?.poolSize);
|
|
544
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|