@skrr-ai/cli 0.1.50 → 0.1.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +95 -8
  3. package/dist/commands/agents/chat.d.ts +14 -0
  4. package/dist/commands/agents/chat.js +37 -1
  5. package/dist/commands/agents/create.js +8 -4
  6. package/dist/commands/agents/show.d.ts +9 -0
  7. package/dist/commands/agents/show.js +53 -0
  8. package/dist/commands/agents/update.d.ts +32 -0
  9. package/dist/commands/agents/update.js +81 -9
  10. package/dist/commands/balance/show.d.ts +34 -0
  11. package/dist/commands/balance/show.js +92 -2
  12. package/dist/commands/commitments/doctor.d.ts +1 -0
  13. package/dist/commands/commitments/doctor.js +19 -4
  14. package/dist/commands/commitments/effective-policy.js +15 -1
  15. package/dist/commands/commitments/next-tick.js +12 -2
  16. package/dist/commands/commitments/preflight.js +10 -0
  17. package/dist/commands/commitments/show.js +11 -1
  18. package/dist/commands/context.js +2 -1
  19. package/dist/commands/daemon/config.d.ts +15 -0
  20. package/dist/commands/daemon/config.js +53 -0
  21. package/dist/commands/daemon/index.js +1 -0
  22. package/dist/commands/daemon/status.js +15 -1
  23. package/dist/commands/doctor.js +8 -1
  24. package/dist/commands/harnesses/models.d.ts +54 -0
  25. package/dist/commands/harnesses/models.js +100 -7
  26. package/dist/commands/harnesses/show.d.ts +11 -0
  27. package/dist/commands/harnesses/show.js +35 -0
  28. package/dist/commands/harnesses/update.d.ts +28 -1
  29. package/dist/commands/harnesses/update.js +73 -10
  30. package/dist/commands/login.d.ts +2 -1
  31. package/dist/commands/login.js +26 -7
  32. package/dist/commands/logout.js +3 -0
  33. package/dist/commands/machines/dedicated/show.js +25 -2
  34. package/dist/commands/outbox/list.d.ts +12 -1
  35. package/dist/commands/outbox/list.js +31 -2
  36. package/dist/commands/revoke-daemon.js +9 -0
  37. package/dist/commands/spaces/create.d.ts +1 -0
  38. package/dist/commands/spaces/create.js +17 -0
  39. package/dist/commands/spaces/show.js +5 -0
  40. package/dist/commands/spaces/update.d.ts +1 -0
  41. package/dist/commands/spaces/update.js +16 -0
  42. package/dist/commands/tasks/create.d.ts +6 -0
  43. package/dist/commands/tasks/create.js +31 -6
  44. package/dist/commands/tasks/show.js +45 -0
  45. package/dist/commands/tasks/start.d.ts +14 -0
  46. package/dist/commands/tasks/start.js +31 -1
  47. package/dist/commands/tasks/update.d.ts +1 -0
  48. package/dist/commands/tasks/update.js +29 -1
  49. package/dist/commands/tasks/upsert.d.ts +1 -0
  50. package/dist/commands/tasks/upsert.js +16 -0
  51. package/dist/commands/whoami.js +5 -3
  52. package/dist/lib/agent-config.d.ts +2 -0
  53. package/dist/lib/agent-config.js +14 -10
  54. package/dist/lib/agentic-stream.d.ts +88 -1
  55. package/dist/lib/agentic-stream.js +330 -9
  56. package/dist/lib/auth-core-init.d.ts +2 -10
  57. package/dist/lib/auth-core-init.js +20 -67
  58. package/dist/lib/auth-failure.d.ts +33 -0
  59. package/dist/lib/auth-failure.js +68 -0
  60. package/dist/lib/auth-storage.d.ts +24 -0
  61. package/dist/lib/auth-storage.js +342 -11
  62. package/dist/lib/cli-id.d.ts +22 -0
  63. package/dist/lib/cli-id.js +56 -0
  64. package/dist/lib/cli-identity.d.ts +49 -0
  65. package/dist/lib/cli-identity.js +159 -0
  66. package/dist/lib/commitments.d.ts +25 -1
  67. package/dist/lib/commitments.js +46 -1
  68. package/dist/lib/config.d.ts +17 -0
  69. package/dist/lib/config.js +15 -3
  70. package/dist/lib/daemonBroker.d.ts +37 -1
  71. package/dist/lib/daemonBroker.js +146 -22
  72. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  73. package/dist/lib/daemonBrokerRefusal.js +127 -0
  74. package/dist/lib/dedicated-machines.d.ts +33 -2
  75. package/dist/lib/dedicated-machines.js +50 -1
  76. package/dist/lib/delegated-cli.js +20 -1
  77. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  78. package/dist/lib/exec-runtime-binary.js +4 -2
  79. package/dist/lib/first-party-harness-broker.js +10 -5
  80. package/dist/lib/harnesses.d.ts +10 -0
  81. package/dist/lib/keychain.d.ts +39 -0
  82. package/dist/lib/keychain.js +228 -39
  83. package/dist/lib/label-ref.d.ts +34 -0
  84. package/dist/lib/label-ref.js +68 -1
  85. package/dist/lib/live-runtime-binary.d.ts +40 -0
  86. package/dist/lib/live-runtime-binary.js +193 -0
  87. package/dist/lib/login.d.ts +23 -0
  88. package/dist/lib/login.js +67 -21
  89. package/dist/lib/message-intent.js +3 -1
  90. package/dist/lib/oauthLogin.js +2 -8
  91. package/dist/lib/refresh.js +6 -2
  92. package/dist/lib/tasks.d.ts +20 -0
  93. package/dist/lib/tasks.js +87 -8
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  113. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
  114. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
  115. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  116. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  117. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  118. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  119. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  120. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  121. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  122. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  123. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  124. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  125. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
  126. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  127. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  128. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  129. package/dist/node_modules/@skrr-ai/data-provider/index.js +3139 -3097
  130. package/oclif.manifest.json +28591 -28438
  131. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.profileScopedCliId = profileScopedCliId;
