@agentteams/cli 0.1.84 → 0.1.86
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/.eslintcache +1 -1
- package/dist/api/document.d.ts +10 -1
- package/dist/api/document.d.ts.map +1 -1
- package/dist/api/document.js +3 -1
- package/dist/api/document.js.map +1 -1
- package/dist/api/plan.d.ts +2 -0
- package/dist/api/plan.d.ts.map +1 -1
- package/dist/api/plan.js.map +1 -1
- package/dist/auth/credentialStore.d.ts +30 -1
- package/dist/auth/credentialStore.d.ts.map +1 -1
- package/dist/auth/credentialStore.js +110 -24
- package/dist/auth/credentialStore.js.map +1 -1
- package/dist/auth/personalTokenClient.d.ts +34 -0
- package/dist/auth/personalTokenClient.d.ts.map +1 -1
- package/dist/auth/personalTokenClient.js +73 -6
- package/dist/auth/personalTokenClient.js.map +1 -1
- package/dist/auth/personalTokenStore.d.ts +3 -2
- package/dist/auth/personalTokenStore.d.ts.map +1 -1
- package/dist/auth/personalTokenStore.js +1 -1
- package/dist/auth/personalTokenStore.js.map +1 -1
- package/dist/auth/refreshLock.d.ts +74 -0
- package/dist/auth/refreshLock.d.ts.map +1 -0
- package/dist/auth/refreshLock.js +225 -0
- package/dist/auth/refreshLock.js.map +1 -0
- package/dist/commands/attachment.d.ts.map +1 -1
- package/dist/commands/attachment.js +10 -2
- package/dist/commands/attachment.js.map +1 -1
- package/dist/commands/convention.d.ts.map +1 -1
- package/dist/commands/convention.js +60 -5
- package/dist/commands/convention.js.map +1 -1
- package/dist/commands/document.d.ts +3 -0
- package/dist/commands/document.d.ts.map +1 -1
- package/dist/commands/document.js +18 -1
- package/dist/commands/document.js.map +1 -1
- package/dist/commands/plan.d.ts +15 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +28 -0
- package/dist/commands/plan.js.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/context.d.ts +9 -0
- package/dist/mcp/context.d.ts.map +1 -1
- package/dist/mcp/context.js +5 -1
- package/dist/mcp/context.js.map +1 -1
- package/dist/mcp/guides.d.ts +50 -0
- package/dist/mcp/guides.d.ts.map +1 -0
- package/dist/mcp/guides.js +141 -0
- package/dist/mcp/guides.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +20 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/writeTools.d.ts +25 -0
- package/dist/mcp/writeTools.d.ts.map +1 -0
- package/dist/mcp/writeTools.js +161 -0
- package/dist/mcp/writeTools.js.map +1 -0
- package/dist/utils/authFormat.d.ts +18 -0
- package/dist/utils/authFormat.d.ts.map +1 -0
- package/dist/utils/authFormat.js +130 -0
- package/dist/utils/authFormat.js.map +1 -0
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +23 -4
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +20 -1
- package/dist/utils/errors.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,12 +12,27 @@
|
|
|
12
12
|
* 3. Delete the stored refresh token **only** on an explicit `invalid_grant`.
|
|
13
13
|
* A flaky network must not log the user out — otherwise one offline moment
|
|
14
14
|
* costs a full re-login even after connectivity returns.
|
|
15
|
+
*
|
|
16
|
+
* Guarantee 2 covers one process. Rotation also has to be serialized *across*
|
|
17
|
+
* processes, because a one-shot `agentteams` command and a long-lived
|
|
18
|
+
* `agentteams mcp` share one credential slot: see {@link RefreshLock}.
|
|
15
19
|
*/
|
|
16
|
-
import { createPersonalTokenStore } from './personalTokenStore.js';
|
|
20
|
+
import { createPersonalTokenStore, personalTokenSlot } from './personalTokenStore.js';
|
|
21
|
+
import { createFileRefreshLock, noopRefreshLock, DEFAULT_STALE_AFTER_MS, RefreshLockTimeoutError, RefreshLockUnavailableError, } from './refreshLock.js';
|
|
17
22
|
/** Registered in `api/src/services/personalTokenClients.ts`; the server rejects anything else. */
|
|
18
23
|
export const CLI_OAUTH_CLIENT_ID = 'agentteams-cli';
|
|
19
24
|
/** Refresh this far before the server-declared expiry rather than at it. */
|
|
20
25
|
export const ACCESS_REFRESH_SKEW_MS = 60_000;
|
|
26
|
+
/**
|
|
27
|
+
* Cap on a token-endpoint request.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately below {@link DEFAULT_STALE_AFTER_MS}: a rotation runs with the
|
|
30
|
+
* cross-process lock held, and if it could outlast the staleness window another
|
|
31
|
+
* process would judge the lock abandoned, take it, and rotate the same refresh
|
|
32
|
+
* token — the reuse that revokes the whole family. Bounding the request keeps the
|
|
33
|
+
* holder inside the window it promised.
|
|
34
|
+
*/
|
|
35
|
+
export const TOKEN_REQUEST_TIMEOUT_MS = Math.floor(DEFAULT_STALE_AFTER_MS / 2);
|
|
21
36
|
export class PersonalTokenError extends Error {
|
|
22
37
|
code;
|
|
23
38
|
constructor(code, message) {
|
|
@@ -79,6 +94,7 @@ export class PersonalTokenClient {
|
|
|
79
94
|
accessExpiresAt = 0;
|
|
80
95
|
identity = null;
|
|
81
96
|
reconnectRequired = false;
|
|
97
|
+
refreshFailure = null;
|
|
82
98
|
refreshInFlight = null;
|
|
83
99
|
constructor(deps) {
|
|
84
100
|
this.deps = deps;
|
|
@@ -93,6 +109,7 @@ export class PersonalTokenClient {
|
|
|
93
109
|
identity: this.identity,
|
|
94
110
|
expiresAt: this.accessToken ? this.accessExpiresAt : null,
|
|
95
111
|
reconnectRequired: this.reconnectRequired,
|
|
112
|
+
refreshFailure: this.refreshFailure,
|
|
96
113
|
};
|
|
97
114
|
}
|
|
98
115
|
hasCredential() {
|
|
@@ -194,7 +211,15 @@ export class PersonalTokenClient {
|
|
|
194
211
|
* cancel.
|
|
195
212
|
*/
|
|
196
213
|
async revoke() {
|
|
197
|
-
|
|
214
|
+
// Fresh: a logout has to revoke the token the family is actually on, not a
|
|
215
|
+
// copy this process cached before someone else rotated it.
|
|
216
|
+
//
|
|
217
|
+
// Falling back to the cached copy is safe and necessary here: revocation
|
|
218
|
+
// kills the whole family, so a superseded token from the same family does the
|
|
219
|
+
// same job, and a read that failed (locked keychain, denied prompt) must not
|
|
220
|
+
// make logout claim there is nothing to revoke while a live token sits in the
|
|
221
|
+
// store — that would leave it valid server-side with no way to reach it.
|
|
222
|
+
const refreshToken = this.deps.store.read({ fresh: true }) ?? this.deps.store.read();
|
|
198
223
|
if (!refreshToken) {
|
|
199
224
|
throw new PersonalTokenError('NOT_LOGGED_IN', 'No personal token is stored for this server.');
|
|
200
225
|
}
|
|
@@ -225,7 +250,39 @@ export class PersonalTokenClient {
|
|
|
225
250
|
this.clear();
|
|
226
251
|
}
|
|
227
252
|
async refresh() {
|
|
228
|
-
|
|
253
|
+
// Cheap pre-check off the cache: no credential means there is nothing to
|
|
254
|
+
// serialize against, so the lock is never taken for a logged-out process.
|
|
255
|
+
if (!this.deps.store.read())
|
|
256
|
+
return null;
|
|
257
|
+
try {
|
|
258
|
+
const accessToken = await (this.deps.lock ?? noopRefreshLock).withLock(() => this.rotate());
|
|
259
|
+
if (accessToken)
|
|
260
|
+
this.refreshFailure = null;
|
|
261
|
+
return accessToken;
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
// Both lock problems leave the refresh token exactly where it is, so the
|
|
265
|
+
// next command retries. Rotating anyway would risk the server's reuse
|
|
266
|
+
// detection revoking the whole family, which costs a full re-login. They
|
|
267
|
+
// are recorded distinctly so the caller does not blame the network.
|
|
268
|
+
if (error instanceof RefreshLockTimeoutError) {
|
|
269
|
+
this.refreshFailure = 'LOCK_CONTENTION';
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (error instanceof RefreshLockUnavailableError) {
|
|
273
|
+
this.refreshFailure = 'LOCK_UNAVAILABLE';
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** The rotation itself. Runs with the cross-process lock held. */
|
|
280
|
+
async rotate() {
|
|
281
|
+
// Re-read past the store's process-lifetime cache. Another process may have
|
|
282
|
+
// rotated — or cleared — the family while this one waited for the lock, and
|
|
283
|
+
// presenting the superseded token is precisely what makes the server revoke
|
|
284
|
+
// every token in the family.
|
|
285
|
+
const refreshToken = this.deps.store.read({ fresh: true });
|
|
229
286
|
if (!refreshToken)
|
|
230
287
|
return null;
|
|
231
288
|
let parsed;
|
|
@@ -237,8 +294,10 @@ export class PersonalTokenClient {
|
|
|
237
294
|
});
|
|
238
295
|
}
|
|
239
296
|
catch {
|
|
240
|
-
// Network-level failure
|
|
241
|
-
//
|
|
297
|
+
// Network-level failure, which now includes the request outrunning
|
|
298
|
+
// TOKEN_REQUEST_TIMEOUT_MS. The refresh token is still presumed valid, so
|
|
299
|
+
// it stays exactly where it is; the next command can try again.
|
|
300
|
+
this.refreshFailure = 'NETWORK';
|
|
242
301
|
return null;
|
|
243
302
|
}
|
|
244
303
|
if (parsed.kind === 'invalidGrant') {
|
|
@@ -258,6 +317,7 @@ export class PersonalTokenClient {
|
|
|
258
317
|
method: 'POST',
|
|
259
318
|
headers: { 'Content-Type': 'application/json' },
|
|
260
319
|
body: JSON.stringify(body),
|
|
320
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
261
321
|
});
|
|
262
322
|
return parseTokenResponse(response);
|
|
263
323
|
}
|
|
@@ -279,6 +339,7 @@ export class PersonalTokenClient {
|
|
|
279
339
|
this.accessExpiresAt = 0;
|
|
280
340
|
this.identity = null;
|
|
281
341
|
this.reconnectRequired = false;
|
|
342
|
+
this.refreshFailure = null;
|
|
282
343
|
this.deps.store.remove();
|
|
283
344
|
}
|
|
284
345
|
now() {
|
|
@@ -300,7 +361,13 @@ export function getPersonalTokenClient(apiUrl) {
|
|
|
300
361
|
const key = apiUrl.replace(/\/+$/, '');
|
|
301
362
|
let client = clients.get(key);
|
|
302
363
|
if (!client) {
|
|
303
|
-
|
|
364
|
+
const slot = personalTokenSlot(key);
|
|
365
|
+
client = new PersonalTokenClient({
|
|
366
|
+
apiUrl: key,
|
|
367
|
+
store: createPersonalTokenStore(key),
|
|
368
|
+
// Per slot, so a dev-API rotation never blocks a production one.
|
|
369
|
+
lock: createFileRefreshLock(slot),
|
|
370
|
+
});
|
|
304
371
|
clients.set(key, client);
|
|
305
372
|
}
|
|
306
373
|
return client;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personalTokenClient.js","sourceRoot":"","sources":["../../src/auth/personalTokenClient.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"personalTokenClient.js","sourceRoot":"","sources":["../../src/auth/personalTokenClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAA2B,MAAM,yBAAyB,CAAC;AAC/G,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,sBAAsB,EACtB,uBAAuB,EACvB,2BAA2B,GAE5B,MAAM,kBAAkB,CAAC;AAE1B,kGAAkG;AAClG,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC,KAAK,CAAC,sBAAsB,GAAG,CAAC,CAAC,CAAC;AAW/E,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC;IADX,YACW,IAA4B,EACrC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,SAAI,GAAJ,IAAI,CAAwB;QAIrC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAuED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAkB;IACzD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,QAAQ,CAAC,MAAM,0BAA0B,EAAE,CAAC;IAC1F,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,CAAC,CAAC,CAAE,IAA4B,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAEpH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;YACtF,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,IAAI,GACR,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,CAAC,CAAC,CAAE,IAA2C,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACrH,MAAM,QAAQ,GACZ,IAAI,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,QAAoC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE/G,IACE,OAAO,IAAI,EAAE,WAAW,KAAK,QAAQ;QACrC,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ;QACrC,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;QAClC,CAAC,QAAQ;QACT,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ;QACrC,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ;QAClC,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,EACrC,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,iDAAiD,EAAE,CAAC;IAC1F,CAAC;IAED,OAAO;QACL,IAAI,EAAE,SAAS;QACf,MAAM,EAAE;YACN,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE;gBACR,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;aAC5B;SACF;KACF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,mBAAmB;IAQD;IAPrB,WAAW,GAAkB,IAAI,CAAC;IAClC,eAAe,GAAG,CAAC,CAAC;IACpB,QAAQ,GAAiC,IAAI,CAAC;IAC9C,iBAAiB,GAAG,KAAK,CAAC;IAC1B,cAAc,GAAuC,IAAI,CAAC;IAC1D,eAAe,GAAkC,IAAI,CAAC;IAE9D,YAA6B,IAA6B;QAA7B,SAAI,GAAJ,IAAI,CAAyB;IAAG,CAAC;IAE9D,KAAK;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAC7C,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI;YAC1C,SAAS,EAAE,WAAW,CAAC,SAAS;YAChC,YAAY,EAAE,WAAW,CAAC,OAAO;YACjC,WAAW,EAAE,WAAW,CAAC,MAAM;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI;YACzD,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,cAAc,EAAE,IAAI,CAAC,cAAc;SACpC,CAAC;IACJ,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;IACzC,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,yBAAyB,CAAC,KAA6B;QAC3D,IAAI,MAA2B,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC;gBAC5B,SAAS,EAAE,oBAAoB;gBAC/B,QAAQ,EAAE,mBAAmB;gBAC7B,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,kBAAkB,CAC1B,WAAW,EACX,kEAAkE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAC5H,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACnC,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,oHAAoH,CACrH,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,kBAAkB,CAAC,WAAW,EAAE,0BAA0B,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;QAE/B,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC;QAED,OAAO;YACL,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW;YACtC,SAAS,EAAE,IAAI,CAAC,eAAe;YAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ;SACjC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,uBAAuB,CAAC,OAA8B;QAClE,IAAI,aAAa,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,aAAa,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;QAED,MAAM,KAAK,GACT,OAAO,CAAC,MAAM,KAAK,cAAc;YAC/B,CAAC,CAAC,6CAA6C,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC7F,CAAC,CAAC,gDAAgD,CAAC;QAEvD,MAAM,IAAI,kBAAkB,CAC1B,mBAAmB,EACnB,gDAAgD,KAAK,IAAI;YACvD,CAAC,aAAa;gBACZ,CAAC,CAAC,0DAA0D;gBAC5D,CAAC,CAAC,iFAAiF,CAAC;YACtF,sFAAsF,CACzF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,eAAe,GAAG,sBAAsB,GAAG,GAAG,EAAE,CAAC;YAC5E,OAAO,IAAI,CAAC,WAAW,CAAC;QAC1B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBACjD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;YAC9B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED,6FAA6F;IAC7F,qBAAqB;QACnB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM;QACV,2EAA2E;QAC3E,2DAA2D;QAC3D,EAAE;QACF,yEAAyE;QACzE,8EAA8E;QAC9E,6EAA6E;QAC7E,8EAA8E;QAC9E,yEAAyE;QACzE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACrF,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,8CAA8C,CAAC,CAAC;QAChG,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;QACpD,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,0BAA0B,EAAE;gBACpE,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,mBAAmB,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;aAC7E,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,mDAAmD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAC5I,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,kBAAkB,CAC1B,eAAe,EACf,gDAAgD,QAAQ,CAAC,MAAM,mCAAmC,CACnG,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACH,qBAAqB;QACnB,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,yEAAyE;QACzE,0EAA0E;QAC1E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;QAEzC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,eAAe,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YAC5F,IAAI,WAAW;gBAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC5C,OAAO,WAAW,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,sEAAsE;YACtE,yEAAyE;YACzE,oEAAoE;YACpE,IAAI,KAAK,YAAY,uBAAuB,EAAE,CAAC;gBAC7C,IAAI,CAAC,cAAc,GAAG,iBAAiB,CAAC;gBACxC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,KAAK,YAAY,2BAA2B,EAAE,CAAC;gBACjD,IAAI,CAAC,cAAc,GAAG,kBAAkB,CAAC;gBACzC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,kEAAkE;IAC1D,KAAK,CAAC,MAAM;QAClB,4EAA4E;QAC5E,4EAA4E;QAC5E,4EAA4E;QAC5E,6BAA6B;QAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QAE/B,IAAI,MAA2B,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC;gBAC5B,SAAS,EAAE,eAAe;gBAC1B,QAAQ,EAAE,mBAAmB;gBAC7B,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,0EAA0E;YAC1E,gEAAgE;YAChE,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAE3C,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,IAA6B;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,yBAAyB,EAAE;YACzE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,wBAAwB,CAAC;SACtD,CAAC,CAAC;QACH,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED;;;;OAIG;IACK,YAAY,CAAC,MAAqB;QACxC,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACtC,yFAAyF;QACzF,kDAAkD;QAClD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;QAC5D,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACnD,CAAC;IAEO,KAAK;QACX,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;QAC/B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IAC3B,CAAC;IAEO,GAAG;QACT,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,CAAC;IAEO,OAAO;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAA+B,CAAC;AAEvD,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvC,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,GAAG,IAAI,mBAAmB,CAAC;YAC/B,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,wBAAwB,CAAC,GAAG,CAAC;YACpC,iEAAiE;YACjE,IAAI,EAAE,qBAAqB,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,iCAAiC;IAC/C,OAAO,CAAC,KAAK,EAAE,CAAC;AAClB,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type CredentialSaveOutcome, type CredentialStore, type CredentialStoreStatus } from './credentialStore.js';
|
|
1
|
+
import { type CredentialReadOptions, type CredentialSaveOutcome, type CredentialStore, type CredentialStoreStatus } from './credentialStore.js';
|
|
2
2
|
export interface PersonalTokenStore {
|
|
3
3
|
status(): CredentialStoreStatus;
|
|
4
|
-
|
|
4
|
+
/** Pass `{ fresh: true }` before presenting the token — see {@link CredentialReadOptions}. */
|
|
5
|
+
read(options?: CredentialReadOptions): string | null;
|
|
5
6
|
save(refreshToken: string): CredentialSaveOutcome;
|
|
6
7
|
remove(): void;
|
|
7
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personalTokenStore.d.ts","sourceRoot":"","sources":["../../src/auth/personalTokenStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAU9B,MAAM,WAAW,kBAAkB;IACjC,MAAM,IAAI,qBAAqB,CAAC;IAChC,
|
|
1
|
+
{"version":3,"file":"personalTokenStore.d.ts","sourceRoot":"","sources":["../../src/auth/personalTokenStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAU9B,MAAM,WAAW,kBAAkB;IACjC,MAAM,IAAI,qBAAqB,CAAC;IAChC,8FAA8F;IAC9F,IAAI,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAAC;IACrD,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,qBAAqB,CAAC;IAClD,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,eAAsC,GAC5C,kBAAkB,CASpB"}
|
|
@@ -19,7 +19,7 @@ export function createPersonalTokenStore(apiUrl, store = getCredentialStore()) {
|
|
|
19
19
|
const slot = personalTokenSlot(apiUrl);
|
|
20
20
|
return {
|
|
21
21
|
status: () => store.status(),
|
|
22
|
-
read: () => store.read(slot),
|
|
22
|
+
read: (options) => store.read(slot, options),
|
|
23
23
|
save: (refreshToken) => store.save(slot, refreshToken),
|
|
24
24
|
remove: () => store.remove(slot),
|
|
25
25
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"personalTokenStore.js","sourceRoot":"","sources":["../../src/auth/personalTokenStore.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,
|
|
1
|
+
{"version":3,"file":"personalTokenStore.js","sourceRoot":"","sources":["../../src/auth/personalTokenStore.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAKnB,MAAM,sBAAsB,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAUvC,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,GAAG,WAAW,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAc,EACd,QAAyB,kBAAkB,EAAE;IAE7C,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAEvC,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE;QAC5B,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC;QAC5C,IAAI,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QACtD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process mutex for refresh-token rotation.
|
|
3
|
+
*
|
|
4
|
+
* Rotation is destructive on the server: presenting a refresh token that has
|
|
5
|
+
* already been rotated is reuse, and the server answers reuse by revoking the
|
|
6
|
+
* **entire token family** (`rotatePersonalRefreshToken` in
|
|
7
|
+
* `api/src/services/personalToken.ts`). Every `agentteams` invocation rotates,
|
|
8
|
+
* and several can run at once — one-shot commands next to a long-lived
|
|
9
|
+
* `agentteams mcp` server. Without serialization, two processes reading the same
|
|
10
|
+
* token and rotating it concurrently log the user out of every process at once,
|
|
11
|
+
* recoverable only by a fresh `agentteams auth login`.
|
|
12
|
+
*
|
|
13
|
+
* The lock is a file created with `wx`, which is atomic on every platform the
|
|
14
|
+
* CLI supports, so no native dependency is needed. Two failure modes are
|
|
15
|
+
* deliberately not treated as errors:
|
|
16
|
+
*
|
|
17
|
+
* - **No usable lock directory** (read-only home, sandbox) → run unlocked.
|
|
18
|
+
* The lock protects against a race; refusing to authenticate at all because
|
|
19
|
+
* the guard is unavailable would be the worse outcome.
|
|
20
|
+
* - **A lock left behind by a crashed process** → broken once it is older than
|
|
21
|
+
* {@link DEFAULT_STALE_AFTER_MS}, so a killed process cannot lock the user
|
|
22
|
+
* out permanently.
|
|
23
|
+
*
|
|
24
|
+
* Contention that outlives the timeout raises {@link RefreshLockTimeoutError}
|
|
25
|
+
* instead of rotating anyway: the caller treats it like a network failure and
|
|
26
|
+
* keeps the credential, which costs one retry. Rotating unlocked would risk the
|
|
27
|
+
* family revocation this module exists to prevent.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* How long a held lock stays credible. A token request that has not finished by
|
|
31
|
+
* then is either hung or its process is gone; both justify taking over.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_STALE_AFTER_MS = 20000;
|
|
34
|
+
/** Long enough to outlast a slow-but-real rotation ahead of us in the queue. */
|
|
35
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
36
|
+
export interface RefreshLock {
|
|
37
|
+
withLock<T>(run: () => Promise<T>): Promise<T>;
|
|
38
|
+
}
|
|
39
|
+
export declare class RefreshLockTimeoutError extends Error {
|
|
40
|
+
readonly lockPath: string;
|
|
41
|
+
readonly timeoutMs: number;
|
|
42
|
+
constructor(lockPath: string, timeoutMs: number);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The lock file itself could not be maintained — distinct from losing a race for
|
|
46
|
+
* it. Kept separate so the caller can say which one happened instead of
|
|
47
|
+
* reporting every lock problem as a network failure.
|
|
48
|
+
*/
|
|
49
|
+
export declare class RefreshLockUnavailableError extends Error {
|
|
50
|
+
readonly lockPath: string;
|
|
51
|
+
readonly cause: unknown;
|
|
52
|
+
constructor(lockPath: string, cause: unknown);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A lock that never blocks. The default for {@link PersonalTokenClient} so unit
|
|
56
|
+
* tests touch no filesystem; production wiring passes a real lock.
|
|
57
|
+
*/
|
|
58
|
+
export declare const noopRefreshLock: RefreshLock;
|
|
59
|
+
export interface CreateFileRefreshLockOptions {
|
|
60
|
+
/** Defaults to `~/.agentteams/locks`. */
|
|
61
|
+
directory?: string;
|
|
62
|
+
staleAfterMs?: number;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
retryDelayMs?: number;
|
|
65
|
+
now?: () => number;
|
|
66
|
+
sleep?: (ms: number) => Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One lock file per credential slot, named by hash: a slot embeds an API URL,
|
|
70
|
+
* whose `:` and `/` are not portable in a filename.
|
|
71
|
+
*/
|
|
72
|
+
export declare function refreshLockFileName(slot: string): string;
|
|
73
|
+
export declare function createFileRefreshLock(slot: string, options?: CreateFileRefreshLockOptions): RefreshLock;
|
|
74
|
+
//# sourceMappingURL=refreshLock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refreshLock.d.ts","sourceRoot":"","sources":["../../src/auth/refreshLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAUH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAOzC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM;gBADjB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;CAK7B;AAED;;;;GAIG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IAElD,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO;gBADd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO;CAS1B;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,WAE7B,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAOD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,4BAAiC,GAAG,WAAW,CA2I3G"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process mutex for refresh-token rotation.
|
|
3
|
+
*
|
|
4
|
+
* Rotation is destructive on the server: presenting a refresh token that has
|
|
5
|
+
* already been rotated is reuse, and the server answers reuse by revoking the
|
|
6
|
+
* **entire token family** (`rotatePersonalRefreshToken` in
|
|
7
|
+
* `api/src/services/personalToken.ts`). Every `agentteams` invocation rotates,
|
|
8
|
+
* and several can run at once — one-shot commands next to a long-lived
|
|
9
|
+
* `agentteams mcp` server. Without serialization, two processes reading the same
|
|
10
|
+
* token and rotating it concurrently log the user out of every process at once,
|
|
11
|
+
* recoverable only by a fresh `agentteams auth login`.
|
|
12
|
+
*
|
|
13
|
+
* The lock is a file created with `wx`, which is atomic on every platform the
|
|
14
|
+
* CLI supports, so no native dependency is needed. Two failure modes are
|
|
15
|
+
* deliberately not treated as errors:
|
|
16
|
+
*
|
|
17
|
+
* - **No usable lock directory** (read-only home, sandbox) → run unlocked.
|
|
18
|
+
* The lock protects against a race; refusing to authenticate at all because
|
|
19
|
+
* the guard is unavailable would be the worse outcome.
|
|
20
|
+
* - **A lock left behind by a crashed process** → broken once it is older than
|
|
21
|
+
* {@link DEFAULT_STALE_AFTER_MS}, so a killed process cannot lock the user
|
|
22
|
+
* out permanently.
|
|
23
|
+
*
|
|
24
|
+
* Contention that outlives the timeout raises {@link RefreshLockTimeoutError}
|
|
25
|
+
* instead of rotating anyway: the caller treats it like a network failure and
|
|
26
|
+
* keeps the credential, which costs one retry. Rotating unlocked would risk the
|
|
27
|
+
* family revocation this module exists to prevent.
|
|
28
|
+
*/
|
|
29
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
30
|
+
import { linkSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
31
|
+
import { homedir } from 'node:os';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
/** Same `.agentteams` directory the global config lives in. */
|
|
34
|
+
const LOCK_DIR = ['.agentteams', 'locks'];
|
|
35
|
+
/**
|
|
36
|
+
* How long a held lock stays credible. A token request that has not finished by
|
|
37
|
+
* then is either hung or its process is gone; both justify taking over.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_STALE_AFTER_MS = 20_000;
|
|
40
|
+
/** Long enough to outlast a slow-but-real rotation ahead of us in the queue. */
|
|
41
|
+
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
42
|
+
const DEFAULT_RETRY_DELAY_MS = 50;
|
|
43
|
+
const DIRECTORY_MODE = 0o700;
|
|
44
|
+
const FILE_MODE = 0o600;
|
|
45
|
+
export class RefreshLockTimeoutError extends Error {
|
|
46
|
+
lockPath;
|
|
47
|
+
timeoutMs;
|
|
48
|
+
constructor(lockPath, timeoutMs) {
|
|
49
|
+
super(`Timed out after ${timeoutMs}ms waiting for the credential rotation lock at ${lockPath}.`);
|
|
50
|
+
this.lockPath = lockPath;
|
|
51
|
+
this.timeoutMs = timeoutMs;
|
|
52
|
+
this.name = 'RefreshLockTimeoutError';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The lock file itself could not be maintained — distinct from losing a race for
|
|
57
|
+
* it. Kept separate so the caller can say which one happened instead of
|
|
58
|
+
* reporting every lock problem as a network failure.
|
|
59
|
+
*/
|
|
60
|
+
export class RefreshLockUnavailableError extends Error {
|
|
61
|
+
lockPath;
|
|
62
|
+
cause;
|
|
63
|
+
constructor(lockPath, cause) {
|
|
64
|
+
super(`Could not maintain the credential rotation lock at ${lockPath}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
65
|
+
this.lockPath = lockPath;
|
|
66
|
+
this.cause = cause;
|
|
67
|
+
this.name = 'RefreshLockUnavailableError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A lock that never blocks. The default for {@link PersonalTokenClient} so unit
|
|
72
|
+
* tests touch no filesystem; production wiring passes a real lock.
|
|
73
|
+
*/
|
|
74
|
+
export const noopRefreshLock = {
|
|
75
|
+
withLock: (run) => run(),
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* One lock file per credential slot, named by hash: a slot embeds an API URL,
|
|
79
|
+
* whose `:` and `/` are not portable in a filename.
|
|
80
|
+
*/
|
|
81
|
+
export function refreshLockFileName(slot) {
|
|
82
|
+
return `personal-token-${createHash('sha256').update(slot).digest('hex').slice(0, 16)}.lock`;
|
|
83
|
+
}
|
|
84
|
+
function isAlreadyExists(error) {
|
|
85
|
+
return error?.code === 'EEXIST';
|
|
86
|
+
}
|
|
87
|
+
export function createFileRefreshLock(slot, options = {}) {
|
|
88
|
+
const directory = options.directory ?? join(homedir(), ...LOCK_DIR);
|
|
89
|
+
const lockPath = join(directory, refreshLockFileName(slot));
|
|
90
|
+
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
91
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
92
|
+
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
93
|
+
const now = options.now ?? Date.now;
|
|
94
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
95
|
+
/**
|
|
96
|
+
* When the lock was taken, or null if that cannot be established.
|
|
97
|
+
*
|
|
98
|
+
* The timestamp inside the file is preferred so the clock the caller injects is
|
|
99
|
+
* the one that decides. An unreadable or half-written file falls back to mtime
|
|
100
|
+
* rather than counting as "no holder": the file existing is itself evidence
|
|
101
|
+
* that someone created it, and treating it as free would delete a live lock.
|
|
102
|
+
*/
|
|
103
|
+
const readHeldSince = () => {
|
|
104
|
+
try {
|
|
105
|
+
const record = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
106
|
+
if (typeof record.startedAt === 'number')
|
|
107
|
+
return record.startedAt;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Gone, or content we cannot interpret. mtime is the fallback below.
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return statSync(lockPath).mtimeMs;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Really gone — the holder released between the two calls.
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const breakIfStale = () => {
|
|
121
|
+
const heldSince = readHeldSince();
|
|
122
|
+
if (heldSince !== null && now() - heldSince < staleAfterMs) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(lockPath);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Another waiter won the race to break it. Either way it is gone.
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Publish the lock file with its content already in place.
|
|
134
|
+
*
|
|
135
|
+
* `writeFileSync` with `wx` creates the file and writes afterwards, leaving a
|
|
136
|
+
* window in which the lock exists but is empty — long enough for a waiter to
|
|
137
|
+
* read nothing, conclude there is no holder, and delete a lock that was just
|
|
138
|
+
* taken. Writing to a private path and `link`ing it into place closes that
|
|
139
|
+
* window: `link` is atomic and fails with EEXIST when the target exists, which
|
|
140
|
+
* is the same mutual exclusion `wx` provides.
|
|
141
|
+
*/
|
|
142
|
+
const publishLockFile = (owner, record) => {
|
|
143
|
+
const stagingPath = `${lockPath}.${process.pid}.${randomUUID()}`;
|
|
144
|
+
writeFileSync(stagingPath, JSON.stringify(record), { flag: 'wx', mode: FILE_MODE });
|
|
145
|
+
try {
|
|
146
|
+
linkSync(stagingPath, lockPath);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
try {
|
|
150
|
+
unlinkSync(stagingPath);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// The link either succeeded (the content lives on at lockPath) or threw
|
|
154
|
+
// and is being propagated; either way the staging copy is disposable.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/** Returns the owner token, or null when this machine cannot host a lock. */
|
|
159
|
+
const acquire = async () => {
|
|
160
|
+
try {
|
|
161
|
+
// `recursive` makes an existing directory a no-op, so anything thrown here
|
|
162
|
+
// means no lock is possible at all (read-only home, no permission, a file
|
|
163
|
+
// sitting on the path). Proceed unguarded rather than block every command.
|
|
164
|
+
mkdirSync(directory, { recursive: true, mode: DIRECTORY_MODE });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const deadline = now() + timeoutMs;
|
|
170
|
+
let firstWriteError;
|
|
171
|
+
for (;;) {
|
|
172
|
+
const owner = `${process.pid}:${randomUUID()}`;
|
|
173
|
+
const record = { owner, startedAt: now() };
|
|
174
|
+
try {
|
|
175
|
+
publishLockFile(owner, record);
|
|
176
|
+
return owner;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (!isAlreadyExists(error)) {
|
|
180
|
+
// Not contention. A transient failure (ENOSPC, a permission change
|
|
181
|
+
// mid-run) must not silently downgrade to an unguarded rotation, so it
|
|
182
|
+
// is retried within the same deadline and only reported if it persists.
|
|
183
|
+
firstWriteError ??= error;
|
|
184
|
+
if (now() >= deadline) {
|
|
185
|
+
throw new RefreshLockUnavailableError(lockPath, firstWriteError);
|
|
186
|
+
}
|
|
187
|
+
await sleep(retryDelayMs);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
breakIfStale();
|
|
192
|
+
if (now() >= deadline) {
|
|
193
|
+
throw new RefreshLockTimeoutError(lockPath, timeoutMs);
|
|
194
|
+
}
|
|
195
|
+
await sleep(retryDelayMs);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const release = (owner) => {
|
|
199
|
+
if (owner === null)
|
|
200
|
+
return;
|
|
201
|
+
try {
|
|
202
|
+
// Only remove a lock still recorded as ours: if it was broken as stale
|
|
203
|
+
// while we ran, the file now belongs to whoever took over.
|
|
204
|
+
const record = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
205
|
+
if (record.owner !== owner)
|
|
206
|
+
return;
|
|
207
|
+
unlinkSync(lockPath);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Already gone, or unreadable. Nothing left to release.
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
async withLock(run) {
|
|
215
|
+
const owner = await acquire();
|
|
216
|
+
try {
|
|
217
|
+
return await run();
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
release(owner);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=refreshLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refreshLock.js","sourceRoot":"","sources":["../../src/auth/refreshLock.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/commands/attachment.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/commands/attachment.ts"],"names":[],"mappings":"AA2EA,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,OAAO,CAAC,CAiElB"}
|
|
@@ -27,9 +27,14 @@ const CONTENT_TYPE_BY_EXTENSION = {
|
|
|
27
27
|
pdf: 'application/pdf',
|
|
28
28
|
html: 'text/html',
|
|
29
29
|
htm: 'text/html',
|
|
30
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
31
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
32
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
30
33
|
};
|
|
34
|
+
const OFFICE_EXTENSIONS = new Set(['docx', 'pptx', 'xlsx']);
|
|
35
|
+
const resolveExtension = (fileName) => fileName.includes('.') ? (fileName.split('.').pop()?.toLowerCase() ?? '') : '';
|
|
31
36
|
const resolveContentType = (fileName) => {
|
|
32
|
-
const ext =
|
|
37
|
+
const ext = resolveExtension(fileName);
|
|
33
38
|
const contentType = CONTENT_TYPE_BY_EXTENSION[ext];
|
|
34
39
|
if (!contentType) {
|
|
35
40
|
throw new Error(`Unsupported attachment type ".${ext}". Allowed: ${Object.keys(CONTENT_TYPE_BY_EXTENSION).join(', ')}`);
|
|
@@ -83,9 +88,12 @@ export async function executeAttachmentCommand(apiUrl, headers, action, options)
|
|
|
83
88
|
}
|
|
84
89
|
const fileName = basename(filePath);
|
|
85
90
|
const contentType = resolveContentType(fileName);
|
|
91
|
+
if (OFFICE_EXTENSIONS.has(resolveExtension(fileName)) && targetType !== 'document') {
|
|
92
|
+
throw new Error('Office attachments are supported only with --document-id.');
|
|
93
|
+
}
|
|
86
94
|
printFileSizeInfo(filePathOption, buffer.length);
|
|
87
95
|
// 1) Presigned draft 업로드 URL 발급
|
|
88
|
-
const draftResponse = await axios.post(`${apiUrl}/api/attachments/draft-upload-url`, { fileName, contentType, size: buffer.length }, { headers });
|
|
96
|
+
const draftResponse = await axios.post(`${apiUrl}/api/attachments/draft-upload-url`, { fileName, contentType, size: buffer.length, targetType }, { headers });
|
|
89
97
|
const { uploadUrl, key } = draftResponse.data.data;
|
|
90
98
|
// 2) R2에 파일 바이트 직접 PUT (API 서버를 경유하지 않음)
|
|
91
99
|
await axios.put(uploadUrl, buffer, { headers: { 'Content-Type': contentType } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/commands/attachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,aAAa,GAAG,CAAC,KAAc,EAAE,IAAY,EAAE,EAAE;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,KAAc,EAAsB,EAAE;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,yBAAyB,GAA2B;IACxD,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,YAAY;IACjB,EAAE,EAAE,eAAe;IACnB,QAAQ,EAAE,eAAe;IACzB,GAAG,EAAE,iBAAiB;IACtB,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,WAAW;
|
|
1
|
+
{"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/commands/attachment.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convention.d.ts","sourceRoot":"","sources":["../../src/commands/convention.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAWhD,KAAK,wBAAwB,GAAG;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,CAAC;
|
|
1
|
+
{"version":3,"file":"convention.d.ts","sourceRoot":"","sources":["../../src/commands/convention.ts"],"names":[],"mappings":"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"}
|