@skrr-ai/cli 0.1.89 → 0.1.90

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 (45) hide show
  1. package/dist/commands/machines/dedicated/exec.js +60 -14
  2. package/dist/commands/machines/dedicated/index.js +2 -0
  3. package/dist/commands/machines/dedicated/power/explain.d.ts +13 -0
  4. package/dist/commands/machines/dedicated/power/explain.js +36 -0
  5. package/dist/commands/machines/dedicated/power/index.d.ts +10 -0
  6. package/dist/commands/machines/dedicated/power/index.js +24 -0
  7. package/dist/commands/machines/dedicated/power/keep-awake.d.ts +20 -0
  8. package/dist/commands/machines/dedicated/power/keep-awake.js +69 -0
  9. package/dist/commands/machines/dedicated/power/set.d.ts +23 -0
  10. package/dist/commands/machines/dedicated/power/set.js +106 -0
  11. package/dist/commands/machines/dedicated/power/show.d.ts +13 -0
  12. package/dist/commands/machines/dedicated/power/show.js +36 -0
  13. package/dist/lib/commitments.d.ts +1 -1
  14. package/dist/lib/commitments.js +4 -2
  15. package/dist/lib/dedicated-keep-awake.d.ts +19 -0
  16. package/dist/lib/dedicated-keep-awake.js +64 -0
  17. package/dist/lib/dedicated-power.d.ts +61 -0
  18. package/dist/lib/dedicated-power.js +243 -0
  19. package/dist/lib/format.d.ts +13 -0
  20. package/dist/lib/format.js +51 -0
  21. package/dist/lib/keychain.js +3 -0
  22. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +32 -0
  23. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +27 -3
  24. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeBusyWire.d.ts +107 -0
  25. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeBusyWire.js +151 -0
  26. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeWakeWire.d.ts +115 -0
  27. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeWakeWire.js +117 -0
  28. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  29. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +8 -3
  30. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +31 -7
  31. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +149 -9
  32. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +32 -0
  33. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +24 -0
  34. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeBusyWire.d.ts +107 -0
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeBusyWire.js +147 -0
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeWakeWire.d.ts +115 -0
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeWakeWire.js +113 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +31 -7
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +144 -8
  42. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  43. package/dist/node_modules/@skrr-ai/data-provider/index.js +6393 -6278
  44. package/oclif.manifest.json +30493 -30127
  45. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ import { type DedicatedRuntimePowerSummary, type DedicatedPowerPolicy, type DedicatedRuntimePowerPolicyState, type OnDemandIneligibleError, type SetDedicatedRuntimePowerPolicyResponse } from '@skrr-ai/data-provider';