40
+ exports.hasStoredCredential = hasStoredCredential;
41
+ exports.planLoginCliId = planLoginCliId;
42
+ exports.cliIdForLogin = cliIdForLogin;
43
+ exports.persistPresentedCliId = persistPresentedCliId;
44
+ /**
45
+ * cli-identity.ts — which cliId a LOGIN presents, and when a profile gets its own.
46
+ *
47
+ * Reading the identity is `resolveCliId()` in `config.ts` (it has to live there:
48
+ * `auth-storage.ts` judges credential ownership by it, and this module reads
49
+ * auth-storage). This module owns the one decision a read must never make —
50
+ * issuing an identity — and the one write that restores a backed-up pair.
51
+ *
52
+ * Why a profile needs its own (OSK-12156): the server's `cli-handoff/confirm`
53
+ * retires every OTHER live cli family of (user, cliId) once a hand-off lands.
54
+ * That is right for one config replacing its own previous session, and wrong
55
+ * when every profile on the machine presents the same machine-scoped `cliId`:
56
+ * profile B's daemon-brokered login then revoked profile A's session, silently.
57
+ * The server scoping is already per cliId, so the fix is the id, not the route.
58
+ *
59
+ * Why only a profile with NO stored credential: an existing session was minted
60
+ * under the machine id, and the refresh route refuses a rotation that presents
61
+ * another. Re-identifying such a profile would log it out on its next refresh —
62
+ * and a re-login under a new id would leave its old family live (the confirm
63
+ * only sweeps the id it was minted for) in a bucket the server evicts
64
+ * oldest-first. So a profile keeps presenting the id its credential is bound
65
+ * to, and only a profile starting from nothing is issued one. That is also why
66
+ * an upgrade forces no re-login: nothing that already works changes id.
67
+ *
68
+ * The default profile keeps the machine id. It IS the machine file, and it is
69
+ * the identity every pre-profile install already presents.
70
+ */
71
+ const node_crypto_1 = require("node:crypto");
72
+ const node_os_1 = __importDefault(require("node:os"));
73
+ const path = __importStar(require("node:path"));
74
+ const auth_storage_1 = require("./auth-storage");
75
+ const config_1 = require("./config");
76
+ const cli_id_1 = require("./cli-id");
77
+ const device_id_1 = require("./device-id");
78
+ const keychain_1 = require("./keychain");
79
+ /**
80
+ * A named profile's own identity. Deterministic in (host, user, config root,
81
+ * profile), so a profile that loses its config file and signs in again is
82
+ * issued the same id rather than scattering families, and two profiles — or the
83
+ * same profile name under two config roots — never share one. Same `cli_<12hex>`
84
+ * shape as the machine id: the server requires the `cli_` namespace.
85
+ */
86
+ function profileScopedCliId(profile = (0, keychain_1.getActiveProfile)(), root = (0, config_1.configRoot)()) {
87
+ const hostname = node_os_1.default.hostname();
88
+ const username = node_os_1.default.userInfo().username || 'unknown';
89
+ const digest = (0, node_crypto_1.createHash)('sha256')
90
+ .update(`${hostname}|${username}|cli|profile:${profile}|root:${path.resolve(root)}`)
91
+ .digest('hex');
92
+ return `cli_${digest.slice(0, 12)}`;
93
+ }
94
+ /**
95
+ * Does this profile hold a credential it would present? Its own backend slot
96
+ * (ownership-checked, any origin) or a pre-migration token still in the config.
97
+ *
98
+ * A read that FAILS answers true: "keep the id you already present" is the
99
+ * outcome that cannot log anyone out, so doubt falls on that side.
100
+ */
101
+ function hasStoredCredential(cfg) {
102
+ if (cfg.token || cfg.refreshToken)
103
+ return true;
104
+ try {
105
+ const read = (0, auth_storage_1.readFromBackend)();
106
+ return Boolean(read.bundle?.token || read.bundle?.refreshToken);
107
+ }
108
+ catch {
109
+ return true;
110
+ }
111
+ }
112
+ /**
113
+ * Decide the id a login is about to present. Pure apart from the credential
114
+ * read; the caller persists `updatedConfig` (see `cliIdForLogin`).
115
+ */
116
+ function planLoginCliId(cfg, deps = {}) {
117
+ const env = (0, cli_id_1.envCliIdOverride)();
118
+ if (env)
119
+ return { cliId: env, source: 'env' };
120
+ const issued = cfg.profileCliId?.trim();
121
+ if (issued)
122
+ return { cliId: issued, source: 'profile' };
123
+ if ((0, cli_id_1.isNamedProfile)() && !(deps.hasStoredCredential ?? hasStoredCredential)(cfg)) {
124
+ const cliId = profileScopedCliId();
125
+ return { cliId, source: 'issued', updatedConfig: { ...cfg, profileCliId: cliId } };
126
+ }
127
+ const machine = cfg.cliId?.trim();
128
+ if (machine)
129
+ return { cliId: machine, source: 'machine' };
130
+ const cliId = (0, device_id_1.generateCliId)();
131
+ return { cliId, source: 'machine', updatedConfig: { ...cfg, cliId } };
132
+ }
133
+ /**
134
+ * The id an interactive login presents, persisted BEFORE the exchange so a
135
+ * retry — or a sibling process — presents the same one.
136
+ */
137
+ function cliIdForLogin(cfg = (0, config_1.loadConfig)()) {
138
+ const plan = planLoginCliId(cfg);
139
+ if (plan.updatedConfig)
140
+ (0, config_1.saveConfig)(plan.updatedConfig);
141
+ return plan.cliId;
142
+ }
143
+ /**
144
+ * Record that this profile now presents `cliId` — a restored (refresh token,
145
+ * cliId) pair. A named profile records it as its own identity; writing the
146
+ * machine field from a named profile would re-identify the default profile and
147
+ * every profile still on the machine id, whose next refresh would be refused.
148
+ */
149
+ function persistPresentedCliId(cfg, cliId) {
150
+ if ((0, cli_id_1.isNamedProfile)()) {
151
+ const current = cfg.profileCliId?.trim() || cfg.cliId?.trim();
152
+ if (current === cliId)
153
+ return;
154
+ (0, config_1.saveConfig)({ ...cfg, profileCliId: cliId });
155
+ return;
156
+ }
157
+ if (cfg.cliId !== cliId)
158
+ (0, config_1.saveConfig)({ ...cfg, cliId });
159
+ }
@@ -17,7 +17,7 @@
17
17
  * "hold this state", derived from a Compass invariant — not authored here).
