@theokit/sdk 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/eval.cjs CHANGED
@@ -3506,6 +3506,72 @@ var init_cloud_tool_parity = __esm({
3506
3506
  init_errors();
3507
3507
  }
3508
3508
  });
3509
+
3510
+ // src/internal/persistence/file-lock.ts
3511
+ async function getProperLockfile() {
3512
+ if (cached !== void 0) return cached;
3513
+ try {
3514
+ const mod = await import('proper-lockfile');
3515
+ if (!validateLockModule(mod)) {
3516
+ if (!warnedStructural) {
3517
+ warnedStructural = true;
3518
+ process.stderr.write(
3519
+ "[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"
3520
+ );
3521
+ }
3522
+ cached = null;
3523
+ return cached;
3524
+ }
3525
+ cached = mod;
3526
+ } catch {
3527
+ cached = null;
3528
+ }
3529
+ return cached;
3530
+ }
3531
+ function validateLockModule(mod) {
3532
+ if (mod === null || mod === void 0 || typeof mod !== "object") return false;
3533
+ const m = mod;
3534
+ return typeof m.lock === "function" && typeof m.unlock === "function";
3535
+ }
3536
+ async function withFileLock(path, fn, options) {
3537
+ const lib = await getProperLockfile();
3538
+ if (lib === null) {
3539
+ if (!warnedMissing) {
3540
+ warnedMissing = true;
3541
+ process.stderr.write(
3542
+ "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
3543
+ );
3544
+ }
3545
+ return withCwdMutex(`file-lock:${path}`, fn);
3546
+ }
3547
+ return withCwdMutex(`file-lock:${path}`, async () => {
3548
+ const release = await lib.lock(path, {
3549
+ // EC-1: companion lockfile, target path may not exist yet.
3550
+ lockfilePath: `${path}.lock`,
3551
+ realpath: false,
3552
+ stale: 3e4,
3553
+ retries: {
3554
+ retries: 5,
3555
+ factor: 1.5,
3556
+ minTimeout: 100,
3557
+ maxTimeout: 5e3
3558
+ }
3559
+ });
3560
+ try {
3561
+ return await fn();
3562
+ } finally {
3563
+ await release();
3564
+ }
3565
+ });
3566
+ }
3567
+ var cached, warnedMissing, warnedStructural;
3568
+ var init_file_lock = __esm({
3569
+ "src/internal/persistence/file-lock.ts"() {
3570
+ init_cwd_mutex();
3571
+ warnedMissing = false;
3572
+ warnedStructural = false;
3573
+ }
3574
+ });
3509
3575
  function encodeProjectDir(cwd) {
3510
3576
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
3511
3577
  }
@@ -3732,70 +3798,31 @@ var init_session_transcript = __esm({
3732
3798
  };
3733
3799
  }
3734
3800
  });
