@skrr-ai/cli 0.1.88 → 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 (59) hide show
  1. package/dist/commands/machines/computer-adoption.d.ts +3 -2
  2. package/dist/commands/machines/computer-adoption.js +4 -3
  3. package/dist/commands/machines/dedicated/agent-browser.d.ts +25 -0
  4. package/dist/commands/machines/dedicated/agent-browser.js +65 -0
  5. package/dist/commands/machines/dedicated/exec.js +60 -14
  6. package/dist/commands/machines/dedicated/index.js +2 -0
  7. package/dist/commands/machines/dedicated/power/explain.d.ts +13 -0
  8. package/dist/commands/machines/dedicated/power/explain.js +36 -0
  9. package/dist/commands/machines/dedicated/power/index.d.ts +10 -0
  10. package/dist/commands/machines/dedicated/power/index.js +24 -0
  11. package/dist/commands/machines/dedicated/power/keep-awake.d.ts +20 -0
  12. package/dist/commands/machines/dedicated/power/keep-awake.js +69 -0
  13. package/dist/commands/machines/dedicated/power/set.d.ts +23 -0
  14. package/dist/commands/machines/dedicated/power/set.js +106 -0
  15. package/dist/commands/machines/dedicated/power/show.d.ts +13 -0
  16. package/dist/commands/machines/dedicated/power/show.js +36 -0
  17. package/dist/commands/machines/dedicated/show.js +17 -2
  18. package/dist/lib/commitments.d.ts +1 -1
  19. package/dist/lib/commitments.js +4 -2
  20. package/dist/lib/computer-adoption.d.ts +5 -0
  21. package/dist/lib/computer-adoption.js +6 -1
  22. package/dist/lib/computer-consent.d.ts +43 -1
  23. package/dist/lib/computer-consent.js +125 -4
  24. package/dist/lib/dedicated-keep-awake.d.ts +19 -0
  25. package/dist/lib/dedicated-keep-awake.js +64 -0
  26. package/dist/lib/dedicated-machines.d.ts +31 -0
  27. package/dist/lib/dedicated-machines.js +40 -0
  28. package/dist/lib/dedicated-power.d.ts +61 -0
  29. package/dist/lib/dedicated-power.js +243 -0
  30. package/dist/lib/exec-runtime-binary.d.ts +3 -1
  31. package/dist/lib/exec-runtime-binary.js +16 -2
  32. package/dist/lib/format.d.ts +13 -0
  33. package/dist/lib/format.js +51 -0
  34. package/dist/lib/keychain.js +3 -0
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +135 -8
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +166 -16
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeBusyWire.d.ts +107 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeBusyWire.js +151 -0
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeWakeWire.d.ts +115 -0
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeWakeWire.js +117 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +8 -3
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +31 -7
  44. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +149 -9
  45. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +135 -8
  46. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +161 -13
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeBusyWire.d.ts +107 -0
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeBusyWire.js +147 -0
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeWakeWire.d.ts +115 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeWakeWire.js +113 -0
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +31 -7
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +144 -8
  55. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  56. package/dist/node_modules/@skrr-ai/data-provider/index.js +6730 -6366
  57. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  58. package/oclif.manifest.json +6303 -5852
  59. package/package.json +2 -2
