@remnic/core 9.3.644 → 9.3.646
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-cli.js +28 -27
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.js +16 -16
- package/dist/access-mcp.js +13 -13
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +11 -11
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +2 -2
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/causal-consolidation.js +3 -3
- package/dist/{chunk-2GCNTHRA.js → chunk-3JSWINVD.js} +98 -35
- package/dist/chunk-3JSWINVD.js.map +1 -0
- package/dist/{chunk-SZL2GGFY.js → chunk-4BISW7RX.js} +2 -2
- package/dist/{chunk-GYITUZFT.js → chunk-A4BTPHIN.js} +171 -134
- package/dist/chunk-A4BTPHIN.js.map +1 -0
- package/dist/{chunk-NV2MGIW7.js → chunk-ALUZN7BE.js} +2 -2
- package/dist/{chunk-RFSCIBCB.js → chunk-AMACWKM4.js} +2 -2
- package/dist/{chunk-AGNBY3VG.js → chunk-APJQ6UEA.js} +4 -4
- package/dist/{chunk-HRXCPKN4.js → chunk-APWJRJFW.js} +2 -2
- package/dist/{chunk-VDX2J7OX.js → chunk-BUKK5SWA.js} +2 -2
- package/dist/{chunk-RREI7CNM.js → chunk-D6WVJIS3.js} +9 -42
- package/dist/chunk-D6WVJIS3.js.map +1 -0
- package/dist/{chunk-EGBH5IPJ.js → chunk-DC66QVL2.js} +4 -4
- package/dist/chunk-EVWIEEKZ.js +315 -0
- package/dist/chunk-EVWIEEKZ.js.map +1 -0
- package/dist/{chunk-DM4US77W.js → chunk-FAYDM5WD.js} +6 -4
- package/dist/{chunk-DM4US77W.js.map → chunk-FAYDM5WD.js.map} +1 -1
- package/dist/{chunk-3UR4SBZF.js → chunk-H67ZTMTL.js} +20 -20
- package/dist/{chunk-3SF7QNKF.js → chunk-I3IWTRYB.js} +2 -2
- package/dist/{chunk-Y2XKJJEI.js → chunk-IMWFHBG2.js} +10 -33
- package/dist/chunk-IMWFHBG2.js.map +1 -0
- package/dist/{chunk-IHG27THY.js → chunk-JLOJ5RJ7.js} +68 -59
- package/dist/chunk-JLOJ5RJ7.js.map +1 -0
- package/dist/{chunk-AK4DSORQ.js → chunk-L7W5YW6Y.js} +7 -7
- package/dist/{chunk-4HYWWO3W.js → chunk-MCYT2RNT.js} +3 -3
- package/dist/{chunk-DGNQRNLL.js → chunk-NT5TINK5.js} +2 -2
- package/dist/{chunk-THWT7RJU.js → chunk-OR7R6M5Z.js} +2 -2
- package/dist/{chunk-4MNDVDBS.js → chunk-OTC2KOZ2.js} +2 -2
- package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-TTOYT5RB.js → chunk-PQFUUXWK.js} +2 -2
- package/dist/{chunk-CDHRUNBX.js → chunk-QKE4LHNR.js} +2 -2
- package/dist/{chunk-S2SEPLLA.js → chunk-RAELB5NX.js} +5 -5
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/chunk-S4DDLTPX.js +140 -0
- package/dist/chunk-S4DDLTPX.js.map +1 -0
- package/dist/{chunk-KVUG7FF7.js → chunk-U55D5UD5.js} +7 -7
- package/dist/{chunk-4HYLRROV.js → chunk-UMKPSD35.js} +2 -2
- package/dist/{chunk-NLPH23IQ.js → chunk-UMTG2BN2.js} +2 -2
- package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-D2B22JDF.js → chunk-WEPMT6SC.js} +7 -7
- package/dist/{chunk-ECMMM2VE.js → chunk-Z3PZRDLW.js} +2 -2
- package/dist/{chunk-GJSNDJHR.js → chunk-ZPPFKVSD.js} +4 -4
- package/dist/{chunk-TKNTAUA3.js → chunk-ZXWAQFDE.js} +2 -2
- package/dist/cli.js +37 -35
- package/dist/compounding/engine.js +2 -2
- package/dist/connectors/codex-materialize-runner.js +2 -2
- package/dist/connectors/index.js +2 -2
- package/dist/entity-retrieval.js +2 -2
- package/dist/{first-start-migration-FF7YFGRP.js → first-start-migration-PG5HBC3K.js} +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +91 -78
- package/dist/index.js.map +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +11 -11
- package/dist/maintenance/memory-governance.js +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +3 -3
- package/dist/namespaces/migrate.js +7 -7
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +2 -2
- package/dist/operator-toolkit.js +9 -9
- package/dist/orchestrator.js +23 -22
- package/dist/resume-bundles.js +3 -2
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +7 -7
- package/dist/semantic-consolidation.js +3 -3
- package/dist/semantic-rule-promotion.js +2 -2
- package/dist/semantic-rule-verifier.js +2 -2
- package/dist/session-identity.d.ts +118 -0
- package/dist/session-identity.js +21 -0
- package/dist/session-identity.js.map +1 -0
- package/dist/session-transcript-migration.d.ts +65 -0
- package/dist/session-transcript-migration.js +16 -0
- package/dist/session-transcript-migration.js.map +1 -0
- package/dist/storage.js +1 -1
- package/dist/summarizer.js +2 -1
- package/dist/transcript.d.ts +12 -6
- package/dist/transcript.js +2 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +2 -2
- package/package.json +1 -1
- package/src/cli.ts +79 -0
- package/src/index.ts +18 -0
- package/src/session-identity.test.ts +130 -0
- package/src/session-identity.ts +281 -0
- package/src/session-transcript-migration.test.ts +350 -0
- package/src/session-transcript-migration.ts +527 -0
- package/src/storage-fallback-category-dirs.test.ts +389 -0
- package/src/storage.ts +36 -4
- package/src/summarizer.ts +15 -35
- package/src/transcript-session-identity.test.ts +424 -0
- package/src/transcript.ts +100 -70
- package/src/utils/category-dir.ts +26 -0
- package/dist/chunk-2GCNTHRA.js.map +0 -1
- package/dist/chunk-GYITUZFT.js.map +0 -1
- package/dist/chunk-IHG27THY.js.map +0 -1
- package/dist/chunk-RREI7CNM.js.map +0 -1
- package/dist/chunk-Y2XKJJEI.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{chunk-SZL2GGFY.js.map → chunk-4BISW7RX.js.map} +0 -0
- /package/dist/{chunk-NV2MGIW7.js.map → chunk-ALUZN7BE.js.map} +0 -0
- /package/dist/{chunk-RFSCIBCB.js.map → chunk-AMACWKM4.js.map} +0 -0
- /package/dist/{chunk-AGNBY3VG.js.map → chunk-APJQ6UEA.js.map} +0 -0
- /package/dist/{chunk-HRXCPKN4.js.map → chunk-APWJRJFW.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-BUKK5SWA.js.map} +0 -0
- /package/dist/{chunk-EGBH5IPJ.js.map → chunk-DC66QVL2.js.map} +0 -0
- /package/dist/{chunk-3UR4SBZF.js.map → chunk-H67ZTMTL.js.map} +0 -0
- /package/dist/{chunk-3SF7QNKF.js.map → chunk-I3IWTRYB.js.map} +0 -0
- /package/dist/{chunk-AK4DSORQ.js.map → chunk-L7W5YW6Y.js.map} +0 -0
- /package/dist/{chunk-4HYWWO3W.js.map → chunk-MCYT2RNT.js.map} +0 -0
- /package/dist/{chunk-DGNQRNLL.js.map → chunk-NT5TINK5.js.map} +0 -0
- /package/dist/{chunk-THWT7RJU.js.map → chunk-OR7R6M5Z.js.map} +0 -0
- /package/dist/{chunk-4MNDVDBS.js.map → chunk-OTC2KOZ2.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-TTOYT5RB.js.map → chunk-PQFUUXWK.js.map} +0 -0
- /package/dist/{chunk-CDHRUNBX.js.map → chunk-QKE4LHNR.js.map} +0 -0
- /package/dist/{chunk-S2SEPLLA.js.map → chunk-RAELB5NX.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-KVUG7FF7.js.map → chunk-U55D5UD5.js.map} +0 -0
- /package/dist/{chunk-4HYLRROV.js.map → chunk-UMKPSD35.js.map} +0 -0
- /package/dist/{chunk-NLPH23IQ.js.map → chunk-UMTG2BN2.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-D2B22JDF.js.map → chunk-WEPMT6SC.js.map} +0 -0
- /package/dist/{chunk-ECMMM2VE.js.map → chunk-Z3PZRDLW.js.map} +0 -0
- /package/dist/{chunk-GJSNDJHR.js.map → chunk-ZPPFKVSD.js.map} +0 -0
- /package/dist/{chunk-TKNTAUA3.js.map → chunk-ZXWAQFDE.js.map} +0 -0
- /package/dist/{first-start-migration-FF7YFGRP.js.map → first-start-migration-PG5HBC3K.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/namespaces/migrate.ts"],"sourcesContent":["import path from \"node:path\";\nimport { access, lstat, mkdir, readdir, realpath, rename, rmdir } from \"node:fs/promises\";\nimport type { PluginConfig } from \"../types.js\";\nimport { NamespaceStorageRouter } from \"./storage.js\";\nimport { namespaceCollectionName } from \"./search.js\";\nimport { isSafeRouteNamespace } from \"../routing/engine.js\";\nimport { namespaceIdentityFromToken, namespaceIdentityToken } from \"./identity.js\";\nimport { ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\nconst LEGACY_NAMESPACE_CHILDREN = [\n ...ALL_CATEGORY_DIRS,\n \"entities\",\n \"artifacts\",\n \"identity\",\n \"state\",\n \"config\",\n \"summaries\",\n \"profile.md\",\n] as const;\n\nexport interface NamespaceInventoryEntry {\n namespace: string;\n rootDir: string;\n exists: boolean;\n usesLegacyRoot: boolean;\n hasMemoryData: boolean;\n collection: string;\n}\n\nexport interface NamespaceVerifyReport {\n ok: boolean;\n problems: string[];\n namespaces: NamespaceInventoryEntry[];\n}\n\nexport interface NamespaceMigrationMove {\n from: string;\n to: string;\n}\n\nexport interface NamespaceMigrationReport {\n dryRun: boolean;\n fromRoot: string;\n targetRoot: string;\n moved: NamespaceMigrationMove[];\n collection: string;\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function hasAnyLegacyData(rootDir: string): Promise<boolean> {\n for (const child of LEGACY_NAMESPACE_CHILDREN) {\n if (await exists(path.join(rootDir, child))) return true;\n }\n return false;\n}\n\nasync function discoverConfiguredNamespaces(\n config: PluginConfig,\n): Promise<string[]> {\n const discovered = new Set<string>([\n config.defaultNamespace,\n config.sharedNamespace,\n ...config.namespacePolicies.map((policy) => policy.name),\n ]);\n const configuredNamespaces = new Set(discovered);\n\n const namespacesDir = path.join(config.memoryDir, \"namespaces\");\n try {\n const entries = await readdir(namespacesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const namespace = configuredNamespaces.has(entry.name)\n ? entry.name\n : namespaceIdentityFromToken(entry.name) ?? entry.name;\n if (isSafeRouteNamespace(namespace)) {\n discovered.add(namespace);\n }\n }\n } catch {\n // No namespace directory yet.\n }\n\n return [...discovered];\n}\n\nexport async function listNamespaces(options: {\n config: PluginConfig;\n storageRouter?: NamespaceStorageRouter;\n}): Promise<NamespaceInventoryEntry[]> {\n const storageRouter = options.storageRouter ?? new NamespaceStorageRouter(options.config);\n const namespaces = await discoverConfiguredNamespaces(options.config);\n const items = await Promise.all(\n namespaces.map(async (namespace) => {\n const storage = await storageRouter.storageFor(namespace);\n const usesLegacyRoot =\n namespace === options.config.defaultNamespace &&\n storage.dir === options.config.memoryDir;\n return {\n namespace,\n rootDir: storage.dir,\n exists: await exists(storage.dir),\n usesLegacyRoot,\n hasMemoryData: await hasAnyLegacyData(storage.dir),\n collection: namespaceCollectionName(options.config.qmdCollection, namespace, {\n defaultNamespace: options.config.defaultNamespace,\n useLegacyDefaultCollection: usesLegacyRoot,\n }),\n } satisfies NamespaceInventoryEntry;\n }),\n );\n\n return items.sort((a, b) => a.namespace.localeCompare(b.namespace));\n}\n\nexport async function verifyNamespaces(options: {\n config: PluginConfig;\n storageRouter?: NamespaceStorageRouter;\n}): Promise<NamespaceVerifyReport> {\n const namespaces = await listNamespaces(options);\n const problems: string[] = [];\n\n for (const entry of namespaces) {\n if (entry.exists && !entry.hasMemoryData) {\n problems.push(`${entry.namespace}: root exists but contains no Engram data`);\n }\n }\n\n return {\n ok: problems.length === 0,\n problems,\n namespaces,\n };\n}\n\nexport async function runNamespaceMigration(options: {\n config: PluginConfig;\n to: string;\n dryRun?: boolean;\n renameFn?: typeof rename;\n}): Promise<NamespaceMigrationReport> {\n if (!options.config.namespacesEnabled) {\n throw new Error(\"Namespaces are disabled.\");\n }\n\n const targetNamespace = options.to.trim();\n if (!isSafeRouteNamespace(targetNamespace)) {\n throw new Error(`Invalid namespace: ${options.to}`);\n }\n\n const targetRoot = path.join(\n options.config.memoryDir,\n \"namespaces\",\n namespaceIdentityToken(targetNamespace),\n );\n const moved: NamespaceMigrationMove[] = [];\n const renamePath = options.renameFn ?? rename;\n\n for (const child of LEGACY_NAMESPACE_CHILDREN) {\n const from = path.join(options.config.memoryDir, child);\n if (!(await exists(from))) continue;\n const to = path.join(targetRoot, child);\n if (await exists(to)) {\n throw new Error(`Target already contains ${child}: ${to}`);\n }\n const sourceStat = await lstat(from);\n if (sourceStat.isSymbolicLink()) {\n throw new Error(`Refusing to migrate symlinked legacy path: ${from}`);\n }\n moved.push({ from, to });\n }\n\n if (!options.dryRun && moved.length > 0) {\n await assertSafeNamespaceTarget(options.config.memoryDir, targetRoot);\n await mkdir(targetRoot, { recursive: true });\n await assertSafeNamespaceTarget(options.config.memoryDir, targetRoot);\n const completed: NamespaceMigrationMove[] = [];\n try {\n for (const move of moved) {\n await renamePath(move.from, move.to);\n completed.push(move);\n }\n } catch (cause) {\n const rollbackErrors: string[] = [];\n for (const move of completed.reverse()) {\n try {\n if (!(await exists(move.from)) && (await exists(move.to))) {\n await rename(move.to, move.from);\n }\n } catch (rollbackCause) {\n rollbackErrors.push(rollbackCause instanceof Error ? rollbackCause.message : String(rollbackCause));\n }\n }\n try {\n await rmdir(targetRoot);\n } catch {\n // Target may predate migration or still contain unrelated files.\n }\n const message = cause instanceof Error ? cause.message : String(cause);\n throw new Error(\n `Namespace migration failed and was rolled back: ${message}` +\n (rollbackErrors.length > 0 ? `; rollback errors: ${rollbackErrors.join(\"; \")}` : \"\"),\n { cause },\n );\n }\n }\n\n return {\n dryRun: options.dryRun === true,\n fromRoot: options.config.memoryDir,\n targetRoot,\n moved,\n collection: namespaceCollectionName(options.config.qmdCollection, targetNamespace, {\n defaultNamespace: options.config.defaultNamespace,\n useLegacyDefaultCollection: false,\n }),\n };\n}\n\nasync function assertSafeNamespaceTarget(memoryDir: string, targetRoot: string): Promise<void> {\n const memoryReal = await realpath(memoryDir);\n const namespacesDir = path.join(memoryDir, \"namespaces\");\n if (await exists(namespacesDir)) {\n const namespacesStat = await lstat(namespacesDir);\n if (namespacesStat.isSymbolicLink()) {\n throw new Error(`Refusing to migrate through symlinked namespaces directory: ${namespacesDir}`);\n }\n const namespacesReal = await realpath(namespacesDir);\n if (!isPathInside(memoryReal, namespacesReal)) {\n throw new Error(`Refusing to migrate through namespaces directory outside memoryDir: ${namespacesDir}`);\n }\n }\n if (await exists(targetRoot)) {\n const targetStat = await lstat(targetRoot);\n if (targetStat.isSymbolicLink()) {\n throw new Error(`Refusing to migrate into symlinked namespace root: ${targetRoot}`);\n }\n const targetReal = await realpath(targetRoot);\n if (!isPathInside(memoryReal, targetReal)) {\n throw new Error(`Refusing to migrate into namespace root outside memoryDir: ${targetRoot}`);\n }\n }\n}\n\nfunction isPathInside(root: string, child: string): boolean {\n const relative = path.relative(root, child);\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/namespaces/migrate.ts"],"sourcesContent":["import path from \"node:path\";\nimport { access, lstat, mkdir, readdir, realpath, rename, rmdir } from \"node:fs/promises\";\nimport type { PluginConfig } from \"../types.js\";\nimport { NamespaceStorageRouter } from \"./storage.js\";\nimport { namespaceCollectionName } from \"./search.js\";\nimport { isSafeRouteNamespace } from \"../routing/engine.js\";\nimport { namespaceIdentityFromToken, namespaceIdentityToken } from \"./identity.js\";\nimport { ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\nconst LEGACY_NAMESPACE_CHILDREN = [\n ...ALL_CATEGORY_DIRS,\n \"entities\",\n \"artifacts\",\n \"identity\",\n \"state\",\n \"config\",\n \"summaries\",\n \"profile.md\",\n] as const;\n\nexport interface NamespaceInventoryEntry {\n namespace: string;\n rootDir: string;\n exists: boolean;\n usesLegacyRoot: boolean;\n hasMemoryData: boolean;\n collection: string;\n}\n\nexport interface NamespaceVerifyReport {\n ok: boolean;\n problems: string[];\n namespaces: NamespaceInventoryEntry[];\n}\n\nexport interface NamespaceMigrationMove {\n from: string;\n to: string;\n}\n\nexport interface NamespaceMigrationReport {\n dryRun: boolean;\n fromRoot: string;\n targetRoot: string;\n moved: NamespaceMigrationMove[];\n collection: string;\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function hasAnyLegacyData(rootDir: string): Promise<boolean> {\n for (const child of LEGACY_NAMESPACE_CHILDREN) {\n if (await exists(path.join(rootDir, child))) return true;\n }\n return false;\n}\n\nasync function discoverConfiguredNamespaces(\n config: PluginConfig,\n): Promise<string[]> {\n const discovered = new Set<string>([\n config.defaultNamespace,\n config.sharedNamespace,\n ...config.namespacePolicies.map((policy) => policy.name),\n ]);\n const configuredNamespaces = new Set(discovered);\n\n const namespacesDir = path.join(config.memoryDir, \"namespaces\");\n try {\n const entries = await readdir(namespacesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const namespace = configuredNamespaces.has(entry.name)\n ? entry.name\n : namespaceIdentityFromToken(entry.name) ?? entry.name;\n if (isSafeRouteNamespace(namespace)) {\n discovered.add(namespace);\n }\n }\n } catch {\n // No namespace directory yet.\n }\n\n return [...discovered];\n}\n\nexport async function listNamespaces(options: {\n config: PluginConfig;\n storageRouter?: NamespaceStorageRouter;\n}): Promise<NamespaceInventoryEntry[]> {\n const storageRouter = options.storageRouter ?? new NamespaceStorageRouter(options.config);\n const namespaces = await discoverConfiguredNamespaces(options.config);\n const items = await Promise.all(\n namespaces.map(async (namespace) => {\n const storage = await storageRouter.storageFor(namespace);\n const usesLegacyRoot =\n namespace === options.config.defaultNamespace &&\n storage.dir === options.config.memoryDir;\n return {\n namespace,\n rootDir: storage.dir,\n exists: await exists(storage.dir),\n usesLegacyRoot,\n hasMemoryData: await hasAnyLegacyData(storage.dir),\n collection: namespaceCollectionName(options.config.qmdCollection, namespace, {\n defaultNamespace: options.config.defaultNamespace,\n useLegacyDefaultCollection: usesLegacyRoot,\n }),\n } satisfies NamespaceInventoryEntry;\n }),\n );\n\n return items.sort((a, b) => a.namespace.localeCompare(b.namespace));\n}\n\nexport async function verifyNamespaces(options: {\n config: PluginConfig;\n storageRouter?: NamespaceStorageRouter;\n}): Promise<NamespaceVerifyReport> {\n const namespaces = await listNamespaces(options);\n const problems: string[] = [];\n\n for (const entry of namespaces) {\n if (entry.exists && !entry.hasMemoryData) {\n problems.push(`${entry.namespace}: root exists but contains no Engram data`);\n }\n }\n\n return {\n ok: problems.length === 0,\n problems,\n namespaces,\n };\n}\n\nexport async function runNamespaceMigration(options: {\n config: PluginConfig;\n to: string;\n dryRun?: boolean;\n renameFn?: typeof rename;\n}): Promise<NamespaceMigrationReport> {\n if (!options.config.namespacesEnabled) {\n throw new Error(\"Namespaces are disabled.\");\n }\n\n const targetNamespace = options.to.trim();\n if (!isSafeRouteNamespace(targetNamespace)) {\n throw new Error(`Invalid namespace: ${options.to}`);\n }\n\n const targetRoot = path.join(\n options.config.memoryDir,\n \"namespaces\",\n namespaceIdentityToken(targetNamespace),\n );\n const moved: NamespaceMigrationMove[] = [];\n const renamePath = options.renameFn ?? rename;\n\n for (const child of LEGACY_NAMESPACE_CHILDREN) {\n const from = path.join(options.config.memoryDir, child);\n if (!(await exists(from))) continue;\n const to = path.join(targetRoot, child);\n if (await exists(to)) {\n throw new Error(`Target already contains ${child}: ${to}`);\n }\n const sourceStat = await lstat(from);\n if (sourceStat.isSymbolicLink()) {\n throw new Error(`Refusing to migrate symlinked legacy path: ${from}`);\n }\n moved.push({ from, to });\n }\n\n if (!options.dryRun && moved.length > 0) {\n await assertSafeNamespaceTarget(options.config.memoryDir, targetRoot);\n await mkdir(targetRoot, { recursive: true });\n await assertSafeNamespaceTarget(options.config.memoryDir, targetRoot);\n const completed: NamespaceMigrationMove[] = [];\n try {\n for (const move of moved) {\n await renamePath(move.from, move.to);\n completed.push(move);\n }\n } catch (cause) {\n const rollbackErrors: string[] = [];\n for (const move of completed.reverse()) {\n try {\n if (!(await exists(move.from)) && (await exists(move.to))) {\n await rename(move.to, move.from);\n }\n } catch (rollbackCause) {\n rollbackErrors.push(rollbackCause instanceof Error ? rollbackCause.message : String(rollbackCause));\n }\n }\n try {\n await rmdir(targetRoot);\n } catch {\n // Target may predate migration or still contain unrelated files.\n }\n const message = cause instanceof Error ? cause.message : String(cause);\n throw new Error(\n `Namespace migration failed and was rolled back: ${message}` +\n (rollbackErrors.length > 0 ? `; rollback errors: ${rollbackErrors.join(\"; \")}` : \"\"),\n { cause },\n );\n }\n }\n\n return {\n dryRun: options.dryRun === true,\n fromRoot: options.config.memoryDir,\n targetRoot,\n moved,\n collection: namespaceCollectionName(options.config.qmdCollection, targetNamespace, {\n defaultNamespace: options.config.defaultNamespace,\n useLegacyDefaultCollection: false,\n }),\n };\n}\n\nasync function assertSafeNamespaceTarget(memoryDir: string, targetRoot: string): Promise<void> {\n const memoryReal = await realpath(memoryDir);\n const namespacesDir = path.join(memoryDir, \"namespaces\");\n if (await exists(namespacesDir)) {\n const namespacesStat = await lstat(namespacesDir);\n if (namespacesStat.isSymbolicLink()) {\n throw new Error(`Refusing to migrate through symlinked namespaces directory: ${namespacesDir}`);\n }\n const namespacesReal = await realpath(namespacesDir);\n if (!isPathInside(memoryReal, namespacesReal)) {\n throw new Error(`Refusing to migrate through namespaces directory outside memoryDir: ${namespacesDir}`);\n }\n }\n if (await exists(targetRoot)) {\n const targetStat = await lstat(targetRoot);\n if (targetStat.isSymbolicLink()) {\n throw new Error(`Refusing to migrate into symlinked namespace root: ${targetRoot}`);\n }\n const targetReal = await realpath(targetRoot);\n if (!isPathInside(memoryReal, targetReal)) {\n throw new Error(`Refusing to migrate into namespace root outside memoryDir: ${targetRoot}`);\n }\n }\n}\n\nfunction isPathInside(root: string, child: string): boolean {\n const relative = path.relative(root, child);\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,OAAO,UAAU;AACjB,SAAS,QAAQ,OAAO,OAAO,SAAS,UAAU,QAAQ,aAAa;AAQvE,IAAM,4BAA4B;AAAA,EAChC,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA8BA,eAAe,OAAO,GAA6B;AACjD,MAAI;AACF,UAAM,OAAO,CAAC;AACd,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,iBAAiB,SAAmC;AACjE,aAAW,SAAS,2BAA2B;AAC7C,QAAI,MAAM,OAAO,KAAK,KAAK,SAAS,KAAK,CAAC,EAAG,QAAO;AAAA,EACtD;AACA,SAAO;AACT;AAEA,eAAe,6BACb,QACmB;AACnB,QAAM,aAAa,oBAAI,IAAY;AAAA,IACjC,OAAO;AAAA,IACP,OAAO;AAAA,IACP,GAAG,OAAO,kBAAkB,IAAI,CAAC,WAAW,OAAO,IAAI;AAAA,EACzD,CAAC;AACD,QAAM,uBAAuB,IAAI,IAAI,UAAU;AAE/C,QAAM,gBAAgB,KAAK,KAAK,OAAO,WAAW,YAAY;AAC9D,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,eAAe,EAAE,eAAe,KAAK,CAAC;AACpE,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,YAAM,YAAY,qBAAqB,IAAI,MAAM,IAAI,IACjD,MAAM,OACN,2BAA2B,MAAM,IAAI,KAAK,MAAM;AACpD,UAAI,qBAAqB,SAAS,GAAG;AACnC,mBAAW,IAAI,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,CAAC,GAAG,UAAU;AACvB;AAEA,eAAsB,eAAe,SAGE;AACrC,QAAM,gBAAgB,QAAQ,iBAAiB,IAAI,uBAAuB,QAAQ,MAAM;AACxF,QAAM,aAAa,MAAM,6BAA6B,QAAQ,MAAM;AACpE,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,WAAW,IAAI,OAAO,cAAc;AAClC,YAAM,UAAU,MAAM,cAAc,WAAW,SAAS;AACxD,YAAM,iBACJ,cAAc,QAAQ,OAAO,oBAC7B,QAAQ,QAAQ,QAAQ,OAAO;AACjC,aAAO;AAAA,QACL;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,QAAQ,MAAM,OAAO,QAAQ,GAAG;AAAA,QAChC;AAAA,QACA,eAAe,MAAM,iBAAiB,QAAQ,GAAG;AAAA,QACjD,YAAY,wBAAwB,QAAQ,OAAO,eAAe,WAAW;AAAA,UAC3E,kBAAkB,QAAQ,OAAO;AAAA,UACjC,4BAA4B;AAAA,QAC9B,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AACpE;AAEA,eAAsB,iBAAiB,SAGJ;AACjC,QAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,QAAM,WAAqB,CAAC;AAE5B,aAAW,SAAS,YAAY;AAC9B,QAAI,MAAM,UAAU,CAAC,MAAM,eAAe;AACxC,eAAS,KAAK,GAAG,MAAM,SAAS,2CAA2C;AAAA,IAC7E;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,SAAS,WAAW;AAAA,IACxB;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,sBAAsB,SAKN;AACpC,MAAI,CAAC,QAAQ,OAAO,mBAAmB;AACrC,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,QAAM,kBAAkB,QAAQ,GAAG,KAAK;AACxC,MAAI,CAAC,qBAAqB,eAAe,GAAG;AAC1C,UAAM,IAAI,MAAM,sBAAsB,QAAQ,EAAE,EAAE;AAAA,EACpD;AAEA,QAAM,aAAa,KAAK;AAAA,IACtB,QAAQ,OAAO;AAAA,IACf;AAAA,IACA,uBAAuB,eAAe;AAAA,EACxC;AACA,QAAM,QAAkC,CAAC;AACzC,QAAM,aAAa,QAAQ,YAAY;AAEvC,aAAW,SAAS,2BAA2B;AAC7C,UAAM,OAAO,KAAK,KAAK,QAAQ,OAAO,WAAW,KAAK;AACtD,QAAI,CAAE,MAAM,OAAO,IAAI,EAAI;AAC3B,UAAM,KAAK,KAAK,KAAK,YAAY,KAAK;AACtC,QAAI,MAAM,OAAO,EAAE,GAAG;AACpB,YAAM,IAAI,MAAM,2BAA2B,KAAK,KAAK,EAAE,EAAE;AAAA,IAC3D;AACA,UAAM,aAAa,MAAM,MAAM,IAAI;AACnC,QAAI,WAAW,eAAe,GAAG;AAC/B,YAAM,IAAI,MAAM,8CAA8C,IAAI,EAAE;AAAA,IACtE;AACA,UAAM,KAAK,EAAE,MAAM,GAAG,CAAC;AAAA,EACzB;AAEA,MAAI,CAAC,QAAQ,UAAU,MAAM,SAAS,GAAG;AACvC,UAAM,0BAA0B,QAAQ,OAAO,WAAW,UAAU;AACpE,UAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAC3C,UAAM,0BAA0B,QAAQ,OAAO,WAAW,UAAU;AACpE,UAAM,YAAsC,CAAC;AAC7C,QAAI;AACF,iBAAW,QAAQ,OAAO;AACxB,cAAM,WAAW,KAAK,MAAM,KAAK,EAAE;AACnC,kBAAU,KAAK,IAAI;AAAA,MACrB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,iBAA2B,CAAC;AAClC,iBAAW,QAAQ,UAAU,QAAQ,GAAG;AACtC,YAAI;AACF,cAAI,CAAE,MAAM,OAAO,KAAK,IAAI,KAAO,MAAM,OAAO,KAAK,EAAE,GAAI;AACzD,kBAAM,OAAO,KAAK,IAAI,KAAK,IAAI;AAAA,UACjC;AAAA,QACF,SAAS,eAAe;AACtB,yBAAe,KAAK,yBAAyB,QAAQ,cAAc,UAAU,OAAO,aAAa,CAAC;AAAA,QACpG;AAAA,MACF;AACA,UAAI;AACF,cAAM,MAAM,UAAU;AAAA,MACxB,QAAQ;AAAA,MAER;AACA,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAM,IAAI;AAAA,QACR,mDAAmD,OAAO,MACvD,eAAe,SAAS,IAAI,sBAAsB,eAAe,KAAK,IAAI,CAAC,KAAK;AAAA,QACnF,EAAE,MAAM;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ,QAAQ,WAAW;AAAA,IAC3B,UAAU,QAAQ,OAAO;AAAA,IACzB;AAAA,IACA;AAAA,IACA,YAAY,wBAAwB,QAAQ,OAAO,eAAe,iBAAiB;AAAA,MACjF,kBAAkB,QAAQ,OAAO;AAAA,MACjC,4BAA4B;AAAA,IAC9B,CAAC;AAAA,EACH;AACF;AAEA,eAAe,0BAA0B,WAAmB,YAAmC;AAC7F,QAAM,aAAa,MAAM,SAAS,SAAS;AAC3C,QAAM,gBAAgB,KAAK,KAAK,WAAW,YAAY;AACvD,MAAI,MAAM,OAAO,aAAa,GAAG;AAC/B,UAAM,iBAAiB,MAAM,MAAM,aAAa;AAChD,QAAI,eAAe,eAAe,GAAG;AACnC,YAAM,IAAI,MAAM,+DAA+D,aAAa,EAAE;AAAA,IAChG;AACA,UAAM,iBAAiB,MAAM,SAAS,aAAa;AACnD,QAAI,CAAC,aAAa,YAAY,cAAc,GAAG;AAC7C,YAAM,IAAI,MAAM,uEAAuE,aAAa,EAAE;AAAA,IACxG;AAAA,EACF;AACA,MAAI,MAAM,OAAO,UAAU,GAAG;AAC5B,UAAM,aAAa,MAAM,MAAM,UAAU;AACzC,QAAI,WAAW,eAAe,GAAG;AAC/B,YAAM,IAAI,MAAM,sDAAsD,UAAU,EAAE;AAAA,IACpF;AACA,UAAM,aAAa,MAAM,SAAS,UAAU;AAC5C,QAAI,CAAC,aAAa,YAAY,UAAU,GAAG;AACzC,YAAM,IAAI,MAAM,8DAA8D,UAAU,EAAE;AAAA,IAC5F;AAAA,EACF;AACF;AAEA,SAAS,aAAa,MAAc,OAAwB;AAC1D,QAAM,WAAW,KAAK,SAAS,MAAM,KAAK;AAC1C,SAAO,aAAa,MAAO,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,KAAK,WAAW,QAAQ;AACpF;","names":[]}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "./chunk-666A3MOW.js";
|
|
20
20
|
import {
|
|
21
21
|
LcmEngine
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WEPMT6SC.js";
|
|
23
23
|
import {
|
|
24
24
|
chunkTranscriptEntries
|
|
25
25
|
} from "./chunk-TECVW3JP.js";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
CompoundingEngine,
|
|
31
31
|
defaultTierMigrationCycleBudget
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-Z3PZRDLW.js";
|
|
33
33
|
import {
|
|
34
34
|
SharedContextManager
|
|
35
35
|
} from "./chunk-DRD2Q7HQ.js";
|
|
@@ -38,6 +38,9 @@ import {
|
|
|
38
38
|
applyUtilityRankingRuntimeDelta,
|
|
39
39
|
loadUtilityRuntimeValues
|
|
40
40
|
} from "./chunk-LXOM6IQU.js";
|
|
41
|
+
import {
|
|
42
|
+
TierMigrationExecutor
|
|
43
|
+
} from "./chunk-VOUOLGIP.js";
|
|
41
44
|
import {
|
|
42
45
|
decideTierTransition
|
|
43
46
|
} from "./chunk-S75M5ZRK.js";
|
|
@@ -47,6 +50,9 @@ import {
|
|
|
47
50
|
import {
|
|
48
51
|
extractTopics
|
|
49
52
|
} from "./chunk-UHGBNIOS.js";
|
|
53
|
+
import {
|
|
54
|
+
HourlySummarizer
|
|
55
|
+
} from "./chunk-IMWFHBG2.js";
|
|
50
56
|
import {
|
|
51
57
|
buildTargetedFactRecallSection,
|
|
52
58
|
shouldRecallTargetedFactEvidence
|
|
@@ -59,12 +65,6 @@ import {
|
|
|
59
65
|
import {
|
|
60
66
|
isValidAsOf
|
|
61
67
|
} from "./chunk-MDYG7VI7.js";
|
|
62
|
-
import {
|
|
63
|
-
TierMigrationExecutor
|
|
64
|
-
} from "./chunk-VOUOLGIP.js";
|
|
65
|
-
import {
|
|
66
|
-
HourlySummarizer
|
|
67
|
-
} from "./chunk-Y2XKJJEI.js";
|
|
68
68
|
import {
|
|
69
69
|
SessionObserverState
|
|
70
70
|
} from "./chunk-MHQC2WU2.js";
|
|
@@ -175,7 +175,7 @@ import {
|
|
|
175
175
|
buildEntityRecallSection,
|
|
176
176
|
entityRecentTranscriptLookbackHours,
|
|
177
177
|
readRecentEntityTranscriptEntries
|
|
178
|
-
} from "./chunk-
|
|
178
|
+
} from "./chunk-ALUZN7BE.js";
|
|
179
179
|
import {
|
|
180
180
|
buildEventOrderRecallSection,
|
|
181
181
|
shouldRecallEventOrderEvidence
|
|
@@ -210,7 +210,7 @@ import {
|
|
|
210
210
|
materializeAfterSemanticConsolidation,
|
|
211
211
|
parseConsolidationResponse,
|
|
212
212
|
parseOperatorAwareConsolidationResponse
|
|
213
|
-
} from "./chunk-
|
|
213
|
+
} from "./chunk-ZXWAQFDE.js";
|
|
214
214
|
import {
|
|
215
215
|
normalizeReplaySessionKey
|
|
216
216
|
} from "./chunk-2PRQG7PV.js";
|
|
@@ -219,22 +219,22 @@ import {
|
|
|
219
219
|
} from "./chunk-X6IRLNOO.js";
|
|
220
220
|
import {
|
|
221
221
|
searchVerifiedEpisodes
|
|
222
|
-
} from "./chunk-
|
|
222
|
+
} from "./chunk-UMKPSD35.js";
|
|
223
223
|
import {
|
|
224
224
|
ThreadingManager
|
|
225
225
|
} from "./chunk-W4RVMTHR.js";
|
|
226
226
|
import {
|
|
227
227
|
searchVerifiedSemanticRules
|
|
228
|
-
} from "./chunk-
|
|
228
|
+
} from "./chunk-4BISW7RX.js";
|
|
229
229
|
import {
|
|
230
230
|
searchWorkProductLedgerEntries
|
|
231
231
|
} from "./chunk-ZRWB5D4H.js";
|
|
232
232
|
import {
|
|
233
233
|
TranscriptManager
|
|
234
|
-
} from "./chunk-
|
|
234
|
+
} from "./chunk-JLOJ5RJ7.js";
|
|
235
235
|
import {
|
|
236
236
|
NamespaceStorageRouter
|
|
237
|
-
} from "./chunk-
|
|
237
|
+
} from "./chunk-D6WVJIS3.js";
|
|
238
238
|
import {
|
|
239
239
|
PolicyRuntimeManager
|
|
240
240
|
} from "./chunk-33JBK2XP.js";
|
|
@@ -317,11 +317,11 @@ import {
|
|
|
317
317
|
} from "./chunk-BEMWL2FZ.js";
|
|
318
318
|
import {
|
|
319
319
|
NamespaceSearchRouter
|
|
320
|
-
} from "./chunk-
|
|
320
|
+
} from "./chunk-APWJRJFW.js";
|
|
321
321
|
import {
|
|
322
322
|
createConversationIndexRuntime,
|
|
323
323
|
createSearchBackend
|
|
324
|
-
} from "./chunk-
|
|
324
|
+
} from "./chunk-U55D5UD5.js";
|
|
325
325
|
import {
|
|
326
326
|
NoopSearchBackend
|
|
327
327
|
} from "./chunk-CYEPCZN5.js";
|
|
@@ -352,7 +352,7 @@ import {
|
|
|
352
352
|
} from "./chunk-Y56J7CXW.js";
|
|
353
353
|
import {
|
|
354
354
|
buildChainFollowupGenerator
|
|
355
|
-
} from "./chunk-
|
|
355
|
+
} from "./chunk-AMACWKM4.js";
|
|
356
356
|
import {
|
|
357
357
|
ContentHashIndex,
|
|
358
358
|
StorageManager,
|
|
@@ -362,7 +362,7 @@ import {
|
|
|
362
362
|
normalizeEntityName,
|
|
363
363
|
parseEntityFile,
|
|
364
364
|
stripAttributesSuffix
|
|
365
|
-
} from "./chunk-
|
|
365
|
+
} from "./chunk-A4BTPHIN.js";
|
|
366
366
|
import {
|
|
367
367
|
isValidTranscriptDate,
|
|
368
368
|
loadSpeakerRegistry,
|
|
@@ -3355,7 +3355,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
3355
3355
|
if (signal.aborted) return;
|
|
3356
3356
|
if (this.config.lifecyclePolicyEnabled && this.config.qmdTierMigrationEnabled) {
|
|
3357
3357
|
try {
|
|
3358
|
-
const { runFirstStartMigration } = await import("./first-start-migration-
|
|
3358
|
+
const { runFirstStartMigration } = await import("./first-start-migration-PG5HBC3K.js");
|
|
3359
3359
|
const result = await runFirstStartMigration({
|
|
3360
3360
|
storage: this.storage,
|
|
3361
3361
|
config: this.config,
|
|
@@ -14302,4 +14302,4 @@ export {
|
|
|
14302
14302
|
resolvePersistedMemoryRelativePath,
|
|
14303
14303
|
Orchestrator
|
|
14304
14304
|
};
|
|
14305
|
-
//# sourceMappingURL=chunk-
|
|
14305
|
+
//# sourceMappingURL=chunk-H67ZTMTL.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-ZRWB5D4H.js";
|
|
5
5
|
import {
|
|
6
6
|
TranscriptManager
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JLOJ5RJ7.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveCommitmentLedgerDir,
|
|
10
10
|
validateCommitmentLedgerEntry
|
|
@@ -268,4 +268,4 @@ export {
|
|
|
268
268
|
recordResumeBundle,
|
|
269
269
|
getResumeBundleStatus
|
|
270
270
|
};
|
|
271
|
-
//# sourceMappingURL=chunk-
|
|
271
|
+
//# sourceMappingURL=chunk-I3IWTRYB.js.map
|
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
LocalLlmClient
|
|
11
11
|
} from "./chunk-H3HDXD3U.js";
|
|
12
|
+
import {
|
|
13
|
+
sessionStoragePaths
|
|
14
|
+
} from "./chunk-S4DDLTPX.js";
|
|
12
15
|
import {
|
|
13
16
|
FallbackLlmClient,
|
|
14
17
|
fallbackLlmRuntimeContextFromConfig,
|
|
@@ -16,10 +19,7 @@ import {
|
|
|
16
19
|
} from "./chunk-A3BS64GV.js";
|
|
17
20
|
import {
|
|
18
21
|
encodeStoragePathSegment,
|
|
19
|
-
|
|
20
|
-
isSafeLegacyPathSegment,
|
|
21
|
-
resolveSafeStoragePath,
|
|
22
|
-
storagePathHash
|
|
22
|
+
resolveSafeStoragePath
|
|
23
23
|
} from "./chunk-FF4KLI5W.js";
|
|
24
24
|
import {
|
|
25
25
|
extractJsonCandidates
|
|
@@ -639,40 +639,16 @@ ${truncatedConversation}`;
|
|
|
639
639
|
}
|
|
640
640
|
// Get transcript entries for a session within a time range
|
|
641
641
|
async getTranscriptEntries(sessionKey, startTime, endTime) {
|
|
642
|
-
const
|
|
643
|
-
let channelType = "other";
|
|
644
|
-
let channelId = "default";
|
|
645
|
-
if (parts.length >= 3) {
|
|
646
|
-
channelType = parts[2];
|
|
647
|
-
if (channelType === "main") {
|
|
648
|
-
channelId = "default";
|
|
649
|
-
} else if (channelType === "discord" && parts.length >= 5 && parts[3] === "channel") {
|
|
650
|
-
channelId = parts[4];
|
|
651
|
-
} else if (channelType === "slack" && parts.length >= 5 && parts[3] === "channel") {
|
|
652
|
-
channelId = parts[4];
|
|
653
|
-
} else if (channelType === "cron" && parts.length >= 4) {
|
|
654
|
-
channelId = parts[3];
|
|
655
|
-
} else if (parts.length >= 4) {
|
|
656
|
-
channelId = parts[3];
|
|
657
|
-
}
|
|
658
|
-
}
|
|
642
|
+
const paths = sessionStoragePaths(sessionKey);
|
|
659
643
|
try {
|
|
660
644
|
const transcriptRoot = path.join(this.config.memoryDir, "transcripts");
|
|
661
|
-
const encodedDir = path.join(
|
|
662
|
-
encodeStoragePathSegment(channelType),
|
|
663
|
-
encodeStoragePathSegment(channelId)
|
|
664
|
-
);
|
|
665
|
-
const alternateDir = path.join(
|
|
666
|
-
encodeStoragePathSegmentWithHash(channelType),
|
|
667
|
-
`${encodeStoragePathSegmentWithHash(channelId)}--session-${storagePathHash(sessionKey)}`
|
|
668
|
-
);
|
|
669
|
-
const legacyDir = isSafeLegacyPathSegment(channelType) && isSafeLegacyPathSegment(channelId) ? path.join(channelType, channelId) : void 0;
|
|
670
645
|
const candidateDirs = new Set(
|
|
671
|
-
[
|
|
646
|
+
[paths.dir, paths.alternateDir, paths.legacyDir, ...paths.readbackDirs].filter(
|
|
672
647
|
(dir) => typeof dir === "string" && dir.length > 0
|
|
673
648
|
)
|
|
674
649
|
);
|
|
675
650
|
const entries = [];
|
|
651
|
+
const seenRawLines = /* @__PURE__ */ new Set();
|
|
676
652
|
for (const candidateDir of candidateDirs) {
|
|
677
653
|
const transcriptDir = await resolveSafeStoragePath(transcriptRoot, candidateDir).catch(() => null);
|
|
678
654
|
if (transcriptDir === null) continue;
|
|
@@ -689,7 +665,8 @@ ${truncatedConversation}`;
|
|
|
689
665
|
try {
|
|
690
666
|
const entry = JSON.parse(line);
|
|
691
667
|
const entryTime = new Date(entry.timestamp).getTime();
|
|
692
|
-
if (entry.sessionKey === sessionKey && entryTime >= startTime.getTime() && entryTime < endTime.getTime()) {
|
|
668
|
+
if (entry.sessionKey === sessionKey && entryTime >= startTime.getTime() && entryTime < endTime.getTime() && !seenRawLines.has(line)) {
|
|
669
|
+
seenRawLines.add(line);
|
|
693
670
|
entries.push(entry);
|
|
694
671
|
}
|
|
695
672
|
} catch {
|
|
@@ -709,4 +686,4 @@ ${truncatedConversation}`;
|
|
|
709
686
|
export {
|
|
710
687
|
HourlySummarizer
|
|
711
688
|
};
|
|
712
|
-
//# sourceMappingURL=chunk-
|
|
689
|
+
//# sourceMappingURL=chunk-IMWFHBG2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/summarizer.ts"],"sourcesContent":["import { mkdir, readFile, writeFile, readdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { log } from \"./logger.js\";\nimport { LocalLlmClient } from \"./local-llm.js\";\nimport { FallbackLlmClient, fallbackLlmRuntimeContextFromConfig, gatewayTaskChainOptions } from \"./fallback-llm.js\";\nimport { ModelRegistry } from \"./model-registry.js\";\nimport { extractJsonCandidates } from \"./json-extract.js\";\nimport type { HourlySummary, TranscriptEntry, PluginConfig, GatewayConfig } from \"./types.js\";\nimport type { TranscriptManager } from \"./transcript.js\";\nimport { readSummarySnapshot, upsertSummarySnapshot, writeSummarySnapshot } from \"./summary-snapshot.js\";\nimport {\n encodeStoragePathSegment,\n resolveSafeStoragePath,\n} from \"./storage-paths.js\";\nimport { sessionStoragePaths } from \"./session-identity.js\";\n\n// Schema for LLM summary output\nconst HourlySummarySchema = z.object({\n bullets: z\n .array(z.string().trim().min(1))\n .min(1)\n .describe(\"3-5 bullet points summarizing the hour's activity\"),\n});\n\ntype HourlySummaryResult = z.infer<typeof HourlySummarySchema>;\n\nconst HourlySummaryExtendedSchema = z.object({\n topics: z.array(z.string()).default([]),\n decisions: z.array(z.string()).default([]),\n actionItems: z.array(z.string()).default([]),\n rejected: z.array(z.string()).default([]),\n});\n\ntype HourlySummaryExtendedResult = z.infer<typeof HourlySummaryExtendedSchema>;\n\ntype HourlySummaryExtendedMeta = {\n userTurns: number;\n assistantTurns: number;\n toolCalls: number;\n toolCounts: Record<string, number>;\n};\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport class HourlySummarizer {\n private summariesDir: string;\n private config: PluginConfig;\n private localLlm: LocalLlmClient;\n private fallbackLlm: FallbackLlmClient;\n private modelRegistry: ModelRegistry;\n private transcript?: TranscriptManager;\n\n constructor(config: PluginConfig, gatewayConfig?: GatewayConfig, modelRegistry?: ModelRegistry, transcript?: TranscriptManager) {\n this.config = config;\n this.summariesDir = path.join(config.memoryDir, \"summaries\", \"hourly\");\n this.modelRegistry = modelRegistry ?? new ModelRegistry(config.memoryDir);\n this.transcript = transcript;\n\n // Initialize local LLM client with shared model registry\n this.localLlm = new LocalLlmClient(config, this.modelRegistry);\n\n // Initialize fallback client with gateway config\n this.fallbackLlm = new FallbackLlmClient(\n gatewayConfig,\n fallbackLlmRuntimeContextFromConfig(config),\n );\n\n if (!gatewayConfig?.agents?.defaults?.model?.primary && !config.localLlmEnabled && config.modelSource !== \"gateway\") {\n log.warn(\"no gateway default AI and local LLM disabled — hourly summarization disabled\");\n }\n }\n\n private get useGatewayModelSource(): boolean {\n return this.config.modelSource === \"gateway\";\n }\n\n private get shouldUseLocalLlm(): boolean {\n return this.config.localLlmEnabled && !this.useGatewayModelSource;\n }\n\n private withGatewayAgent(options: import(\"./fallback-llm.js\").FallbackLlmOptions): import(\"./fallback-llm.js\").FallbackLlmOptions {\n if (!this.useGatewayModelSource) return options;\n // Shared resolution (taskModelChain > gatewayAgentId) — gotcha #22. Issue #1365.\n return { ...options, ...gatewayTaskChainOptions(this.config) };\n }\n\n async initialize(): Promise<void> {\n await mkdir(this.summariesDir, { recursive: true });\n log.info(\"hourly summarizer initialized\");\n }\n\n private async summarySessionDir(sessionKey: string): Promise<string> {\n return resolveSafeStoragePath(\n this.summariesDir,\n encodeStoragePathSegment(sessionKey, \"session\"),\n );\n }\n\n private async legacySummarySessionDir(sessionKey: string): Promise<string | null> {\n if (sessionKey.includes(\"\\0\")) return null;\n try {\n return await resolveSafeStoragePath(this.summariesDir, sessionKey);\n } catch {\n return null;\n }\n }\n\n private summaryDateString(hour: string): string {\n const dateStr = hour.slice(0, 10);\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(dateStr)) {\n throw new Error(`invalid hourly summary timestamp: ${hour}`);\n }\n return dateStr;\n }\n\n // Generate summary for a specific hour and session\n async generateSummary(\n sessionKey: string,\n hourStart: Date,\n entries: TranscriptEntry[]\n ): Promise<HourlySummary | null> {\n if (entries.length === 0) return null;\n\n // Format entries for the LLM\n const conversation = entries\n .map((e) => `[${e.role}] ${e.content}`)\n .join(\"\\n\\n\");\n\n if (this.config.hourlySummariesExtendedEnabled) {\n const extended = await this.generateExtended(sessionKey, hourStart, conversation, entries);\n if (!extended) return null;\n const meta: HourlySummaryExtendedMeta = {\n userTurns: entries.filter((e) => e.role === \"user\").length,\n assistantTurns: entries.filter((e) => e.role === \"assistant\").length,\n toolCalls: extended._meta.toolCalls,\n toolCounts: extended._meta.toolCounts,\n };\n // Keep HourlySummary surface stable; encode \"topics\" as bullets for recall injection.\n const base: HourlySummary = {\n hour: hourStart.toISOString(),\n sessionKey,\n bullets: extended.topics.length > 0 ? extended.topics.slice(0, 5) : [\"(summary generated)\"],\n turnCount: entries.length,\n generatedAt: new Date().toISOString(),\n };\n const withExtras = base as any;\n withExtras._extended = extended;\n withExtras._extendedMeta = meta;\n return base;\n }\n\n const hourIso = hourStart.toISOString();\n const startTime = Date.now();\n\n // Try local LLM first if enabled\n if (this.shouldUseLocalLlm) {\n try {\n const localResult = await this.generateWithLocalLlm(conversation);\n if (localResult) {\n const durationMs = Date.now() - startTime;\n log.debug(\n `generated hourly summary for ${sessionKey} at ${hourIso} in ${durationMs}ms using local LLM`\n );\n return {\n hour: hourIso,\n sessionKey,\n bullets: localResult.bullets,\n turnCount: entries.length,\n generatedAt: new Date().toISOString(),\n };\n }\n // Local failed, fall back if allowed\n if (!this.config.localLlmFallback) {\n log.warn(\"summary generation: local LLM failed and fallback disabled\");\n return null;\n }\n log.info(\"summary generation: local LLM unavailable, falling back to gateway default AI\");\n } catch (err) {\n if (!this.config.localLlmFallback) {\n log.warn(\"summary generation: local LLM error and fallback disabled:\", err);\n return null;\n }\n log.info(\"summary generation: local LLM error, falling back to gateway default AI:\", err);\n }\n }\n\n // Fall back to gateway's default AI\n log.info(\"summary generation: falling back to gateway default AI\");\n\n try {\n const messages = [\n {\n role: \"system\" as const,\n content: `You are a conversation summarization system. Summarize the following conversation transcript into 3-5 concise bullet points.\n\nGuidelines:\n- Focus on what was accomplished, decided, or discussed\n- Include specific topics, projects, or entities mentioned\n- Note any significant user requests or agent actions\n- Keep bullets brief but informative (1-2 sentences each)\n- Skip trivial greetings or meta-conversation\n- Use present tense for ongoing work, past for completed items\n\nRespond with valid JSON matching this schema:\n{\n \"bullets\": [\"bullet 1\", \"bullet 2\", \"bullet 3\"]\n}`,\n },\n { role: \"user\" as const, content: `Summarize this conversation:\\n\\n${conversation}` },\n ];\n\n const result = await this.fallbackLlm.parseWithSchema(\n messages,\n HourlySummarySchema,\n this.withGatewayAgent({ temperature: 0.3, maxTokens: 8192 }),\n );\n\n const durationMs = Date.now() - startTime;\n\n if (result) {\n log.debug(\n `generated hourly summary for ${sessionKey} at ${hourIso} in ${durationMs}ms via fallback`,\n );\n return {\n hour: hourIso,\n sessionKey,\n bullets: result.bullets,\n turnCount: entries.length,\n generatedAt: new Date().toISOString(),\n };\n }\n\n log.warn(\"summary generation fallback returned no parsed output\");\n return null;\n } catch (err) {\n log.error(\"summary generation fallback failed\", err);\n return null;\n }\n }\n\n private async generateExtended(\n sessionKey: string,\n hourStart: Date,\n conversation: string,\n entries: TranscriptEntry[],\n ): Promise<(HourlySummaryExtendedResult & { _meta: HourlySummaryExtendedMeta }) | null> {\n const hourIso = hourStart.toISOString();\n const startTime = Date.now();\n\n const hourEnd = new Date(hourStart.getTime() + 60 * 60 * 1000);\n let toolCounts: Record<string, number> = {};\n if (this.config.hourlySummariesIncludeToolStats && this.transcript) {\n const uses = await this.transcript.readToolUse(sessionKey, hourStart, hourEnd);\n for (const u of uses) toolCounts[u.tool] = (toolCounts[u.tool] ?? 0) + 1;\n }\n\n const sys = `You are a conversation summarization system.\\n\\nSummarize the hour into structured sections.\\n\\nReturn valid JSON matching:\\n{\\n \\\"topics\\\": [\\\"...\\\"],\\n \\\"decisions\\\": [\\\"...\\\"],\\n \\\"actionItems\\\": [\\\"...\\\"],\\n \\\"rejected\\\": [\\\"...\\\"]\\n}\\n\\nGuidelines:\\n- Prefer concrete topics and decisions.\\n- Action items should be imperative.\\n- Rejected ideas are things that were explicitly discarded or reversed.\\n- If there are none for a section, return an empty array.\\n`;\n\n const toolStatsLine = Object.keys(toolCounts).length > 0\n ? `Tools used (counts): ${Object.entries(toolCounts).sort((a,b)=>b[1]-a[1]).slice(0,10).map(([k,v])=>`${k}=${v}`).join(\", \")}\\n\\n`\n : \"\";\n const userTurns = entries.filter((e) => e.role === \"user\").length;\n const assistantTurns = entries.filter((e) => e.role === \"assistant\").length;\n const toolCalls = Object.values(toolCounts).reduce((a,b)=>a+b,0);\n const statsLine = `Stats: userTurns=${userTurns}, assistantTurns=${assistantTurns}, toolCalls=${toolCalls}\\n\\n`;\n\n const user = `Hour: ${hourIso}\\nSession: ${sessionKey}\\n\\n${statsLine}${toolStatsLine}Conversation:\\n${conversation}\\n`;\n\n // Try local LLM first if enabled\n if (this.shouldUseLocalLlm) {\n try {\n const contextSizes = this.modelRegistry.calculateContextSizes(this.config.localLlmModel, this.config.localLlmMaxContext);\n const truncated = user.length > contextSizes.maxInputChars ? user.slice(0, contextSizes.maxInputChars) + \"\\n\\n[truncated]\" : user;\n const response = await this.localLlm.chatCompletion(\n [\n { role: \"system\", content: \"Output valid JSON only.\" },\n { role: \"user\", content: sys + \"\\n\\n\" + truncated },\n ],\n {\n temperature: 0.2,\n maxTokens: contextSizes.maxOutputTokens,\n operation: \"hourly_summary_extended\",\n priority: \"background\",\n },\n );\n if (response?.content) {\n const content = response.content.trim();\n for (const candidate of extractJsonCandidates(content)) {\n try {\n const parsed = JSON.parse(candidate);\n const result = HourlySummaryExtendedSchema.parse(parsed);\n log.debug(\n `generated extended hourly summary for ${sessionKey} at ${hourIso} in ${Date.now() - startTime}ms (local)`,\n );\n return { ...result, _meta: { userTurns, assistantTurns, toolCalls, toolCounts } };\n } catch {\n // keep trying candidates\n }\n }\n }\n } catch (err) {\n if (!this.config.localLlmFallback) return null;\n log.info(\"extended summary: local failed, falling back:\", err);\n }\n }\n\n try {\n const result = await this.fallbackLlm.parseWithSchema(\n [\n { role: \"system\" as const, content: sys },\n { role: \"user\" as const, content: user },\n ],\n HourlySummaryExtendedSchema,\n this.withGatewayAgent({ temperature: 0.2, maxTokens: 2048 }),\n );\n if (result) {\n log.debug(`generated extended hourly summary for ${sessionKey} at ${hourIso} in ${Date.now() - startTime}ms (fallback)`);\n return { ...result, _meta: { userTurns, assistantTurns, toolCalls, toolCounts } };\n }\n return null;\n } catch (err) {\n log.error(\"extended summary generation failed\", err);\n return null;\n }\n }\n\n /**\n * Generate summary using local LLM with JSON mode.\n * Uses dynamic context sizing based on model capabilities.\n */\n private async generateWithLocalLlm(conversation: string): Promise<HourlySummaryResult | null> {\n // Get dynamic context sizes based on model capabilities\n const contextSizes = this.modelRegistry.calculateContextSizes(\n this.config.localLlmModel,\n this.config.localLlmMaxContext\n );\n log.debug(`Summarizer model context: ${contextSizes.description}`);\n\n const maxConversationChars = contextSizes.maxInputChars;\n const truncatedConversation = conversation.length > maxConversationChars\n ? conversation.slice(0, maxConversationChars) + \"\\n\\n[truncated]\"\n : conversation;\n\n const instructions = `You are a conversation summarization system. Summarize the following conversation transcript into 3-5 concise bullet points.\n\nGuidelines:\n- Focus on what was accomplished, decided, or discussed\n- Include specific topics, projects, or entities mentioned\n- Note any significant user requests or agent actions\n- Keep bullets brief but informative (1-2 sentences each)\n- Skip trivial greetings or meta-conversation\n- Use present tense for ongoing work, past for completed items\n\nRespond with valid JSON matching this schema:\n{\n \"bullets\": [\"bullet 1\", \"bullet 2\", \"bullet 3\"]\n}`;\n\n const fullPrompt = `${instructions}\\n\\nConversation to summarize:\\n${truncatedConversation}`;\n\n const response = await this.localLlm.chatCompletion(\n [\n { role: \"system\", content: \"You are a conversation summarization system. Output valid JSON only.\" },\n { role: \"user\", content: fullPrompt },\n ],\n {\n temperature: 0.3,\n maxTokens: contextSizes.maxOutputTokens,\n operation: \"hourly_summary\",\n priority: \"background\",\n },\n );\n\n if (!response?.content) {\n return null;\n }\n\n try {\n // Parse JSON response\n const content = response.content.trim();\n for (const candidate of extractJsonCandidates(content)) {\n try {\n const parsed = JSON.parse(candidate);\n return HourlySummarySchema.parse(parsed);\n } catch {\n // keep trying candidates\n }\n }\n return null;\n } catch (err) {\n log.warn(\"local LLM summary: failed to parse JSON response:\", err);\n return null;\n }\n }\n\n // Save summary to file\n async saveSummary(summary: HourlySummary): Promise<void> {\n const sessionDir = await this.summarySessionDir(summary.sessionKey);\n await mkdir(sessionDir, { recursive: true });\n\n // Format date as YYYY-MM-DD for the filename\n const dateStr = this.summaryDateString(summary.hour);\n const filePath = await resolveSafeStoragePath(sessionDir, `${dateStr}.md`);\n\n // Format hour as HH:00 for display\n const hourStr = summary.hour.slice(11, 13);\n\n // Build markdown content\n const lines: string[] = [];\n\n // Check if file exists to append or create\n let existingContent = \"\";\n try {\n existingContent = await readFile(filePath, \"utf-8\");\n } catch {\n // File doesn't exist yet, will create new\n }\n\n // Check if this hour already exists (idempotent)\n const hourHeader = `## ${hourStr}:00`;\n if (existingContent.includes(hourHeader)) {\n // Replace existing hour section\n const headerMatch = new RegExp(`(^|\\\\n)${escapeRegExp(hourHeader)}\\\\n`).exec(existingContent);\n const sectionStart = headerMatch\n ? headerMatch.index + headerMatch[1].length\n : existingContent.indexOf(hourHeader);\n const nextHeaderPattern = /\\n## \\d{2}:00\\n/g;\n nextHeaderPattern.lastIndex = sectionStart + hourHeader.length;\n const nextHeader = nextHeaderPattern.exec(existingContent);\n const beforeHour = existingContent.slice(0, sectionStart);\n const afterHour = nextHeader\n ? existingContent.slice(nextHeader.index + 1)\n : \"\";\n const newSection = this.formatHourSection(summary, hourHeader);\n existingContent = beforeHour + newSection.trimEnd() + (afterHour ? \"\\n\\n\" + afterHour : \"\\n\");\n\n await writeFile(filePath, existingContent, \"utf-8\");\n log.debug(`updated hourly summary for ${summary.sessionKey} at ${hourStr}:00`);\n } else {\n // Append new hour section\n const newSection = this.formatHourSection(summary, hourHeader);\n\n if (existingContent) {\n // Add to existing file\n await writeFile(filePath, existingContent.trimEnd() + \"\\n\\n\" + newSection, \"utf-8\");\n } else {\n // Create new file with header\n const header = `# Hourly Summaries — ${dateStr}\\n\\n*Session: ${summary.sessionKey}*\\n`;\n await writeFile(filePath, header + \"\\n\" + newSection, \"utf-8\");\n }\n log.debug(`saved hourly summary for ${summary.sessionKey} at ${hourStr}:00`);\n }\n try {\n await upsertSummarySnapshot(this.config.memoryDir, summary);\n } catch (error) {\n log.warn(\n `hourly summarizer: failed to update summary snapshot for ${summary.sessionKey} (fail-open): ${String(error)}`,\n );\n }\n }\n\n private formatHourSection(summary: HourlySummary, hourHeader: string): string {\n const ext = (summary as any)._extended as (HourlySummaryExtendedResult & { _meta?: HourlySummaryExtendedMeta }) | undefined;\n const meta = (summary as any)._extendedMeta as HourlySummaryExtendedMeta | undefined;\n const lines: string[] = [hourHeader, \"\"];\n\n if (this.config.hourlySummariesExtendedEnabled && ext) {\n lines.push(\"### Topics Discussed\");\n for (const t of ext.topics) lines.push(`- ${t}`);\n lines.push(\"\");\n lines.push(\"### Decisions Made\");\n for (const d of ext.decisions) lines.push(`- ${d}`);\n lines.push(\"\");\n lines.push(\"### Action Items\");\n for (const a of ext.actionItems) lines.push(`- ${a}`);\n lines.push(\"\");\n lines.push(\"### Rejected Ideas / Reversals\");\n for (const r of ext.rejected) lines.push(`- ${r}`);\n lines.push(\"\");\n if (meta && Object.keys(meta.toolCounts).length > 0) {\n lines.push(\"### Tools Used\");\n const top = Object.entries(meta.toolCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 12);\n for (const [name, count] of top) lines.push(`- ${name}: ${count}`);\n lines.push(\"\");\n }\n lines.push(\"### Stats\");\n lines.push(`- Turns: ${summary.turnCount}`);\n if (meta) {\n lines.push(`- User turns: ${meta.userTurns}`);\n lines.push(`- Assistant turns: ${meta.assistantTurns}`);\n lines.push(`- Tool calls: ${meta.toolCalls}`);\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n\n for (const bullet of summary.bullets) lines.push(`- ${bullet}`);\n lines.push(` *(${summary.turnCount} turns)*`);\n lines.push(\"\");\n return lines.join(\"\\n\");\n }\n\n // Read recent summaries for recall injection\n async readRecent(sessionKey: string, hours: number): Promise<HourlySummary[]> {\n try {\n const cutoffTime = Date.now() - hours * 60 * 60 * 1000;\n\n const snapshot = await readSummarySnapshot(this.config.memoryDir, sessionKey);\n if (snapshot) {\n return snapshot\n .filter((s) => new Date(s.hour).getTime() >= cutoffTime)\n .sort((a, b) => new Date(b.hour).getTime() - new Date(a.hour).getTime());\n }\n\n const summaries: HourlySummary[] = [];\n const encodedDir = await this.summarySessionDir(sessionKey);\n const legacyDir = await this.legacySummarySessionDir(sessionKey);\n const seenDirs = new Set<string>();\n\n for (const sessionDir of [encodedDir, legacyDir]) {\n if (!sessionDir || seenDirs.has(sessionDir)) continue;\n seenDirs.add(sessionDir);\n const parsed = await this.readSummaryDir(sessionDir, sessionKey);\n summaries.push(...parsed);\n }\n\n const byHour = new Map<string, HourlySummary>();\n for (const summary of summaries) {\n if (!byHour.has(summary.hour)) byHour.set(summary.hour, summary);\n }\n\n const sortedSummaries = Array.from(byHour.values()).sort(\n (a, b) => new Date(b.hour).getTime() - new Date(a.hour).getTime(),\n );\n\n // Filter to recent hours while materializing the full parsed history.\n const recent = sortedSummaries.filter(\n (s) => new Date(s.hour).getTime() >= cutoffTime,\n );\n\n if (sortedSummaries.length > 0) {\n try {\n await writeSummarySnapshot(\n this.config.memoryDir,\n sessionKey,\n sortedSummaries,\n );\n } catch (error) {\n log.warn(\n `hourly summarizer: failed to materialize summary snapshot for ${sessionKey} (fail-open): ${String(error)}`,\n );\n }\n }\n\n return recent;\n } catch {\n // Directory doesn't exist or error reading\n return [];\n }\n }\n\n private async readSummaryDir(\n sessionDir: string,\n sessionKey: string,\n ): Promise<HourlySummary[]> {\n let files: string[];\n try {\n files = await readdir(sessionDir);\n } catch {\n return [];\n }\n\n const summaries: HourlySummary[] = [];\n const mdFiles = files.filter((f) => f.endsWith(\".md\"));\n for (const file of mdFiles) {\n const filePath = await resolveSafeStoragePath(sessionDir, file).catch(() => null);\n if (filePath === null) continue;\n\n try {\n const content = await readFile(filePath, \"utf-8\");\n summaries.push(...this.parseSummaryFile(content, sessionKey, file));\n } catch {\n // Skip unreadable summary files.\n }\n }\n\n return summaries;\n }\n\n private parseSummaryFile(\n content: string,\n sessionKey: string,\n filename: string\n ): HourlySummary[] {\n const summaries: HourlySummary[] = [];\n\n // Extract date from filename (YYYY-MM-DD.md)\n const dateMatch = filename.match(/^(\\d{4}-\\d{2}-\\d{2})\\.md$/);\n if (!dateMatch) return summaries;\n const dateStr = dateMatch[1];\n\n // Split by hour sections\n const hourSections = content.split(/\\n## (\\d{2}):00\\n/);\n\n // First element is the header, skip it\n for (let i = 1; i < hourSections.length; i += 2) {\n const hourStr = hourSections[i];\n const sectionContent = hourSections[i + 1] || \"\";\n\n // Parse bullets\n const bullets: string[] = [];\n const lines = sectionContent.split(\"\\n\");\n let turnCount = 0;\n\n let inTopics = false;\n let sawExtendedTopicsHeader = false;\n for (const line of lines) {\n if (line.startsWith(\"### Topics\")) {\n inTopics = true;\n sawExtendedTopicsHeader = true;\n continue;\n }\n if (line.startsWith(\"### \") && !line.startsWith(\"### Topics\")) {\n inTopics = false;\n }\n const bulletMatch = line.match(/^- (.+)$/);\n if (bulletMatch) {\n // For extended format, only treat topic bullets as recall bullets.\n if (!sawExtendedTopicsHeader || inTopics) bullets.push(bulletMatch[1]);\n }\n const turnMatch = line.match(/\\((\\d+) turns?\\)/);\n if (turnMatch) turnCount = parseInt(turnMatch[1], 10);\n }\n\n if (bullets.length > 0) {\n summaries.push({\n hour: `${dateStr}T${hourStr}:00:00.000Z`,\n sessionKey,\n bullets,\n turnCount,\n generatedAt: \"\", // Not stored in file, not needed for recall\n });\n }\n }\n\n return summaries;\n }\n\n // Format summaries for recall injection\n formatForRecall(summaries: HourlySummary[], maxCount: number): string {\n if (summaries.length === 0) return \"\";\n\n const limited = summaries.slice(0, maxCount);\n const lines: string[] = [`## Recent Activity (last ${limited.length} hours)`];\n\n for (const summary of limited) {\n const hourStr = summary.hour.slice(11, 16); // HH:MM\n for (const bullet of summary.bullets) {\n lines.push(`- ${hourStr}: ${bullet}`);\n }\n }\n\n return lines.join(\"\\n\");\n }\n\n // Main entry point for cron job\n async runHourly(): Promise<void> {\n log.debug(\"running hourly summary generation\");\n\n // Get active sessions from transcript\n const sessions = await this.getActiveSessions();\n\n for (const sessionKey of sessions) {\n // Calculate the hour we want to summarize (previous hour)\n const now = new Date();\n const hourStart = new Date(now.getTime() - 60 * 60 * 1000);\n hourStart.setMinutes(0, 0, 0);\n const hourEnd = new Date(hourStart.getTime() + 60 * 60 * 1000);\n\n // Get entries for this session in the target hour\n const entries = await this.getTranscriptEntries(sessionKey, hourStart, hourEnd);\n\n if (entries.length === 0) {\n log.debug(`no transcript entries for ${sessionKey} at ${hourStart.toISOString()}`);\n continue;\n }\n\n // Generate and save summary\n const summary = await this.generateSummary(sessionKey, hourStart, entries);\n if (summary) {\n await this.saveSummary(summary);\n log.info(`generated hourly summary for ${sessionKey} (${entries.length} turns)`);\n }\n }\n }\n\n // Get list of active sessions from transcript directory\n private async getActiveSessions(): Promise<string[]> {\n const transcriptDir = await resolveSafeStoragePath(\n this.config.memoryDir,\n \"transcripts\",\n ).catch(() => null);\n if (transcriptDir === null) return [];\n\n try {\n const sessionKeys = new Set<string>();\n const typeEntries = await readdir(transcriptDir, { withFileTypes: true });\n for (const typeEnt of typeEntries) {\n if (!typeEnt.isDirectory()) continue;\n const typeDir = await resolveSafeStoragePath(transcriptDir, typeEnt.name).catch(() => null);\n if (typeDir === null) continue;\n const idEntries = await readdir(typeDir, { withFileTypes: true });\n for (const idEnt of idEntries) {\n if (!idEnt.isDirectory()) continue;\n const chanDir = await resolveSafeStoragePath(typeDir, idEnt.name).catch(() => null);\n if (chanDir === null) continue;\n const files = (await readdir(chanDir)).filter((f) => f.endsWith(\".jsonl\")).sort();\n for (const file of files) {\n const transcriptPath = await resolveSafeStoragePath(chanDir, file).catch(() => null);\n if (transcriptPath === null) continue;\n await this.collectTranscriptSessionKeys(transcriptPath, sessionKeys);\n }\n }\n }\n return Array.from(sessionKeys);\n } catch {\n return [];\n }\n }\n\n private async collectTranscriptSessionKeys(\n transcriptPath: string,\n sessionKeys: Set<string>,\n ): Promise<void> {\n try {\n const raw = await readFile(transcriptPath, \"utf-8\");\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as TranscriptEntry;\n if (typeof entry.sessionKey === \"string\" && entry.sessionKey.length > 0) {\n sessionKeys.add(entry.sessionKey);\n }\n } catch {\n // ignore malformed transcript lines\n }\n }\n } catch {\n // ignore unreadable transcript files\n }\n }\n\n // Get transcript entries for a session within a time range\n private async getTranscriptEntries(\n sessionKey: string,\n startTime: Date,\n endTime: Date\n ): Promise<TranscriptEntry[]> {\n // Shared session-identity layer (issue #1496, rule #22). Arbitrary keys\n // route to `session/<hash>`; legacy `agent:<id>:...` keep their paths. The\n // shared helper also supplies the read-back-only candidate dirs (mirrors\n // TranscriptManager) so legacy `other/default` AND old `parts.length >= 3`\n // data stays discoverable here too.\n const paths = sessionStoragePaths(sessionKey);\n\n try {\n const transcriptRoot = path.join(this.config.memoryDir, \"transcripts\");\n const candidateDirs = new Set(\n [paths.dir, paths.alternateDir, paths.legacyDir, ...paths.readbackDirs].filter(\n (dir): dir is string => typeof dir === \"string\" && dir.length > 0,\n ),\n );\n const entries: TranscriptEntry[] = [];\n // Dedup identical raw rows that a partially-applied migration may have\n // left in both the primary dir and a read-back dir (issue #1496, cursor\n // review). Exact raw JSONL line is the stable identity.\n const seenRawLines = new Set<string>();\n\n // Read all daily transcript files in the directory\n for (const candidateDir of candidateDirs) {\n const transcriptDir = await resolveSafeStoragePath(transcriptRoot, candidateDir).catch(() => null);\n if (transcriptDir === null) continue;\n\n const files = await readdir(transcriptDir).catch(() => []);\n for (const file of files) {\n if (!file.endsWith(\".jsonl\")) continue;\n\n const transcriptPath = await resolveSafeStoragePath(transcriptDir, file).catch(() => null);\n if (transcriptPath === null) continue;\n\n try {\n const content = await readFile(transcriptPath, \"utf-8\");\n const lines = content.trim().split(\"\\n\");\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as TranscriptEntry;\n const entryTime = new Date(entry.timestamp).getTime();\n\n if (\n entry.sessionKey === sessionKey &&\n entryTime >= startTime.getTime() &&\n entryTime < endTime.getTime() &&\n !seenRawLines.has(line)\n ) {\n seenRawLines.add(line);\n entries.push(entry);\n }\n } catch {\n // Skip malformed lines\n }\n }\n } catch {\n // Skip unreadable files\n }\n }\n }\n\n return entries;\n } catch {\n return [];\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sessionStoragePaths
|
|
3
|
+
} from "./chunk-S4DDLTPX.js";
|
|
1
4
|
import {
|
|
2
5
|
analyzeSessionIntegrity
|
|
3
6
|
} from "./chunk-KFY3SGN7.js";
|
|
4
7
|
import {
|
|
5
|
-
|
|
6
|
-
encodeStoragePathSegmentWithHash,
|
|
7
|
-
isSafeLegacyPathSegment,
|
|
8
|
-
resolveSafeStoragePath,
|
|
9
|
-
storagePathHash
|
|
8
|
+
resolveSafeStoragePath
|
|
10
9
|
} from "./chunk-FF4KLI5W.js";
|
|
11
10
|
import {
|
|
12
11
|
log
|
|
@@ -15,12 +14,13 @@ import {
|
|
|
15
14
|
// src/transcript.ts
|
|
16
15
|
import { appendFile, mkdir, readdir, readFile, stat, unlink, writeFile } from "fs/promises";
|
|
17
16
|
import path from "path";
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
function makeRawLineDeduper() {
|
|
18
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19
|
+
return (rawLine) => {
|
|
20
|
+
if (seen.has(rawLine)) return false;
|
|
21
|
+
seen.add(rawLine);
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
24
|
}
|
|
25
25
|
var TranscriptManager = class _TranscriptManager {
|
|
26
26
|
transcriptsDir;
|
|
@@ -42,50 +42,31 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
42
42
|
this.toolUsageDir = path.join(this.stateDir, "tool-usage");
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Resolve the storage path pieces for a sessionKey via the shared
|
|
46
|
+
* {@link sessionStoragePaths} helper (issue #1496, rule #22).
|
|
46
47
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
48
|
+
* Legacy `agent:<id>:...` shapes keep their existing readable channel paths.
|
|
49
|
+
* Arbitrary / non-legacy keys (e.g. `pi-geek:abc123`) route to
|
|
50
|
+
* `transcripts/session/<hash>/YYYY-MM-DD.jsonl` from the FIRST write — they
|
|
51
|
+
* never start life in `other/default`. For those keys a list of
|
|
52
|
+
* read-back-only `readbackDirs` is returned so data written by older builds
|
|
53
|
+
* (under `other/default`, or under an old `parts.length >= 3` directory such
|
|
54
|
+
* as `baz/default`) stays discoverable until migrated. New writes never
|
|
55
|
+
* target read-back dirs. See `session-identity.ts`.
|
|
52
56
|
*
|
|
53
57
|
* @returns Object with raw channel identifiers and encoded storage path pieces.
|
|
54
58
|
*/
|
|
55
59
|
getTranscriptPath(sessionKey) {
|
|
56
|
-
const
|
|
57
|
-
let channelType = "other";
|
|
58
|
-
let channelId = "default";
|
|
59
|
-
if (parts.length >= 3) {
|
|
60
|
-
channelType = parts[2];
|
|
61
|
-
if (channelType === "main") {
|
|
62
|
-
channelId = "default";
|
|
63
|
-
} else if (channelType === "discord" && parts.length >= 5 && parts[3] === "channel") {
|
|
64
|
-
channelId = parts[4];
|
|
65
|
-
} else if (channelType === "slack" && parts.length >= 5 && parts[3] === "channel") {
|
|
66
|
-
channelId = parts[4];
|
|
67
|
-
} else if (channelType === "cron" && parts.length >= 4) {
|
|
68
|
-
channelId = parts[3];
|
|
69
|
-
} else if (parts.length >= 4) {
|
|
70
|
-
channelId = parts[3];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
60
|
+
const paths = sessionStoragePaths(sessionKey);
|
|
73
61
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
74
|
-
const dir = path.join(
|
|
75
|
-
encodeStoragePathSegment(channelType),
|
|
76
|
-
encodeStoragePathSegment(channelId)
|
|
77
|
-
);
|
|
78
|
-
const alternateDir = path.join(
|
|
79
|
-
encodeStoragePathSegmentWithHash(channelType),
|
|
80
|
-
`${encodeStoragePathSegmentWithHash(channelId)}--session-${storagePathHash(sessionKey)}`
|
|
81
|
-
);
|
|
82
62
|
return {
|
|
83
|
-
dir,
|
|
63
|
+
dir: paths.dir,
|
|
84
64
|
file: `${today}.jsonl`,
|
|
85
|
-
channelType,
|
|
86
|
-
channelId,
|
|
87
|
-
alternateDir,
|
|
88
|
-
legacyDir:
|
|
65
|
+
channelType: paths.channelType,
|
|
66
|
+
channelId: paths.channelId,
|
|
67
|
+
alternateDir: paths.alternateDir,
|
|
68
|
+
legacyDir: paths.legacyDir,
|
|
69
|
+
readbackDirs: paths.readbackDirs
|
|
89
70
|
};
|
|
90
71
|
}
|
|
91
72
|
/**
|
|
@@ -136,7 +117,13 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
136
117
|
}
|
|
137
118
|
getToolUsagePath(sessionKey) {
|
|
138
119
|
const p = this.getTranscriptPath(sessionKey);
|
|
139
|
-
return {
|
|
120
|
+
return {
|
|
121
|
+
dir: p.dir,
|
|
122
|
+
file: p.file,
|
|
123
|
+
alternateDir: p.alternateDir,
|
|
124
|
+
legacyDir: p.legacyDir,
|
|
125
|
+
readbackDirs: p.readbackDirs
|
|
126
|
+
};
|
|
140
127
|
}
|
|
141
128
|
async selectStorageDirForWrite(root, dir, legacyDir, sessionKey, alternateDir) {
|
|
142
129
|
const channelDir = await resolveSafeStoragePath(root, dir);
|
|
@@ -247,10 +234,10 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
247
234
|
} catch {
|
|
248
235
|
}
|
|
249
236
|
}
|
|
250
|
-
async getSessionStorageFiles(root, dir, legacyDir, alternateDir) {
|
|
237
|
+
async getSessionStorageFiles(root, dir, legacyDir, alternateDir, readbackDirs = []) {
|
|
251
238
|
const files = [];
|
|
252
239
|
const seenDirs = /* @__PURE__ */ new Set();
|
|
253
|
-
for (const candidateDir of [dir, alternateDir, legacyDir]) {
|
|
240
|
+
for (const candidateDir of [dir, alternateDir, legacyDir, ...readbackDirs]) {
|
|
254
241
|
if (!candidateDir || seenDirs.has(candidateDir)) continue;
|
|
255
242
|
seenDirs.add(candidateDir);
|
|
256
243
|
let channelDir;
|
|
@@ -292,10 +279,17 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
292
279
|
await this.rememberDirectoryOwnership(this.toolUsageDir, writeDir, entry.sessionKey);
|
|
293
280
|
}
|
|
294
281
|
async readToolUse(sessionKey, startTime, endTime) {
|
|
295
|
-
const { dir, alternateDir, legacyDir } = this.getToolUsagePath(sessionKey);
|
|
282
|
+
const { dir, alternateDir, legacyDir, readbackDirs } = this.getToolUsagePath(sessionKey);
|
|
296
283
|
try {
|
|
297
|
-
const files = await this.getSessionStorageFiles(
|
|
284
|
+
const files = await this.getSessionStorageFiles(
|
|
285
|
+
this.toolUsageDir,
|
|
286
|
+
dir,
|
|
287
|
+
legacyDir,
|
|
288
|
+
alternateDir,
|
|
289
|
+
readbackDirs
|
|
290
|
+
);
|
|
298
291
|
const out = [];
|
|
292
|
+
const keepRawLine = makeRawLineDeduper();
|
|
299
293
|
for (const file of files) {
|
|
300
294
|
const raw = await readFile(file.path, "utf-8");
|
|
301
295
|
for (const line of raw.split("\n")) {
|
|
@@ -306,7 +300,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
306
300
|
if (!Number.isFinite(ts)) continue;
|
|
307
301
|
if (ts >= startTime.getTime() && ts < endTime.getTime()) {
|
|
308
302
|
if (typeof obj.tool === "string" && typeof obj.sessionKey === "string") {
|
|
309
|
-
if (obj.sessionKey === sessionKey) {
|
|
303
|
+
if (obj.sessionKey === sessionKey && keepRawLine(line)) {
|
|
310
304
|
out.push({ timestamp: obj.timestamp, sessionKey: obj.sessionKey, tool: obj.tool });
|
|
311
305
|
}
|
|
312
306
|
}
|
|
@@ -321,10 +315,16 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
321
315
|
}
|
|
322
316
|
}
|
|
323
317
|
async estimateSessionFootprint(sessionKey) {
|
|
324
|
-
const { dir, alternateDir, legacyDir } = this.getTranscriptPath(sessionKey);
|
|
318
|
+
const { dir, alternateDir, legacyDir, readbackDirs } = this.getTranscriptPath(sessionKey);
|
|
325
319
|
let bytes = 0;
|
|
326
320
|
try {
|
|
327
|
-
const files = await this.getSessionStorageFiles(
|
|
321
|
+
const files = await this.getSessionStorageFiles(
|
|
322
|
+
this.transcriptsDir,
|
|
323
|
+
dir,
|
|
324
|
+
legacyDir,
|
|
325
|
+
alternateDir,
|
|
326
|
+
readbackDirs
|
|
327
|
+
);
|
|
328
328
|
const cached = this.sessionFootprintCache.get(sessionKey);
|
|
329
329
|
if (!cached) {
|
|
330
330
|
const fileBytes = /* @__PURE__ */ new Map();
|
|
@@ -494,6 +494,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
494
494
|
const start = new Date(startTime);
|
|
495
495
|
const end = new Date(endTime);
|
|
496
496
|
const entries = [];
|
|
497
|
+
const keepRawLine = sessionKey ? makeRawLineDeduper() : void 0;
|
|
497
498
|
try {
|
|
498
499
|
const transcriptFiles = await this.getAllTranscriptFiles();
|
|
499
500
|
for (const relativePath of transcriptFiles) {
|
|
@@ -507,6 +508,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
507
508
|
const entryTime = new Date(entry.timestamp);
|
|
508
509
|
if (entryTime >= start && entryTime < end) {
|
|
509
510
|
if (!sessionKey || entry.sessionKey === sessionKey) {
|
|
511
|
+
if (keepRawLine && !keepRawLine(line)) continue;
|
|
510
512
|
entries.push(entry);
|
|
511
513
|
}
|
|
512
514
|
}
|
|
@@ -544,7 +546,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
544
546
|
* directory and only reads files whose date falls within the lookback window.
|
|
545
547
|
*/
|
|
546
548
|
async readRecentForSession(start, end, sessionKey) {
|
|
547
|
-
const { dir, alternateDir, legacyDir } = this.getTranscriptPath(sessionKey);
|
|
549
|
+
const { dir, alternateDir, legacyDir, readbackDirs } = this.getTranscriptPath(sessionKey);
|
|
548
550
|
const dateStrings = /* @__PURE__ */ new Set();
|
|
549
551
|
const cursor = new Date(start);
|
|
550
552
|
while (cursor <= end) {
|
|
@@ -553,7 +555,14 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
553
555
|
}
|
|
554
556
|
dateStrings.add(end.toISOString().slice(0, 10));
|
|
555
557
|
const entries = [];
|
|
556
|
-
const files = await this.getSessionStorageFiles(
|
|
558
|
+
const files = await this.getSessionStorageFiles(
|
|
559
|
+
this.transcriptsDir,
|
|
560
|
+
dir,
|
|
561
|
+
legacyDir,
|
|
562
|
+
alternateDir,
|
|
563
|
+
readbackDirs
|
|
564
|
+
);
|
|
565
|
+
const keepRawLine = makeRawLineDeduper();
|
|
557
566
|
for (const file of files) {
|
|
558
567
|
const dateStr = file.name.slice(0, 10);
|
|
559
568
|
if (!dateStrings.has(dateStr)) continue;
|
|
@@ -564,7 +573,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
564
573
|
try {
|
|
565
574
|
const entry = JSON.parse(line);
|
|
566
575
|
const ts = new Date(entry.timestamp);
|
|
567
|
-
if (ts >= start && ts < end && entry.sessionKey === sessionKey) {
|
|
576
|
+
if (ts >= start && ts < end && entry.sessionKey === sessionKey && keepRawLine(line)) {
|
|
568
577
|
entries.push(entry);
|
|
569
578
|
}
|
|
570
579
|
} catch {
|
|
@@ -911,4 +920,4 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
911
920
|
export {
|
|
912
921
|
TranscriptManager
|
|
913
922
|
};
|
|
914
|
-
//# sourceMappingURL=chunk-
|
|
923
|
+
//# sourceMappingURL=chunk-JLOJ5RJ7.js.map
|