3735
-
3736
- // src/internal/persistence/file-lock.ts
3737
- async function getProperLockfile() {
3738
- if (cached !== void 0) return cached;
3739
- try {
3740
- const mod = await import('proper-lockfile');
3741
- if (!validateLockModule(mod)) {
3742
- if (!warnedStructural) {
3743
- warnedStructural = true;
3744
- process.stderr.write(
3745
- "[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"
3746
- );
3747
- }
3748
- cached = null;
3749
- return cached;
3750
- }
3751
- cached = mod;
3752
- } catch {
3753
- cached = null;
3754
- }
3755
- return cached;
3756
- }
3757
- function validateLockModule(mod) {
3758
- if (mod === null || mod === void 0 || typeof mod !== "object") return false;
3759
- const m = mod;
3760
- return typeof m.lock === "function" && typeof m.unlock === "function";
3761
- }
3762
- async function withFileLock(path, fn, options) {
3763
- const lib = await getProperLockfile();
3764
- if (lib === null) {
3765
- if (!warnedMissing) {
3766
- warnedMissing = true;
3767
- process.stderr.write(
3768
- "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
3769
- );
3770
- }
3771
- return withCwdMutex(`file-lock:${path}`, fn);
3772
- }
3773
- return withCwdMutex(`file-lock:${path}`, async () => {
3774
- const release = await lib.lock(path, {
3775
- // EC-1: companion lockfile, target path may not exist yet.
3776
- lockfilePath: `${path}.lock`,
3777
- realpath: false,
3778
- stale: 3e4,
3779
- retries: {
3780
- retries: 5,
3781
- factor: 1.5,
3782
- minTimeout: 100,
3783
- maxTimeout: 5e3
3801
+ var FsSessionStore;
3802
+ var init_fs_session_store = __esm({
3803
+ "src/internal/persistence/fs-session-store.ts"() {
3804
+ init_file_lock();
3805
+ init_session_transcript();
3806
+ FsSessionStore = class {
3807
+ #baseDir;
3808
+ #cwd;
3809
+ constructor(options) {
3810
+ this.#baseDir = options.baseDir;
3811
+ this.#cwd = options.cwd;
3812
+ }
3813
+ async readRecords(agentId) {
3814
+ return readTranscript(transcriptPath(this.#baseDir, this.#cwd, agentId));
3815
+ }
3816
+ async appendRecords(agentId, records) {
3817
+ if (records.length === 0) return;
3818
+ const path$1 = transcriptPath(this.#baseDir, this.#cwd, agentId);
3819
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
3820
+ await withFileLock(path$1, async () => {
3821
+ const prior = await readTranscript(path$1);
3822
+ await writeTranscript(path$1, [...prior, ...records]);
3823
+ });
3784
3824
  }
3785
- });
3786
- try {
3787
- return await fn();
3788
- } finally {
3789
- await release();
3790
- }
3791
- });
3792
- }
3793
- var cached, warnedMissing, warnedStructural;
3794
- var init_file_lock = __esm({
3795
- "src/internal/persistence/file-lock.ts"() {
3796
- init_cwd_mutex();
3797
- warnedMissing = false;
3798
- warnedStructural = false;
3825
+ };
3799
3826
  }
3800
3827
  });
3801
3828
  function getTheokitHome(cwd) {
@@ -5127,6 +5154,8 @@ var init_memory_path_selector = __esm({
5127
5154
  PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
5128
5155
  }
5129
5156
  });
5157
+
5158
+ // src/internal/runtime/session/agent-session-store.ts
5130
5159
  function seedTranscript(prior, opts) {
5131
5160
  return SessionTranscript.fromRecords(prior, opts);
5132
5161
  }
@@ -5164,8 +5193,8 @@ function appendConversation(transcript, conversation) {
5164
5193
  if (toolResults.length > 0) transcript.appendToolResults(toolResults);
5165
5194
  }
5166
5195
  }
5167
- async function readSessionMessages(baseDir, cwd, agentId) {
5168
- const records = await readTranscript(transcriptPath(baseDir, cwd, agentId));
5196
+ async function readSessionMessages(store, agentId) {
5197
+ const records = await store.readRecords(agentId);
5169
5198
  return reconstructMessages(records).map(narrowToSessionMessage);
5170
5199
  }
5171
5200
  function partToText(p) {
@@ -5182,37 +5211,31 @@ function narrowToSessionMessage(m) {
5182
5211
  const text = m.content.map(partToText).filter((s) => s.length > 0).join("\n");
5183
5212
  return { role, text };
5184
5213
  }
5185
- async function persistTurn(loc, sessionId, turn) {
5186
- const path$1 = transcriptPath(loc.baseDir, loc.cwd, loc.agentId);
5187
- await promises.mkdir(path.dirname(path$1), { recursive: true });
5188
- await withFileLock(path$1, async () => {
5189
- const prior = await readTranscript(path$1);
5190
- const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5191
- transcript.appendUserTurn(turn.userText);
5192
- appendConversation(transcript, turn.conversation);
5193
- await writeTranscript(path$1, transcript.records());
5194
- });
5214
+ function deltaRecords(transcript, priorLength) {
5215
+ return transcript.records().slice(priorLength);
5195
5216
  }
5196
- async function appendCompactBoundaryRecord(loc, sessionId, meta) {
5197
- const path$1 = transcriptPath(loc.baseDir, loc.cwd, loc.agentId);
5198
- await promises.mkdir(path.dirname(path$1), { recursive: true });
5199
- await withFileLock(path$1, async () => {
5200
- const prior = await readTranscript(path$1);
5201
- const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5202
- transcript.appendCompactBoundary(meta);
5203
- await writeTranscript(path$1, transcript.records());
5204
- });
5217
+ async function persistTurn(store, loc, sessionId, turn) {
5218
+ const prior = await store.readRecords(loc.agentId);
5219
+ const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5220
+ transcript.appendUserTurn(turn.userText);
5221
+ appendConversation(transcript, turn.conversation);
5222
+ await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));
5223
+ }
5224
+ async function appendCompactBoundaryRecord(store, loc, sessionId, meta) {
5225
+ const prior = await store.readRecords(loc.agentId);
5226
+ const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5227
+ transcript.appendCompactBoundary(meta);
5228
+ await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));
5205
5229
  }
5206
5230
  var init_agent_session_store = __esm({
5207
5231
  "src/internal/runtime/session/agent-session-store.ts"() {
5208
- init_file_lock();
5209
5232
  init_session_transcript();
5210
5233
  }
5211
5234
  });
5212
5235
 
5213
5236
  // src/internal/runtime/session/agent-session.ts
5214
- function transcriptKey(baseDir, cwd, agentId) {
5215
- return `${baseDir}::${cwd}::${agentId}`;
5237
+ function transcriptKey(cwd, agentId) {
5238
+ return `${cwd}::${agentId}`;
5216
5239
  }
5217
5240
  function appendSessionMessage(agentId, message) {
5218
5241
  const existing = sessions.get(agentId) ?? [];
@@ -5222,15 +5245,15 @@ function appendSessionMessage(agentId, message) {
5222
5245
  function getSessionMessages(agentId) {
5223
5246
  return sessions.get(agentId) ?? [];
5224
5247
  }
5225
- function persistTurnToTranscript(loc, sessionId, turn, onCompact) {
5226
- const key = transcriptKey(loc.baseDir, loc.cwd, loc.agentId);
5248
+ function persistTurnToTranscript(store, loc, sessionId, turn, onCompact) {
5249
+ const key = transcriptKey(loc.cwd, loc.agentId);
5227
5250
  const chained = (pendingWrites.get(key) ?? Promise.resolve()).then(async () => {
5228
5251
  try {
5229
- await persistTurn(loc, sessionId, turn);
5252
+ await persistTurn(store, loc, sessionId, turn);
5230
5253
  const count = (recordCounts.get(key) ?? 0) + 1;
5231
5254
  recordCounts.set(key, count);
5232
5255
  if (count % COMPACTION_CHECK_INTERVAL === 0) {
5233
- await appendCompactBoundaryRecord(loc, sessionId, {
5256
+ await appendCompactBoundaryRecord(store, loc, sessionId, {
5234
5257
  preTokens: 0,
5235
5258
  trigger: "auto"
5236
5259
  });
@@ -5253,10 +5276,10 @@ function persistTurnToTranscript(loc, sessionId, turn, onCompact) {
5253
5276
  );
5254
5277
  }
5255
5278
  async function hydrateSession(agentId, loc) {
5256
- const key = transcriptKey(loc.baseDir, loc.cwd, agentId);
5279
+ const key = transcriptKey(loc.cwd, agentId);
5257
5280
  if (hydratedKeys.has(key)) return;
5258
5281
  hydratedKeys.add(key);
5259
- const persisted = await readSessionMessages(loc.baseDir, loc.cwd, agentId);
5282
+ const persisted = await readSessionMessages(loc.store, agentId);
5260
5283
  if (persisted.length === 0) return;
5261
5284
  if (!sessions.has(agentId) || sessions.get(agentId)?.length === 0) {
5262
5285
  sessions.set(agentId, persisted);
@@ -5291,7 +5314,7 @@ async function runPostRunLifecycle(inputs) {
5291
5314
  userText,
5292
5315
  agentId,
5293
5316
  workspaceCwd,
5294
- baseDir,
5317
+ sessionStore,
5295
5318
  model,
5296
5319
  onRunEvent,
5297
5320
  hooksExecutor,
@@ -5310,7 +5333,8 @@ async function runPostRunLifecycle(inputs) {
5310
5333
  }
5311
5334
  const conversation = await safeConversation(run);
5312
5335
  persistTurnToTranscript(
5313
- { baseDir, cwd: workspaceCwd, agentId, model },
5336
+ sessionStore,
5337
+ { cwd: workspaceCwd, agentId, model },
5314
5338
  agentId,
5315
5339
  { userText, conversation },
5316
5340
  onRunEvent !== void 0 ? () => emitRunEvent(onRunEvent, { type: "compact_boundary", trigger: "auto" }) : void 0
@@ -17917,6 +17941,7 @@ var init_local_agent = __esm({
17917
17941
  init_errors();
17918
17942
  init_ids();
17919
17943
  init_cwd_mutex();
17944
+ init_fs_session_store();
17920
17945
  init_session_transcript();
17921
17946
  init_store();
17922
17947
  init_manager();
@@ -17960,6 +17985,12 @@ var init_local_agent = __esm({
17960
17985
  * `~/.theokit`; set `local.baseDir: "~/.claude"` for Claude Code CLI interop.
17961
17986
  */
17962
17987
  transcriptBaseDir;
17988
+ /**
17989
+ * SE41 — the session record store. Defaults to the FS transcript store (byte-
17990
+ * identical to SE40); `local.sessionStore` injects an external store (Postgres /
17991
+ * Redis / KV) so resume works on serverless (ephemeral FS) and multi-host.
17992
+ */
17993
+ sessionStore;
17963
17994
  /**
17964
17995
  * D319: lifecycle AbortController fired on `dispose()`. Composed with the
17965
17996
  * caller's `SendOptions.signal` via `anySignal` so the LLM `fetch()`
@@ -18002,6 +18033,7 @@ var init_local_agent = __esm({
18002
18033
  this.options = options;
18003
18034
  this.workspaceCwd = resolveCwd(options.local?.cwd);
18004
18035
  this.transcriptBaseDir = resolveBaseDir(options.local?.baseDir);
18036
+ this.sessionStore = options.local?.sessionStore ?? new FsSessionStore({ baseDir: this.transcriptBaseDir, cwd: this.workspaceCwd });
18005
18037
  this.settingSourcesIncludeProject = includesSetting(options, "project");
18006
18038
  this.settingSourcesIncludePlugins = includesSetting(options, "plugins");
18007
18039
  const sub = bootstrapSubmanagers({
@@ -18049,7 +18081,7 @@ var init_local_agent = __esm({
18049
18081
  this.settingSourcesIncludeProject,
18050
18082
  this.options.agents
18051
18083
  );
18052
- await hydrateSession(this.agentId, { baseDir: this.transcriptBaseDir, cwd: this.workspaceCwd });
18084
+ await hydrateSession(this.agentId, { store: this.sessionStore, cwd: this.workspaceCwd });
18053
18085
  await this.personalityStore.hydrate(this.agentId);
18054
18086
  }
18055
18087
  /** T4.2 — expose PluginManager so agent-loop can fire pre_tool_call hooks. @internal */
@@ -18105,7 +18137,7 @@ var init_local_agent = __esm({
18105
18137
  userText,
18106
18138
  agentId: this.agentId,
18107
18139
  workspaceCwd: this.workspaceCwd,
18108
- baseDir: this.transcriptBaseDir,
18140
+ sessionStore: this.sessionStore,
18109
18141
  model: this.model?.id ?? "unknown",
18110
18142
  ...options.onRunEvent !== void 0 ? { onRunEvent: options.onRunEvent } : {},
18111
18143
  hooksExecutor: this.hooksExecutor,