18
18
  */
19
19
  import { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_AUTONOMY_MODES, COMMITMENT_EXECUTION_ISOLATION_MODES, COMMITMENT_STATUSES, COMMITMENT_KIND_VALUES } from '@skrr-ai/data-provider';
20
- import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse, CommitmentCyclesResponse, CommitmentExplanationResponse } from '@skrr-ai/data-provider';
20
+ import type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, CommitmentExecutionReceiptListResponse, CommitmentExecutionReceiptQuarterlyMetricsResponse, CommitmentCapabilitySource, CommitmentDeliveryPatchResponse, CommitmentDeliveryBundleResponse, CommitmentCyclesResponse, CommitmentExplanationResponse, CommitmentRunPermissions } from '@skrr-ai/data-provider';
21
21
  import { withQuery } from './triggers';
22
22
  export { COMMITMENT_WATCHED_SOURCES, COMMITMENT_TEMPLATE_IDS, COMMITMENT_CADENCE_MODES, COMMITMENT_CADENCE_TIME_PATTERN, COMMITMENT_CADENCE_MAX_TIMES, COMMITMENT_STATUSES, COMMITMENT_KIND_VALUES, };
23
23
  export type { CommitmentCheck, CommitmentChecksResponse, Commitment, CommitmentActionProposal, CommitmentListResponse, CommitmentPlanResponse, CommitmentStatusResponse, CommitmentWorkLink, };
