@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.js CHANGED
@@ -3503,6 +3503,72 @@ var init_cloud_tool_parity = __esm({
3503
3503
  init_errors();
3504
3504
  }
3505
3505
  });
3506
+
3507
+ // src/internal/persistence/file-lock.ts
3508
+ async function getProperLockfile() {
3509
+ if (cached !== void 0) return cached;
3510
+ try {
3511
+ const mod = await import('proper-lockfile');
3512
+ if (!validateLockModule(mod)) {
3513
+ if (!warnedStructural) {
3514
+ warnedStructural = true;
3515
+ process.stderr.write(
3516
+ "[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"
3517
+ );
3518
+ }
3519
+ cached = null;
3520
+ return cached;
3521
+ }
3522
+ cached = mod;
3523
+ } catch {
3524
+ cached = null;
3525
+ }
3526
+ return cached;
3527
+ }
3528
+ function validateLockModule(mod) {
3529
+ if (mod === null || mod === void 0 || typeof mod !== "object") return false;
3530
+ const m = mod;
3531
+ return typeof m.lock === "function" && typeof m.unlock === "function";
3532
+ }
3533
+ async function withFileLock(path, fn, options) {
3534
+ const lib = await getProperLockfile();
3535
+ if (lib === null) {
3536
+ if (!warnedMissing) {
3537
+ warnedMissing = true;
3538
+ process.stderr.write(
3539
+ "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
3540
+ );
3541
+ }
3542
+ return withCwdMutex(`file-lock:${path}`, fn);
3543
+ }
3544
+ return withCwdMutex(`file-lock:${path}`, async () => {
3545
+ const release = await lib.lock(path, {
3546
+ // EC-1: companion lockfile, target path may not exist yet.
3547
+ lockfilePath: `${path}.lock`,
3548
+ realpath: false,
3549
+ stale: 3e4,
3550
+ retries: {
3551
+ retries: 5,
3552
+ factor: 1.5,
3553
+ minTimeout: 100,
3554
+ maxTimeout: 5e3
3555
+ }
3556
+ });
3557
+ try {
3558
+ return await fn();
3559
+ } finally {
3560
+ await release();
3561
+ }
3562
+ });
3563
+ }
3564
+ var cached, warnedMissing, warnedStructural;
3565
+ var init_file_lock = __esm({
3566
+ "src/internal/persistence/file-lock.ts"() {
3567
+ init_cwd_mutex();
3568
+ warnedMissing = false;
3569
+ warnedStructural = false;
3570
+ }
3571
+ });
3506
3572
  function encodeProjectDir(cwd) {
3507
3573
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
3508
3574
  }
@@ -3729,70 +3795,31 @@ var init_session_transcript = __esm({
3729
3795
  };
3730
3796
  }
3731
3797
  });
