@skrr-ai/cli 0.1.50 → 0.1.51

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 (100) hide show
  1. package/dist/base-command.js +59 -2
  2. package/dist/commands/agents/chat.d.ts +14 -0
  3. package/dist/commands/agents/chat.js +37 -1
  4. package/dist/commands/agents/show.d.ts +9 -0
  5. package/dist/commands/agents/show.js +53 -0
  6. package/dist/commands/agents/update.d.ts +9 -0
  7. package/dist/commands/agents/update.js +27 -0
  8. package/dist/commands/balance/show.d.ts +34 -0
  9. package/dist/commands/balance/show.js +92 -2
  10. package/dist/commands/commitments/doctor.d.ts +1 -0
  11. package/dist/commands/commitments/doctor.js +19 -4
  12. package/dist/commands/commitments/effective-policy.js +15 -1
  13. package/dist/commands/commitments/next-tick.js +3 -1
  14. package/dist/commands/commitments/preflight.js +10 -0
  15. package/dist/commands/commitments/show.js +11 -1
  16. package/dist/commands/daemon/config.d.ts +15 -0
  17. package/dist/commands/daemon/config.js +38 -0
  18. package/dist/commands/daemon/index.js +1 -0
  19. package/dist/commands/daemon/status.js +15 -1
  20. package/dist/commands/harnesses/models.d.ts +54 -0
  21. package/dist/commands/harnesses/models.js +100 -7
  22. package/dist/commands/harnesses/show.d.ts +11 -0
  23. package/dist/commands/harnesses/show.js +35 -0
  24. package/dist/commands/harnesses/update.d.ts +28 -1
  25. package/dist/commands/harnesses/update.js +73 -10
  26. package/dist/commands/outbox/list.d.ts +12 -1
  27. package/dist/commands/outbox/list.js +31 -2
  28. package/dist/commands/revoke-daemon.js +9 -0
  29. package/dist/commands/spaces/create.d.ts +1 -0
  30. package/dist/commands/spaces/create.js +17 -0
  31. package/dist/commands/spaces/show.js +5 -0
  32. package/dist/commands/spaces/update.d.ts +1 -0
  33. package/dist/commands/spaces/update.js +16 -0
  34. package/dist/commands/tasks/create.d.ts +1 -0
  35. package/dist/commands/tasks/create.js +5 -0
  36. package/dist/commands/tasks/show.js +45 -0
  37. package/dist/commands/tasks/update.d.ts +1 -0
  38. package/dist/commands/tasks/update.js +21 -0
  39. package/dist/commands/tasks/upsert.d.ts +1 -0
  40. package/dist/commands/tasks/upsert.js +16 -0
  41. package/dist/lib/agentic-stream.d.ts +68 -1
  42. package/dist/lib/agentic-stream.js +294 -7
  43. package/dist/lib/auth-core-init.d.ts +2 -10
  44. package/dist/lib/auth-core-init.js +20 -67
  45. package/dist/lib/auth-failure.d.ts +33 -0
  46. package/dist/lib/auth-failure.js +65 -0
  47. package/dist/lib/commitments.d.ts +21 -1
  48. package/dist/lib/commitments.js +34 -0
  49. package/dist/lib/daemonBroker.d.ts +14 -0
  50. package/dist/lib/daemonBroker.js +64 -12
  51. package/dist/lib/delegated-cli.js +4 -1
  52. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  53. package/dist/lib/exec-runtime-binary.js +4 -2
  54. package/dist/lib/first-party-harness-broker.js +4 -1
  55. package/dist/lib/harnesses.d.ts +10 -0
  56. package/dist/lib/live-runtime-binary.d.ts +40 -0
  57. package/dist/lib/live-runtime-binary.js +193 -0
  58. package/dist/lib/login.d.ts +23 -0
  59. package/dist/lib/login.js +44 -10
  60. package/dist/lib/message-intent.js +3 -1
  61. package/dist/lib/tasks.d.ts +20 -0
  62. package/dist/lib/tasks.js +54 -0
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
  97. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  98. package/dist/node_modules/@skrr-ai/data-provider/index.js +1928 -1924
  99. package/oclif.manifest.json +23832 -23688
  100. package/package.json +1 -1
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * auth-failure.ts — say WHY a request was refused on authentication (OSK-12116).
4
+ *
5
+ * A 401 reaches the CLI after its own renewal has already failed: the access
6
+ * token expired, the refresh was refused, and the retry went out with the dead
7
+ * token. The server's answer to that retry is `TOKEN_EXPIRED`, which is true and
8
+ * useless — the expiry is routine, the refusal to RENEW is the fault, and it was
9
+ * visible only as an `[auth-core warn]` line on stderr.
10
+ *
11
+ * auth-core latches every permanent refresh refusal (`needs-reauth.json`, the
12
+ * same record `skrr whoami` prints as "Refresh state"). This module reads that
13
+ * latch and turns it into the sentence the error should have carried.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.describeRefreshRefusal = describeRefreshRefusal;
17
+ /**
18
+ * Explain one refresh refusal in terms of what the reader can do about it.
19
+ * Returns null when there is no latched refusal, so the caller keeps its
20
+ * generic wording rather than inventing a cause.
21
+ */
22
+ function describeRefreshRefusal(refusal, opts) {
23
+ if (!refusal?.reason)
24
+ return null;
25
+ const { bin } = opts;
26
+ const code = refusal.reason;
27
+ const said = refusal.message ? ` (${code}: ${refusal.message})` : ` (${code})`;
28
+ const relogin = `Run \`${bin} login\` to sign in again.`;
29
+ const message = refusal.message ?? '';
30
+ // The row the stored refresh token belongs to was minted for a different CLI
31
+ // identity than the one this config presents. The keychain slot is keyed by
32
+ // profile, while the CLI ID lives in the config directory — so a second skrr
33
+ // configuration on this machine (a different HOME or config dir sharing the
34
+ // same login Keychain) can write this profile's credential under its own ID.
35
+ // Observed: a token minted for `cli_devin_dogfood` sitting in the default
36
+ // profile's slot.
37
+ if (/bound to a different daemon/i.test(message)) {
38
+ return {
39
+ code,
40
+ message: `This profile's stored sign-in could not be renewed${said}. The credential in its ` +
41
+ `Keychain slot was issued to a different CLI identity than this one` +
42
+ `${opts.cliId ? ` (${opts.cliId})` : ''} — usually another skrr configuration on this ` +
43
+ `machine (a different HOME or config directory) signed in and wrote over it. ${relogin}`,
44
+ };
45
+ }
46
+ if (code === 'SESSION_REVOKED') {
47
+ return {
48
+ code,
49
+ message: `This sign-in was revoked${said}. Signing out elsewhere revokes it, and so does ` +
50
+ `revoking a daemon that brokered it (\`${bin} login\` without a browser signs in ` +
51
+ `through this machine's daemon). ${relogin}`,
52
+ };
53
+ }
54
+ if (code === 'REFRESH_REUSED') {
55
+ return {
56
+ code,
57
+ message: `This sign-in was ended because its refresh token was presented twice${said}; the ` +
58
+ `server treats that as possible theft and revokes the whole family. ${relogin}`,
59
+ };
60
+ }
61
+ return {
62
+ code,
63
+ message: `This sign-in could not be renewed${said}. ${relogin}`,
64
+ };
65
+ }
@@ -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
  *