@@ -99,7 +99,27 @@ export type CommitmentPreflightReport = {
99
99
  suppressedBy?: string | null;
100
100
  [key: string]: unknown;
101
101
  };
102
+ /** Whose permissions a run uses (OSK-11975). Absent from older servers. */
103
+ permissions?: CommitmentRunPermissions | null;
104
+ /** `retired` names the fence fields OSK-11975 made inert (OSK-12065). */
105
+ execution?: {
106
+ retired?: string[];
107
+ retiredReason?: string;
108
+ } & Record<string, unknown>;
102
109
  };
110
+ /**
111
+ * The lines every Commitment read surface prints for "whose permissions does a
112
+ * run use?" — `show`, `effective-policy` and `preflight` share it so the three
113
+ * cannot phrase it three ways (OSK-12065). The sentence and the warnings are
114
+ * the server's; this only lays them out. `null` (an older server that does not
115
+ * report it) prints nothing rather than a guess.
116
+ */
117
+ export declare function permissionLines(permissions: CommitmentRunPermissions | null | undefined): string[];
118
+ /**
119
+ * One line naming fields that are still in the JSON but decide nothing any
120
+ * more. Deprecation, not removal: `--json` keeps them for scripts.
121
+ */
122
+ export declare function retiredFieldsLine(retired: string[] | undefined, reason: string | undefined): string | null;
103
123
  /**
104
124
  * A Commitment Pack's readiness receipt.
105
125
  *
@@ -466,6 +486,10 @@ export interface CommitmentNextTickProjection {
466
486
  diagnosisKind?: string;
467
487
  terminalDispositionCensus?: {
468
488
  workFailures?: number;
489
+ /** Failed runs the runtime never started (`classifyRunFailure` machine). */
490
+ machineFailures?: number;
491
+ /** Failed runs whose result was rejected (`classifyRunFailure` contract). */
492
+ contractFailures?: number;
469
493
  budgetRefusals?: number;
470
494
  terminal?: number;
471
495
  };
@@ -4,6 +4,8 @@ exports.COMMITMENT_CLI_EVALUATORS = exports.COMMITMENT_CREATE_KEYS = exports.COM
4
4
  exports.storedAutonomyModeForAuthoring = storedAutonomyModeForAuthoring;
5
5
  exports.authoringAutonomyModeForStored = authoringAutonomyModeForStored;
6
6
  exports.commitmentAutonomyLabel = commitmentAutonomyLabel;
7
+ exports.permissionLines = permissionLines;
8
+ exports.retiredFieldsLine = retiredFieldsLine;
7
9
  exports.readCommitmentAgentDirective = readCommitmentAgentDirective;
8
10
  exports.normalizeCreateBody = normalizeCreateBody;
9
11
  exports.parseCommitmentConcurrencyLimit = parseCommitmentConcurrencyLimit;
