@skrr-ai/cli 0.1.50 → 0.1.52
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 +7 -0
- package/dist/base-command.js +95 -8
- package/dist/commands/agents/chat.d.ts +14 -0
- package/dist/commands/agents/chat.js +37 -1
- package/dist/commands/agents/create.js +8 -4
- package/dist/commands/agents/show.d.ts +9 -0
- package/dist/commands/agents/show.js +53 -0
- package/dist/commands/agents/update.d.ts +32 -0
- package/dist/commands/agents/update.js +81 -9
- package/dist/commands/balance/show.d.ts +34 -0
- package/dist/commands/balance/show.js +92 -2
- package/dist/commands/commitments/doctor.d.ts +1 -0
- package/dist/commands/commitments/doctor.js +19 -4
- package/dist/commands/commitments/effective-policy.js +15 -1
- package/dist/commands/commitments/next-tick.js +12 -2
- package/dist/commands/commitments/preflight.js +10 -0
- package/dist/commands/commitments/show.js +11 -1
- package/dist/commands/context.js +2 -1
- package/dist/commands/daemon/config.d.ts +15 -0
- package/dist/commands/daemon/config.js +53 -0
- package/dist/commands/daemon/index.js +1 -0
- package/dist/commands/daemon/status.js +15 -1
- package/dist/commands/doctor.js +8 -1
- package/dist/commands/harnesses/models.d.ts +54 -0
- package/dist/commands/harnesses/models.js +100 -7
- package/dist/commands/harnesses/show.d.ts +11 -0
- package/dist/commands/harnesses/show.js +35 -0
- package/dist/commands/harnesses/update.d.ts +28 -1
- package/dist/commands/harnesses/update.js +73 -10
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.js +26 -7
- package/dist/commands/logout.js +3 -0
- package/dist/commands/machines/dedicated/show.js +25 -2
- package/dist/commands/outbox/list.d.ts +12 -1
- package/dist/commands/outbox/list.js +31 -2
- package/dist/commands/revoke-daemon.js +9 -0
- package/dist/commands/spaces/create.d.ts +1 -0
- package/dist/commands/spaces/create.js +17 -0
- package/dist/commands/spaces/show.js +5 -0
- package/dist/commands/spaces/update.d.ts +1 -0
- package/dist/commands/spaces/update.js +16 -0
- package/dist/commands/tasks/create.d.ts +6 -0
- package/dist/commands/tasks/create.js +31 -6
- package/dist/commands/tasks/show.js +45 -0
- package/dist/commands/tasks/start.d.ts +14 -0
- package/dist/commands/tasks/start.js +31 -1
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +29 -1
- package/dist/commands/tasks/upsert.d.ts +1 -0
- package/dist/commands/tasks/upsert.js +16 -0
- package/dist/commands/whoami.js +5 -3
- package/dist/lib/agent-config.d.ts +2 -0
- package/dist/lib/agent-config.js +14 -10
- package/dist/lib/agentic-stream.d.ts +88 -1
- package/dist/lib/agentic-stream.js +330 -9
- package/dist/lib/auth-core-init.d.ts +2 -10
- package/dist/lib/auth-core-init.js +20 -67
- package/dist/lib/auth-failure.d.ts +33 -0
- package/dist/lib/auth-failure.js +68 -0
- package/dist/lib/auth-storage.d.ts +24 -0
- package/dist/lib/auth-storage.js +342 -11
- package/dist/lib/cli-id.d.ts +22 -0
- package/dist/lib/cli-id.js +56 -0
- package/dist/lib/cli-identity.d.ts +49 -0
- package/dist/lib/cli-identity.js +159 -0
- package/dist/lib/commitments.d.ts +25 -1
- package/dist/lib/commitments.js +46 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +15 -3
- package/dist/lib/daemonBroker.d.ts +37 -1
- package/dist/lib/daemonBroker.js +146 -22
- package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
- package/dist/lib/daemonBrokerRefusal.js +127 -0
- package/dist/lib/dedicated-machines.d.ts +33 -2
- package/dist/lib/dedicated-machines.js +50 -1
- package/dist/lib/delegated-cli.js +20 -1
- package/dist/lib/exec-runtime-binary.d.ts +3 -1
- package/dist/lib/exec-runtime-binary.js +4 -2
- package/dist/lib/first-party-harness-broker.js +10 -5
- package/dist/lib/harnesses.d.ts +10 -0
- package/dist/lib/keychain.d.ts +39 -0
- package/dist/lib/keychain.js +228 -39
- package/dist/lib/label-ref.d.ts +34 -0
- package/dist/lib/label-ref.js +68 -1
- package/dist/lib/live-runtime-binary.d.ts +40 -0
- package/dist/lib/live-runtime-binary.js +193 -0
- package/dist/lib/login.d.ts +23 -0
- package/dist/lib/login.js +67 -21
- package/dist/lib/message-intent.js +3 -1
- package/dist/lib/oauthLogin.js +2 -8
- package/dist/lib/refresh.js +6 -2
- package/dist/lib/tasks.d.ts +20 -0
- package/dist/lib/tasks.js +87 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3139 -3097
- package/oclif.manifest.json +28591 -28438
- package/package.json +1 -1
package/dist/lib/daemonBroker.js
CHANGED
|
@@ -77,6 +77,7 @@ exports.resolveBootstrapPath = resolveBootstrapPath;
|
|
|
77
77
|
exports.resolveProfileBootstrapPaths = resolveProfileBootstrapPaths;
|
|
78
78
|
exports.resolveBootstrapCandidatePaths = resolveBootstrapCandidatePaths;
|
|
79
79
|
exports.resolveCliHandoffCandidatePaths = resolveCliHandoffCandidatePaths;
|
|
80
|
+
exports.resolveExactProfileBootstrapPaths = resolveExactProfileBootstrapPaths;
|
|
80
81
|
exports.findLiveDaemonBootstrap = findLiveDaemonBootstrap;
|
|
81
82
|
exports.readBootstrap = readBootstrap;
|
|
82
83
|
exports.attemptDaemonBrokerLogin = attemptDaemonBrokerLogin;
|
|
@@ -91,10 +92,11 @@ const fs = __importStar(require("node:fs"));
|
|
|
91
92
|
const path = __importStar(require("node:path"));
|
|
92
93
|
const os = __importStar(require("node:os"));
|
|
93
94
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
95
|
+
const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
|
|
94
96
|
const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
|
|
95
97
|
const auth_storage_1 = require("./auth-storage");
|
|
96
98
|
const config_1 = require("./config");
|
|
97
|
-
const
|
|
99
|
+
const cli_identity_1 = require("./cli-identity");
|
|
98
100
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
99
101
|
/**
|
|
100
102
|
* Normalize a base URL for equality comparison. Mirrors the daemon-side
|
|
@@ -118,6 +120,19 @@ function normalizeBaseUrl(u) {
|
|
|
118
120
|
return (0, config_1.canonicalizeKnownBaseURL)(trimmed.toLowerCase());
|
|
119
121
|
}
|
|
120
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Codes the daemon's legacy `/v1/auth/cli-handoff` route writes itself. Every
|
|
125
|
+
* other coded body on that route is the server's, passed through verbatim.
|
|
126
|
+
*/
|
|
127
|
+
const DAEMON_OWN_HANDOFF_CODES = new Set([
|
|
128
|
+
cli_handoff_wire_1.CLI_HANDOFF_ERROR_BROKER_ONLY,
|
|
129
|
+
'BODY_TOO_LARGE',
|
|
130
|
+
'BODY_PARSE_FAILED',
|
|
131
|
+
'CLI_ID_INVALID',
|
|
132
|
+
'BASE_URL_MISMATCH',
|
|
133
|
+
'DAEMON_NOT_AUTHED',
|
|
134
|
+
'UPSTREAM_UNREACHABLE',
|
|
135
|
+
]);
|
|
121
136
|
/**
|
|
122
137
|
* Compute the bootstrap file path. Exported for tests so they can assert
|
|
123
138
|
* the platform fork without monkeypatching `os.platform()`.
|
|
@@ -139,6 +154,16 @@ function resolveProfileBootstrapPaths(primary, entryNames) {
|
|
|
139
154
|
.map((name) => path.join(path.dirname(primary), name));
|
|
140
155
|
return [primary, ...extras];
|
|
141
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Legacy bootstrap directory (darwin): pre-rename daemons wrote under
|
|
159
|
+
* `~/Library/Application Support/OverSky/` while this reader looked under
|
|
160
|
+
* `skrr/` — a drift that silently killed island bootstrapping on macOS
|
|
161
|
+
* (OSK-12057). New daemons dual-write both; old daemons keep writing only
|
|
162
|
+
* `OverSky/`, so these stay consulted as fallbacks.
|
|
163
|
+
*/
|
|
164
|
+
function legacyDarwinBootstrapDir() {
|
|
165
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'OverSky');
|
|
166
|
+
}
|
|
142
167
|
/**
|
|
143
168
|
* List the bootstrap files that a Sky CLI may use for a local daemon
|
|
144
169
|
* handoff. `default` stays first for backwards compatibility; any other
|
|
@@ -150,16 +175,31 @@ function resolveProfileBootstrapPaths(primary, entryNames) {
|
|
|
150
175
|
*/
|
|
151
176
|
function resolveBootstrapCandidatePaths(profile = 'default') {
|
|
152
177
|
const primary = resolveBootstrapPath(profile);
|
|
153
|
-
if (profile !== 'default')
|
|
154
|
-
|
|
178
|
+
if (profile !== 'default') {
|
|
179
|
+
const legacy = process.platform === 'darwin'
|
|
180
|
+
? [path.join(legacyDarwinBootstrapDir(), `daemon-island.${profile}.json`)]
|
|
181
|
+
: [];
|
|
182
|
+
return [primary, ...legacy];
|
|
183
|
+
}
|
|
155
184
|
try {
|
|
156
185
|
if (process.platform === 'darwin') {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
const candidates = [primary];
|
|
187
|
+
for (const dir of [path.dirname(primary), legacyDarwinBootstrapDir()]) {
|
|
188
|
+
let entryNames = [];
|
|
189
|
+
try {
|
|
190
|
+
entryNames = fs
|
|
191
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
192
|
+
.filter((entry) => entry.isFile())
|
|
193
|
+
.map((entry) => entry.name);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
for (const p of resolveProfileBootstrapPaths(path.join(dir, 'daemon-island.default.json'), entryNames)) {
|
|
199
|
+
candidates.push(p);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [...new Set(candidates)];
|
|
163
203
|
}
|
|
164
204
|
const profilesDir = path.dirname(path.dirname(primary));
|
|
165
205
|
const extras = fs
|
|
@@ -208,6 +248,20 @@ function resolveCliHandoffCandidatePaths(profile = 'default') {
|
|
|
208
248
|
function legacyBootstrapPath() {
|
|
209
249
|
return path.join(os.homedir(), '.skrr', 'daemon-island.json');
|
|
210
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* The island files that belong to exactly `profile` — its canonical path and
|
|
253
|
+
* the pre-rename darwin location — and never another profile's. The broker's
|
|
254
|
+
* candidate list for `default` deliberately falls through to any running
|
|
255
|
+
* profile, which is right for borrowing a credential and wrong for naming
|
|
256
|
+
* which daemon a profile's status describes (OSK-12102).
|
|
257
|
+
*/
|
|
258
|
+
function resolveExactProfileBootstrapPaths(profile = 'default') {
|
|
259
|
+
const primary = resolveBootstrapPath(profile);
|
|
260
|
+
if (process.platform === 'darwin') {
|
|
261
|
+
return [primary, path.join(legacyDarwinBootstrapDir(), `daemon-island.${profile}.json`)];
|
|
262
|
+
}
|
|
263
|
+
return profile === 'default' ? [primary, legacyBootstrapPath()] : [primary];
|
|
264
|
+
}
|
|
211
265
|
/**
|
|
212
266
|
* Resolve the first live daemon bootstrap for a profile. Message-intent
|
|
213
267
|
* submission reuses the same authenticated loopback discovery as the login
|
|
@@ -301,6 +355,7 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
301
355
|
const usable = [];
|
|
302
356
|
const unbound = [];
|
|
303
357
|
let mismatchedBootstrap = null;
|
|
358
|
+
let mismatchedPath = null;
|
|
304
359
|
for (const candidate of candidates) {
|
|
305
360
|
const candidateBootstrap = readBootstrap(candidate);
|
|
306
361
|
if (!candidateBootstrap)
|
|
@@ -310,7 +365,10 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
310
365
|
typeof opts.baseURL === 'string' &&
|
|
311
366
|
opts.baseURL.length > 0 &&
|
|
312
367
|
normalizeBaseUrl(opts.baseURL) !== normalizeBaseUrl(candidateBootstrap.serverUrl)) {
|
|
313
|
-
mismatchedBootstrap
|
|
368
|
+
if (!mismatchedBootstrap) {
|
|
369
|
+
mismatchedBootstrap = candidateBootstrap;
|
|
370
|
+
mismatchedPath = candidate;
|
|
371
|
+
}
|
|
314
372
|
continue;
|
|
315
373
|
}
|
|
316
374
|
if (typeof candidateBootstrap.serverUrl !== 'string' ||
|
|
@@ -326,8 +384,11 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
326
384
|
return {
|
|
327
385
|
ok: false,
|
|
328
386
|
reason: 'base_url_mismatch',
|
|
329
|
-
|
|
387
|
+
// What was READ, not a claim about the daemon: the descriptor may be
|
|
388
|
+
// another profile's (OSK-12117).
|
|
389
|
+
detail: `The daemon descriptor ${mismatchedPath ?? ''} is for ${mismatchedBootstrap.serverUrl}, not ${opts.baseURL}.`,
|
|
330
390
|
daemonServerUrl: mismatchedBootstrap.serverUrl,
|
|
391
|
+
...(mismatchedPath ? { descriptorPath: mismatchedPath } : {}),
|
|
331
392
|
};
|
|
332
393
|
}
|
|
333
394
|
return {
|
|
@@ -346,6 +407,17 @@ async function attemptDaemonBrokerLogin(opts) {
|
|
|
346
407
|
let outcome = null;
|
|
347
408
|
for (const { bootstrap, sourcePath } of usable) {
|
|
348
409
|
outcome = await brokerThrough(bootstrap, opts, sourcePath);
|
|
410
|
+
if (!outcome.ok) {
|
|
411
|
+
// Say which daemon answered: a guest's refusal has a different repair.
|
|
412
|
+
outcome = {
|
|
413
|
+
...outcome,
|
|
414
|
+
descriptorPath: outcome.descriptorPath ?? sourcePath,
|
|
415
|
+
...(bootstrap.daemonId ? { daemonId: bootstrap.daemonId } : {}),
|
|
416
|
+
...(sourcePath === exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR
|
|
417
|
+
? { dedicatedGuest: true }
|
|
418
|
+
: {}),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
349
421
|
if (outcome.ok || outcome.reason !== 'network')
|
|
350
422
|
return outcome;
|
|
351
423
|
}
|
|
@@ -400,6 +472,13 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
|
|
|
400
472
|
// 403 HANDOFF_BROKER_ONLY anyway.
|
|
401
473
|
if (!offersRefreshFamily)
|
|
402
474
|
return redeem;
|
|
475
|
+
const minted = await mintRefreshFamilyThrough(bootstrap, opts);
|
|
476
|
+
// Both doors refused: report the one that says more. The legacy route
|
|
477
|
+
// usually carries the server's code itself, but an older daemon's token
|
|
478
|
+
// route may be the only one that named it.
|
|
479
|
+
if (!minted.ok && !minted.code && redeem.code && redeem.reason !== 'network')
|
|
480
|
+
return redeem;
|
|
481
|
+
return minted;
|
|
403
482
|
}
|
|
404
483
|
return mintRefreshFamilyThrough(bootstrap, opts);
|
|
405
484
|
}
|
|
@@ -415,7 +494,9 @@ async function redeemCliHandoffToken(bootstrap, opts = {}) {
|
|
|
415
494
|
const abortTimer = setTimeout(() => controller.abort(), timeoutMs);
|
|
416
495
|
let response;
|
|
417
496
|
try {
|
|
418
|
-
|
|
497
|
+
// The descriptor secret is a loopback-only credential: never `fetch`, which
|
|
498
|
+
// sends 127.0.0.1 to HTTP_PROXY under Bun and NODE_USE_ENV_PROXY (OSK-12134).
|
|
499
|
+
response = await (0, loopback_http_1.loopbackFetch)(url, {
|
|
419
500
|
method: 'POST',
|
|
420
501
|
headers: {
|
|
421
502
|
Authorization: `Bearer ${bootstrap.secret}`,
|
|
@@ -467,6 +548,28 @@ async function redeemCliHandoffToken(bootstrap, opts = {}) {
|
|
|
467
548
|
...(code ? { code } : {}),
|
|
468
549
|
};
|
|
469
550
|
}
|
|
551
|
+
// A daemon that names the SERVER's refusal of its own mint carries
|
|
552
|
+
// `upstreamCode` / `upstreamStatus` beside the 503 (older daemons omit them). Surface the
|
|
553
|
+
// server's code — `DEVICE_PROOF_PIN_MISMATCH` has a specific repair that
|
|
554
|
+
// `HANDOFF_TOKEN_UNAVAILABLE` cannot point at (OSK-12149).
|
|
555
|
+
const upstreamCode = typeof parsedBody?.upstreamCode === 'string'
|
|
556
|
+
? parsedBody.upstreamCode
|
|
557
|
+
: undefined;
|
|
558
|
+
const upstreamStatus = typeof parsedBody?.upstreamStatus === 'number'
|
|
559
|
+
? parsedBody.upstreamStatus
|
|
560
|
+
: undefined;
|
|
561
|
+
if (upstreamCode || upstreamStatus !== undefined) {
|
|
562
|
+
return {
|
|
563
|
+
ok: false,
|
|
564
|
+
reason: 'upstream_refused',
|
|
565
|
+
refusedBy: 'server',
|
|
566
|
+
detail: message,
|
|
567
|
+
...(upstreamStatus !== undefined
|
|
568
|
+
? { status: upstreamStatus }
|
|
569
|
+
: { status: response.status }),
|
|
570
|
+
...(upstreamCode ? { code: upstreamCode } : code ? { code } : {}),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
470
573
|
return {
|
|
471
574
|
ok: false,
|
|
472
575
|
// 503 HANDOFF_TOKEN_UNAVAILABLE lands in server_error with its code
|
|
@@ -474,6 +577,7 @@ async function redeemCliHandoffToken(bootstrap, opts = {}) {
|
|
|
474
577
|
// upstream unreachable, mint cool-down). That is a daemon outage, not a
|
|
475
578
|
// decline, and the code keeps it diagnosable.
|
|
476
579
|
reason: response.status >= 500 ? 'server_error' : 'unknown',
|
|
580
|
+
refusedBy: 'daemon',
|
|
477
581
|
detail: message,
|
|
478
582
|
status: response.status,
|
|
479
583
|
...(code ? { code } : {}),
|
|
@@ -497,7 +601,9 @@ async function mintRefreshFamilyThrough(bootstrap, opts) {
|
|
|
497
601
|
const abortTimer = setTimeout(() => controller.abort(), timeoutMs);
|
|
498
602
|
let response;
|
|
499
603
|
try {
|
|
500
|
-
|
|
604
|
+
// The descriptor secret is a loopback-only credential: never `fetch`, which
|
|
605
|
+
// sends 127.0.0.1 to HTTP_PROXY under Bun and NODE_USE_ENV_PROXY (OSK-12134).
|
|
606
|
+
response = await (0, loopback_http_1.loopbackFetch)(url, {
|
|
501
607
|
method: 'POST',
|
|
502
608
|
headers: {
|
|
503
609
|
Authorization: `Bearer ${bootstrap.secret}`,
|
|
@@ -558,7 +664,14 @@ async function mintRefreshFamilyThrough(bootstrap, opts) {
|
|
|
558
664
|
};
|
|
559
665
|
}
|
|
560
666
|
if (response.status === 503 && code === 'DAEMON_NOT_AUTHED') {
|
|
561
|
-
return {
|
|
667
|
+
return {
|
|
668
|
+
ok: false,
|
|
669
|
+
reason: 'daemon_not_authed',
|
|
670
|
+
refusedBy: 'daemon',
|
|
671
|
+
detail: message,
|
|
672
|
+
status: 503,
|
|
673
|
+
code,
|
|
674
|
+
};
|
|
562
675
|
}
|
|
563
676
|
// 429 is TRANSIENT and its remedy is time, which makes it the one failure
|
|
564
677
|
// here that a fall-through cannot substitute for. Left in the `unknown`
|
|
@@ -592,12 +705,26 @@ async function mintRefreshFamilyThrough(bootstrap, opts) {
|
|
|
592
705
|
...(daemonServerUrl ? { daemonServerUrl } : {}),
|
|
593
706
|
};
|
|
594
707
|
}
|
|
708
|
+
// A coded body the daemon did not write is the server's refusal of the
|
|
709
|
+
// daemon's mint, passed through verbatim (`cliHandoff.ts`). A 4xx one —
|
|
710
|
+
// observed: 401 DEVICE_PROOF_PIN_MISMATCH — is a decision with a named
|
|
711
|
+
// repair, and used to land in `unknown`, where no caller could classify it.
|
|
712
|
+
const refusedBy = code
|
|
713
|
+
? DAEMON_OWN_HANDOFF_CODES.has(code)
|
|
714
|
+
? 'daemon'
|
|
715
|
+
: 'server'
|
|
716
|
+
: undefined;
|
|
595
717
|
return {
|
|
596
718
|
ok: false,
|
|
597
|
-
reason: response.status >= 500
|
|
719
|
+
reason: response.status >= 500
|
|
720
|
+
? 'server_error'
|
|
721
|
+
: refusedBy === 'server'
|
|
722
|
+
? 'upstream_refused'
|
|
723
|
+
: 'unknown',
|
|
598
724
|
detail: message,
|
|
599
725
|
status: response.status,
|
|
600
726
|
...(code ? { code } : {}),
|
|
727
|
+
...(refusedBy ? { refusedBy } : {}),
|
|
601
728
|
};
|
|
602
729
|
}
|
|
603
730
|
// Happy path — validate the response shape minimally.
|
|
@@ -894,14 +1021,11 @@ async function maybeAutoBroker(opts) {
|
|
|
894
1021
|
if (opts.commandId && AUTH_SELF_MANAGED_COMMANDS.has(opts.commandId)) {
|
|
895
1022
|
return { triggered: false };
|
|
896
1023
|
}
|
|
897
|
-
//
|
|
1024
|
+
// The same decision an interactive login makes (OSK-12156): a named profile
|
|
1025
|
+
// with no stored credential is issued its own id, so a durable hand-off's
|
|
1026
|
+
// confirm cannot retire another profile's session. Persisted by the caller on
|
|
898
1027
|
// success so the rotation row stays bound across processes.
|
|
899
|
-
|
|
900
|
-
let updatedConfig;
|
|
901
|
-
if (!cliId) {
|
|
902
|
-
cliId = (0, device_id_1.generateCliId)();
|
|
903
|
-
updatedConfig = { ...opts.cliConfig, cliId };
|
|
904
|
-
}
|
|
1028
|
+
const { cliId, updatedConfig } = (0, cli_identity_1.planLoginCliId)(opts.cliConfig);
|
|
905
1029
|
const outcome = await attemptDaemonBrokerLoginAndPersist({
|
|
906
1030
|
cliId,
|
|
907
1031
|
baseURL: opts.cliConfig.baseURL,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to tell a person whose local daemon ANSWERED the CLI hand-off and
|
|
3
|
+
* refused it (OSK-12149, OSK-12151).
|
|
4
|
+
*
|
|
5
|
+
* The auto-broker used to be "fail-quiet": every refusal except a rate limit
|
|
6
|
+
* and a base-URL mismatch was dropped, and the command then said only "Not
|
|
7
|
+
* signed in. Run `skrr login` first." That sentence is true and points at the
|
|
8
|
+
* wrong repair whenever the daemon's refusal is specific — observed: the server
|
|
9
|
+
* refused the daemon's own mint with `401 DEVICE_PROOF_PIN_MISMATCH`, which
|
|
10
|
+
* `skrr daemon login` repairs (an interactive daemon login re-pins its device
|
|
11
|
+
* key), and which on a Dedicated Runtime guest no login the tenant can run
|
|
12
|
+
* repairs at all.
|
|
13
|
+
*
|
|
14
|
+
* One function decides the headline and the remedy so the NOT_SIGNED_IN error,
|
|
15
|
+
* `skrr login`'s progress line and its headless refusal cannot drift apart.
|
|
16
|
+
*/
|
|
17
|
+
import type { DaemonBrokerFailure } from './daemonBroker';
|
|
18
|
+
/**
|
|
19
|
+
* True when a daemon ANSWERED the hand-off and refused it — as opposed to no
|
|
20
|
+
* daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
|
|
21
|
+
* behind). This is the case the CLI must explain rather than reduce to "Not
|
|
22
|
+
* signed in": the person has a daemon, it said something specific, and that
|
|
23
|
+
* decides whether `skrr login` would even help.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isAnsweredBrokerRefusal(outcome: DaemonBrokerFailure): boolean;
|
|
26
|
+
export interface BrokerRefusalDescription {
|
|
27
|
+
/** One sentence: who refused and what they said, with status and code. */
|
|
28
|
+
headline: string;
|
|
29
|
+
/** What to do about it — never a login the reader cannot perform. */
|
|
30
|
+
remedy: string;
|
|
31
|
+
/** True when waiting and retrying is the repair. */
|
|
32
|
+
transient: boolean;
|
|
33
|
+
/** True when the refusing daemon is a Dedicated Runtime guest's. */
|
|
34
|
+
dedicatedGuest: boolean;
|
|
35
|
+
/** Machine-readable twin for `--json` error details. */
|
|
36
|
+
broker: {
|
|
37
|
+
reason: DaemonBrokerFailure['reason'];
|
|
38
|
+
refusedBy?: 'daemon' | 'server';
|
|
39
|
+
status?: number;
|
|
40
|
+
code?: string;
|
|
41
|
+
detail?: string;
|
|
42
|
+
descriptorPath?: string;
|
|
43
|
+
daemonId?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Describe a broker failure for a person, or null when there is nothing to
|
|
48
|
+
* say: no daemon was there to ask, or the failure already has its own message
|
|
49
|
+
* (`base_url_mismatch`, `rate_limited`).
|
|
50
|
+
*/
|
|
51
|
+
export declare function describeBrokerRefusal(failure: DaemonBrokerFailure, bin?: string): BrokerRefusalDescription | null;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAnsweredBrokerRefusal = isAnsweredBrokerRefusal;
|
|
4
|
+
exports.describeBrokerRefusal = describeBrokerRefusal;
|
|
5
|
+
/**
|
|
6
|
+
* True when a daemon ANSWERED the hand-off and refused it — as opposed to no
|
|
7
|
+
* daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
|
|
8
|
+
* behind). This is the case the CLI must explain rather than reduce to "Not
|
|
9
|
+
* signed in": the person has a daemon, it said something specific, and that
|
|
10
|
+
* decides whether `skrr login` would even help.
|
|
11
|
+
*/
|
|
12
|
+
function isAnsweredBrokerRefusal(outcome) {
|
|
13
|
+
switch (outcome.reason) {
|
|
14
|
+
case 'no_bootstrap':
|
|
15
|
+
case 'bootstrap_parse':
|
|
16
|
+
case 'network':
|
|
17
|
+
return false;
|
|
18
|
+
case 'stale_bootstrap':
|
|
19
|
+
// A bare 401 is the daemon refusing a descriptor secret that rotated;
|
|
20
|
+
// re-reading the file next time heals it. A coded one is a real answer.
|
|
21
|
+
return Boolean(outcome.code);
|
|
22
|
+
default:
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Codes whose repair is signing the DAEMON in again (laptop only). */
|
|
27
|
+
const DAEMON_RELOGIN_CODES = new Set([
|
|
28
|
+
'DEVICE_PROOF_PIN_MISMATCH',
|
|
29
|
+
'SESSION_REVOKED',
|
|
30
|
+
'PRINCIPAL_UNRESOLVED',
|
|
31
|
+
'DAEMON_NOT_AUTHED',
|
|
32
|
+
]);
|
|
33
|
+
/** Codes whose repair is time. */
|
|
34
|
+
const TRANSIENT_CODES = new Set([
|
|
35
|
+
'DEVICE_PROOF_VERIFY_UNAVAILABLE',
|
|
36
|
+
'SERVICE_UNAVAILABLE',
|
|
37
|
+
'HANDOFF_RATE_LIMITED',
|
|
38
|
+
'RATE_LIMITED',
|
|
39
|
+
'UPSTREAM_UNREACHABLE',
|
|
40
|
+
]);
|
|
41
|
+
function statusAndCode(f) {
|
|
42
|
+
const parts = [typeof f.status === 'number' ? `HTTP ${f.status}` : '', f.code ?? ''].filter(Boolean);
|
|
43
|
+
return parts.length > 0 ? ` (${parts.join(' ')})` : '';
|
|
44
|
+
}
|
|
45
|
+
function sentence(detail) {
|
|
46
|
+
const d = (detail ?? '').trim();
|
|
47
|
+
if (!d)
|
|
48
|
+
return '';
|
|
49
|
+
return /[.!?]$/.test(d) ? ` ${d}` : ` ${d}.`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Describe a broker failure for a person, or null when there is nothing to
|
|
53
|
+
* say: no daemon was there to ask, or the failure already has its own message
|
|
54
|
+
* (`base_url_mismatch`, `rate_limited`).
|
|
55
|
+
*/
|
|
56
|
+
function describeBrokerRefusal(failure, bin = 'skrr') {
|
|
57
|
+
if (!isAnsweredBrokerRefusal(failure))
|
|
58
|
+
return null;
|
|
59
|
+
if (failure.reason === 'base_url_mismatch' || failure.reason === 'rate_limited')
|
|
60
|
+
return null;
|
|
61
|
+
const dedicatedGuest = failure.dedicatedGuest === true;
|
|
62
|
+
const code = failure.code ?? '';
|
|
63
|
+
const who = dedicatedGuest
|
|
64
|
+
? `This Dedicated Runtime's skrr service${failure.daemonId ? ` (daemon ${failure.daemonId})` : ''}`
|
|
65
|
+
: 'The skrr background service on this computer';
|
|
66
|
+
const refusal = failure.reason === 'timeout'
|
|
67
|
+
? `${who} did not answer the sign-in hand-off in time.`
|
|
68
|
+
: failure.refusedBy === 'server' || failure.reason === 'upstream_refused'
|
|
69
|
+
? `${who} asked the skrr server for a credential for this CLI, and the server refused${statusAndCode(failure)}.${sentence(failure.detail)}`
|
|
70
|
+
: `${who} could not hand this CLI a credential${statusAndCode(failure)}.${sentence(failure.detail)}`;
|
|
71
|
+
const transient = failure.reason === 'timeout' ||
|
|
72
|
+
TRANSIENT_CODES.has(code) ||
|
|
73
|
+
(failure.reason === 'server_error' && !DAEMON_RELOGIN_CODES.has(code));
|
|
74
|
+
let remedy;
|
|
75
|
+
if (dedicatedGuest) {
|
|
76
|
+
// Nobody signs in on a guest: the daemon's credential comes from the
|
|
77
|
+
// machine's attested bootstrap, and a tenant `skrr login` would only put a
|
|
78
|
+
// personal credential on a shared machine. Name the machine instead.
|
|
79
|
+
remedy = transient
|
|
80
|
+
? 'This is usually temporary; retry in a minute. Nobody signs in on a Dedicated Runtime — ' +
|
|
81
|
+
`if it persists, check the machine from your own computer with \`${bin} machines dedicated health-check <lease-id>\` ` +
|
|
82
|
+
`(\`${bin} machines dedicated list\` shows the lease id).`
|
|
83
|
+
: 'Nobody signs in on a Dedicated Runtime: its daemon holds the account. From your own computer, run ' +
|
|
84
|
+
`\`${bin} machines dedicated health-check <lease-id>\` (\`${bin} machines dedicated list\` shows the lease id), ` +
|
|
85
|
+
`and \`${bin} machines dedicated restart <lease-id>\` if the hand-off stays refused.`;
|
|
86
|
+
}
|
|
87
|
+
else if (code === 'DEVICE_PROOF_PIN_MISMATCH') {
|
|
88
|
+
remedy =
|
|
89
|
+
`Run \`${bin} daemon login\` to sign the background service in again — an interactive daemon login re-pins its device key — then retry. ` +
|
|
90
|
+
`\`${bin} login\` signs this CLI in on its own instead.`;
|
|
91
|
+
}
|
|
92
|
+
else if (DAEMON_RELOGIN_CODES.has(code) || failure.reason === 'daemon_not_authed') {
|
|
93
|
+
remedy =
|
|
94
|
+
`Run \`${bin} daemon login\` to sign the background service in again, then retry. ` +
|
|
95
|
+
`\`${bin} login\` signs this CLI in on its own instead.`;
|
|
96
|
+
}
|
|
97
|
+
else if (failure.reason === 'ci_token_refused') {
|
|
98
|
+
remedy =
|
|
99
|
+
`The background service is signed in with a CI token, which cannot sign a person in. Use \`${bin} login\`, ` +
|
|
100
|
+
`or \`${bin} create-token\` for a CI credential.`;
|
|
101
|
+
}
|
|
102
|
+
else if (transient) {
|
|
103
|
+
remedy =
|
|
104
|
+
`This is usually temporary; retry in a minute. \`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side, ` +
|
|
105
|
+
`and \`${bin} login\` signs this CLI in without it.`;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
remedy =
|
|
109
|
+
`\`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side. ` +
|
|
110
|
+
`\`${bin} login\` signs this CLI in without it.`;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
headline: refusal,
|
|
114
|
+
remedy,
|
|
115
|
+
transient,
|
|
116
|
+
dedicatedGuest,
|
|
117
|
+
broker: {
|
|
118
|
+
reason: failure.reason,
|
|
119
|
+
...(failure.refusedBy ? { refusedBy: failure.refusedBy } : {}),
|
|
120
|
+
...(typeof failure.status === 'number' ? { status: failure.status } : {}),
|
|
121
|
+
...(failure.code ? { code: failure.code } : {}),
|
|
122
|
+
...(failure.detail ? { detail: failure.detail } : {}),
|
|
123
|
+
...(failure.descriptorPath ? { descriptorPath: failure.descriptorPath } : {}),
|
|
124
|
+
...(failure.daemonId ? { daemonId: failure.daemonId } : {}),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -111,8 +111,17 @@ export declare function describeDedicatedCheckpoint(lease: DedicatedLease | unde
|
|
|
111
111
|
* machine to the previous image. Files under the workspace survive both.
|
|
112
112
|
*/
|
|
113
113
|
export interface DedicatedImageStanding {
|
|
114
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* A newer APPROVED image exists that has not already failed on this machine.
|
|
116
|
+
* False is not "running the latest daemon": a release no approved image
|
|
117
|
+
* carries yet is not an update this machine can move to (OSK-12153).
|
|
118
|
+
*/
|
|
115
119
|
updateAvailable?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* The daemon the approved image carries, when the server can say: this
|
|
122
|
+
* machine runs that image and its daemon reported a version. Null otherwise.
|
|
123
|
+
*/
|
|
124
|
+
approvedImageDaemonVersion?: string | null;
|
|
116
125
|
/** When the older image stops being supported; null when there is no deadline. */
|
|
117
126
|
updateBy?: string | null;
|
|
118
127
|
lastUpdate?: {
|
|
@@ -121,6 +130,26 @@ export interface DedicatedImageStanding {
|
|
|
121
130
|
settledAt?: string | null;
|
|
122
131
|
} | null;
|
|
123
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The latest daemon release, as `show` read it from the signed release feed:
|
|
135
|
+
* a version, or why it could not be read. Never inferred from the machine.
|
|
136
|
+
*/
|
|
137
|
+
export type LatestDaemonRelease = {
|
|
138
|
+
version: string;
|
|
139
|
+
} | {
|
|
140
|
+
error: string;
|
|
141
|
+
};
|
|
142
|
+
/** -1 / 0 / 1, or null when either side is not a version. */
|
|
143
|
+
export declare function compareDaemonVersions(a: string, b: string): number | null;
|
|
144
|
+
/**
|
|
145
|
+
* Which daemon the approved image carries against the latest daemon release
|
|
146
|
+
* (OSK-12153). `image update: current` used to be the whole answer, while the
|
|
147
|
+
* daemon on the same machine said a newer release existed: "current" means no
|
|
148
|
+
* newer APPROVED image, and a release that no approved image carries yet is not
|
|
149
|
+
* something this machine can move to. Both halves are said, and an unknown
|
|
150
|
+
* half is said to be unknown rather than left out.
|
|
151
|
+
*/
|
|
152
|
+
export declare function describeDedicatedDaemonRelease(image: DedicatedImageStanding, latest: LatestDaemonRelease | null | undefined): string | null;
|
|
124
153
|
/** One line saying where a machine's image stands and what the owner can do. */
|
|
125
154
|
export declare function describeDedicatedImageStanding(image: DedicatedImageStanding, leaseId?: string): string;
|
|
126
155
|
/**
|
|
@@ -527,7 +556,9 @@ export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
|
|
|
527
556
|
*/
|
|
528
557
|
/** One line saying what a deletion still allows. */
|
|
529
558
|
export declare function describeDedicatedDeletion(deletion: DedicatedLeaseDeletion, leaseId?: string): string;
|
|
530
|
-
export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?: readonly Harness[]
|
|
559
|
+
export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?: readonly Harness[], opts?: {
|
|
560
|
+
latestDaemonRelease?: LatestDaemonRelease | null;
|
|
561
|
+
}): string[];
|
|
531
562
|
/** "2026-09-30 14:00 UTC (in 17 days)" — a deadline readable without converting it. */
|
|
532
563
|
export declare function formatDedicatedDeadline(value: string | null | undefined, now?: Date): string;
|
|
533
564
|
/**
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEDICATED_FILE_MAX_BYTES = exports.DEDICATED_FILE_CHUNK_BYTES = exports.DEDICATED_EXEC_DEFAULT_TIMEOUT_MS = exports.DEDICATED_EXEC_MAX_TIMEOUT_MS = exports.DEDICATED_MACHINE_PRODUCT = exports.DEDICATED_LEASE_ACTIONS = void 0;
|
|
4
4
|
exports.describeDedicatedCheckpoint = describeDedicatedCheckpoint;
|
|
5
|
+
exports.compareDaemonVersions = compareDaemonVersions;
|
|
6
|
+
exports.describeDedicatedDaemonRelease = describeDedicatedDaemonRelease;
|
|
5
7
|
exports.describeDedicatedImageStanding = describeDedicatedImageStanding;
|
|
6
8
|
exports.dedicatedRuntimeCreateInput = dedicatedRuntimeCreateInput;
|
|
7
9
|
exports.isDedicatedRepositoryRef = isDedicatedRepositoryRef;
|
|
@@ -73,6 +75,52 @@ function describeDedicatedCheckpoint(lease) {
|
|
|
73
75
|
return null;
|
|
74
76
|
return (0, data_provider_1.describeMachineCheckpointOutcome)(operation.checkpoint, { action: operation.action });
|
|
75
77
|
}
|
|
78
|
+
function versionParts(version) {
|
|
79
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(version || '').trim());
|
|
80
|
+
return match ? match.slice(1, 4).map(Number) : null;
|
|
81
|
+
}
|
|
82
|
+
/** -1 / 0 / 1, or null when either side is not a version. */
|
|
83
|
+
function compareDaemonVersions(a, b) {
|
|
84
|
+
const left = versionParts(a);
|
|
85
|
+
const right = versionParts(b);
|
|
86
|
+
if (!left || !right)
|
|
87
|
+
return null;
|
|
88
|
+
for (let i = 0; i < 3; i += 1) {
|
|
89
|
+
if (left[i] !== right[i])
|
|
90
|
+
return left[i] < right[i] ? -1 : 1;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Which daemon the approved image carries against the latest daemon release
|
|
96
|
+
* (OSK-12153). `image update: current` used to be the whole answer, while the
|
|
97
|
+
* daemon on the same machine said a newer release existed: "current" means no
|
|
98
|
+
* newer APPROVED image, and a release that no approved image carries yet is not
|
|
99
|
+
* something this machine can move to. Both halves are said, and an unknown
|
|
100
|
+
* half is said to be unknown rather than left out.
|
|
101
|
+
*/
|
|
102
|
+
function describeDedicatedDaemonRelease(image, latest) {
|
|
103
|
+
if (!latest)
|
|
104
|
+
return null;
|
|
105
|
+
const carried = image.approvedImageDaemonVersion || null;
|
|
106
|
+
const latestText = 'version' in latest
|
|
107
|
+
? `latest release skrrd ${latest.version}`
|
|
108
|
+
: `latest release unknown (${latest.error})`;
|
|
109
|
+
if (!carried) {
|
|
110
|
+
return image.updateAvailable ? latestText : `approved image's daemon unknown · ${latestText}`;
|
|
111
|
+
}
|
|
112
|
+
if (!('version' in latest)) {
|
|
113
|
+
return `approved image carries skrrd ${carried} · ${latestText}`;
|
|
114
|
+
}
|
|
115
|
+
const compared = compareDaemonVersions(carried, latest.version);
|
|
116
|
+
if (compared === 0) {
|
|
117
|
+
return `approved image carries skrrd ${carried}, the latest release`;
|
|
118
|
+
}
|
|
119
|
+
if (compared === -1) {
|
|
120
|
+
return `approved image carries skrrd ${carried} · latest release skrrd ${latest.version} is not in an approved image yet, so there is nothing to update to`;
|
|
121
|
+
}
|
|
122
|
+
return `approved image carries skrrd ${carried} · ${latestText}`;
|
|
123
|
+
}
|
|
76
124
|
/** One line saying where a machine's image stands and what the owner can do. */
|
|
77
125
|
function describeDedicatedImageStanding(image, leaseId) {
|
|
78
126
|
const last = image.lastUpdate;
|
|
@@ -547,7 +595,7 @@ function describeDedicatedDeletion(deletion, leaseId) {
|
|
|
547
595
|
}
|
|
548
596
|
return 'nothing to restore; every copy is deleted when the window closes';
|
|
549
597
|
}
|
|
550
|
-
function describeDedicatedLease(lease, harnesses = []) {
|
|
598
|
+
function describeDedicatedLease(lease, harnesses = [], opts = {}) {
|
|
551
599
|
const lines = [dedicatedLeaseDisplayName(lease)];
|
|
552
600
|
const row = (label, value) => {
|
|
553
601
|
if (value === undefined || value === null || value === '')
|
|
@@ -584,6 +632,7 @@ function describeDedicatedLease(lease, harnesses = []) {
|
|
|
584
632
|
row('image', image);
|
|
585
633
|
if (lease.health?.image) {
|
|
586
634
|
row('image update', describeDedicatedImageStanding(lease.health.image, dedicatedLeaseId(lease)));
|
|
635
|
+
row('image daemon', describeDedicatedDaemonRelease(lease.health.image, opts.latestDaemonRelease));
|
|
587
636
|
}
|
|
588
637
|
if (lease.daemon?.version || lease.daemon?.lastHeartbeatAt) {
|
|
589
638
|
row('daemon', [
|
|
@@ -49,6 +49,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
49
49
|
const os = __importStar(require("node:os"));
|
|
50
50
|
const path = __importStar(require("node:path"));
|
|
51
51
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
52
|
+
const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
|
|
52
53
|
function isDelegatedCliSession(env = process.env) {
|
|
53
54
|
return Boolean(env.OVERSKY_AUTONOMOUS_SESSION === '1' &&
|
|
54
55
|
env.OVERSKY_DELEGATED_CLI_BROKER_URL?.trim() &&
|
|
@@ -213,7 +214,9 @@ async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
|
213
214
|
try {
|
|
214
215
|
let res;
|
|
215
216
|
try {
|
|
216
|
-
|
|
217
|
+
// Proxy-free and loopback-only (OSK-12134): the capability and the
|
|
218
|
+
// credential it returns must never reach HTTP_PROXY.
|
|
219
|
+
res = await (0, loopback_http_1.loopbackFetch)(brokerUrl, {
|
|
217
220
|
method: 'POST',
|
|
218
221
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
219
222
|
body: JSON.stringify({
|
|
@@ -257,6 +260,22 @@ async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
|
257
260
|
const body = (await res.json().catch(() => null));
|
|
258
261
|
if (res.status === 403 && body?.code === 'DELEGATED_CLI_CAPABILITY_REJECTED')
|
|
259
262
|
return null;
|
|
263
|
+
if ((res.status === 503 || body?.code === 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE') &&
|
|
264
|
+
(typeof body?.upstreamCode === 'string' || typeof body?.upstreamStatus === 'number')) {
|
|
265
|
+
// The daemon named the server's refusal of its own mint (OSK-12151):
|
|
266
|
+
// say that, with the code, rather than guess at three possible causes.
|
|
267
|
+
const named = [
|
|
268
|
+
typeof body?.upstreamStatus === 'number' ? `HTTP ${body.upstreamStatus}` : '',
|
|
269
|
+
typeof body?.upstreamCode === 'string' ? body.upstreamCode : '',
|
|
270
|
+
]
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join(' ');
|
|
273
|
+
// The daemon's `message` already names the refusal; fall back to the
|
|
274
|
+
// status and code when an older daemon sent none.
|
|
275
|
+
throw new Error(typeof body?.message === 'string' && body.message
|
|
276
|
+
? `the daemon's delegated CLI broker could not mint a credential: ${body.message}`
|
|
277
|
+
: `the skrr server refused the daemon's request for this session's credential (${named}).`);
|
|
278
|
+
}
|
|
260
279
|
if (res.status === 503 || body?.code === 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE') {
|
|
261
280
|
throw new Error(`the daemon's delegated CLI broker is alive but cannot mint a credential ` +
|
|
262
281
|
`(HTTP ${res.status}). The daemon cannot reach the auth upstream, holds ` +
|
|
@@ -71,7 +71,9 @@ export declare function notInstalledMessage(): string;
|
|
|
71
71
|
* preserving "exit code = the daemon's exit code" semantics that scripts
|
|
72
72
|
* may depend on.
|
|
73
73
|
*/
|
|
74
|
-
export declare function execRuntimeBinary(args: string[]
|
|
74
|
+
export declare function execRuntimeBinary(args: string[], opts?: {
|
|
75
|
+
binary?: string;
|
|
76
|
+
}): Promise<number>;
|
|
75
77
|
/** Output from a short-lived runtime probe such as `skrrd status --json`. */
|
|
76
78
|
export type RuntimeBinaryOutput = {
|
|
77
79
|
code: number;
|