@skrr-ai/cli 0.1.52 → 0.1.53

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/commands/agents/chat.js +2 -1
  2. package/dist/commands/agents/create.js +13 -4
  3. package/dist/commands/agents/update.d.ts +32 -0
  4. package/dist/commands/agents/update.js +65 -14
  5. package/dist/commands/tasks/approvals/list.d.ts +22 -0
  6. package/dist/commands/tasks/approvals/list.js +80 -3
  7. package/dist/commands/tasks/create.js +2 -1
  8. package/dist/commands/tasks/start.js +1 -1
  9. package/dist/commands/tasks/update.js +23 -3
  10. package/dist/lib/agent-config.d.ts +13 -0
  11. package/dist/lib/agent-config.js +32 -0
  12. package/dist/lib/agentic-stream.d.ts +1 -1
  13. package/dist/lib/agentic-stream.js +1 -1
  14. package/dist/lib/auth-storage.js +7 -3
  15. package/dist/lib/cred-envelope.d.ts +12 -1
  16. package/dist/lib/cred-envelope.js +20 -3
  17. package/dist/lib/dedicated-machines.d.ts +20 -2
  18. package/dist/lib/dedicated-machines.js +46 -5
  19. package/dist/lib/keychain.js +26 -1
  20. package/dist/lib/label-ref.d.ts +1 -1
  21. package/dist/lib/label-ref.js +7 -3
  22. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +13 -1
  23. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +9 -3
  24. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  25. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +4 -3
  26. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +13 -1
  27. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +8 -3
  28. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  29. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  30. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  31. package/oclif.manifest.json +35912 -35900
  32. package/package.json +1 -1
