@skrr-ai/cli 0.1.79 → 0.1.81
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/base-command.d.ts +4 -0
- package/dist/base-command.js +40 -4
- package/dist/commands/agents/visibility.d.ts +14 -0
- package/dist/commands/agents/visibility.js +52 -0
- package/dist/commands/create-token.d.ts +0 -1
- package/dist/commands/create-token.js +3 -1
- package/dist/commands/inbox/index.js +4 -2
- package/dist/commands/list-tokens.d.ts +0 -1
- package/dist/commands/list-tokens.js +1 -1
- package/dist/commands/login.d.ts +8 -3
- package/dist/commands/login.js +14 -4
- package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
- package/dist/commands/revoke-token.d.ts +0 -1
- package/dist/commands/revoke-token.js +1 -1
- package/dist/commands/spaces/workflows/events.js +9 -2
- package/dist/commands/store/follow.d.ts +1 -0
- package/dist/commands/store/follow.js +7 -1
- package/dist/commands/store/follows/list.d.ts +11 -0
- package/dist/commands/store/follows/list.js +29 -0
- package/dist/commands/store/follows/remove.d.ts +16 -0
- package/dist/commands/store/follows/remove.js +37 -0
- package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
- package/dist/commands/store/publishers/profile/draft.js +54 -0
- package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
- package/dist/commands/store/publishers/profile/handle.js +39 -0
- package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
- package/dist/commands/store/publishers/profile/hide.js +39 -0
- package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
- package/dist/commands/store/publishers/profile/preview.js +39 -0
- package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
- package/dist/commands/store/publishers/profile/publish.js +41 -0
- package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
- package/dist/commands/store/publishers/profile/restore.js +49 -0
- package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
- package/dist/commands/store/publishers/profile/revisions.js +30 -0
- package/dist/commands/store/publishers/profile/show.d.ts +11 -0
- package/dist/commands/store/publishers/profile/show.js +39 -0
- package/dist/commands/store/review.d.ts +26 -0
- package/dist/commands/store/review.js +93 -0
- package/dist/commands/store/reviews.d.ts +29 -0
- package/dist/commands/store/reviews.js +170 -0
- package/dist/commands/store/tip.d.ts +20 -0
- package/dist/commands/store/tip.js +57 -0
- package/dist/commands/tokens/create.d.ts +28 -0
- package/dist/commands/tokens/create.js +136 -0
- package/dist/commands/tokens/list.d.ts +11 -0
- package/dist/commands/tokens/list.js +53 -0
- package/dist/commands/tokens/revoke.d.ts +14 -0
- package/dist/commands/tokens/revoke.js +42 -0
- package/dist/lib/access-tokens.d.ts +59 -0
- package/dist/lib/access-tokens.js +50 -0
- package/dist/lib/agentic-stream.js +13 -4
- package/dist/lib/api-fetch.js +6 -2
- package/dist/lib/auth-core-init.d.ts +6 -0
- package/dist/lib/auth-core-init.js +28 -6
- package/dist/lib/brokered-dpop.d.ts +34 -0
- package/dist/lib/brokered-dpop.js +89 -0
- package/dist/lib/credential-resolver.d.ts +1 -1
- package/dist/lib/credential-resolver.js +8 -2
- package/dist/lib/daemonBroker.d.ts +84 -4
- package/dist/lib/daemonBroker.js +209 -40
- package/dist/lib/daemonBrokerRefusal.d.ts +26 -0
- package/dist/lib/daemonBrokerRefusal.js +83 -4
- package/dist/lib/daemonRestartWait.d.ts +39 -0
- package/dist/lib/daemonRestartWait.js +181 -0
- package/dist/lib/dedicated-service.js +10 -5
- package/dist/lib/dedicated-ssh.d.ts +9 -0
- package/dist/lib/dedicated-ssh.js +235 -63
- package/dist/lib/dedicated-terminal.js +6 -3
- package/dist/lib/dpop-auth.d.ts +35 -0
- package/dist/lib/dpop-auth.js +57 -0
- package/dist/lib/login.d.ts +6 -0
- package/dist/lib/login.js +13 -4
- package/dist/lib/node-adapter.js +6 -2
- package/dist/lib/publisher-profile-flags.d.ts +19 -0
- package/dist/lib/publisher-profile-flags.js +48 -0
- package/dist/lib/refresh.d.ts +1 -1
- package/dist/lib/refresh.js +6 -1
- package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
- package/dist/lib/ssh-ordered-bytes.js +31 -0
- package/dist/lib/usd-micros.d.ts +9 -0
- package/dist/lib/usd-micros.js +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +42 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +35 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +49 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +47 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +56 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +42 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.d.ts +101 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +179 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +23 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +42 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +32 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +49 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +47 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +56 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +39 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.d.ts +101 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +138 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3895 -3882
- package/oclif.manifest.json +34985 -33643
- package/package.json +28 -4
package/dist/lib/daemonBroker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* daemonBroker.ts —
|
|
3
|
+
* daemonBroker.ts — skrr CLI client for the local `skrrd` daemon's
|
|
4
4
|
* cli-handoff endpoint.
|
|
5
5
|
*
|
|
6
6
|
* When the daemon is already authenticated for the user on this machine,
|
|
@@ -71,7 +71,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
71
71
|
};
|
|
72
72
|
})();
|
|
73
73
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
-
exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = void 0;
|
|
74
|
+
exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR = exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = void 0;
|
|
75
75
|
exports.normalizeBaseUrl = normalizeBaseUrl;
|
|
76
76
|
exports.resolveBootstrapPath = resolveBootstrapPath;
|
|
77
77
|
exports.resolveProfileBootstrapPaths = resolveProfileBootstrapPaths;
|
|
@@ -80,6 +80,7 @@ exports.resolveCliHandoffCandidatePaths = resolveCliHandoffCandidatePaths;
|
|
|
80
80
|
exports.resolveExactProfileBootstrapPaths = resolveExactProfileBootstrapPaths;
|
|
81
81
|
exports.findLiveDaemonBootstrap = findLiveDaemonBootstrap;
|
|
82
82
|
exports.readBootstrap = readBootstrap;
|
|
83
|
+
exports.inspectBootstrap = inspectBootstrap;
|
|
83
84
|
exports.attemptDaemonBrokerLogin = attemptDaemonBrokerLogin;
|
|
84
85
|
exports.isBrokeredHandoffToken = isBrokeredHandoffToken;
|
|
85
86
|
exports.redeemBrokeredHandoffToken = redeemBrokeredHandoffToken;
|
|
@@ -95,9 +96,12 @@ const os = __importStar(require("node:os"));
|
|
|
95
96
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
96
97
|
const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
|
|
97
98
|
const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
|
|
99
|
+
const brokered_dpop_1 = require("./brokered-dpop");
|
|
98
100
|
const auth_storage_1 = require("./auth-storage");
|
|
99
101
|
const config_1 = require("./config");
|
|
100
102
|
const cli_identity_1 = require("./cli-identity");
|
|
103
|
+
const daemonRestartWait_1 = require("./daemonRestartWait");
|
|
104
|
+
const keychain_1 = require("./keychain");
|
|
101
105
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
102
106
|
/**
|
|
103
107
|
* Normalize a base URL for equality comparison. Mirrors the daemon-side
|
|
@@ -230,14 +234,25 @@ function resolveBootstrapCandidatePaths(profile = 'default') {
|
|
|
230
234
|
* user configures can point the broker somewhere else.
|
|
231
235
|
*/
|
|
232
236
|
exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = '/run/skrr-dedicated-runtime/cli-handoff/descriptor.json';
|
|
237
|
+
/**
|
|
238
|
+
* A Hosted Machine's workload hand-off descriptor, published only on a box
|
|
239
|
+
* booted under the identity split (OSK-11981, OSK-12019), where the daemon runs
|
|
240
|
+
* as `oversky-daemon` and its island file is unreadable to the workload. Same
|
|
241
|
+
* format and same broker-only contract as the Dedicated descriptor
|
|
242
|
+
* (`daemon/src/dedicated-cli-handoff.ts`, `workloadCliHandoffTarget`), in a
|
|
243
|
+
* DIFFERENT directory so a Hosted Machine is never mistaken for a Dedicated
|
|
244
|
+
* Runtime guest (`runningOnDedicatedRuntimeGuest` keys on that directory). The
|
|
245
|
+
* parent is created by the box's launcher as root and owned by the daemon.
|
|
246
|
+
*/
|
|
247
|
+
exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR = '/run/skrr-hosted-machine/cli-handoff/descriptor.json';
|
|
233
248
|
/** Where the cli-handoff broker looks, in order. */
|
|
234
249
|
function resolveCliHandoffCandidatePaths(profile = 'default') {
|
|
235
250
|
const candidates = resolveBootstrapCandidatePaths(profile);
|
|
236
|
-
// Any profile: a guest runs one daemon
|
|
237
|
-
//
|
|
238
|
-
// only
|
|
251
|
+
// Any profile: a guest runs one daemon holding the one account the guest
|
|
252
|
+
// serves, so every profile on it is that account. On a laptop a named profile
|
|
253
|
+
// reaches only its own island file above (OSK-13257).
|
|
239
254
|
if (process.platform === 'linux') {
|
|
240
|
-
candidates.push(exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR);
|
|
255
|
+
candidates.push(exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR, exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR);
|
|
241
256
|
}
|
|
242
257
|
return candidates;
|
|
243
258
|
}
|
|
@@ -286,21 +301,28 @@ function findLiveDaemonBootstrap({ profile = 'default', bootstrapPathOverride, }
|
|
|
286
301
|
* file is malformed, the CLI shouldn't try to repair it).
|
|
287
302
|
*/
|
|
288
303
|
function readBootstrap(filePath) {
|
|
304
|
+
const inspected = inspectBootstrap(filePath);
|
|
305
|
+
return inspected.state === 'ok' ? inspected.bootstrap : null;
|
|
306
|
+
}
|
|
307
|
+
function inspectBootstrap(filePath) {
|
|
289
308
|
let raw;
|
|
290
309
|
try {
|
|
291
310
|
if (!fs.existsSync(filePath))
|
|
292
|
-
return
|
|
311
|
+
return { state: 'absent' };
|
|
293
312
|
raw = fs.readFileSync(filePath, 'utf8');
|
|
294
313
|
}
|
|
295
|
-
catch {
|
|
296
|
-
|
|
314
|
+
catch (err) {
|
|
315
|
+
// Removed between the exists check and the read: a daemon shutting down.
|
|
316
|
+
if (err.code === 'ENOENT')
|
|
317
|
+
return { state: 'absent' };
|
|
318
|
+
return { state: 'unreadable', detail: err.message };
|
|
297
319
|
}
|
|
298
320
|
let parsedJson;
|
|
299
321
|
try {
|
|
300
322
|
parsedJson = JSON.parse(raw);
|
|
301
323
|
}
|
|
302
|
-
catch {
|
|
303
|
-
return
|
|
324
|
+
catch (err) {
|
|
325
|
+
return { state: 'invalid', detail: `not JSON: ${err.message}` };
|
|
304
326
|
}
|
|
305
327
|
// Shared wire parser (`@skrr-ai/auth-core/cli-handoff-wire`) validates the
|
|
306
328
|
// fields a caller cannot proceed without — host, port, secret — and
|
|
@@ -309,13 +331,13 @@ function readBootstrap(filePath) {
|
|
|
309
331
|
// port must be a real TCP port, and the secret is a fixed-width key.
|
|
310
332
|
const parsed = (0, cli_handoff_wire_1.parseCliHandoffDescriptor)(parsedJson);
|
|
311
333
|
if (!parsed)
|
|
312
|
-
return
|
|
334
|
+
return { state: 'invalid', detail: 'missing host, port or secret' };
|
|
313
335
|
if (parsed.host !== '127.0.0.1' ||
|
|
314
336
|
typeof parsed.port !== 'number' ||
|
|
315
337
|
parsed.port > 65_535 ||
|
|
316
338
|
typeof parsed.secret !== 'string' ||
|
|
317
339
|
Buffer.from(parsed.secret, 'base64').length !== 32) {
|
|
318
|
-
return
|
|
340
|
+
return { state: 'invalid', detail: 'not a loopback descriptor with a 32-byte secret' };
|
|
319
341
|
}
|
|
320
342
|
// PID liveness — ESRCH means the daemon was force-killed and the file
|
|
321
343
|
// outlived the process. EPERM means alive but not signalable by us,
|
|
@@ -327,11 +349,11 @@ function readBootstrap(filePath) {
|
|
|
327
349
|
catch (err) {
|
|
328
350
|
const code = err.code;
|
|
329
351
|
if (code === 'ESRCH')
|
|
330
|
-
return
|
|
352
|
+
return { state: 'dead_pid', pid: parsed.pid, bootstrap: parsed };
|
|
331
353
|
// EPERM / other: keep going.
|
|
332
354
|
}
|
|
333
355
|
}
|
|
334
|
-
return parsed;
|
|
356
|
+
return { state: 'ok', bootstrap: parsed };
|
|
335
357
|
}
|
|
336
358
|
/**
|
|
337
359
|
* Try the daemon-as-broker handoff. Returns a tagged outcome so callers
|
|
@@ -343,12 +365,83 @@ function readBootstrap(filePath) {
|
|
|
343
365
|
* PKCE", and `ok: true` means "we're done, persist + return".
|
|
344
366
|
*/
|
|
345
367
|
async function attemptDaemonBrokerLogin(opts) {
|
|
368
|
+
const first = await attemptDaemonBrokerLoginOnce(opts);
|
|
369
|
+
if (first.ok || !isRestartShapedFailure(first))
|
|
370
|
+
return first;
|
|
371
|
+
// The local service may be restarting: a graceful restart removes the
|
|
372
|
+
// descriptor and republishes it only once the new daemon is listening
|
|
373
|
+
// (measured ~14s), and a crash leaves it naming a dead pid until launchd
|
|
374
|
+
// brings a replacement up. Every `skrr` process in that window used to be
|
|
375
|
+
// told "Not signed in" and succeed on an immediate retry (OSK-13222). Wait —
|
|
376
|
+
// bounded, and only when something says a daemon is coming back — and ask
|
|
377
|
+
// again.
|
|
346
378
|
const profile = opts.profile ?? 'default';
|
|
347
|
-
const
|
|
379
|
+
const failedFingerprints = new Set();
|
|
380
|
+
if (first.descriptorPort !== undefined || first.descriptorPid !== undefined) {
|
|
381
|
+
failedFingerprints.add(`${first.descriptorPid ?? ''}:${first.descriptorPort ?? ''}`);
|
|
382
|
+
}
|
|
383
|
+
const waited = await (0, daemonRestartWait_1.awaitRestartedDaemon)({
|
|
384
|
+
profile,
|
|
385
|
+
...(opts.restartWait ?? {}),
|
|
386
|
+
ready: () => {
|
|
387
|
+
const fresh = listUsableBootstraps(opts);
|
|
388
|
+
return fresh.some(({ bootstrap }) => !failedFingerprints.has(`${bootstrap.pid ?? ''}:${bootstrap.port}`));
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
if (!waited.waited)
|
|
392
|
+
return first;
|
|
393
|
+
const second = await attemptDaemonBrokerLoginOnce(opts);
|
|
394
|
+
if (second.ok)
|
|
395
|
+
return second;
|
|
396
|
+
return { ...second, restartWaitedMs: waited.waitedMs };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* A failure that looks like the local daemon is absent or mid-restart rather
|
|
400
|
+
* than a daemon that answered: no usable descriptor, a refused port, or an
|
|
401
|
+
* uncoded 401 on a secret it rotates when it restarts.
|
|
402
|
+
*/
|
|
403
|
+
function isRestartShapedFailure(failure) {
|
|
404
|
+
if (failure.dedicatedGuest)
|
|
405
|
+
return false;
|
|
406
|
+
switch (failure.reason) {
|
|
407
|
+
case 'no_bootstrap':
|
|
408
|
+
case 'bootstrap_parse':
|
|
409
|
+
case 'network':
|
|
410
|
+
return true;
|
|
411
|
+
case 'stale_bootstrap':
|
|
412
|
+
return !failure.code;
|
|
413
|
+
default:
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function candidatePathsFor(opts) {
|
|
418
|
+
const profile = opts.profile ?? 'default';
|
|
419
|
+
return opts.candidatePathsOverride
|
|
348
420
|
? [...opts.candidatePathsOverride]
|
|
349
421
|
: opts.bootstrapPathOverride
|
|
350
422
|
? [opts.bootstrapPathOverride]
|
|
351
423
|
: resolveCliHandoffCandidatePaths(profile);
|
|
424
|
+
}
|
|
425
|
+
/** Live descriptors whose serverUrl does not contradict this CLI's baseURL. */
|
|
426
|
+
function listUsableBootstraps(opts) {
|
|
427
|
+
const out = [];
|
|
428
|
+
for (const candidate of candidatePathsFor(opts)) {
|
|
429
|
+
const bootstrap = readBootstrap(candidate);
|
|
430
|
+
if (!bootstrap)
|
|
431
|
+
continue;
|
|
432
|
+
if (typeof bootstrap.serverUrl === 'string' &&
|
|
433
|
+
bootstrap.serverUrl.length > 0 &&
|
|
434
|
+
typeof opts.baseURL === 'string' &&
|
|
435
|
+
opts.baseURL.length > 0 &&
|
|
436
|
+
normalizeBaseUrl(opts.baseURL) !== normalizeBaseUrl(bootstrap.serverUrl)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
out.push({ bootstrap, sourcePath: candidate });
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
async function attemptDaemonBrokerLoginOnce(opts) {
|
|
444
|
+
const candidates = candidatePathsFor(opts);
|
|
352
445
|
// Bound candidates (their serverUrl matches this CLI) in order, then unbound
|
|
353
446
|
// ones. Every usable candidate is kept, not just the first: see below.
|
|
354
447
|
// `sourcePath` rides along — the brokered-migration cleanup keys on whether
|
|
@@ -357,10 +450,18 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
357
450
|
const unbound = [];
|
|
358
451
|
let mismatchedBootstrap = null;
|
|
359
452
|
let mismatchedPath = null;
|
|
453
|
+
// The first descriptor that EXISTED but could not be used, so a failure can
|
|
454
|
+
// say what was on disk instead of "no daemon" (OSK-13222).
|
|
455
|
+
let unusableDescriptor = null;
|
|
360
456
|
for (const candidate of candidates) {
|
|
361
|
-
const
|
|
362
|
-
if (
|
|
457
|
+
const inspection = inspectBootstrap(candidate);
|
|
458
|
+
if (inspection.state !== 'ok') {
|
|
459
|
+
if (inspection.state !== 'absent' && !unusableDescriptor) {
|
|
460
|
+
unusableDescriptor = { path: candidate, inspection };
|
|
461
|
+
}
|
|
363
462
|
continue;
|
|
463
|
+
}
|
|
464
|
+
const candidateBootstrap = inspection.bootstrap;
|
|
364
465
|
if (typeof candidateBootstrap.serverUrl === 'string' &&
|
|
365
466
|
candidateBootstrap.serverUrl.length > 0 &&
|
|
366
467
|
typeof opts.baseURL === 'string' &&
|
|
@@ -392,6 +493,25 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
392
493
|
...(mismatchedPath ? { descriptorPath: mismatchedPath } : {}),
|
|
393
494
|
};
|
|
394
495
|
}
|
|
496
|
+
if (unusableDescriptor) {
|
|
497
|
+
const { path: descriptorPath, inspection } = unusableDescriptor;
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
reason: 'no_bootstrap',
|
|
501
|
+
detail: inspection.state === 'dead_pid'
|
|
502
|
+
? `The daemon hand-off ${descriptorPath} names pid ${inspection.pid}, which has exited`
|
|
503
|
+
: `The daemon hand-off ${descriptorPath} could not be used: ${inspection.detail}`,
|
|
504
|
+
descriptorPath,
|
|
505
|
+
descriptorState: inspection.state,
|
|
506
|
+
...(inspection.state === 'dead_pid'
|
|
507
|
+
? {
|
|
508
|
+
descriptorPid: inspection.pid,
|
|
509
|
+
descriptorPort: inspection.bootstrap.port,
|
|
510
|
+
...(inspection.bootstrap.daemonId ? { daemonId: inspection.bootstrap.daemonId } : {}),
|
|
511
|
+
}
|
|
512
|
+
: {}),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
395
515
|
return {
|
|
396
516
|
ok: false,
|
|
397
517
|
reason: 'no_bootstrap',
|
|
@@ -413,6 +533,8 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
413
533
|
outcome = {
|
|
414
534
|
...outcome,
|
|
415
535
|
descriptorPath: outcome.descriptorPath ?? sourcePath,
|
|
536
|
+
descriptorPort: bootstrap.port,
|
|
537
|
+
...(typeof bootstrap.pid === 'number' ? { descriptorPid: bootstrap.pid } : {}),
|
|
416
538
|
...(bootstrap.daemonId ? { daemonId: bootstrap.daemonId } : {}),
|
|
417
539
|
...(sourcePath === exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR
|
|
418
540
|
? { dedicatedGuest: true }
|
|
@@ -432,18 +554,41 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
|
|
|
432
554
|
// either the caller prefers broker mode (auto-broker) or the durable mint is
|
|
433
555
|
// not offered at all (the Dedicated guest shape — its hand-off-only secret
|
|
434
556
|
// cannot mint a family, so the token route is the only door).
|
|
557
|
+
//
|
|
558
|
+
// The no-durable-mint policy for the AUTOMATIC path lives here, in the CLI,
|
|
559
|
+
// and not in the daemon (OSK-12018). On a laptop the daemon cannot tell an
|
|
560
|
+
// explicit `skrr login` from an agent-originated implicit sign-in — same
|
|
561
|
+
// uid, same island secret — so a daemon-side refusal of the legacy mint
|
|
562
|
+
// would either break explicit login or be bypassed by anyone who calls it
|
|
563
|
+
// the way login does. It is not a boundary, so it is not attempted. What the
|
|
564
|
+
// CLI CAN guarantee is that its own automatic path never mints and persists
|
|
565
|
+
// a refresh family: when `access_token` is advertised, a failed redeem is
|
|
566
|
+
// the answer, and the daemon's outage is reported with its repair.
|
|
567
|
+
//
|
|
568
|
+
// COMPATIBILITY WINDOW: a v1 descriptor (no `handoffModes`, so no
|
|
569
|
+
// `access_token`) still takes the durable mint on the automatic path below,
|
|
570
|
+
// because that is the only door an older daemon has and refusing it would
|
|
571
|
+
// sign every laptop user out on a CLI upgrade. Remove that fallback for the
|
|
572
|
+
// automatic path once no supported laptop daemon still writes a v1 island
|
|
573
|
+
// descriptor — the v2 island descriptor (`handoffModes` in
|
|
574
|
+
// `daemon/src/local-server.ts`) and its token route shipped together in
|
|
575
|
+
// 1a636216b0, so the condition is "every daemon in use is at or past that
|
|
576
|
+
// release", which is a fleet measurement, not a date.
|
|
435
577
|
const modes = (0, cli_handoff_wire_1.advertisedHandoffModes)(bootstrap);
|
|
436
|
-
const offersAccessToken =
|
|
578
|
+
const offersAccessToken = (0, cli_handoff_wire_1.descriptorOffersAccessToken)(bootstrap);
|
|
437
579
|
const offersRefreshFamily = modes.includes('refresh_family');
|
|
438
580
|
const preferAccessToken = opts.preferHandoffMode === 'access_token';
|
|
439
581
|
if (offersAccessToken && (preferAccessToken || !offersRefreshFamily)) {
|
|
440
582
|
const redeem = await redeemCliHandoffToken(bootstrap, {
|
|
441
583
|
forceRefresh: false,
|
|
442
584
|
timeoutMs: opts.timeoutMs,
|
|
585
|
+
// OSK-12020 — ask for a key-bound token whenever the daemon can serve
|
|
586
|
+
// its proofs. Whether it IS bound is read from the answer's tokenType.
|
|
587
|
+
dpop: (0, cli_handoff_wire_1.descriptorOffersDpop)(bootstrap),
|
|
443
588
|
});
|
|
444
589
|
if (redeem.ok) {
|
|
445
590
|
const response = redeem.body;
|
|
446
|
-
registerBrokeredHandoff(bootstrap, response.accessToken, sourcePath);
|
|
591
|
+
registerBrokeredHandoff(bootstrap, response.accessToken, sourcePath, response.tokenType === 'DPoP');
|
|
447
592
|
if (sourcePath === exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR) {
|
|
448
593
|
// A guest descriptor's secret can ONLY redeem access tokens, so a
|
|
449
594
|
// stored cli-auth.json on this machine predates broker mode. Retire
|
|
@@ -467,20 +612,17 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
|
|
|
467
612
|
...(sourcePath ? { descriptorPath: sourcePath } : {}),
|
|
468
613
|
};
|
|
469
614
|
}
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
// through
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
if (!minted.ok && !minted.code && redeem.code && redeem.reason !== 'network')
|
|
482
|
-
return redeem;
|
|
483
|
-
return minted;
|
|
615
|
+
// A redeem failure is final for the automatic path even when the
|
|
616
|
+
// descriptor ALSO offers the durable mint (the laptop island): falling
|
|
617
|
+
// through would silently mint and persist a refresh family nobody asked
|
|
618
|
+
// for, which is exactly what broker mode exists to stop (OSK-12018). The
|
|
619
|
+
// caller reports the outage (`describeBrokerRefusal`) and names
|
|
620
|
+
// `skrr login` as the way to a durable personal sign-in. When
|
|
621
|
+
// refresh_family is NOT offered (a guest) the legacy route would only
|
|
622
|
+
// answer 403 HANDOFF_BROKER_ONLY anyway. (An explicit `skrr login`
|
|
623
|
+
// against a laptop island never enters this branch: it prefers
|
|
624
|
+
// refresh_family, which is offered, and takes the mint below directly.)
|
|
625
|
+
return redeem;
|
|
484
626
|
}
|
|
485
627
|
return mintRefreshFamilyThrough(bootstrap, opts);
|
|
486
628
|
}
|
|
@@ -508,7 +650,10 @@ async function redeemCliHandoffToken(bootstrap, opts = {}) {
|
|
|
508
650
|
// No cliId — the family is daemon-owned; the caller is not enrolling a
|
|
509
651
|
// device. forceRefresh bypasses the daemon's cached access token (the
|
|
510
652
|
// 401-recovery path).
|
|
511
|
-
body: JSON.stringify({
|
|
653
|
+
body: JSON.stringify({
|
|
654
|
+
forceRefresh: opts.forceRefresh === true,
|
|
655
|
+
...(opts.dpop ? { dpop: true } : {}),
|
|
656
|
+
}),
|
|
512
657
|
signal: controller.signal,
|
|
513
658
|
});
|
|
514
659
|
}
|
|
@@ -766,8 +911,24 @@ async function mintRefreshFamilyThrough(bootstrap, opts) {
|
|
|
766
911
|
* and re-redeems on entry via `maybeAutoBroker`.
|
|
767
912
|
*/
|
|
768
913
|
let _brokeredHandoff = null;
|
|
769
|
-
function registerBrokeredHandoff(descriptor, accessToken, sourcePath) {
|
|
914
|
+
function registerBrokeredHandoff(descriptor, accessToken, sourcePath, dpopBound = false) {
|
|
770
915
|
_brokeredHandoff = { descriptor, accessToken, ...(sourcePath ? { sourcePath } : {}) };
|
|
916
|
+
// OSK-12020 — a token the daemon reported as `tokenType: 'DPoP'` carries
|
|
917
|
+
// `cnf.jkt`; every request it makes needs a fresh proof from the daemon's
|
|
918
|
+
// key (`brokered-dpop.ts`). Never inferred from having asked: an older
|
|
919
|
+
// server ignores the binding request and the daemon then says `Bearer`.
|
|
920
|
+
if (dpopBound) {
|
|
921
|
+
(0, brokered_dpop_1.registerDpopBinding)(accessToken, () => {
|
|
922
|
+
const current = (sourcePath ? readBootstrap(sourcePath) : null) ?? descriptor;
|
|
923
|
+
if (!current.host || !current.port || !current.secret) {
|
|
924
|
+
throw new Error('hand-off descriptor is missing its loopback address');
|
|
925
|
+
}
|
|
926
|
+
return { host: current.host, port: current.port, secret: current.secret };
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
(0, brokered_dpop_1.clearDpopBinding)();
|
|
931
|
+
}
|
|
771
932
|
}
|
|
772
933
|
/**
|
|
773
934
|
* True when `token` is the access token this process redeemed through the
|
|
@@ -790,15 +951,19 @@ async function redeemBrokeredHandoffToken(forceRefresh = true) {
|
|
|
790
951
|
if (!state)
|
|
791
952
|
return null;
|
|
792
953
|
const descriptor = (state.sourcePath ? readBootstrap(state.sourcePath) : null) ?? state.descriptor;
|
|
793
|
-
const result = await redeemCliHandoffToken(descriptor, {
|
|
954
|
+
const result = await redeemCliHandoffToken(descriptor, {
|
|
955
|
+
forceRefresh,
|
|
956
|
+
dpop: (0, cli_handoff_wire_1.descriptorOffersDpop)(descriptor),
|
|
957
|
+
});
|
|
794
958
|
if (!result.ok)
|
|
795
959
|
return null;
|
|
796
|
-
|
|
960
|
+
registerBrokeredHandoff(descriptor, result.body.accessToken, state.sourcePath, result.body.tokenType === 'DPoP');
|
|
797
961
|
return result.body;
|
|
798
962
|
}
|
|
799
963
|
/** @internal test seam — clears the module-scoped brokered credential. */
|
|
800
964
|
function __resetBrokeredHandoffForTest() {
|
|
801
965
|
_brokeredHandoff = null;
|
|
966
|
+
(0, brokered_dpop_1.clearDpopBinding)();
|
|
802
967
|
}
|
|
803
968
|
/**
|
|
804
969
|
* True when a live local descriptor advertises broker-mode access — i.e.
|
|
@@ -908,7 +1073,7 @@ function shouldBrokerBeforeStoredCredential(descriptorPath = exports.DEDICATED_R
|
|
|
908
1073
|
if (!bootstrap)
|
|
909
1074
|
return false;
|
|
910
1075
|
const modes = (0, cli_handoff_wire_1.advertisedHandoffModes)(bootstrap);
|
|
911
|
-
if (!
|
|
1076
|
+
if (!(0, cli_handoff_wire_1.descriptorOffersAccessToken)(bootstrap) || modes.includes('refresh_family'))
|
|
912
1077
|
return false;
|
|
913
1078
|
return retirableCliAuthFamily(bootstrap.serverUrl) !== null;
|
|
914
1079
|
}
|
|
@@ -1052,7 +1217,10 @@ const AUTH_SELF_MANAGED_COMMANDS = new Set([
|
|
|
1052
1217
|
* 'access_token'`. On a descriptor that advertises broker mode the
|
|
1053
1218
|
* outcome is `brokered` — an in-memory access token, nothing written;
|
|
1054
1219
|
* the caller holds it for this process and the next `skrr` re-redeems.
|
|
1055
|
-
*
|
|
1220
|
+
* A failed redeem on such a descriptor is returned as the outcome and
|
|
1221
|
+
* never retried as a durable mint (OSK-12018). Only on a legacy/v1
|
|
1222
|
+
* descriptor (no `access_token` advertised — the compatibility window
|
|
1223
|
+
* documented in `brokerThrough`) is the durable mint persisted to the
|
|
1056
1224
|
* keychain / cli-auth.json exactly as before.
|
|
1057
1225
|
* - May generate a new `cliId` if the config doesn't have one. The
|
|
1058
1226
|
* caller MUST persist `updatedConfig` so the next invocation
|
|
@@ -1085,6 +1253,7 @@ async function maybeAutoBroker(opts) {
|
|
|
1085
1253
|
// family mint remains the fallback for v1 descriptors and the redeem
|
|
1086
1254
|
// path for explicit `skrr login`.
|
|
1087
1255
|
preferHandoffMode: 'access_token',
|
|
1256
|
+
profile: opts.profile ?? (0, keychain_1.getActiveProfile)(),
|
|
1088
1257
|
...(opts.bootstrapPathOverride ? { bootstrapPathOverride: opts.bootstrapPathOverride } : {}),
|
|
1089
1258
|
});
|
|
1090
1259
|
return {
|
|
@@ -32,6 +32,12 @@ export interface BrokerRefusalDescription {
|
|
|
32
32
|
transient: boolean;
|
|
33
33
|
/** True when the refusing daemon is a Dedicated Runtime guest's. */
|
|
34
34
|
dedicatedGuest: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* True when no daemon answered because the local service is down or
|
|
37
|
+
* restarting (OSK-13222). The person is not signed out — their sign-in
|
|
38
|
+
* lives in that service — so the message must not open with "Not signed in".
|
|
39
|
+
*/
|
|
40
|
+
daemonUnavailable?: boolean;
|
|
35
41
|
/** Machine-readable twin for `--json` error details. */
|
|
36
42
|
broker: {
|
|
37
43
|
reason: DaemonBrokerFailure['reason'];
|
|
@@ -41,6 +47,10 @@ export interface BrokerRefusalDescription {
|
|
|
41
47
|
detail?: string;
|
|
42
48
|
descriptorPath?: string;
|
|
43
49
|
daemonId?: string;
|
|
50
|
+
descriptorState?: DaemonBrokerFailure['descriptorState'];
|
|
51
|
+
descriptorPid?: number;
|
|
52
|
+
descriptorPort?: number;
|
|
53
|
+
restartWaitedMs?: number;
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
56
|
/**
|
|
@@ -70,3 +80,19 @@ export declare function describeGuestDaemonUnavailable(failure: DaemonBrokerFail
|
|
|
70
80
|
onGuest: boolean;
|
|
71
81
|
descriptorPresent?: boolean;
|
|
72
82
|
}): BrokerRefusalDescription | null;
|
|
83
|
+
/**
|
|
84
|
+
* What to tell a person on their own computer when NO daemon answered the
|
|
85
|
+
* hand-off, but something on disk says one was there (OSK-13222).
|
|
86
|
+
*
|
|
87
|
+
* The laptop twin of `describeGuestDaemonUnavailable`. A daemon that crashed
|
|
88
|
+
* leaves its descriptor naming a dead pid; one mid-restart has a descriptor
|
|
89
|
+
* whose port refuses, or a secret it already rotated; or the CLI waited for a
|
|
90
|
+
* restart that did not finish. Each of these used to read "Not signed in. Run
|
|
91
|
+
* `skrr login` first." — a claim about the person's account made on evidence
|
|
92
|
+
* about a local process. Their sign-in is held by that service; say the
|
|
93
|
+
* service is down and name what was read.
|
|
94
|
+
*
|
|
95
|
+
* Returns null when there is no evidence of a daemon at all (no descriptor,
|
|
96
|
+
* nothing waited for): "Not signed in" is then the true sentence.
|
|
97
|
+
*/
|
|
98
|
+
export declare function describeLocalDaemonUnavailable(failure: DaemonBrokerFailure, bin?: string): BrokerRefusalDescription | null;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isAnsweredBrokerRefusal = isAnsweredBrokerRefusal;
|
|
4
4
|
exports.describeBrokerRefusal = describeBrokerRefusal;
|
|
5
5
|
exports.describeGuestDaemonUnavailable = describeGuestDaemonUnavailable;
|
|
6
|
+
exports.describeLocalDaemonUnavailable = describeLocalDaemonUnavailable;
|
|
6
7
|
/**
|
|
7
8
|
* True when a daemon ANSWERED the hand-off and refused it — as opposed to no
|
|
8
9
|
* daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
|
|
@@ -97,18 +98,18 @@ function describeBrokerRefusal(failure, bin = 'skrr') {
|
|
|
97
98
|
}
|
|
98
99
|
else if (failure.reason === 'ci_token_refused') {
|
|
99
100
|
remedy =
|
|
100
|
-
`The background service is signed in with a CI token, which cannot sign a person in. Use \`${bin} login
|
|
101
|
-
`
|
|
101
|
+
`The background service is signed in with a CI token, which cannot sign a person in. Use \`${bin} login\` ` +
|
|
102
|
+
`(\`--device-code\` without a terminal). A \`${bin} create-token\` token would not help: ${bin} commands reject it.`;
|
|
102
103
|
}
|
|
103
104
|
else if (transient) {
|
|
104
105
|
remedy =
|
|
105
106
|
`This is usually temporary; retry in a minute. \`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side, ` +
|
|
106
|
-
`and \`${bin} login\`
|
|
107
|
+
`and \`${bin} login\` gives this CLI a durable sign-in of its own, without the service.`;
|
|
107
108
|
}
|
|
108
109
|
else {
|
|
109
110
|
remedy =
|
|
110
111
|
`\`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side. ` +
|
|
111
|
-
`\`${bin} login\`
|
|
112
|
+
`\`${bin} login\` gives this CLI a durable sign-in of its own, without the service.`;
|
|
112
113
|
}
|
|
113
114
|
return {
|
|
114
115
|
headline: refusal,
|
|
@@ -210,3 +211,81 @@ function describeGuestDaemonUnavailable(failure, opts) {
|
|
|
210
211
|
},
|
|
211
212
|
};
|
|
212
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* What to tell a person on their own computer when NO daemon answered the
|
|
216
|
+
* hand-off, but something on disk says one was there (OSK-13222).
|
|
217
|
+
*
|
|
218
|
+
* The laptop twin of `describeGuestDaemonUnavailable`. A daemon that crashed
|
|
219
|
+
* leaves its descriptor naming a dead pid; one mid-restart has a descriptor
|
|
220
|
+
* whose port refuses, or a secret it already rotated; or the CLI waited for a
|
|
221
|
+
* restart that did not finish. Each of these used to read "Not signed in. Run
|
|
222
|
+
* `skrr login` first." — a claim about the person's account made on evidence
|
|
223
|
+
* about a local process. Their sign-in is held by that service; say the
|
|
224
|
+
* service is down and name what was read.
|
|
225
|
+
*
|
|
226
|
+
* Returns null when there is no evidence of a daemon at all (no descriptor,
|
|
227
|
+
* nothing waited for): "Not signed in" is then the true sentence.
|
|
228
|
+
*/
|
|
229
|
+
function describeLocalDaemonUnavailable(failure, bin = 'skrr') {
|
|
230
|
+
if (failure.dedicatedGuest)
|
|
231
|
+
return null;
|
|
232
|
+
if (isAnsweredBrokerRefusal(failure))
|
|
233
|
+
return null;
|
|
234
|
+
const who = `The skrr background service on this computer${failure.daemonId ? ` (daemon ${failure.daemonId})` : ''}`;
|
|
235
|
+
const where = failure.descriptorPath ? ` (${failure.descriptorPath})` : '';
|
|
236
|
+
const waited = typeof failure.restartWaitedMs === 'number' && failure.restartWaitedMs > 0
|
|
237
|
+
? ` Waited ${Math.max(1, Math.round(failure.restartWaitedMs / 1000))}s for it to restart.`
|
|
238
|
+
: '';
|
|
239
|
+
let headline = null;
|
|
240
|
+
if (failure.reason === 'network' && failure.descriptorPath) {
|
|
241
|
+
headline =
|
|
242
|
+
`${who} is not answering: its sign-in hand-off${where} names port ` +
|
|
243
|
+
`${failure.descriptorPort ?? '?'} on 127.0.0.1, where nothing is listening.`;
|
|
244
|
+
}
|
|
245
|
+
else if (failure.reason === 'stale_bootstrap') {
|
|
246
|
+
headline = `${who} refused this CLI's hand-off secret${where}, which it rotates when it restarts.`;
|
|
247
|
+
}
|
|
248
|
+
else if (failure.reason === 'bootstrap_parse' ||
|
|
249
|
+
failure.descriptorState === 'unreadable' ||
|
|
250
|
+
failure.descriptorState === 'invalid') {
|
|
251
|
+
headline = `${who} is not available: its sign-in hand-off${where} could not be read${failure.detail ? ` (${failure.detail.replace(/[.]$/, '')})` : ''}.`;
|
|
252
|
+
}
|
|
253
|
+
else if (failure.descriptorState === 'dead_pid') {
|
|
254
|
+
headline =
|
|
255
|
+
`${who} is not running: its sign-in hand-off${where} names pid ` +
|
|
256
|
+
`${failure.descriptorPid ?? '?'}, which has exited (a crash, or a restart in progress).`;
|
|
257
|
+
}
|
|
258
|
+
else if (failure.reason === 'no_bootstrap' && waited) {
|
|
259
|
+
headline = `${who} was restarting and did not publish its sign-in hand-off in time.`;
|
|
260
|
+
}
|
|
261
|
+
if (!headline)
|
|
262
|
+
return null;
|
|
263
|
+
const remedy = 'You are not signed out: your sign-in is held by that service, which normally restarts on its own. ' +
|
|
264
|
+
`Retry in a moment. \`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side, ` +
|
|
265
|
+
`and \`${bin} login\` signs this CLI in without it.`;
|
|
266
|
+
return {
|
|
267
|
+
headline: `${headline}${waited}`,
|
|
268
|
+
remedy,
|
|
269
|
+
transient: true,
|
|
270
|
+
dedicatedGuest: false,
|
|
271
|
+
daemonUnavailable: true,
|
|
272
|
+
broker: {
|
|
273
|
+
reason: failure.reason,
|
|
274
|
+
...(typeof failure.status === 'number' ? { status: failure.status } : {}),
|
|
275
|
+
...(failure.code ? { code: failure.code } : {}),
|
|
276
|
+
...(failure.detail ? { detail: failure.detail } : {}),
|
|
277
|
+
...(failure.descriptorPath ? { descriptorPath: failure.descriptorPath } : {}),
|
|
278
|
+
...(failure.daemonId ? { daemonId: failure.daemonId } : {}),
|
|
279
|
+
...(failure.descriptorState ? { descriptorState: failure.descriptorState } : {}),
|
|
280
|
+
...(typeof failure.descriptorPid === 'number'
|
|
281
|
+
? { descriptorPid: failure.descriptorPid }
|
|
282
|
+
: {}),
|
|
283
|
+
...(typeof failure.descriptorPort === 'number'
|
|
284
|
+
? { descriptorPort: failure.descriptorPort }
|
|
285
|
+
: {}),
|
|
286
|
+
...(typeof failure.restartWaitedMs === 'number'
|
|
287
|
+
? { restartWaitedMs: failure.restartWaitedMs }
|
|
288
|
+
: {}),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type DaemonRestartSignal = 'starting' | 'supervised' | 'none';
|
|
2
|
+
/** A lock acquired within this window belongs to a daemon still starting up. */
|
|
3
|
+
export declare const STARTING_WINDOW_MS = 60000;
|
|
4
|
+
/** Total wait once a starting daemon is seen. Startup was measured at ~14s. */
|
|
5
|
+
export declare const DEFAULT_RESTART_WAIT_MS = 20000;
|
|
6
|
+
/** Wait for a supervisor to start a daemon when none holds the lock yet. */
|
|
7
|
+
export declare const SUPERVISED_GRACE_MS = 4000;
|
|
8
|
+
export declare const DEFAULT_POLL_MS = 250;
|
|
9
|
+
/**
|
|
10
|
+
* Read the restart signal for `profile` from the daemon's own on-disk state.
|
|
11
|
+
* Never throws; anything unreadable reads as absent.
|
|
12
|
+
*/
|
|
13
|
+
export declare function probeDaemonRestart(profile: string, opts?: {
|
|
14
|
+
homeDir?: string;
|
|
15
|
+
now?: number;
|
|
16
|
+
platform?: NodeJS.Platform;
|
|
17
|
+
}): DaemonRestartSignal;
|
|
18
|
+
export interface AwaitRestartedDaemonOptions {
|
|
19
|
+
profile: string;
|
|
20
|
+
/** True once a fresh descriptor the caller has not already failed on exists. */
|
|
21
|
+
ready: () => boolean;
|
|
22
|
+
/** Total budget once a starting daemon is seen. `SKRR_DAEMON_RESTART_WAIT_MS` overrides; 0 disables. */
|
|
23
|
+
maxWaitMs?: number;
|
|
24
|
+
supervisedGraceMs?: number;
|
|
25
|
+
pollMs?: number;
|
|
26
|
+
probe?: (profile: string) => DaemonRestartSignal;
|
|
27
|
+
now?: () => number;
|
|
28
|
+
sleep?: (ms: number) => Promise<void>;
|
|
29
|
+
/** Told once when the wait begins. Defaults to one stderr line. */
|
|
30
|
+
notify?: (message: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface AwaitRestartedDaemonResult {
|
|
33
|
+
/** True when a restart was plausible and the CLI waited for it. */
|
|
34
|
+
waited: boolean;
|
|
35
|
+
waitedMs: number;
|
|
36
|
+
/** True when a fresh descriptor appeared inside the budget. */
|
|
37
|
+
ready: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function awaitRestartedDaemon(opts: AwaitRestartedDaemonOptions): Promise<AwaitRestartedDaemonResult>;
|