@theokit/sdk 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +187 -0
- package/dist/a2a/agent-mailbox.d.cts +27 -0
- package/dist/a2a/agent-mailbox.d.ts +27 -0
- package/dist/a2a/index.cjs +16850 -0
- package/dist/a2a/index.cjs.map +1 -0
- package/dist/a2a/index.d.cts +9 -0
- package/dist/a2a/index.d.ts +9 -0
- package/dist/a2a/index.js +16844 -0
- package/dist/a2a/index.js.map +1 -0
- package/dist/a2a/message-bus.d.cts +27 -0
- package/dist/a2a/message-bus.d.ts +27 -0
- package/dist/a2a/subagent.d.cts +25 -0
- package/dist/a2a/subagent.d.ts +25 -0
- package/dist/a2a/types.d.cts +12 -0
- package/dist/a2a/types.d.ts +12 -0
- package/dist/agent.d.ts +1 -1
- package/dist/client/index.cjs +73 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +7 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.js +71 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/theokit-client.d.cts +18 -0
- package/dist/client/theokit-client.d.ts +18 -0
- package/dist/client/types.d.cts +19 -0
- package/dist/client/types.d.ts +19 -0
- package/dist/{run-DkCD5DeO.d.cts → cron-BnywDYLq.d.cts} +496 -910
- package/dist/{run-DkCD5DeO.d.ts → cron-CtZvJD9J.d.ts} +496 -910
- package/dist/cron.cjs +4285 -2893
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -3
- package/dist/cron.d.ts +2 -71
- package/dist/cron.js +4289 -2897
- package/dist/cron.js.map +1 -1
- package/dist/{errors-CvAeEWgE.d.ts → errors-ChqOmFH1.d.cts} +52 -6
- package/dist/{errors-CK8brCJ1.d.cts → errors-DV9e0rcp.d.ts} +52 -6
- package/dist/errors.cjs +218 -3
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +2 -3
- package/dist/errors.d.ts +50 -4
- package/dist/errors.js +217 -4
- package/dist/errors.js.map +1 -1
- package/dist/eval.cjs +4285 -2893
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.d.cts +35 -0
- package/dist/eval.js +4289 -2897
- package/dist/eval.js.map +1 -1
- package/dist/event-bus.d.ts +23 -0
- package/dist/index.cjs +5132 -4200
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +298 -278
- package/dist/index.d.ts +1898 -24
- package/dist/index.js +6441 -5509
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/loop-context-init.d.ts +2 -0
- package/dist/internal/agent-loop/tool-dispatch.d.ts +22 -1
- package/dist/internal/auth/api-key-validator.d.ts +46 -0
- package/dist/internal/llm/anthropic-shared.d.ts +8 -1
- package/dist/internal/llm/retry.d.ts +22 -0
- package/dist/internal/llm/types.d.ts +47 -1
- package/dist/internal/memory/active-memory-cache.d.ts +3 -3
- package/dist/internal/memory/active-memory-types.d.ts +8 -0
- package/dist/internal/memory/active-memory.d.ts +24 -20
- package/dist/internal/memory/adapters/azure-openai-embedding.d.ts +2 -0
- package/dist/internal/memory/adapters/cohere-embedding.d.ts +2 -0
- package/dist/internal/memory/adapters/gemini-embedding.d.ts +2 -0
- package/dist/internal/memory/adapters/jina-embedding.d.ts +2 -0
- package/dist/internal/memory/index-manager-contract.d.ts +26 -0
- package/dist/internal/memory/index-manager-dispatch.d.ts +1 -1
- package/dist/internal/memory/index-manager.d.ts +8 -26
- package/dist/internal/memory/{chunk-markdown.d.ts → storage/chunk-markdown.d.ts} +1 -1
- package/dist/internal/memory/{markdown-store.d.ts → storage/markdown-store.d.ts} +1 -1
- package/dist/internal/memory/{reader.d.ts → storage/reader.d.ts} +1 -1
- package/dist/internal/observability/context.d.cts +23 -0
- package/dist/internal/observability/context.d.ts +23 -0
- package/dist/internal/observability/index.cjs +38 -0
- package/dist/internal/observability/index.cjs.map +1 -0
- package/dist/internal/observability/index.d.cts +8 -0
- package/dist/internal/observability/index.d.ts +8 -0
- package/dist/internal/observability/index.js +33 -0
- package/dist/internal/observability/index.js.map +1 -0
- package/dist/internal/observability/tracer-loader.d.cts +20 -0
- package/dist/internal/persistence/conversation-storage-fs.d.cts +37 -0
- package/dist/internal/persistence/conversation-storage-memory.d.cts +24 -0
- package/dist/internal/persistence/credential-pool-store.d.cts +32 -0
- package/dist/internal/persistence/credential-pool-store.d.ts +32 -0
- package/dist/internal/persistence/cwd-mutex.d.cts +1 -0
- package/dist/internal/persistence/exclusive-create.d.cts +22 -0
- package/dist/internal/persistence/exclusive-create.d.ts +22 -0
- package/dist/internal/persistence/file-lock.d.cts +14 -0
- package/dist/internal/persistence/fts5-sanitize.d.cts +16 -0
- package/dist/internal/persistence/index.cjs +359 -0
- package/dist/internal/persistence/index.cjs.map +1 -0
- package/dist/internal/persistence/index.d.cts +20 -0
- package/dist/internal/persistence/index.d.ts +20 -0
- package/dist/internal/persistence/index.js +341 -0
- package/dist/internal/persistence/index.js.map +1 -0
- package/dist/internal/persistence/markdown-config-loader.d.cts +35 -0
- package/dist/internal/persistence/paths.d.cts +19 -0
- package/dist/internal/persistence/persistence-schema.d.cts +21 -0
- package/dist/internal/persistence/persistence-schema.d.ts +4 -0
- package/dist/internal/persistence/schema-version.d.cts +13 -0
- package/dist/internal/persistence/sqlite-cas.d.cts +25 -0
- package/dist/internal/persistence/sqlite-cas.d.ts +25 -0
- package/dist/internal/persistence/sqlite-wal.d.cts +10 -0
- package/dist/internal/plugins/context.d.cts +31 -0
- package/dist/internal/plugins/index.cjs +228 -0
- package/dist/internal/plugins/index.cjs.map +1 -0
- package/dist/internal/plugins/index.d.cts +8 -0
- package/dist/internal/plugins/index.d.ts +8 -0
- package/dist/internal/plugins/index.js +222 -0
- package/dist/internal/plugins/index.js.map +1 -0
- package/dist/internal/plugins/lifecycle.d.cts +14 -0
- package/dist/internal/plugins/lifecycle.d.ts +14 -0
- package/dist/internal/plugins/manager.d.cts +37 -0
- package/dist/internal/plugins/types.d.cts +102 -0
- package/dist/internal/providers/catalog-loader.d.ts +39 -0
- package/dist/internal/runtime/agent-session-store.d.ts +1 -1
- package/dist/internal/runtime/agent-session.d.ts +1 -0
- package/dist/internal/runtime/budget-tracker.d.ts +73 -0
- package/dist/internal/runtime/{context-manager.d.ts → context/context-manager.d.ts} +1 -1
- package/dist/internal/runtime/{fixture-events.d.ts → fixtures/fixture-events.d.ts} +1 -1
- package/dist/internal/runtime/{fixture-run-base.d.ts → fixtures/fixture-run-base.d.ts} +4 -4
- package/dist/internal/runtime/{fixture-scripts.d.ts → fixtures/fixture-scripts.d.ts} +1 -1
- package/dist/internal/runtime/local-agent-bootstrap.d.ts +2 -2
- package/dist/internal/runtime/local-agent-memory-provider.d.ts +57 -0
- package/dist/internal/runtime/memory-path-selector.d.ts +73 -0
- package/dist/internal/runtime/memory-provider.d.ts +165 -0
- package/dist/internal/runtime/{agent-registry.d.ts → registry/agent-registry-contract.d.ts} +15 -9
- package/dist/internal/runtime/registry/agent-registry.d.ts +7 -0
- package/dist/internal/runtime/{live-agent-registry.d.ts → registry/live-agent-registry.d.ts} +1 -1
- package/dist/internal/runtime/{run-registry.d.ts → registry/run-registry.d.ts} +1 -1
- package/dist/internal/runtime/session-types.d.ts +35 -0
- package/dist/internal/runtime/system-prompt/sources/skills-provider.d.ts +1 -0
- package/dist/internal/runtime/validate-response.d.ts +18 -0
- package/dist/internal/security/index.cjs +361 -0
- package/dist/internal/security/index.cjs.map +1 -0
- package/dist/internal/security/index.d.cts +11 -0
- package/dist/internal/security/index.js +350 -0
- package/dist/internal/security/index.js.map +1 -0
- package/dist/internal/security/path-guard.d.cts +59 -0
- package/dist/internal/security/path-guard.d.ts +3 -0
- package/dist/internal/security/redact.d.cts +21 -0
- package/dist/internal/security/secret-redactor.d.cts +1 -0
- package/dist/internal/security/secret-redactor.d.ts +1 -0
- package/dist/internal/security/test-reset.d.cts +10 -0
- package/dist/internal/security/test-reset.d.ts +10 -0
- package/dist/internal/telemetry/adapters/arize.d.ts +2 -0
- package/dist/internal/telemetry/adapters/braintrust.d.ts +2 -0
- package/dist/internal/telemetry/adapters/datadog.d.ts +2 -0
- package/dist/internal/telemetry/adapters/langsmith.d.ts +2 -0
- package/dist/internal/telemetry/span-names.d.ts +6 -0
- package/dist/internal/telemetry/tracer.d.ts +1 -0
- package/dist/internal/workflow/evented-executor.d.ts +42 -0
- package/dist/internal/workflow/scheduler.d.ts +23 -0
- package/dist/internal/zod/to-json-schema.d.ts +5 -15
- package/dist/job-queue.d.ts +28 -0
- package/dist/path-safety.cjs +67 -6
- package/dist/path-safety.cjs.map +1 -1
- package/dist/path-safety.d.cts +15 -0
- package/dist/path-safety.d.ts +1 -1
- package/dist/path-safety.js +67 -7
- package/dist/path-safety.js.map +1 -1
- package/dist/permission-engine.d.ts +21 -0
- package/dist/provider-catalog.json +702 -0
- package/dist/rag/index.cjs +136 -0
- package/dist/rag/index.cjs.map +1 -0
- package/dist/rag/index.d.cts +11 -0
- package/dist/rag/index.d.ts +11 -0
- package/dist/rag/index.js +129 -0
- package/dist/rag/index.js.map +1 -0
- package/dist/rag/reranker.d.cts +26 -0
- package/dist/rag/reranker.d.ts +26 -0
- package/dist/rag/retriever.d.cts +25 -0
- package/dist/rag/retriever.d.ts +25 -0
- package/dist/rag/text-splitter.d.cts +12 -0
- package/dist/rag/text-splitter.d.ts +12 -0
- package/dist/rag/types.d.cts +37 -0
- package/dist/rag/types.d.ts +37 -0
- package/dist/run-DrwUpFxZ.d.cts +823 -0
- package/dist/run-DrwUpFxZ.d.ts +823 -0
- package/dist/sandbox/index.cjs +133 -0
- package/dist/sandbox/index.cjs.map +1 -0
- package/dist/sandbox/index.d.cts +2 -0
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +128 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/local-sandbox.d.cts +17 -0
- package/dist/sandbox/local-sandbox.d.ts +17 -0
- package/dist/sandbox/types.d.cts +44 -0
- package/dist/sandbox/types.d.ts +44 -0
- package/dist/server/adapter/express.d.cts +9 -0
- package/dist/server/adapter/express.d.ts +9 -0
- package/dist/server/adapter/fastify.d.cts +9 -0
- package/dist/server/adapter/fastify.d.ts +9 -0
- package/dist/server/adapter/hono.d.cts +9 -0
- package/dist/server/adapter/hono.d.ts +9 -0
- package/dist/server/adapter/index.d.cts +8 -0
- package/dist/server/adapter/index.d.ts +8 -0
- package/dist/server/adapter/shared-handler.d.cts +9 -0
- package/dist/server/adapter/shared-handler.d.ts +9 -0
- package/dist/server/adapter/types.d.cts +33 -0
- package/dist/server/adapter/types.d.ts +33 -0
- package/dist/server/auth/errors.d.cts +53 -0
- package/dist/server/auth/errors.d.ts +53 -0
- package/dist/server/auth/index.cjs +48 -42
- package/dist/server/auth/index.cjs.map +1 -1
- package/dist/server/auth/index.d.cts +11 -172
- package/dist/server/auth/index.d.ts +11 -172
- package/dist/server/auth/index.js +49 -43
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/auth/oauth-transaction-store.d.cts +39 -0
- package/dist/server/auth/oauth-transaction-store.d.ts +39 -0
- package/dist/server/auth/orchestrator.d.cts +8 -0
- package/dist/server/auth/orchestrator.d.ts +8 -0
- package/dist/server/auth/types.d.cts +91 -0
- package/dist/server/auth/types.d.ts +91 -0
- package/dist/server/auth/validate-return-to.d.cts +17 -0
- package/dist/server/auth/validate-return-to.d.ts +17 -0
- package/dist/server/errors-envelope.cjs +409 -0
- package/dist/server/errors-envelope.cjs.map +1 -0
- package/dist/server/errors-envelope.d.cts +61 -0
- package/dist/server/errors-envelope.d.ts +61 -0
- package/dist/server/errors-envelope.js +405 -0
- package/dist/server/errors-envelope.js.map +1 -0
- package/dist/subscription/define-subscription.d.cts +63 -0
- package/dist/subscription/define-subscription.d.ts +63 -0
- package/dist/subscription/index.cjs +402 -0
- package/dist/subscription/index.cjs.map +1 -0
- package/dist/subscription/index.d.cts +18 -0
- package/dist/subscription/index.d.ts +18 -0
- package/dist/subscription/index.js +394 -0
- package/dist/subscription/index.js.map +1 -0
- package/dist/subscription/internal/adapter-types.d.cts +11 -0
- package/dist/subscription/internal/adapter-types.d.ts +11 -0
- package/dist/subscription/internal/backpressure.d.cts +24 -0
- package/dist/subscription/internal/backpressure.d.ts +24 -0
- package/dist/subscription/internal/server-integration.d.cts +17 -0
- package/dist/subscription/internal/server-integration.d.ts +17 -0
- package/dist/subscription/internal/sse-encoder.d.cts +13 -0
- package/dist/subscription/internal/sse-encoder.d.ts +13 -0
- package/dist/subscription/internal/sse-parser.d.cts +15 -0
- package/dist/subscription/internal/sse-parser.d.ts +15 -0
- package/dist/subscription/internal/subscription-runtime.d.cts +9 -0
- package/dist/subscription/internal/subscription-runtime.d.ts +9 -0
- package/dist/subscription/internal/ws-adapter-node.d.cts +10 -0
- package/dist/subscription/internal/ws-adapter-node.d.ts +10 -0
- package/dist/subscription/theokit-subscribe.d.cts +41 -0
- package/dist/subscription/theokit-subscribe.d.ts +41 -0
- package/dist/subscription/types.d.cts +140 -0
- package/dist/subscription/types.d.ts +140 -0
- package/dist/task-store.cjs +30 -2
- package/dist/task-store.cjs.map +1 -1
- package/dist/task-store.d.cts +8 -0
- package/dist/task-store.js +31 -3
- package/dist/task-store.js.map +1 -1
- package/dist/types/agent-prims.d.ts +61 -0
- package/dist/types/agent.d.ts +48 -53
- package/dist/types/conversation.d.ts +20 -8
- package/dist/types/index.d.ts +0 -2
- package/dist/types/messages-base.d.ts +20 -0
- package/dist/types/messages.d.ts +1 -1
- package/dist/types/run.d.ts +1 -1
- package/dist/types/updates.d.ts +1 -1
- package/dist/voice/index.d.ts +7 -0
- package/dist/voice/openai-realtime.d.ts +21 -0
- package/dist/voice/types.d.ts +35 -0
- package/dist/workflow.cjs +179 -88
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +97 -0
- package/dist/workflow.js +180 -89
- package/dist/workflow.js.map +1 -1
- package/package.json +126 -25
- package/dist/budget.d.ts +0 -48
- package/dist/cache.d.ts +0 -74
- package/dist/cron-1yxL3K2S.d.cts +0 -221
- package/dist/cron-BYVdYzob.d.ts +0 -221
- package/dist/handoff.d.ts +0 -55
- package/dist/internal/budget/calendar-window.d.ts +0 -19
- package/dist/internal/budget/enforcement.d.ts +0 -32
- package/dist/internal/budget/ledger.d.ts +0 -25
- package/dist/internal/budget/normalize-usage.d.ts +0 -27
- package/dist/internal/budget/registry.d.ts +0 -16
- package/dist/internal/cache/cosine.d.ts +0 -14
- package/dist/internal/cache/embed-helper.d.ts +0 -15
- package/dist/internal/cache/key.d.ts +0 -15
- package/dist/internal/cache/lookup.d.ts +0 -28
- package/dist/internal/cache/store-handler.d.ts +0 -24
- package/dist/internal/cache/store-json.d.ts +0 -48
- package/dist/internal/cache/store.d.ts +0 -54
- package/dist/internal/cache/telemetry.d.ts +0 -20
- package/dist/internal/cache/ttl.d.ts +0 -11
- package/dist/internal/catalog/fixtures.d.ts +0 -16
- package/dist/internal/catalog/local-models.d.ts +0 -24
- package/dist/internal/handoff/dispatcher.d.ts +0 -29
- package/dist/internal/handoff/registry.d.ts +0 -23
- package/dist/internal/handoff/telemetry.d.ts +0 -18
- package/dist/internal/handoff/tool-injector.d.ts +0 -34
- package/dist/internal/memory/atomic-write.d.ts +0 -7
- package/dist/internal/memory/dreaming/diary.d.ts +0 -4
- package/dist/internal/memory/dreaming/phases.d.ts +0 -15
- package/dist/internal/memory/dreaming/run.d.ts +0 -10
- package/dist/internal/memory/migrate-sqlite-to-lance.d.ts +0 -15
- package/dist/memory-adapter-helpers.d.ts +0 -28
- package/dist/memory.d.ts +0 -123
- package/dist/migrate.d.ts +0 -33
- package/dist/security.d.ts +0 -67
- package/dist/task.d.ts +0 -87
- package/dist/theokit.d.ts +0 -84
- package/dist/tools/_path-scope.d.ts +0 -8
- package/dist/tools/_subprocess.d.ts +0 -28
- package/dist/tools/git-diff.d.ts +0 -22
- package/dist/tools/index.d.ts +0 -29
- package/dist/tools/list-dir.d.ts +0 -26
- package/dist/tools/read-file.d.ts +0 -31
- package/dist/tools/run-vitest.d.ts +0 -46
- package/dist/tools/search-text.d.ts +0 -32
- package/dist/tools.cjs +0 -690
- package/dist/tools.cjs.map +0 -1
- package/dist/tools.js +0 -683
- package/dist/tools.js.map +0 -1
- package/dist/trajectory-helpers.d.ts +0 -31
- package/dist/types/cache.d.ts +0 -76
- package/dist/types/handoff.d.ts +0 -135
- /package/dist/{internal/cron/run-job.d.ts → agent-helpers.d.ts} +0 -0
- /package/dist/internal/{cron/scheduler.d.ts → agent-loop/loop-llm-stream.d.ts} +0 -0
- /package/dist/internal/{cron/store.d.ts → agent-loop/tool-executors.d.ts} +0 -0
- /package/dist/internal/{cron/validate.d.ts → memory/index-manager-helpers.d.ts} +0 -0
- /package/dist/internal/memory/{session-loader.d.ts → storage/session-loader.d.ts} +0 -0
- /package/dist/internal/memory/{session-summary-writer.d.ts → storage/session-summary-writer.d.ts} +0 -0
- /package/dist/internal/memory/{transcript-store.d.ts → storage/transcript-store.d.ts} +0 -0
- /package/dist/internal/memory/{wiki-loader.d.ts → storage/wiki-loader.d.ts} +0 -0
- /package/dist/internal/{memory/cwd-mutex.d.ts → persistence/atomic-write.d.cts} +0 -0
- /package/dist/internal/runtime/{context-aggregator.d.ts → context/context-aggregator.d.ts} +0 -0
- /package/dist/internal/runtime/{context-discovery-runner.d.ts → context/context-discovery-runner.d.ts} +0 -0
- /package/dist/internal/runtime/{context-discovery.d.ts → context/context-discovery.d.ts} +0 -0
- /package/dist/internal/runtime/{context-frontmatter.d.ts → context/context-frontmatter.d.ts} +0 -0
- /package/dist/internal/runtime/{context-import-resolver.d.ts → context/context-import-resolver.d.ts} +0 -0
- /package/dist/internal/runtime/{context-loaders.d.ts → context/context-loaders.d.ts} +0 -0
- /package/dist/internal/runtime/{context-mdc-parser.d.ts → context/context-mdc-parser.d.ts} +0 -0
- /package/dist/internal/runtime/{fixture-responder.d.ts → fixtures/fixture-responder.d.ts} +0 -0
- /package/dist/internal/runtime/{fixture-types.d.ts → fixtures/fixture-types.d.ts} +0 -0
- /package/dist/internal/runtime/{plugins-manager.d.ts → local-agent-send.d.ts} +0 -0
- /package/dist/internal/runtime/{plugin-frontmatter.d.ts → plugins/plugin-frontmatter.d.ts} +0 -0
- /package/dist/internal/runtime/{system-prompt/providers/active-memory-provider.d.ts → plugins/plugins-manager.d.ts} +0 -0
- /package/dist/internal/runtime/{agent-factory-registry.d.ts → registry/agent-factory-registry.d.ts} +0 -0
- /package/dist/internal/runtime/{agent-registry-store.d.ts → registry/agent-registry-store.d.ts} +0 -0
- /package/dist/internal/runtime/system-prompt/{providers/base-provider.d.ts → sources/active-memory-provider.d.ts} +0 -0
- /package/dist/internal/runtime/system-prompt/{providers/context-provider.d.ts → sources/base-provider.d.ts} +0 -0
- /package/dist/internal/runtime/system-prompt/{providers/memory-provider.d.ts → sources/context-provider.d.ts} +0 -0
- /package/dist/internal/runtime/system-prompt/{providers/skills-provider.d.ts → sources/memory-provider.d.ts} +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { open, rename, unlink, mkdir, readFile, statfs } from 'fs/promises';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
// src/internal/persistence/atomic-write.ts
|
|
8
|
+
var NETWORK_FS_MAGIC = /* @__PURE__ */ new Map([
|
|
9
|
+
[26985, "nfs"],
|
|
10
|
+
[20859, "smb"],
|
|
11
|
+
[4283649346, "cifs"],
|
|
12
|
+
[1702057286, "fuse"]
|
|
13
|
+
]);
|
|
14
|
+
function detectNetworkFsName(typeMagic) {
|
|
15
|
+
return NETWORK_FS_MAGIC.get(typeMagic) ?? null;
|
|
16
|
+
}
|
|
17
|
+
var warnedNfsDirs = /* @__PURE__ */ new Set();
|
|
18
|
+
async function warnOnNetworkFsOnce(dirPath, label) {
|
|
19
|
+
const key = `${dirPath}\0${label}`;
|
|
20
|
+
if (warnedNfsDirs.has(key)) return;
|
|
21
|
+
warnedNfsDirs.add(key);
|
|
22
|
+
try {
|
|
23
|
+
const info = await statfs(dirPath);
|
|
24
|
+
const fsName = detectNetworkFsName(info.type);
|
|
25
|
+
if (fsName === null) return;
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
`[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} \u2014 rename() atomicity guarantees may be weaker than expected.
|
|
28
|
+
`
|
|
29
|
+
);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function replaceFileAtomic(filePath, content) {
|
|
34
|
+
await warnOnNetworkFsOnce(dirname(filePath), "atomic-write");
|
|
35
|
+
const suffix = randomBytes(8).toString("hex");
|
|
36
|
+
const tmp = `${filePath}.${process.pid}.${suffix}.tmp`;
|
|
37
|
+
const handle = await open(tmp, "w", 384);
|
|
38
|
+
try {
|
|
39
|
+
await handle.writeFile(content, "utf8");
|
|
40
|
+
await handle.sync();
|
|
41
|
+
} finally {
|
|
42
|
+
await handle.close();
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await rename(tmp, filePath);
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
await unlink(tmp).catch(() => void 0);
|
|
48
|
+
throw cause;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function atomicWriteJson(filePath, data, options) {
|
|
52
|
+
const indent = options?.indent ?? 2;
|
|
53
|
+
const trailingNewline = options?.trailingNewline ?? true;
|
|
54
|
+
const json = JSON.stringify(data, null, indent);
|
|
55
|
+
if (json === void 0) {
|
|
56
|
+
throw new TypeError("atomicWriteJson: cannot serialize undefined");
|
|
57
|
+
}
|
|
58
|
+
const content = trailingNewline ? `${json}
|
|
59
|
+
` : json;
|
|
60
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
61
|
+
await replaceFileAtomic(filePath, content);
|
|
62
|
+
}
|
|
63
|
+
async function atomicWriteText(filePath, content) {
|
|
64
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
65
|
+
await replaceFileAtomic(filePath, content);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/internal/persistence/cwd-mutex.ts
|
|
69
|
+
var tails = /* @__PURE__ */ new Map();
|
|
70
|
+
function withCwdMutex(key, fn) {
|
|
71
|
+
const prev = tails.get(key) ?? Promise.resolve();
|
|
72
|
+
const next = prev.then(fn, fn);
|
|
73
|
+
tails.set(
|
|
74
|
+
key,
|
|
75
|
+
next.then(
|
|
76
|
+
() => void 0,
|
|
77
|
+
() => void 0
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
async function createExclusive(path, data, options) {
|
|
83
|
+
const mode = options?.mode ?? 384;
|
|
84
|
+
try {
|
|
85
|
+
const handle = await open(path, "wx", mode);
|
|
86
|
+
try {
|
|
87
|
+
await handle.writeFile(data);
|
|
88
|
+
return true;
|
|
89
|
+
} finally {
|
|
90
|
+
await handle.close();
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err.code === "EEXIST") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/internal/persistence/file-lock.ts
|
|
101
|
+
var cached;
|
|
102
|
+
var warnedMissing = false;
|
|
103
|
+
var warnedStructural = false;
|
|
104
|
+
async function getProperLockfile() {
|
|
105
|
+
if (cached !== void 0) return cached;
|
|
106
|
+
try {
|
|
107
|
+
const mod = await import('proper-lockfile');
|
|
108
|
+
if (!validateLockModule(mod)) {
|
|
109
|
+
if (!warnedStructural) {
|
|
110
|
+
warnedStructural = true;
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
"[theokit-sdk] proper-lockfile: imported module does NOT expose the expected `lock`/`unlock` API surface. This may indicate a supply-chain compromise or an incompatible major version. Falling back to in-process mutex (no cross-process safety). Reinstall with: pnpm add proper-lockfile@^11\n"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
cached = null;
|
|
116
|
+
return cached;
|
|
117
|
+
}
|
|
118
|
+
cached = mod;
|
|
119
|
+
} catch {
|
|
120
|
+
cached = null;
|
|
121
|
+
}
|
|
122
|
+
return cached;
|
|
123
|
+
}
|
|
124
|
+
function validateLockModule(mod) {
|
|
125
|
+
if (mod === null || mod === void 0 || typeof mod !== "object") return false;
|
|
126
|
+
const m = mod;
|
|
127
|
+
return typeof m.lock === "function" && typeof m.unlock === "function";
|
|
128
|
+
}
|
|
129
|
+
async function withFileLock(path, fn, options) {
|
|
130
|
+
const lib = await getProperLockfile();
|
|
131
|
+
if (lib === null) {
|
|
132
|
+
if (!warnedMissing) {
|
|
133
|
+
warnedMissing = true;
|
|
134
|
+
process.stderr.write(
|
|
135
|
+
"[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return withCwdMutex(`file-lock:${path}`, fn);
|
|
139
|
+
}
|
|
140
|
+
return withCwdMutex(`file-lock:${path}`, async () => {
|
|
141
|
+
const release = await lib.lock(path, {
|
|
142
|
+
// EC-1: companion lockfile, target path may not exist yet.
|
|
143
|
+
lockfilePath: `${path}.lock`,
|
|
144
|
+
realpath: false,
|
|
145
|
+
stale: options?.stale ?? 3e4,
|
|
146
|
+
retries: {
|
|
147
|
+
retries: options?.retries ?? 5,
|
|
148
|
+
factor: options?.retryFactor ?? 1.5,
|
|
149
|
+
minTimeout: 100,
|
|
150
|
+
maxTimeout: 5e3
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
return await fn();
|
|
155
|
+
} finally {
|
|
156
|
+
await release();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/internal/persistence/fts5-sanitize.ts
|
|
162
|
+
var PHRASE_OPEN = "";
|
|
163
|
+
var PHRASE_CLOSE = "";
|
|
164
|
+
function sanitizeFts5Query(query) {
|
|
165
|
+
if (query.length === 0) return query;
|
|
166
|
+
const phrases = [];
|
|
167
|
+
let text = query.replace(/"[^"]+"/g, (match) => {
|
|
168
|
+
phrases.push(match);
|
|
169
|
+
return `${PHRASE_OPEN}${phrases.length - 1}${PHRASE_CLOSE}`;
|
|
170
|
+
});
|
|
171
|
+
text = text.replace(/[[\]{}()"^]/g, " ");
|
|
172
|
+
text = text.replace(/\*+/g, "*");
|
|
173
|
+
text = text.replace(/^\s*(AND|OR|NOT)\s+/i, "");
|
|
174
|
+
text = text.replace(/\s+(AND|OR|NOT)\s*$/i, "");
|
|
175
|
+
text = text.replace(/\b\w+[-._]\w[\w\-._]*\b/g, (match) => `"${match}"`);
|
|
176
|
+
for (let i = 0; i < phrases.length; i += 1) {
|
|
177
|
+
text = text.replace(`${PHRASE_OPEN}${i}${PHRASE_CLOSE}`, phrases[i] ?? "");
|
|
178
|
+
}
|
|
179
|
+
return text.trim();
|
|
180
|
+
}
|
|
181
|
+
var CJK_RANGES = [
|
|
182
|
+
[12288, 12351],
|
|
183
|
+
// CJK Symbols and Punctuation
|
|
184
|
+
[12352, 12447],
|
|
185
|
+
// Hiragana
|
|
186
|
+
[12448, 12543],
|
|
187
|
+
// Katakana
|
|
188
|
+
[13312, 19903],
|
|
189
|
+
// CJK Unified Ideographs Extension A
|
|
190
|
+
[19968, 40959],
|
|
191
|
+
// CJK Unified Ideographs
|
|
192
|
+
[44032, 55215]
|
|
193
|
+
// Hangul Syllables
|
|
194
|
+
];
|
|
195
|
+
function containsCjk(text) {
|
|
196
|
+
for (const char of text) {
|
|
197
|
+
const cp = char.codePointAt(0);
|
|
198
|
+
if (cp === void 0) continue;
|
|
199
|
+
for (const [lo, hi] of CJK_RANGES) {
|
|
200
|
+
if (cp >= lo && cp <= hi) return true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
var THEOKIT_DIR_NAME = ".theokit";
|
|
206
|
+
function getTheokitHome(cwd) {
|
|
207
|
+
const override = process.env.THEOKIT_HOME?.trim();
|
|
208
|
+
if (override !== void 0 && override.length > 0) {
|
|
209
|
+
return override;
|
|
210
|
+
}
|
|
211
|
+
return join(cwd, THEOKIT_DIR_NAME);
|
|
212
|
+
}
|
|
213
|
+
function getProfilesRoot() {
|
|
214
|
+
return join(homedir(), THEOKIT_DIR_NAME, "profiles");
|
|
215
|
+
}
|
|
216
|
+
function displayTheokitHome(cwd) {
|
|
217
|
+
const resolved = getTheokitHome(cwd);
|
|
218
|
+
const home = homedir();
|
|
219
|
+
if (resolved === home) return "~";
|
|
220
|
+
if (resolved.startsWith(`${home}/`)) {
|
|
221
|
+
return `~${resolved.slice(home.length)}`;
|
|
222
|
+
}
|
|
223
|
+
return resolved;
|
|
224
|
+
}
|
|
225
|
+
var PersistenceSchema = z.object({
|
|
226
|
+
backend: z.enum(["memory", "json"]),
|
|
227
|
+
dir: z.string().optional()
|
|
228
|
+
}).refine((p) => p.backend !== "json" || typeof p.dir === "string" && p.dir.length > 0, {
|
|
229
|
+
message: 'persistence.dir is required when backend = "json"'
|
|
230
|
+
}).optional();
|
|
231
|
+
function migrateSchema(opts) {
|
|
232
|
+
const { db, currentVersion, migrations, label = "db" } = opts;
|
|
233
|
+
const storedRaw = db.pragma("user_version", { simple: true });
|
|
234
|
+
const stored = typeof storedRaw === "number" ? storedRaw : 0;
|
|
235
|
+
if (stored > currentVersion) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`[${label}] schema version ${stored} > current ${currentVersion}; did you downgrade the SDK? Forward-only migrations only.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (stored === currentVersion) {
|
|
241
|
+
return { from: stored, to: stored, ran: 0 };
|
|
242
|
+
}
|
|
243
|
+
const pending = [...migrations].sort((a, b) => a.toVersion - b.toVersion).filter((m) => m.toVersion > stored && m.toVersion <= currentVersion);
|
|
244
|
+
let ran = 0;
|
|
245
|
+
let lastApplied = stored;
|
|
246
|
+
db.transaction(() => {
|
|
247
|
+
for (const m of pending) {
|
|
248
|
+
m.up(db);
|
|
249
|
+
db.pragma(`user_version = ${m.toVersion}`);
|
|
250
|
+
lastApplied = m.toVersion;
|
|
251
|
+
ran += 1;
|
|
252
|
+
}
|
|
253
|
+
})();
|
|
254
|
+
return { from: stored, to: lastApplied, ran };
|
|
255
|
+
}
|
|
256
|
+
async function readVersionedJson(opts) {
|
|
257
|
+
const { path, currentVersion, migrate, defaultValue } = opts;
|
|
258
|
+
let raw;
|
|
259
|
+
try {
|
|
260
|
+
raw = await readFile(path, "utf-8");
|
|
261
|
+
} catch {
|
|
262
|
+
return defaultValue();
|
|
263
|
+
}
|
|
264
|
+
let parsed;
|
|
265
|
+
try {
|
|
266
|
+
parsed = JSON.parse(raw);
|
|
267
|
+
} catch {
|
|
268
|
+
const asidePath = `${path}.corrupt.${Date.now()}`;
|
|
269
|
+
try {
|
|
270
|
+
await rename(path, asidePath);
|
|
271
|
+
process.stderr.write(
|
|
272
|
+
`[theokit-sdk] ${path} is corrupt; moved to ${asidePath}. Using default value.
|
|
273
|
+
`
|
|
274
|
+
);
|
|
275
|
+
} catch {
|
|
276
|
+
process.stderr.write(`[theokit-sdk] ${path} is corrupt; using default value.
|
|
277
|
+
`);
|
|
278
|
+
}
|
|
279
|
+
return defaultValue();
|
|
280
|
+
}
|
|
281
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
282
|
+
return defaultValue();
|
|
283
|
+
}
|
|
284
|
+
const file = parsed;
|
|
285
|
+
const storedRaw = file._schemaVersion;
|
|
286
|
+
const stored = typeof storedRaw === "number" ? storedRaw : 0;
|
|
287
|
+
if (stored === currentVersion) {
|
|
288
|
+
return file.data;
|
|
289
|
+
}
|
|
290
|
+
if (stored > currentVersion) {
|
|
291
|
+
process.stderr.write(
|
|
292
|
+
`[theokit-sdk] ${path} schema version ${stored} > current ${currentVersion}; using default value (forward-only).
|
|
293
|
+
`
|
|
294
|
+
);
|
|
295
|
+
return defaultValue();
|
|
296
|
+
}
|
|
297
|
+
return migrate(parsed, stored);
|
|
298
|
+
}
|
|
299
|
+
async function writeVersionedJson(path, data, currentVersion) {
|
|
300
|
+
const file = {
|
|
301
|
+
_schemaVersion: currentVersion,
|
|
302
|
+
data
|
|
303
|
+
};
|
|
304
|
+
await atomicWriteJson(path, file);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/internal/persistence/sqlite-cas.ts
|
|
308
|
+
function casUpdate(db, sql, params, expectedChanges = 1) {
|
|
309
|
+
const stmt = db.prepare(sql);
|
|
310
|
+
const result = stmt.run(...params);
|
|
311
|
+
return result.changes === expectedChanges;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/internal/persistence/sqlite-wal.ts
|
|
315
|
+
var warnedLabels = /* @__PURE__ */ new Set();
|
|
316
|
+
function applyWalWithFallback(db, label) {
|
|
317
|
+
try {
|
|
318
|
+
const result = db.pragma("journal_mode = WAL", { simple: true });
|
|
319
|
+
if (typeof result === "string" && result.toLowerCase() === "wal") {
|
|
320
|
+
return { mode: "wal", fellBack: false };
|
|
321
|
+
}
|
|
322
|
+
logFallback(label, `got "${String(result)}" instead of "wal"`);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
325
|
+
logFallback(label, msg);
|
|
326
|
+
}
|
|
327
|
+
db.pragma("journal_mode = DELETE");
|
|
328
|
+
return { mode: "delete", fellBack: true };
|
|
329
|
+
}
|
|
330
|
+
function logFallback(label, reason) {
|
|
331
|
+
if (warnedLabels.has(label)) return;
|
|
332
|
+
warnedLabels.add(label);
|
|
333
|
+
process.stderr.write(
|
|
334
|
+
`[theokit-sdk] ${label}: WAL unavailable (${reason}); using DELETE journal mode. This is normal on NFS/SMB/FUSE; expect slightly slower concurrent access.
|
|
335
|
+
`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export { PersistenceSchema, applyWalWithFallback, atomicWriteJson, atomicWriteText, casUpdate, containsCjk, createExclusive, displayTheokitHome, getProfilesRoot, getTheokitHome, migrateSchema, readVersionedJson, replaceFileAtomic, sanitizeFts5Query, withCwdMutex, withFileLock, writeVersionedJson };
|
|
340
|
+
//# sourceMappingURL=index.js.map
|
|
341
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/internal/persistence/atomic-write.ts","../../../src/internal/persistence/cwd-mutex.ts","../../../src/internal/persistence/exclusive-create.ts","../../../src/internal/persistence/file-lock.ts","../../../src/internal/persistence/fts5-sanitize.ts","../../../src/internal/persistence/paths.ts","../../../src/internal/persistence/persistence-schema.ts","../../../src/internal/persistence/schema-version.ts","../../../src/internal/persistence/sqlite-cas.ts","../../../src/internal/persistence/sqlite-wal.ts"],"names":["open","rename"],"mappings":"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","file":"index.js","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, open, rename, statfs, unlink } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\n// T5.8 — Linux filesystem magic numbers (from `<linux/magic.h>`).\n// Used by `detectNetworkFsName` to identify the parent directory's\n// filesystem type from a `statfs()` return value. The four entries\n// below cover the network/FUSE cases where `rename()` is best-effort\n// rather than strictly atomic; everything else is treated as local.\nconst NETWORK_FS_MAGIC: ReadonlyMap<number, string> = new Map([\n [0x6969, \"nfs\"],\n [0x517b, \"smb\"],\n [0xff534d42, \"cifs\"],\n [0x65735546, \"fuse\"],\n]);\n\n/**\n * T5.8 — Map a `statfs().type` magic number to a network-FS label, or\n * `null` for local filesystems. Pure function — exported via the\n * `__TESTING__` seam so unit tests can drive the parse logic without\n * needing a network mount.\n *\n * @internal\n */\nfunction detectNetworkFsName(typeMagic: number): string | null {\n return NETWORK_FS_MAGIC.get(typeMagic) ?? null;\n}\n\nconst warnedNfsDirs = new Set<string>();\n\n/**\n * T5.8 — Best-effort one-shot stderr warning when `dirPath` lives on a\n * network/FUSE filesystem. Silent no-op on local filesystems, on\n * statfs failure (Windows / Node < 18.15 / EACCES), or after the\n * first warning per (dir + label) pair. Mirrors the `sqlite-wal.ts`\n * warn-once-per-label pattern (D63).\n *\n * @internal\n */\nasync function warnOnNetworkFsOnce(dirPath: string, label: string): Promise<void> {\n const key = `${dirPath}\\0${label}`;\n if (warnedNfsDirs.has(key)) return;\n warnedNfsDirs.add(key);\n try {\n const info = await statfs(dirPath);\n const fsName = detectNetworkFsName(info.type);\n if (fsName === null) return;\n process.stderr.write(\n `[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} — ` +\n \"rename() atomicity guarantees may be weaker than expected.\\n\",\n );\n } catch {\n // statfs unavailable (Windows / Node < 18.15) or unreadable —\n // silent fallback. The warning is purely informational.\n }\n}\n\n/**\n * T5.8 — Test seam exposing the pure detection function so unit tests\n * can assert magic-number coverage without spinning up a network FS.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__detectNetworkFsName(typeMagic: number): string | null {\n return detectNetworkFsName(typeMagic);\n}\n\n/**\n * T5.8 — Test seam: clear the per-directory warn-once registry between\n * tests so warning-emission tests stay deterministic.\n *\n * @internal\n */\nexport function __TESTING__resetNfsWarnings(): void {\n warnedNfsDirs.clear();\n}\n\n/**\n * Atomic file replacement: write content to a per-call unique tmp path,\n * fsync, then rename over the target. Crash mid-write leaves either the old\n * file intact or the new file complete — never a half-written file.\n *\n * The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent\n * burst writes within one process) never collide on the same tmp path — a\n * race that would manifest as `ENOENT` on `rename` after the rival process\n * already moved its tmp into place.\n *\n * Mirrors OpenClaw's `replaceFileAtomic` from\n * `referencia/openclaw/packages/memory-host-sdk/src/host/fs-utils.ts` with\n * the multi-writer robustness fix.\n *\n * @internal\n */\nexport async function replaceFileAtomic(filePath: string, content: string): Promise<void> {\n // T5.8 — warn once per parent directory if it lives on a network /\n // FUSE filesystem where `rename()` atomicity is best-effort. The\n // write proceeds unchanged; the warning is purely informational so\n // operators can spot the case in stderr / log aggregators.\n await warnOnNetworkFsOnce(dirname(filePath), \"atomic-write\");\n // T5.7 — crypto-random tmp suffix (CSPRNG, 64 bits of entropy)\n // replaces the predictable `Math.random().toString(36)` source. An\n // attacker observing the process can no longer predict the next\n // tmp path and pre-stage a hostile file to be renamed into place.\n const suffix = randomBytes(8).toString(\"hex\");\n const tmp = `${filePath}.${process.pid}.${suffix}.tmp`;\n // T5.7 — mode 0o600 on the tmp file (owner read+write only). The\n // tmp file holds the FULL in-flight content (credential snapshots,\n // OAuth tokens) before the rename. World-readable default would\n // expose secrets during the ms-window between open and rename\n // (TOCTOU). On modern Linux the post-rename target inherits the\n // tmp's permission bits, so the final file is also 0o600.\n const handle = await open(tmp, \"w\", 0o600);\n try {\n await handle.writeFile(content, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n try {\n await rename(tmp, filePath);\n } catch (cause) {\n // Cleanup tmp on rename failure so we don't leak stale .tmp files.\n await unlink(tmp).catch(() => undefined);\n throw cause;\n }\n}\n\n/**\n * Options for `atomicWriteJson`.\n *\n * @internal\n */\nexport interface AtomicWriteJsonOptions {\n /** Indent passed to `JSON.stringify`. Default: 2. */\n indent?: number;\n /** Whether to append a trailing newline (POSIX convention). Default: true. */\n trailingNewline?: boolean;\n}\n\n/**\n * Typed JSON atomic write helper.\n *\n * Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The\n * parent directory is auto-created (recursive `mkdir`) to make this helper\n * safe for callers who haven't ensured the directory exists (EC-4 in the\n * persistence-state-hardening plan).\n *\n * Throws `TypeError` on circular refs or `undefined` data (propagates from\n * `JSON.stringify`).\n *\n * @internal\n */\nexport async function atomicWriteJson<T>(\n filePath: string,\n data: T,\n options?: AtomicWriteJsonOptions,\n): Promise<void> {\n const indent = options?.indent ?? 2;\n const trailingNewline = options?.trailingNewline ?? true;\n const json = JSON.stringify(data, null, indent);\n if (json === undefined) {\n throw new TypeError(\"atomicWriteJson: cannot serialize undefined\");\n }\n const content = trailingNewline ? `${json}\\n` : json;\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content);\n}\n\n/**\n * Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +\n * auto-mkdir of the parent directory. Used by `theokit-migrate-config`\n * (T4.1, EC-2 MUST FIX) so a crash mid-migration leaves previous MD files\n * intact rather than corrupting them.\n *\n * @internal\n */\nexport async function atomicWriteText(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content);\n}\n","/**\n * Per-key serialization. Returns a promise that resolves after the previous\n * `withCwdMutex(key, fn)` call for the same key has completed. Prevents\n * read-modify-write races on `MEMORY.md` within a single process.\n *\n * Multi-process safety is NOT covered (would need OS file locks — see\n * `withFileLock` in `./file-lock.ts`).\n *\n * **Public utility (SDK 2.0 Phase 2 physical-survey unblock — see ADR-008).**\n * Extracted packages (`@theokit/sdk-budget`, `@theokit/sdk-memory`) consume\n * this via `import { withCwdMutex } from \"@theokit/sdk\"` to ensure the\n * cross-package mutex Map IS the same process-level registry (single source\n * of truth) — duplicating the impl per package would defeat the purpose\n * (each package would have its own Map; concurrent writes from different\n * packages would race).\n *\n * Stability guarantee: signature + semantics will not change before sdk-core\n * v3.0. The mutex Map is module-scoped — restart-on-import resets state.\n *\n * @public\n */\nconst tails = new Map<string, Promise<unknown>>();\n\nexport function withCwdMutex<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prev = tails.get(key) ?? Promise.resolve();\n const next = prev.then(fn, fn); // run fn whether prev fulfilled or rejected\n // Save the new tail. Store the .then() chain that swallows the result so a\n // failure here doesn't poison subsequent waiters.\n tails.set(\n key,\n next.then(\n () => undefined,\n () => undefined,\n ),\n );\n return next;\n}\n","/**\n * O_EXCL exclusive file creation (ADR D82).\n *\n * `createExclusive(path, data, { mode })` creates a file in a single\n * syscall (`open(path, \"wx\", mode)`). Returns `true` if created, `false`\n * if it already existed (EEXIST swallowed — caller decides). All other\n * errors propagate.\n *\n * Default mode is 0o600 (owner-only) — EC-2 fix from edge-case review:\n * token files, lockfiles, and PID files MUST NOT default to world-\n * readable 0o644 under typical umask 022. Callers writing non-sensitive\n * files can pass `mode: 0o644` explicitly.\n *\n * NFS sem honor de O_EXCL é documentado (D61 — mesma postura do\n * `withFileLock`); SDK target é ext4/APFS/NTFS.\n *\n * @internal\n */\n\nimport { open } from \"node:fs/promises\";\n\nexport interface CreateExclusiveOptions {\n /** Unix mode for the new file (default 0o600 — owner-only). */\n mode?: number;\n}\n\n/**\n * Atomically create `path` with `data`. Returns true iff this call\n * created the file (race-free under POSIX-compliant filesystems).\n *\n * @internal\n */\nexport async function createExclusive(\n path: string,\n data: string | Uint8Array,\n options?: CreateExclusiveOptions,\n): Promise<boolean> {\n const mode = options?.mode ?? 0o600;\n try {\n const handle = await open(path, \"wx\", mode);\n try {\n await handle.writeFile(data);\n return true;\n } finally {\n await handle.close();\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return false;\n }\n throw err;\n }\n}\n","/**\n * Cross-process file lock helper (ADR D61).\n *\n * Uses `proper-lockfile` (optional peer dep) for cross-process locks. When\n * the peer dep is absent, falls back to `withCwdMutex` (in-process only)\n * with a one-shot stderr warning.\n *\n * EC-1 fix: uses a companion `<path>.lock` file with `realpath: false` so\n * `withFileLock` works even when the target `path` does not exist yet.\n * Without this, fresh installs that lock-then-create would crash with ENOENT.\n *\n * @internal\n */\n\nimport { withCwdMutex } from \"./cwd-mutex.js\";\n\ninterface ProperLockfileModule {\n lock: (file: string, options: ProperLockfileOptions) => Promise<() => Promise<void>>;\n}\n\ninterface ProperLockfileOptions {\n lockfilePath?: string;\n realpath?: boolean;\n stale?: number;\n retries?: {\n retries: number;\n factor?: number;\n minTimeout?: number;\n maxTimeout?: number;\n };\n}\n\nlet cached: ProperLockfileModule | null | undefined;\nlet warnedMissing = false;\nlet warnedStructural = false;\n\nasync function getProperLockfile(): Promise<ProperLockfileModule | null> {\n if (cached !== undefined) return cached;\n try {\n const mod = await import(\"proper-lockfile\");\n // T5.9 — supply-chain hardening: validate the imported module\n // exposes the API surface we depend on BEFORE caching it. A\n // tampered or incompatible version that lacks `lock`/`unlock`\n // functions gets treated as \"not installed\" with an advisory\n // warning — never silently used.\n if (!validateLockModule(mod)) {\n if (!warnedStructural) {\n warnedStructural = true;\n process.stderr.write(\n \"[theokit-sdk] proper-lockfile: imported module does NOT expose \" +\n \"the expected `lock`/`unlock` API surface. This may indicate a \" +\n \"supply-chain compromise or an incompatible major version. \" +\n \"Falling back to in-process mutex (no cross-process safety). \" +\n \"Reinstall with: pnpm add proper-lockfile@^11\\n\",\n );\n }\n cached = null;\n return cached;\n }\n cached = mod as ProperLockfileModule;\n } catch {\n cached = null;\n }\n return cached;\n}\n\n/**\n * T5.9 — Structural validation of the dynamically-imported\n * `proper-lockfile` module. Verifies the API surface we depend on\n * (`lock` and `unlock` as functions) is present. Pure function —\n * never throws, never mutates, never performs I/O.\n *\n * Exported via `__TESTING__validateLockModule` seam so unit tests\n * can drive the check without spinning up the dynamic import.\n *\n * @internal\n */\nfunction validateLockModule(mod: unknown): boolean {\n if (mod === null || mod === undefined || typeof mod !== \"object\") return false;\n const m = mod as Record<string, unknown>;\n return typeof m.lock === \"function\" && typeof m.unlock === \"function\";\n}\n\n/**\n * T5.9 — Test seam: expose the structural validator for unit tests.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__validateLockModule(mod: unknown): boolean {\n return validateLockModule(mod);\n}\n\n/**\n * T5.9 — Test seam: reset the module cache + warning flags between\n * tests so each test starts fresh. NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__resetFileLockCache(): void {\n cached = undefined;\n warnedMissing = false;\n warnedStructural = false;\n}\n\n/**\n * Options for `withFileLock`.\n *\n * @internal\n */\nexport interface FileLockOptions {\n /** Stale lock timeout in ms. Default 30_000 (30s). */\n stale?: number;\n /** Max retries on busy lock. Default 5. */\n retries?: number;\n /** Backoff factor between retries. Default 1.5. */\n retryFactor?: number;\n}\n\n/**\n * Run `fn` while holding an OS-level cross-process lock on `path`.\n *\n * If `proper-lockfile` is installed, uses it with a companion `<path>.lock`\n * file (`realpath: false`, so target file does NOT need to exist yet).\n * Otherwise falls back to in-process `withCwdMutex` and prints a one-shot\n * stderr warning telling the user to install `proper-lockfile` for\n * cross-process safety.\n *\n * The lock is released even when `fn` throws.\n *\n * @internal\n */\nexport async function withFileLock<T>(\n path: string,\n fn: () => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n const lib = await getProperLockfile();\n\n if (lib === null) {\n if (!warnedMissing) {\n warnedMissing = true;\n process.stderr.write(\n \"[theokit-sdk] proper-lockfile not installed; \" +\n \"cross-process file lock unavailable. \" +\n \"Install with: pnpm add proper-lockfile\\n\",\n );\n }\n return withCwdMutex(`file-lock:${path}`, fn);\n }\n\n // proper-lockfile errors immediately on same-process concurrent acquire\n // (\"Lock file is already being held\"). Wrap with cwd-mutex first so\n // in-process callers queue and only ONE thread at a time enters the\n // cross-process acquire path. Combined: full in-process + cross-process\n // serialization.\n return withCwdMutex(`file-lock:${path}`, async () => {\n const release = await lib.lock(path, {\n // EC-1: companion lockfile, target path may not exist yet.\n lockfilePath: `${path}.lock`,\n realpath: false,\n stale: options?.stale ?? 30_000,\n retries: {\n retries: options?.retries ?? 5,\n factor: options?.retryFactor ?? 1.5,\n minTimeout: 100,\n maxTimeout: 5_000,\n },\n });\n\n try {\n return await fn();\n } finally {\n await release();\n }\n });\n}\n\n/**\n * Test helper — resets the cached proper-lockfile module + warning flag.\n * Allows tests to simulate \"module absent\" by clearing cache then\n * monkey-patching the dynamic import resolution.\n *\n * @internal\n */\nexport function _resetFileLockCacheForTesting(): void {\n cached = undefined;\n warnedMissing = false;\n}\n","/**\n * FTS5 query sanitization + CJK detection (ADR D64).\n *\n * Port of Hermes' 6-step sanitizer (`hermes_state.py:1797-1847`). Prevents\n * crashes on user inputs with hyphens, dots, underscores, and unmatched\n * specials. Auto-quotes identifier-shaped tokens so `error-code` finds\n * literal `error-code` instead of `error AND code`.\n *\n * CJK detection returns true for inputs containing characters in the main\n * CJK Unicode ranges (Chinese, Japanese, Korean). CJK trigram routing is\n * deferred to v1.4 — callers receiving `containsCjk === true` should\n * short-circuit to an empty result or LIKE fallback for v1.3.\n *\n * @internal\n */\n\n// Control-char sentinels for the phrase-preservation placeholder. U+0001\n// and U+0002 are essentially never present in real user queries, and\n// crucially do NOT form word boundaries / `\\w` characters, so Step 5's\n// auto-quote regex (`\\b\\w+[-._]\\w[\\w\\-._]*\\b`) cannot match them.\n// Choosing `__PHRASE_N__` instead would re-trigger Step 5 on the second\n// sanitize pass and break idempotence.\nconst PHRASE_OPEN = \"\u0001\";\nconst PHRASE_CLOSE = \"\u0002\";\n\n/**\n * Six-step FTS5 query sanitizer. Returns a query string safe for passing\n * to `WHERE <fts5_table> MATCH ?` as a parameter.\n *\n * Returns empty string when the input contains only specials (caller MUST\n * short-circuit to avoid runtime SQL error — EC-3).\n *\n * @internal\n */\nexport function sanitizeFts5Query(query: string): string {\n if (query.length === 0) return query;\n\n // Step 1: preserve \"quoted phrases\" via control-char placeholders.\n const phrases: string[] = [];\n let text = query.replace(/\"[^\"]+\"/g, (match) => {\n phrases.push(match);\n return `${PHRASE_OPEN}${phrases.length - 1}${PHRASE_CLOSE}`;\n });\n\n // Step 2: strip unmatched specials (brackets, braces, parens, double quotes, caret).\n text = text.replace(/[[\\]{}()\"^]/g, \" \");\n\n // Step 3: collapse repeated asterisks (FTS5 prefix operator is single `*`).\n text = text.replace(/\\*+/g, \"*\");\n\n // Step 4: strip dangling boolean operators (AND/OR/NOT) at start/end.\n text = text.replace(/^\\s*(AND|OR|NOT)\\s+/i, \"\");\n text = text.replace(/\\s+(AND|OR|NOT)\\s*$/i, \"\");\n\n // Step 5: auto-quote identifier-shaped tokens that contain `-`, `.`, or `_`.\n // FTS5 tokenizer would otherwise split them as boolean conjunctions.\n text = text.replace(/\\b\\w+[-._]\\w[\\w\\-._]*\\b/g, (match) => `\"${match}\"`);\n\n // Step 6: restore preserved phrases.\n for (let i = 0; i < phrases.length; i += 1) {\n text = text.replace(`${PHRASE_OPEN}${i}${PHRASE_CLOSE}`, phrases[i] ?? \"\");\n }\n\n return text.trim();\n}\n\n// CJK code-point ranges. Coverage matches Hermes' coarse detection for v1.3;\n// fine-grained trigram routing comes in v1.4.\nconst CJK_RANGES: ReadonlyArray<readonly [number, number]> = [\n [0x3000, 0x303f], // CJK Symbols and Punctuation\n [0x3040, 0x309f], // Hiragana\n [0x30a0, 0x30ff], // Katakana\n [0x3400, 0x4dbf], // CJK Unified Ideographs Extension A\n [0x4e00, 0x9fff], // CJK Unified Ideographs\n [0xac00, 0xd7af], // Hangul Syllables\n];\n\n/**\n * Returns true if `text` contains at least one character in the main CJK\n * Unicode ranges.\n *\n * @internal\n */\nexport function containsCjk(text: string): boolean {\n for (const char of text) {\n const cp = char.codePointAt(0);\n if (cp === undefined) continue;\n for (const [lo, hi] of CJK_RANGES) {\n if (cp >= lo && cp <= hi) return true;\n }\n }\n return false;\n}\n","/**\n * Path resolution for SDK state files (ADR D60).\n *\n * Theokit anchors state at `<cwd>/.theokit/` by default (per-cwd). An\n * optional `THEOKIT_HOME` environment variable overrides this, enabling\n * test isolation, profile switching, and multi-tenant deployments.\n *\n * Rules:\n * - `getTheokitHome(cwd)` is the ONLY canonical resolver. Never hardcode\n * `path.join(cwd, \".theokit\")` in callers — use this function so tests\n * and overrides stay consistent.\n * - `getProfilesRoot()` is intentionally home-anchored (not affected by\n * `THEOKIT_HOME`) so `theokit profile list` discovers all profiles\n * regardless of which is active.\n * - `displayTheokitHome(cwd)` returns a human-readable path for logs.\n *\n * @internal\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst THEOKIT_DIR_NAME = \".theokit\";\n\n/**\n * Resolve the active Theokit state directory.\n *\n * Returns the value of `THEOKIT_HOME` env var if set (and non-empty after\n * trim); otherwise returns `<cwd>/.theokit`.\n *\n * @internal\n */\nexport function getTheokitHome(cwd: string): string {\n const override = process.env.THEOKIT_HOME?.trim();\n if (override !== undefined && override.length > 0) {\n return override;\n }\n return join(cwd, THEOKIT_DIR_NAME);\n}\n\n/**\n * Profiles root is ALWAYS at `~/.theokit/profiles/`, regardless of\n * `THEOKIT_HOME`. This lets `theokit profile list` see all profiles\n * regardless of which one is currently active.\n *\n * @internal\n */\nexport function getProfilesRoot(): string {\n return join(homedir(), THEOKIT_DIR_NAME, \"profiles\");\n}\n\n/**\n * Human-readable Theokit home for log/print output. Collapses `$HOME` to\n * `~` when applicable. NEVER used for `fs.*` calls — use `getTheokitHome`\n * for those.\n *\n * @internal\n */\nexport function displayTheokitHome(cwd: string): string {\n const resolved = getTheokitHome(cwd);\n const home = homedir();\n if (resolved === home) return \"~\";\n if (resolved.startsWith(`${home}/`)) {\n return `~${resolved.slice(home.length)}`;\n }\n return resolved;\n}\n","/**\n * Shared Zod schema for the `{ backend: \"memory\" | \"json\", dir? }` options\n * used by both `Workflow` snapshots and `Cache` semantic store persistence.\n * Extracted to remove the cross-module clone flagged by jscpd.\n *\n * @internal\n */\n\nimport { z } from \"zod\";\n\n/**\n * `persistence?` opt-in JSON disk backend with `dir` required when chosen.\n *\n * `.refine()` restored after Zod v4-only migration (plan zod-v4-migration\n * T1.1, ADR D4). With single v4 instance across workspace, ZodEffects\n * inside `z.object()` works correctly.\n */\nexport const PersistenceSchema = z\n .object({\n backend: z.enum([\"memory\", \"json\"]),\n dir: z.string().optional(),\n })\n .refine((p) => p.backend !== \"json\" || (typeof p.dir === \"string\" && p.dir.length > 0), {\n message: 'persistence.dir is required when backend = \"json\"',\n })\n .optional();\n","/**\n * Schema versioning helpers (ADR D62).\n *\n * Two parallel APIs:\n * - `migrateSchema` — SQLite via `PRAGMA user_version` + ordered forward-only migrations.\n * - `readVersionedJson` / `writeVersionedJson` — JSON files with `_schemaVersion` field.\n *\n * Forward-only: never deletes data, never downgrades. Migration callbacks\n * own the transformation; this module owns the bookkeeping.\n *\n * @internal\n */\n\nimport { readFile, rename } from \"node:fs/promises\";\n\nimport { atomicWriteJson } from \"./atomic-write.js\";\n\n// ────────────────────── SQLite migrations ──────────────────────\n\n/**\n * Minimal Database interface so this file does not hard-depend on\n * `better-sqlite3` at module load time. Compatible with the runtime\n * shape exposed by `better-sqlite3`.\n *\n * @internal\n */\nexport interface SqliteLike {\n pragma: (statement: string, options?: { simple?: boolean }) => unknown;\n exec: (sql: string) => void;\n transaction: <Args extends unknown[], R>(fn: (...args: Args) => R) => (...args: Args) => R;\n}\n\n/**\n * One forward migration step. `up` receives the DB inside a transaction;\n * `toVersion` is the value the pragma will be set to AFTER `up` returns.\n *\n * @internal\n */\nexport interface Migration {\n toVersion: number;\n up: (db: SqliteLike) => void;\n}\n\n/**\n * Options for `migrateSchema`.\n *\n * @internal\n */\nexport interface MigrateSchemaOptions {\n db: SqliteLike;\n currentVersion: number;\n migrations: ReadonlyArray<Migration>;\n /** For log/error context (e.g., \"memory-index\", \"registry\"). */\n label?: string;\n}\n\n/**\n * Result of `migrateSchema`.\n *\n * @internal\n */\nexport interface MigrateSchemaResult {\n from: number;\n to: number;\n ran: number;\n}\n\n/**\n * Run pending migrations to bring the DB from its current `user_version` up\n * to `currentVersion`. Migrations are sorted ascending by `toVersion` and\n * only those `> stored && <= currentVersion` execute. Each runs inside the\n * shared transaction.\n *\n * Throws if `stored > currentVersion` (downgrade attempt — forward-only).\n *\n * @internal\n */\nexport function migrateSchema(opts: MigrateSchemaOptions): MigrateSchemaResult {\n const { db, currentVersion, migrations, label = \"db\" } = opts;\n const storedRaw = db.pragma(\"user_version\", { simple: true });\n const stored = typeof storedRaw === \"number\" ? storedRaw : 0;\n\n if (stored > currentVersion) {\n throw new Error(\n `[${label}] schema version ${stored} > current ${currentVersion}; ` +\n \"did you downgrade the SDK? Forward-only migrations only.\",\n );\n }\n\n if (stored === currentVersion) {\n return { from: stored, to: stored, ran: 0 };\n }\n\n const pending = [...migrations]\n .sort((a, b) => a.toVersion - b.toVersion)\n .filter((m) => m.toVersion > stored && m.toVersion <= currentVersion);\n\n let ran = 0;\n let lastApplied = stored;\n\n db.transaction(() => {\n for (const m of pending) {\n m.up(db);\n db.pragma(`user_version = ${m.toVersion}`);\n lastApplied = m.toVersion;\n ran += 1;\n }\n })();\n\n return { from: stored, to: lastApplied, ran };\n}\n\n// ────────────────────── JSON versioned files ──────────────────────\n\n/**\n * Standard wrapper shape: `{ _schemaVersion: N, data: T }`. Use\n * `readVersionedJson` / `writeVersionedJson` for read/write.\n *\n * @internal\n */\nexport interface VersionedJsonFile<T> {\n _schemaVersion: number;\n data: T;\n}\n\n/**\n * Migration callback for `readVersionedJson`. Receives the FULL parsed\n * JSON object (not just `.data`), so legacy shapes without the\n * `_schemaVersion` / `data` wrapper can be migrated correctly (EC-2 fix).\n *\n * @internal\n */\nexport type VersionedJsonMigrate<T> = (parsed: unknown, fromVersion: number) => T;\n\n/**\n * Options for `readVersionedJson`.\n *\n * @internal\n */\nexport interface ReadVersionedJsonOptions<T> {\n path: string;\n currentVersion: number;\n migrate: VersionedJsonMigrate<T>;\n defaultValue: () => T;\n}\n\n/**\n * Read a versioned JSON file. Returns:\n * - file's `.data` when `_schemaVersion === currentVersion`\n * - migrated value (via `migrate(parsed, stored)`) when stored < current\n * - `defaultValue()` when file missing, corrupt, or stored > current\n *\n * Fail-soft: never throws. Corrupt or mismatched-newer files log a stderr\n * warning and fall back to `defaultValue()`.\n *\n * @internal\n */\nexport async function readVersionedJson<T>(opts: ReadVersionedJsonOptions<T>): Promise<T> {\n const { path, currentVersion, migrate, defaultValue } = opts;\n\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch {\n return defaultValue();\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // T5.10 — move corrupt file aside so (a) user can investigate and\n // (b) next run starts fresh instead of hitting the same warning loop.\n const asidePath = `${path}.corrupt.${Date.now()}`;\n try {\n await rename(path, asidePath);\n process.stderr.write(\n `[theokit-sdk] ${path} is corrupt; moved to ${asidePath}. Using default value.\\n`,\n );\n } catch {\n process.stderr.write(`[theokit-sdk] ${path} is corrupt; using default value.\\n`);\n }\n return defaultValue();\n }\n\n if (typeof parsed !== \"object\" || parsed === null) {\n return defaultValue();\n }\n\n const file = parsed as { _schemaVersion?: unknown; data?: unknown };\n const storedRaw = file._schemaVersion;\n const stored = typeof storedRaw === \"number\" ? storedRaw : 0;\n\n if (stored === currentVersion) {\n return file.data as T;\n }\n\n if (stored > currentVersion) {\n process.stderr.write(\n `[theokit-sdk] ${path} schema version ${stored} > current ${currentVersion}; ` +\n \"using default value (forward-only).\\n\",\n );\n return defaultValue();\n }\n\n // EC-2 fix: pass the FULL parsed object to migrate, not just `file.data`.\n // Legacy shapes (e.g., `{ schemaVersion: \"1.0\", agents: {...} }` without\n // a `data` field) need to inspect the whole thing.\n return migrate(parsed, stored);\n}\n\n/**\n * Write `data` as a versioned JSON file via atomic write.\n *\n * @internal\n */\nexport async function writeVersionedJson<T>(\n path: string,\n data: T,\n currentVersion: number,\n): Promise<void> {\n const file: VersionedJsonFile<T> = {\n _schemaVersion: currentVersion,\n data,\n };\n await atomicWriteJson(path, file);\n}\n","/**\n * SQLite optimistic compare-and-swap (ADR D83).\n *\n * `casUpdate(db, sql, params, expectedChanges)` executes a prepared\n * UPDATE and returns true if `result.changes === expectedChanges`.\n * Caller provides the full SQL (including `WHERE version = ?` predicate);\n * helper does NOT generate SQL — DRY at the level of \"wrap the\n * convention\", not \"build queries\".\n *\n * Use case canonical (Hermes `kanban_db.py:1922-1934`):\n *\n * const won = casUpdate(\n * db,\n * \"UPDATE registry SET status = ?, version = version + 1 WHERE id = ? AND version = ?\",\n * [\"running\", \"agent-foo\", 3],\n * );\n * if (!won) { ... re-read and retry ... }\n *\n * Helper does NOT retry — caller responsible for backoff (avoids hidden\n * loops). Helper does NOT cache prepared statements — `better-sqlite3`\n * caches internally; SDK use is one-shot per mutation, not hot loops.\n *\n * @internal\n */\n\nimport type Database from \"better-sqlite3\";\n\ntype DatabaseInstance = InstanceType<typeof Database>;\n\n/**\n * Execute a CAS UPDATE. Returns true iff the actual changes count matches\n * `expectedChanges`. SQL errors (invalid syntax, closed db) propagate.\n *\n * @internal\n */\nexport function casUpdate(\n db: DatabaseInstance,\n sql: string,\n params: ReadonlyArray<unknown>,\n expectedChanges: number = 1,\n): boolean {\n const stmt = db.prepare(sql);\n const result = stmt.run(...(params as unknown[]));\n return result.changes === expectedChanges;\n}\n","/**\n * SQLite WAL mode helper with NFS/SMB/FUSE fallback to DELETE (ADR D63).\n *\n * WAL is faster (concurrent readers + one writer) but unsupported on some\n * network/FUSE filesystems. Try WAL; if the pragma returns something else\n * or throws, fall back to DELETE journal mode. Warn one time per label.\n *\n * @internal\n */\n\ninterface PragmaCapable {\n pragma: (statement: string, options?: { simple?: boolean }) => unknown;\n}\n\n/**\n * Result of `applyWalWithFallback`.\n *\n * @internal\n */\nexport interface WalApplyResult {\n /** Final journal_mode actually in effect. */\n mode: \"wal\" | \"delete\";\n /** True if we wanted WAL but the filesystem refused. */\n fellBack: boolean;\n}\n\nconst warnedLabels = new Set<string>();\n\n/**\n * Apply WAL mode with DELETE fallback. Idempotent — safe to call multiple\n * times on the same connection.\n *\n * @param db any `pragma()`-capable SQLite handle (e.g., `better-sqlite3`)\n * @param label short identifier used in the warning (e.g., \"memory-index\")\n *\n * @internal\n */\nexport function applyWalWithFallback(db: PragmaCapable, label: string): WalApplyResult {\n try {\n const result = db.pragma(\"journal_mode = WAL\", { simple: true });\n if (typeof result === \"string\" && result.toLowerCase() === \"wal\") {\n return { mode: \"wal\", fellBack: false };\n }\n logFallback(label, `got \"${String(result)}\" instead of \"wal\"`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logFallback(label, msg);\n }\n\n db.pragma(\"journal_mode = DELETE\");\n return { mode: \"delete\", fellBack: true };\n}\n\nfunction logFallback(label: string, reason: string): void {\n if (warnedLabels.has(label)) return;\n warnedLabels.add(label);\n process.stderr.write(\n `[theokit-sdk] ${label}: WAL unavailable (${reason}); using DELETE journal mode. ` +\n \"This is normal on NFS/SMB/FUSE; expect slightly slower concurrent access.\\n\",\n );\n}\n\n/**\n * Test helper — clears the warn-once registry.\n *\n * @internal\n */\nexport function _resetWalWarnings(): void {\n warnedLabels.clear();\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared loader for `.theokit/<dir>/<name>.md` (or `.theokit/<dir>/<name>/PLUGIN.md`)
|
|
3
|
+
* config files. Mirrors the SKILL.md pattern: YAML frontmatter delimited by
|
|
4
|
+
* `---` blocks + optional markdown body for prose / rationale.
|
|
5
|
+
*
|
|
6
|
+
* Consumed by hooks-loader (T1.2), context-manager (T2.2), and
|
|
7
|
+
* plugins-manager (T3.2) — DRY across all 3 user-edited config surfaces.
|
|
8
|
+
*
|
|
9
|
+
* ADRs: D74 (markdown format), D75 (1 file = 1 entity), D76 (Zod schema).
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
import type { z } from "zod";
|
|
14
|
+
export interface MarkdownEntity<T> {
|
|
15
|
+
/** Slug from filename (without `.md`) for flat pattern, or subdir name for nested. */
|
|
16
|
+
slug: string;
|
|
17
|
+
/** Validated frontmatter. */
|
|
18
|
+
frontmatter: T;
|
|
19
|
+
/** Markdown body (everything after the closing `---`). May be empty. */
|
|
20
|
+
body: string;
|
|
21
|
+
/** Absolute source path for audit / error context. */
|
|
22
|
+
source: string;
|
|
23
|
+
}
|
|
24
|
+
export interface LoadOptions<T> {
|
|
25
|
+
/** Absolute path to the directory containing `*.md` files (flat) or
|
|
26
|
+
* subdirs with `PLUGIN.md` (nested). */
|
|
27
|
+
dir: string;
|
|
28
|
+
/** Zod schema validating the frontmatter shape. */
|
|
29
|
+
schema: z.ZodType<T>;
|
|
30
|
+
/** `"flat"` = top-level `<slug>.md` files; `"nested"` = subdirs each with
|
|
31
|
+
* `PLUGIN.md`. Default: `"flat"`. */
|
|
32
|
+
pattern?: "flat" | "nested";
|
|
33
|
+
/** Error code prefix (e.g., `"hook"` → `"hook_frontmatter_invalid"`). */
|
|
34
|
+
errorCodePrefix: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path resolution for SDK state files (ADR D60).
|
|
3
|
+
*
|
|
4
|
+
* Theokit anchors state at `<cwd>/.theokit/` by default (per-cwd). An
|
|
5
|
+
* optional `THEOKIT_HOME` environment variable overrides this, enabling
|
|
6
|
+
* test isolation, profile switching, and multi-tenant deployments.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - `getTheokitHome(cwd)` is the ONLY canonical resolver. Never hardcode
|
|
10
|
+
* `path.join(cwd, ".theokit")` in callers — use this function so tests
|
|
11
|
+
* and overrides stay consistent.
|
|
12
|
+
* - `getProfilesRoot()` is intentionally home-anchored (not affected by
|
|
13
|
+
* `THEOKIT_HOME`) so `theokit profile list` discovers all profiles
|
|
14
|
+
* regardless of which is active.
|
|
15
|
+
* - `displayTheokitHome(cwd)` returns a human-readable path for logs.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Zod schema for the `{ backend: "memory" | "json", dir? }` options
|
|
3
|
+
* used by both `Workflow` snapshots and `Cache` semantic store persistence.
|
|
4
|
+
* Extracted to remove the cross-module clone flagged by jscpd.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* `persistence?` opt-in JSON disk backend with `dir` required when chosen.
|
|
10
|
+
*
|
|
11
|
+
* `.refine()` restored after Zod v4-only migration (plan zod-v4-migration
|
|
12
|
+
* T1.1, ADR D4). With single v4 instance across workspace, ZodEffects
|
|
13
|
+
* inside `z.object()` works correctly.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PersistenceSchema: z.ZodOptional<z.ZodObject<{
|
|
16
|
+
backend: z.ZodEnum<{
|
|
17
|
+
memory: "memory";
|
|
18
|
+
json: "json";
|
|
19
|
+
}>;
|
|
20
|
+
dir: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strip>>;
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* `persistence?` opt-in JSON disk backend with `dir` required when chosen.
|
|
10
|
+
*
|
|
11
|
+
* `.refine()` restored after Zod v4-only migration (plan zod-v4-migration
|
|
12
|
+
* T1.1, ADR D4). With single v4 instance across workspace, ZodEffects
|
|
13
|
+
* inside `z.object()` works correctly.
|
|
10
14
|
*/
|
|
11
15
|
export declare const PersistenceSchema: z.ZodOptional<z.ZodObject<{
|
|
12
16
|
backend: z.ZodEnum<{
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema versioning helpers (ADR D62).
|
|
3
|
+
*
|
|
4
|
+
* Two parallel APIs:
|
|
5
|
+
* - `migrateSchema` — SQLite via `PRAGMA user_version` + ordered forward-only migrations.
|
|
6
|
+
* - `readVersionedJson` / `writeVersionedJson` — JSON files with `_schemaVersion` field.
|
|
7
|
+
*
|
|
8
|
+
* Forward-only: never deletes data, never downgrades. Migration callbacks
|
|
9
|
+
* own the transformation; this module owns the bookkeeping.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite optimistic compare-and-swap (ADR D83).
|
|
3
|
+
*
|
|
4
|
+
* `casUpdate(db, sql, params, expectedChanges)` executes a prepared
|
|
5
|
+
* UPDATE and returns true if `result.changes === expectedChanges`.
|
|
6
|
+
* Caller provides the full SQL (including `WHERE version = ?` predicate);
|
|
7
|
+
* helper does NOT generate SQL — DRY at the level of "wrap the
|
|
8
|
+
* convention", not "build queries".
|
|
9
|
+
*
|
|
10
|
+
* Use case canonical (Hermes `kanban_db.py:1922-1934`):
|
|
11
|
+
*
|
|
12
|
+
* const won = casUpdate(
|
|
13
|
+
* db,
|
|
14
|
+
* "UPDATE registry SET status = ?, version = version + 1 WHERE id = ? AND version = ?",
|
|
15
|
+
* ["running", "agent-foo", 3],
|
|
16
|
+
* );
|
|
17
|
+
* if (!won) { ... re-read and retry ... }
|
|
18
|
+
*
|
|
19
|
+
* Helper does NOT retry — caller responsible for backoff (avoids hidden
|
|
20
|
+
* loops). Helper does NOT cache prepared statements — `better-sqlite3`
|
|
21
|
+
* caches internally; SDK use is one-shot per mutation, not hot loops.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite optimistic compare-and-swap (ADR D83).
|
|
3
|
+
*
|
|
4
|
+
* `casUpdate(db, sql, params, expectedChanges)` executes a prepared
|
|
5
|
+
* UPDATE and returns true if `result.changes === expectedChanges`.
|
|
6
|
+
* Caller provides the full SQL (including `WHERE version = ?` predicate);
|
|
7
|
+
* helper does NOT generate SQL — DRY at the level of "wrap the
|
|
8
|
+
* convention", not "build queries".
|
|
9
|
+
*
|
|
10
|
+
* Use case canonical (Hermes `kanban_db.py:1922-1934`):
|
|
11
|
+
*
|
|
12
|
+
* const won = casUpdate(
|
|
13
|
+
* db,
|
|
14
|
+
* "UPDATE registry SET status = ?, version = version + 1 WHERE id = ? AND version = ?",
|
|
15
|
+
* ["running", "agent-foo", 3],
|
|
16
|
+
* );
|
|
17
|
+
* if (!won) { ... re-read and retry ... }
|
|
18
|
+
*
|
|
19
|
+
* Helper does NOT retry — caller responsible for backoff (avoids hidden
|
|
20
|
+
* loops). Helper does NOT cache prepared statements — `better-sqlite3`
|
|
21
|
+
* caches internally; SDK use is one-shot per mutation, not hot loops.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite WAL mode helper with NFS/SMB/FUSE fallback to DELETE (ADR D63).
|
|
3
|
+
*
|
|
4
|
+
* WAL is faster (concurrent readers + one writer) but unsupported on some
|
|
5
|
+
* network/FUSE filesystems. Try WAL; if the pragma returns something else
|
|
6
|
+
* or throws, fall back to DELETE journal mode. Warn one time per label.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginContext implementation + dev-mode seal (T1.2, ADR D99).
|
|
3
|
+
*
|
|
4
|
+
* `createPluginContext()` returns a fresh `{ ctx, registrations }` pair
|
|
5
|
+
* for each plugin. In dev mode (`NODE_ENV !== "production"`) the context
|
|
6
|
+
* is wrapped in a Proxy that throws on `set`/`delete` to catch plugin
|
|
7
|
+
* abuse early. In production the raw impl is returned (zero overhead).
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
import type { CommandHandler, HookHandler, HookName, PluginContext } from "./types.js";
|
|
12
|
+
interface CommandEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
handler: CommandHandler;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
interface InjectedMessage {
|
|
18
|
+
content: string;
|
|
19
|
+
role: "user" | "system";
|
|
20
|
+
}
|
|
21
|
+
export interface PluginRegistrations {
|
|
22
|
+
tools: CustomTool[];
|
|
23
|
+
commands: CommandEntry[];
|
|
24
|
+
hooks: Map<HookName, HookHandler[]>;
|
|
25
|
+
injected: InjectedMessage[];
|
|
26
|
+
}
|
|
27
|
+
export declare function createPluginContext(): {
|
|
28
|
+
ctx: PluginContext;
|
|
29
|
+
registrations: PluginRegistrations;
|
|
30
|
+
};
|
|
31
|
+
export {};
|