@@ -109,6 +111,33 @@ function commitmentAutonomyLabel(mode) {
109
111
  */
110
112
  exports.MIN_COMMITMENT_INTERVAL_MS = 60_000;
111
113
  exports.MAX_COMMITMENT_INTERVAL_MS = 365 * 24 * 60 * 60 * 1000;
114
+ /**
115
+ * The lines every Commitment read surface prints for "whose permissions does a
116
+ * run use?" — `show`, `effective-policy` and `preflight` share it so the three
117
+ * cannot phrase it three ways (OSK-12065). The sentence and the warnings are
118
+ * the server's; this only lays them out. `null` (an older server that does not
119
+ * report it) prints nothing rather than a guess.
120
+ */
121
+ function permissionLines(permissions) {
122
+ if (!permissions?.summary)
123
+ return [];
124
+ const lines = [`Permissions: ${permissions.summary}`];
125
+ for (const warning of permissions.warnings || []) {
126
+ lines.push(`Warning: ${warning.message}`);
127
+ if (warning.fix)
128
+ lines.push(` Fix: ${warning.fix}`);
129
+ }
130
+ return lines;
131
+ }
132
+ /**
133
+ * One line naming fields that are still in the JSON but decide nothing any
134
+ * more. Deprecation, not removal: `--json` keeps them for scripts.
135
+ */
136
+ function retiredFieldsLine(retired, reason) {
137
+ if (!Array.isArray(retired) || !retired.length)
138
+ return null;
139
+ return `Retired fields (no effect): ${retired.join(', ')}${reason ? ` — ${reason}` : ''}`;
140
+ }
112
141
  function base(id) {
113
142
  return `/api/commitments/${encodeURIComponent(id)}`;
114
143
  }
@@ -626,6 +655,8 @@ function describeEvidenceHealth(health, criteriaCount) {
626
655
  const STALLED_DIAGNOSES = {
627
656
  scheduler_stalled: 'its expected checks are no longer arriving',
628
657
  work_failing: 'every recent run reached the agent and failed while doing the work',
658
+ runtime_failing: 'recent runs never started — the runtime refused them',
659
+ result_rejected: 'every recent run reached the agent, but its result was rejected',
629
660
  observation_unreadable: 'the latest check could not read every configured source',
630
661
  execution_budget_exhausted: 'recent planned work was skipped — the execution budget is exhausted',
631
662
  no_advancement: 'no server-recorded advancement exists inside the declared window',
@@ -645,8 +676,17 @@ function describeStalledHealth(commitment) {
645
676
  const kind = typeof liveness.diagnosisKind === 'string' ? liveness.diagnosisKind : undefined;
646
677
  const clause = (kind && STALLED_DIAGNOSES[kind]) ||
647
678
  'this loop has stopped producing; the reconciler did not record which check failed';
679
+ // What refused and what to change, when the reconciler recorded them
680
+ // (OSK-12137). A machine failure has a specific fix; printing only the
681
+ // verdict would send the owner to read run history for it.
682
+ const code = typeof liveness.diagnosisCode === 'string' && liveness.diagnosisCode
683
+ ? ` (${liveness.diagnosisCode})`
684
+ : '';
648
685
  const since = typeof liveness.unhealthySinceAt === 'string' ? ` (since ${liveness.unhealthySinceAt})` : '';
649
- return `${clause}${since}`;
686
+ const fix = typeof liveness.diagnosisFix === 'string' && liveness.diagnosisFix
687
+ ? ` — ${liveness.diagnosisFix}`
688
+ : '';
689
+ return `${clause}${code}${since}${fix}`;
650
690
  }
651
691
  /** Render an interval back as a compact human string. */
652
692
  function describeIntervalMs(ms) {
@@ -1013,6 +1053,11 @@ function describeWakeDecision(check) {
1013
1053
  if (verdict === 'skipped') {
1014
1054
  const reason = typeof entry.reason === 'string' ? entry.reason : '';
1015
1055
  if (reason === 'posture_does_not_admit_agent_run') {
1056
+ // The server names the commonest cause on the check's `stop`: nothing
1057
+ // was open to act on. "record_only" alone read as a setting to change
1058
+ // when the remedy was to give the Commitment some work (OSK-12090).
1059
+ if (check.stop?.reason === 'no_open_loop')
1060
+ return 'no — nothing open';
1016
1061
  const posture = typeof entry.posture === 'string' ? entry.posture : '';
1017
1062
  return posture ? `no — ${posture}` : 'no — posture';
1018
1063
  }
@@ -29,6 +29,23 @@ export interface CliConfig {
29
29
  * different daemonId.
30
30
  */
31
31
  cliId?: string;
32
+ /**
33
+ * The CLI identity of ONE named profile (OSK-12156), stored in that profile's
34
+ * own file and never in the machine file.
35
+ *
36
+ * `cliId` above is machine-scoped, so every profile used to present it — and
37
+ * the server's `cli-handoff/confirm` retires every OTHER live family of
38
+ * (user, cliId), so one profile's daemon-brokered login silently ended
39
+ * another's session. This is issued ONLY to a named profile that holds no
40
+ * stored credential at the moment it signs in (`cli-identity.ts`), because
41
+ * an existing session was minted under the machine id and a refresh that
42
+ * presents another is refused: a profile that already has a session keeps
43
+ * presenting the id it was minted under, so an upgrade forces no re-login.
44
+ *
45
+ * Never read this field, or `cliId`, to decide what to PRESENT — call
46
+ * `resolveCliId()` (`cli-id.ts`), the one resolver every caller goes through.
47
+ */
48
+ profileCliId?: string;
32
49
  /**
33
50
  * OSK-3892 — the agent a locally-hosted `skrr code` session is attributed to.
34
51
  *
@@ -53,6 +53,7 @@ const os = __importStar(require("node:os"));
53
53
  const path = __importStar(require("node:path"));
54
54
  const keychain_1 = require("./keychain");
55
55
  const auth_core_1 = require("@skrr-ai/auth-core");
56
+ const cli_id_1 = require("./cli-id");
56
57
  const publicEndpoints_generated_1 = require("./publicEndpoints.generated");
57
58
  // Both from config/public-endpoints.json, the one domain contract, rather than a
58
59
  // fourth hand-copied pair. The CLI previously knew only about dev — there was no
@@ -174,7 +175,8 @@ const ENV_VAR = {
174
175
  serverUrl: 'OVERSKY_SERVER_URL',
175
176
  token: 'OVERSKY_TOKEN',
176
177
  refreshToken: 'OVERSKY_REFRESH_TOKEN',
177
- cliId: 'OVERSKY_CLI_ID',
178
+ // OVERSKY_CLI_ID / SKRR_CLI_ID: `envCliIdOverride` (cli-id.ts), which
179
+ // `resolveCliId` shares, so the overlay and the resolver cannot disagree.
178
180
  };
179
181
  /**
180
182
  * Read one overlay variable under either prefix.
@@ -215,8 +217,8 @@ function overlayEnv(cfg) {
215
217
  const rt = envOverlayValue('refreshToken');
216
218
  if (rt && rt.length > 0)
217
219
  out.refreshToken = rt;
218
- const cid = envOverlayValue('cliId');
219
- if (cid && cid.length > 0)
220
+ const cid = (0, cli_id_1.envCliIdOverride)();
221
+ if (cid)
220
222
  out.cliId = cid;
221
223
  return out;
222
224
  }
@@ -275,6 +277,16 @@ function newConfigDir() {
275
277
  * and rejects a rotation against a different one. Split it per profile and a
276
278
  * second profile does not degrade — it fails to stay logged in.
277
279
  *
280
+ * So `cliId` stays here, and a profile's OWN identity is a different key,
281
+ * `profileCliId`, which is profile-scoped (OSK-12156). Sharing the machine id
282
+ * meant the server's cli-handoff confirm — which retires the other families of
283
+ * (user, cliId) — could not tell two profiles of one user apart, so one
284
+ * profile's daemon-brokered login ended another's session. A named profile that
285
+ * signs in holding no credential is now issued its own id; a profile that
286
+ * already has a session keeps the machine id it was minted under, because a
287
+ * refresh presenting another is refused. `resolveCliId()` (`cli-id.ts`) is the
288
+ * one reader.
289
+ *
278
290
  * `credentials.store` is a machine posture (keychain / file / env-only), and
279
291
  * `migrated` is a breadcrumb about this machine's DISK LAYOUT. Neither is a
280
292
  * property of which deployment you are addressing.
@@ -122,7 +122,7 @@ export interface DaemonBrokeredOutcome {
122
122
  * fall through to PKCE; `ci_token_refused` is the one we surface
123
123
  * because re-driving it via PKCE would not change the outcome.
124
124
  */
125
- export type DaemonBrokerErrorReason = 'no_bootstrap' | 'stale_bootstrap' | 'bootstrap_parse' | 'base_url_mismatch' | 'daemon_not_authed' | 'ci_token_refused' | 'handoff_broker_only' | 'rate_limited' | 'server_error' | 'network' | 'timeout' | 'unknown';
125
+ export type DaemonBrokerErrorReason = 'no_bootstrap' | 'stale_bootstrap' | 'bootstrap_parse' | 'base_url_mismatch' | 'daemon_not_authed' | 'ci_token_refused' | 'handoff_broker_only' | 'rate_limited' | 'upstream_refused' | 'server_error' | 'network' | 'timeout' | 'unknown';
126
126
  export interface DaemonBrokerFailure {
127
127
  ok: false;
128
128
  reason: DaemonBrokerErrorReason;
@@ -132,12 +132,40 @@ export interface DaemonBrokerFailure {
132
132
  status?: number;
133
133
  /** Server-supplied error code when present (e.g. 'CI_TOKEN_REFUSED'). */
134
134
  code?: string;
135
+ /**
136
+ * Who refused, when the daemon answered at all: `daemon` for a refusal the
137
+ * local service made itself (not signed in, broker-only, its own outage),
138
+ * `server` for the skrr server's refusal of the daemon's request, carried
139
+ * through the loopback route (`upstreamCode` on the token route, the
140
+ * verbatim body on the legacy one). The two have different repairs, and a
141
+ * caller that cannot tell them apart can only say "Not signed in"
142
+ * (OSK-12149).
143
+ */
144
+ refusedBy?: 'daemon' | 'server';
135
145
  /**
136
146
  * For `base_url_mismatch`: the daemon's serverUrl, so the caller can
137
147
  * surface a helpful hint like "run `skrr login --base-url <X>` to use
138
148
  * the daemon".
139
149
  */
140
150
  daemonServerUrl?: string;
151
+ /**
152
+ * The descriptor file the outcome came from. For `base_url_mismatch` found
153
+ * locally it is the file that named `daemonServerUrl`, so a message can say
154
+ * what was read rather than assert where "the" daemon is logged in
155
+ * (OSK-12117). For a refusal it is the descriptor whose daemon answered —
156
+ * which is how a caller knows the answer came from a Dedicated Runtime
157
+ * guest's daemon (`DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR`), where nobody
158
+ * signs in and the repair is the machine, not a login.
159
+ */
160
+ descriptorPath?: string;
161
+ /** The answering daemon's id, from its descriptor, when it published one. */
162
+ daemonId?: string;
163
+ /**
164
+ * True when the answering descriptor is a Dedicated Runtime guest's
165
+ * (`DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR`): nobody signs in there, so
166
+ * the repair for a refusal is the machine, never a login.
167
+ */
168
+ dedicatedGuest?: boolean;
141
169
  }
142
170
  export type DaemonBrokerOutcome = (DaemonBrokerResult & {
143
171
  ok: true;
@@ -224,6 +252,14 @@ export declare function resolveBootstrapCandidatePaths(profile?: string): string
224
252
  export declare const DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = "/run/skrr-dedicated-runtime/cli-handoff/descriptor.json";
225
253
  /** Where the cli-handoff broker looks, in order. */
226
254
  export declare function resolveCliHandoffCandidatePaths(profile?: string): string[];
255
+ /**
256
+ * The island files that belong to exactly `profile` — its canonical path and
257
+ * the pre-rename darwin location — and never another profile's. The broker's
258
+ * candidate list for `default` deliberately falls through to any running
259
+ * profile, which is right for borrowing a credential and wrong for naming
260
+ * which daemon a profile's status describes (OSK-12102).
261
+ */
262
+ export declare function resolveExactProfileBootstrapPaths(profile?: string): string[];
227
263
  /**
228
264
  * Resolve the first live daemon bootstrap for a profile. Message-intent
229
265
  * submission reuses the same authenticated loopback discovery as the login