@geminixiang/mama 0.2.0-beta.6 → 0.2.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -14
- package/dist/adapter.d.ts +5 -2
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +1 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +29 -9
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +1 -2
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +8 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +177 -11
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +1 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -1
- package/dist/adapters/slack/branch-manager.js +9 -8
- package/dist/adapters/slack/branch-manager.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +10 -8
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/tools/attach.d.ts +1 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +33 -2
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/agent.d.ts +1 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +507 -422
- package/dist/agent.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +41 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/model.d.ts +1 -1
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +25 -7
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +1 -1
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +88 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts.map +1 -1
- package/dist/commands/session-view.js +34 -10
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +1 -3
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/utils.d.ts +3 -0
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +5 -0
- package/dist/commands/utils.js.map +1 -1
- package/dist/config.d.ts +7 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +64 -23
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -44
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -210
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +15 -14
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +3 -2
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +40 -7
- package/dist/execution-resolver.js.map +1 -1
- package/dist/file-guards.d.ts +6 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +48 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/log.d.ts +1 -5
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +13 -38
- package/dist/log.js.map +1 -1
- package/dist/login/index.d.ts +14 -2
- package/dist/login/index.d.ts.map +1 -1
- package/dist/login/index.js +40 -13
- package/dist/login/index.js.map +1 -1
- package/dist/login/portal.d.ts +2 -1
- package/dist/login/portal.d.ts.map +1 -1
- package/dist/login/portal.js +12 -12
- package/dist/login/portal.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +33 -28
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +12 -2
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +43 -14
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-orchestrator.d.ts +42 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +150 -0
- package/dist/runtime/conversation-orchestrator.js.map +1 -0
- package/dist/runtime/session-runtime.d.ts +1 -1
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/session-runtime.js +49 -148
- package/dist/runtime/session-runtime.js.map +1 -1
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +2 -2
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +1 -1
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +4 -4
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sentry.d.ts +1 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +2 -2
- package/dist/sentry.js.map +1 -1
- package/dist/session-store.d.ts +2 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +19 -15
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/portal.d.ts +6 -1
- package/dist/session-view/portal.d.ts.map +1 -1
- package/dist/session-view/portal.js +829 -71
- package/dist/session-view/portal.js.map +1 -1
- package/dist/session-view/service.d.ts.map +1 -1
- package/dist/session-view/service.js +5 -4
- package/dist/session-view/service.js.map +1 -1
- package/dist/session-view/store.d.ts +2 -1
- package/dist/session-view/store.d.ts.map +1 -1
- package/dist/session-view/store.js +2 -1
- package/dist/session-view/store.js.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +7 -13
- package/dist/store.js.map +1 -1
- package/dist/tool-diagnostics.d.ts +2 -0
- package/dist/tool-diagnostics.d.ts.map +1 -0
- package/dist/tool-diagnostics.js +7 -0
- package/dist/tool-diagnostics.js.map +1 -0
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +1 -1
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/vault-routing.d.ts +0 -3
- package/dist/vault-routing.d.ts.map +1 -1
- package/dist/vault-routing.js +0 -24
- package/dist/vault-routing.js.map +1 -1
- package/dist/vault.d.ts +21 -57
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +114 -246
- package/dist/vault.js.map +1 -1
- package/package.json +6 -4
- package/dist/bindings.d.ts +0 -45
- package/dist/bindings.d.ts.map +0 -1
- package/dist/bindings.js +0 -75
- package/dist/bindings.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sandbox/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContainerExecutor, containerSandboxAdapter } from \"./container.js\";\nimport { FirecrackerExecutor, firecrackerSandboxAdapter } from \"./firecracker.js\";\nimport { CloudflareSandboxExecutor, cloudflareSandboxAdapter } from \"./cloudflare.js\";\nimport { HostExecutor, hostSandboxAdapter } from \"./host.js\";\nimport { imageSandboxAdapter } from \"./image.js\";\nimport { SandboxError } from \"./errors.js\";\nimport type { Executor, SandboxAdapter, SandboxConfig } from \"./types.js\";\n\nexport type {\n ContainerSandboxConfig,\n CloudflareSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n FirecrackerSandboxConfig,\n HostSandboxConfig,\n ImageSandboxConfig,\n SandboxAdapter,\n SandboxConfig,\n} from \"./types.js\";\nexport { CloudflareSandboxExecutor, ContainerExecutor, FirecrackerExecutor, HostExecutor };\nexport { SandboxError } from \"./errors.js\";\nexport {\n buildContainerExecCommand,\n containerSandboxAdapter,\n parseContainerSandboxArg,\n validateContainerSandbox,\n} from \"./container.js\";\nexport {\n firecrackerSandboxAdapter,\n parseFirecrackerSandboxArg,\n validateFirecrackerSandbox,\n} from \"./firecracker.js\";\nexport {\n cloudflareSandboxAdapter,\n parseCloudflareSandboxArg,\n validateCloudflareSandbox,\n} from \"./cloudflare.js\";\nexport { hostSandboxAdapter, parseHostSandboxArg, validateHostSandbox } from \"./host.js\";\nexport { imageSandboxAdapter, parseImageSandboxArg, validateImageSandbox } from \"./image.js\";\n\nconst sandboxAdapters = [\n hostSandboxAdapter,\n containerSandboxAdapter,\n imageSandboxAdapter,\n firecrackerSandboxAdapter,\n cloudflareSandboxAdapter,\n] as const;\nconst sandboxAdapterByType = new Map(\n sandboxAdapters.map((adapter) => [adapter.type, adapter]),\n) as Map<SandboxConfig[\"type\"], SandboxAdapter>;\n\nexport function getSandboxAdapters(): readonly [...typeof sandboxAdapters] {\n return sandboxAdapters;\n}\n\nexport function parseSandboxArg(value: string): SandboxConfig {\n for (const adapter of sandboxAdapters) {\n const config = adapter.parse(value);\n if (config) {\n return config;\n }\n }\n\n if (value.startsWith(\"docker:\")) {\n throw new SandboxError(\n `Error: '${value}' is not supported. Use 'container:<container-name>' for the shared-container mode or 'image:<image-name>' for mama-managed per-user containers.`,\n );\n }\n\n throw new SandboxError(\n `Error: Invalid sandbox type '${value}'. Use 'host', 'container:<container-name>', 'image:<image-name>', 'firecracker:<vm-id>:<host-path>', or 'cloudflare:<sandbox-id>'`,\n );\n}\n\nexport async function validateSandbox(config: SandboxConfig): Promise<void> {\n const adapter = sandboxAdapterByType.get(config.type);\n if (!adapter) {\n throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);\n }\n\n await adapter.validate(config);\n}\n\n/**\n * Create an executor that runs commands on host, in Docker, in a Firecracker VM, or through a Cloudflare sandbox bridge.\n */\nexport function createExecutor(\n config: SandboxConfig,\n env?: Record<string, string>,\n ensureReady?: () => Promise<void>,\n): Executor {\n const adapter = sandboxAdapterByType.get(config.type);\n if (!adapter) {\n throw new SandboxError(`Error: Unsupported sandbox type '${config.type}'`);\n }\n return adapter.createExecutor(config, env, ensureReady);\n}\n"]}
|
package/dist/sentry.d.ts
CHANGED
package/dist/sentry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA6CpF,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,
|
|
1
|
+
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA6CpF,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,uBAAuB,CAAC,GAAG,CAAC,EAAE,MAAM;;;;;;;;sBAS9B,UAAU,QAAQ,SAAS,GAAG,UAAU,GAAG,IAAI;iCAGpC,UAAU,GAAG,UAAU,GAAG,IAAI;EAI9D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAyBhF;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAChE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAO3C;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAC3D,IAAI,CAON;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,CAAC,GAAG,IAAI,CA2CpF;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI,CAU5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAwB9E","sourcesContent":["import type { Breadcrumb, ErrorEvent, Event, EventHint, Scope } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\nconst REDACTED = \"[REDACTED]\";\nconst REDACTED_PATH = \"[REDACTED_PATH]\";\nconst MAX_STRING_LENGTH = 256;\nconst MAX_DEPTH = 4;\n\nconst SENSITIVE_KEYS = new Set([\n \"args\",\n \"attachment\",\n \"attachments\",\n \"authorization\",\n \"body\",\n \"content\",\n \"contents\",\n \"cookie\",\n \"cookies\",\n \"filePath\",\n \"headers\",\n \"image\",\n \"imageAttachments\",\n \"images\",\n \"localPath\",\n \"messages\",\n \"newUserMessage\",\n \"path\",\n \"paths\",\n \"prompt\",\n \"response\",\n \"result\",\n \"systemPrompt\",\n \"text\",\n \"thinking\",\n]);\n\nconst ABSOLUTE_PATH_PATTERN =\n /(?:\\/Users\\/[^\\s\"'`]+|\\/workspace\\/[^\\s\"'`]+|\\/tmp\\/[^\\s\"'`]+|\\/var\\/folders\\/[^\\s\"'`]+|[A-Za-z]:\\\\[^\\s\"'`]+)/;\nconst TOKEN_PATTERNS = [\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[a-z]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n];\n\nexport interface SentryRunScopeContext {\n conversationId: string;\n sessionKey: string;\n messageId: string;\n platform: string;\n userId: string;\n userName?: string;\n threadTs?: string;\n provider?: string;\n model?: string;\n isSyntheticEvent?: boolean;\n}\n\nexport function createSentryInitOptions(dsn?: string) {\n return {\n dsn,\n environment: process.env.SENTRY_ENVIRONMENT ?? \"production\",\n enabled: Boolean(dsn) && process.env.SENTRY_ENABLED !== \"false\",\n sendDefaultPii: false,\n tracesSampleRate: process.env.NODE_ENV === \"development\" ? 1.0 : 1.0,\n includeLocalVariables: false,\n enableLogs: true,\n beforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null {\n return sanitizeEvent(event, hint);\n },\n beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n return sanitizeBreadcrumb(breadcrumb);\n },\n };\n}\n\nexport function applyRunScope(scope: Scope, context: SentryRunScopeContext): void {\n scope.setTag(\"conversation_id\", context.conversationId);\n scope.setTag(\"channel_id\", context.conversationId);\n scope.setTag(\"session_key\", context.sessionKey);\n scope.setTag(\"platform\", context.platform);\n scope.setTag(\"is_synthetic_event\", String(Boolean(context.isSyntheticEvent)));\n if (context.threadTs) scope.setTag(\"thread_ts\", context.threadTs);\n if (context.provider) scope.setTag(\"provider\", context.provider);\n if (context.model) scope.setTag(\"model\", context.model);\n\n scope.setUser({\n id: context.userId,\n username: context.userName,\n });\n scope.setContext(\"agent_run\", {\n conversationId: context.conversationId,\n channelId: context.conversationId,\n sessionKey: context.sessionKey,\n messageId: context.messageId,\n threadTs: context.threadTs,\n platform: context.platform,\n provider: context.provider,\n model: context.model,\n isSyntheticEvent: Boolean(context.isSyntheticEvent),\n });\n}\n\nexport function metricAttributes(\n attributes: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string | number | boolean] => {\n const [, value] = entry;\n return value !== undefined;\n }),\n );\n}\n\nexport function addLifecycleBreadcrumb(\n message: string,\n data?: Record<string, string | number | boolean | undefined>,\n): void {\n Sentry.addBreadcrumb({\n category: \"agent.lifecycle\",\n message,\n level: \"info\",\n data: data ? metricAttributes(data) : undefined,\n });\n}\n\nexport function sanitizeEvent<T extends Event>(event: T, _hint?: EventHint): T | null {\n const sanitized: T = {\n ...event,\n breadcrumbs: event.breadcrumbs\n ?.map((breadcrumb) => sanitizeBreadcrumb(breadcrumb))\n .filter((breadcrumb): breadcrumb is Breadcrumb => breadcrumb !== null),\n extra: sanitizeValue(event.extra) as T[\"extra\"],\n contexts: sanitizeValue(event.contexts) as T[\"contexts\"],\n request: sanitizeRequest(event.request),\n user: undefined,\n server_name: undefined,\n };\n\n if (sanitized.message) {\n sanitized.message = sanitizeString(sanitized.message);\n }\n\n if (sanitized.logentry) {\n sanitized.logentry = {\n ...sanitized.logentry,\n message: sanitized.logentry.message ? sanitizeString(sanitized.logentry.message) : undefined,\n };\n }\n\n if (sanitized.exception?.values) {\n sanitized.exception.values = sanitized.exception.values.map((value) => ({\n ...value,\n value: value.value ? sanitizeString(value.value) : value.value,\n stacktrace: value.stacktrace\n ? {\n ...value.stacktrace,\n frames: value.stacktrace.frames?.map((frame) => ({\n ...frame,\n filename: frame.filename ? sanitizeString(frame.filename) : frame.filename,\n abs_path: frame.abs_path ? sanitizeString(frame.abs_path) : frame.abs_path,\n vars: undefined,\n })),\n }\n : value.stacktrace,\n }));\n }\n\n return sanitized;\n}\n\nexport function sanitizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n if (breadcrumb.category === \"console\") {\n return null;\n }\n\n return {\n ...breadcrumb,\n message: breadcrumb.message ? sanitizeString(breadcrumb.message) : breadcrumb.message,\n data: sanitizeValue(breadcrumb.data) as Breadcrumb[\"data\"],\n };\n}\n\nexport function sanitizeValue(value: unknown, key?: string, depth = 0): unknown {\n if (value == null) return value;\n if (depth > MAX_DEPTH) return \"[Truncated]\";\n\n if (isSensitiveKey(key)) {\n return summarizeValue(value, key);\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((entry) => sanitizeValue(entry, key, depth + 1));\n }\n\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([entryKey, entryValue]) => [entryKey, sanitizeValue(entryValue, entryKey, depth + 1)],\n );\n return Object.fromEntries(entries);\n }\n\n return value;\n}\n\nfunction sanitizeRequest(request: Event[\"request\"]): Event[\"request\"] {\n if (!request) return request;\n\n return {\n ...request,\n data: request.data ? summarizeValue(request.data, \"body\") : undefined,\n headers: undefined,\n cookies: undefined,\n };\n}\n\nfunction isSensitiveKey(key?: string): boolean {\n if (!key) return false;\n return SENSITIVE_KEYS.has(key);\n}\n\nfunction summarizeValue(value: unknown, key?: string): string {\n const label = key ?? \"field\";\n if (typeof value === \"string\") {\n return `[Redacted ${label}; length=${value.length}]`;\n }\n if (Array.isArray(value)) {\n return `[Redacted ${label}; items=${value.length}]`;\n }\n if (value && typeof value === \"object\") {\n return `[Redacted ${label}; keys=${Object.keys(value as Record<string, unknown>).length}]`;\n }\n return `[Redacted ${label}]`;\n}\n\nfunction sanitizeString(value: string): string {\n let sanitized = value.replace(new RegExp(ABSOLUTE_PATH_PATTERN, \"g\"), REDACTED_PATH);\n for (const pattern of TOKEN_PATTERNS) {\n sanitized = sanitized.replace(new RegExp(pattern, \"g\"), REDACTED);\n }\n if (sanitized.length > MAX_STRING_LENGTH) {\n return `${sanitized.slice(0, MAX_STRING_LENGTH)}… [truncated ${sanitized.length - MAX_STRING_LENGTH} chars]`;\n }\n return sanitized;\n}\n"]}
|
package/dist/sentry.js
CHANGED
|
@@ -59,7 +59,7 @@ export function applyRunScope(scope, context) {
|
|
|
59
59
|
scope.setTag("channel_id", context.conversationId);
|
|
60
60
|
scope.setTag("session_key", context.sessionKey);
|
|
61
61
|
scope.setTag("platform", context.platform);
|
|
62
|
-
scope.setTag("
|
|
62
|
+
scope.setTag("is_synthetic_event", String(Boolean(context.isSyntheticEvent)));
|
|
63
63
|
if (context.threadTs)
|
|
64
64
|
scope.setTag("thread_ts", context.threadTs);
|
|
65
65
|
if (context.provider)
|
|
@@ -79,7 +79,7 @@ export function applyRunScope(scope, context) {
|
|
|
79
79
|
platform: context.platform,
|
|
80
80
|
provider: context.provider,
|
|
81
81
|
model: context.model,
|
|
82
|
-
|
|
82
|
+
isSyntheticEvent: Boolean(context.isSyntheticEvent),
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
export function metricAttributes(attributes) {
|
package/dist/sentry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Breadcrumb, ErrorEvent, Event, EventHint, Scope } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\nconst REDACTED = \"[REDACTED]\";\nconst REDACTED_PATH = \"[REDACTED_PATH]\";\nconst MAX_STRING_LENGTH = 256;\nconst MAX_DEPTH = 4;\n\nconst SENSITIVE_KEYS = new Set([\n \"args\",\n \"attachment\",\n \"attachments\",\n \"authorization\",\n \"body\",\n \"content\",\n \"contents\",\n \"cookie\",\n \"cookies\",\n \"filePath\",\n \"headers\",\n \"image\",\n \"imageAttachments\",\n \"images\",\n \"localPath\",\n \"messages\",\n \"newUserMessage\",\n \"path\",\n \"paths\",\n \"prompt\",\n \"response\",\n \"result\",\n \"systemPrompt\",\n \"text\",\n \"thinking\",\n]);\n\nconst ABSOLUTE_PATH_PATTERN =\n /(?:\\/Users\\/[^\\s\"'`]+|\\/workspace\\/[^\\s\"'`]+|\\/tmp\\/[^\\s\"'`]+|\\/var\\/folders\\/[^\\s\"'`]+|[A-Za-z]:\\\\[^\\s\"'`]+)/;\nconst TOKEN_PATTERNS = [\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[a-z]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n];\n\nexport interface SentryRunScopeContext {\n conversationId: string;\n sessionKey: string;\n messageId: string;\n platform: string;\n userId: string;\n userName?: string;\n threadTs?: string;\n provider?: string;\n model?: string;\n isEvent?: boolean;\n}\n\nexport function createSentryInitOptions(dsn?: string) {\n return {\n dsn,\n environment: process.env.SENTRY_ENVIRONMENT ?? \"production\",\n enabled: Boolean(dsn) && process.env.SENTRY_ENABLED !== \"false\",\n sendDefaultPii: false,\n tracesSampleRate: process.env.NODE_ENV === \"development\" ? 1.0 : 1.0,\n includeLocalVariables: false,\n enableLogs: true,\n beforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null {\n return sanitizeEvent(event, hint);\n },\n beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n return sanitizeBreadcrumb(breadcrumb);\n },\n };\n}\n\nexport function applyRunScope(scope: Scope, context: SentryRunScopeContext): void {\n scope.setTag(\"conversation_id\", context.conversationId);\n scope.setTag(\"channel_id\", context.conversationId);\n scope.setTag(\"session_key\", context.sessionKey);\n scope.setTag(\"platform\", context.platform);\n scope.setTag(\"is_event\", String(Boolean(context.isEvent)));\n if (context.threadTs) scope.setTag(\"thread_ts\", context.threadTs);\n if (context.provider) scope.setTag(\"provider\", context.provider);\n if (context.model) scope.setTag(\"model\", context.model);\n\n scope.setUser({\n id: context.userId,\n username: context.userName,\n });\n scope.setContext(\"agent_run\", {\n conversationId: context.conversationId,\n channelId: context.conversationId,\n sessionKey: context.sessionKey,\n messageId: context.messageId,\n threadTs: context.threadTs,\n platform: context.platform,\n provider: context.provider,\n model: context.model,\n isEvent: Boolean(context.isEvent),\n });\n}\n\nexport function metricAttributes(\n attributes: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string | number | boolean] => {\n const [, value] = entry;\n return value !== undefined;\n }),\n );\n}\n\nexport function addLifecycleBreadcrumb(\n message: string,\n data?: Record<string, string | number | boolean | undefined>,\n): void {\n Sentry.addBreadcrumb({\n category: \"agent.lifecycle\",\n message,\n level: \"info\",\n data: data ? metricAttributes(data) : undefined,\n });\n}\n\nexport function sanitizeEvent<T extends Event>(event: T, _hint?: EventHint): T | null {\n const sanitized: T = {\n ...event,\n breadcrumbs: event.breadcrumbs\n ?.map((breadcrumb) => sanitizeBreadcrumb(breadcrumb))\n .filter((breadcrumb): breadcrumb is Breadcrumb => breadcrumb !== null),\n extra: sanitizeValue(event.extra) as T[\"extra\"],\n contexts: sanitizeValue(event.contexts) as T[\"contexts\"],\n request: sanitizeRequest(event.request),\n user: undefined,\n server_name: undefined,\n };\n\n if (sanitized.message) {\n sanitized.message = sanitizeString(sanitized.message);\n }\n\n if (sanitized.logentry) {\n sanitized.logentry = {\n ...sanitized.logentry,\n message: sanitized.logentry.message ? sanitizeString(sanitized.logentry.message) : undefined,\n };\n }\n\n if (sanitized.exception?.values) {\n sanitized.exception.values = sanitized.exception.values.map((value) => ({\n ...value,\n value: value.value ? sanitizeString(value.value) : value.value,\n stacktrace: value.stacktrace\n ? {\n ...value.stacktrace,\n frames: value.stacktrace.frames?.map((frame) => ({\n ...frame,\n filename: frame.filename ? sanitizeString(frame.filename) : frame.filename,\n abs_path: frame.abs_path ? sanitizeString(frame.abs_path) : frame.abs_path,\n vars: undefined,\n })),\n }\n : value.stacktrace,\n }));\n }\n\n return sanitized;\n}\n\nexport function sanitizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n if (breadcrumb.category === \"console\") {\n return null;\n }\n\n return {\n ...breadcrumb,\n message: breadcrumb.message ? sanitizeString(breadcrumb.message) : breadcrumb.message,\n data: sanitizeValue(breadcrumb.data) as Breadcrumb[\"data\"],\n };\n}\n\nexport function sanitizeValue(value: unknown, key?: string, depth = 0): unknown {\n if (value == null) return value;\n if (depth > MAX_DEPTH) return \"[Truncated]\";\n\n if (isSensitiveKey(key)) {\n return summarizeValue(value, key);\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((entry) => sanitizeValue(entry, key, depth + 1));\n }\n\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([entryKey, entryValue]) => [entryKey, sanitizeValue(entryValue, entryKey, depth + 1)],\n );\n return Object.fromEntries(entries);\n }\n\n return value;\n}\n\nfunction sanitizeRequest(request: Event[\"request\"]): Event[\"request\"] {\n if (!request) return request;\n\n return {\n ...request,\n data: request.data ? summarizeValue(request.data, \"body\") : undefined,\n headers: undefined,\n cookies: undefined,\n };\n}\n\nfunction isSensitiveKey(key?: string): boolean {\n if (!key) return false;\n return SENSITIVE_KEYS.has(key);\n}\n\nfunction summarizeValue(value: unknown, key?: string): string {\n const label = key ?? \"field\";\n if (typeof value === \"string\") {\n return `[Redacted ${label}; length=${value.length}]`;\n }\n if (Array.isArray(value)) {\n return `[Redacted ${label}; items=${value.length}]`;\n }\n if (value && typeof value === \"object\") {\n return `[Redacted ${label}; keys=${Object.keys(value as Record<string, unknown>).length}]`;\n }\n return `[Redacted ${label}]`;\n}\n\nfunction sanitizeString(value: string): string {\n let sanitized = value.replace(new RegExp(ABSOLUTE_PATH_PATTERN, \"g\"), REDACTED_PATH);\n for (const pattern of TOKEN_PATTERNS) {\n sanitized = sanitized.replace(new RegExp(pattern, \"g\"), REDACTED);\n }\n if (sanitized.length > MAX_STRING_LENGTH) {\n return `${sanitized.slice(0, MAX_STRING_LENGTH)}… [truncated ${sanitized.length - MAX_STRING_LENGTH} chars]`;\n }\n return sanitized;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Breadcrumb, ErrorEvent, Event, EventHint, Scope } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\nconst REDACTED = \"[REDACTED]\";\nconst REDACTED_PATH = \"[REDACTED_PATH]\";\nconst MAX_STRING_LENGTH = 256;\nconst MAX_DEPTH = 4;\n\nconst SENSITIVE_KEYS = new Set([\n \"args\",\n \"attachment\",\n \"attachments\",\n \"authorization\",\n \"body\",\n \"content\",\n \"contents\",\n \"cookie\",\n \"cookies\",\n \"filePath\",\n \"headers\",\n \"image\",\n \"imageAttachments\",\n \"images\",\n \"localPath\",\n \"messages\",\n \"newUserMessage\",\n \"path\",\n \"paths\",\n \"prompt\",\n \"response\",\n \"result\",\n \"systemPrompt\",\n \"text\",\n \"thinking\",\n]);\n\nconst ABSOLUTE_PATH_PATTERN =\n /(?:\\/Users\\/[^\\s\"'`]+|\\/workspace\\/[^\\s\"'`]+|\\/tmp\\/[^\\s\"'`]+|\\/var\\/folders\\/[^\\s\"'`]+|[A-Za-z]:\\\\[^\\s\"'`]+)/;\nconst TOKEN_PATTERNS = [\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[a-z]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n];\n\nexport interface SentryRunScopeContext {\n conversationId: string;\n sessionKey: string;\n messageId: string;\n platform: string;\n userId: string;\n userName?: string;\n threadTs?: string;\n provider?: string;\n model?: string;\n isSyntheticEvent?: boolean;\n}\n\nexport function createSentryInitOptions(dsn?: string) {\n return {\n dsn,\n environment: process.env.SENTRY_ENVIRONMENT ?? \"production\",\n enabled: Boolean(dsn) && process.env.SENTRY_ENABLED !== \"false\",\n sendDefaultPii: false,\n tracesSampleRate: process.env.NODE_ENV === \"development\" ? 1.0 : 1.0,\n includeLocalVariables: false,\n enableLogs: true,\n beforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null {\n return sanitizeEvent(event, hint);\n },\n beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n return sanitizeBreadcrumb(breadcrumb);\n },\n };\n}\n\nexport function applyRunScope(scope: Scope, context: SentryRunScopeContext): void {\n scope.setTag(\"conversation_id\", context.conversationId);\n scope.setTag(\"channel_id\", context.conversationId);\n scope.setTag(\"session_key\", context.sessionKey);\n scope.setTag(\"platform\", context.platform);\n scope.setTag(\"is_synthetic_event\", String(Boolean(context.isSyntheticEvent)));\n if (context.threadTs) scope.setTag(\"thread_ts\", context.threadTs);\n if (context.provider) scope.setTag(\"provider\", context.provider);\n if (context.model) scope.setTag(\"model\", context.model);\n\n scope.setUser({\n id: context.userId,\n username: context.userName,\n });\n scope.setContext(\"agent_run\", {\n conversationId: context.conversationId,\n channelId: context.conversationId,\n sessionKey: context.sessionKey,\n messageId: context.messageId,\n threadTs: context.threadTs,\n platform: context.platform,\n provider: context.provider,\n model: context.model,\n isSyntheticEvent: Boolean(context.isSyntheticEvent),\n });\n}\n\nexport function metricAttributes(\n attributes: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string | number | boolean] => {\n const [, value] = entry;\n return value !== undefined;\n }),\n );\n}\n\nexport function addLifecycleBreadcrumb(\n message: string,\n data?: Record<string, string | number | boolean | undefined>,\n): void {\n Sentry.addBreadcrumb({\n category: \"agent.lifecycle\",\n message,\n level: \"info\",\n data: data ? metricAttributes(data) : undefined,\n });\n}\n\nexport function sanitizeEvent<T extends Event>(event: T, _hint?: EventHint): T | null {\n const sanitized: T = {\n ...event,\n breadcrumbs: event.breadcrumbs\n ?.map((breadcrumb) => sanitizeBreadcrumb(breadcrumb))\n .filter((breadcrumb): breadcrumb is Breadcrumb => breadcrumb !== null),\n extra: sanitizeValue(event.extra) as T[\"extra\"],\n contexts: sanitizeValue(event.contexts) as T[\"contexts\"],\n request: sanitizeRequest(event.request),\n user: undefined,\n server_name: undefined,\n };\n\n if (sanitized.message) {\n sanitized.message = sanitizeString(sanitized.message);\n }\n\n if (sanitized.logentry) {\n sanitized.logentry = {\n ...sanitized.logentry,\n message: sanitized.logentry.message ? sanitizeString(sanitized.logentry.message) : undefined,\n };\n }\n\n if (sanitized.exception?.values) {\n sanitized.exception.values = sanitized.exception.values.map((value) => ({\n ...value,\n value: value.value ? sanitizeString(value.value) : value.value,\n stacktrace: value.stacktrace\n ? {\n ...value.stacktrace,\n frames: value.stacktrace.frames?.map((frame) => ({\n ...frame,\n filename: frame.filename ? sanitizeString(frame.filename) : frame.filename,\n abs_path: frame.abs_path ? sanitizeString(frame.abs_path) : frame.abs_path,\n vars: undefined,\n })),\n }\n : value.stacktrace,\n }));\n }\n\n return sanitized;\n}\n\nexport function sanitizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n if (breadcrumb.category === \"console\") {\n return null;\n }\n\n return {\n ...breadcrumb,\n message: breadcrumb.message ? sanitizeString(breadcrumb.message) : breadcrumb.message,\n data: sanitizeValue(breadcrumb.data) as Breadcrumb[\"data\"],\n };\n}\n\nexport function sanitizeValue(value: unknown, key?: string, depth = 0): unknown {\n if (value == null) return value;\n if (depth > MAX_DEPTH) return \"[Truncated]\";\n\n if (isSensitiveKey(key)) {\n return summarizeValue(value, key);\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((entry) => sanitizeValue(entry, key, depth + 1));\n }\n\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([entryKey, entryValue]) => [entryKey, sanitizeValue(entryValue, entryKey, depth + 1)],\n );\n return Object.fromEntries(entries);\n }\n\n return value;\n}\n\nfunction sanitizeRequest(request: Event[\"request\"]): Event[\"request\"] {\n if (!request) return request;\n\n return {\n ...request,\n data: request.data ? summarizeValue(request.data, \"body\") : undefined,\n headers: undefined,\n cookies: undefined,\n };\n}\n\nfunction isSensitiveKey(key?: string): boolean {\n if (!key) return false;\n return SENSITIVE_KEYS.has(key);\n}\n\nfunction summarizeValue(value: unknown, key?: string): string {\n const label = key ?? \"field\";\n if (typeof value === \"string\") {\n return `[Redacted ${label}; length=${value.length}]`;\n }\n if (Array.isArray(value)) {\n return `[Redacted ${label}; items=${value.length}]`;\n }\n if (value && typeof value === \"object\") {\n return `[Redacted ${label}; keys=${Object.keys(value as Record<string, unknown>).length}]`;\n }\n return `[Redacted ${label}]`;\n}\n\nfunction sanitizeString(value: string): string {\n let sanitized = value.replace(new RegExp(ABSOLUTE_PATH_PATTERN, \"g\"), REDACTED_PATH);\n for (const pattern of TOKEN_PATTERNS) {\n sanitized = sanitized.replace(new RegExp(pattern, \"g\"), REDACTED);\n }\n if (sanitized.length > MAX_STRING_LENGTH) {\n return `${sanitized.slice(0, MAX_STRING_LENGTH)}… [truncated ${sanitized.length - MAX_STRING_LENGTH} chars]`;\n }\n return sanitized;\n}\n"]}
|
package/dist/session-store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SessionManager } from "@
|
|
1
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export declare class ThreadRootNotFoundError extends Error {
|
|
3
3
|
constructor(sessionFile: string);
|
|
4
4
|
}
|
|
@@ -16,6 +16,7 @@ export interface ResolvedSessionScope {
|
|
|
16
16
|
export interface ResolveGenericSessionScopeOptions {
|
|
17
17
|
conversationDir: string;
|
|
18
18
|
sessionKey: string;
|
|
19
|
+
cwd?: string;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Returns the shared session directory for a conversation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAG/D,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,WAAW,EAAE,MAAM,EAG9B;CACF;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIjF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAS/D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,cAAc,CAShB;AAQD;;GAEG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvF;AAeD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,oBAAoB,CAoBtB;AA4HD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI1E;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE1E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE3E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,iBAAiB,EAAE,MAAM,EACzB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,MAAM,GACV,MAAM,CAWR;AAED,wBAAgB,sCAAsC,CACpD,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,EAC9B,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAiCR;AAED,wBAAgB,oCAAoC,CAClD,iBAAiB,EAAE,MAAM,EACzB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,GAC7B,MAAM,CAqBR","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from \"fs\";\nimport { join } from \"path\";\nimport { SessionManager } from \"@mariozechner/pi-coding-agent\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class ThreadRootNotFoundError extends Error {\n constructor(sessionFile: string) {\n super(`Thread root message not found in source session: ${sessionFile}`);\n this.name = \"ThreadRootNotFoundError\";\n }\n}\n\nexport interface ThreadRootMessage {\n text?: string;\n userName?: string;\n user?: string;\n loggedAt?: number;\n}\n\nexport interface ResolvedSessionScope {\n sessionDir: string;\n contextFile: string;\n threadRootMessage: ThreadRootMessage | null;\n}\n\nexport interface ResolveGenericSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n}\n\ninterface SessionMessageEntryLike {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n message?: {\n role?: string;\n timestamp?: number;\n content?: Array<{ type?: string; text?: string }> | string;\n };\n}\n\n/**\n * Returns the shared session directory for a conversation.\n * Channel sessions use a current pointer within this directory.\n * Thread sessions are stored as fixed files within the same directory.\n */\nexport function getChannelSessionDir(channelDir: string): string {\n return join(channelDir, \"sessions\");\n}\n\n/**\n * Resolves the current active session file for a session directory.\n * Reads the \"current\" pointer file; creates a new session if none exists\n * or the pointed-to file is missing.\n */\nexport function resolveSessionFile(sessionDir: string): string {\n const existing = tryResolveCurrentSession(sessionDir);\n if (existing) return existing;\n return createNewSessionFile(sessionDir);\n}\n\n/**\n * Resolve the current active session file for a session directory.\n * Creates a fully initialized persistent session with the provided cwd when none exists.\n */\nexport function resolveManagedSessionFile(sessionDir: string, cwd: string): string {\n const existingPath = getCurrentSessionPath(sessionDir);\n if (existingPath) return existingPath;\n return createManagedSessionFile(sessionDir, cwd);\n}\n\n/**\n * Extracts the short UUID from a session file path.\n * e.g. \"2026-04-05T00-00_7b54cf90.jsonl\" → \"7b54cf90\"\n */\nexport function extractSessionUuid(sessionFile: string): string {\n const base = sessionFile.split(\"/\").pop() ?? sessionFile;\n return base.replace(\".jsonl\", \"\").split(\"_\").pop() ?? base;\n}\n\n/**\n * Extracts the thread/suffix part of a session key.\n * \"channelId:threadId\" → \"threadId\", \"channelId\" → \"channelId\"\n */\nexport function extractSessionSuffix(sessionKey: string): string {\n return sessionKey.includes(\":\") ? sessionKey.split(\":\").pop()! : sessionKey;\n}\n\n/**\n * Creates an empty timestamped file and updates the \"current\" pointer.\n * Used only by tests for placeholder-file scenarios.\n *\n * Order matters: write the session file first, then atomic-rename the pointer\n * last so a crash mid-create never leaves \"current\" pointing at a missing file.\n */\nexport function createNewSessionFile(sessionDir: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const uuid = randomUUID().slice(0, 8);\n const filename = `${timestamp}_${uuid}.jsonl`;\n const filePath = join(sessionDir, filename);\n atomicWritePrivateFile(filePath, \"\");\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n return filePath;\n}\n\n/**\n * Creates a new persistent session file with a proper SessionManager header and cwd.\n * Also updates the \"current\" pointer. Header is written before the pointer flips so a\n * partial create cannot leave \"current\" pointing at a missing file.\n */\nexport function createManagedSessionFile(sessionDir: string, cwd: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const sessionId = randomUUID();\n const sessionFile = join(sessionDir, `${timestamp}_${sessionId.slice(0, 8)}.jsonl`);\n writeSessionHeader(sessionFile, cwd, sessionId);\n setCurrentPointer(sessionDir, sessionFile);\n return sessionFile;\n}\n\n/**\n * Open a session file with an explicit cwd, even if the file does not exist yet.\n * This avoids SessionManager.open() falling back to process.cwd() for fresh sessions.\n */\nexport function openManagedSession(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n): SessionManager {\n if (shouldRecreatePreinitializedSession(sessionFile)) {\n rmSync(sessionFile, { force: true });\n }\n\n const SessionManagerCtor = SessionManager as unknown as {\n new (cwd: string, sessionDir: string, sessionFile: string, persist: boolean): SessionManager;\n };\n return new SessionManagerCtor(cwd, sessionDir, sessionFile, true);\n}\n\nfunction setCurrentPointer(sessionDir: string, sessionFilePath: string): void {\n const filename = sessionFilePath.split(\"/\").pop()!;\n mkdirSync(sessionDir, { recursive: true });\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n}\n\n/**\n * Creates or overwrites a fixed-path session file with a valid session header.\n */\nexport function createManagedSessionFileAtPath(sessionFile: string, cwd: string): string {\n writeSessionHeader(sessionFile, cwd);\n return sessionFile;\n}\n\nfunction writeSessionHeader(sessionFile: string, cwd: string, sessionId = randomUUID()): void {\n const sessionDir = getFileDir(sessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: new Date().toISOString(),\n cwd,\n };\n atomicWritePrivateFile(sessionFile, `${JSON.stringify(header)}\\n`);\n}\n\n/**\n * Returns the fixed session file path for a Slack thread.\n */\nexport function getThreadSessionFile(channelDir: string, sessionKey: string): string {\n return join(getChannelSessionDir(channelDir), `${extractSessionSuffix(sessionKey)}.jsonl`);\n}\n\n/**\n * Resolve the default session scope for platforms without Slack-style branch forking.\n * Top-level/private sessions use the conversation's current pointer. Threaded or\n * per-message sessions use a fixed file derived from the session key suffix.\n */\nexport function resolveGenericSessionScope(\n options: ResolveGenericSessionScopeOptions,\n): ResolvedSessionScope {\n const { conversationDir, sessionKey } = options;\n const sessionDir = getChannelSessionDir(conversationDir);\n\n if (!sessionKey.includes(\":\")) {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, conversationDir),\n threadRootMessage: null,\n };\n }\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return {\n sessionDir,\n contextFile:\n tryResolveThreadSession(threadFile) ??\n createManagedSessionFileAtPath(threadFile, conversationDir),\n threadRootMessage: null,\n };\n}\n\nfunction hasSessionHeader(sessionFile: string): boolean {\n try {\n const lines = readFileSync(sessionFile, \"utf-8\").split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const entry = JSON.parse(trimmed) as { type?: string };\n return entry.type === \"session\";\n }\n } catch {\n return false;\n }\n return false;\n}\n\nfunction shouldRecreatePreinitializedSession(sessionFile: string): boolean {\n if (!existsSync(sessionFile)) return false;\n\n try {\n const entries = readFileSync(sessionFile, \"utf-8\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => JSON.parse(line) as { type?: string });\n\n return entries.length === 1 && entries[0]?.type === \"session\";\n } catch {\n return false;\n }\n}\n\nfunction getFileDir(sessionFile: string): string {\n return sessionFile.substring(0, sessionFile.lastIndexOf(\"/\"));\n}\n\nfunction resolveThreadSnapshotEntries(\n sourceSessionFile: string,\n rootMessage: ThreadRootMessage,\n): SessionMessageEntryLike[] | null {\n const targetText = buildComparableRootMessageText(rootMessage);\n if (!targetText) return null;\n\n const entries = SessionManager.open(sourceSessionFile).getEntries() as SessionMessageEntryLike[];\n const matchIndex = findRootMessageIndex(entries, targetText, rootMessage.loggedAt);\n if (matchIndex === -1) return null;\n\n const nextTopLevelUserIndex = entries.findIndex(\n (entry, index) => index > matchIndex && isUserMessageEntry(entry),\n );\n const endIndex = nextTopLevelUserIndex === -1 ? entries.length : nextTopLevelUserIndex;\n return entries.slice(0, endIndex);\n}\n\nfunction findRootMessageIndex(\n entries: SessionMessageEntryLike[],\n targetText: string,\n loggedAt?: number,\n): number {\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (!isUserMessageEntry(entry)) continue;\n\n const comparableText = normalizeComparableUserText(getMessageText(entry));\n if (comparableText !== targetText) continue;\n\n const messageTimestamp = entry.message?.timestamp;\n if (\n loggedAt !== undefined &&\n typeof messageTimestamp === \"number\" &&\n messageTimestamp < loggedAt\n ) {\n continue;\n }\n\n return i;\n }\n\n return -1;\n}\n\nfunction isUserMessageEntry(entry: SessionMessageEntryLike): boolean {\n return entry.type === \"message\" && entry.message?.role === \"user\";\n}\n\nfunction getMessageText(entry: SessionMessageEntryLike): string {\n const content = entry.message?.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n return content\n .filter((part): part is { type?: string; text?: string } => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\\n\\n\");\n}\n\nfunction buildComparableRootMessageText(rootMessage: ThreadRootMessage): string | null {\n const userLabel = rootMessage.userName || rootMessage.user || \"unknown\";\n const text = rootMessage.text?.trim();\n if (!text) return null;\n return normalizeComparableUserText(`[${userLabel}]: ${text}`);\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction getCurrentSessionPath(sessionDir: string): string | null {\n const pointerFile = join(sessionDir, \"current\");\n if (!existsSync(pointerFile)) return null;\n const filename = readFileSync(pointerFile, \"utf-8\").trim();\n if (!filename) return null;\n return join(sessionDir, filename);\n}\n\n/**\n * Try to resolve an existing current session file.\n * Returns null if no current pointer exists or the pointed file has no valid session header.\n */\nexport function tryResolveCurrentSession(sessionDir: string): string | null {\n const fullPath = getCurrentSessionPath(sessionDir);\n if (fullPath && existsSync(fullPath) && hasSessionHeader(fullPath)) return fullPath;\n return null;\n}\n\n/**\n * Try to resolve an existing thread session file.\n * Returns the file path if found, or null if no valid thread session exists yet.\n */\nexport function tryResolveThreadSession(sessionFile: string): string | null {\n return existsSync(sessionFile) && hasSessionHeader(sessionFile) ? sessionFile : null;\n}\n\n/**\n * Resolve the channel's current session file path (for fork source).\n * Returns null if no channel session exists.\n */\nexport function resolveChannelSessionFile(channelDir: string): string | null {\n return tryResolveCurrentSession(getChannelSessionDir(channelDir));\n}\n\n/**\n * Fork a channel session into a fixed thread-session path.\n * The resulting file keeps forkFrom's distinct session/header metadata.\n */\nexport function forkThreadSessionFile(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const forked = SessionManager.forkFrom(sourceSessionFile, cwd, sessionDir);\n const forkedFile = forked.getSessionFile();\n if (!forkedFile) {\n throw new Error(`Failed to fork session from ${sourceSessionFile}`);\n }\n rmSync(targetSessionFile, { force: true });\n renameSync(forkedFile, targetSessionFile);\n return targetSessionFile;\n}\n\nexport function createThreadSessionFileFromRootMessage(\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n parentSession?: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n ...(parentSession ? { parentSession } : {}),\n };\n const rootText = buildComparableRootMessageText(rootMessage);\n if (!rootText) {\n atomicWritePrivateFile(targetSessionFile, `${JSON.stringify(header)}\\n`);\n return targetSessionFile;\n }\n\n const rootEntry = {\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date().toISOString(),\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: rootText }],\n ...(rootMessage.loggedAt !== undefined ? { timestamp: rootMessage.loggedAt } : {}),\n },\n };\n const content = [header, rootEntry].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n\nexport function forkThreadSessionFileFromRootMessage(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n): string {\n const snapshotEntries = resolveThreadSnapshotEntries(sourceSessionFile, rootMessage);\n if (!snapshotEntries) {\n throw new ThreadRootNotFoundError(sourceSessionFile);\n }\n\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n parentSession: sourceSessionFile,\n };\n const content = [header, ...snapshotEntries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAIjE,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,WAAW,EAAE,MAAM,EAG9B;CACF;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAcD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIjF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAS/D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,cAAc,CAShB;AAQD;;GAEG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGvF;AAeD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,oBAAoB,CAoBtB;AAuID;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI1E;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE1E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE3E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,iBAAiB,EAAE,MAAM,EACzB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,MAAM,GACV,MAAM,CAWR;AAED,wBAAgB,sCAAsC,CACpD,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,EAC9B,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAiCR;AAED,wBAAgB,oCAAoC,CAClD,iBAAiB,EAAE,MAAM,EACzB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,GAC7B,MAAM,CAqBR","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, renameSync, rmSync } from \"fs\";\nimport { join } from \"path\";\nimport { SessionManager } from \"@earendil-works/pi-coding-agent\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class ThreadRootNotFoundError extends Error {\n constructor(sessionFile: string) {\n super(`Thread root message not found in source session: ${sessionFile}`);\n this.name = \"ThreadRootNotFoundError\";\n }\n}\n\nexport interface ThreadRootMessage {\n text?: string;\n userName?: string;\n user?: string;\n loggedAt?: number;\n}\n\nexport interface ResolvedSessionScope {\n sessionDir: string;\n contextFile: string;\n threadRootMessage: ThreadRootMessage | null;\n}\n\nexport interface ResolveGenericSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n}\n\ninterface SessionMessageEntryLike {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n message?: {\n role?: string;\n timestamp?: number;\n content?: Array<{ type?: string; text?: string }> | string;\n };\n}\n\n/**\n * Returns the shared session directory for a conversation.\n * Channel sessions use a current pointer within this directory.\n * Thread sessions are stored as fixed files within the same directory.\n */\nexport function getChannelSessionDir(channelDir: string): string {\n return join(channelDir, \"sessions\");\n}\n\n/**\n * Resolves the current active session file for a session directory.\n * Reads the \"current\" pointer file; creates a new session if none exists\n * or the pointed-to file is missing.\n */\nexport function resolveSessionFile(sessionDir: string): string {\n const existing = tryResolveCurrentSession(sessionDir);\n if (existing) return existing;\n return createNewSessionFile(sessionDir);\n}\n\n/**\n * Resolve the current active session file for a session directory.\n * Creates a fully initialized persistent session with the provided cwd when none exists.\n */\nexport function resolveManagedSessionFile(sessionDir: string, cwd: string): string {\n const existingPath = getCurrentSessionPath(sessionDir);\n if (existingPath) return existingPath;\n return createManagedSessionFile(sessionDir, cwd);\n}\n\n/**\n * Extracts the short UUID from a session file path.\n * e.g. \"2026-04-05T00-00_7b54cf90.jsonl\" → \"7b54cf90\"\n */\nexport function extractSessionUuid(sessionFile: string): string {\n const base = sessionFile.split(\"/\").pop() ?? sessionFile;\n return base.replace(\".jsonl\", \"\").split(\"_\").pop() ?? base;\n}\n\n/**\n * Extracts the thread/suffix part of a session key.\n * \"channelId:threadId\" → \"threadId\", \"channelId\" → \"channelId\"\n */\nexport function extractSessionSuffix(sessionKey: string): string {\n const parts = sessionKey.split(\":\");\n return parts.length > 1 ? parts[parts.length - 1] : sessionKey;\n}\n\n/**\n * Creates an empty timestamped file and updates the \"current\" pointer.\n * Used only by tests for placeholder-file scenarios.\n *\n * Order matters: write the session file first, then atomic-rename the pointer\n * last so a crash mid-create never leaves \"current\" pointing at a missing file.\n */\nexport function createNewSessionFile(sessionDir: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const uuid = randomUUID().slice(0, 8);\n const filename = `${timestamp}_${uuid}.jsonl`;\n const filePath = join(sessionDir, filename);\n atomicWritePrivateFile(filePath, \"\");\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n return filePath;\n}\n\n/**\n * Creates a new persistent session file with a proper SessionManager header and cwd.\n * Also updates the \"current\" pointer. Header is written before the pointer flips so a\n * partial create cannot leave \"current\" pointing at a missing file.\n */\nexport function createManagedSessionFile(sessionDir: string, cwd: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const sessionId = randomUUID();\n const sessionFile = join(sessionDir, `${timestamp}_${sessionId.slice(0, 8)}.jsonl`);\n writeSessionHeader(sessionFile, cwd, sessionId);\n setCurrentPointer(sessionDir, sessionFile);\n return sessionFile;\n}\n\n/**\n * Open a session file with an explicit cwd, even if the file does not exist yet.\n * This avoids SessionManager.open() falling back to process.cwd() for fresh sessions.\n */\nexport function openManagedSession(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n): SessionManager {\n if (shouldRecreatePreinitializedSession(sessionFile)) {\n rmSync(sessionFile, { force: true });\n }\n\n const SessionManagerCtor = SessionManager as unknown as {\n new (cwd: string, sessionDir: string, sessionFile: string, persist: boolean): SessionManager;\n };\n return new SessionManagerCtor(cwd, sessionDir, sessionFile, true);\n}\n\nfunction setCurrentPointer(sessionDir: string, sessionFilePath: string): void {\n const filename = sessionFilePath.split(\"/\").pop()!;\n mkdirSync(sessionDir, { recursive: true });\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n}\n\n/**\n * Creates or overwrites a fixed-path session file with a valid session header.\n */\nexport function createManagedSessionFileAtPath(sessionFile: string, cwd: string): string {\n writeSessionHeader(sessionFile, cwd);\n return sessionFile;\n}\n\nfunction writeSessionHeader(sessionFile: string, cwd: string, sessionId = randomUUID()): void {\n const sessionDir = getFileDir(sessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: new Date().toISOString(),\n cwd,\n };\n atomicWritePrivateFile(sessionFile, `${JSON.stringify(header)}\\n`);\n}\n\n/**\n * Returns the fixed session file path for a Slack thread.\n */\nexport function getThreadSessionFile(channelDir: string, sessionKey: string): string {\n return join(getChannelSessionDir(channelDir), `${extractSessionSuffix(sessionKey)}.jsonl`);\n}\n\n/**\n * Resolve the default session scope for platforms without Slack-style branch forking.\n * Top-level/private sessions use the conversation's current pointer. Threaded or\n * per-message sessions use a fixed file derived from the session key suffix.\n */\nexport function resolveGenericSessionScope(\n options: ResolveGenericSessionScopeOptions,\n): ResolvedSessionScope {\n const { conversationDir, sessionKey } = options;\n const cwd = options.cwd ?? conversationDir;\n const sessionDir = getChannelSessionDir(conversationDir);\n\n if (!sessionKey.includes(\":\")) {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, cwd),\n threadRootMessage: null,\n };\n }\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return {\n sessionDir,\n contextFile:\n tryResolveThreadSession(threadFile) ?? createManagedSessionFileAtPath(threadFile, cwd),\n threadRootMessage: null,\n };\n}\n\nfunction hasSessionHeader(sessionFile: string): boolean {\n try {\n const raw = readTextFileIfExists(sessionFile);\n if (raw === undefined) return false;\n const lines = raw.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const entry = parseJsonValue(\n trimmed,\n (value): value is { type?: string } => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n return entry.type === \"session\";\n }\n } catch {\n return false;\n }\n return false;\n}\n\nfunction shouldRecreatePreinitializedSession(sessionFile: string): boolean {\n try {\n const raw = readTextFileIfExists(sessionFile);\n if (raw === undefined) return false;\n const entries = raw\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) =>\n parseJsonValue(\n line,\n (value): value is { type?: string } => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n ),\n );\n\n return entries.length === 1 && entries[0]?.type === \"session\";\n } catch {\n return false;\n }\n}\n\nfunction getFileDir(sessionFile: string): string {\n return sessionFile.substring(0, sessionFile.lastIndexOf(\"/\"));\n}\n\nfunction resolveThreadSnapshotEntries(\n sourceSessionFile: string,\n rootMessage: ThreadRootMessage,\n): SessionMessageEntryLike[] | null {\n const targetText = buildComparableRootMessageText(rootMessage);\n if (!targetText) return null;\n\n const entries = SessionManager.open(sourceSessionFile).getEntries() as SessionMessageEntryLike[];\n const matchIndex = findRootMessageIndex(entries, targetText, rootMessage.loggedAt);\n if (matchIndex === -1) return null;\n\n const nextTopLevelUserIndex = entries.findIndex(\n (entry, index) => index > matchIndex && isUserMessageEntry(entry),\n );\n const endIndex = nextTopLevelUserIndex === -1 ? entries.length : nextTopLevelUserIndex;\n return entries.slice(0, endIndex);\n}\n\nfunction findRootMessageIndex(\n entries: SessionMessageEntryLike[],\n targetText: string,\n loggedAt?: number,\n): number {\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (!isUserMessageEntry(entry)) continue;\n\n const comparableText = normalizeComparableUserText(getMessageText(entry));\n if (comparableText !== targetText) continue;\n\n const messageTimestamp = entry.message?.timestamp;\n if (\n loggedAt !== undefined &&\n typeof messageTimestamp === \"number\" &&\n messageTimestamp < loggedAt\n ) {\n continue;\n }\n\n return i;\n }\n\n return -1;\n}\n\nfunction isUserMessageEntry(entry: SessionMessageEntryLike): boolean {\n return entry.type === \"message\" && entry.message?.role === \"user\";\n}\n\nfunction getMessageText(entry: SessionMessageEntryLike): string {\n const content = entry.message?.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n return content\n .filter((part): part is { type?: string; text?: string } => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\\n\\n\");\n}\n\nfunction buildComparableRootMessageText(rootMessage: ThreadRootMessage): string | null {\n const userLabel = rootMessage.userName || rootMessage.user || \"unknown\";\n const text = rootMessage.text?.trim();\n if (!text) return null;\n return normalizeComparableUserText(`[${userLabel}]: ${text}`);\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction getCurrentSessionPath(sessionDir: string): string | null {\n const pointerFile = join(sessionDir, \"current\");\n const filename = readTextFileIfExists(pointerFile)?.trim();\n if (!filename) return null;\n return join(sessionDir, filename);\n}\n\n/**\n * Try to resolve an existing current session file.\n * Returns null if no current pointer exists or the pointed file has no valid session header.\n */\nexport function tryResolveCurrentSession(sessionDir: string): string | null {\n const fullPath = getCurrentSessionPath(sessionDir);\n if (fullPath && existsSync(fullPath) && hasSessionHeader(fullPath)) return fullPath;\n return null;\n}\n\n/**\n * Try to resolve an existing thread session file.\n * Returns the file path if found, or null if no valid thread session exists yet.\n */\nexport function tryResolveThreadSession(sessionFile: string): string | null {\n return existsSync(sessionFile) && hasSessionHeader(sessionFile) ? sessionFile : null;\n}\n\n/**\n * Resolve the channel's current session file path (for fork source).\n * Returns null if no channel session exists.\n */\nexport function resolveChannelSessionFile(channelDir: string): string | null {\n return tryResolveCurrentSession(getChannelSessionDir(channelDir));\n}\n\n/**\n * Fork a channel session into a fixed thread-session path.\n * The resulting file keeps forkFrom's distinct session/header metadata.\n */\nexport function forkThreadSessionFile(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const forked = SessionManager.forkFrom(sourceSessionFile, cwd, sessionDir);\n const forkedFile = forked.getSessionFile();\n if (!forkedFile) {\n throw new Error(`Failed to fork session from ${sourceSessionFile}`);\n }\n rmSync(targetSessionFile, { force: true });\n renameSync(forkedFile, targetSessionFile);\n return targetSessionFile;\n}\n\nexport function createThreadSessionFileFromRootMessage(\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n parentSession?: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n ...(parentSession ? { parentSession } : {}),\n };\n const rootText = buildComparableRootMessageText(rootMessage);\n if (!rootText) {\n atomicWritePrivateFile(targetSessionFile, `${JSON.stringify(header)}\\n`);\n return targetSessionFile;\n }\n\n const rootEntry = {\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date().toISOString(),\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: rootText }],\n ...(rootMessage.loggedAt !== undefined ? { timestamp: rootMessage.loggedAt } : {}),\n },\n };\n const content = [header, rootEntry].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n\nexport function forkThreadSessionFileFromRootMessage(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n): string {\n const snapshotEntries = resolveThreadSnapshotEntries(sourceSessionFile, rootMessage);\n if (!snapshotEntries) {\n throw new ThreadRootNotFoundError(sourceSessionFile);\n }\n\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n parentSession: sourceSessionFile,\n };\n const content = [header, ...snapshotEntries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n"]}
|
package/dist/session-store.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
-
import { existsSync, mkdirSync,
|
|
2
|
+
import { existsSync, mkdirSync, renameSync, rmSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { SessionManager } from "@
|
|
4
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { isRecord, parseJsonValue, readTextFileIfExists } from "./file-guards.js";
|
|
5
6
|
import { atomicWritePrivateFile } from "./fs-atomic.js";
|
|
6
7
|
export class ThreadRootNotFoundError extends Error {
|
|
7
8
|
constructor(sessionFile) {
|
|
@@ -51,7 +52,8 @@ export function extractSessionUuid(sessionFile) {
|
|
|
51
52
|
* "channelId:threadId" → "threadId", "channelId" → "channelId"
|
|
52
53
|
*/
|
|
53
54
|
export function extractSessionSuffix(sessionKey) {
|
|
54
|
-
|
|
55
|
+
const parts = sessionKey.split(":");
|
|
56
|
+
return parts.length > 1 ? parts[parts.length - 1] : sessionKey;
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
59
|
* Creates an empty timestamped file and updates the "current" pointer.
|
|
@@ -132,30 +134,33 @@ export function getThreadSessionFile(channelDir, sessionKey) {
|
|
|
132
134
|
*/
|
|
133
135
|
export function resolveGenericSessionScope(options) {
|
|
134
136
|
const { conversationDir, sessionKey } = options;
|
|
137
|
+
const cwd = options.cwd ?? conversationDir;
|
|
135
138
|
const sessionDir = getChannelSessionDir(conversationDir);
|
|
136
139
|
if (!sessionKey.includes(":")) {
|
|
137
140
|
return {
|
|
138
141
|
sessionDir,
|
|
139
|
-
contextFile: resolveManagedSessionFile(sessionDir,
|
|
142
|
+
contextFile: resolveManagedSessionFile(sessionDir, cwd),
|
|
140
143
|
threadRootMessage: null,
|
|
141
144
|
};
|
|
142
145
|
}
|
|
143
146
|
const threadFile = getThreadSessionFile(conversationDir, sessionKey);
|
|
144
147
|
return {
|
|
145
148
|
sessionDir,
|
|
146
|
-
contextFile: tryResolveThreadSession(threadFile) ??
|
|
147
|
-
createManagedSessionFileAtPath(threadFile, conversationDir),
|
|
149
|
+
contextFile: tryResolveThreadSession(threadFile) ?? createManagedSessionFileAtPath(threadFile, cwd),
|
|
148
150
|
threadRootMessage: null,
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
153
|
function hasSessionHeader(sessionFile) {
|
|
152
154
|
try {
|
|
153
|
-
const
|
|
155
|
+
const raw = readTextFileIfExists(sessionFile);
|
|
156
|
+
if (raw === undefined)
|
|
157
|
+
return false;
|
|
158
|
+
const lines = raw.split("\n");
|
|
154
159
|
for (const line of lines) {
|
|
155
160
|
const trimmed = line.trim();
|
|
156
161
|
if (!trimmed)
|
|
157
162
|
continue;
|
|
158
|
-
const entry =
|
|
163
|
+
const entry = parseJsonValue(trimmed, (value) => isRecord(value), (detail) => (detail === "unexpected JSON shape" ? "expected a JSON object" : detail));
|
|
159
164
|
return entry.type === "session";
|
|
160
165
|
}
|
|
161
166
|
}
|
|
@@ -165,14 +170,15 @@ function hasSessionHeader(sessionFile) {
|
|
|
165
170
|
return false;
|
|
166
171
|
}
|
|
167
172
|
function shouldRecreatePreinitializedSession(sessionFile) {
|
|
168
|
-
if (!existsSync(sessionFile))
|
|
169
|
-
return false;
|
|
170
173
|
try {
|
|
171
|
-
const
|
|
174
|
+
const raw = readTextFileIfExists(sessionFile);
|
|
175
|
+
if (raw === undefined)
|
|
176
|
+
return false;
|
|
177
|
+
const entries = raw
|
|
172
178
|
.split("\n")
|
|
173
179
|
.map((line) => line.trim())
|
|
174
180
|
.filter(Boolean)
|
|
175
|
-
.map((line) =>
|
|
181
|
+
.map((line) => parseJsonValue(line, (value) => isRecord(value), (detail) => (detail === "unexpected JSON shape" ? "expected a JSON object" : detail)));
|
|
176
182
|
return entries.length === 1 && entries[0]?.type === "session";
|
|
177
183
|
}
|
|
178
184
|
catch {
|
|
@@ -242,9 +248,7 @@ function normalizeComparableUserText(text) {
|
|
|
242
248
|
}
|
|
243
249
|
function getCurrentSessionPath(sessionDir) {
|
|
244
250
|
const pointerFile = join(sessionDir, "current");
|
|
245
|
-
|
|
246
|
-
return null;
|
|
247
|
-
const filename = readFileSync(pointerFile, "utf-8").trim();
|
|
251
|
+
const filename = readTextFileIfExists(pointerFile)?.trim();
|
|
248
252
|
if (!filename)
|
|
249
253
|
return null;
|
|
250
254
|
return join(sessionDir, filename);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from \"fs\";\nimport { join } from \"path\";\nimport { SessionManager } from \"@mariozechner/pi-coding-agent\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class ThreadRootNotFoundError extends Error {\n constructor(sessionFile: string) {\n super(`Thread root message not found in source session: ${sessionFile}`);\n this.name = \"ThreadRootNotFoundError\";\n }\n}\n\nexport interface ThreadRootMessage {\n text?: string;\n userName?: string;\n user?: string;\n loggedAt?: number;\n}\n\nexport interface ResolvedSessionScope {\n sessionDir: string;\n contextFile: string;\n threadRootMessage: ThreadRootMessage | null;\n}\n\nexport interface ResolveGenericSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n}\n\ninterface SessionMessageEntryLike {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n message?: {\n role?: string;\n timestamp?: number;\n content?: Array<{ type?: string; text?: string }> | string;\n };\n}\n\n/**\n * Returns the shared session directory for a conversation.\n * Channel sessions use a current pointer within this directory.\n * Thread sessions are stored as fixed files within the same directory.\n */\nexport function getChannelSessionDir(channelDir: string): string {\n return join(channelDir, \"sessions\");\n}\n\n/**\n * Resolves the current active session file for a session directory.\n * Reads the \"current\" pointer file; creates a new session if none exists\n * or the pointed-to file is missing.\n */\nexport function resolveSessionFile(sessionDir: string): string {\n const existing = tryResolveCurrentSession(sessionDir);\n if (existing) return existing;\n return createNewSessionFile(sessionDir);\n}\n\n/**\n * Resolve the current active session file for a session directory.\n * Creates a fully initialized persistent session with the provided cwd when none exists.\n */\nexport function resolveManagedSessionFile(sessionDir: string, cwd: string): string {\n const existingPath = getCurrentSessionPath(sessionDir);\n if (existingPath) return existingPath;\n return createManagedSessionFile(sessionDir, cwd);\n}\n\n/**\n * Extracts the short UUID from a session file path.\n * e.g. \"2026-04-05T00-00_7b54cf90.jsonl\" → \"7b54cf90\"\n */\nexport function extractSessionUuid(sessionFile: string): string {\n const base = sessionFile.split(\"/\").pop() ?? sessionFile;\n return base.replace(\".jsonl\", \"\").split(\"_\").pop() ?? base;\n}\n\n/**\n * Extracts the thread/suffix part of a session key.\n * \"channelId:threadId\" → \"threadId\", \"channelId\" → \"channelId\"\n */\nexport function extractSessionSuffix(sessionKey: string): string {\n return sessionKey.includes(\":\") ? sessionKey.split(\":\").pop()! : sessionKey;\n}\n\n/**\n * Creates an empty timestamped file and updates the \"current\" pointer.\n * Used only by tests for placeholder-file scenarios.\n *\n * Order matters: write the session file first, then atomic-rename the pointer\n * last so a crash mid-create never leaves \"current\" pointing at a missing file.\n */\nexport function createNewSessionFile(sessionDir: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const uuid = randomUUID().slice(0, 8);\n const filename = `${timestamp}_${uuid}.jsonl`;\n const filePath = join(sessionDir, filename);\n atomicWritePrivateFile(filePath, \"\");\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n return filePath;\n}\n\n/**\n * Creates a new persistent session file with a proper SessionManager header and cwd.\n * Also updates the \"current\" pointer. Header is written before the pointer flips so a\n * partial create cannot leave \"current\" pointing at a missing file.\n */\nexport function createManagedSessionFile(sessionDir: string, cwd: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const sessionId = randomUUID();\n const sessionFile = join(sessionDir, `${timestamp}_${sessionId.slice(0, 8)}.jsonl`);\n writeSessionHeader(sessionFile, cwd, sessionId);\n setCurrentPointer(sessionDir, sessionFile);\n return sessionFile;\n}\n\n/**\n * Open a session file with an explicit cwd, even if the file does not exist yet.\n * This avoids SessionManager.open() falling back to process.cwd() for fresh sessions.\n */\nexport function openManagedSession(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n): SessionManager {\n if (shouldRecreatePreinitializedSession(sessionFile)) {\n rmSync(sessionFile, { force: true });\n }\n\n const SessionManagerCtor = SessionManager as unknown as {\n new (cwd: string, sessionDir: string, sessionFile: string, persist: boolean): SessionManager;\n };\n return new SessionManagerCtor(cwd, sessionDir, sessionFile, true);\n}\n\nfunction setCurrentPointer(sessionDir: string, sessionFilePath: string): void {\n const filename = sessionFilePath.split(\"/\").pop()!;\n mkdirSync(sessionDir, { recursive: true });\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n}\n\n/**\n * Creates or overwrites a fixed-path session file with a valid session header.\n */\nexport function createManagedSessionFileAtPath(sessionFile: string, cwd: string): string {\n writeSessionHeader(sessionFile, cwd);\n return sessionFile;\n}\n\nfunction writeSessionHeader(sessionFile: string, cwd: string, sessionId = randomUUID()): void {\n const sessionDir = getFileDir(sessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: new Date().toISOString(),\n cwd,\n };\n atomicWritePrivateFile(sessionFile, `${JSON.stringify(header)}\\n`);\n}\n\n/**\n * Returns the fixed session file path for a Slack thread.\n */\nexport function getThreadSessionFile(channelDir: string, sessionKey: string): string {\n return join(getChannelSessionDir(channelDir), `${extractSessionSuffix(sessionKey)}.jsonl`);\n}\n\n/**\n * Resolve the default session scope for platforms without Slack-style branch forking.\n * Top-level/private sessions use the conversation's current pointer. Threaded or\n * per-message sessions use a fixed file derived from the session key suffix.\n */\nexport function resolveGenericSessionScope(\n options: ResolveGenericSessionScopeOptions,\n): ResolvedSessionScope {\n const { conversationDir, sessionKey } = options;\n const sessionDir = getChannelSessionDir(conversationDir);\n\n if (!sessionKey.includes(\":\")) {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, conversationDir),\n threadRootMessage: null,\n };\n }\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return {\n sessionDir,\n contextFile:\n tryResolveThreadSession(threadFile) ??\n createManagedSessionFileAtPath(threadFile, conversationDir),\n threadRootMessage: null,\n };\n}\n\nfunction hasSessionHeader(sessionFile: string): boolean {\n try {\n const lines = readFileSync(sessionFile, \"utf-8\").split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const entry = JSON.parse(trimmed) as { type?: string };\n return entry.type === \"session\";\n }\n } catch {\n return false;\n }\n return false;\n}\n\nfunction shouldRecreatePreinitializedSession(sessionFile: string): boolean {\n if (!existsSync(sessionFile)) return false;\n\n try {\n const entries = readFileSync(sessionFile, \"utf-8\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => JSON.parse(line) as { type?: string });\n\n return entries.length === 1 && entries[0]?.type === \"session\";\n } catch {\n return false;\n }\n}\n\nfunction getFileDir(sessionFile: string): string {\n return sessionFile.substring(0, sessionFile.lastIndexOf(\"/\"));\n}\n\nfunction resolveThreadSnapshotEntries(\n sourceSessionFile: string,\n rootMessage: ThreadRootMessage,\n): SessionMessageEntryLike[] | null {\n const targetText = buildComparableRootMessageText(rootMessage);\n if (!targetText) return null;\n\n const entries = SessionManager.open(sourceSessionFile).getEntries() as SessionMessageEntryLike[];\n const matchIndex = findRootMessageIndex(entries, targetText, rootMessage.loggedAt);\n if (matchIndex === -1) return null;\n\n const nextTopLevelUserIndex = entries.findIndex(\n (entry, index) => index > matchIndex && isUserMessageEntry(entry),\n );\n const endIndex = nextTopLevelUserIndex === -1 ? entries.length : nextTopLevelUserIndex;\n return entries.slice(0, endIndex);\n}\n\nfunction findRootMessageIndex(\n entries: SessionMessageEntryLike[],\n targetText: string,\n loggedAt?: number,\n): number {\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (!isUserMessageEntry(entry)) continue;\n\n const comparableText = normalizeComparableUserText(getMessageText(entry));\n if (comparableText !== targetText) continue;\n\n const messageTimestamp = entry.message?.timestamp;\n if (\n loggedAt !== undefined &&\n typeof messageTimestamp === \"number\" &&\n messageTimestamp < loggedAt\n ) {\n continue;\n }\n\n return i;\n }\n\n return -1;\n}\n\nfunction isUserMessageEntry(entry: SessionMessageEntryLike): boolean {\n return entry.type === \"message\" && entry.message?.role === \"user\";\n}\n\nfunction getMessageText(entry: SessionMessageEntryLike): string {\n const content = entry.message?.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n return content\n .filter((part): part is { type?: string; text?: string } => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\\n\\n\");\n}\n\nfunction buildComparableRootMessageText(rootMessage: ThreadRootMessage): string | null {\n const userLabel = rootMessage.userName || rootMessage.user || \"unknown\";\n const text = rootMessage.text?.trim();\n if (!text) return null;\n return normalizeComparableUserText(`[${userLabel}]: ${text}`);\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction getCurrentSessionPath(sessionDir: string): string | null {\n const pointerFile = join(sessionDir, \"current\");\n if (!existsSync(pointerFile)) return null;\n const filename = readFileSync(pointerFile, \"utf-8\").trim();\n if (!filename) return null;\n return join(sessionDir, filename);\n}\n\n/**\n * Try to resolve an existing current session file.\n * Returns null if no current pointer exists or the pointed file has no valid session header.\n */\nexport function tryResolveCurrentSession(sessionDir: string): string | null {\n const fullPath = getCurrentSessionPath(sessionDir);\n if (fullPath && existsSync(fullPath) && hasSessionHeader(fullPath)) return fullPath;\n return null;\n}\n\n/**\n * Try to resolve an existing thread session file.\n * Returns the file path if found, or null if no valid thread session exists yet.\n */\nexport function tryResolveThreadSession(sessionFile: string): string | null {\n return existsSync(sessionFile) && hasSessionHeader(sessionFile) ? sessionFile : null;\n}\n\n/**\n * Resolve the channel's current session file path (for fork source).\n * Returns null if no channel session exists.\n */\nexport function resolveChannelSessionFile(channelDir: string): string | null {\n return tryResolveCurrentSession(getChannelSessionDir(channelDir));\n}\n\n/**\n * Fork a channel session into a fixed thread-session path.\n * The resulting file keeps forkFrom's distinct session/header metadata.\n */\nexport function forkThreadSessionFile(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const forked = SessionManager.forkFrom(sourceSessionFile, cwd, sessionDir);\n const forkedFile = forked.getSessionFile();\n if (!forkedFile) {\n throw new Error(`Failed to fork session from ${sourceSessionFile}`);\n }\n rmSync(targetSessionFile, { force: true });\n renameSync(forkedFile, targetSessionFile);\n return targetSessionFile;\n}\n\nexport function createThreadSessionFileFromRootMessage(\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n parentSession?: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n ...(parentSession ? { parentSession } : {}),\n };\n const rootText = buildComparableRootMessageText(rootMessage);\n if (!rootText) {\n atomicWritePrivateFile(targetSessionFile, `${JSON.stringify(header)}\\n`);\n return targetSessionFile;\n }\n\n const rootEntry = {\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date().toISOString(),\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: rootText }],\n ...(rootMessage.loggedAt !== undefined ? { timestamp: rootMessage.loggedAt } : {}),\n },\n };\n const content = [header, rootEntry].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n\nexport function forkThreadSessionFileFromRootMessage(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n): string {\n const snapshotEntries = resolveThreadSnapshotEntries(sourceSessionFile, rootMessage);\n if (!snapshotEntries) {\n throw new ThreadRootNotFoundError(sourceSessionFile);\n }\n\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n parentSession: sourceSessionFile,\n };\n const content = [header, ...snapshotEntries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, renameSync, rmSync } from \"fs\";\nimport { join } from \"path\";\nimport { SessionManager } from \"@earendil-works/pi-coding-agent\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class ThreadRootNotFoundError extends Error {\n constructor(sessionFile: string) {\n super(`Thread root message not found in source session: ${sessionFile}`);\n this.name = \"ThreadRootNotFoundError\";\n }\n}\n\nexport interface ThreadRootMessage {\n text?: string;\n userName?: string;\n user?: string;\n loggedAt?: number;\n}\n\nexport interface ResolvedSessionScope {\n sessionDir: string;\n contextFile: string;\n threadRootMessage: ThreadRootMessage | null;\n}\n\nexport interface ResolveGenericSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n}\n\ninterface SessionMessageEntryLike {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n message?: {\n role?: string;\n timestamp?: number;\n content?: Array<{ type?: string; text?: string }> | string;\n };\n}\n\n/**\n * Returns the shared session directory for a conversation.\n * Channel sessions use a current pointer within this directory.\n * Thread sessions are stored as fixed files within the same directory.\n */\nexport function getChannelSessionDir(channelDir: string): string {\n return join(channelDir, \"sessions\");\n}\n\n/**\n * Resolves the current active session file for a session directory.\n * Reads the \"current\" pointer file; creates a new session if none exists\n * or the pointed-to file is missing.\n */\nexport function resolveSessionFile(sessionDir: string): string {\n const existing = tryResolveCurrentSession(sessionDir);\n if (existing) return existing;\n return createNewSessionFile(sessionDir);\n}\n\n/**\n * Resolve the current active session file for a session directory.\n * Creates a fully initialized persistent session with the provided cwd when none exists.\n */\nexport function resolveManagedSessionFile(sessionDir: string, cwd: string): string {\n const existingPath = getCurrentSessionPath(sessionDir);\n if (existingPath) return existingPath;\n return createManagedSessionFile(sessionDir, cwd);\n}\n\n/**\n * Extracts the short UUID from a session file path.\n * e.g. \"2026-04-05T00-00_7b54cf90.jsonl\" → \"7b54cf90\"\n */\nexport function extractSessionUuid(sessionFile: string): string {\n const base = sessionFile.split(\"/\").pop() ?? sessionFile;\n return base.replace(\".jsonl\", \"\").split(\"_\").pop() ?? base;\n}\n\n/**\n * Extracts the thread/suffix part of a session key.\n * \"channelId:threadId\" → \"threadId\", \"channelId\" → \"channelId\"\n */\nexport function extractSessionSuffix(sessionKey: string): string {\n const parts = sessionKey.split(\":\");\n return parts.length > 1 ? parts[parts.length - 1] : sessionKey;\n}\n\n/**\n * Creates an empty timestamped file and updates the \"current\" pointer.\n * Used only by tests for placeholder-file scenarios.\n *\n * Order matters: write the session file first, then atomic-rename the pointer\n * last so a crash mid-create never leaves \"current\" pointing at a missing file.\n */\nexport function createNewSessionFile(sessionDir: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const uuid = randomUUID().slice(0, 8);\n const filename = `${timestamp}_${uuid}.jsonl`;\n const filePath = join(sessionDir, filename);\n atomicWritePrivateFile(filePath, \"\");\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n return filePath;\n}\n\n/**\n * Creates a new persistent session file with a proper SessionManager header and cwd.\n * Also updates the \"current\" pointer. Header is written before the pointer flips so a\n * partial create cannot leave \"current\" pointing at a missing file.\n */\nexport function createManagedSessionFile(sessionDir: string, cwd: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const sessionId = randomUUID();\n const sessionFile = join(sessionDir, `${timestamp}_${sessionId.slice(0, 8)}.jsonl`);\n writeSessionHeader(sessionFile, cwd, sessionId);\n setCurrentPointer(sessionDir, sessionFile);\n return sessionFile;\n}\n\n/**\n * Open a session file with an explicit cwd, even if the file does not exist yet.\n * This avoids SessionManager.open() falling back to process.cwd() for fresh sessions.\n */\nexport function openManagedSession(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n): SessionManager {\n if (shouldRecreatePreinitializedSession(sessionFile)) {\n rmSync(sessionFile, { force: true });\n }\n\n const SessionManagerCtor = SessionManager as unknown as {\n new (cwd: string, sessionDir: string, sessionFile: string, persist: boolean): SessionManager;\n };\n return new SessionManagerCtor(cwd, sessionDir, sessionFile, true);\n}\n\nfunction setCurrentPointer(sessionDir: string, sessionFilePath: string): void {\n const filename = sessionFilePath.split(\"/\").pop()!;\n mkdirSync(sessionDir, { recursive: true });\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n}\n\n/**\n * Creates or overwrites a fixed-path session file with a valid session header.\n */\nexport function createManagedSessionFileAtPath(sessionFile: string, cwd: string): string {\n writeSessionHeader(sessionFile, cwd);\n return sessionFile;\n}\n\nfunction writeSessionHeader(sessionFile: string, cwd: string, sessionId = randomUUID()): void {\n const sessionDir = getFileDir(sessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: new Date().toISOString(),\n cwd,\n };\n atomicWritePrivateFile(sessionFile, `${JSON.stringify(header)}\\n`);\n}\n\n/**\n * Returns the fixed session file path for a Slack thread.\n */\nexport function getThreadSessionFile(channelDir: string, sessionKey: string): string {\n return join(getChannelSessionDir(channelDir), `${extractSessionSuffix(sessionKey)}.jsonl`);\n}\n\n/**\n * Resolve the default session scope for platforms without Slack-style branch forking.\n * Top-level/private sessions use the conversation's current pointer. Threaded or\n * per-message sessions use a fixed file derived from the session key suffix.\n */\nexport function resolveGenericSessionScope(\n options: ResolveGenericSessionScopeOptions,\n): ResolvedSessionScope {\n const { conversationDir, sessionKey } = options;\n const cwd = options.cwd ?? conversationDir;\n const sessionDir = getChannelSessionDir(conversationDir);\n\n if (!sessionKey.includes(\":\")) {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, cwd),\n threadRootMessage: null,\n };\n }\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return {\n sessionDir,\n contextFile:\n tryResolveThreadSession(threadFile) ?? createManagedSessionFileAtPath(threadFile, cwd),\n threadRootMessage: null,\n };\n}\n\nfunction hasSessionHeader(sessionFile: string): boolean {\n try {\n const raw = readTextFileIfExists(sessionFile);\n if (raw === undefined) return false;\n const lines = raw.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const entry = parseJsonValue(\n trimmed,\n (value): value is { type?: string } => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n return entry.type === \"session\";\n }\n } catch {\n return false;\n }\n return false;\n}\n\nfunction shouldRecreatePreinitializedSession(sessionFile: string): boolean {\n try {\n const raw = readTextFileIfExists(sessionFile);\n if (raw === undefined) return false;\n const entries = raw\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) =>\n parseJsonValue(\n line,\n (value): value is { type?: string } => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n ),\n );\n\n return entries.length === 1 && entries[0]?.type === \"session\";\n } catch {\n return false;\n }\n}\n\nfunction getFileDir(sessionFile: string): string {\n return sessionFile.substring(0, sessionFile.lastIndexOf(\"/\"));\n}\n\nfunction resolveThreadSnapshotEntries(\n sourceSessionFile: string,\n rootMessage: ThreadRootMessage,\n): SessionMessageEntryLike[] | null {\n const targetText = buildComparableRootMessageText(rootMessage);\n if (!targetText) return null;\n\n const entries = SessionManager.open(sourceSessionFile).getEntries() as SessionMessageEntryLike[];\n const matchIndex = findRootMessageIndex(entries, targetText, rootMessage.loggedAt);\n if (matchIndex === -1) return null;\n\n const nextTopLevelUserIndex = entries.findIndex(\n (entry, index) => index > matchIndex && isUserMessageEntry(entry),\n );\n const endIndex = nextTopLevelUserIndex === -1 ? entries.length : nextTopLevelUserIndex;\n return entries.slice(0, endIndex);\n}\n\nfunction findRootMessageIndex(\n entries: SessionMessageEntryLike[],\n targetText: string,\n loggedAt?: number,\n): number {\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (!isUserMessageEntry(entry)) continue;\n\n const comparableText = normalizeComparableUserText(getMessageText(entry));\n if (comparableText !== targetText) continue;\n\n const messageTimestamp = entry.message?.timestamp;\n if (\n loggedAt !== undefined &&\n typeof messageTimestamp === \"number\" &&\n messageTimestamp < loggedAt\n ) {\n continue;\n }\n\n return i;\n }\n\n return -1;\n}\n\nfunction isUserMessageEntry(entry: SessionMessageEntryLike): boolean {\n return entry.type === \"message\" && entry.message?.role === \"user\";\n}\n\nfunction getMessageText(entry: SessionMessageEntryLike): string {\n const content = entry.message?.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n return content\n .filter((part): part is { type?: string; text?: string } => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\\n\\n\");\n}\n\nfunction buildComparableRootMessageText(rootMessage: ThreadRootMessage): string | null {\n const userLabel = rootMessage.userName || rootMessage.user || \"unknown\";\n const text = rootMessage.text?.trim();\n if (!text) return null;\n return normalizeComparableUserText(`[${userLabel}]: ${text}`);\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction getCurrentSessionPath(sessionDir: string): string | null {\n const pointerFile = join(sessionDir, \"current\");\n const filename = readTextFileIfExists(pointerFile)?.trim();\n if (!filename) return null;\n return join(sessionDir, filename);\n}\n\n/**\n * Try to resolve an existing current session file.\n * Returns null if no current pointer exists or the pointed file has no valid session header.\n */\nexport function tryResolveCurrentSession(sessionDir: string): string | null {\n const fullPath = getCurrentSessionPath(sessionDir);\n if (fullPath && existsSync(fullPath) && hasSessionHeader(fullPath)) return fullPath;\n return null;\n}\n\n/**\n * Try to resolve an existing thread session file.\n * Returns the file path if found, or null if no valid thread session exists yet.\n */\nexport function tryResolveThreadSession(sessionFile: string): string | null {\n return existsSync(sessionFile) && hasSessionHeader(sessionFile) ? sessionFile : null;\n}\n\n/**\n * Resolve the channel's current session file path (for fork source).\n * Returns null if no channel session exists.\n */\nexport function resolveChannelSessionFile(channelDir: string): string | null {\n return tryResolveCurrentSession(getChannelSessionDir(channelDir));\n}\n\n/**\n * Fork a channel session into a fixed thread-session path.\n * The resulting file keeps forkFrom's distinct session/header metadata.\n */\nexport function forkThreadSessionFile(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const forked = SessionManager.forkFrom(sourceSessionFile, cwd, sessionDir);\n const forkedFile = forked.getSessionFile();\n if (!forkedFile) {\n throw new Error(`Failed to fork session from ${sourceSessionFile}`);\n }\n rmSync(targetSessionFile, { force: true });\n renameSync(forkedFile, targetSessionFile);\n return targetSessionFile;\n}\n\nexport function createThreadSessionFileFromRootMessage(\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n parentSession?: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n ...(parentSession ? { parentSession } : {}),\n };\n const rootText = buildComparableRootMessageText(rootMessage);\n if (!rootText) {\n atomicWritePrivateFile(targetSessionFile, `${JSON.stringify(header)}\\n`);\n return targetSessionFile;\n }\n\n const rootEntry = {\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date().toISOString(),\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: rootText }],\n ...(rootMessage.loggedAt !== undefined ? { timestamp: rootMessage.loggedAt } : {}),\n },\n };\n const content = [header, rootEntry].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n\nexport function forkThreadSessionFileFromRootMessage(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n): string {\n const snapshotEntries = resolveThreadSnapshotEntries(sourceSessionFile, rootMessage);\n if (!snapshotEntries) {\n throw new ThreadRootNotFoundError(sourceSessionFile);\n }\n\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n parentSession: sourceSessionFile,\n };\n const content = [header, ...snapshotEntries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n"]}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
2
|
+
import type { Bot, BotHandler } from "../adapter.js";
|
|
2
3
|
import type { InMemorySessionViewTokenStore } from "./store.js";
|
|
3
|
-
export
|
|
4
|
+
export interface SessionViewInteractiveOptions {
|
|
5
|
+
handler: BotHandler;
|
|
6
|
+
botsByPlatform: Partial<Record<string, Bot>>;
|
|
7
|
+
}
|
|
8
|
+
export declare function handleSessionViewRequest(req: IncomingMessage, res: ServerResponse, url: URL, sessionViewTokenStore?: InMemorySessionViewTokenStore, interactive?: SessionViewInteractiveOptions): Promise<boolean>;
|
|
4
9
|
export declare function parseUserBody(raw: string): {
|
|
5
10
|
timestamp: string | null;
|
|
6
11
|
username: string | null;
|