@@ -122,6 +122,11 @@ export interface DedicatedImageStanding {
122
122
  * machine runs that image and its daemon reported a version. Null otherwise.
123
123
  */
124
124
  approvedImageDaemonVersion?: string | null;
125
+ /**
126
+ * Why `approvedImageDaemonVersion` is null, from a closed server vocabulary.
127
+ * Absent from a server older than this field.
128
+ */
129
+ approvedImageDaemonUnknownReason?: 'image_superseded' | 'approval_unreadable' | 'daemon_unreported' | null;
125
130
  /** When the older image stops being supported; null when there is no deadline. */
126
131
  updateBy?: string | null;
127
132
  lastUpdate?: {
@@ -150,8 +155,21 @@ export declare function compareDaemonVersions(a: string, b: string): number | nu
150
155
  * half is said to be unknown rather than left out.
151
156
  */
152
157
  export declare function describeDedicatedDaemonRelease(image: DedicatedImageStanding, latest: LatestDaemonRelease | null | undefined): string | null;
153
- /** One line saying where a machine's image stands and what the owner can do. */
154
- export declare function describeDedicatedImageStanding(image: DedicatedImageStanding, leaseId?: string): string;
158
+ /**
159
+ * One line saying where a machine's image stands and what the owner can do.
160
+ *
161
+ * "current" is claimed only when it is KNOWN: no newer approved image AND this
162
+ * machine's own daemon is at the latest release (OSK-12153). A guest never
163
+ * updates its own daemon, so "no newer approved image" beside a daemon that is
164
+ * behind the latest release is a different — and worse — state than "current",
165
+ * and the line says so instead of collapsing toward the reassuring reading.
166
+ * With the release feed unread, or no daemon version, it says only what is
167
+ * known: there is no newer approved image.
168
+ */
169
+ export declare function describeDedicatedImageStanding(image: DedicatedImageStanding, leaseId?: string, opts?: {
170
+ daemonVersion?: string | null;
171
+ latestDaemonRelease?: LatestDaemonRelease | null;
172
+ }): string;
155
173
  /**
156
174
  * A deleted workspace: restorable into a new machine until `recoverableUntil`,
157
175
  * then every copy is deleted and `completedAt` records it. `withheld`: an unpaid
@@ -107,7 +107,7 @@ function describeDedicatedDaemonRelease(image, latest) {
107
107
  ? `latest release skrrd ${latest.version}`
108
108
  : `latest release unknown (${latest.error})`;
109
109
  if (!carried) {
110
- return image.updateAvailable ? latestText : `approved image's daemon unknown · ${latestText}`;
110
+ return `${approvedImageDaemonUnknownText(image)} · ${latestText}`;
111
111
  }
112
112
  if (!('version' in latest)) {
113
113
  return `approved image carries skrrd ${carried} · ${latestText}`;
@@ -121,8 +121,35 @@ function describeDedicatedDaemonRelease(image, latest) {
121
121
  }
122
122
  return `approved image carries skrrd ${carried} · ${latestText}`;
123
123
  }
124
- /** One line saying where a machine's image stands and what the owner can do. */
125
- function describeDedicatedImageStanding(image, leaseId) {
124
+ /**
125
+ * Why the server could not say which daemon the approved image carries, in
126
+ * words an owner can act on (OSK-12153). A bare "unknown" named no cause and no
127
+ * next step.
128
+ */
129
+ function approvedImageDaemonUnknownText(image) {
130
+ switch (image.approvedImageDaemonUnknownReason) {
131
+ case 'image_superseded':
132
+ return 'this machine runs an older image than the approved one, so which skrrd the approved image carries is seen once it moves onto it';
133
+ case 'approval_unreadable':
134
+ return "the server cannot compare this machine's image with the approved one, so it cannot say which skrrd the approved image carries";
135
+ case 'daemon_unreported':
136
+ return 'this machine runs the approved image, but its daemon has not reported a version yet';
137
+ default:
138
+ return 'the server did not say which skrrd the approved image carries';
139
+ }
140
+ }
141
+ /**
142
+ * One line saying where a machine's image stands and what the owner can do.
143
+ *
144
+ * "current" is claimed only when it is KNOWN: no newer approved image AND this
145
+ * machine's own daemon is at the latest release (OSK-12153). A guest never
146
+ * updates its own daemon, so "no newer approved image" beside a daemon that is
147
+ * behind the latest release is a different — and worse — state than "current",
148
+ * and the line says so instead of collapsing toward the reassuring reading.
149
+ * With the release feed unread, or no daemon version, it says only what is
150
+ * known: there is no newer approved image.
151
+ */
152
+ function describeDedicatedImageStanding(image, leaseId, opts = {}) {
126
153
  const last = image.lastUpdate;
127
154
  if (last?.status === 'applying') {
128
155
  return `moving onto the current image${last.startedAt ? ` since ${last.startedAt}` : ''}; files are kept`;
@@ -141,7 +168,18 @@ function describeDedicatedImageStanding(image, leaseId) {
141
168
  parts.push('the last image update did not complete on either image; files are kept');
142
169
  }
143
170
  else {
144
- parts.push('current');
171
+ const here = String(opts.daemonVersion || '').trim();
172
+ const latest = opts.latestDaemonRelease;
173
+ const compared = here && latest && 'version' in latest ? compareDaemonVersions(here, latest.version) : null;
174
+ if (compared === -1 && latest && 'version' in latest) {
175
+ parts.push(`no newer approved image, but this machine's skrrd ${here} is behind the latest release ${latest.version}`);
176
+ }
177
+ else if (compared !== null) {
178
+ parts.push('current');
179
+ }
180
+ else {
181
+ parts.push('no newer approved image');
182
+ }
145
183
  }
146
184
  if (last?.status === 'succeeded' && last.settledAt)
147
185
  parts.push(`updated ${last.settledAt}`);
@@ -631,7 +669,10 @@ function describeDedicatedLease(lease, harnesses = [], opts = {}) {
631
669
  const image = [resources.imageVersion || lease.daemon?.imageVersion].filter(Boolean).join('');
632
670
  row('image', image);
633
671
  if (lease.health?.image) {
634
- row('image update', describeDedicatedImageStanding(lease.health.image, dedicatedLeaseId(lease)));
672
+ row('image update', describeDedicatedImageStanding(lease.health.image, dedicatedLeaseId(lease), {
673
+ daemonVersion: lease.daemon?.version,
674
+ latestDaemonRelease: opts.latestDaemonRelease,
675
+ }));
635
676
  row('image daemon', describeDedicatedDaemonRelease(lease.health.image, opts.latestDaemonRelease));
636
677
  }
637
678
  if (lease.daemon?.version || lease.daemon?.lastHeartbeatAt) {
@@ -290,7 +290,7 @@ function readSlot(account, service = currentService(), raw_ = false) {
290
290
  // re-login; the bundle is unrecoverable without the DEK).
291
291
  // - Legacy plaintext + path active → returns plaintext + needsMigration;
292
292
  // we re-write in place to upgrade the slot.
293
- const result = (0, cred_envelope_1.maybeDecryptOnRead)(raw);
293
+ const result = (0, cred_envelope_1.maybeDecryptOnRead)(raw, { foreignOwnerRoot: foreignOwnerRootAt(service) });
294
294
  if (result.plaintext === null)
295
295
  return null;
296
296
  // Only this root's OWN slot is upgraded. A slot read as a migration source may
@@ -308,6 +308,31 @@ function readSlot(account, service = currentService(), raw_ = false) {
308
308
  }
309
309
  return result.plaintext;
310
310
  }
311
+ /**
312
+ * The config root named by `service`'s owner record, when it is NOT this
313
+ * process's root; otherwise undefined. The record is non-secret JSON
314
+ * (`{configRoot, cliId}`), so it can be read before the secret it describes.
315
+ *
316
+ * Only a diagnostic input. Whether the bundle is ours is still decided by
317
+ * `auth-storage`'s `judgeOwnership`, which also weighs the token's own `did`
318
+ * — so the secret is decrypted as before, and only what a failure SAYS changes.
319
+ */
320
+ function foreignOwnerRootAt(service) {
321
+ const raw = readSlot(ACCOUNT_OWNER, service);
322
+ if (!raw)
323
+ return undefined;
324
+ let root;
325
+ try {
326
+ root = JSON.parse(raw)?.configRoot;
327
+ }
328
+ catch {
329
+ return undefined;
330
+ }
331
+ if (typeof root !== 'string' || root.length === 0)
332
+ return undefined;
333
+ const resolved = path.resolve(root);
334
+ return resolved === resolvedConfigRoot() ? undefined : resolved;
335
+ }
311
336
  /**
312
337
  * Escape a value for `security -i` (stdin interactive mode).
313
338
  * `security` accepts double-quoted args with `\"` and `\\` backslash escapes.
@@ -103,7 +103,7 @@ export declare class LabelRefError extends Error {
103
103
  */
104
104
  export declare function resolveLabelRefs(refs: string[] | undefined, context: LabelRefContext): Promise<string[]>;
105
105
  /**
106
- * Help for a `--tag` flag that sits beside `--label` (OSK-12053).
106
+ * Help for a `--tag` flag that sits beside `--label-id` (OSK-12053, OSK-12166).
107
107
  *
108
108
  * Tags and labels are two DIFFERENT vocabularies: a tag is any free string,
109
109
  * stored as typed on `task.tags`; a label is declared (`skrr labels`) and is
@@ -175,7 +175,7 @@ async function resolveLabelRefs(refs, context) {
175
175
  return resolved;
176
176
  }
177
177
  /**
178
- * Help for a `--tag` flag that sits beside `--label` (OSK-12053).
178
+ * Help for a `--tag` flag that sits beside `--label-id` (OSK-12053, OSK-12166).
179
179
  *
180
180
  * Tags and labels are two DIFFERENT vocabularies: a tag is any free string,
181
181
  * stored as typed on `task.tags`; a label is declared (`skrr labels`) and is
@@ -185,7 +185,8 @@ async function resolveLabelRefs(refs, context) {
185
185
  * and none of them was labelled.
186
186
  */
187
187
  exports.TAG_FLAG_HELP = 'Free-form tag (repeatable), stored exactly as typed and never checked against anything. ' +
188
- 'NOT a label: to classify the task under a declared label (`labels list`), use --label.';
188
+ 'NOT a label: to classify the task under a declared label (`labels list`), use --label-id ' +
189
+ '(it takes a label name or id).';
189
190
  /**
190
191
  * The `--tag` values that equal a declared label's name, each paired with that
191
192
  * label — the one case where the mistake is certain enough to say so.
@@ -221,7 +222,10 @@ function describeTagsNamingLabels(hits) {
221
222
  if (hits.length === 0)
222
223
  return '';
223
224
  const names = hits.map((hit) => `"${hit.tag}"`).join(', ');
224
- const flags = hits.map((hit) => `--label ${hit.label.name}`).join(' ');
225
+ // `--label-id` is the flag the help LISTS; `--label` is a hidden alias of it,
226
+ // so a warning that named the alias sent people to a flag they could not
227
+ // find in `--help` (OSK-12166).
228
+ const flags = hits.map((hit) => `--label-id ${hit.label.name}`).join(' ');
225
229
  return (`--tag ${names} ${hits.length === 1 ? 'was' : 'were'} stored as free-form ` +
226
230
  `${hits.length === 1 ? 'tag' : 'tags'}, NOT as the declared label of the same name, ` +
227
231
  `so a label filter will not find this task. To attach the label, pass ${flags}.`);
@@ -141,7 +141,19 @@ export interface ReadTransformResult {
141
141
  * path is active — caller should re-write to upgrade. */
142
142
  needsMigration: boolean;
143
143
  }
144
- export declare function maybeDecryptOnRead(stored: string): ReadTransformResult;
144
+ /** What the caller knows about the stored value that the envelope does not. */
145
+ export interface DecryptOnReadOptions {
146
+ /**
147
+ * Replaces the default `decrypt_failed` hint. The default says a re-login
148
+ * rebuilds the credential, which is true only of THIS config's own value. A
149
+ * caller reading a value another config sealed under its own key knows the
150
+ * failure is expected and a re-login here changes nothing (OSK-12135).
151
+ */
152
+ unreadableHint?: string;
153
+ }
154
+ /** The `decrypt_failed` hint when the caller has no better explanation. */
155
+ export declare const DEFAULT_UNREADABLE_CREDENTIAL_HINT = "stored credential is unreadable on this machine \u2014 signing in again rebuilds it";
156
+ export declare function maybeDecryptOnRead(stored: string, options?: DecryptOnReadOptions): ReadTransformResult;
145
157
  /** @internal Test seam — wrapped-DEK path resolver. */
146
158
  export declare function __wrappedDekFilePathForTest(profile: string): string;
147
159
  export {};
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_UNREADABLE_CREDENTIAL_HINT = void 0;
6
7
  exports.__setStateForTest = __setStateForTest;
7
8
  exports.__getStateForTest = __getStateForTest;
8
9
  exports.__clearWarnDedupeForTest = __clearWarnDedupeForTest;
@@ -165,7 +166,10 @@ function emit(eventName, success, metadata) {
165
166
  metadata: { event: eventName, ...metadata },
166
167
  });
167
168
  if (!success) {
168
- const signature = `${eventName}:${metadata.kekId ?? ''}:${metadata.kekKind ?? ''}:${metadata.message ?? ''}`;
169
+ // The hint is part of the signature: the same crypto failure on this
170
+ // config's own credential and on another config's is two different facts,
171
+ // and the second must not swallow the first (OSK-12135).
172
+ const signature = `${eventName}:${metadata.kekId ?? ''}:${metadata.kekKind ?? ''}:${metadata.message ?? ''}:${metadata.hint ?? ''}`;
169
173
  if (!warnedSignatures.has(signature)) {
170
174
  if (warnedSignatures.size >= MAX_WARNED_SIGNATURES)
171
175
  warnedSignatures.clear();
@@ -755,7 +759,9 @@ function wrapIfActiveOrPassthrough(plaintext) {
755
759
  return plaintext;
756
760
  }
757
761
  }
758
- function maybeDecryptOnRead(stored) {
762
+ /** The `decrypt_failed` hint when the caller has no better explanation. */
763
+ exports.DEFAULT_UNREADABLE_CREDENTIAL_HINT = 'stored credential is unreadable on this machine — signing in again rebuilds it';
764
+ function maybeDecryptOnRead(stored, options = {}) {
759
765
  if (typeof stored !== 'string' || stored.length === 0) {
760
766
  return { plaintext: stored, needsMigration: false };
761
767
  }
@@ -787,7 +793,7 @@ function maybeDecryptOnRead(stored) {
787
793
  // The crypto error alone reads as a crash; the actionable truth is
788
794
  // that this stored credential is unreadable by THIS binary (usually
789
795
  // a signing-identity or profile drift) and re-login rebuilds it.
790
- hint: 'stored credential is unreadable on this machine — signing in again rebuilds it',
796
+ hint: options.unreadableHint ?? exports.DEFAULT_UNREADABLE_CREDENTIAL_HINT,
791
797
  });
792
798
  return { plaintext: null, needsMigration: false };
793
799
  }
@@ -27,7 +27,7 @@ export { formatRecoveryCode, parseRecoveryCode, normalizeRecoveryCode, isValidRe
27
27
  export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as credEnvelopeDeserialize, encrypt as credEnvelopeEncrypt, ENVELOPE_MAGIC as CRED_ENVELOPE_MAGIC, generateDek, isEnvelopeString as isCredEnvelopeString, KEY_LEN as CRED_KEY_LEN, openBuffer as credOpenBuffer, sealBuffer as credSealBuffer, serialize as credEnvelopeSerialize, unwrap as credEnvelopeUnwrap, wrap as credEnvelopeWrap, type SealedBlob as CredSealedBlob, } from './credentialEnvelope.js';
28
28
  export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, LinuxKek, MacosKek, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type KekStrategy, } from './kek/index.js';
29
29
  export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyPair, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
30
- export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
30
+ export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
31
31
  export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
32
32
  export { getMachineUuid, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
33
33
  export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
@@ -24,9 +24,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.generateVerifier = exports.invalidateHeadersHelperCache = exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = 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.publicJwkFromX = exports.normalizeHtu = 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 = void 0;
27
- 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 = 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.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDeviceProof = exports.tokenHash = void 0;
28
- 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 = 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 = 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 = void 0;
27
+ 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 = 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.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.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDeviceProof = exports.tokenHash = void 0;
28
+ 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 = 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 = 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 = 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
@@ -234,6 +234,7 @@ Object.defineProperty(exports, "hasCredEnvelopePathConflict", { enumerable: true
234
234
  Object.defineProperty(exports, "describeCredEnvelopeState", { enumerable: true, get: function () { return credentialEnvelopeBridge_js_1.describeCredEnvelopeState; } });
235
235
  Object.defineProperty(exports, "maybeEncryptForWrite", { enumerable: true, get: function () { return credentialEnvelopeBridge_js_1.maybeEncryptForWrite; } });
236
236
  Object.defineProperty(exports, "maybeDecryptOnRead", { enumerable: true, get: function () { return credentialEnvelopeBridge_js_1.maybeDecryptOnRead; } });
237
+ Object.defineProperty(exports, "DEFAULT_UNREADABLE_CREDENTIAL_HINT", { enumerable: true, get: function () { return credentialEnvelopeBridge_js_1.DEFAULT_UNREADABLE_CREDENTIAL_HINT; } });
237
238
  // Test seams — `__`-prefixed so consumers don't import them in
238
239
  // production code paths. Re-exported here so CLI/daemon spec files
239
240
  // can resolve the wrapped-DEK path against the same module instance.
@@ -141,7 +141,19 @@ export interface ReadTransformResult {
141
141
  * path is active — caller should re-write to upgrade. */
142
142
  needsMigration: boolean;
143
143
  }
144
- export declare function maybeDecryptOnRead(stored: string): ReadTransformResult;
144
+ /** What the caller knows about the stored value that the envelope does not. */
145
+ export interface DecryptOnReadOptions {
146
+ /**
147
+ * Replaces the default `decrypt_failed` hint. The default says a re-login
148
+ * rebuilds the credential, which is true only of THIS config's own value. A
149
+ * caller reading a value another config sealed under its own key knows the
150
+ * failure is expected and a re-login here changes nothing (OSK-12135).
151
+ */
152
+ unreadableHint?: string;
153
+ }
154
+ /** The `decrypt_failed` hint when the caller has no better explanation. */
155
+ export declare const DEFAULT_UNREADABLE_CREDENTIAL_HINT = "stored credential is unreadable on this machine \u2014 signing in again rebuilds it";
156
+ export declare function maybeDecryptOnRead(stored: string, options?: DecryptOnReadOptions): ReadTransformResult;
145
157
  /** @internal Test seam — wrapped-DEK path resolver. */
146
158
  export declare function __wrappedDekFilePathForTest(profile: string): string;
147
159
  export {};
@@ -146,7 +146,10 @@ function emit(eventName, success, metadata) {
146
146
  metadata: { event: eventName, ...metadata },
147
147
  });
148
148
  if (!success) {
149
- const signature = `${eventName}:${metadata.kekId ?? ''}:${metadata.kekKind ?? ''}:${metadata.message ?? ''}`;
149
+ // The hint is part of the signature: the same crypto failure on this
150
+ // config's own credential and on another config's is two different facts,
151
+ // and the second must not swallow the first (OSK-12135).
152
+ const signature = `${eventName}:${metadata.kekId ?? ''}:${metadata.kekKind ?? ''}:${metadata.message ?? ''}:${metadata.hint ?? ''}`;
150
153
  if (!warnedSignatures.has(signature)) {
151
154
  if (warnedSignatures.size >= MAX_WARNED_SIGNATURES)
152
155
  warnedSignatures.clear();
@@ -736,7 +739,9 @@ export function wrapIfActiveOrPassthrough(plaintext) {
736
739
  return plaintext;
737
740
  }
738
741
  }
739
- export function maybeDecryptOnRead(stored) {
742
+ /** The `decrypt_failed` hint when the caller has no better explanation. */
743
+ export const DEFAULT_UNREADABLE_CREDENTIAL_HINT = 'stored credential is unreadable on this machine — signing in again rebuilds it';
744
+ export function maybeDecryptOnRead(stored, options = {}) {
740
745
  if (typeof stored !== 'string' || stored.length === 0) {
741
746
  return { plaintext: stored, needsMigration: false };
742
747
  }
@@ -768,7 +773,7 @@ export function maybeDecryptOnRead(stored) {
768
773
  // The crypto error alone reads as a crash; the actionable truth is
769
774
  // that this stored credential is unreadable by THIS binary (usually
770
775
  // a signing-identity or profile drift) and re-login rebuilds it.
771
- hint: 'stored credential is unreadable on this machine — signing in again rebuilds it',
776
+ hint: options.unreadableHint ?? DEFAULT_UNREADABLE_CREDENTIAL_HINT,
772
777
  });
773
778
  return { plaintext: null, needsMigration: false };
774
779
  }
@@ -27,7 +27,7 @@ export { formatRecoveryCode, parseRecoveryCode, normalizeRecoveryCode, isValidRe
27
27
  export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as credEnvelopeDeserialize, encrypt as credEnvelopeEncrypt, ENVELOPE_MAGIC as CRED_ENVELOPE_MAGIC, generateDek, isEnvelopeString as isCredEnvelopeString, KEY_LEN as CRED_KEY_LEN, openBuffer as credOpenBuffer, sealBuffer as credSealBuffer, serialize as credEnvelopeSerialize, unwrap as credEnvelopeUnwrap, wrap as credEnvelopeWrap, type SealedBlob as CredSealedBlob, } from './credentialEnvelope.js';
28
28
  export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, LinuxKek, MacosKek, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type KekStrategy, } from './kek/index.js';
29
29
  export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyPair, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
30
- export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
30
+ export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
31
31
  export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
32
32
  export { getMachineUuid, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
33
33
  export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
@@ -108,7 +108,7 @@ export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, n
108
108
  // Daemon and CLI consume these; the underlying primitives live above.
109
109
  export {
110
110
  // credEnvelope bridge
111
- initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead,
111
+ initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT,
112
112
  // Test seams — `__`-prefixed so consumers don't import them in
113
113
  // production code paths. Re-exported here so CLI/daemon spec files
114
114
  // can resolve the wrapped-DEK path against the same module instance.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/auth-core",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "main": "dist/cjs/index.js",
5
5
  "types": "dist/esm/index.d.ts",
6
6
  "exports": {