agent-shim 8.13.0 → 8.14.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/cli.cjs CHANGED
@@ -61710,7 +61710,7 @@ function registerIdentityCommand(program2, deps) {
61710
61710
  // package.json
61711
61711
  var package_default = {
61712
61712
  name: "agent-shim",
61713
- version: "8.13.0",
61713
+ version: "8.14.0",
61714
61714
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61715
61715
  license: "MIT",
61716
61716
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -71793,31 +71793,54 @@ function credentialOfJson(parsed) {
71793
71793
  anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
71794
71794
  };
71795
71795
  }
71796
+ function sequenceNumOfJson(value2) {
71797
+ return typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : void 0;
71798
+ }
71796
71799
  function createRcCredentialStore(fs19, dir) {
71797
71800
  fs19.mkdirPrivate(dir);
71798
71801
  const fileFor = (sessionId) => import_node_path32.default.join(dir, `${sessionId}.json`);
71802
+ const readParsed = (sessionId) => {
71803
+ const raw = fs19.readFileUtf8(fileFor(sessionId));
71804
+ if (raw === void 0) {
71805
+ return void 0;
71806
+ }
71807
+ let parsed;
71808
+ try {
71809
+ parsed = JSON.parse(raw);
71810
+ } catch {
71811
+ return void 0;
71812
+ }
71813
+ return isRecord4(parsed) ? parsed : void 0;
71814
+ };
71799
71815
  return {
71800
71816
  read: (sessionId) => {
71801
71817
  if (!isSafeSessionId(sessionId)) {
71802
71818
  return void 0;
71803
71819
  }
71804
- const raw = fs19.readFileUtf8(fileFor(sessionId));
71805
- if (raw === void 0) {
71806
- return void 0;
71820
+ return credentialOfJson(readParsed(sessionId));
71821
+ },
71822
+ write: (sessionId, credential) => {
71823
+ if (!isSafeSessionId(sessionId)) {
71824
+ return;
71807
71825
  }
71808
- let parsed;
71809
- try {
71810
- parsed = JSON.parse(raw);
71811
- } catch {
71826
+ const cursor3 = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
71827
+ const record2 = { ...credential, ...cursor3 === void 0 ? {} : { highestSequenceNum: cursor3 } };
71828
+ fs19.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
71829
+ `);
71830
+ },
71831
+ readCursor: (sessionId) => {
71832
+ if (!isSafeSessionId(sessionId)) {
71812
71833
  return void 0;
71813
71834
  }
71814
- return credentialOfJson(parsed);
71835
+ return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
71815
71836
  },
71816
- write: (sessionId, credential) => {
71837
+ writeCursor: (sessionId, highestSequenceNum) => {
71817
71838
  if (!isSafeSessionId(sessionId)) {
71818
71839
  return;
71819
71840
  }
71820
- fs19.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(credential, null, 2)}
71841
+ const credential = credentialOfJson(readParsed(sessionId));
71842
+ const record2 = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
71843
+ fs19.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
71821
71844
  `);
71822
71845
  },
71823
71846
  remove: (sessionId) => {
@@ -72643,6 +72666,18 @@ function createRcEventFanout() {
72643
72666
  }
72644
72667
  function createRcStreamHub(deps) {
72645
72668
  const attachments = /* @__PURE__ */ new Map();
72669
+ const lastSavedSequenceNums = /* @__PURE__ */ new Map();
72670
+ const persistCursor = (sessionId) => {
72671
+ if (deps.saveSequenceNum === void 0) {
72672
+ return;
72673
+ }
72674
+ const cursor3 = deps.sequenceNumOf(sessionId);
72675
+ if (cursor3 === void 0 || cursor3 === lastSavedSequenceNums.get(sessionId)) {
72676
+ return;
72677
+ }
72678
+ deps.saveSequenceNum(sessionId, cursor3);
72679
+ lastSavedSequenceNums.set(sessionId, cursor3);
72680
+ };
72646
72681
  const birth = (sessionId) => {
72647
72682
  const controller = new AbortController();
72648
72683
  let stopped = false;
@@ -72679,7 +72714,7 @@ function createRcStreamHub(deps) {
72679
72714
  if (!isSuccessful3(presence.status)) {
72680
72715
  throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
72681
72716
  }
72682
- const cursor3 = deps.sequenceNumOf(attachment.sessionId);
72717
+ const cursor3 = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
72683
72718
  const resume = cursor3 === void 0 ? void 0 : { fromSequenceNum: cursor3 };
72684
72719
  const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
72685
72720
  if (answer.status === HTTP_STATUS.unauthorized) {
@@ -72713,6 +72748,7 @@ function createRcStreamHub(deps) {
72713
72748
  const credential = deps.credentialOf(attachment.sessionId);
72714
72749
  if (!live || credential?.authorization === void 0) {
72715
72750
  deps.log?.(`rc stream ${attachment.sessionId}: the attachment ended (${live ? "the session's client credential is no longer known" : "the tracker gave up on the session"})`);
72751
+ persistCursor(attachment.sessionId);
72716
72752
  if (attachments.get(attachment.sessionId) === attachment) {
72717
72753
  attachments.delete(attachment.sessionId);
72718
72754
  }
@@ -72722,6 +72758,7 @@ function createRcStreamHub(deps) {
72722
72758
  try {
72723
72759
  outcome = await attempt(attachment, credential);
72724
72760
  } catch (error62) {
72761
+ persistCursor(attachment.sessionId);
72725
72762
  if (attachment.isStopped()) {
72726
72763
  return;
72727
72764
  }
@@ -72734,6 +72771,7 @@ function createRcStreamHub(deps) {
72734
72771
  return;
72735
72772
  }
72736
72773
  if (outcome === "dropped") {
72774
+ persistCursor(attachment.sessionId);
72737
72775
  deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
72738
72776
  retriedUnauthorized = false;
72739
72777
  continue;
@@ -72772,6 +72810,7 @@ function createRcStreamHub(deps) {
72772
72810
  },
72773
72811
  close: () => {
72774
72812
  for (const attachment of attachments.values()) {
72813
+ persistCursor(attachment.sessionId);
72775
72814
  attachment.stop();
72776
72815
  }
72777
72816
  attachments.clear();
@@ -73107,7 +73146,8 @@ function realFrontDoorSupervisorPorts(paths) {
73107
73146
  credentialRecord: () => readRcSelfHostRecord(realFarmFs, paths.frontdoorDir),
73108
73147
  log
73109
73148
  }) : void 0;
73110
- const rcTracker = createRcSessionTracker({ now: () => Date.now(), idleMs: RC_IDLE_EXPIRY_MS, credentialStore: createRcCredentialStore(realFarmFs, paths.frontdoorRcCredentialsDir) });
73149
+ const rcCredentialStore = createRcCredentialStore(realFarmFs, paths.frontdoorRcCredentialsDir);
73150
+ const rcTracker = createRcSessionTracker({ now: () => Date.now(), idleMs: RC_IDLE_EXPIRY_MS, credentialStore: rcCredentialStore });
73111
73151
  const rcFanout = createRcEventFanout();
73112
73152
  const rcHub = createRcStreamHub({
73113
73153
  now: () => Date.now(),
@@ -73115,6 +73155,8 @@ function realFrontDoorSupervisorPorts(paths) {
73115
73155
  trackedSessions: () => rcTracker.list().map((session) => session.id),
73116
73156
  fileStreamEvent: rcTracker.fileStreamEvent,
73117
73157
  sequenceNumOf: rcTracker.sequenceNumOf,
73158
+ storedSequenceNumOf: rcCredentialStore.readCursor,
73159
+ saveSequenceNum: rcCredentialStore.writeCursor,
73118
73160
  dial: rcSelfHost ? lateRcStreamDial(rcSelfHostDialTarget) : realRcStreamDial(),
73119
73161
  fanout: rcFanout,
73120
73162
  newClientId: import_node_crypto12.randomUUID,
package/dist/index.cjs CHANGED
@@ -4941,31 +4941,54 @@ function credentialOfJson(parsed) {
4941
4941
  anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
4942
4942
  };
4943
4943
  }
4944
+ function sequenceNumOfJson(value) {
4945
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
4946
+ }
4944
4947
  function createRcCredentialStore(fs12, dir) {
4945
4948
  fs12.mkdirPrivate(dir);
4946
4949
  const fileFor = (sessionId) => import_node_path9.default.join(dir, `${sessionId}.json`);
4950
+ const readParsed = (sessionId) => {
4951
+ const raw = fs12.readFileUtf8(fileFor(sessionId));
4952
+ if (raw === void 0) {
4953
+ return void 0;
4954
+ }
4955
+ let parsed;
4956
+ try {
4957
+ parsed = JSON.parse(raw);
4958
+ } catch {
4959
+ return void 0;
4960
+ }
4961
+ return isRecord3(parsed) ? parsed : void 0;
4962
+ };
4947
4963
  return {
4948
4964
  read: (sessionId) => {
4949
4965
  if (!isSafeSessionId(sessionId)) {
4950
4966
  return void 0;
4951
4967
  }
4952
- const raw = fs12.readFileUtf8(fileFor(sessionId));
4953
- if (raw === void 0) {
4954
- return void 0;
4968
+ return credentialOfJson(readParsed(sessionId));
4969
+ },
4970
+ write: (sessionId, credential) => {
4971
+ if (!isSafeSessionId(sessionId)) {
4972
+ return;
4955
4973
  }
4956
- let parsed;
4957
- try {
4958
- parsed = JSON.parse(raw);
4959
- } catch {
4974
+ const cursor = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
4975
+ const record = { ...credential, ...cursor === void 0 ? {} : { highestSequenceNum: cursor } };
4976
+ fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
4977
+ `);
4978
+ },
4979
+ readCursor: (sessionId) => {
4980
+ if (!isSafeSessionId(sessionId)) {
4960
4981
  return void 0;
4961
4982
  }
4962
- return credentialOfJson(parsed);
4983
+ return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
4963
4984
  },
4964
- write: (sessionId, credential) => {
4985
+ writeCursor: (sessionId, highestSequenceNum) => {
4965
4986
  if (!isSafeSessionId(sessionId)) {
4966
4987
  return;
4967
4988
  }
4968
- fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(credential, null, 2)}
4989
+ const credential = credentialOfJson(readParsed(sessionId));
4990
+ const record = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
4991
+ fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
4969
4992
  `);
4970
4993
  },
4971
4994
  remove: (sessionId) => {
@@ -5300,6 +5323,18 @@ function createRcEventFanout() {
5300
5323
  }
5301
5324
  function createRcStreamHub(deps) {
5302
5325
  const attachments = /* @__PURE__ */ new Map();
5326
+ const lastSavedSequenceNums = /* @__PURE__ */ new Map();
5327
+ const persistCursor = (sessionId) => {
5328
+ if (deps.saveSequenceNum === void 0) {
5329
+ return;
5330
+ }
5331
+ const cursor = deps.sequenceNumOf(sessionId);
5332
+ if (cursor === void 0 || cursor === lastSavedSequenceNums.get(sessionId)) {
5333
+ return;
5334
+ }
5335
+ deps.saveSequenceNum(sessionId, cursor);
5336
+ lastSavedSequenceNums.set(sessionId, cursor);
5337
+ };
5303
5338
  const birth = (sessionId) => {
5304
5339
  const controller = new AbortController();
5305
5340
  let stopped = false;
@@ -5336,7 +5371,7 @@ function createRcStreamHub(deps) {
5336
5371
  if (!isSuccessful3(presence.status)) {
5337
5372
  throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
5338
5373
  }
5339
- const cursor = deps.sequenceNumOf(attachment.sessionId);
5374
+ const cursor = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
5340
5375
  const resume = cursor === void 0 ? void 0 : { fromSequenceNum: cursor };
5341
5376
  const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
5342
5377
  if (answer.status === HTTP_STATUS.unauthorized) {
@@ -5370,6 +5405,7 @@ function createRcStreamHub(deps) {
5370
5405
  const credential = deps.credentialOf(attachment.sessionId);
5371
5406
  if (!live || credential?.authorization === void 0) {
5372
5407
  deps.log?.(`rc stream ${attachment.sessionId}: the attachment ended (${live ? "the session's client credential is no longer known" : "the tracker gave up on the session"})`);
5408
+ persistCursor(attachment.sessionId);
5373
5409
  if (attachments.get(attachment.sessionId) === attachment) {
5374
5410
  attachments.delete(attachment.sessionId);
5375
5411
  }
@@ -5379,6 +5415,7 @@ function createRcStreamHub(deps) {
5379
5415
  try {
5380
5416
  outcome = await attempt(attachment, credential);
5381
5417
  } catch (error) {
5418
+ persistCursor(attachment.sessionId);
5382
5419
  if (attachment.isStopped()) {
5383
5420
  return;
5384
5421
  }
@@ -5391,6 +5428,7 @@ function createRcStreamHub(deps) {
5391
5428
  return;
5392
5429
  }
5393
5430
  if (outcome === "dropped") {
5431
+ persistCursor(attachment.sessionId);
5394
5432
  deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
5395
5433
  retriedUnauthorized = false;
5396
5434
  continue;
@@ -5429,6 +5467,7 @@ function createRcStreamHub(deps) {
5429
5467
  },
5430
5468
  close: () => {
5431
5469
  for (const attachment of attachments.values()) {
5470
+ persistCursor(attachment.sessionId);
5432
5471
  attachment.stop();
5433
5472
  }
5434
5473
  attachments.clear();
package/dist/index.mjs CHANGED
@@ -4711,31 +4711,54 @@ function credentialOfJson(parsed) {
4711
4711
  anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
4712
4712
  };
4713
4713
  }
4714
+ function sequenceNumOfJson(value) {
4715
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
4716
+ }
4714
4717
  function createRcCredentialStore(fs12, dir) {
4715
4718
  fs12.mkdirPrivate(dir);
4716
4719
  const fileFor = (sessionId) => path9.join(dir, `${sessionId}.json`);
4720
+ const readParsed = (sessionId) => {
4721
+ const raw = fs12.readFileUtf8(fileFor(sessionId));
4722
+ if (raw === void 0) {
4723
+ return void 0;
4724
+ }
4725
+ let parsed;
4726
+ try {
4727
+ parsed = JSON.parse(raw);
4728
+ } catch {
4729
+ return void 0;
4730
+ }
4731
+ return isRecord3(parsed) ? parsed : void 0;
4732
+ };
4717
4733
  return {
4718
4734
  read: (sessionId) => {
4719
4735
  if (!isSafeSessionId(sessionId)) {
4720
4736
  return void 0;
4721
4737
  }
4722
- const raw = fs12.readFileUtf8(fileFor(sessionId));
4723
- if (raw === void 0) {
4724
- return void 0;
4738
+ return credentialOfJson(readParsed(sessionId));
4739
+ },
4740
+ write: (sessionId, credential) => {
4741
+ if (!isSafeSessionId(sessionId)) {
4742
+ return;
4725
4743
  }
4726
- let parsed;
4727
- try {
4728
- parsed = JSON.parse(raw);
4729
- } catch {
4744
+ const cursor = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
4745
+ const record = { ...credential, ...cursor === void 0 ? {} : { highestSequenceNum: cursor } };
4746
+ fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
4747
+ `);
4748
+ },
4749
+ readCursor: (sessionId) => {
4750
+ if (!isSafeSessionId(sessionId)) {
4730
4751
  return void 0;
4731
4752
  }
4732
- return credentialOfJson(parsed);
4753
+ return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
4733
4754
  },
4734
- write: (sessionId, credential) => {
4755
+ writeCursor: (sessionId, highestSequenceNum) => {
4735
4756
  if (!isSafeSessionId(sessionId)) {
4736
4757
  return;
4737
4758
  }
4738
- fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(credential, null, 2)}
4759
+ const credential = credentialOfJson(readParsed(sessionId));
4760
+ const record = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
4761
+ fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
4739
4762
  `);
4740
4763
  },
4741
4764
  remove: (sessionId) => {
@@ -5070,6 +5093,18 @@ function createRcEventFanout() {
5070
5093
  }
5071
5094
  function createRcStreamHub(deps) {
5072
5095
  const attachments = /* @__PURE__ */ new Map();
5096
+ const lastSavedSequenceNums = /* @__PURE__ */ new Map();
5097
+ const persistCursor = (sessionId) => {
5098
+ if (deps.saveSequenceNum === void 0) {
5099
+ return;
5100
+ }
5101
+ const cursor = deps.sequenceNumOf(sessionId);
5102
+ if (cursor === void 0 || cursor === lastSavedSequenceNums.get(sessionId)) {
5103
+ return;
5104
+ }
5105
+ deps.saveSequenceNum(sessionId, cursor);
5106
+ lastSavedSequenceNums.set(sessionId, cursor);
5107
+ };
5073
5108
  const birth = (sessionId) => {
5074
5109
  const controller = new AbortController();
5075
5110
  let stopped = false;
@@ -5106,7 +5141,7 @@ function createRcStreamHub(deps) {
5106
5141
  if (!isSuccessful3(presence.status)) {
5107
5142
  throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
5108
5143
  }
5109
- const cursor = deps.sequenceNumOf(attachment.sessionId);
5144
+ const cursor = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
5110
5145
  const resume = cursor === void 0 ? void 0 : { fromSequenceNum: cursor };
5111
5146
  const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
5112
5147
  if (answer.status === HTTP_STATUS.unauthorized) {
@@ -5140,6 +5175,7 @@ function createRcStreamHub(deps) {
5140
5175
  const credential = deps.credentialOf(attachment.sessionId);
5141
5176
  if (!live || credential?.authorization === void 0) {
5142
5177
  deps.log?.(`rc stream ${attachment.sessionId}: the attachment ended (${live ? "the session's client credential is no longer known" : "the tracker gave up on the session"})`);
5178
+ persistCursor(attachment.sessionId);
5143
5179
  if (attachments.get(attachment.sessionId) === attachment) {
5144
5180
  attachments.delete(attachment.sessionId);
5145
5181
  }
@@ -5149,6 +5185,7 @@ function createRcStreamHub(deps) {
5149
5185
  try {
5150
5186
  outcome = await attempt(attachment, credential);
5151
5187
  } catch (error) {
5188
+ persistCursor(attachment.sessionId);
5152
5189
  if (attachment.isStopped()) {
5153
5190
  return;
5154
5191
  }
@@ -5161,6 +5198,7 @@ function createRcStreamHub(deps) {
5161
5198
  return;
5162
5199
  }
5163
5200
  if (outcome === "dropped") {
5201
+ persistCursor(attachment.sessionId);
5164
5202
  deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
5165
5203
  retriedUnauthorized = false;
5166
5204
  continue;
@@ -5199,6 +5237,7 @@ function createRcStreamHub(deps) {
5199
5237
  },
5200
5238
  close: () => {
5201
5239
  for (const attachment of attachments.values()) {
5240
+ persistCursor(attachment.sessionId);
5202
5241
  attachment.stop();
5203
5242
  }
5204
5243
  attachments.clear();
@@ -1648,18 +1648,22 @@ declare function realConnectCertStore(paths: {
1648
1648
  }): ConnectCertStore;
1649
1649
 
1650
1650
  /**
1651
- * The persisted half of the tracker's client credential: the OAuth-kind bearer and protocol headers a Remote Control session's client-half calls were last observed with, written per session id so a later door generation can still attach to a session whose create crossed an earlier one.
1651
+ * The persisted half of the tracker's client credential and of its sequence cursor: the OAuth-kind bearer and protocol headers a Remote Control session's client-half calls were last observed with, and the highest sequence number the door's stream attachment had confirmed when its last attachment boundary passed, written per session id so a later door generation can still attach to a session whose create crossed an earlier one and resume its read stream from where the last generation left off.
1652
1652
  *
1653
- * The tracker holds the credential in memory only, and the worker's recurring calls carry the worker JWT rather than the OAuth bearer, so a door that starts after the session's create (an idle shutdown and restart, a release upgrade, a reboot) would never learn it: nothing the door can observe re-states it until some client-half call happens to cross. Since the session outlives any one door process, the credential must outlive the process too. It is the same secret the identity's own store already holds on the same machine, so persisting it adds a second copy inside the same trust boundary, at the same owner-only protection: one JSON file per session, mode 0600, under the door's own front-door directory.
1653
+ * The tracker holds both in memory only, and the worker's recurring calls carry the worker JWT rather than the OAuth bearer, so a door that starts after the session's create (an idle shutdown and restart, a release upgrade, a reboot) would never learn the credential: nothing the door can observe re-states it until some client-half call happens to cross. Since the session outlives any one door process, the credential must outlive the process too, and so must the cursor, or every restarted generation would re-read the stream from its head. The credential is the same secret the identity's own store already holds on the same machine, so persisting it adds a second copy inside the same trust boundary, at the same owner-only protection: one JSON file per session, mode 0600, under the door's own front-door directory, with the cursor (no secret, but the same file and the same lifecycle) beside it. The native client does exactly this: its `bridge-session` transcript entry carries `lastSequenceNum` beside the session id.
1654
1654
  *
1655
- * A file is removed when the tracker observes the session closing (the conflict header, or an accepted archive). Nothing else removes it: the tracker's idle sweep is not a close (a quiet session is not a dead one, and a resumed session must re-attach), and the credential's own expiry is the protocol's to judge, as a 401 on the dial the hub already retries and backs off from. A session that ends without an observable close leaves its file behind, overwritten only if the same id is ever observed again.
1655
+ * Each half is written at its own boundaries (the credential whenever an OAuth-kind bearer is observed, the cursor when the stream hub's attachment reaches a boundary), so each write preserves the other half, and neither is written per stream event. A file is removed when the tracker observes the session closing (the conflict header, or an accepted archive). Nothing else removes it: the tracker's idle sweep is not a close (a quiet session is not a dead one, and a resumed session must re-attach), and the credential's own expiry is the protocol's to judge, as a 401 on the dial the hub already retries and backs off from. A session that ends without an observable close leaves its file behind, overwritten only if the same id is ever observed again.
1656
1656
  */
1657
1657
  interface RcCredentialStore {
1658
1658
  /** The session's last observed client credential, or undefined when none was ever persisted for it (or what was persisted does not parse). */
1659
1659
  readonly read: (sessionId: string) => RcObservedCredential | undefined;
1660
- /** Persists the session's client credential, replacing whatever was held for it. */
1660
+ /** Persists the session's client credential, replacing whatever was held for it and keeping the session's persisted cursor. */
1661
1661
  readonly write: (sessionId: string, credential: RcObservedCredential) => void;
1662
- /** Forgets the session's persisted credential, succeeding silently when none was held. */
1662
+ /** The session's persisted sequence cursor, or undefined when none was ever persisted for it (or what was persisted is not a usable number). */
1663
+ readonly readCursor: (sessionId: string) => number | undefined;
1664
+ /** Persists the session's sequence cursor, keeping whatever credential the same file holds. */
1665
+ readonly writeCursor: (sessionId: string, highestSequenceNum: number) => void;
1666
+ /** Forgets the session's persisted record, credential and cursor together, succeeding silently when none was held. */
1663
1667
  readonly remove: (sessionId: string) => void;
1664
1668
  }
1665
1669
  /** Creates the credential store over one directory: one owner-only JSON file per session id. */
@@ -2061,6 +2065,10 @@ interface RcStreamHubDeps {
2061
2065
  readonly fileStreamEvent: (sessionId: string, event: RcStreamEnvelope) => void;
2062
2066
  /** Reads the session's sequence cursor: the highest number the door has seen, from the stream or from its own confirmed writes. */
2063
2067
  readonly sequenceNumOf: (sessionId: string) => number | undefined;
2068
+ /** Reads the session's persisted sequence cursor, consulted only when the tracker's in-memory cursor is undefined at attach time (a door generation that started after the session's traffic, the same fresh-generation case the persisted credential solves for attaching at all), so that generation's first stream resumes where the last one left off instead of at the stream's head. */
2069
+ readonly storedSequenceNumOf?: (sessionId: string) => number | undefined;
2070
+ /** Persists the session's sequence cursor at an attachment boundary (a stream drop, a failed attempt, the attachment's end), never per stream event: the native client persists its own cursor at bridge-session boundaries, and so does this. */
2071
+ readonly saveSequenceNum?: (sessionId: string, sequenceNum: number) => void;
2064
2072
  readonly dial: RcStreamDial;
2065
2073
  readonly fanout: RcEventFanout;
2066
2074
  /** Mints the stable client id one session's attachment announces: a v4 UUID or better in production. Stable means it survives reconnects, so the host sees one client, not a new one per drop. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.13.0",
3
+ "version": "8.14.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",