@skrr-ai/cli 0.1.81 → 0.1.82
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 +16 -0
- package/dist/base-command.js +124 -9
- package/dist/commands/followups/checkin.js +1 -1
- package/dist/commands/login.js +10 -0
- package/dist/commands/logout.js +4 -0
- package/dist/commands/store/installs.js +6 -1
- package/dist/lib/auth-storage.d.ts +5 -0
- package/dist/lib/auth-storage.js +8 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/credential-resolver.js +7 -1
- package/dist/lib/daemonBroker.d.ts +35 -0
- package/dist/lib/daemonBroker.js +187 -11
- package/dist/lib/daemonBrokerRefusal.d.ts +14 -0
- package/dist/lib/daemonBrokerRefusal.js +18 -1
- package/dist/lib/harness-context.d.ts +8 -0
- package/dist/lib/harness-context.js +55 -0
- package/dist/lib/login.js +4 -1
- package/dist/lib/refresh.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +25 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +35 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +25 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3856 -3801
- package/oclif.manifest.json +15792 -15792
- package/package.json +2 -2
package/dist/base-command.d.ts
CHANGED
|
@@ -471,5 +471,21 @@ export declare function describeDaemonScopedCredential(bin: string, scope?: Inco
|
|
|
471
471
|
* question. Merging the two is the mistake its own comment warns about.
|
|
472
472
|
*/
|
|
473
473
|
export declare function isTypedServerRefusal(body: Record<string, unknown> | null): boolean;
|
|
474
|
+
/**
|
|
475
|
+
* Did the API itself answer this gateway-class status (502/503/504) with a
|
|
476
|
+
* typed code?
|
|
477
|
+
*
|
|
478
|
+
* Those statuses mean "outage" only when something IN FRONT of the API wrote
|
|
479
|
+
* them — the load balancer or CDN, whose bodies are HTML or empty, and which
|
|
480
|
+
* `parseErrorBody` therefore reads as null. A JSON body carrying `code` or
|
|
481
|
+
* `errorType` was written by a route or middleware that ran and chose the
|
|
482
|
+
* status: the API was up, and the request did not time out at the gateway.
|
|
483
|
+
*
|
|
484
|
+
* Checked AFTER `isTypedServerRefusal`, which handles the subset that also
|
|
485
|
+
* explains itself; this covers the coded rest — including production bodies
|
|
486
|
+
* whose message the 5xx redaction boundary replaced. A JSON body with no code
|
|
487
|
+
* stays in the outage branch: it says nothing a reader could act on.
|
|
488
|
+
*/
|
|
489
|
+
export declare function isApplicationCodedUnavailable(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
474
490
|
export declare function isRetryableFailure(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
475
491
|
export { Flags };
|
package/dist/base-command.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.describeForbidden = describeForbidden;
|
|
|
15
15
|
exports.isDaemonScopedCredential = isDaemonScopedCredential;
|
|
16
16
|
exports.describeDaemonScopedCredential = describeDaemonScopedCredential;
|
|
17
17
|
exports.isTypedServerRefusal = isTypedServerRefusal;
|
|
18
|
+
exports.isApplicationCodedUnavailable = isApplicationCodedUnavailable;
|
|
18
19
|
exports.isRetryableFailure = isRetryableFailure;
|
|
19
20
|
const node_fs_1 = require("node:fs");
|
|
20
21
|
const core_1 = require("@oclif/core");
|
|
@@ -54,6 +55,7 @@ const edge_error_page_1 = require("./lib/edge-error-page");
|
|
|
54
55
|
const step_up_1 = require("./lib/step-up");
|
|
55
56
|
const delegated_cli_1 = require("./lib/delegated-cli");
|
|
56
57
|
const dedicated_guest_1 = require("./lib/dedicated-guest");
|
|
58
|
+
const harness_context_1 = require("./lib/harness-context");
|
|
57
59
|
const tasks_1 = require("./lib/tasks");
|
|
58
60
|
function assigneePayloadNeedsResolution(payload) {
|
|
59
61
|
const fields = [
|
|
@@ -163,12 +165,17 @@ class BaseCommand extends core_1.Command {
|
|
|
163
165
|
const result = await (0, daemonBroker_1.redeemBrokeredHandoffToken)(true);
|
|
164
166
|
if (!result?.accessToken)
|
|
165
167
|
throw new Error('The local daemon could not renew CLI authentication.');
|
|
166
|
-
this.resolvedCredential = {
|
|
167
|
-
|
|
168
|
+
this.resolvedCredential = {
|
|
169
|
+
token: result.accessToken,
|
|
170
|
+
source: 'env-token',
|
|
171
|
+
kind: (0, auth_core_1.classifyTokenKind)(result.accessToken),
|
|
172
|
+
};
|
|
168
173
|
}
|
|
169
174
|
else {
|
|
170
|
-
this.resolvedCredential = (await (0, refresh_1.ensureFreshCliCredential)({
|
|
171
|
-
|
|
175
|
+
this.resolvedCredential = (await (0, refresh_1.ensureFreshCliCredential)({
|
|
176
|
+
credential: this.resolvedCredential,
|
|
177
|
+
bareMode: this.bareMode,
|
|
178
|
+
})).credential;
|
|
172
179
|
}
|
|
173
180
|
if (!this.resolvedCredential.token)
|
|
174
181
|
throw new Error('CLI authentication is unavailable.');
|
|
@@ -272,6 +279,21 @@ class BaseCommand extends core_1.Command {
|
|
|
272
279
|
(0, data_provider_1.setHttpAdapter)((0, node_adapter_1.createNodeAdapter)());
|
|
273
280
|
return;
|
|
274
281
|
}
|
|
282
|
+
// OSK-13254 — a process the daemon started for an agent or tool borrows
|
|
283
|
+
// from the daemon and never owns the person's stored session, so the
|
|
284
|
+
// commands that create, replace or end that session are not its to run.
|
|
285
|
+
const harnessContext = (0, harness_context_1.isHarnessCliContext)();
|
|
286
|
+
if (harnessContext && this.id && harness_context_1.HARNESS_REFUSED_AUTH_COMMANDS.has(this.id)) {
|
|
287
|
+
this.failWithCliError({
|
|
288
|
+
message: `\`${this.config.bin} ${this.id}\` is not available here: this process was started by the skrr ` +
|
|
289
|
+
'background service for an agent or tool, and skrr in it borrows short-lived tokens from ' +
|
|
290
|
+
'that service instead of holding a sign-in. Run it in your own terminal.',
|
|
291
|
+
code: 'HARNESS_CONTEXT_AUTH_COMMAND',
|
|
292
|
+
exit: 2,
|
|
293
|
+
retryable: false,
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
275
297
|
// A replacement guest keeps its workspace but receives a fresh Linux
|
|
276
298
|
// machine-id. Retire the prior instance's machine-bound CLI envelopes
|
|
277
299
|
// before migration/prefetch reads them; the current daemon handoff below
|
|
@@ -280,7 +302,9 @@ class BaseCommand extends core_1.Command {
|
|
|
280
302
|
// One-shot migration: legacy token/refreshToken in cli-config.json
|
|
281
303
|
// → keychain (macOS) or cli-auth.json (elsewhere). Idempotent.
|
|
282
304
|
try {
|
|
283
|
-
|
|
305
|
+
// A borrower never writes the person's stored session (OSK-13254).
|
|
306
|
+
if (!harnessContext)
|
|
307
|
+
(0, migrate_1.migrateAuthOutOfConfig)();
|
|
284
308
|
}
|
|
285
309
|
catch {
|
|
286
310
|
// Never block a command on migration failure; the resolver still
|
|
@@ -336,9 +360,18 @@ class BaseCommand extends core_1.Command {
|
|
|
336
360
|
allowUnreadableStoredCredential: this.id === 'login' || this.id === 'logout' || this.id === 'pair',
|
|
337
361
|
});
|
|
338
362
|
let ignoredStoredCredential = null;
|
|
339
|
-
if (!this.resolvedCredential.token && !this.bareMode && !parsed.token) {
|
|
363
|
+
if (!this.resolvedCredential.token && !this.bareMode && !parsed.token && !harnessContext) {
|
|
340
364
|
ignoredStoredCredential = (0, auth_storage_1.readFromBackend)(this.cliConfig.baseURL);
|
|
341
365
|
}
|
|
366
|
+
// OSK-13254 — nothing at all is stored for this profile: a first sign-in,
|
|
367
|
+
// which may ADOPT a CLI session of its own through the daemon instead of
|
|
368
|
+
// borrowing per process. Anything stored — a refused session, another
|
|
369
|
+
// server's, another config root's — is left alone and the daemon lends.
|
|
370
|
+
const nothingStored = !harnessContext &&
|
|
371
|
+
!!ignoredStoredCredential &&
|
|
372
|
+
!ignoredStoredCredential.bundle &&
|
|
373
|
+
ignoredStoredCredential.ownerStatus !== 'foreign' &&
|
|
374
|
+
(ignoredStoredCredential.originStatus ?? 'none') === 'none';
|
|
342
375
|
// Daemon-as-broker auto-mint. When the resolver came up empty AND
|
|
343
376
|
// a local `oversky` daemon is already authenticated for this user,
|
|
344
377
|
// silently mint a sibling cli-scope refresh family via the daemon's
|
|
@@ -366,8 +399,14 @@ class BaseCommand extends core_1.Command {
|
|
|
366
399
|
cliConfig: this.cliConfig,
|
|
367
400
|
bareMode: this.bareMode,
|
|
368
401
|
commandId: this.id,
|
|
402
|
+
adopt: nothingStored,
|
|
369
403
|
}));
|
|
370
|
-
if (autoResult.
|
|
404
|
+
if (autoResult.adoptedBySibling) {
|
|
405
|
+
// Another `skrr` adopted this profile's session while this one waited
|
|
406
|
+
// for the adoption lock: read it like any stored session.
|
|
407
|
+
this.resolvedCredential = await (0, credential_resolver_1.resolveCredential)({ bareMode: this.bareMode });
|
|
408
|
+
}
|
|
409
|
+
else if (autoResult.triggered && autoResult.outcome) {
|
|
371
410
|
if (autoResult.outcome.ok) {
|
|
372
411
|
// Persist the cliId now so a sibling skrr process spawned
|
|
373
412
|
// immediately after sees the same identity.
|
|
@@ -415,6 +454,14 @@ class BaseCommand extends core_1.Command {
|
|
|
415
454
|
: {}),
|
|
416
455
|
}
|
|
417
456
|
: null;
|
|
457
|
+
if (!autoResult.outcome.brokered && nothingStored) {
|
|
458
|
+
// Said once, on stderr, the moment it happens: a session now exists
|
|
459
|
+
// that the person did not type a command to create (OSK-13254).
|
|
460
|
+
process.stderr.write(`[skrr] Signed this CLI in through the skrr background service on this computer. ` +
|
|
461
|
+
`The session is saved to ${(0, credential_resolver_1.describeCredentialSource)(this.resolvedCredential.source)} ` +
|
|
462
|
+
`and renews itself, so skrr keeps working while the service restarts. ` +
|
|
463
|
+
`It is listed under Active Sessions; \`${this.config.bin} logout\` ends it.\n`);
|
|
464
|
+
}
|
|
418
465
|
}
|
|
419
466
|
else if ((this.brokerRefusal =
|
|
420
467
|
(0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin) ??
|
|
@@ -1215,7 +1262,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1215
1262
|
// login to run, so its remedy replaces the generic "Run login".
|
|
1216
1263
|
const brokerRefusal = presentedCiToken || presentedPat || !e.brokerFailure
|
|
1217
1264
|
? null
|
|
1218
|
-
: (0, daemonBrokerRefusal_1.
|
|
1265
|
+
: (0, daemonBrokerRefusal_1.describeBrokerFailure)(e.brokerFailure, this.config.bin);
|
|
1219
1266
|
const renewal = refreshRefusal
|
|
1220
1267
|
? refreshRefusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
1221
1268
|
: "This CLI's own sign-in could not be renewed.";
|
|
@@ -1361,6 +1408,45 @@ class BaseCommand extends core_1.Command {
|
|
|
1361
1408
|
requestId: stringProp(body, 'requestId') || e.requestId,
|
|
1362
1409
|
});
|
|
1363
1410
|
}
|
|
1411
|
+
else if (isApplicationCodedUnavailable(e.status, body)) {
|
|
1412
|
+
// A 502/503/504 the API ITSELF wrote, with a typed code — not a page
|
|
1413
|
+
// from the load balancer or CDN. The outage branch below would say the
|
|
1414
|
+
// platform is down, absolve this command, and (for a write) call the
|
|
1415
|
+
// outcome unknown because "the gateway may have timed out after the
|
|
1416
|
+
// server committed it". None of that holds when the API answered.
|
|
1417
|
+
//
|
|
1418
|
+
// Observed 2026-10-01: `skrr agents chat --daemon <id>` printed exactly
|
|
1419
|
+
// that outage paragraph for `POST /api/agentic/chat` → 503
|
|
1420
|
+
// `preferred_daemon_unavailable` — "The selected local daemon is not
|
|
1421
|
+
// connected" — a synchronous refusal before anything was enqueued,
|
|
1422
|
+
// while skrr.ai served every other request. The reader went looking
|
|
1423
|
+
// for a platform outage.
|
|
1424
|
+
//
|
|
1425
|
+
// No incident is recorded: one route refusing in its own words is not
|
|
1426
|
+
// evidence the control plane is failing. `isOutageFailure` is left
|
|
1427
|
+
// alone because it also decides whether the outbox queues a write.
|
|
1428
|
+
const retryable = isRetryableFailure(e.status, body);
|
|
1429
|
+
const authored = bodyMessage && !isRedactedServerMessage(bodyMessage) ? bodyMessage : '';
|
|
1430
|
+
const requestId = stringProp(body, 'requestId') || e.requestId;
|
|
1431
|
+
this.failWithCliError({
|
|
1432
|
+
message: say(`${this.cliConfig.baseURL} refused this request (HTTP ${e.status}, ${bodyCode})` +
|
|
1433
|
+
`${authored ? `: ${authored}` : '.'} ` +
|
|
1434
|
+
'The API answered this itself, so the request did not time out at the gateway. ' +
|
|
1435
|
+
(authored ? '' : 'It did not say why in this response. ') +
|
|
1436
|
+
(retryable
|
|
1437
|
+
? 'Retrying may succeed once that condition clears.'
|
|
1438
|
+
: 'Retrying the same request will not help until that condition changes.') +
|
|
1439
|
+
(requestId && !authored
|
|
1440
|
+
? ` Request id ${requestId} — quote it when reporting this.`
|
|
1441
|
+
: '')),
|
|
1442
|
+
code: bodyCode,
|
|
1443
|
+
status: e.status,
|
|
1444
|
+
exit: retryable ? 5 : 1,
|
|
1445
|
+
retryable,
|
|
1446
|
+
details: body ?? undefined,
|
|
1447
|
+
...(requestId ? { requestId } : {}),
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1364
1450
|
else if ((0, outbox_1.isOutageFailure)(e)) {
|
|
1365
1451
|
// A gateway/unavailable status is an OUTAGE, and saying so is the whole
|
|
1366
1452
|
// point. During the OSK-2806 incident these surfaced as bare
|
|
@@ -1541,7 +1627,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1541
1627
|
cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
|
|
1542
1628
|
});
|
|
1543
1629
|
const brokerRefusal = e.brokerFailure
|
|
1544
|
-
? (0, daemonBrokerRefusal_1.
|
|
1630
|
+
? (0, daemonBrokerRefusal_1.describeBrokerFailure)(e.brokerFailure, this.config.bin)
|
|
1545
1631
|
: null;
|
|
1546
1632
|
const why = (refusal
|
|
1547
1633
|
? refusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
@@ -2059,6 +2145,35 @@ function isTypedServerRefusal(body) {
|
|
|
2059
2145
|
return false;
|
|
2060
2146
|
return (typeof body.cause === 'string' || typeof body.remedy === 'string' || body.retryable === false);
|
|
2061
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Did the API itself answer this gateway-class status (502/503/504) with a
|
|
2150
|
+
* typed code?
|
|
2151
|
+
*
|
|
2152
|
+
* Those statuses mean "outage" only when something IN FRONT of the API wrote
|
|
2153
|
+
* them — the load balancer or CDN, whose bodies are HTML or empty, and which
|
|
2154
|
+
* `parseErrorBody` therefore reads as null. A JSON body carrying `code` or
|
|
2155
|
+
* `errorType` was written by a route or middleware that ran and chose the
|
|
2156
|
+
* status: the API was up, and the request did not time out at the gateway.
|
|
2157
|
+
*
|
|
2158
|
+
* Checked AFTER `isTypedServerRefusal`, which handles the subset that also
|
|
2159
|
+
* explains itself; this covers the coded rest — including production bodies
|
|
2160
|
+
* whose message the 5xx redaction boundary replaced. A JSON body with no code
|
|
2161
|
+
* stays in the outage branch: it says nothing a reader could act on.
|
|
2162
|
+
*/
|
|
2163
|
+
function isApplicationCodedUnavailable(status, body) {
|
|
2164
|
+
if (status !== 502 && status !== 503 && status !== 504)
|
|
2165
|
+
return false;
|
|
2166
|
+
return Boolean(stringProp(body, 'code') || stringProp(body, 'errorType'));
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* The sentence `redactServerErrorResponses` substitutes for a 5xx message in
|
|
2170
|
+
* production. It is the boundary's, not the route's, so it is never relayed as
|
|
2171
|
+
* if it explained anything.
|
|
2172
|
+
*/
|
|
2173
|
+
const REDACTED_SERVER_MESSAGE = 'An unexpected error occurred.';
|
|
2174
|
+
function isRedactedServerMessage(message) {
|
|
2175
|
+
return message.trim() === REDACTED_SERVER_MESSAGE;
|
|
2176
|
+
}
|
|
2062
2177
|
function isRetryableFailure(status, body) {
|
|
2063
2178
|
const declared = body?.retryable;
|
|
2064
2179
|
if (typeof declared === 'boolean')
|
|
@@ -49,7 +49,7 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
49
49
|
catch (err) {
|
|
50
50
|
this.failWithCliError({
|
|
51
51
|
message: err instanceof Error ? err.message : String(err),
|
|
52
|
-
code: '
|
|
52
|
+
code: 'FOLLOWUP_INVALID_FIELD',
|
|
53
53
|
exit: 2,
|
|
54
54
|
retryable: false,
|
|
55
55
|
});
|
package/dist/commands/login.js
CHANGED
|
@@ -126,6 +126,16 @@ class Login extends base_command_1.BaseCommand {
|
|
|
126
126
|
`(${explicitBaseUrl}). Pass one of them.`, { exit: 2 });
|
|
127
127
|
}
|
|
128
128
|
const requestedBaseUrl = envBaseUrl ?? explicitBaseUrl;
|
|
129
|
+
// OSK-13254 — an explicit sign-in lifts what `skrr logout` set: from here a
|
|
130
|
+
// command that finds no stored session may adopt one through the daemon
|
|
131
|
+
// again. Cleared up front, because asking to sign in is the decision.
|
|
132
|
+
{
|
|
133
|
+
const current = (0, config_1.loadConfig)();
|
|
134
|
+
if (current.adoptDaemonSession === false) {
|
|
135
|
+
const { adoptDaemonSession: _cleared, ...rest } = current;
|
|
136
|
+
(0, config_1.saveConfig)(rest);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
129
139
|
/**
|
|
130
140
|
* Undo the base-URL write if this login does not complete.
|
|
131
141
|
*
|
package/dist/commands/logout.js
CHANGED
|
@@ -260,6 +260,10 @@ class Logout extends base_command_1.BaseCommand {
|
|
|
260
260
|
...(agentSetting.value ? { [first_party_harness_managed_1.AGENT_CONFIG_KEY]: agentSetting.value } : {}),
|
|
261
261
|
...(cfg.credentials ? { credentials: cfg.credentials } : {}),
|
|
262
262
|
migrated: { ...(cfg.migrated ?? {}), authOutOfConfig: true },
|
|
263
|
+
// OSK-13254 — signed out means no durable session reappears behind this
|
|
264
|
+
// command. The daemon may still LEND this CLI short-lived tokens (said
|
|
265
|
+
// below); `skrr login` clears the flag.
|
|
266
|
+
adoptDaemonSession: false,
|
|
263
267
|
});
|
|
264
268
|
const finalMessage = flags['all-devices']
|
|
265
269
|
? allDevicesError
|
|
@@ -44,7 +44,12 @@ class StoreInstalls extends base_command_1.BaseCommand {
|
|
|
44
44
|
release: install.installedReleaseId ?? '-',
|
|
45
45
|
version: install.installedVersionLabel ?? '-',
|
|
46
46
|
state: install.copyMissing ? 'copy missing' : install.stopReason ? 'stopped' : 'installed',
|
|
47
|
-
|
|
47
|
+
// The server's projection decides both (OSK-13290): `stopReason` is
|
|
48
|
+
// the shared stop vocabulary (`recalled`, `revoked`, …). An advisory
|
|
49
|
+
// that does not stop the copy outright (a `revoke` recall still lets
|
|
50
|
+
// its owner chat with it) is named rather than shown as '-'.
|
|
51
|
+
reason: install.stopReason ??
|
|
52
|
+
(install.advisory?.remedy ? `advisory: ${install.advisory.remedy}` : '-'),
|
|
48
53
|
})), [
|
|
49
54
|
{ key: 'agent', header: 'LISTING' },
|
|
50
55
|
{ key: 'copy', header: 'YOUR COPY' },
|
|
@@ -124,6 +124,11 @@ export declare function describeForeignCredential(result: ReadResult): string |
|
|
|
124
124
|
* a specific backend (bare mode, store-mode overrides) should call the
|
|
125
125
|
* lower-level write helpers directly — not exported here to keep the
|
|
126
126
|
* default path honest.
|
|
127
|
+
*
|
|
128
|
+
* Refuses in a process the daemon started for an agent or tool (OSK-13254):
|
|
129
|
+
* the person's stored session is theirs, and a borrower never writes it. This
|
|
130
|
+
* is the one choke point every persisting path goes through, so the policy
|
|
131
|
+
* cannot be missed by a new caller.
|
|
127
132
|
*/
|
|
128
133
|
export declare function writeToBackend(bundle: AuthBundle, options?: WriteOptions): AuthBackend;
|
|
129
134
|
/**
|
package/dist/lib/auth-storage.js
CHANGED
|
@@ -102,6 +102,7 @@ const keychain = __importStar(require("./keychain"));
|
|
|
102
102
|
const cred_envelope_1 = require("./cred-envelope");
|
|
103
103
|
const config_1 = require("./config");
|
|
104
104
|
const cli_id_1 = require("./cli-id");
|
|
105
|
+
const harness_context_1 = require("./harness-context");
|
|
105
106
|
/** Filename for the non-darwin file backend. Parallel to the daemon's
|
|
106
107
|
* `auth.json`, deliberately distinct so the two binaries never clobber
|
|
107
108
|
* each other's tokens (CLI holds scope=cli, daemon holds scope=daemon). */
|
|
@@ -759,8 +760,15 @@ function describeForeignCredential(result) {
|
|
|
759
760
|
* a specific backend (bare mode, store-mode overrides) should call the
|
|
760
761
|
* lower-level write helpers directly — not exported here to keep the
|
|
761
762
|
* default path honest.
|
|
763
|
+
*
|
|
764
|
+
* Refuses in a process the daemon started for an agent or tool (OSK-13254):
|
|
765
|
+
* the person's stored session is theirs, and a borrower never writes it. This
|
|
766
|
+
* is the one choke point every persisting path goes through, so the policy
|
|
767
|
+
* cannot be missed by a new caller.
|
|
762
768
|
*/
|
|
763
769
|
function writeToBackend(bundle, options = {}) {
|
|
770
|
+
if ((0, harness_context_1.isHarnessCliContext)())
|
|
771
|
+
throw new harness_context_1.HarnessCredentialPersistRefused();
|
|
764
772
|
const requestedOrigin = options.serverOrigin ?? bundle.serverOrigin;
|
|
765
773
|
let persistedBundle = bundle;
|
|
766
774
|
if (requestedOrigin !== undefined) {
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -46,6 +46,14 @@ export interface CliConfig {
|
|
|
46
46
|
* `resolveCliId()` (`cli-id.ts`), the one resolver every caller goes through.
|
|
47
47
|
*/
|
|
48
48
|
profileCliId?: string;
|
|
49
|
+
/**
|
|
50
|
+
* OSK-13254 — `false` once `skrr logout` has signed this profile out: from
|
|
51
|
+
* then on a command that finds no stored session BORROWS a short-lived token
|
|
52
|
+
* from the local daemon for that process and does not adopt a durable CLI
|
|
53
|
+
* session of its own. Absent means adoption is allowed. `skrr login` clears
|
|
54
|
+
* it. Read through `cliSessionAdoptionBlocker` (`daemonBroker.ts`).
|
|
55
|
+
*/
|
|
56
|
+
adoptDaemonSession?: boolean;
|
|
49
57
|
/**
|
|
50
58
|
* OSK-3892 — the agent a locally-hosted `skrr code` session is attributed to.
|
|
51
59
|
*
|
|
@@ -31,6 +31,7 @@ const auth_core_init_1 = require("./auth-core-init");
|
|
|
31
31
|
const config_1 = require("./config");
|
|
32
32
|
const auth_storage_1 = require("./auth-storage");
|
|
33
33
|
const cred_envelope_1 = require("./cred-envelope");
|
|
34
|
+
const harness_context_1 = require("./harness-context");
|
|
34
35
|
/**
|
|
35
36
|
* Return the only credential an Agent-owned CLI process is allowed to use.
|
|
36
37
|
*
|
|
@@ -68,7 +69,12 @@ async function resolveCredential(opts = {}) {
|
|
|
68
69
|
return undefined;
|
|
69
70
|
}
|
|
70
71
|
})();
|
|
71
|
-
|
|
72
|
+
// A process the daemon started for an agent or tool never reads the
|
|
73
|
+
// person's stored CLI session (OSK-13254): it borrows from the daemon, or
|
|
74
|
+
// uses a token it was explicitly given.
|
|
75
|
+
const storeMode = (0, harness_context_1.isHarnessCliContext)()
|
|
76
|
+
? 'env-only'
|
|
77
|
+
: (opts.storeModeOverride ?? cfg?.credentials?.store ?? 'auto');
|
|
72
78
|
const bareMode = opts.bareMode || ['1', 'true', 'TRUE', 'yes'].includes(process.env.OVERSKY_BARE_MODE ?? '');
|
|
73
79
|
// Clear an observation left by a prior, unrelated direct backend probe in
|
|
74
80
|
// this process. Only a blocked read made by THIS resolver should trigger the
|
|
@@ -246,6 +246,14 @@ export interface DaemonBrokerOptions {
|
|
|
246
246
|
* regardless — its secret cannot mint a family at all.
|
|
247
247
|
*/
|
|
248
248
|
preferHandoffMode?: 'access_token' | 'refresh_family';
|
|
249
|
+
/**
|
|
250
|
+
* Never take the durable mint, whatever the descriptor offers (OSK-13254).
|
|
251
|
+
* Set for a process the daemon started for an agent or tool
|
|
252
|
+
* (`harness-context.ts`): it borrows an access token or nothing. A v1
|
|
253
|
+
* descriptor, which offers only the durable mint, is reported as unable to
|
|
254
|
+
* lend rather than minting a family nobody would hold.
|
|
255
|
+
*/
|
|
256
|
+
borrowOnly?: boolean;
|
|
249
257
|
/**
|
|
250
258
|
* Overrides for the bounded wait on a restarting local daemon (OSK-13222):
|
|
251
259
|
* budgets, the clock and the supervisor probe. Tests use it; production
|
|
@@ -472,6 +480,22 @@ export interface MaybeAutoBrokerOptions {
|
|
|
472
480
|
* profile, and otherwise reports NOT_SIGNED_IN for the profile it was given.
|
|
473
481
|
*/
|
|
474
482
|
profile?: string;
|
|
483
|
+
/**
|
|
484
|
+
* Adopt a CLI session of this profile's own instead of borrowing (OSK-13254).
|
|
485
|
+
*
|
|
486
|
+
* Set by `BaseCommand.init` ONLY when the resolver found no stored
|
|
487
|
+
* credential at all — a first sign-in. The 401-recovery callers leave it
|
|
488
|
+
* unset: a stored session that was refused may have been revoked on purpose
|
|
489
|
+
* (Active Sessions, a password change), and re-adopting a new one behind
|
|
490
|
+
* that decision would undo it. They borrow, as before.
|
|
491
|
+
*
|
|
492
|
+
* Ignored (borrow instead) in a harness context, after `skrr logout`
|
|
493
|
+
* (`cliConfig.adoptDaemonSession === false`), and under
|
|
494
|
+
* `SKRR_NO_CLI_SESSION_ADOPTION=1`. See {@link cliSessionAdoptionBlocker}.
|
|
495
|
+
*/
|
|
496
|
+
adopt?: boolean;
|
|
497
|
+
/** Test seam: lock-acquisition budget for the adoption lock. */
|
|
498
|
+
adoptionLockRetries?: number;
|
|
475
499
|
}
|
|
476
500
|
export interface MaybeAutoBrokerResult {
|
|
477
501
|
/**
|
|
@@ -489,7 +513,18 @@ export interface MaybeAutoBrokerResult {
|
|
|
489
513
|
* and end up with a divergent refresh family.
|
|
490
514
|
*/
|
|
491
515
|
updatedConfig?: CliConfig;
|
|
516
|
+
/**
|
|
517
|
+
* True when this process waited on the adoption lock and found that a
|
|
518
|
+
* sibling `skrr` had adopted a session for this profile meanwhile. Nothing
|
|
519
|
+
* was minted; the caller re-reads the stored credential (OSK-13254).
|
|
520
|
+
*/
|
|
521
|
+
adoptedBySibling?: boolean;
|
|
492
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Why this invocation must borrow instead of adopting a CLI session of its
|
|
525
|
+
* own, or null when it may adopt (OSK-13254).
|
|
526
|
+
*/
|
|
527
|
+
export declare function cliSessionAdoptionBlocker(cliConfig: Pick<CliConfig, 'adoptDaemonSession'>, env?: NodeJS.ProcessEnv): 'harness_context' | 'signed_out' | 'opted_out' | null;
|
|
493
528
|
/**
|
|
494
529
|
* Auto-broker pre-flight for `BaseCommand.init()`. When the credential
|
|
495
530
|
* resolver returns no token, this attempts a silent daemon broker so
|