@theokit/sdk 4.0.2 → 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/a2a/index.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
@@ -17755,6 +17779,7 @@ var init_local_agent = __esm({
17755
17779
  init_errors();
17756
17780
  init_ids();
17757
17781
  init_cwd_mutex();
17782
+ init_fs_session_store();
17758
17783
  init_session_transcript();
17759
17784
  init_store();
17760
17785
  init_manager();
@@ -17798,6 +17823,12 @@ var init_local_agent = __esm({
17798
17823
  * `~/.theokit`; set `local.baseDir: "~/.claude"` for Claude Code CLI interop.
17799
17824
  */
17800
17825
  transcriptBaseDir;
17826
+ /**
17827
+ * SE41 — the session record store. Defaults to the FS transcript store (byte-
17828
+ * identical to SE40); `local.sessionStore` injects an external store (Postgres /
17829
+ * Redis / KV) so resume works on serverless (ephemeral FS) and multi-host.
17830
+ */
17831
+ sessionStore;
17801
17832
  /**
17802
17833
  * D319: lifecycle AbortController fired on `dispose()`. Composed with the
17803
17834
  * caller's `SendOptions.signal` via `anySignal` so the LLM `fetch()`
@@ -17840,6 +17871,7 @@ var init_local_agent = __esm({
17840
17871
  this.options = options;
17841
17872
  this.workspaceCwd = resolveCwd(options.local?.cwd);
17842
17873
  this.transcriptBaseDir = resolveBaseDir(options.local?.baseDir);
17874
+ this.sessionStore = options.local?.sessionStore ?? new FsSessionStore({ baseDir: this.transcriptBaseDir, cwd: this.workspaceCwd });
17843
17875
  this.settingSourcesIncludeProject = includesSetting(options, "project");
17844
17876
  this.settingSourcesIncludePlugins = includesSetting(options, "plugins");
17845
17877
  const sub = bootstrapSubmanagers({
@@ -17887,7 +17919,7 @@ var init_local_agent = __esm({
17887
17919
  this.settingSourcesIncludeProject,
17888
17920
  this.options.agents
17889
17921
  );
17890
- await hydrateSession(this.agentId, { baseDir: this.transcriptBaseDir, cwd: this.workspaceCwd });
17922
+ await hydrateSession(this.agentId, { store: this.sessionStore, cwd: this.workspaceCwd });
17891
17923
  await this.personalityStore.hydrate(this.agentId);
17892
17924
  }
17893
17925
  /** T4.2 — expose PluginManager so agent-loop can fire pre_tool_call hooks. @internal */
@@ -17943,7 +17975,7 @@ var init_local_agent = __esm({
17943
17975
  userText,
17944
17976
  agentId: this.agentId,
17945
17977
  workspaceCwd: this.workspaceCwd,
17946
- baseDir: this.transcriptBaseDir,
17978
+ sessionStore: this.sessionStore,
17947
17979
  model: this.model?.id ?? "unknown",
17948
17980
  ...options.onRunEvent !== void 0 ? { onRunEvent: options.onRunEvent } : {},
17949
17981
  hooksExecutor: this.hooksExecutor,