3732
-
3733
- // src/internal/persistence/file-lock.ts
3734
- async function getProperLockfile() {
3735
- if (cached !== void 0) return cached;
3736
- try {
3737
- const mod = await import('proper-lockfile');
3738
- if (!validateLockModule(mod)) {
3739
- if (!warnedStructural) {
3740
- warnedStructural = true;
3741
- process.stderr.write(
3742
- "[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"
3743
- );
3744
- }
3745
- cached = null;
3746
- return cached;
3747
- }
3748
- cached = mod;
3749
- } catch {
3750
- cached = null;
3751
- }
3752
- return cached;
3753
- }
3754
- function validateLockModule(mod) {
3755
- if (mod === null || mod === void 0 || typeof mod !== "object") return false;
3756
- const m = mod;
3757
- return typeof m.lock === "function" && typeof m.unlock === "function";
3758
- }
3759
- async function withFileLock(path, fn, options) {
3760
- const lib = await getProperLockfile();
3761
- if (lib === null) {
3762
- if (!warnedMissing) {
3763
- warnedMissing = true;
3764
- process.stderr.write(
3765
- "[theokit-sdk] proper-lockfile not installed; cross-process file lock unavailable. Install with: pnpm add proper-lockfile\n"
3766
- );
3767
- }
3768
- return withCwdMutex(`file-lock:${path}`, fn);
3769
- }
3770
- return withCwdMutex(`file-lock:${path}`, async () => {
3771
- const release = await lib.lock(path, {
3772
- // EC-1: companion lockfile, target path may not exist yet.
3773
- lockfilePath: `${path}.lock`,
3774
- realpath: false,
3775
- stale: 3e4,
3776
- retries: {
3777
- retries: 5,
3778
- factor: 1.5,
3779
- minTimeout: 100,
3780
- maxTimeout: 5e3
3798
+ var FsSessionStore;
3799
+ var init_fs_session_store = __esm({
3800
+ "src/internal/persistence/fs-session-store.ts"() {
3801
+ init_file_lock();
3802
+ init_session_transcript();
3803
+ FsSessionStore = class {
3804
+ #baseDir;
3805
+ #cwd;
3806
+ constructor(options) {
3807
+ this.#baseDir = options.baseDir;
3808
+ this.#cwd = options.cwd;
3809
+ }
3810
+ async readRecords(agentId) {
3811
+ return readTranscript(transcriptPath(this.#baseDir, this.#cwd, agentId));
3812
+ }
3813
+ async appendRecords(agentId, records) {
3814
+ if (records.length === 0) return;
3815
+ const path = transcriptPath(this.#baseDir, this.#cwd, agentId);
3816
+ await mkdir(dirname(path), { recursive: true });
3817
+ await withFileLock(path, async () => {
3818
+ const prior = await readTranscript(path);
3819
+ await writeTranscript(path, [...prior, ...records]);
3820
+ });
3781
3821
  }
3782
- });
3783
- try {
3784
- return await fn();
3785
- } finally {
3786
- await release();
3787
- }
3788
- });
3789
- }
3790
- var cached, warnedMissing, warnedStructural;
3791
- var init_file_lock = __esm({
3792
- "src/internal/persistence/file-lock.ts"() {
3793
- init_cwd_mutex();
3794
- warnedMissing = false;
3795
- warnedStructural = false;
3822
+ };
3796
3823
  }
3797
3824
  });
3798
3825
  function getTheokitHome(cwd) {
@@ -5124,6 +5151,8 @@ var init_memory_path_selector = __esm({
5124
5151
  PORT_MEMORY_PATH_ENV_VAR = "THEOKIT_PORT_MEMORY_PATH";
5125
5152
  }
5126
5153
  });
5154
+
5155
+ // src/internal/runtime/session/agent-session-store.ts
5127
5156
  function seedTranscript(prior, opts) {
5128
5157
  return SessionTranscript.fromRecords(prior, opts);
5129
5158
  }
@@ -5161,8 +5190,8 @@ function appendConversation(transcript, conversation) {
5161
5190
  if (toolResults.length > 0) transcript.appendToolResults(toolResults);
5162
5191
  }
5163
5192
  }
5164
- async function readSessionMessages(baseDir, cwd, agentId) {
5165
- const records = await readTranscript(transcriptPath(baseDir, cwd, agentId));
5193
+ async function readSessionMessages(store, agentId) {
5194
+ const records = await store.readRecords(agentId);
5166
5195
  return reconstructMessages(records).map(narrowToSessionMessage);
5167
5196
  }
5168
5197
  function partToText(p) {
@@ -5179,37 +5208,31 @@ function narrowToSessionMessage(m) {
5179
5208
  const text = m.content.map(partToText).filter((s) => s.length > 0).join("\n");
5180
5209
  return { role, text };
5181
5210
  }
5182
- async function persistTurn(loc, sessionId, turn) {
5183
- const path = transcriptPath(loc.baseDir, loc.cwd, loc.agentId);
5184
- await mkdir(dirname(path), { recursive: true });
5185
- await withFileLock(path, async () => {
5186
- const prior = await readTranscript(path);
5187
- const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5188
- transcript.appendUserTurn(turn.userText);
5189
- appendConversation(transcript, turn.conversation);
5190
- await writeTranscript(path, transcript.records());
5191
- });
5211
+ function deltaRecords(transcript, priorLength) {
5212
+ return transcript.records().slice(priorLength);
5192
5213
  }
5193
- async function appendCompactBoundaryRecord(loc, sessionId, meta) {
5194
- const path = transcriptPath(loc.baseDir, loc.cwd, loc.agentId);
5195
- await mkdir(dirname(path), { recursive: true });
5196
- await withFileLock(path, async () => {
5197
- const prior = await readTranscript(path);
5198
- const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5199
- transcript.appendCompactBoundary(meta);
5200
- await writeTranscript(path, transcript.records());
5201
- });
5214
+ async function persistTurn(store, loc, sessionId, turn) {
5215
+ const prior = await store.readRecords(loc.agentId);
5216
+ const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5217
+ transcript.appendUserTurn(turn.userText);
5218
+ appendConversation(transcript, turn.conversation);
5219
+ await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));
5220
+ }
5221
+ async function appendCompactBoundaryRecord(store, loc, sessionId, meta) {
5222
+ const prior = await store.readRecords(loc.agentId);
5223
+ const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });
5224
+ transcript.appendCompactBoundary(meta);
5225
+ await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));
5202
5226
  }
5203
5227
  var init_agent_session_store = __esm({
5204
5228
  "src/internal/runtime/session/agent-session-store.ts"() {
5205
- init_file_lock();
5206
5229
  init_session_transcript();
5207
5230
  }
5208
5231
  });
5209
5232
 
5210
5233
  // src/internal/runtime/session/agent-session.ts
5211
- function transcriptKey(baseDir, cwd, agentId) {
5212
- return `${baseDir}::${cwd}::${agentId}`;
5234
+ function transcriptKey(cwd, agentId) {
5235
+ return `${cwd}::${agentId}`;
5213
5236
  }
5214
5237
  function appendSessionMessage(agentId, message) {
5215
5238
  const existing = sessions.get(agentId) ?? [];
@@ -5219,15 +5242,15 @@ function appendSessionMessage(agentId, message) {
5219
5242
  function getSessionMessages(agentId) {
5220
5243
  return sessions.get(agentId) ?? [];
5221
5244
  }
5222
- function persistTurnToTranscript(loc, sessionId, turn, onCompact) {
5223
- const key = transcriptKey(loc.baseDir, loc.cwd, loc.agentId);
5245
+ function persistTurnToTranscript(store, loc, sessionId, turn, onCompact) {
5246
+ const key = transcriptKey(loc.cwd, loc.agentId);
5224
5247
  const chained = (pendingWrites.get(key) ?? Promise.resolve()).then(async () => {
5225
5248
  try {
5226
- await persistTurn(loc, sessionId, turn);
5249
+ await persistTurn(store, loc, sessionId, turn);
5227
5250
  const count = (recordCounts.get(key) ?? 0) + 1;
5228
5251
  recordCounts.set(key, count);
5229
5252
  if (count % COMPACTION_CHECK_INTERVAL === 0) {
5230
- await appendCompactBoundaryRecord(loc, sessionId, {
5253
+ await appendCompactBoundaryRecord(store, loc, sessionId, {
5231
5254
  preTokens: 0,
5232
5255
  trigger: "auto"
5233
5256
  });
@@ -5250,10 +5273,10 @@ function persistTurnToTranscript(loc, sessionId, turn, onCompact) {
5250
5273
  );
5251
5274
  }
5252
5275
  async function hydrateSession(agentId, loc) {
5253
- const key = transcriptKey(loc.baseDir, loc.cwd, agentId);
5276
+ const key = transcriptKey(loc.cwd, agentId);
5254
5277
  if (hydratedKeys.has(key)) return;
5255
5278
  hydratedKeys.add(key);
5256
- const persisted = await readSessionMessages(loc.baseDir, loc.cwd, agentId);
5279
+ const persisted = await readSessionMessages(loc.store, agentId);
5257
5280
  if (persisted.length === 0) return;
5258
5281
  if (!sessions.has(agentId) || sessions.get(agentId)?.length === 0) {
5259
5282
  sessions.set(agentId, persisted);
@@ -5288,7 +5311,7 @@ async function runPostRunLifecycle(inputs) {
5288
5311
  userText,
5289
5312
  agentId,
5290
5313
  workspaceCwd,
5291
- baseDir,
5314
+ sessionStore,
5292
5315
  model,
5293
5316
  onRunEvent,
5294
5317
  hooksExecutor,
@@ -5307,7 +5330,8 @@ async function runPostRunLifecycle(inputs) {
5307
5330
  }
5308
5331
  const conversation = await safeConversation(run);
5309
5332
  persistTurnToTranscript(
5310
- { baseDir, cwd: workspaceCwd, agentId, model },
5333
+ sessionStore,
5334
+ { cwd: workspaceCwd, agentId, model },
5311
5335
  agentId,
5312
5336
  { userText, conversation },
5313
5337
  onRunEvent !== void 0 ? () => emitRunEvent(onRunEvent, { type: "compact_boundary", trigger: "auto" }) : void 0
@@ -17914,6 +17938,7 @@ var init_local_agent = __esm({
17914
17938
  init_errors();
17915
17939
  init_ids();
17916
17940
  init_cwd_mutex();
17941
+ init_fs_session_store();
17917
17942
  init_session_transcript();
17918
17943
  init_store();
17919
17944
  init_manager();
@@ -17957,6 +17982,12 @@ var init_local_agent = __esm({
17957
17982
  * `~/.theokit`; set `local.baseDir: "~/.claude"` for Claude Code CLI interop.
17958
17983
  */
17959
17984
  transcriptBaseDir;
17985
+ /**
17986
+ * SE41 — the session record store. Defaults to the FS transcript store (byte-
17987
+ * identical to SE40); `local.sessionStore` injects an external store (Postgres /
17988
+ * Redis / KV) so resume works on serverless (ephemeral FS) and multi-host.
17989
+ */
17990
+ sessionStore;
17960
17991
  /**
17961
17992
  * D319: lifecycle AbortController fired on `dispose()`. Composed with the
17962
17993
  * caller's `SendOptions.signal` via `anySignal` so the LLM `fetch()`
@@ -17999,6 +18030,7 @@ var init_local_agent = __esm({
17999
18030
  this.options = options;
18000
18031
  this.workspaceCwd = resolveCwd(options.local?.cwd);
18001
18032
  this.transcriptBaseDir = resolveBaseDir(options.local?.baseDir);
18033
+ this.sessionStore = options.local?.sessionStore ?? new FsSessionStore({ baseDir: this.transcriptBaseDir, cwd: this.workspaceCwd });
18002
18034
  this.settingSourcesIncludeProject = includesSetting(options, "project");
18003
18035
  this.settingSourcesIncludePlugins = includesSetting(options, "plugins");
18004
18036
  const sub = bootstrapSubmanagers({
@@ -18046,7 +18078,7 @@ var init_local_agent = __esm({
18046
18078
  this.settingSourcesIncludeProject,
18047
18079
  this.options.agents
18048
18080
  );
18049
- await hydrateSession(this.agentId, { baseDir: this.transcriptBaseDir, cwd: this.workspaceCwd });
18081
+ await hydrateSession(this.agentId, { store: this.sessionStore, cwd: this.workspaceCwd });
18050
18082
  await this.personalityStore.hydrate(this.agentId);
18051
18083
  }
18052
18084
  /** T4.2 — expose PluginManager so agent-loop can fire pre_tool_call hooks. @internal */
@@ -18102,7 +18134,7 @@ var init_local_agent = __esm({
18102
18134
  userText,
18103
18135
  agentId: this.agentId,
18104
18136
  workspaceCwd: this.workspaceCwd,
18105
- baseDir: this.transcriptBaseDir,
18137
+ sessionStore: this.sessionStore,
18106
18138
  model: this.model?.id ?? "unknown",
18107
18139
  ...options.onRunEvent !== void 0 ? { onRunEvent: options.onRunEvent } : {},
18108
18140
  hooksExecutor: this.hooksExecutor,