@@ -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
  }
@@ -1013,6 +1042,11 @@ function describeWakeDecision(check) {
1013
1042
  if (verdict === 'skipped') {
1014
1043
  const reason = typeof entry.reason === 'string' ? entry.reason : '';
1015
1044
  if (reason === 'posture_does_not_admit_agent_run') {
1045
+ // The server names the commonest cause on the check's `stop`: nothing
1046
+ // was open to act on. "record_only" alone read as a setting to change
1047
+ // when the remedy was to give the Commitment some work (OSK-12090).
1048
+ if (check.stop?.reason === 'no_open_loop')
1049
+ return 'no — nothing open';
1016
1050
  const posture = typeof entry.posture === 'string' ? entry.posture : '';
1017
1051
  return posture ? `no — ${posture}` : 'no — posture';
1018
1052
  }
@@ -138,6 +138,12 @@ export interface DaemonBrokerFailure {
138
138
  * the daemon".
139
139
  */
140
140
  daemonServerUrl?: string;
141
+ /**
142
+ * For `base_url_mismatch` found locally: the descriptor file that named
143
+ * `daemonServerUrl`, so a message can say what was read rather than assert
144
+ * where "the" daemon is logged in (OSK-12117).
145
+ */
146
+ descriptorPath?: string;
141
147
  }
142
148
  export type DaemonBrokerOutcome = (DaemonBrokerResult & {
143
149
  ok: true;
@@ -224,6 +230,14 @@ export declare function resolveBootstrapCandidatePaths(profile?: string): string
224
230
  export declare const DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR = "/run/skrr-dedicated-runtime/cli-handoff/descriptor.json";
225
231
  /** Where the cli-handoff broker looks, in order. */
226
232
  export declare function resolveCliHandoffCandidatePaths(profile?: string): string[];
233
+ /**
234
+ * The island files that belong to exactly `profile` — its canonical path and
235
+ * the pre-rename darwin location — and never another profile's. The broker's
236
+ * candidate list for `default` deliberately falls through to any running
237
+ * profile, which is right for borrowing a credential and wrong for naming
238
+ * which daemon a profile's status describes (OSK-12102).
239
+ */
240
+ export declare function resolveExactProfileBootstrapPaths(profile?: string): string[];
227
241
  /**
228
242
  * Resolve the first live daemon bootstrap for a profile. Message-intent
229
243
  * submission reuses the same authenticated loopback discovery as the login
@@ -77,6 +77,7 @@ exports.resolveBootstrapPath = resolveBootstrapPath;
77
77
  exports.resolveProfileBootstrapPaths = resolveProfileBootstrapPaths;
78
78
  exports.resolveBootstrapCandidatePaths = resolveBootstrapCandidatePaths;
79
79
  exports.resolveCliHandoffCandidatePaths = resolveCliHandoffCandidatePaths;
80
+ exports.resolveExactProfileBootstrapPaths = resolveExactProfileBootstrapPaths;
80
81
  exports.findLiveDaemonBootstrap = findLiveDaemonBootstrap;
81
82
  exports.readBootstrap = readBootstrap;
82
83
  exports.attemptDaemonBrokerLogin = attemptDaemonBrokerLogin;
@@ -91,6 +92,7 @@ const fs = __importStar(require("node:fs"));
91
92
  const path = __importStar(require("node:path"));
92
93
  const os = __importStar(require("node:os"));
93
94
  const auth_core_1 = require("@skrr-ai/auth-core");
95
+ const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
94
96
  const cli_handoff_wire_1 = require("@skrr-ai/auth-core/cli-handoff-wire");
95
97
  const auth_storage_1 = require("./auth-storage");
96
98
  const config_1 = require("./config");
@@ -139,6 +141,16 @@ function resolveProfileBootstrapPaths(primary, entryNames) {
139
141
  .map((name) => path.join(path.dirname(primary), name));
140
142
  return [primary, ...extras];
141
143
  }
144
+ /**
145
+ * Legacy bootstrap directory (darwin): pre-rename daemons wrote under
146
+ * `~/Library/Application Support/OverSky/` while this reader looked under
147
+ * `skrr/` — a drift that silently killed island bootstrapping on macOS
148
+ * (OSK-12057). New daemons dual-write both; old daemons keep writing only
149
+ * `OverSky/`, so these stay consulted as fallbacks.
150
+ */
151
+ function legacyDarwinBootstrapDir() {
152
+ return path.join(os.homedir(), 'Library', 'Application Support', 'OverSky');
153
+ }
142
154
  /**
143
155
  * List the bootstrap files that a Sky CLI may use for a local daemon
144
156
  * handoff. `default` stays first for backwards compatibility; any other
@@ -150,16 +162,31 @@ function resolveProfileBootstrapPaths(primary, entryNames) {
150
162
  */
151
163
  function resolveBootstrapCandidatePaths(profile = 'default') {
152
164
  const primary = resolveBootstrapPath(profile);
153
- if (profile !== 'default')
154
- return [primary];
165
+ if (profile !== 'default') {
166
+ const legacy = process.platform === 'darwin'
167
+ ? [path.join(legacyDarwinBootstrapDir(), `daemon-island.${profile}.json`)]
168
+ : [];
169
+ return [primary, ...legacy];
170
+ }
155
171
  try {
156
172
  if (process.platform === 'darwin') {
157
- const dir = path.dirname(primary);
158
- const entryNames = fs
159
- .readdirSync(dir, { withFileTypes: true })
160
- .filter((entry) => entry.isFile())
161
- .map((entry) => entry.name);
162
- return resolveProfileBootstrapPaths(primary, entryNames);
173
+ const candidates = [primary];
174
+ for (const dir of [path.dirname(primary), legacyDarwinBootstrapDir()]) {
175
+ let entryNames = [];
176
+ try {
177
+ entryNames = fs
178
+ .readdirSync(dir, { withFileTypes: true })
179
+ .filter((entry) => entry.isFile())
180
+ .map((entry) => entry.name);
181
+ }
182
+ catch {
183
+ continue;
184
+ }
185
+ for (const p of resolveProfileBootstrapPaths(path.join(dir, 'daemon-island.default.json'), entryNames)) {
186
+ candidates.push(p);
187
+ }
188
+ }
189
+ return [...new Set(candidates)];
163
190
  }
164
191
  const profilesDir = path.dirname(path.dirname(primary));
165
192
  const extras = fs
@@ -208,6 +235,20 @@ function resolveCliHandoffCandidatePaths(profile = 'default') {
208
235
  function legacyBootstrapPath() {
209
236
  return path.join(os.homedir(), '.skrr', 'daemon-island.json');
210
237
  }
238
+ /**
239
+ * The island files that belong to exactly `profile` — its canonical path and
240
+ * the pre-rename darwin location — and never another profile's. The broker's
241
+ * candidate list for `default` deliberately falls through to any running
242
+ * profile, which is right for borrowing a credential and wrong for naming
243
+ * which daemon a profile's status describes (OSK-12102).
244
+ */
245
+ function resolveExactProfileBootstrapPaths(profile = 'default') {
246
+ const primary = resolveBootstrapPath(profile);
247
+ if (process.platform === 'darwin') {
248
+ return [primary, path.join(legacyDarwinBootstrapDir(), `daemon-island.${profile}.json`)];
249
+ }
250
+ return profile === 'default' ? [primary, legacyBootstrapPath()] : [primary];
251
+ }
211
252
  /**
212
253
  * Resolve the first live daemon bootstrap for a profile. Message-intent
213
254
  * submission reuses the same authenticated loopback discovery as the login
@@ -301,6 +342,7 @@ async function attemptDaemonBrokerLogin(opts) {
301
342
  const usable = [];
302
343
  const unbound = [];
303
344
  let mismatchedBootstrap = null;
345
+ let mismatchedPath = null;
304
346
  for (const candidate of candidates) {
305
347
  const candidateBootstrap = readBootstrap(candidate);
306
348
  if (!candidateBootstrap)
@@ -310,7 +352,10 @@ async function attemptDaemonBrokerLogin(opts) {
310
352
  typeof opts.baseURL === 'string' &&
311
353
  opts.baseURL.length > 0 &&
312
354
  normalizeBaseUrl(opts.baseURL) !== normalizeBaseUrl(candidateBootstrap.serverUrl)) {
313
- mismatchedBootstrap ??= candidateBootstrap;
355
+ if (!mismatchedBootstrap) {
356
+ mismatchedBootstrap = candidateBootstrap;
357
+ mismatchedPath = candidate;
358
+ }
314
359
  continue;
315
360
  }
316
361
  if (typeof candidateBootstrap.serverUrl !== 'string' ||
@@ -326,8 +371,11 @@ async function attemptDaemonBrokerLogin(opts) {
326
371
  return {
327
372
  ok: false,
328
373
  reason: 'base_url_mismatch',
329
- detail: `The skrr background service on this computer is not logged in to ${opts.baseURL}; it is logged in to ${mismatchedBootstrap.serverUrl}.`,
374
+ // What was READ, not a claim about the daemon: the descriptor may be
375
+ // another profile's (OSK-12117).
376
+ detail: `The daemon descriptor ${mismatchedPath ?? ''} is for ${mismatchedBootstrap.serverUrl}, not ${opts.baseURL}.`,
330
377
  daemonServerUrl: mismatchedBootstrap.serverUrl,
378
+ ...(mismatchedPath ? { descriptorPath: mismatchedPath } : {}),
331
379
  };
332
380
  }
333
381
  return {
@@ -415,7 +463,9 @@ async function redeemCliHandoffToken(bootstrap, opts = {}) {
415
463
  const abortTimer = setTimeout(() => controller.abort(), timeoutMs);
416
464
  let response;
417
465
  try {
418
- response = await fetch(url, {
466
+ // The descriptor secret is a loopback-only credential: never `fetch`, which
467
+ // sends 127.0.0.1 to HTTP_PROXY under Bun and NODE_USE_ENV_PROXY (OSK-12134).
468
+ response = await (0, loopback_http_1.loopbackFetch)(url, {
419
469
  method: 'POST',
420
470
  headers: {
421
471
  Authorization: `Bearer ${bootstrap.secret}`,
@@ -497,7 +547,9 @@ async function mintRefreshFamilyThrough(bootstrap, opts) {
497
547
  const abortTimer = setTimeout(() => controller.abort(), timeoutMs);
498
548
  let response;
499
549
  try {
500
- response = await fetch(url, {
550
+ // The descriptor secret is a loopback-only credential: never `fetch`, which
551
+ // sends 127.0.0.1 to HTTP_PROXY under Bun and NODE_USE_ENV_PROXY (OSK-12134).
552
+ response = await (0, loopback_http_1.loopbackFetch)(url, {
501
553
  method: 'POST',
502
554
  headers: {
503
555
  Authorization: `Bearer ${bootstrap.secret}`,
@@ -49,6 +49,7 @@ const fs = __importStar(require("node:fs"));
49
49
  const os = __importStar(require("node:os"));
50
50
  const path = __importStar(require("node:path"));
51
51
  const auth_core_1 = require("@skrr-ai/auth-core");
52
+ const loopback_http_1 = require("@skrr-ai/auth-core/loopback-http");
52
53
  function isDelegatedCliSession(env = process.env) {
53
54
  return Boolean(env.OVERSKY_AUTONOMOUS_SESSION === '1' &&
54
55
  env.OVERSKY_DELEGATED_CLI_BROKER_URL?.trim() &&
@@ -213,7 +214,9 @@ async function fetchDelegatedCliCredential(env = process.env, options = {}) {
213
214
  try {
214
215
  let res;
215
216
  try {
216
- res = await fetch(brokerUrl, {
217
+ // Proxy-free and loopback-only (OSK-12134): the capability and the
218
+ // credential it returns must never reach HTTP_PROXY.
219
+ res = await (0, loopback_http_1.loopbackFetch)(brokerUrl, {
217
220
  method: 'POST',
218
221
  headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
219
222
  body: JSON.stringify({
@@ -71,7 +71,9 @@ export declare function notInstalledMessage(): string;
71
71
  * preserving "exit code = the daemon's exit code" semantics that scripts
72
72
  * may depend on.
73
73
  */
74
- export declare function execRuntimeBinary(args: string[]): Promise<number>;
74
+ export declare function execRuntimeBinary(args: string[], opts?: {
75
+ binary?: string;
76
+ }): Promise<number>;
75
77
  /** Output from a short-lived runtime probe such as `skrrd status --json`. */
76
78
  export type RuntimeBinaryOutput = {
77
79
  code: number;
@@ -212,9 +212,11 @@ function notInstalledMessage() {
212
212
  * preserving "exit code = the daemon's exit code" semantics that scripts
213
213
  * may depend on.
214
214
  */
215
- function execRuntimeBinary(args) {
215
+ function execRuntimeBinary(args, opts = {}) {
216
216
  return new Promise((resolve, reject) => {
217
- const bin = findRuntimeBinary();
217
+ // `binary` lets `status` render from the LIVE daemon's program rather than
218
+ // the first one on PATH (OSK-12102); every other verb forwards to PATH.
219
+ const bin = opts.binary ?? findRuntimeBinary();
218
220
  if (!bin) {
219
221
  reject(new Error('runtime-binary-not-installed'));
220
222
  return;
@@ -259,7 +259,10 @@ async function acquireManagedSession(input = {}, deps = {}) {
259
259
  sessionToken,
260
260
  model: input.model?.trim() || AUTOMATIC_MODEL,
261
261
  localRuntimeId: (deps.localRuntimeId ?? localRuntimeId)(),
262
- machineUuid: (deps.machineUuid ?? auth_core_1.ensureMachineDaemonId)(),
262
+ // The hardware id every Daemon row is keyed on — the same function
263
+ // the daemon calls — not the minted `~/.skrr/daemon.id` UUIDv7,
264
+ // so the reservation's attribution joins to this machine (OSK-12063).
265
+ machineUuid: (deps.machineUuid ?? auth_core_1.getMachineUuid)(),
263
266
  }, fetchImpl, acquisition.signal);
264
267
  }
265
268
  catch (err) {
@@ -64,6 +64,16 @@ export interface Harness {
64
64
  version?: number;
65
65
  claudeEngine?: 'cli' | 'sdk';
66
66
  lastSeenAt?: string;
67
+ /** This harness's own stored permission-mode fallback, when set. */
68
+ defaultPermissionMode?: string | null;
69
+ /**
70
+ * The permission modes this harness can honour, and where the answer came
71
+ * from — sent by `GET /api/harnesses/:id` for a local row (OSK-12130).
72
+ */
73
+ permissionModes?: {
74
+ modes: string[];
75
+ source: 'daemon' | 'daemon_record' | 'static' | 'vocabulary' | string;
76
+ };
67
77
  /**
68
78
  * Registration metadata. A managed machine's rows carry `machineProduct` and
69
79
  * `machineLeaseId`, which is the only link from a lease to the harness rows its
@@ -0,0 +1,40 @@
1
+ export interface LiveProgram {
2
+ /** The executable the OS reports for the live pid. */
3
+ exec: string;
4
+ /** For an interpreter-launched daemon, the script it runs. */
5
+ script: string | null;
6
+ }
7
+ export interface LiveRuntimeDeps {
8
+ /** pid of the live daemon for `profile`, or null. */
9
+ livePid(profile: string): number | null;
10
+ /** The live pid's program, or null when the OS will not say. */
11
+ programOf(pid: number): LiveProgram | null;
12
+ realpath(p: string): string;
13
+ isExecutable(p: string): boolean;
14
+ }
15
+ export type StatusBinaryDecision = {
16
+ kind: 'same';
17
+ binary: string;
18
+ } | {
19
+ kind: 'unknown';
20
+ binary: string;
21
+ } | {
22
+ kind: 'forward-live';
23
+ binary: string;
24
+ pathBinary: string;
25
+ } | {
26
+ kind: 'mismatch';
27
+ binary: string;
28
+ live: string;
29
+ };
30
+ /** The profile a forwarded argv names, `default` otherwise. */
31
+ export declare function profileFromArgs(args: readonly string[]): string;
32
+ /**
33
+ * Decide which binary renders `status`. Never throws: a probe that cannot
34
+ * answer leaves the PATH binary in place (`unknown`).
35
+ */
36
+ export declare function decideStatusBinary(pathBinary: string, profile: string, deps: LiveRuntimeDeps): StatusBinaryDecision;
37
+ /** One stderr line naming the binary that rendered status, when it is not obvious. */
38
+ export declare function describeStatusBinary(decision: StatusBinaryDecision): string | null;
39
+ export declare function scriptFromArgs(exec: string, args: string): string | null;
40
+ export declare function defaultLiveRuntimeDeps(): LiveRuntimeDeps;
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.profileFromArgs = profileFromArgs;
7
+ exports.decideStatusBinary = decideStatusBinary;
8
+ exports.describeStatusBinary = describeStatusBinary;
9
+ exports.scriptFromArgs = scriptFromArgs;
10
+ exports.defaultLiveRuntimeDeps = defaultLiveRuntimeDeps;
11
+ /**
12
+ * Which runtime binary is the LIVE daemon running, and is it the one
13
+ * `skrr daemon status` is about to forward to? (OSK-12102)
14
+ *
15
+ * `execRuntimeBinary` forwards to the first `skrrd` on PATH. That is right for
16
+ * lifecycle verbs, where PATH order is the person's choice of install. It is
17
+ * wrong for `status`, whose whole subject is the daemon that is running: with
18
+ * an old dev build early on PATH and the release daemon live, the header named
19
+ * `.../releases/0.8.62/skrrd` while the body was rendered by the dev build —
20
+ * its old layout, no per-category permission table — and nothing on screen
21
+ * said two binaries were involved.
22
+ *
23
+ * The live daemon's pid comes from its island bootstrap file (the same
24
+ * discovery the CLI's login broker uses); the program from the OS. When the
25
+ * live program is itself a runtime executable, status is forwarded to it.
26
+ * When it cannot be exec'd directly (a `node .../dist/index.js` dev daemon),
27
+ * status stays on the PATH binary and says so.
28
+ */
29
+ const node_child_process_1 = require("node:child_process");
30
+ const node_fs_1 = require("node:fs");
31
+ const node_path_1 = __importDefault(require("node:path"));
32
+ const daemonBroker_1 = require("./daemonBroker");
33
+ const exec_runtime_binary_1 = require("./exec-runtime-binary");
34
+ function safeRealpath(deps, p) {
35
+ try {
36
+ return deps.realpath(p);
37
+ }
38
+ catch {
39
+ return p;
40
+ }
41
+ }
42
+ function isRuntimeName(p) {
43
+ const base = node_path_1.default
44
+ .basename(p)
45
+ .toLowerCase()
46
+ .replace(/\.(exe|cmd)$/, '');
47
+ return exec_runtime_binary_1.RUNTIME_BINARY_NAMES.some((name) => base === name || base.startsWith(`${name}-`));
48
+ }
49
+ /** The profile a forwarded argv names, `default` otherwise. */
50
+ function profileFromArgs(args) {
51
+ for (let i = 0; i < args.length; i += 1) {
52
+ const arg = args[i];
53
+ if (arg === '--')
54
+ break;
55
+ if (arg === '--profile' && args[i + 1] && !args[i + 1].startsWith('-'))
56
+ return args[i + 1];
57
+ if (arg.startsWith('--profile=') && arg.length > '--profile='.length) {
58
+ return arg.slice('--profile='.length);
59
+ }
60
+ }
61
+ return 'default';
62
+ }
63
+ /**
64
+ * Decide which binary renders `status`. Never throws: a probe that cannot
65
+ * answer leaves the PATH binary in place (`unknown`).
66
+ */
67
+ function decideStatusBinary(pathBinary, profile, deps) {
68
+ let program = null;
69
+ try {
70
+ const pid = deps.livePid(profile);
71
+ program = pid ? deps.programOf(pid) : null;
72
+ }
73
+ catch {
74
+ program = null;
75
+ }
76
+ if (!program)
77
+ return { kind: 'unknown', binary: pathBinary };
78
+ const pathReal = safeRealpath(deps, pathBinary);
79
+ const execReal = safeRealpath(deps, program.exec);
80
+ const scriptReal = program.script ? safeRealpath(deps, program.script) : null;
81
+ if (pathReal === execReal || (scriptReal !== null && pathReal === scriptReal)) {
82
+ return { kind: 'same', binary: pathBinary };
83
+ }
84
+ // Only a runtime executable is forwarded to. A `node`/Electron-hosted daemon
85
+ // has an interpreter as its exec, and running the interpreter with status
86
+ // argv would be nonsense.
87
+ const directlyRunnable = program.script === null &&
88
+ (isRuntimeName(program.exec) || isRuntimeName(execReal)) &&
89
+ deps.isExecutable(program.exec);
90
+ if (directlyRunnable) {
91
+ return { kind: 'forward-live', binary: program.exec, pathBinary };
92
+ }
93
+ return { kind: 'mismatch', binary: pathBinary, live: program.script ?? program.exec };
94
+ }
95
+ /** One stderr line naming the binary that rendered status, when it is not obvious. */
96
+ function describeStatusBinary(decision) {
97
+ switch (decision.kind) {
98
+ case 'forward-live':
99
+ return (`Rendered by the live daemon's binary ${decision.binary} ` +
100
+ `(the first skrrd on PATH is ${decision.pathBinary}, a different build).`);
101
+ case 'mismatch':
102
+ return (`Rendered by ${decision.binary}, the first skrrd on PATH — NOT the live daemon's ` +
103
+ `program (${decision.live}). This layout and these checks are that build's, not the live one's.`);
104
+ default:
105
+ return null;
106
+ }
107
+ }
108
+ function darwinProgramOf(pid) {
109
+ const out = (args) => (0, node_child_process_1.execFileSync)('ps', ['-p', String(pid), '-o', ...args], {
110
+ encoding: 'utf8',
111
+ timeout: 2_000,
112
+ stdio: ['ignore', 'pipe', 'ignore'],
113
+ }).trim();
114
+ const exec = out(['comm=']);
115
+ if (!exec || !node_path_1.default.isAbsolute(exec))
116
+ return null;
117
+ return { exec, script: scriptFromArgs(exec, out(['args='])) };
118
+ }
119
+ function linuxProgramOf(pid) {
120
+ const exec = (0, node_fs_1.readlinkSync)(`/proc/${pid}/exe`);
121
+ let script = null;
122
+ try {
123
+ const cmdline = (0, node_fs_1.readFileSync)(`/proc/${pid}/cmdline`, 'utf8');
124
+ script = scriptFromArgs(exec, cmdline.split('\0').filter(Boolean).join(' '));
125
+ }
126
+ catch {
127
+ script = null;
128
+ }
129
+ return { exec, script };
130
+ }
131
+ /**
132
+ * For an interpreter-hosted daemon (`node --import x.mjs /x/dist/index.js
133
+ * start …`), the script: the first argument after the exec that is neither an
134
+ * option nor the value of a module-loading option. `ps -o args=` is
135
+ * space-joined, so a path containing a space is not recovered — the result is
136
+ * only ever used for display and an equality check, where a miss degrades to
137
+ * the "mismatch" note rather than to a wrong forward.
138
+ */
139
+ const INTERPRETER_VALUE_OPTIONS = new Set([
140
+ '-r',
141
+ '--require',
142
+ '--import',
143
+ '--loader',
144
+ '--experimental-loader',
145
+ '-e',
146
+ '--eval',
147
+ ]);
148
+ function scriptFromArgs(exec, args) {
149
+ if (isRuntimeName(exec))
150
+ return null;
151
+ const rest = args.startsWith(exec) ? args.slice(exec.length) : args.replace(/^\S+/, '');
152
+ const tokens = rest.split(/\s+/).filter(Boolean);
153
+ for (let i = 0; i < tokens.length; i += 1) {
154
+ const token = tokens[i];
155
+ if (INTERPRETER_VALUE_OPTIONS.has(token)) {
156
+ i += 1;
157
+ continue;
158
+ }
159
+ if (token.startsWith('-'))
160
+ continue;
161
+ return token;
162
+ }
163
+ return null;
164
+ }
165
+ function defaultLiveRuntimeDeps() {
166
+ return {
167
+ livePid(profile) {
168
+ for (const candidate of (0, daemonBroker_1.resolveExactProfileBootstrapPaths)(profile)) {
169
+ const bootstrap = (0, daemonBroker_1.readBootstrap)(candidate);
170
+ if (typeof bootstrap?.pid === 'number' && bootstrap.pid > 0)
171
+ return bootstrap.pid;
172
+ }
173
+ return null;
174
+ },
175
+ programOf(pid) {
176
+ if (process.platform === 'darwin')
177
+ return darwinProgramOf(pid);
178
+ if (process.platform === 'linux')
179
+ return linuxProgramOf(pid);
180
+ return null;
181
+ },
182
+ realpath: (p) => (0, node_fs_1.realpathSync)(p),
183
+ isExecutable(p) {
184
+ try {
185
+ (0, node_fs_1.accessSync)(p, node_fs_1.constants.X_OK);
186
+ return true;
187
+ }
188
+ catch {
189
+ return false;
190
+ }
191
+ },
192
+ };
193
+ }