2
+ import { type WhenOptions } from './format';
3
+ import { type DedicatedRuntimeScope } from './dedicated-machines';
4
+ /**
5
+ * Domain lib for `skrr machines dedicated power …` (W6). Like its siblings in
6
+ * `dedicated-machines.ts` it speaks `apiFetch` so every refusal arrives as an
7
+ * `ApiFetchError` the Dedicated commands already know how to word; the route is
8
+ * covered by the machines parity spec through this file. The web calls the same
9
+ * route through `dataService.setDedicatedRuntimePowerPolicy`.
10
+ */
11
+ export type SetPowerOutcome = {
12
+ ok: true;
13
+ response: SetDedicatedRuntimePowerPolicyResponse;
14
+ } | {
15
+ ok: false;
16
+ refusal: OnDemandIneligibleError;
17
+ };
18
+ /**
19
+ * Set the policy. An ineligible `on_demand` is a RESULT, not an exception: the
20
+ * refusal is the answer the caller must explain (reason + fix). Every other
21
+ * failure is rethrown for the command's usual error handling.
22
+ */
23
+ export declare function setDedicatedPowerPolicy(leaseId: string, request: {
24
+ policy?: DedicatedPowerPolicy;
25
+ idleAfterMinutes?: number;
26
+ budgetHoursPerMonth?: number | null;
27
+ }, scope?: DedicatedRuntimeScope): Promise<SetPowerOutcome>;
28
+ /**
29
+ * The policy in words. `on_demand` promises a stop only when the platform stops idle machines at all
30
+ * (`autoStopMode`); an unknown mode is never read as on, so the sentence never promises one.
31
+ */
32
+ export declare function describePolicyWords(policy: DedicatedPowerPolicy, idleAfterMinutes: number, autoStopMode?: 'off' | 'canary' | 'on'): string;
33
+ export declare function describePowerPolicy(power: DedicatedRuntimePowerPolicyState, autoStopMode?: 'off' | 'canary' | 'on'): string;
34
+ /** `GET /api/machines/dedicated/:leaseId/power` — the read-only power card. */
35
+ export declare function getDedicatedPowerSummary(leaseId: string, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimePowerSummary>;
36
+ type PowerDecisionView = NonNullable<DedicatedRuntimePowerSummary['decision']>;
37
+ /** The stay/stop/wake decision in words: the shared reason copy backs the server's detail. */
38
+ export declare function describePowerDecisionLines(decision: PowerDecisionView): {
39
+ headline: string;
40
+ detail: string;
41
+ fix: string | null;
42
+ };
43
+ /**
44
+ * The last automatic stop and how it was judged (A3). Every word comes from the shared copy
45
+ * (`describeAutoStopVerdict`, `describeMachineCheckpointOutcome`); nothing is composed here.
46
+ * `verdict: null` is "not judged yet", and is said so: it is never rendered as clean. A sentence
47
+ * the decision text already carries (the server appends it while the machine is stopped) is not
48
+ * repeated.
49
+ */
50
+ export declare function describeLastAutoStopLines(last: DedicatedRuntimePowerSummary['lastAutoStop'], decisionDetail?: string, when?: WhenOptions): {
51
+ lines: string[];
52
+ fix: string | null;
53
+ };
54
+ /** The latest wake decision and who asked, as lines; empty when there is none. */
55
+ export declare function describeLastWakeDecisionLines(wake: DedicatedRuntimePowerSummary['lastWakeDecision'], when?: WhenOptions): string[];
56
+ /** The lines of `power show`. */
57
+ export declare function describePowerSummary(summary: DedicatedRuntimePowerSummary, when?: WhenOptions): string[];
58
+ /** The lines of `power explain`; the fix is always last. */
59
+ export declare function describePowerExplanation(summary: DedicatedRuntimePowerSummary, when?: WhenOptions): string[];
60
+ export declare function describeBudget(power: DedicatedRuntimePowerPolicyState): string;
61
+ export {};
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setDedicatedPowerPolicy = setDedicatedPowerPolicy;
4
+ exports.describePolicyWords = describePolicyWords;
5
+ exports.describePowerPolicy = describePowerPolicy;
6
+ exports.getDedicatedPowerSummary = getDedicatedPowerSummary;
7
+ exports.describePowerDecisionLines = describePowerDecisionLines;
8
+ exports.describeLastAutoStopLines = describeLastAutoStopLines;
9
+ exports.describeLastWakeDecisionLines = describeLastWakeDecisionLines;
10
+ exports.describePowerSummary = describePowerSummary;
11
+ exports.describePowerExplanation = describePowerExplanation;
12
+ exports.describeBudget = describeBudget;
13
+ const data_provider_1 = require("@skrr-ai/data-provider");
14
+ const api_fetch_1 = require("./api-fetch");
15
+ const format_1 = require("./format");
16
+ const dedicated_machines_1 = require("./dedicated-machines");
17
+ function bodyOf(err) {
18
+ const raw = err?.body;
19
+ if (typeof raw !== 'string')
20
+ return null;
21
+ try {
22
+ const parsed = JSON.parse(raw);
23
+ return parsed && typeof parsed === 'object' ? parsed : null;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ /**
30
+ * Set the policy. An ineligible `on_demand` is a RESULT, not an exception: the
31
+ * refusal is the answer the caller must explain (reason + fix). Every other
32
+ * failure is rethrown for the command's usual error handling.
33
+ */
34
+ async function setDedicatedPowerPolicy(leaseId, request, scope = {}) {
35
+ try {
36
+ const response = await (0, api_fetch_1.apiFetch)((0, dedicated_machines_1.withDedicatedScope)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/power`, scope), { method: 'PUT', body: request });
37
+ return { ok: true, response };
38
+ }
39
+ catch (err) {
40
+ const body = bodyOf(err);
41
+ if (err?.status === 409 && body?.code === 'ON_DEMAND_INELIGIBLE') {
42
+ const reason = String(body.reason ?? '');
43
+ // The vocabulary is shared; the server's words win, the shared copy backs them up.
44
+ const copy = (0, data_provider_1.describeOnDemandIneligibility)(reason);
45
+ return {
46
+ ok: false,
47
+ refusal: {
48
+ error: String(body.error ?? 'on_demand is not available for this machine yet.'),
49
+ code: 'ON_DEMAND_INELIGIBLE',
50
+ reason: reason,
51
+ detail: String(body.detail ?? copy?.detail ?? ''),
52
+ fix: String(body.fix ?? copy?.fix ?? ''),
53
+ },
54
+ };
55
+ }
56
+ throw err;
57
+ }
58
+ }
59
+ /**
60
+ * The policy in words. `on_demand` promises a stop only when the platform stops idle machines at all
61
+ * (`autoStopMode`); an unknown mode is never read as on, so the sentence never promises one.
62
+ */
63
+ function describePolicyWords(policy, idleAfterMinutes, autoStopMode) {
64
+ if (policy === 'on_demand') {
65
+ if (autoStopMode === 'on' || autoStopMode === 'canary') {
66
+ return `on_demand (stops after ${idleAfterMinutes} minutes idle)`;
67
+ }
68
+ // Unknown is neutral, never a claim in either direction.
69
+ return autoStopMode === 'off'
70
+ ? `on_demand (idle limit ${idleAfterMinutes} minutes; automatic stop is not turned on yet, so nothing stops it)`
71
+ : `on_demand (stops after ${idleAfterMinutes} minutes idle when automatic stop is on)`;
72
+ }
73
+ if (policy === 'manual')
74
+ return 'manual (stops and starts only when you do)';
75
+ return policy;
76
+ }
77
+ function describePowerPolicy(power, autoStopMode) {
78
+ const base = describePolicyWords(power.policy, power.idleAfterMinutes, autoStopMode);
79
+ return power.policyStored ? base : `${base} (default; never set)`;
80
+ }
81
+ /* ---- power show / explain (V5) ---------------------------------------------- */
82
+ /** `GET /api/machines/dedicated/:leaseId/power` — the read-only power card. */
83
+ function getDedicatedPowerSummary(leaseId, scope = {}) {
84
+ return (0, api_fetch_1.apiFetch)((0, dedicated_machines_1.withDedicatedScope)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/power`, scope));
85
+ }
86
+ /** The stay/stop/wake decision in words: the shared reason copy backs the server's detail. */
87
+ function describePowerDecisionLines(decision) {
88
+ const copy = (0, data_provider_1.describePowerReason)(decision.reason);
89
+ const VERBS = { stay: 'Staying on', stop: 'Stopped', wake: 'Wake' };
90
+ const verb = VERBS[decision.question] ?? decision.question;
91
+ return {
92
+ // The headline is the human label only; the reason token lives in `--json` and `power explain`.
93
+ // With no shared copy and no server detail there is no sentence, so the token is the last resort.
94
+ headline: decision.detail || copy?.detail ? verb : `${verb}: ${decision.reason}`,
95
+ detail: decision.detail || copy?.detail || '',
96
+ fix: decision.fix ?? copy?.fix ?? null,
97
+ };
98
+ }
99
+ /**
100
+ * The last automatic stop and how it was judged (A3). Every word comes from the shared copy
101
+ * (`describeAutoStopVerdict`, `describeMachineCheckpointOutcome`); nothing is composed here.
102
+ * `verdict: null` is "not judged yet", and is said so: it is never rendered as clean. A sentence
103
+ * the decision text already carries (the server appends it while the machine is stopped) is not
104
+ * repeated.
105
+ */
106
+ function describeLastAutoStopLines(last, decisionDetail = '', when = {}) {
107
+ if (!last)
108
+ return { lines: [], fix: null };
109
+ const lines = [];
110
+ const verdict = (0, data_provider_1.describeAutoStopVerdict)(last.verdict, { evidence: last.evidence });
111
+ const atText = last.at ? ` at ${(0, format_1.formatWhen)(last.at, when)}` : '';
112
+ lines.push(`Auto stop: ${verdict ? `${verdict.label} (${verdict.verdict})` : 'not checked yet'}${atText}`);
113
+ const checkpoint = (0, data_provider_1.describeMachineCheckpointOutcome)(last.checkpointOutcome, { action: 'stop' });
114
+ if (checkpoint && !decisionDetail.includes(checkpoint.sentence)) {
115
+ lines.push(` ${checkpoint.sentence}`);
116
+ }
117
+ if (verdict && last.verdict !== 'clean') {
118
+ if (!decisionDetail.includes(verdict.detail))
119
+ lines.push(` ${verdict.detail}`);
120
+ return { lines, fix: verdict.fix };
121
+ }
122
+ return { lines, fix: null };
123
+ }
124
+ function formatHours(hours) {
125
+ return `${Math.round(hours * 100) / 100}h`;
126
+ }
127
+ function formatUsage(usage) {
128
+ if (!usage.available)
129
+ return `unavailable (${usage.reason})`;
130
+ const cost = String(usage.currency).toLowerCase() === 'usd'
131
+ ? (0, dedicated_machines_1.formatUsdCents)(usage.costCents)
132
+ : `${usage.cost.toFixed(2)} ${String(usage.currency).toUpperCase()}`;
133
+ return `${formatHours(usage.hours)}, ${cost} in ${usage.month}${usage.openInterval ? ' (still running)' : ''}`;
134
+ }
135
+ function formatHolder(h, when) {
136
+ if (h.kind === 'unreadable') {
137
+ return `cannot see — staying on${h.detail ? ` (${h.detail})` : ''}`;
138
+ }
139
+ return [h.kind, h.ref, h.since ? `since ${(0, format_1.formatWhen)(h.since, when)}` : null]
140
+ .filter(Boolean)
141
+ .join(' · ');
142
+ }
143
+ /** The latest wake decision and who asked, as lines; empty when there is none. */
144
+ function describeLastWakeDecisionLines(wake, when = {}) {
145
+ if (!wake)
146
+ return [];
147
+ // The sentence is the answer. The reason token and the transport ("http") are internals; who asked is
148
+ // named only when it is not the person reading this.
149
+ const asked = wake.source && wake.source !== 'person'
150
+ ? ` Asked by ${wake.source}${wake.sourceRef ? ` ${wake.sourceRef}` : ''}.`
151
+ : '';
152
+ const copy = (0, data_provider_1.describePowerReason)(wake.reason);
153
+ const sentence = wake.detail || copy?.detail || wake.reason;
154
+ const lines = [
155
+ `Wake: ${sentence}${asked}${wake.at ? ` (${(0, format_1.formatWhen)(wake.at, when)})` : ''}`,
156
+ ];
157
+ if (wake.fix)
158
+ lines.push(` ${wake.fix}`);
159
+ return lines;
160
+ }
161
+ /** The lines of `power show`. */
162
+ function describePowerSummary(summary, when = {}) {
163
+ const { power } = summary;
164
+ const lines = [];
165
+ lines.push(`Dedicated Runtime ${summary.leaseId}`);
166
+ lines.push(`State: ${summary.state}${summary.stateSince ? ` since ${(0, format_1.formatWhen)(summary.stateSince, when)}` : ''}`);
167
+ const policy = describePolicyWords(power.policy, power.idleAfterMinutes, summary.autoStopMode);
168
+ lines.push(`Policy: ${policy}`);
169
+ if (power.keepAwakeUntil)
170
+ lines.push(`Keep awake until: ${(0, format_1.formatWhen)(power.keepAwakeUntil, when)}`);
171
+ if (power.budgetHoursPerMonth !== null)
172
+ lines.push(`Budget: ${power.budgetHoursPerMonth} hours per month`);
173
+ if (summary.decision) {
174
+ const d = describePowerDecisionLines(summary.decision);
175
+ lines.push(`Decision: ${d.headline}`);
176
+ if (d.detail)
177
+ lines.push(` ${d.detail}`);
178
+ }
179
+ else {
180
+ const none = (0, data_provider_1.describeNoPowerDecision)(summary);
181
+ if (none) {
182
+ lines.push(`Decision: ${none.headline}`);
183
+ lines.push(` ${none.detail}`);
184
+ }
185
+ else {
186
+ lines.push('Decision: nothing to explain for this state');
187
+ }
188
+ }
189
+ lines.push(...describeLastAutoStopLines(summary.lastAutoStop, summary.decision?.detail, when).lines);
190
+ lines.push(...describeLastWakeDecisionLines(summary.lastWakeDecision, when));
191
+ if (summary.holders === null) {
192
+ lines.push('Holders: not checked (the machine is not running)');
193
+ }
194
+ else if (summary.holders.length === 0) {
195
+ lines.push('Holders: none — idle');
196
+ }
197
+ else {
198
+ lines.push('Holders:');
199
+ for (const h of summary.holders)
200
+ lines.push(` - ${formatHolder(h, when)}`);
201
+ }
202
+ lines.push(`This month: ${formatUsage(summary.usage)}`);
203
+ lines.push(`Next expected wake: ${(summary.nextExpectedWake ? (0, format_1.formatWhen)(summary.nextExpectedWake, when) : null) ??
204
+ (summary.nextExpectedWakeUnavailable ? 'unavailable' : 'none scheduled')}`);
205
+ lines.push(`Last wake: ${summary.lastWake
206
+ ? `${summary.lastWake.at ? (0, format_1.formatWhen)(summary.lastWake.at, when) : 'unknown time'}${summary.lastWake.source ? ` (${summary.lastWake.source})` : ''}`
207
+ : 'none'}`);
208
+ return lines;
209
+ }
210
+ /** The lines of `power explain`; the fix is always last. */
211
+ function describePowerExplanation(summary, when = {}) {
212
+ const lines = [];
213
+ if (!summary.decision) {
214
+ const none = (0, data_provider_1.describeNoPowerDecision)(summary);
215
+ lines.push(none ? `${none.headline}. ${none.detail}` : `Nothing to explain for ${summary.leaseId}.`);
216
+ lines.push(...describeLastWakeDecisionLines(summary.lastWakeDecision, when));
217
+ return lines;
218
+ }
219
+ const d = summary.decision;
220
+ const text = describePowerDecisionLines(d);
221
+ const wakeLines = describeLastWakeDecisionLines(summary.lastWakeDecision, when);
222
+ lines.push(`Question: ${d.question}`);
223
+ lines.push(`Gate: ${d.gate}`);
224
+ lines.push(`Reason: ${d.reason}`);
225
+ if (d.at)
226
+ lines.push(`At: ${(0, format_1.formatWhen)(d.at, when)}`);
227
+ if (text.detail)
228
+ lines.push(`Detail: ${text.detail}`);
229
+ const stop = describeLastAutoStopLines(summary.lastAutoStop, d.detail, when);
230
+ lines.push(...stop.lines);
231
+ lines.push(...wakeLines);
232
+ // A false stop's fix replaces nothing: the decision's own fix is still last-but-one.
233
+ if (text.fix && text.fix !== stop.fix)
234
+ lines.push(`Fix: ${text.fix}`);
235
+ if (stop.fix)
236
+ lines.push(`Fix: ${stop.fix}`);
237
+ return lines;
238
+ }
239
+ function describeBudget(power) {
240
+ return power.budgetHoursPerMonth == null
241
+ ? 'Monthly compute-hour budget: none.'
242
+ : `Monthly compute-hour budget: ${power.budgetHoursPerMonth} hours. At the budget, automatic starts are refused; a running machine is never stopped by it and you can still start it yourself.`;
243
+ }
@@ -98,3 +98,16 @@ export declare function localTime(value: unknown): string;
98
98
  * rather than print an empty parenthesis.
99
99
  */
100
100
  export declare function localZone(at?: Date): string;
101
+ export type WhenOptions = {
102
+ /** The reference instant; injectable so output is deterministic in tests. */
103
+ now?: Date;
104
+ /** IANA zone to render in; defaults to the reader's own. */
105
+ timeZone?: string;
106
+ };
107
+ /**
108
+ * A timestamp for a person: local time plus how far away it is, `07:38 (2h ago)`
109
+ * or `09:39 (in 1h 59m)`. The date is added only when it is not today in the
110
+ * rendered zone (`2026-10-07 22:10 (11h ago)`). Machine output (`--json`) keeps
111
+ * the raw ISO string; this is for text only. Unparseable input is returned as is.
112
+ */
113
+ export declare function formatWhen(value: unknown, options?: WhenOptions): string;
@@ -9,6 +9,7 @@ exports.renderTable = renderTable;
9
9
  exports.localStamp = localStamp;
10
10
  exports.localTime = localTime;
11
11
  exports.localZone = localZone;
12
+ exports.formatWhen = formatWhen;
12
13
  /**
13
14
  * Code points that occupy TWO terminal columns (Unicode East Asian Wide and
14
15
  * Fullwidth), plus the emoji blocks terminals render double-width.
@@ -259,3 +260,53 @@ function localZone(at = new Date()) {
259
260
  const abs = Math.abs(offset);
260
261
  return `UTC${sign}${`${Math.floor(abs / 60)}`.padStart(2, '0')}:${`${abs % 60}`.padStart(2, '0')}`;
261
262
  }
263
+ function zonedParts(date, timeZone) {
264
+ const parts = new Intl.DateTimeFormat('en-CA', {
265
+ timeZone,
266
+ year: 'numeric',
267
+ month: '2-digit',
268
+ day: '2-digit',
269
+ hour: '2-digit',
270
+ minute: '2-digit',
271
+ hourCycle: 'h23',
272
+ }).formatToParts(date);
273
+ const out = {};
274
+ for (const p of parts)
275
+ out[p.type] = p.value;
276
+ return out;
277
+ }
278
+ function relativeSpan(ms) {
279
+ const minutes = Math.floor(ms / 60_000);
280
+ if (minutes < 1)
281
+ return 'under a minute';
282
+ if (minutes < 60)
283
+ return `${minutes}m`;
284
+ const hours = Math.floor(minutes / 60);
285
+ if (hours < 24)
286
+ return minutes % 60 ? `${hours}h ${minutes % 60}m` : `${hours}h`;
287
+ const days = Math.floor(hours / 24);
288
+ return hours % 24 ? `${days}d ${hours % 24}h` : `${days}d`;
289
+ }
290
+ /**
291
+ * A timestamp for a person: local time plus how far away it is, `07:38 (2h ago)`
292
+ * or `09:39 (in 1h 59m)`. The date is added only when it is not today in the
293
+ * rendered zone (`2026-10-07 22:10 (11h ago)`). Machine output (`--json`) keeps
294
+ * the raw ISO string; this is for text only. Unparseable input is returned as is.
295
+ */
296
+ function formatWhen(value, options = {}) {
297
+ if (value === null || value === undefined || value === '')
298
+ return '-';
299
+ const date = value instanceof Date ? value : new Date(typeof value === 'number' ? value : String(value));
300
+ if (Number.isNaN(date.getTime()))
301
+ return String(value);
302
+ const now = options.now ?? new Date();
303
+ const at = zonedParts(date, options.timeZone);
304
+ const ref = zonedParts(now, options.timeZone);
305
+ const sameDay = at.year === ref.year && at.month === ref.month && at.day === ref.day;
306
+ const clock = `${at.hour}:${at.minute}`;
307
+ const stamp = sameDay ? clock : `${at.year}-${at.month}-${at.day} ${clock}`;
308
+ const delta = date.getTime() - now.getTime();
309
+ const span = relativeSpan(Math.abs(delta));
310
+ const relative = span === 'under a minute' ? 'just now' : delta >= 0 ? `in ${span}` : `${span} ago`;
311
+ return `${stamp} (${relative})`;
312
+ }
@@ -150,6 +150,9 @@ function setActiveProfile(profile) {
150
150
  _profileResolved = true;
151
151
  if (profile && profile.length > 0)
152
152
  _activeProfile = profile;
153
+ // The machineUuid the CLI sends (first-party harness broker) is per profile,
154
+ // exactly as the daemon's is (OSK-13845); both read the same persisted choice.
155
+ (0, auth_core_1.setMachineUuidProfile)(_activeProfile);
153
156
  }
154
157
  /**
155
158
  * Whether the profile has been chosen for this process.
@@ -309,6 +309,28 @@ export declare const COMPUTER_CAPABILITIES: {
309
309
  * before, and never sends the input.
310
310
  */
311
311
  readonly browserViewport: "computer_browser_viewport_v1";
312
+ /**
313
+ * A browser surface whose browser is still starting says so (OSK-13824): its
314
+ * descriptor carries `starting: { since }` from the moment its Chromium
315
+ * begins to launch until that launch ends (running, or failed), and the
316
+ * window shows "Starting the browser…" instead of subscribing into a
317
+ * refusal. A cold start on a fresh guest takes 25–30 s. A daemon announcing
318
+ * this states the ABSENCE of `starting` too: a browser surface without it
319
+ * is not starting. Without the announcement a client cannot tell, and falls
320
+ * back to reading a surface it saw open moments ago as starting.
321
+ */
322
+ readonly browserStarting: "computer_browser_starting_v1";
323
+ /**
324
+ * An agent actor on the machine's activity and control leases (OSK-13827)
325
+ * names the REAL agent: `agentId` is the agent's own id for tool-call and
326
+ * browser sessions too (v1 daemons put the SESSION id there for tool calls,
327
+ * so no reader could ever name them), and may carry `engine`, the harness
328
+ * the session runs on, so one agent's sessions on different engines can be
329
+ * told apart. A daemon announcing this states what it sends; the server
330
+ * withholds `engine` from every other daemon's actors, and a reader treats
331
+ * an actor without it as it always did.
332
+ */
333
+ readonly activityActorV2: "computer_activity_actor_v2";
312
334
  };
313
335
  export type ComputerCapabilityKey = keyof typeof COMPUTER_CAPABILITIES;
314
336
  export type ComputerCapability = (typeof COMPUTER_CAPABILITIES)[ComputerCapabilityKey];
@@ -355,7 +377,17 @@ export interface ComputerWireAgentActor {
355
377
  agentId: string;
356
378
  sessionId: string;
357
379
  label: string;
380
+ /**
381
+ * The engine the session runs on (`claude-code`, `codex`, …), as the daemon
382
+ * names its backend. Present only from a daemon that announced
383
+ * `computer_activity_actor_v2` (`COMPUTER_CAPABILITIES.activityActorV2`).
384
+ * A display distinction between one agent's sessions, never an identity:
385
+ * `sameComputerActor` still compares the agent id and the session id.
386
+ */
387
+ engine?: string;
358
388
  }
389
+ /** The longest `engine` the wire carries; a longer one is not an engine name. */
390
+ export declare const COMPUTER_ACTOR_ENGINE_MAX_LENGTH = 64;
359
391
  export interface ComputerWireHumanActor {
360
392
  kind: 'human';
361
393
  userId: string;
@@ -25,9 +25,9 @@
25
25
  * dropped every command event for a release (OSK-13605).
26
26
  */
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.COMPUTER_BROWSER_DIALOG_OUTCOMES = exports.COMPUTER_BROWSER_DIALOG_KINDS = exports.COMPUTER_INPUT_TEXT_MAX_LENGTH = exports.COMPUTER_BROWSER_INPUT_PLATFORMS = exports.COMPUTER_BROWSER_VIEWPORT_LIMITS = exports.COMPUTER_BROWSER_INPUT_TYPES = exports.COMPUTER_TERMINAL_CAVEATS = exports.COMPUTER_TERMINAL_HISTORY_CAUSES = exports.COMPUTER_TERMINAL_LIFETIMES = exports.COMPUTER_TERMINAL_INPUT_TYPES = exports.COMPUTER_TERMINAL_SIGNALS = exports.COMPUTER_DIAGNOSTICS_MAX_STREAMS = exports.COMPUTER_DIAGNOSTICS_INTERVAL_MS = exports.COMPUTER_DIAGNOSTIC_COUNTERS = exports.COMPUTER_FRAME_ACK_TIMEOUT_MS = exports.COMPUTER_FRAME_MAX_BYTES = exports.COMPUTER_BROWSER_VIEWPORT = exports.COMPUTER_SURFACE_END_REASONS = exports.COMPUTER_SURFACE_STATES = exports.COMPUTER_SURFACE_KINDS = exports.COMPUTER_CONTROL_ACTIVITY_CHANGES = exports.COMPUTER_CONTROL_CHANGE_REASONS = exports.COMPUTER_SURFACE_UNPIN_REASONS = exports.COMPUTER_SURFACE_PIN_ACTIONS = exports.COMPUTER_SHARE_MODES = exports.COMPUTER_SHARE_ACTIONS = exports.COMPUTER_CONTROL_ACTIONS = exports.COMPUTER_AGENT_WRITE_WAIT_MS = exports.COMPUTER_RECONNECT_GRACE_MS = exports.COMPUTER_RECLAIM_WARNING_MS = exports.COMPUTER_CONTROL_IDLE_MS = exports.COMPUTER_HANDBACK_NOTE_MAX_LENGTH = exports.COMPUTER_GRANT_TIERS = exports.COMPUTER_OPERATIONS = exports.COMPUTER_ACCESS_LEVELS = exports.COMPUTER_PLATFORM_ROUTE_SESSION_PREFIXES = exports.COMPUTER_ACTIVITY_ACTOR_KINDS = exports.COMPUTER_ACTOR_KINDS = exports.COMPUTER_REFUSAL_CODES = exports.COMPUTER_CAPABILITY_VALUES = exports.COMPUTER_CAPABILITIES = exports.COMPUTER_MANIFEST_REORDER_WAIT_MS = exports.COMPUTER_RESYNC_TIMEOUT_MS = exports.COMPUTER_API_ANSWERED_EVENT_KEYS = exports.COMPUTER_SERVER_EVENT_KEYS = exports.DAEMON_COMPUTER_REPORT_EVENT_KEYS = exports.DAEMON_COMPUTER_EVENT_NAMES = exports.COMPUTER_EVENT_NAMES = exports.DAEMON_COMPUTER_EVENTS = exports.COMPUTER_EVENTS = void 0;
29
- exports.COMPUTER_DECLARED_SERVICE_NAME_MAX_LENGTH = exports.COMPUTER_DECLARED_SERVICES_MAX = exports.COMPUTER_DECLARED_SERVICE_PROTOCOLS = exports.COMPUTER_SERVICES_TOOL_ERROR_PREFIX = exports.COMPUTER_SERVICES_TOOL_NAME = exports.COMPUTER_DECLARED_SERVICE_ACTIONS = exports.COMPUTER_COMMAND_OUTPUT_STREAMS = exports.COMPUTER_COMMAND_START_REFUSALS = exports.COMPUTER_COMMAND_UNKNOWN_END_SIGNAL = exports.COMPUTER_COMMAND_NOT_STARTED_SIGNAL = exports.COMPUTER_COMMAND_OUTCOMES = exports.COMPUTER_LIFECYCLE_EVENTS = exports.COMPUTER_COMMAND_PHASES = exports.COMPUTER_ACTIVITY_WITHHELD_PLACEHOLDER = exports.COMPUTER_ACTIVITY_OUTPUT_MAX_LENGTH = exports.COMPUTER_ACTIVITY_SUMMARY_MAX_LENGTH = exports.COMPUTER_ACTIVITY_KINDS = exports.COMPUTER_FILE_CONTENTS_TAG_PATTERN = exports.COMPUTER_FILE_CONTENTS_TAG_MAX_ENTRIES = exports.COMPUTER_FILE_CHANGES = exports.COMPUTER_FILES_CHANGED_MAX_CHANGES = exports.COMPUTER_FILES_WATCH_DEGRADED_REASONS = exports.COMPUTER_FILE_WITHHELD_REASONS = exports.COMPUTER_FILE_TYPES = exports.COMPUTER_FILE_GIT_STATUS_BUDGET_MS = exports.COMPUTER_FILE_SEARCH_MAX_QUERY_LENGTH = exports.COMPUTER_FILE_SEARCH_MAX_BUDGET_MS = exports.COMPUTER_FILE_SEARCH_DEFAULT_BUDGET_MS = exports.COMPUTER_FILE_SEARCH_MAX_LIMIT = exports.COMPUTER_FILE_SEARCH_DEFAULT_LIMIT = exports.COMPUTER_FILE_GIT_MARKS = exports.COMPUTER_FILE_SEARCH_STATES = exports.COMPUTER_FILE_SEARCH_OPERATIONS = exports.COMPUTER_FILE_OPERATIONS = exports.COMPUTER_BROWSER_URL_MAX_LENGTH = exports.COMPUTER_BROWSER_TAB_ACTIONS = exports.COMPUTER_BROWSER_HISTORY_ACTIONS = exports.COMPUTER_BROWSER_COPY_MAX_LENGTH = exports.COMPUTER_TERMINAL_CWD_MAX_LENGTH = exports.COMPUTER_BROWSER_FILE_CHOOSER_PATH_MAX_LENGTH = exports.COMPUTER_BROWSER_FILE_CHOOSER_ACCEPT_MAX_LENGTH = exports.COMPUTER_BROWSER_FILE_CHOOSER_MAX_FILES = exports.COMPUTER_BROWSER_FILE_CHOOSER_OUTCOMES = exports.COMPUTER_BROWSER_FILE_CHOOSER_MODES = exports.COMPUTER_BROWSER_DOWNLOADS_MAX = exports.COMPUTER_BROWSER_DOWNLOAD_STATES = exports.COMPUTER_BROWSER_DIALOG_CREDENTIAL_MAX_LENGTH = exports.COMPUTER_BROWSER_DIALOG_PROMPT_MAX_LENGTH = exports.COMPUTER_BROWSER_DIALOG_MESSAGE_MAX_LENGTH = exports.COMPUTER_BROWSER_DIALOG_ANSWERERS = void 0;
30
- exports.COMPUTER_LEASE_STATE_POWER_PHASE = exports.COMPUTER_START_REFUSALS = exports.COMPUTER_PERSONAL_SIGN_IN_REASONS = exports.COMPUTER_PERSONAL_POWER_PHASES = exports.COMPUTER_POWER_PHASES = exports.COMPUTER_MACHINE_KINDS = exports.COMPUTER_DECLARED_SERVICE_PROBLEMS = exports.COMPUTER_DECLARED_SERVICE_NAME_PATTERN = exports.COMPUTER_DECLARED_SERVICE_HOST = exports.COMPUTER_DECLARED_SERVICE_PATH_MAX_LENGTH = void 0;
28
+ exports.COMPUTER_BROWSER_DIALOG_KINDS = exports.COMPUTER_INPUT_TEXT_MAX_LENGTH = exports.COMPUTER_BROWSER_INPUT_PLATFORMS = exports.COMPUTER_BROWSER_VIEWPORT_LIMITS = exports.COMPUTER_BROWSER_INPUT_TYPES = exports.COMPUTER_TERMINAL_CAVEATS = exports.COMPUTER_TERMINAL_HISTORY_CAUSES = exports.COMPUTER_TERMINAL_LIFETIMES = exports.COMPUTER_TERMINAL_INPUT_TYPES = exports.COMPUTER_TERMINAL_SIGNALS = exports.COMPUTER_DIAGNOSTICS_MAX_STREAMS = exports.COMPUTER_DIAGNOSTICS_INTERVAL_MS = exports.COMPUTER_DIAGNOSTIC_COUNTERS = exports.COMPUTER_FRAME_ACK_TIMEOUT_MS = exports.COMPUTER_FRAME_MAX_BYTES = exports.COMPUTER_BROWSER_VIEWPORT = exports.COMPUTER_SURFACE_END_REASONS = exports.COMPUTER_SURFACE_STATES = exports.COMPUTER_SURFACE_KINDS = exports.COMPUTER_CONTROL_ACTIVITY_CHANGES = exports.COMPUTER_CONTROL_CHANGE_REASONS = exports.COMPUTER_SURFACE_UNPIN_REASONS = exports.COMPUTER_SURFACE_PIN_ACTIONS = exports.COMPUTER_SHARE_MODES = exports.COMPUTER_SHARE_ACTIONS = exports.COMPUTER_CONTROL_ACTIONS = exports.COMPUTER_AGENT_WRITE_WAIT_MS = exports.COMPUTER_RECONNECT_GRACE_MS = exports.COMPUTER_RECLAIM_WARNING_MS = exports.COMPUTER_CONTROL_IDLE_MS = exports.COMPUTER_HANDBACK_NOTE_MAX_LENGTH = exports.COMPUTER_GRANT_TIERS = exports.COMPUTER_OPERATIONS = exports.COMPUTER_ACCESS_LEVELS = exports.COMPUTER_ACTOR_ENGINE_MAX_LENGTH = exports.COMPUTER_PLATFORM_ROUTE_SESSION_PREFIXES = exports.COMPUTER_ACTIVITY_ACTOR_KINDS = exports.COMPUTER_ACTOR_KINDS = exports.COMPUTER_REFUSAL_CODES = exports.COMPUTER_CAPABILITY_VALUES = exports.COMPUTER_CAPABILITIES = exports.COMPUTER_MANIFEST_REORDER_WAIT_MS = exports.COMPUTER_RESYNC_TIMEOUT_MS = exports.COMPUTER_API_ANSWERED_EVENT_KEYS = exports.COMPUTER_SERVER_EVENT_KEYS = exports.DAEMON_COMPUTER_REPORT_EVENT_KEYS = exports.DAEMON_COMPUTER_EVENT_NAMES = exports.COMPUTER_EVENT_NAMES = exports.DAEMON_COMPUTER_EVENTS = exports.COMPUTER_EVENTS = void 0;
29
+ exports.COMPUTER_DECLARED_SERVICES_MAX = exports.COMPUTER_DECLARED_SERVICE_PROTOCOLS = exports.COMPUTER_SERVICES_TOOL_ERROR_PREFIX = exports.COMPUTER_SERVICES_TOOL_NAME = exports.COMPUTER_DECLARED_SERVICE_ACTIONS = exports.COMPUTER_COMMAND_OUTPUT_STREAMS = exports.COMPUTER_COMMAND_START_REFUSALS = exports.COMPUTER_COMMAND_UNKNOWN_END_SIGNAL = exports.COMPUTER_COMMAND_NOT_STARTED_SIGNAL = exports.COMPUTER_COMMAND_OUTCOMES = exports.COMPUTER_LIFECYCLE_EVENTS = exports.COMPUTER_COMMAND_PHASES = exports.COMPUTER_ACTIVITY_WITHHELD_PLACEHOLDER = exports.COMPUTER_ACTIVITY_OUTPUT_MAX_LENGTH = exports.COMPUTER_ACTIVITY_SUMMARY_MAX_LENGTH = exports.COMPUTER_ACTIVITY_KINDS = exports.COMPUTER_FILE_CONTENTS_TAG_PATTERN = exports.COMPUTER_FILE_CONTENTS_TAG_MAX_ENTRIES = exports.COMPUTER_FILE_CHANGES = exports.COMPUTER_FILES_CHANGED_MAX_CHANGES = exports.COMPUTER_FILES_WATCH_DEGRADED_REASONS = exports.COMPUTER_FILE_WITHHELD_REASONS = exports.COMPUTER_FILE_TYPES = exports.COMPUTER_FILE_GIT_STATUS_BUDGET_MS = exports.COMPUTER_FILE_SEARCH_MAX_QUERY_LENGTH = exports.COMPUTER_FILE_SEARCH_MAX_BUDGET_MS = exports.COMPUTER_FILE_SEARCH_DEFAULT_BUDGET_MS = exports.COMPUTER_FILE_SEARCH_MAX_LIMIT = exports.COMPUTER_FILE_SEARCH_DEFAULT_LIMIT = exports.COMPUTER_FILE_GIT_MARKS = exports.COMPUTER_FILE_SEARCH_STATES = exports.COMPUTER_FILE_SEARCH_OPERATIONS = exports.COMPUTER_FILE_OPERATIONS = exports.COMPUTER_BROWSER_URL_MAX_LENGTH = exports.COMPUTER_BROWSER_TAB_ACTIONS = exports.COMPUTER_BROWSER_HISTORY_ACTIONS = exports.COMPUTER_BROWSER_COPY_MAX_LENGTH = exports.COMPUTER_TERMINAL_CWD_MAX_LENGTH = exports.COMPUTER_BROWSER_FILE_CHOOSER_PATH_MAX_LENGTH = exports.COMPUTER_BROWSER_FILE_CHOOSER_ACCEPT_MAX_LENGTH = exports.COMPUTER_BROWSER_FILE_CHOOSER_MAX_FILES = exports.COMPUTER_BROWSER_FILE_CHOOSER_OUTCOMES = exports.COMPUTER_BROWSER_FILE_CHOOSER_MODES = exports.COMPUTER_BROWSER_DOWNLOADS_MAX = exports.COMPUTER_BROWSER_DOWNLOAD_STATES = exports.COMPUTER_BROWSER_DIALOG_CREDENTIAL_MAX_LENGTH = exports.COMPUTER_BROWSER_DIALOG_PROMPT_MAX_LENGTH = exports.COMPUTER_BROWSER_DIALOG_MESSAGE_MAX_LENGTH = exports.COMPUTER_BROWSER_DIALOG_ANSWERERS = exports.COMPUTER_BROWSER_DIALOG_OUTCOMES = void 0;
30
+ exports.COMPUTER_LEASE_STATE_POWER_PHASE = exports.COMPUTER_START_REFUSALS = exports.COMPUTER_PERSONAL_SIGN_IN_REASONS = exports.COMPUTER_PERSONAL_POWER_PHASES = exports.COMPUTER_POWER_PHASES = exports.COMPUTER_MACHINE_KINDS = exports.COMPUTER_DECLARED_SERVICE_PROBLEMS = exports.COMPUTER_DECLARED_SERVICE_NAME_PATTERN = exports.COMPUTER_DECLARED_SERVICE_HOST = exports.COMPUTER_DECLARED_SERVICE_PATH_MAX_LENGTH = exports.COMPUTER_DECLARED_SERVICE_NAME_MAX_LENGTH = void 0;
31
31
  exports.normalizeComputerRefusalCode = normalizeComputerRefusalCode;
32
32
  exports.isComputerPlatformRouteSession = isComputerPlatformRouteSession;
33
33
  exports.computerTierOperations = computerTierOperations;
@@ -350,6 +350,28 @@ exports.COMPUTER_CAPABILITIES = {
350
350
  * before, and never sends the input.
351
351
  */
352
352
  browserViewport: 'computer_browser_viewport_v1',
353
+ /**
354
+ * A browser surface whose browser is still starting says so (OSK-13824): its
355
+ * descriptor carries `starting: { since }` from the moment its Chromium
356
+ * begins to launch until that launch ends (running, or failed), and the
357
+ * window shows "Starting the browser…" instead of subscribing into a
358
+ * refusal. A cold start on a fresh guest takes 25–30 s. A daemon announcing
359
+ * this states the ABSENCE of `starting` too: a browser surface without it
360
+ * is not starting. Without the announcement a client cannot tell, and falls
361
+ * back to reading a surface it saw open moments ago as starting.
362
+ */
363
+ browserStarting: 'computer_browser_starting_v1',
364
+ /**
365
+ * An agent actor on the machine's activity and control leases (OSK-13827)
366
+ * names the REAL agent: `agentId` is the agent's own id for tool-call and
367
+ * browser sessions too (v1 daemons put the SESSION id there for tool calls,
368
+ * so no reader could ever name them), and may carry `engine`, the harness
369
+ * the session runs on, so one agent's sessions on different engines can be
370
+ * told apart. A daemon announcing this states what it sends; the server
371
+ * withholds `engine` from every other daemon's actors, and a reader treats
372
+ * an actor without it as it always did.
373
+ */
374
+ activityActorV2: 'computer_activity_actor_v2',
353
375
  };
354
376
  exports.COMPUTER_CAPABILITY_VALUES = Object.values(exports.COMPUTER_CAPABILITIES);
355
377
  // ---------------------------------------------------------------------------
@@ -453,6 +475,8 @@ function isComputerPlatformRouteSession(sessionId) {
453
475
  return (typeof sessionId === 'string' &&
454
476
  exports.COMPUTER_PLATFORM_ROUTE_SESSION_PREFIXES.some((prefix) => sessionId.startsWith(prefix)));
455
477
  }
478
+ /** The longest `engine` the wire carries; a longer one is not an engine name. */
479
+ exports.COMPUTER_ACTOR_ENGINE_MAX_LENGTH = 64;
456
480
  /**
457
481
  * How a person present on the machine reached it: as its owner, or through a
458
482
  * grant at one of the two tiers. Agents carry no access level — an agent
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The Dedicated Runtime busy-report wire contract (power-control task K2,
3
+ * `dedicated-runtime-power-and-demand-wake-2026-10-07.md` §7.3 / §13.5).
4
+ *
5
+ * Declared ONCE, here. The daemon is published and cannot import the API, so
6
+ * the capability name, the payload shape and its bounds live in this package,
7
+ * which every end can import (`@skrr-ai/auth-core/dedicated-runtime-busy-wire`).
8
+ * `dedicatedRuntimeBusyWire.test.ts` fails on a second spelling of the
9
+ * capability anywhere else in the repo.
10
+ *
11
+ * WHAT THE REPORT SAYS
12
+ * ---------------------------------------------------------------------------
13
+ * "Does anything run in the tenant workload slice?" — read by the guest daemon
14
+ * from cgroup v2 `skrr-workload.slice`, recursively (the slice root is empty by
15
+ * contract; processes live in leaves), with the root-owned sshd listener left
16
+ * out. It is a LOCAL read summarised into counts; no per-process list ever
17
+ * crosses the wire.
18
+ *
19
+ * FAIL CLOSED. `busy` is `true | false | 'unknown'`. Any failure to read — a
20
+ * missing slice, an unreadable file, a tree too large to walk, a quiesce in
21
+ * progress, an sshd that is not where the image promises — is `'unknown'`, and
22
+ * a reader MUST treat `'unknown'` as busy. `false` is only ever the daemon
23
+ * having positively read an empty slice.
24
+ *
25
+ * THE POLICY KNOB (OPEN PRODUCT QUESTION — not decided here)
26
+ * ---------------------------------------------------------------------------
27
+ * An idle tmux server or a shell prompt keeps a leaf populated forever. The
28
+ * owner decision is that tmux/SSH shells ARE protected, but whether an IDLE
29
+ * shell should pin the machine is undecided. So the report carries both
30
+ * readings and the server chooses:
31
+ *
32
+ * - `busy` the STRICT reading (`policy: 'strict'`): every
33
+ * process in the slice counts. This is the
34
+ * default and the only one a server should act
35
+ * on until the product question is answered.
36
+ * - `busyIgnoringIdleShells` the same read with the `idleShell` bucket
37
+ * removed. Classification is by process name
38
+ * (a shell or tmux with nothing else running),
39
+ * a HEURISTIC: it cannot see a shell loop, so
40
+ * it must never be the default.
41
+ *
42
+ * The `breakdown` says which bucket made it busy, for the "why" text.
43
+ */
44
+ export declare const DEDICATED_RUNTIME_BUSY_REPORT_CAPABILITY = "dedicated_runtime_busy_report_v1";
45
+ /** The heartbeat key the report rides under. Sent only by a daemon that announced the capability. */
46
+ export declare const DEDICATED_RUNTIME_BUSY_HEARTBEAT_KEY = "dedicatedRuntimeBusy";
47
+ export declare const DEDICATED_RUNTIME_BUSY_REPORT_VERSION = 1;
48
+ /**
49
+ * `strict`: every process in the slice (less the root sshd listener) counts.
50
+ * The only value v1 sends; the field exists so a later relaxation is a new
51
+ * value rather than a silent change of meaning.
52
+ */
53
+ export declare const DEDICATED_RUNTIME_BUSY_POLICIES: readonly ["strict"];
54
+ export type DedicatedRuntimeBusyPolicy = (typeof DEDICATED_RUNTIME_BUSY_POLICIES)[number];
55
+ /**
56
+ * Why the daemon could not give a definite answer, or `ok`. Anything but `ok`
57
+ * pairs with `busy: 'unknown'`.
58
+ */
59
+ export declare const DEDICATED_RUNTIME_BUSY_SLICE_HEALTH: readonly ["ok", "not_a_guest", "slice_missing", "slice_unreadable", "ssh_not_in_slice", "tree_too_large", "quiesced", "read_error"];
60
+ export type DedicatedRuntimeBusySliceHealth = (typeof DEDICATED_RUNTIME_BUSY_SLICE_HEALTH)[number];
61
+ export declare const DEDICATED_RUNTIME_BUSY_LIMITS: {
62
+ /** Service leaf names carried for the "busy because of service X" text. */
63
+ readonly maxServiceNames: 8;
64
+ readonly maxServiceNameLength: 48;
65
+ /** Cgroup leaves the daemon will visit; more is `tree_too_large` -> unknown. */
66
+ readonly maxLeaves: 64;
67
+ readonly maxDepth: 4;
68
+ /** Processes the daemon classifies per read; the rest count as workload. */
69
+ readonly maxClassifiedPids: 256;
70
+ /** Hard ceiling on the serialized report. */
71
+ readonly maxSerializedBytes: 1024;
72
+ };
73
+ export interface DedicatedRuntimeBusyBreakdown {
74
+ /** Processes in non-service, non-ssh leaves (the `default` leaf, the slice root) that are not idle-shell class. */
75
+ workload: number;
76
+ /** Processes in `skrr-svc-*.scope` leaves: a declared service running, always-on by design. */
77
+ service: number;
78
+ /** SSH connection plumbing in `ssh.service` that is not the root listener (non-root sshd, the session helper) and other non-shell processes there. */
79
+ ssh: number;
80
+ /** Shell or tmux processes in `default` / `ssh.service` (heuristic by name; see header). */
81
+ idleShell: number;
82
+ /** Root-owned sshd processes left out of every count above. */
83
+ excludedSshd: number;
84
+ /** True when more processes existed than were classified; the rest are in `workload`. */
85
+ truncated: boolean;
86
+ /** Up to `maxServiceNames` service leaf names that have processes. */
87
+ services: string[];
88
+ }
89
+ export interface DedicatedRuntimeBusyReport {
90
+ version: typeof DEDICATED_RUNTIME_BUSY_REPORT_VERSION;
91
+ policy: DedicatedRuntimeBusyPolicy;
92
+ /** Strict reading. `'unknown'` MUST be treated as busy. */
93
+ busy: boolean | 'unknown';
94
+ /** Same read with the `idleShell` bucket removed; a heuristic, not the default. */
95
+ busyIgnoringIdleShells: boolean | 'unknown';
96
+ breakdown: DedicatedRuntimeBusyBreakdown;
97
+ /** ISO time the daemon read the slice. */
98
+ readAt: string;
99
+ sliceHealth: DedicatedRuntimeBusySliceHealth;
100
+ }
101
+ /**
102
+ * The reader's view of a daemon's heartbeat section. Anything malformed, from
103
+ * a daemon that did not announce the capability, or oversized is `null`, and
104
+ * the caller must then treat the machine as busy. Never trusts a field it did
105
+ * not validate; a value is clamped, not coerced to "idle".
106
+ */
107
+ export declare function parseDedicatedRuntimeBusyReport(raw: unknown): DedicatedRuntimeBusyReport | null;