@@ -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
+ }
@@ -86,7 +86,9 @@ export type RuntimeBinaryOutput = {
86
86
  * post-condition probes, where output is evidence rather than user-facing
87
87
  * command output.
88
88
  */
89
- export declare function execRuntimeBinaryCapture(args: string[]): Promise<RuntimeBinaryOutput>;
89
+ export declare function execRuntimeBinaryCapture(args: string[], opts?: {
90
+ timeoutMs?: number;
91
+ }): Promise<RuntimeBinaryOutput>;
90
92
  /**
91
93
  * Check an installed daemon's top-level help before forwarding a command that
92
94
  * was added after older daemon releases shipped. A false result is useful to
@@ -251,7 +251,7 @@ function execRuntimeBinary(args, opts = {}) {
251
251
  * post-condition probes, where output is evidence rather than user-facing
252
252
  * command output.
253
253
  */
254
- function execRuntimeBinaryCapture(args) {
254
+ function execRuntimeBinaryCapture(args, opts = {}) {
255
255
  return new Promise((resolve, reject) => {
256
256
  const bin = findRuntimeBinary();
257
257
  if (!bin) {
@@ -279,8 +279,22 @@ function execRuntimeBinaryCapture(args) {
279
279
  child.stderr?.on('data', (chunk) => {
280
280
  stderr = append(stderr, chunk);
281
281
  });
282
- child.on('error', reject);
282
+ // A probe is a read: past its bound it is abandoned (and the child
283
+ // stopped) rather than allowed to stall the command that asked.
284
+ const timer = opts.timeoutMs && opts.timeoutMs > 0
285
+ ? setTimeout(() => {
286
+ child.kill('SIGTERM');
287
+ reject(new Error('runtime-binary-probe-timeout'));
288
+ }, opts.timeoutMs)
289
+ : null;
290
+ child.on('error', (error) => {
291
+ if (timer)
292
+ clearTimeout(timer);
293
+ reject(error);
294
+ });
283
295
  child.on('exit', (code, signal) => {
296
+ if (timer)
297
+ clearTimeout(timer);
284
298
  resolve({
285
299
  code: signal ? 128 + signalNumber(signal) : (code ?? 0),
286
300
  stdout,
@@ -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.
@@ -33,6 +33,22 @@ export declare const COMPUTER_EVENTS: {
33
33
  readonly attach: "computer:attach";
34
34
  /** client → API: leave. */
35
35
  readonly detach: "computer:detach";
36
+ /**
37
+ * client → API: read the whole manifest again ON THE SAME ATTACHMENT, after
38
+ * a missed delta or a `stale_epoch` (OSK-13815). The ack carries what an
39
+ * attach's does — the attachment id (unchanged), the manifest and the
40
+ * operations — or a typed refusal; `not_found` means the attachment is gone
41
+ * and only then does the client attach afresh. Re-reading state must never
42
+ * change who the reader is: a control lease is bound to the attachment
43
+ * (`holderAttachmentId`), and a resync that detached and attached again
44
+ * orphaned the hold the person had just taken. Answered by the API, which
45
+ * renews the binding with the daemon exactly as its periodic re-check does
46
+ * (a repeated `daemon:computer:attach` with the same id), so it has no
47
+ * daemon mirror and needs no daemon capability. An API that predates it
48
+ * never answers; the client bounds the wait and falls back to attaching
49
+ * afresh.
50
+ */
51
+ readonly resync: "computer:resync";
36
52
  /** API → client: this attachment was detached, with the reason (revoked, stale generation, …). */
37
53
  readonly detached: "computer:detached";
38
54
  /** API → client: a whole manifest, or a revisioned delta. */
@@ -162,6 +178,22 @@ export declare const DAEMON_COMPUTER_EVENT_NAMES: readonly DaemonComputerEventNa
162
178
  export declare const DAEMON_COMPUTER_REPORT_EVENT_KEYS: readonly ["ack", "manifest", "data", "activity", "detached", "filesChanged", "diagnostics"];
163
179
  /** The API → client events; every other `computer:*` event flows client → API. */
164
180
  export declare const COMPUTER_SERVER_EVENT_KEYS: readonly ["detached", "manifest", "data", "activity", "filesChanged"];
181
+ /**
182
+ * Client → API requests the API answers itself, with no `daemon:computer:*`
183
+ * mirror: every other client request is relayed to the daemon under the same
184
+ * key. `resync` renews the binding through the daemon's existing attach
185
+ * (OSK-13815).
186
+ */
187
+ export declare const COMPUTER_API_ANSWERED_EVENT_KEYS: readonly ["resync"];
188
+ /** How long the client waits for a `computer:resync` answer before attaching afresh (an API that predates it never answers). */
189
+ export declare const COMPUTER_RESYNC_TIMEOUT_MS = 12000;
190
+ /**
191
+ * How long a client holds a manifest delta that arrived AHEAD of its
192
+ * predecessor before reading the hole as a loss (OSK-13815). Deltas fan out
193
+ * through a relay of several replicas; a reorder is milliseconds, a loss is
194
+ * forever, and only a loss needs the whole manifest again.
195
+ */
196
+ export declare const COMPUTER_MANIFEST_REORDER_WAIT_MS = 300;
165
197
  /**
166
198
  * One capability per wire addition, each withheld positively at dispatch: the
167
199
  * API never sends a computer event to a daemon that did not announce the
@@ -263,6 +295,42 @@ export declare const COMPUTER_CAPABILITIES: {
263
295
  * browser; nothing is proxied to it. Announced by the relay that serves it.
264
296
  */
265
297
  readonly declaredServices: "computer_declared_services_v1";
298
+ /**
299
+ * The page follows the controlling window (§6.2 "a stable viewport", §7.1,
300
+ * OSK-13808): a browser `viewport` input, sent only by the attachment that
301
+ * holds the browser's lease at the current epoch, sizes the streamed page
302
+ * to that window's content box (`COMPUTER_BROWSER_VIEWPORT_LIMITS`). Only
303
+ * while a person holds control: handing back — or the hold lapsing, being
304
+ * revoked or moving to the agent any other way — restores the browser's own
305
+ * viewport before the agent can act, so an agent never inherits a person's
306
+ * size. Watchers keep seeing the page at whatever size it is, scaled to
307
+ * fit. Announced only where the browser driver can override and restore
308
+ * the page's metrics; without it the window scales the fixed page, as
309
+ * before, and never sends the input.
310
+ */
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";
266
334
  };
267
335
  export type ComputerCapabilityKey = keyof typeof COMPUTER_CAPABILITIES;
268
336
  export type ComputerCapability = (typeof COMPUTER_CAPABILITIES)[ComputerCapabilityKey];
@@ -271,14 +339,13 @@ export declare const COMPUTER_CAPABILITY_VALUES: [ComputerCapability, ...Compute
271
339
  * Every refusal and limit the computer protocol can answer with. Callers branch
272
340
  * on the code; a message beside it is free text and may change.
273
341
  */
274
- export declare const COMPUTER_REFUSAL_CODES: readonly ["human_control", "stale_epoch", "stale_generation", "file_changed", "capability_missing", "unauthorized", "control_denied", "not_holder", "attachment_limit", "observer_limit", "watch_limit", "grant_limit", "browser_limit", "rate_limited", "not_found", "surface_ended", "machine_unavailable", "daemon_unreachable", "timeout", "invalid_request", "consent_missing", "path_withheld", "too_large", "service_limit"];
342
+ export declare const COMPUTER_REFUSAL_CODES: readonly ["human_control", "stale_epoch", "stale_generation", "file_changed", "capability_missing", "unauthorized", "control_denied", "not_holder", "attachment_limit", "observer_limit", "watch_limit", "grant_limit", "browser_limit", "rate_limited", "not_found", "surface_ended", "machine_unavailable", "daemon_unreachable", "relay_error", "timeout", "invalid_request", "consent_missing", "path_withheld", "too_large", "service_limit"];
275
343
  export type ComputerRefusalCode = (typeof COMPUTER_REFUSAL_CODES)[number];
276
344
  /**
277
- * Older spellings the daemon boundary accepts for one release (§5.2). After
278
- * that release this map is emptied, and nothing else names the old spelling.
345
+ * The code when it is a computer refusal, else `null`. There is one spelling
346
+ * per code: the browser's older spelling of `human_control` was accepted for
347
+ * one release and retired with the old browser events (§5.2, OSK-13509).
279
348
  */
280
- export declare const COMPUTER_LEGACY_REFUSAL_SPELLINGS: Readonly<Record<string, ComputerRefusalCode>>;
281
- /** The canonical code for a received spelling, or `null` when it is not a computer refusal. */
282
349
  export declare function normalizeComputerRefusalCode(code: unknown): ComputerRefusalCode | null;
283
350
  export declare const COMPUTER_ACTOR_KINDS: readonly ["agent", "human"];
284
351
  export type ComputerActorKind = (typeof COMPUTER_ACTOR_KINDS)[number];
@@ -291,13 +358,36 @@ export type ComputerActorKind = (typeof COMPUTER_ACTOR_KINDS)[number];
291
358
  */
292
359
  export declare const COMPUTER_ACTIVITY_ACTOR_KINDS: readonly ["agent", "human", "system"];
293
360
  export type ComputerActivityActorKind = (typeof COMPUTER_ACTIVITY_ACTOR_KINDS)[number];
361
+ /**
362
+ * The session ids the PLATFORM's own routes run daemon tools under — the web
363
+ * Files surface (`computer-files:` on a personal machine, `dedicated-runtime:`
364
+ * on a Dedicated Runtime) and the owner's `skrr machines dedicated` commands.
365
+ * No agent and no model is behind such a session: the person who asked is
366
+ * known to the API (which audits it), not to the daemon. So an activity event
367
+ * produced under one names no agent (OSK-13790: a person's upload used to read
368
+ * "agent created …"). Built by `ComputerFileService.runPersonalMachineTool`
369
+ * and `DedicatedRuntimeMachineAccess.runDedicatedRuntimeTool`.
370
+ */
371
+ export declare const COMPUTER_PLATFORM_ROUTE_SESSION_PREFIXES: readonly ["dedicated-runtime:", "computer-files:"];
372
+ /** Whether a tool call's session is one of the platform's own routes, not an agent's. */
373
+ export declare function isComputerPlatformRouteSession(sessionId: unknown): boolean;
294
374
  /** An agent session, as the wire names it. Same session = same `agentId` AND `sessionId`. */
295
375
  export interface ComputerWireAgentActor {
296
376
  kind: 'agent';
297
377
  agentId: string;
298
378
  sessionId: string;
299
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;
300
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;
301
391
  export interface ComputerWireHumanActor {
302
392
  kind: 'human';
303
393
  userId: string;
@@ -420,7 +510,11 @@ export type ComputerSurfaceState = (typeof COMPUTER_SURFACE_STATES)[number];
420
510
  */
421
511
  export declare const COMPUTER_SURFACE_END_REASONS: readonly ["machine_stopped", "closed_by_owner", "closed", "process_exited", "daemon_restarted", "browser_crashed", "expired", "browser_reset"];
422
512
  export type ComputerSurfaceEndReason = (typeof COMPUTER_SURFACE_END_REASONS)[number];
423
- /** The browser's fixed logical viewport, whoever is looking (§6.2). */
513
+ /**
514
+ * The browser's logical viewport whoever is looking (§6.2): what the agent
515
+ * always works at. A person holding control may size the page to their window
516
+ * (`computer_browser_viewport_v1`); handing back restores this.
517
+ */
424
518
  export declare const COMPUTER_BROWSER_VIEWPORT: Readonly<{
425
519
  width: 1280;
426
520
  height: 800;
@@ -492,8 +586,41 @@ export interface ComputerTerminalInfo {
492
586
  };
493
587
  caveats?: ComputerTerminalCaveat[];
494
588
  }
495
- /** Input a browser surface takes. `tab` needs `computer_browser_tabs_v1`. */
496
- export declare const COMPUTER_BROWSER_INPUT_TYPES: readonly ["pointer", "key", "text", "navigate", "history", "tab"];
589
+ /**
590
+ * Input a browser surface takes. `tab` needs `computer_browser_tabs_v1`;
591
+ * `viewport` needs `computer_browser_viewport_v1`.
592
+ */
593
+ export declare const COMPUTER_BROWSER_INPUT_TYPES: readonly ["pointer", "key", "text", "navigate", "history", "tab", "viewport"];
594
+ /**
595
+ * What a `viewport` input may ask of the page (OSK-13808), in CSS pixels: the
596
+ * controlling window's content box, bounded so no window can make the page
597
+ * absurd for the agent that gets it back, and aspect-clamped (`minAspect` and
598
+ * `maxAspect` are width ÷ height) so a sliver of a window cannot either.
599
+ * Device pixel ratio is not part of it: the page keeps the browser's own, so a
600
+ * high-density screen changes no layout and no picture size.
601
+ */
602
+ export declare const COMPUTER_BROWSER_VIEWPORT_LIMITS: Readonly<{
603
+ minWidth: 320;
604
+ maxWidth: 2560;
605
+ minHeight: 240;
606
+ maxHeight: 1600;
607
+ minAspect: 0.4;
608
+ maxAspect: 3.2;
609
+ }>;
610
+ /**
611
+ * The size a `viewport` input asks for, made legal: whole CSS pixels inside
612
+ * `COMPUTER_BROWSER_VIEWPORT_LIMITS`, its aspect inside the clamp. Total and
613
+ * idempotent: the window sends what this returns and the daemon applies what
614
+ * this returns, so both ends agree on the page a size produces. Null for a size
615
+ * with no area (a hidden or collapsed window asks for nothing).
616
+ */
617
+ export declare function clampComputerBrowserViewport(size: {
618
+ width: number;
619
+ height: number;
620
+ }): {
621
+ width: number;
622
+ height: number;
623
+ } | null;
497
624
  /**
498
625
  * The client OS a browser `key` input comes from. A person's shortcuts are
499
626
  * mapped onto the Linux page's by the daemon: on `mac`, Command is Control,