@skrr-ai/cli 0.1.81 → 0.1.83

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.
Files changed (32) hide show
  1. package/dist/base-command.d.ts +16 -0
  2. package/dist/base-command.js +124 -9
  3. package/dist/commands/followups/checkin.js +1 -1
  4. package/dist/commands/login.js +10 -0
  5. package/dist/commands/logout.js +4 -0
  6. package/dist/commands/store/installs.js +6 -1
  7. package/dist/lib/auth-storage.d.ts +5 -0
  8. package/dist/lib/auth-storage.js +8 -0
  9. package/dist/lib/config.d.ts +8 -0
  10. package/dist/lib/credential-resolver.js +7 -1
  11. package/dist/lib/daemonBroker.d.ts +35 -0
  12. package/dist/lib/daemonBroker.js +191 -11
  13. package/dist/lib/daemonBrokerRefusal.d.ts +14 -0
  14. package/dist/lib/daemonBrokerRefusal.js +18 -1
  15. package/dist/lib/first-party-harness-broker.d.ts +2 -0
  16. package/dist/lib/first-party-harness-broker.js +77 -3
  17. package/dist/lib/harness-context.d.ts +8 -0
  18. package/dist/lib/harness-context.js +55 -0
  19. package/dist/lib/login.js +4 -1
  20. package/dist/lib/refresh.js +4 -0
  21. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  22. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -2
  23. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +25 -0
  24. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +35 -1
  25. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  26. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  27. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +25 -0
  28. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +33 -0
  29. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  30. package/dist/node_modules/@skrr-ai/data-provider/index.js +3856 -3801
  31. package/oclif.manifest.json +19574 -19574
  32. package/package.json +2 -2
@@ -70,6 +70,9 @@ var __importStar = (this && this.__importStar) || (function () {
70
70
  return result;
71
71
  };
72
72
  })();
73
+ var __importDefault = (this && this.__importDefault) || function (mod) {
74
+ return (mod && mod.__esModule) ? mod : { "default": mod };
75
+ };
73
76
  Object.defineProperty(exports, "__esModule", { value: true });
74
77
  exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR = exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = void 0;
75
78
  exports.normalizeBaseUrl = normalizeBaseUrl;
@@ -89,16 +92,19 @@ exports.findBrokeredHandoffDescriptor = findBrokeredHandoffDescriptor;
89
92
  exports.maybeRetireStoredCliAuthFile = maybeRetireStoredCliAuthFile;
90
93
  exports.shouldBrokerBeforeStoredCredential = shouldBrokerBeforeStoredCredential;
91
94
  exports.attemptDaemonBrokerLoginAndPersist = attemptDaemonBrokerLoginAndPersist;
95
+ exports.cliSessionAdoptionBlocker = cliSessionAdoptionBlocker;
92
96
  exports.maybeAutoBroker = maybeAutoBroker;
93
97
  const fs = __importStar(require("node:fs"));
94
98
  const path = __importStar(require("node:path"));
95
99
  const os = __importStar(require("node:os"));
100
+ const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
96
101
  const auth_core_1 = require("@skrr-ai/auth-core");
97
102
  const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
98
103
  const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
99
104
  const brokered_dpop_1 = require("./brokered-dpop");
100
105
  const auth_storage_1 = require("./auth-storage");
101
106
  const config_1 = require("./config");
107
+ const harness_context_1 = require("./harness-context");
102
108
  const cli_identity_1 = require("./cli-identity");
103
109
  const daemonRestartWait_1 = require("./daemonRestartWait");
104
110
  const keychain_1 = require("./keychain");
