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 +55 -13
- package/dist/index.cjs +50 -11
- package/dist/index.mjs +50 -11
- package/dist/types/index.d.ts +13 -5
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
71805
|
-
|
|
71806
|
-
|
|
71820
|
+
return credentialOfJson(readParsed(sessionId));
|
|
71821
|
+
},
|
|
71822
|
+
write: (sessionId, credential) => {
|
|
71823
|
+
if (!isSafeSessionId(sessionId)) {
|
|
71824
|
+
return;
|
|
71807
71825
|
}
|
|
71808
|
-
|
|
71809
|
-
|
|
71810
|
-
|
|
71811
|
-
|
|
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
|
|
71835
|
+
return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
71815
71836
|
},
|
|
71816
|
-
|
|
71837
|
+
writeCursor: (sessionId, highestSequenceNum) => {
|
|
71817
71838
|
if (!isSafeSessionId(sessionId)) {
|
|
71818
71839
|
return;
|
|
71819
71840
|
}
|
|
71820
|
-
|
|
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
|
|
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
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4968
|
+
return credentialOfJson(readParsed(sessionId));
|
|
4969
|
+
},
|
|
4970
|
+
write: (sessionId, credential) => {
|
|
4971
|
+
if (!isSafeSessionId(sessionId)) {
|
|
4972
|
+
return;
|
|
4955
4973
|
}
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
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
|
|
4983
|
+
return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
4963
4984
|
},
|
|
4964
|
-
|
|
4985
|
+
writeCursor: (sessionId, highestSequenceNum) => {
|
|
4965
4986
|
if (!isSafeSessionId(sessionId)) {
|
|
4966
4987
|
return;
|
|
4967
4988
|
}
|
|
4968
|
-
|
|
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
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4738
|
+
return credentialOfJson(readParsed(sessionId));
|
|
4739
|
+
},
|
|
4740
|
+
write: (sessionId, credential) => {
|
|
4741
|
+
if (!isSafeSessionId(sessionId)) {
|
|
4742
|
+
return;
|
|
4725
4743
|
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
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
|
|
4753
|
+
return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
4733
4754
|
},
|
|
4734
|
-
|
|
4755
|
+
writeCursor: (sessionId, highestSequenceNum) => {
|
|
4735
4756
|
if (!isSafeSessionId(sessionId)) {
|
|
4736
4757
|
return;
|
|
4737
4758
|
}
|
|
4738
|
-
|
|
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();
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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.
|
|
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>",
|