agent-shim 8.13.0 → 8.14.1
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 +58 -13
- package/dist/index.cjs +52 -11
- package/dist/index.mjs +52 -11
- package/dist/types/index.d.ts +17 -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.1",
|
|
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>",
|
|
@@ -65063,6 +65063,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
65063
65063
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
65064
65064
|
});
|
|
65065
65065
|
const fail = (message) => {
|
|
65066
|
+
ports.closeRcStreamHub();
|
|
65066
65067
|
writeFrontDoorState(fs19, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
65067
65068
|
fs19.removeRecursive(paths.frontdoorLockFile);
|
|
65068
65069
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
@@ -65123,6 +65124,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
65123
65124
|
await direct.close();
|
|
65124
65125
|
await connect3.close();
|
|
65125
65126
|
await http6.close();
|
|
65127
|
+
ports.closeRcStreamHub();
|
|
65126
65128
|
writeFrontDoorState(fs19, paths.frontdoorStateFile, sticky());
|
|
65127
65129
|
return 0;
|
|
65128
65130
|
}
|
|
@@ -71793,31 +71795,54 @@ function credentialOfJson(parsed) {
|
|
|
71793
71795
|
anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
|
|
71794
71796
|
};
|
|
71795
71797
|
}
|
|
71798
|
+
function sequenceNumOfJson(value2) {
|
|
71799
|
+
return typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : void 0;
|
|
71800
|
+
}
|
|
71796
71801
|
function createRcCredentialStore(fs19, dir) {
|
|
71797
71802
|
fs19.mkdirPrivate(dir);
|
|
71798
71803
|
const fileFor = (sessionId) => import_node_path32.default.join(dir, `${sessionId}.json`);
|
|
71804
|
+
const readParsed = (sessionId) => {
|
|
71805
|
+
const raw = fs19.readFileUtf8(fileFor(sessionId));
|
|
71806
|
+
if (raw === void 0) {
|
|
71807
|
+
return void 0;
|
|
71808
|
+
}
|
|
71809
|
+
let parsed;
|
|
71810
|
+
try {
|
|
71811
|
+
parsed = JSON.parse(raw);
|
|
71812
|
+
} catch {
|
|
71813
|
+
return void 0;
|
|
71814
|
+
}
|
|
71815
|
+
return isRecord4(parsed) ? parsed : void 0;
|
|
71816
|
+
};
|
|
71799
71817
|
return {
|
|
71800
71818
|
read: (sessionId) => {
|
|
71801
71819
|
if (!isSafeSessionId(sessionId)) {
|
|
71802
71820
|
return void 0;
|
|
71803
71821
|
}
|
|
71804
|
-
|
|
71805
|
-
|
|
71806
|
-
|
|
71822
|
+
return credentialOfJson(readParsed(sessionId));
|
|
71823
|
+
},
|
|
71824
|
+
write: (sessionId, credential) => {
|
|
71825
|
+
if (!isSafeSessionId(sessionId)) {
|
|
71826
|
+
return;
|
|
71807
71827
|
}
|
|
71808
|
-
|
|
71809
|
-
|
|
71810
|
-
|
|
71811
|
-
|
|
71828
|
+
const cursor3 = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
71829
|
+
const record2 = { ...credential, ...cursor3 === void 0 ? {} : { highestSequenceNum: cursor3 } };
|
|
71830
|
+
fs19.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
|
|
71831
|
+
`);
|
|
71832
|
+
},
|
|
71833
|
+
readCursor: (sessionId) => {
|
|
71834
|
+
if (!isSafeSessionId(sessionId)) {
|
|
71812
71835
|
return void 0;
|
|
71813
71836
|
}
|
|
71814
|
-
return
|
|
71837
|
+
return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
71815
71838
|
},
|
|
71816
|
-
|
|
71839
|
+
writeCursor: (sessionId, highestSequenceNum) => {
|
|
71817
71840
|
if (!isSafeSessionId(sessionId)) {
|
|
71818
71841
|
return;
|
|
71819
71842
|
}
|
|
71820
|
-
|
|
71843
|
+
const credential = credentialOfJson(readParsed(sessionId));
|
|
71844
|
+
const record2 = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
|
|
71845
|
+
fs19.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record2, null, 2)}
|
|
71821
71846
|
`);
|
|
71822
71847
|
},
|
|
71823
71848
|
remove: (sessionId) => {
|
|
@@ -72643,6 +72668,18 @@ function createRcEventFanout() {
|
|
|
72643
72668
|
}
|
|
72644
72669
|
function createRcStreamHub(deps) {
|
|
72645
72670
|
const attachments = /* @__PURE__ */ new Map();
|
|
72671
|
+
const lastSavedSequenceNums = /* @__PURE__ */ new Map();
|
|
72672
|
+
const persistCursor = (sessionId) => {
|
|
72673
|
+
if (deps.saveSequenceNum === void 0) {
|
|
72674
|
+
return;
|
|
72675
|
+
}
|
|
72676
|
+
const cursor3 = deps.sequenceNumOf(sessionId);
|
|
72677
|
+
if (cursor3 === void 0 || cursor3 === lastSavedSequenceNums.get(sessionId)) {
|
|
72678
|
+
return;
|
|
72679
|
+
}
|
|
72680
|
+
deps.saveSequenceNum(sessionId, cursor3);
|
|
72681
|
+
lastSavedSequenceNums.set(sessionId, cursor3);
|
|
72682
|
+
};
|
|
72646
72683
|
const birth = (sessionId) => {
|
|
72647
72684
|
const controller = new AbortController();
|
|
72648
72685
|
let stopped = false;
|
|
@@ -72679,7 +72716,7 @@ function createRcStreamHub(deps) {
|
|
|
72679
72716
|
if (!isSuccessful3(presence.status)) {
|
|
72680
72717
|
throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
|
|
72681
72718
|
}
|
|
72682
|
-
const cursor3 = deps.sequenceNumOf(attachment.sessionId);
|
|
72719
|
+
const cursor3 = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
|
|
72683
72720
|
const resume = cursor3 === void 0 ? void 0 : { fromSequenceNum: cursor3 };
|
|
72684
72721
|
const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
|
|
72685
72722
|
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
@@ -72713,6 +72750,7 @@ function createRcStreamHub(deps) {
|
|
|
72713
72750
|
const credential = deps.credentialOf(attachment.sessionId);
|
|
72714
72751
|
if (!live || credential?.authorization === void 0) {
|
|
72715
72752
|
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"})`);
|
|
72753
|
+
persistCursor(attachment.sessionId);
|
|
72716
72754
|
if (attachments.get(attachment.sessionId) === attachment) {
|
|
72717
72755
|
attachments.delete(attachment.sessionId);
|
|
72718
72756
|
}
|
|
@@ -72722,6 +72760,7 @@ function createRcStreamHub(deps) {
|
|
|
72722
72760
|
try {
|
|
72723
72761
|
outcome = await attempt(attachment, credential);
|
|
72724
72762
|
} catch (error62) {
|
|
72763
|
+
persistCursor(attachment.sessionId);
|
|
72725
72764
|
if (attachment.isStopped()) {
|
|
72726
72765
|
return;
|
|
72727
72766
|
}
|
|
@@ -72734,6 +72773,7 @@ function createRcStreamHub(deps) {
|
|
|
72734
72773
|
return;
|
|
72735
72774
|
}
|
|
72736
72775
|
if (outcome === "dropped") {
|
|
72776
|
+
persistCursor(attachment.sessionId);
|
|
72737
72777
|
deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
|
|
72738
72778
|
retriedUnauthorized = false;
|
|
72739
72779
|
continue;
|
|
@@ -72772,6 +72812,7 @@ function createRcStreamHub(deps) {
|
|
|
72772
72812
|
},
|
|
72773
72813
|
close: () => {
|
|
72774
72814
|
for (const attachment of attachments.values()) {
|
|
72815
|
+
persistCursor(attachment.sessionId);
|
|
72775
72816
|
attachment.stop();
|
|
72776
72817
|
}
|
|
72777
72818
|
attachments.clear();
|
|
@@ -73107,7 +73148,8 @@ function realFrontDoorSupervisorPorts(paths) {
|
|
|
73107
73148
|
credentialRecord: () => readRcSelfHostRecord(realFarmFs, paths.frontdoorDir),
|
|
73108
73149
|
log
|
|
73109
73150
|
}) : void 0;
|
|
73110
|
-
const
|
|
73151
|
+
const rcCredentialStore = createRcCredentialStore(realFarmFs, paths.frontdoorRcCredentialsDir);
|
|
73152
|
+
const rcTracker = createRcSessionTracker({ now: () => Date.now(), idleMs: RC_IDLE_EXPIRY_MS, credentialStore: rcCredentialStore });
|
|
73111
73153
|
const rcFanout = createRcEventFanout();
|
|
73112
73154
|
const rcHub = createRcStreamHub({
|
|
73113
73155
|
now: () => Date.now(),
|
|
@@ -73115,6 +73157,8 @@ function realFrontDoorSupervisorPorts(paths) {
|
|
|
73115
73157
|
trackedSessions: () => rcTracker.list().map((session) => session.id),
|
|
73116
73158
|
fileStreamEvent: rcTracker.fileStreamEvent,
|
|
73117
73159
|
sequenceNumOf: rcTracker.sequenceNumOf,
|
|
73160
|
+
storedSequenceNumOf: rcCredentialStore.readCursor,
|
|
73161
|
+
saveSequenceNum: rcCredentialStore.writeCursor,
|
|
73118
73162
|
dial: rcSelfHost ? lateRcStreamDial(rcSelfHostDialTarget) : realRcStreamDial(),
|
|
73119
73163
|
fanout: rcFanout,
|
|
73120
73164
|
newClientId: import_node_crypto12.randomUUID,
|
|
@@ -73282,6 +73326,7 @@ function realFrontDoorSupervisorPorts(paths) {
|
|
|
73282
73326
|
});
|
|
73283
73327
|
return { port: handle.port, close: handle.close };
|
|
73284
73328
|
},
|
|
73329
|
+
closeRcStreamHub: rcHub.close,
|
|
73285
73330
|
log: (line) => {
|
|
73286
73331
|
appendLog(paths, line);
|
|
73287
73332
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -2930,6 +2930,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2930
2930
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
2931
2931
|
});
|
|
2932
2932
|
const fail = (message) => {
|
|
2933
|
+
ports.closeRcStreamHub();
|
|
2933
2934
|
writeFrontDoorState(fs12, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2934
2935
|
fs12.removeRecursive(paths.frontdoorLockFile);
|
|
2935
2936
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
@@ -2990,6 +2991,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2990
2991
|
await direct.close();
|
|
2991
2992
|
await connect3.close();
|
|
2992
2993
|
await http2.close();
|
|
2994
|
+
ports.closeRcStreamHub();
|
|
2993
2995
|
writeFrontDoorState(fs12, paths.frontdoorStateFile, sticky());
|
|
2994
2996
|
return 0;
|
|
2995
2997
|
}
|
|
@@ -4941,31 +4943,54 @@ function credentialOfJson(parsed) {
|
|
|
4941
4943
|
anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
|
|
4942
4944
|
};
|
|
4943
4945
|
}
|
|
4946
|
+
function sequenceNumOfJson(value) {
|
|
4947
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
4948
|
+
}
|
|
4944
4949
|
function createRcCredentialStore(fs12, dir) {
|
|
4945
4950
|
fs12.mkdirPrivate(dir);
|
|
4946
4951
|
const fileFor = (sessionId) => import_node_path9.default.join(dir, `${sessionId}.json`);
|
|
4952
|
+
const readParsed = (sessionId) => {
|
|
4953
|
+
const raw = fs12.readFileUtf8(fileFor(sessionId));
|
|
4954
|
+
if (raw === void 0) {
|
|
4955
|
+
return void 0;
|
|
4956
|
+
}
|
|
4957
|
+
let parsed;
|
|
4958
|
+
try {
|
|
4959
|
+
parsed = JSON.parse(raw);
|
|
4960
|
+
} catch {
|
|
4961
|
+
return void 0;
|
|
4962
|
+
}
|
|
4963
|
+
return isRecord3(parsed) ? parsed : void 0;
|
|
4964
|
+
};
|
|
4947
4965
|
return {
|
|
4948
4966
|
read: (sessionId) => {
|
|
4949
4967
|
if (!isSafeSessionId(sessionId)) {
|
|
4950
4968
|
return void 0;
|
|
4951
4969
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4970
|
+
return credentialOfJson(readParsed(sessionId));
|
|
4971
|
+
},
|
|
4972
|
+
write: (sessionId, credential) => {
|
|
4973
|
+
if (!isSafeSessionId(sessionId)) {
|
|
4974
|
+
return;
|
|
4955
4975
|
}
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4976
|
+
const cursor = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
4977
|
+
const record = { ...credential, ...cursor === void 0 ? {} : { highestSequenceNum: cursor } };
|
|
4978
|
+
fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
|
|
4979
|
+
`);
|
|
4980
|
+
},
|
|
4981
|
+
readCursor: (sessionId) => {
|
|
4982
|
+
if (!isSafeSessionId(sessionId)) {
|
|
4960
4983
|
return void 0;
|
|
4961
4984
|
}
|
|
4962
|
-
return
|
|
4985
|
+
return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
4963
4986
|
},
|
|
4964
|
-
|
|
4987
|
+
writeCursor: (sessionId, highestSequenceNum) => {
|
|
4965
4988
|
if (!isSafeSessionId(sessionId)) {
|
|
4966
4989
|
return;
|
|
4967
4990
|
}
|
|
4968
|
-
|
|
4991
|
+
const credential = credentialOfJson(readParsed(sessionId));
|
|
4992
|
+
const record = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
|
|
4993
|
+
fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
|
|
4969
4994
|
`);
|
|
4970
4995
|
},
|
|
4971
4996
|
remove: (sessionId) => {
|
|
@@ -5300,6 +5325,18 @@ function createRcEventFanout() {
|
|
|
5300
5325
|
}
|
|
5301
5326
|
function createRcStreamHub(deps) {
|
|
5302
5327
|
const attachments = /* @__PURE__ */ new Map();
|
|
5328
|
+
const lastSavedSequenceNums = /* @__PURE__ */ new Map();
|
|
5329
|
+
const persistCursor = (sessionId) => {
|
|
5330
|
+
if (deps.saveSequenceNum === void 0) {
|
|
5331
|
+
return;
|
|
5332
|
+
}
|
|
5333
|
+
const cursor = deps.sequenceNumOf(sessionId);
|
|
5334
|
+
if (cursor === void 0 || cursor === lastSavedSequenceNums.get(sessionId)) {
|
|
5335
|
+
return;
|
|
5336
|
+
}
|
|
5337
|
+
deps.saveSequenceNum(sessionId, cursor);
|
|
5338
|
+
lastSavedSequenceNums.set(sessionId, cursor);
|
|
5339
|
+
};
|
|
5303
5340
|
const birth = (sessionId) => {
|
|
5304
5341
|
const controller = new AbortController();
|
|
5305
5342
|
let stopped = false;
|
|
@@ -5336,7 +5373,7 @@ function createRcStreamHub(deps) {
|
|
|
5336
5373
|
if (!isSuccessful3(presence.status)) {
|
|
5337
5374
|
throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
|
|
5338
5375
|
}
|
|
5339
|
-
const cursor = deps.sequenceNumOf(attachment.sessionId);
|
|
5376
|
+
const cursor = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
|
|
5340
5377
|
const resume = cursor === void 0 ? void 0 : { fromSequenceNum: cursor };
|
|
5341
5378
|
const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
|
|
5342
5379
|
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
@@ -5370,6 +5407,7 @@ function createRcStreamHub(deps) {
|
|
|
5370
5407
|
const credential = deps.credentialOf(attachment.sessionId);
|
|
5371
5408
|
if (!live || credential?.authorization === void 0) {
|
|
5372
5409
|
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"})`);
|
|
5410
|
+
persistCursor(attachment.sessionId);
|
|
5373
5411
|
if (attachments.get(attachment.sessionId) === attachment) {
|
|
5374
5412
|
attachments.delete(attachment.sessionId);
|
|
5375
5413
|
}
|
|
@@ -5379,6 +5417,7 @@ function createRcStreamHub(deps) {
|
|
|
5379
5417
|
try {
|
|
5380
5418
|
outcome = await attempt(attachment, credential);
|
|
5381
5419
|
} catch (error) {
|
|
5420
|
+
persistCursor(attachment.sessionId);
|
|
5382
5421
|
if (attachment.isStopped()) {
|
|
5383
5422
|
return;
|
|
5384
5423
|
}
|
|
@@ -5391,6 +5430,7 @@ function createRcStreamHub(deps) {
|
|
|
5391
5430
|
return;
|
|
5392
5431
|
}
|
|
5393
5432
|
if (outcome === "dropped") {
|
|
5433
|
+
persistCursor(attachment.sessionId);
|
|
5394
5434
|
deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
|
|
5395
5435
|
retriedUnauthorized = false;
|
|
5396
5436
|
continue;
|
|
@@ -5429,6 +5469,7 @@ function createRcStreamHub(deps) {
|
|
|
5429
5469
|
},
|
|
5430
5470
|
close: () => {
|
|
5431
5471
|
for (const attachment of attachments.values()) {
|
|
5472
|
+
persistCursor(attachment.sessionId);
|
|
5432
5473
|
attachment.stop();
|
|
5433
5474
|
}
|
|
5434
5475
|
attachments.clear();
|
package/dist/index.mjs
CHANGED
|
@@ -2700,6 +2700,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2700
2700
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
2701
2701
|
});
|
|
2702
2702
|
const fail = (message) => {
|
|
2703
|
+
ports.closeRcStreamHub();
|
|
2703
2704
|
writeFrontDoorState(fs12, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2704
2705
|
fs12.removeRecursive(paths.frontdoorLockFile);
|
|
2705
2706
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
@@ -2760,6 +2761,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2760
2761
|
await direct.close();
|
|
2761
2762
|
await connect3.close();
|
|
2762
2763
|
await http2.close();
|
|
2764
|
+
ports.closeRcStreamHub();
|
|
2763
2765
|
writeFrontDoorState(fs12, paths.frontdoorStateFile, sticky());
|
|
2764
2766
|
return 0;
|
|
2765
2767
|
}
|
|
@@ -4711,31 +4713,54 @@ function credentialOfJson(parsed) {
|
|
|
4711
4713
|
anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
|
|
4712
4714
|
};
|
|
4713
4715
|
}
|
|
4716
|
+
function sequenceNumOfJson(value) {
|
|
4717
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
4718
|
+
}
|
|
4714
4719
|
function createRcCredentialStore(fs12, dir) {
|
|
4715
4720
|
fs12.mkdirPrivate(dir);
|
|
4716
4721
|
const fileFor = (sessionId) => path9.join(dir, `${sessionId}.json`);
|
|
4722
|
+
const readParsed = (sessionId) => {
|
|
4723
|
+
const raw = fs12.readFileUtf8(fileFor(sessionId));
|
|
4724
|
+
if (raw === void 0) {
|
|
4725
|
+
return void 0;
|
|
4726
|
+
}
|
|
4727
|
+
let parsed;
|
|
4728
|
+
try {
|
|
4729
|
+
parsed = JSON.parse(raw);
|
|
4730
|
+
} catch {
|
|
4731
|
+
return void 0;
|
|
4732
|
+
}
|
|
4733
|
+
return isRecord3(parsed) ? parsed : void 0;
|
|
4734
|
+
};
|
|
4717
4735
|
return {
|
|
4718
4736
|
read: (sessionId) => {
|
|
4719
4737
|
if (!isSafeSessionId(sessionId)) {
|
|
4720
4738
|
return void 0;
|
|
4721
4739
|
}
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4740
|
+
return credentialOfJson(readParsed(sessionId));
|
|
4741
|
+
},
|
|
4742
|
+
write: (sessionId, credential) => {
|
|
4743
|
+
if (!isSafeSessionId(sessionId)) {
|
|
4744
|
+
return;
|
|
4725
4745
|
}
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4746
|
+
const cursor = sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
4747
|
+
const record = { ...credential, ...cursor === void 0 ? {} : { highestSequenceNum: cursor } };
|
|
4748
|
+
fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
|
|
4749
|
+
`);
|
|
4750
|
+
},
|
|
4751
|
+
readCursor: (sessionId) => {
|
|
4752
|
+
if (!isSafeSessionId(sessionId)) {
|
|
4730
4753
|
return void 0;
|
|
4731
4754
|
}
|
|
4732
|
-
return
|
|
4755
|
+
return sequenceNumOfJson(readParsed(sessionId)?.highestSequenceNum);
|
|
4733
4756
|
},
|
|
4734
|
-
|
|
4757
|
+
writeCursor: (sessionId, highestSequenceNum) => {
|
|
4735
4758
|
if (!isSafeSessionId(sessionId)) {
|
|
4736
4759
|
return;
|
|
4737
4760
|
}
|
|
4738
|
-
|
|
4761
|
+
const credential = credentialOfJson(readParsed(sessionId));
|
|
4762
|
+
const record = credential === void 0 ? { highestSequenceNum } : { ...credential, highestSequenceNum };
|
|
4763
|
+
fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(record, null, 2)}
|
|
4739
4764
|
`);
|
|
4740
4765
|
},
|
|
4741
4766
|
remove: (sessionId) => {
|
|
@@ -5070,6 +5095,18 @@ function createRcEventFanout() {
|
|
|
5070
5095
|
}
|
|
5071
5096
|
function createRcStreamHub(deps) {
|
|
5072
5097
|
const attachments = /* @__PURE__ */ new Map();
|
|
5098
|
+
const lastSavedSequenceNums = /* @__PURE__ */ new Map();
|
|
5099
|
+
const persistCursor = (sessionId) => {
|
|
5100
|
+
if (deps.saveSequenceNum === void 0) {
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
const cursor = deps.sequenceNumOf(sessionId);
|
|
5104
|
+
if (cursor === void 0 || cursor === lastSavedSequenceNums.get(sessionId)) {
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
deps.saveSequenceNum(sessionId, cursor);
|
|
5108
|
+
lastSavedSequenceNums.set(sessionId, cursor);
|
|
5109
|
+
};
|
|
5073
5110
|
const birth = (sessionId) => {
|
|
5074
5111
|
const controller = new AbortController();
|
|
5075
5112
|
let stopped = false;
|
|
@@ -5106,7 +5143,7 @@ function createRcStreamHub(deps) {
|
|
|
5106
5143
|
if (!isSuccessful3(presence.status)) {
|
|
5107
5144
|
throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
|
|
5108
5145
|
}
|
|
5109
|
-
const cursor = deps.sequenceNumOf(attachment.sessionId);
|
|
5146
|
+
const cursor = deps.sequenceNumOf(attachment.sessionId) ?? deps.storedSequenceNumOf?.(attachment.sessionId);
|
|
5110
5147
|
const resume = cursor === void 0 ? void 0 : { fromSequenceNum: cursor };
|
|
5111
5148
|
const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
|
|
5112
5149
|
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
@@ -5140,6 +5177,7 @@ function createRcStreamHub(deps) {
|
|
|
5140
5177
|
const credential = deps.credentialOf(attachment.sessionId);
|
|
5141
5178
|
if (!live || credential?.authorization === void 0) {
|
|
5142
5179
|
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"})`);
|
|
5180
|
+
persistCursor(attachment.sessionId);
|
|
5143
5181
|
if (attachments.get(attachment.sessionId) === attachment) {
|
|
5144
5182
|
attachments.delete(attachment.sessionId);
|
|
5145
5183
|
}
|
|
@@ -5149,6 +5187,7 @@ function createRcStreamHub(deps) {
|
|
|
5149
5187
|
try {
|
|
5150
5188
|
outcome = await attempt(attachment, credential);
|
|
5151
5189
|
} catch (error) {
|
|
5190
|
+
persistCursor(attachment.sessionId);
|
|
5152
5191
|
if (attachment.isStopped()) {
|
|
5153
5192
|
return;
|
|
5154
5193
|
}
|
|
@@ -5161,6 +5200,7 @@ function createRcStreamHub(deps) {
|
|
|
5161
5200
|
return;
|
|
5162
5201
|
}
|
|
5163
5202
|
if (outcome === "dropped") {
|
|
5203
|
+
persistCursor(attachment.sessionId);
|
|
5164
5204
|
deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
|
|
5165
5205
|
retriedUnauthorized = false;
|
|
5166
5206
|
continue;
|
|
@@ -5199,6 +5239,7 @@ function createRcStreamHub(deps) {
|
|
|
5199
5239
|
},
|
|
5200
5240
|
close: () => {
|
|
5201
5241
|
for (const attachment of attachments.values()) {
|
|
5242
|
+
persistCursor(attachment.sessionId);
|
|
5202
5243
|
attachment.stop();
|
|
5203
5244
|
}
|
|
5204
5245
|
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. */
|
|
@@ -2586,6 +2594,10 @@ interface FrontDoorSupervisorPorts {
|
|
|
2586
2594
|
* Starts the direct listener on `preferredPort` (same bind-time fallback): the same routes as the provider listener, minus the headroom hop, over plain HTTP because headroom is what connects to it. Headroom forwards routed traffic back here, which is what keeps the hop from looping, and the port is sticky like the others because headroom's allowlist admits its exact origin. It admits only the hop's own requests and holds no credential until it redeems one from the hop's custody.
|
|
2587
2595
|
*/
|
|
2588
2596
|
readonly startDirectListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
2597
|
+
/**
|
|
2598
|
+
* Stops the Remote Control stream hub: persists every live attachment's sequence cursor synchronously, then frees the dials. Called exactly once on every exit the supervisor owns, after every listener it started is down (a live listener could still settle an observed Remote Control exchange, and the reconcile that follows would re-attach a stream the close had just ended) and before the final state write and the process's exit.
|
|
2599
|
+
*/
|
|
2600
|
+
readonly closeRcStreamHub: () => void;
|
|
2589
2601
|
readonly log: (line: string) => void;
|
|
2590
2602
|
}
|
|
2591
2603
|
/** Options for `runFrontDoorSupervisor`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-shim",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.14.1",
|
|
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>",
|