@@ -368,6 +374,10 @@ async function attemptDaemonBrokerLogin(opts) {
368
374
  const first = await attemptDaemonBrokerLoginOnce(opts);
369
375
  if (first.ok || !isRestartShapedFailure(first))
370
376
  return first;
377
+ // The guest descriptor is owned by the runtime supervisor, not this CLI's
378
+ // local daemon service. Waiting on a laptop launchd restart cannot repair it.
379
+ if (opts.bootstrapPathOverride === exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR)
380
+ return first;
371
381
  // The local service may be restarting: a graceful restart removes the
372
382
  // descriptor and republishes it only once the new daemon is listening
373
383
  // (measured ~14s), and a crash leaves it naming a dead pid until launchd
@@ -562,8 +572,19 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
562
572
  // would either break explicit login or be bypassed by anyone who calls it
563
573
  // the way login does. It is not a boundary, so it is not attempted. What the
564
574
  // 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.
575
+ // a refresh family on a borrower's behalf: when `access_token` is advertised,
576
+ // a failed redeem is the answer, and the daemon's outage is reported with its
577
+ // repair.
578
+ //
579
+ // OSK-13254 narrowed "the automatic path" to mean a BORROWER. A person's
580
+ // first `skrr` on a profile with nothing stored now ADOPTS a CLI session of
581
+ // its own (`maybeAutoBroker` with `adopt`, `preferHandoffMode:
582
+ // 'refresh_family'`), because a sign-in that lived only as long as the daemon
583
+ // read as "signed out" every time the daemon restarted. What OSK-12018 was
584
+ // guarding against — an agent-originated implicit sign-in persisting a family
585
+ // — is now refused by name: a process the daemon started for an agent or tool
586
+ // carries `SKRR_DAEMON_CHILD` / `OVERSKY_AUTONOMOUS_SESSION`
587
+ // (`harness-context.ts`), sets `borrowOnly`, and `writeToBackend` refuses it.
567
588
  //
568
589
  // COMPATIBILITY WINDOW: a v1 descriptor (no `handoffModes`, so no
569
590
  // `access_token`) still takes the durable mint on the automatic path below,
@@ -578,7 +599,17 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
578
599
  const offersAccessToken = (0, cli_handoff_wire_1.descriptorOffersAccessToken)(bootstrap);
579
600
  const offersRefreshFamily = modes.includes('refresh_family');
580
601
  const preferAccessToken = opts.preferHandoffMode === 'access_token';
581
- if (offersAccessToken && (preferAccessToken || !offersRefreshFamily)) {
602
+ if (opts.borrowOnly && !offersAccessToken) {
603
+ return {
604
+ ok: false,
605
+ reason: 'unknown',
606
+ refusedBy: 'daemon',
607
+ code: 'HANDOFF_BORROW_UNAVAILABLE',
608
+ detail: 'The skrr background service on this computer is too old to lend this agent process a ' +
609
+ 'short-lived token, and a process it started for an agent never stores a sign-in. Update skrrd.',
610
+ };
611
+ }
612
+ if (offersAccessToken && (preferAccessToken || opts.borrowOnly || !offersRefreshFamily)) {
582
613
  const redeem = await redeemCliHandoffToken(bootstrap, {
583
614
  forceRefresh: false,
584
615
  timeoutMs: opts.timeoutMs,
@@ -1150,6 +1181,16 @@ async function attemptDaemonBrokerLoginAndPersist(opts) {
1150
1181
  // Callers use the token in-memory for this process only.
1151
1182
  if (outcome.brokered)
1152
1183
  return outcome;
1184
+ // Unreachable through `brokerThrough`, which never mints for a borrower;
1185
+ // kept so a future route cannot persist on a borrower's behalf.
1186
+ if (opts.borrowOnly) {
1187
+ return {
1188
+ ok: false,
1189
+ reason: 'unknown',
1190
+ code: 'HANDOFF_BORROW_UNAVAILABLE',
1191
+ detail: 'A borrower was handed a durable credential; it was not stored.',
1192
+ };
1193
+ }
1153
1194
  const serverOrigin = normalizeBaseUrl(opts.baseURL);
1154
1195
  if (!serverOrigin) {
1155
1196
  throw new Error('Daemon-broker credential persistence requires a valid CLI baseURL.');
@@ -1199,6 +1240,123 @@ const AUTH_SELF_MANAGED_COMMANDS = new Set([
1199
1240
  'pair',
1200
1241
  'generate-recovery-code',
1201
1242
  ]);
1243
+ /**
1244
+ * Why this invocation must borrow instead of adopting a CLI session of its
1245
+ * own, or null when it may adopt (OSK-13254).
1246
+ */
1247
+ function cliSessionAdoptionBlocker(cliConfig, env = process.env) {
1248
+ if ((0, harness_context_1.isHarnessCliContext)(env))
1249
+ return 'harness_context';
1250
+ if (env.SKRR_NO_CLI_SESSION_ADOPTION === '1')
1251
+ return 'opted_out';
1252
+ // `skrr logout` said "this CLI is signed out". The daemon may still LEND
1253
+ // tokens (logout says so), but a durable session does not reappear behind
1254
+ // that statement; the next `skrr login` clears the flag.
1255
+ if (cliConfig.adoptDaemonSession === false)
1256
+ return 'signed_out';
1257
+ return null;
1258
+ }
1259
+ /**
1260
+ * Failures that mean no daemon answered (or answered for another server) —
1261
+ * a second attempt on the token route cannot do better and would only wait
1262
+ * out the restart window twice.
1263
+ */
1264
+ function daemonDidNotAnswer(failure) {
1265
+ switch (failure.reason) {
1266
+ case 'no_bootstrap':
1267
+ case 'stale_bootstrap':
1268
+ case 'bootstrap_parse':
1269
+ case 'base_url_mismatch':
1270
+ case 'network':
1271
+ case 'timeout':
1272
+ return true;
1273
+ default:
1274
+ return false;
1275
+ }
1276
+ }
1277
+ /** The adoption lock: one per config root and profile. */
1278
+ function adoptionLockPath(profile) {
1279
+ const safe = profile.replace(/[^A-Za-z0-9._-]/g, '_') || 'default';
1280
+ return path.join((0, config_1.configRoot)(), `cli-session-adopt.${safe}.lock`);
1281
+ }
1282
+ function storedAccessToken(baseURL) {
1283
+ try {
1284
+ return (0, auth_storage_1.readFromBackend)(baseURL).bundle?.token ?? null;
1285
+ }
1286
+ catch {
1287
+ return null;
1288
+ }
1289
+ }
1290
+ /**
1291
+ * Adopt a CLI session through the local daemon (OSK-13254): the durable mint
1292
+ * (`/v1/auth/cli-handoff` → `POST /api/daemons/cli-handoff`), persisted like a
1293
+ * browser login, so that the next `skrr` reads it from the Keychain/file and
1294
+ * renews it itself — the daemon restarting no longer reads as signed out.
1295
+ *
1296
+ * Serialized across processes. The confirm that follows a mint retires every
1297
+ * OTHER live family of this cliId, so two concurrent adopters would each
1298
+ * revoke the other's session mid-command. Under the lock a process that
1299
+ * finds a session a sibling stored while it waited uses that one instead.
1300
+ *
1301
+ * Never worse than borrowing: if the lock cannot be taken in time, or the
1302
+ * daemon answered but would not mint (rate limit, a server refusal, a
1303
+ * broker-only descriptor), this borrows an access token for this process
1304
+ * exactly as before.
1305
+ */
1306
+ async function adoptCliSessionThroughDaemon(base, lockRetries) {
1307
+ const profile = base.profile ?? 'default';
1308
+ const baseURL = base.baseURL ?? '';
1309
+ const borrow = () => attemptDaemonBrokerLoginAndPersist({
1310
+ ...base,
1311
+ preferHandoffMode: 'access_token',
1312
+ borrowOnly: true,
1313
+ });
1314
+ // Adopt only from a live descriptor that can mint a family. One that
1315
+ // cannot (the Dedicated guest's access-token-only shape), or none at all (a
1316
+ // daemon that is down or restarting), has nothing to adopt: borrow without
1317
+ // taking the lock, so concurrent commands never queue behind it and the
1318
+ // borrow path's own restart wait and outage reporting apply unchanged.
1319
+ const usable = listUsableBootstraps(base);
1320
+ if (!usable.some(({ bootstrap }) => (0, cli_handoff_wire_1.advertisedHandoffModes)(bootstrap).includes('refresh_family'))) {
1321
+ return { outcome: await borrow() };
1322
+ }
1323
+ const before = storedAccessToken(baseURL);
1324
+ const lockPath = adoptionLockPath(profile);
1325
+ let release = null;
1326
+ try {
1327
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true, mode: 0o700 });
1328
+ if (!fs.existsSync(lockPath))
1329
+ fs.writeFileSync(lockPath, '', { mode: 0o600 });
1330
+ release = await proper_lockfile_1.default.lock(lockPath, {
1331
+ // Longer than one mint (20s loopback + 10s confirm), so a holder that
1332
+ // is still working is never judged stale.
1333
+ stale: 60_000,
1334
+ retries: { retries: lockRetries, minTimeout: 250, maxTimeout: 1_000, factor: 1.5 },
1335
+ });
1336
+ }
1337
+ catch {
1338
+ // Could not serialize: do not race a sibling's mint. Borrow.
1339
+ return { outcome: await borrow() };
1340
+ }
1341
+ let durable;
1342
+ try {
1343
+ const now = storedAccessToken(baseURL);
1344
+ if (now && now !== before)
1345
+ return { adoptedBySibling: true };
1346
+ durable = await attemptDaemonBrokerLoginAndPersist({
1347
+ ...base,
1348
+ preferHandoffMode: 'refresh_family',
1349
+ });
1350
+ }
1351
+ finally {
1352
+ await release().catch(() => undefined);
1353
+ }
1354
+ if (durable.ok || daemonDidNotAnswer(durable))
1355
+ return { outcome: durable };
1356
+ // The daemon answered but would not mint a family. The token route is a
1357
+ // different door with different limits; take it for this process.
1358
+ return { outcome: await borrow() };
1359
+ }
1202
1360
  /**
1203
1361
  * Auto-broker pre-flight for `BaseCommand.init()`. When the credential
1204
1362
  * resolver returns no token, this attempts a silent daemon broker so
@@ -1244,21 +1402,43 @@ async function maybeAutoBroker(opts) {
1244
1402
  // confirm cannot retire another profile's session. Persisted by the caller on
1245
1403
  // success so the rotation row stays bound across processes.
1246
1404
  const { cliId, updatedConfig } = (0, cli_identity_1.planLoginCliId)(opts.cliConfig);
1247
- const outcome = await attemptDaemonBrokerLoginAndPersist({
1405
+ const base = {
1248
1406
  cliId,
1249
1407
  baseURL: opts.cliConfig.baseURL,
1250
- // Auto-broker prefers the brokered access-token mode when the descriptor
1251
- // advertises it: nothing credential-shaped is persisted, and the next
1252
- // `skrr` process simply re-redeems over loopback. The legacy refresh-
1253
- // family mint remains the fallback for v1 descriptors and the redeem
1254
- // path for explicit `skrr login`.
1255
- preferHandoffMode: 'access_token',
1256
1408
  profile: opts.profile ?? (0, keychain_1.getActiveProfile)(),
1257
1409
  ...(opts.bootstrapPathOverride ? { bootstrapPathOverride: opts.bootstrapPathOverride } : {}),
1410
+ };
1411
+ const harness = (0, harness_context_1.isHarnessCliContext)();
1412
+ // OSK-13254 — a person's first `skrr` on a machine whose daemon is signed in
1413
+ // ADOPTS a CLI session of its own: a separate cli-scope family, minted by the
1414
+ // daemon's hand-off and stored like a browser login, so later commands do
1415
+ // not depend on the daemon being up. Everything else borrows a short-lived
1416
+ // token for this process, which is what the automatic path always did
1417
+ // (OSK-12018): a process the daemon started for an agent or tool, a 401
1418
+ // recovery, a profile signed out with `skrr logout`, and the opt-out.
1419
+ if (opts.adopt && !cliSessionAdoptionBlocker(opts.cliConfig)) {
1420
+ const adopted = await adoptCliSessionThroughDaemon(base, opts.adoptionLockRetries ?? 40);
1421
+ if (adopted.adoptedBySibling)
1422
+ return { triggered: true, adoptedBySibling: true };
1423
+ const outcome = adopted.outcome;
1424
+ return {
1425
+ triggered: true,
1426
+ outcome,
1427
+ ...(updatedConfig && outcome.ok ? { updatedConfig } : {}),
1428
+ };
1429
+ }
1430
+ const outcome = await attemptDaemonBrokerLoginAndPersist({
1431
+ ...base,
1432
+ // Borrow: the access-token mode, held in memory for this process; the next
1433
+ // `skrr` re-redeems over loopback. A v1 descriptor still takes the durable
1434
+ // mint here (the compatibility window in `brokerThrough`), except for a
1435
+ // harness-context process, which never stores a sign-in.
1436
+ preferHandoffMode: 'access_token',
1437
+ ...(harness ? { borrowOnly: true } : {}),
1258
1438
  });
1259
1439
  return {
1260
1440
  triggered: true,
1261
1441
  outcome,
1262
- ...(updatedConfig && outcome.ok ? { updatedConfig } : {}),
1442
+ ...(updatedConfig && outcome.ok && !harness ? { updatedConfig } : {}),
1263
1443
  };
1264
1444
  }
@@ -96,3 +96,17 @@ export declare function describeGuestDaemonUnavailable(failure: DaemonBrokerFail
96
96
  * nothing waited for): "Not signed in" is then the true sentence.
97
97
  */
98
98
  export declare function describeLocalDaemonUnavailable(failure: DaemonBrokerFailure, bin?: string): BrokerRefusalDescription | null;
99
+ /**
100
+ * The one description of a laptop or guest broker failure for every surface
101
+ * that has no separate guest probe of its own: a daemon that answered and
102
+ * refused (`describeBrokerRefusal`), else a daemon whose descriptor is on disk
103
+ * but which did not answer — down, crashed or mid-restart
104
+ * (`describeLocalDaemonUnavailable`).
105
+ *
106
+ * `requireAuth` composed these two by hand; `skrr login`'s progress line and
107
+ * headless refusal, and the mid-command 401 paths, consulted only the first,
108
+ * so a daemon that was restarting read there as "no daemon at all" — the
109
+ * generic browser refusal, or a bare "Run `skrr login`" (OSK-13099). Returns
110
+ * null only when there is genuinely nothing to say about a daemon.
111
+ */
112
+ export declare function describeBrokerFailure(failure: DaemonBrokerFailure, bin?: string): BrokerRefusalDescription | null;
@@ -4,6 +4,7 @@ exports.isAnsweredBrokerRefusal = isAnsweredBrokerRefusal;
4
4
  exports.describeBrokerRefusal = describeBrokerRefusal;
5
5
  exports.describeGuestDaemonUnavailable = describeGuestDaemonUnavailable;
6
6
  exports.describeLocalDaemonUnavailable = describeLocalDaemonUnavailable;
7
+ exports.describeBrokerFailure = describeBrokerFailure;
7
8
  /**
8
9
  * True when a daemon ANSWERED the hand-off and refused it — as opposed to no
9
10
  * daemon being there to ask (`no_bootstrap`, a stale descriptor nobody listens
@@ -261,7 +262,7 @@ function describeLocalDaemonUnavailable(failure, bin = 'skrr') {
261
262
  if (!headline)
262
263
  return null;
263
264
  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
+ `Retry in a few seconds, or start it with \`${bin} daemon start\` if it stays down. \`${bin} daemon status\` and \`${bin} daemon logs\` show the service's side, ` +
265
266
  `and \`${bin} login\` signs this CLI in without it.`;
266
267
  return {
267
268
  headline: `${headline}${waited}`,
@@ -289,3 +290,19 @@ function describeLocalDaemonUnavailable(failure, bin = 'skrr') {
289
290
  },
290
291
  };
291
292
  }
293
+ /**
294
+ * The one description of a laptop or guest broker failure for every surface
295
+ * that has no separate guest probe of its own: a daemon that answered and
296
+ * refused (`describeBrokerRefusal`), else a daemon whose descriptor is on disk
297
+ * but which did not answer — down, crashed or mid-restart
298
+ * (`describeLocalDaemonUnavailable`).
299
+ *
300
+ * `requireAuth` composed these two by hand; `skrr login`'s progress line and
301
+ * headless refusal, and the mid-command 401 paths, consulted only the first,
302
+ * so a daemon that was restarting read there as "no daemon at all" — the
303
+ * generic browser refusal, or a bare "Run `skrr login`" (OSK-13099). Returns
304
+ * null only when there is genuinely nothing to say about a daemon.
305
+ */
306
+ function describeBrokerFailure(failure, bin = 'skrr') {
307
+ return describeBrokerRefusal(failure, bin) ?? describeLocalDaemonUnavailable(failure, bin);
308
+ }
@@ -146,6 +146,8 @@ export interface AcquireManagedSessionInput {
146
146
  export interface AcquireManagedSessionDeps {
147
147
  prefetchAuth?: () => Promise<void>;
148
148
  hasCredential?: () => boolean;
149
+ /** null means this is not a standalone Dedicated guest invocation. */
150
+ borrowGuestCredential?: () => Promise<boolean | null>;
149
151
  /** `signal` is the acquisition-wide deadline; an override that ignores it is unbounded. */
150
152
  resolveAgent?: (ref?: string, signal?: AbortSignal) => Promise<FirstPartyHarnessAgent>;
151
153
  /** The user-JWT call. Defaults to `apiFetch`. */
@@ -75,6 +75,13 @@ const auth_core_init_1 = require("./auth-core-init");
75
75
  const config_1 = require("./config");
76
76
  const cli_id_1 = require("./cli-id");
77
77
  const cred_envelope_1 = require("./cred-envelope");
78
+ const dedicated_guest_1 = require("./dedicated-guest");
79
+ const daemonBroker_1 = require("./daemonBroker");
80
+ const daemonBrokerRefusal_1 = require("./daemonBrokerRefusal");
81
+ const cli_identity_1 = require("./cli-identity");
82
+ const credential_resolver_1 = require("./credential-resolver");
83
+ const harness_context_1 = require("./harness-context");
84
+ const node_adapter_1 = require("./node-adapter");
78
85
  const first_party_harness_1 = require("./first-party-harness");
79
86
  const first_party_harness_agent_1 = require("./first-party-harness-agent");
80
87
  const first_party_harness_managed_1 = require("./first-party-harness-managed");
@@ -162,6 +169,47 @@ class ManagedAcquisitionError extends Error {
162
169
  }
163
170
  }
164
171
  exports.ManagedAcquisitionError = ManagedAcquisitionError;
172
+ async function borrowDedicatedGuestCredential() {
173
+ if (!(0, dedicated_guest_1.runningOnDedicatedRuntimeGuest)() ||
174
+ (0, harness_context_1.isHarnessCliContext)() ||
175
+ (0, credential_resolver_1.resolveTrustedAutonomousCredential)()) {
176
+ return null;
177
+ }
178
+ // `skrr code run` does not extend BaseCommand, so its local credential check
179
+ // used to stop before the guest's daemon hand-off could be redeemed. Borrow
180
+ // only the fixed root-owned guest descriptor. No refresh family or credential
181
+ // is persisted, and apiFetch can re-redeem this DPoP-bound token on a 401.
182
+ try {
183
+ const cfg = (0, config_1.loadConfig)();
184
+ const outcome = await (0, daemonBroker_1.attemptDaemonBrokerLogin)({
185
+ cliId: (0, cli_identity_1.planLoginCliId)(cfg).cliId,
186
+ baseURL: cfg.baseURL,
187
+ bootstrapPathOverride: daemonBroker_1.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR,
188
+ preferHandoffMode: 'access_token',
189
+ borrowOnly: true,
190
+ timeoutMs: 10_000,
191
+ });
192
+ if (!outcome.ok) {
193
+ const description = (0, daemonBrokerRefusal_1.describeBrokerFailure)(outcome);
194
+ throw new ManagedAcquisitionError(outcome.code ?? 'guest-handoff-unavailable', description
195
+ ? `${description.headline} ${description.remedy}`
196
+ : `the Dedicated Runtime could not provide its managed skrr session (${outcome.reason})`);
197
+ }
198
+ if (!outcome.brokered)
199
+ return false;
200
+ (0, node_adapter_1.setAdapterCredentialOverride)({
201
+ token: outcome.accessToken,
202
+ source: 'env-token',
203
+ kind: (0, auth_core_1.classifyTokenKind)(outcome.accessToken),
204
+ });
205
+ return true;
206
+ }
207
+ catch (error) {
208
+ if (error instanceof ManagedAcquisitionError)
209
+ throw error;
210
+ return false;
211
+ }
212
+ }
165
213
  /**
166
214
  * Acquire a managed session, or throw a typed `ManagedAcquisitionError`.
167
215
  *
@@ -183,11 +231,28 @@ async function acquireManagedSession(input = {}, deps = {}) {
183
231
  // Checked locally, BEFORE any network. A fresh machine that has never run
184
232
  // `skrr login` must fall back to BYOK without a round trip and without a 401 that
185
233
  // would drag `apiFetch`'s refresh machinery in behind it.
186
- let localCredentialAvailable;
187
- if (deps.hasCredential) {
234
+ let localCredentialAvailable = false;
235
+ // Match BaseCommand's narrowly scoped retirement path: a stale guest refresh
236
+ // family must not prevent the live machine descriptor from lending a token.
237
+ const brokerBeforeStored = !deps.hasCredential &&
238
+ !(0, config_1.isEnvAuthOverride)() &&
239
+ !(0, harness_context_1.isHarnessCliContext)() &&
240
+ !(0, credential_resolver_1.resolveTrustedAutonomousCredential)() &&
241
+ (0, daemonBroker_1.shouldBrokerBeforeStoredCredential)();
242
+ if (brokerBeforeStored) {
243
+ try {
244
+ const guestHandoff = await (deps.borrowGuestCredential ?? borrowDedicatedGuestCredential)();
245
+ if (guestHandoff === true)
246
+ localCredentialAvailable = true;
247
+ }
248
+ catch {
249
+ // A usable stored credential remains the fallback during a hand-off outage.
250
+ }
251
+ }
252
+ if (!localCredentialAvailable && deps.hasCredential) {
188
253
  localCredentialAvailable = deps.hasCredential();
189
254
  }
190
- else {
255
+ else if (!localCredentialAvailable) {
191
256
  (0, cred_envelope_1.consumeEncryptedCredentialReadWhileInactive)();
192
257
  localCredentialAvailable = hasLocalCredential();
193
258
  if (!localCredentialAvailable && (0, cred_envelope_1.consumeEncryptedCredentialReadWhileInactive)()) {
@@ -195,6 +260,15 @@ async function acquireManagedSession(input = {}, deps = {}) {
195
260
  localCredentialAvailable = hasLocalCredential();
196
261
  }
197
262
  }
263
+ if (!localCredentialAvailable) {
264
+ const guestHandoff = await (deps.borrowGuestCredential ?? borrowDedicatedGuestCredential)();
265
+ if (guestHandoff === true) {
266
+ localCredentialAvailable = true;
267
+ }
268
+ else if (guestHandoff === false) {
269
+ throw new ManagedAcquisitionError('guest-handoff-unavailable', 'the Dedicated Runtime could not provide its managed skrr session');
270
+ }
271
+ }
198
272
  if (!localCredentialAvailable) {
199
273
  throw new ManagedAcquisitionError('not-signed-in', 'no skrr session on this machine — run `skrr login`');
200
274
  }
@@ -0,0 +1,8 @@
1
+ export declare function isHarnessCliContext(env?: Readonly<Record<string, string | undefined>>): boolean;
2
+ /** Thrown by `writeToBackend` when a harness-context process tries to persist. */
3
+ export declare class HarnessCredentialPersistRefused extends Error {
4
+ readonly code = "HARNESS_CONTEXT_PERSIST_REFUSED";
5
+ constructor();
6
+ }
7
+ /** The commands a harness-context process may not run (they own the stored session). */
8
+ export declare const HARNESS_REFUSED_AUTH_COMMANDS: ReadonlySet<string>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HARNESS_REFUSED_AUTH_COMMANDS = exports.HarnessCredentialPersistRefused = void 0;
4
+ exports.isHarnessCliContext = isHarnessCliContext;
5
+ /**
6
+ * harness-context.ts — is this `skrr` running for an agent the daemon started?
7
+ * (OSK-13254)
8
+ *
9
+ * A person's `skrr` may adopt a CLI refresh session of its own through the
10
+ * local daemon's hand-off, so that signing in survives the daemon restarting
11
+ * (`daemonBroker.ts`, `maybeAutoBroker`). A `skrr` the daemon spawned for an
12
+ * agent or a tool must not: it borrows short-lived tokens from the daemon for
13
+ * each process, and never reads, writes, rotates or revokes the person's
14
+ * stored CLI session.
15
+ *
16
+ * Two daemon-set markers say so, and either is enough:
17
+ *
18
+ * - `OVERSKY_AUTONOMOUS_SESSION=1` — a harness session (`session-wrapper.ts`,
19
+ * `api-local-session-client.ts`). Such a process already never reaches the
20
+ * stored-credential chain (`BaseCommand.init` returns early on it); it is
21
+ * listed here so every guard below answers the same question.
22
+ * - `SKRR_DAEMON_CHILD` — every other agent/tool child: one-shot Bash,
23
+ * background Bash, agent terminals, harness backends
24
+ * (`daemon/src/env-sanitize.ts`, `agent-terminal-env.ts`). It survives the
25
+ * shared sanitizer, so grandchildren keep it.
26
+ *
27
+ * What this is and is not, stated so nobody leans on it harder than it holds:
28
+ * on a laptop the daemon, the agent and the person share one uid, so this is
29
+ * POLICY — a child that deletes the marker can do what the person's shell can.
30
+ * Where a uid boundary exists (Dedicated Runtime, identity-split Hosted
31
+ * Machines) the guest descriptor offers only the access-token mode, so a
32
+ * durable family cannot be minted there whatever the environment says. This
33
+ * is the same scope invariant I1 has today
34
+ * (`docs/architecture/managed-inference-execution-shapes-2026-08-12.md` §15).
35
+ */
36
+ const auth_core_1 = require("@skrr-ai/auth-core");
37
+ function isHarnessCliContext(env = process.env) {
38
+ return env.OVERSKY_AUTONOMOUS_SESSION === '1' || (0, auth_core_1.isDaemonChildEnv)(env);
39
+ }
40
+ /** Thrown by `writeToBackend` when a harness-context process tries to persist. */
41
+ class HarnessCredentialPersistRefused extends Error {
42
+ code = 'HARNESS_CONTEXT_PERSIST_REFUSED';
43
+ constructor() {
44
+ super('Refusing to store a skrr sign-in from a process the skrr background service started for an ' +
45
+ 'agent or tool. skrr here borrows short-lived tokens from the service; sign in from your own terminal.');
46
+ this.name = 'HarnessCredentialPersistRefused';
47
+ }
48
+ }
49
+ exports.HarnessCredentialPersistRefused = HarnessCredentialPersistRefused;
50
+ /** The commands a harness-context process may not run (they own the stored session). */
51
+ exports.HARNESS_REFUSED_AUTH_COMMANDS = new Set([
52
+ 'login',
53
+ 'logout',
54
+ 'pair',
55
+ ]);
package/dist/lib/login.js CHANGED
@@ -202,7 +202,10 @@ async function cliLogin(opts = {}) {
202
202
  // The daemon answered and refused. Say so before whatever comes next —
203
203
  // a browser that opens with no stated cause, or a refusal about terminals
204
204
  // that says nothing about the daemon, both hid the real reason.
205
- brokerRefusal = (0, daemonBrokerRefusal_1.describeBrokerRefusal)(brokerResult);
205
+ // A daemon whose descriptor is on disk but which did not answer (down or
206
+ // mid-restart) is described too: without it a headless `skrr login` during
207
+ // a restart said only that this shell has no terminal (OSK-13099).
208
+ brokerRefusal = (0, daemonBrokerRefusal_1.describeBrokerFailure)(brokerResult);
206
209
  if (brokerRefusal && !(0, ssh_detect_1.isNonInteractive)()) {
207
210
  console.log('');
208
211
  console.log(` Asked the local skrr service for a session first. ${brokerRefusal.headline}`);
@@ -30,6 +30,7 @@ const config_1 = require("./config");
30
30
  const cli_id_1 = require("./cli-id");
31
31
  const credential_resolver_1 = require("./credential-resolver");
32
32
  const device_identity_1 = require("./device-identity");
33
+ const harness_context_1 = require("./harness-context");
33
34
  exports.CLI_REFRESH_EARLY_MS = 5 * 60 * 1000;
34
35
  /**
35
36
  * Stable origin header so the server's benign-grace matcher can identify
@@ -96,6 +97,9 @@ function canMutatePersistedCredential(credential, bareMode) {
96
97
  return false;
97
98
  if ((0, auth_core_1.isStaticTokenKind)(credential.kind))
98
99
  return false;
100
+ // A borrower never rotates the person's stored family (OSK-13254).
101
+ if ((0, harness_context_1.isHarnessCliContext)())
102
+ return false;
99
103
  // These sources are operator-owned or generated on demand. Never rotate
100
104
  // them into the local keychain/file store behind the operator's back.
101
105
  return credential.source === 'keychain' || credential.source === 'file';
@@ -35,7 +35,7 @@ export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest,
35
35
  export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
36
36
  export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
37
37
  export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, harnessLoadsClaudeSkillsLayout, } from './harnessSkillLayout.js';
38
- export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
38
+ export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, DAEMON_CHILD_ENV, isDaemonChildEnv, LOOPBACK_NO_PROXY_HOSTS, envHasProxy, withLoopbackNoProxy, } from './spawnEnv.js';
39
39
  export { loopbackFetch, assertLoopbackHttpUrl, isLoopbackHostname, isLoopbackAddress, LoopbackTargetRefusedError, LOOPBACK_DEFAULT_MAX_BODY_BYTES, type LoopbackFetch, type LoopbackFetchInit, } from './loopbackHttp.js';
40
40
  export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
41
41
  export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
@@ -25,8 +25,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.isStaticTokenKind = exports.PERSONAL_ACCESS_TOKEN_PREFIX = exports.CI_TOKEN_PREFIX = exports.classifyTokenKind = exports.invalidateAuthHelperCache = exports.__resetCredentialResolverForTest = exports.CREDENTIAL_PRECEDENCE = exports.CredentialResolver = exports.__setFdReaderForTest = exports.readAuthFromFd = exports.createRefreshScheduler = exports.UNKNOWN_401_BUDGET = exports.__resetUnknown401StreaksForTest = exports.__refreshDiagnostics = exports.installLockExitHandlers = exports.clearReauthState = exports.needsReauthState = exports.persistReauthState = exports.withAuthLock = exports.revokeDaemonRefreshSession = exports.refreshDaemonToken = exports.classifyRefreshResponse = exports.EXIT_NEEDS_REAUTH = exports.reauthCommand = exports.formatReauthMessage = exports.handleMidStreamAuthExpiry = exports.isAuthExpiredSseEvent = exports.decodeJwtPayload = exports.decodeJwtExpiry = exports.TransientAuthFailure = exports.PermanentAuthFailure = exports.emitAuthTelemetry = exports.getAuthBinaryName = exports.getAuthKillSwitches = exports.getAuthConfigDir = exports.getAuthLogger = exports.configureAuthCore = exports.OAUTH_CLI_SCOPE = exports.isDaemonScope = exports.VALID_DAEMON_SCOPE_SET = exports.ALL_DAEMON_SCOPES = exports.SCOPES = void 0;
26
26
  exports.jwkThumbprint = exports.generateDeviceKeyPair = exports.buildDeviceProof = exports.zeroizeKekCaches = exports.WindowsKek = exports.registerZeroizeHook = exports.MacosKek = exports.LinuxKek = exports.KekUnavailableError = exports.UnavailableKek = exports.InMemoryKek = exports.getKekStrategy = exports.__setKekStrategyForTest = exports.credEnvelopeWrap = exports.credEnvelopeUnwrap = exports.credEnvelopeSerialize = exports.credSealBuffer = exports.credOpenBuffer = exports.CRED_KEY_LEN = exports.isCredEnvelopeString = exports.generateDek = exports.CRED_ENVELOPE_MAGIC = exports.credEnvelopeEncrypt = exports.credEnvelopeDeserialize = exports.credEnvelopeDecrypt = exports.buildCredentialAad = exports.isValidRecoveryCode = exports.normalizeRecoveryCode = exports.parseRecoveryCode = exports.formatRecoveryCode = exports.MAX_TTL_DAYS = exports.MIN_TTL_DAYS = exports.parseTtlDays = exports.LoginExchangeError = exports.LoginInitError = exports.loginWithLocalhost = exports.LocalCallbackMalformedError = exports.LocalCallbackDeniedError = exports.LocalCallbackStateMismatchError = exports.LocalCallbackTimeoutError = exports.LocalCallbackBindError = exports.LocalCallbackError = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.startLocalCallback = exports.verifyChallenge = exports.safeEqual = exports.generateState = exports.challenge = exports.generateVerifier = exports.invalidateHeadersHelperCache = void 0;
27
27
  exports.ensureMachineDaemonId = exports.__resetMachineUuidCacheForTest = exports.HOSTED_RUNTIME_MACHINE_SOURCES = exports.hostedRuntimeIdOverride = exports.getMachineUuid = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceKeySplit = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.DEFAULT_UNREADABLE_CREDENTIAL_HINT = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.splitKeyConvergencePolicy = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDpopProof = exports.normalizeDpopHtu = exports.isJwkThumbprint = exports.isAth = exports.createDpopKey = exports.accessTokenHash = exports.DPOP_PROOF_VERIFY_UNAVAILABLE = exports.DPOP_PROOF_TYP = exports.DPOP_PROOF_REQUIRED = exports.DPOP_PROOF_INVALID = exports.DPOP_MAX_AGE_SECONDS = exports.DPOP_HEADER = exports.verifyDeviceProof = exports.tokenHash = exports.publicJwkFromX = exports.normalizeHtu = void 0;
28
- exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES = exports.LoopbackTargetRefusedError = exports.isLoopbackAddress = exports.isLoopbackHostname = exports.assertLoopbackHttpUrl = exports.loopbackFetch = exports.withLoopbackNoProxy = exports.envHasProxy = exports.LOOPBACK_NO_PROXY_HOSTS = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.harnessLoadsClaudeSkillsLayout = exports.claudeSkillsLayoutEvidence = exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = exports.getHarnessTier = exports.HARNESS_TIERS = exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = void 0;
29
- exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = void 0;
28
+ exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.LOOPBACK_DEFAULT_MAX_BODY_BYTES = exports.LoopbackTargetRefusedError = exports.isLoopbackAddress = exports.isLoopbackHostname = exports.assertLoopbackHttpUrl = exports.loopbackFetch = exports.withLoopbackNoProxy = exports.envHasProxy = exports.LOOPBACK_NO_PROXY_HOSTS = exports.isDaemonChildEnv = exports.DAEMON_CHILD_ENV = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.harnessLoadsClaudeSkillsLayout = exports.claudeSkillsLayoutEvidence = exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = exports.getHarnessTier = exports.HARNESS_TIERS = exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = void 0;
29
+ exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.isTrustedArtifactOrigin = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.TRUSTED_FEED_ORIGINS = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = exports.describeLegacyState = exports.findLegacyLocalState = void 0;
30
30
  // Daemon scope vocabulary — single source of truth for non-Mongoose tiers
31
31
  // (daemon, desktop, cli). Schema-bound canonical lives in
32
32
  // `@skrr-ai/data-schemas/common/daemonScopes`; the two are kept byte-equal
@@ -346,6 +346,8 @@ Object.defineProperty(exports, "SENSITIVE_ENV_PREFIXES", { enumerable: true, get
346
346
  Object.defineProperty(exports, "SENSITIVE_ENV_SUFFIXES", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_SUFFIXES; } });
347
347
  Object.defineProperty(exports, "SENSITIVE_ENV_NAME_TOKENS", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_NAME_TOKENS; } });
348
348
  Object.defineProperty(exports, "SENSITIVE_ENV_PREFIX_EXCEPTIONS", { enumerable: true, get: function () { return spawnEnv_js_1.SENSITIVE_ENV_PREFIX_EXCEPTIONS; } });
349
+ Object.defineProperty(exports, "DAEMON_CHILD_ENV", { enumerable: true, get: function () { return spawnEnv_js_1.DAEMON_CHILD_ENV; } });
350
+ Object.defineProperty(exports, "isDaemonChildEnv", { enumerable: true, get: function () { return spawnEnv_js_1.isDaemonChildEnv; } });
349
351
  Object.defineProperty(exports, "LOOPBACK_NO_PROXY_HOSTS", { enumerable: true, get: function () { return spawnEnv_js_1.LOOPBACK_NO_PROXY_HOSTS; } });
350
352
  Object.defineProperty(exports, "envHasProxy", { enumerable: true, get: function () { return spawnEnv_js_1.envHasProxy; } });
351
353
  Object.defineProperty(exports, "withLoopbackNoProxy", { enumerable: true, get: function () { return spawnEnv_js_1.withLoopbackNoProxy; } });
@@ -77,6 +77,31 @@ export declare const SENSITIVE_ENV_SUFFIXES: readonly string[];
77
77
  export declare const SENSITIVE_ENV_NAME_TOKENS: readonly string[];
78
78
  /** Specific names to keep — the prefix sweeps are broad on purpose. */
79
79
  export declare const SENSITIVE_ENV_PREFIX_EXCEPTIONS: ReadonlySet<string>;
80
+ /**
81
+ * OSK-13254 — the marker the daemon stamps on every agent- or tool-driven child
82
+ * (`daemon/src/env-sanitize.ts`, `daemon/src/terminal/agent-terminal-env.ts`).
83
+ *
84
+ * A `skrr` that sees it is a borrower: it redeems short-lived tokens from the
85
+ * daemon's loopback broker, never adopts a CLI refresh session of its own, and
86
+ * never reads the person's stored one (`cli/src/lib/harness-context.ts`). A
87
+ * person's own terminal — including the web terminal, which is a human surface
88
+ * (`humanTerminalEnv`) — does not carry it.
89
+ *
90
+ * The marker can only take authority away, so a process that forges it hurts
91
+ * nobody but itself. On a laptop it is POLICY, not a boundary: a same-uid child
92
+ * that deletes it can do whatever the person's own shell can. Where a uid
93
+ * boundary exists (Dedicated Runtime, identity-split Hosted Machines), the
94
+ * guest descriptor already offers only the access-token mode, so a durable CLI
95
+ * family cannot be minted there whatever the environment says.
96
+ */
97
+ export declare const DAEMON_CHILD_ENV = "SKRR_DAEMON_CHILD";
98
+ /**
99
+ * True when `env` carries the daemon-child marker with ANY non-empty value.
100
+ * Deliberately no "0 means off": the marker only narrows authority, so the
101
+ * one value that could switch it off is the one a request-carried environment
102
+ * would forge.
103
+ */
104
+ export declare function isDaemonChildEnv(env: Readonly<Record<string, string | undefined>>): boolean;
80
105
  /**
81
106
  * Build a sanitized snapshot of the parent's own env for a spawned
82
107
  * child, stripping credentials and library-injection vectors while