@skrr-ai/cli 0.1.91 → 0.1.92
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.
- package/dist/commands/machines/dedicated/power/index.js +3 -2
- package/dist/commands/machines/dedicated/power/waiting.d.ts +13 -0
- package/dist/commands/machines/dedicated/power/waiting.js +36 -0
- package/dist/commands/tasks/chat/send.d.ts +44 -0
- package/dist/commands/tasks/chat/send.js +112 -0
- package/dist/lib/dedicated-power.d.ts +11 -1
- package/dist/lib/dedicated-power.js +123 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4 -0
- package/oclif.manifest.json +35980 -35825
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ const core_1 = require("@oclif/core");
|
|
|
6
6
|
* on its own. `set` is the standing policy; more verbs arrive with the power card.
|
|
7
7
|
*/
|
|
8
8
|
class DedicatedMachinesPower extends core_1.Command {
|
|
9
|
-
static description = 'Power policy of a Dedicated Runtime — when it may stop and start on its own. Files are preserved across stops.\n\nSubcommands:\n show state, policy, holders, usage, next wake\n explain why it stayed on, stopped or woke, and the fix\n set always_on | on_demand | manual (+ --idle-after, --budget-hours)\n keep-awake --for <duration> | --until <iso> | --clear';
|
|
9
|
+
static description = 'Power policy of a Dedicated Runtime — when it may stop and start on its own. Files are preserved across stops.\n\nSubcommands:\n show state, policy, holders, usage, next wake\n explain why it stayed on, stopped or woke, and the fix\n waiting work waiting for the machine\n set always_on | on_demand | manual (+ --idle-after, --budget-hours/--no-budget)\n keep-awake --for <duration> | --until <iso> | --clear';
|
|
10
10
|
static examples = [
|
|
11
11
|
'<%= config.bin %> machines dedicated power set <lease-id> --policy manual',
|
|
12
12
|
'<%= config.bin %> machines dedicated power set <lease-id> --policy on_demand --idle-after 60',
|
|
@@ -15,7 +15,8 @@ class DedicatedMachinesPower extends core_1.Command {
|
|
|
15
15
|
this.log('skrr machines dedicated power commands:');
|
|
16
16
|
this.log(' skrr machines dedicated power show state, policy, holders, usage, next wake');
|
|
17
17
|
this.log(' skrr machines dedicated power explain why it stayed on, stopped or woke, and the fix');
|
|
18
|
-
this.log(' skrr machines dedicated power
|
|
18
|
+
this.log(' skrr machines dedicated power waiting work waiting for the machine');
|
|
19
|
+
this.log(' skrr machines dedicated power set always_on | on_demand | manual (+ --idle-after, --budget-hours/--no-budget)');
|
|
19
20
|
this.log(' skrr machines dedicated power keep-awake --for <duration> | --until <iso> | --clear');
|
|
20
21
|
this.log('');
|
|
21
22
|
this.log('Every command takes a lease id (or `self` on the machine) and --json.');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DedicatedLeaseCommand } from '../../../../lib/dedicated-lease-command';
|
|
2
|
+
/** `skrr machines dedicated power waiting` — what is waiting for this machine. Read-only. */
|
|
3
|
+
export default class DedicatedMachinesPowerWaiting extends DedicatedLeaseCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static args: {
|
|
7
|
+
lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const dedicated_lease_command_1 = require("../../../../lib/dedicated-lease-command");
|
|
5
|
+
const dedicated_power_1 = require("../../../../lib/dedicated-power");
|
|
6
|
+
/** `skrr machines dedicated power waiting` — what is waiting for this machine. Read-only. */
|
|
7
|
+
class DedicatedMachinesPowerWaiting extends dedicated_lease_command_1.DedicatedLeaseCommand {
|
|
8
|
+
static description = 'List work waiting for a Dedicated Runtime: parked trigger fires and runs, queued runs, and work just aimed at it. Derived from the schedulers own records. A source that cannot be read is said, not read as nothing waiting. Read-only.';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> machines dedicated power waiting <lease-id>',
|
|
11
|
+
'<%= config.bin %> machines dedicated power waiting self --json',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
lease: core_1.Args.string({
|
|
15
|
+
description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
|
|
16
|
+
required: true,
|
|
17
|
+
ignoreStdin: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
22
|
+
};
|
|
23
|
+
async run() {
|
|
24
|
+
this.requireAuth();
|
|
25
|
+
const { args, flags } = await this.parse(DedicatedMachinesPowerWaiting);
|
|
26
|
+
const leaseId = await this.resolveLeaseArg(args.lease);
|
|
27
|
+
const waiting = await this.callDedicated(() => (0, dedicated_power_1.getDedicatedWaitingWork)(leaseId, this.scopeFrom(flags)));
|
|
28
|
+
if (flags.json) {
|
|
29
|
+
this.log(JSON.stringify(waiting, null, 2));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const line of (0, dedicated_power_1.describeWaitingWork)(waiting))
|
|
33
|
+
this.log(line);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.default = DedicatedMachinesPowerWaiting;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base-command';
|
|
2
|
+
export interface TaskChatSendInput {
|
|
3
|
+
text: string;
|
|
4
|
+
commandId?: string;
|
|
5
|
+
now?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Build the `POST /api/tasks/:id/exchanges/messages` body.
|
|
9
|
+
*
|
|
10
|
+
* Every mutation on the task-chat surface requires an actor-scoped `commandId`;
|
|
11
|
+
* the server refuses to mint one (a server-minted id cannot make the caller's
|
|
12
|
+
* own retry idempotent). Without `--command-id` each invocation gets a FRESH id,
|
|
13
|
+
* because two sends of identical text are two intents (the unfenced-write rule
|
|
14
|
+
* in `idempotency.ts`). Pass the same `--command-id` to retry safely.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildTaskChatSendBody(input: TaskChatSendInput): {
|
|
17
|
+
immediate?: boolean | undefined;
|
|
18
|
+
commandId: string;
|
|
19
|
+
text: string;
|
|
20
|
+
};
|
|
21
|
+
/** One human line for a send result; never claims dispatch the server did not report. */
|
|
22
|
+
export declare function describeSendResult(result: Record<string, unknown>): string;
|
|
23
|
+
/**
|
|
24
|
+
* `skrr tasks chat send <task-id> --text "..."` — post a message into a Task's
|
|
25
|
+
* chat (the Task Multi-Turn Conversation surface).
|
|
26
|
+
*
|
|
27
|
+
* By default the message joins the current batch and the agent answers when the
|
|
28
|
+
* batch closes (several messages, one response). `--now` closes the batch and
|
|
29
|
+
* dispatches in the same request.
|
|
30
|
+
*/
|
|
31
|
+
export default class TasksChatSend extends BaseCommand {
|
|
32
|
+
static description: string;
|
|
33
|
+
static examples: string[];
|
|
34
|
+
static args: {
|
|
35
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
36
|
+
};
|
|
37
|
+
static flags: {
|
|
38
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
39
|
+
text: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
40
|
+
now: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
41
|
+
'command-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
42
|
+
};
|
|
43
|
+
run(): Promise<void>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTaskChatSendBody = buildTaskChatSendBody;
|
|
4
|
+
exports.describeSendResult = describeSendResult;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const base_command_1 = require("../../../base-command");
|
|
8
|
+
const task_extras_1 = require("../../../lib/task-extras");
|
|
9
|
+
/** The server's `appendSchema` bounds on `commandId`. */
|
|
10
|
+
const COMMAND_ID_MIN = 8;
|
|
11
|
+
const COMMAND_ID_MAX = 128;
|
|
12
|
+
/**
|
|
13
|
+
* Build the `POST /api/tasks/:id/exchanges/messages` body.
|
|
14
|
+
*
|
|
15
|
+
* Every mutation on the task-chat surface requires an actor-scoped `commandId`;
|
|
16
|
+
* the server refuses to mint one (a server-minted id cannot make the caller's
|
|
17
|
+
* own retry idempotent). Without `--command-id` each invocation gets a FRESH id,
|
|
18
|
+
* because two sends of identical text are two intents (the unfenced-write rule
|
|
19
|
+
* in `idempotency.ts`). Pass the same `--command-id` to retry safely.
|
|
20
|
+
*/
|
|
21
|
+
function buildTaskChatSendBody(input) {
|
|
22
|
+
const text = input.text?.trim() ? input.text : '';
|
|
23
|
+
if (!text)
|
|
24
|
+
throw new Error('A message needs non-empty text (--text).');
|
|
25
|
+
const commandId = input.commandId ?? `cli-${(0, node_crypto_1.randomUUID)()}`;
|
|
26
|
+
if (commandId.length < COMMAND_ID_MIN) {
|
|
27
|
+
throw new Error(`--command-id must be at least ${COMMAND_ID_MIN} characters.`);
|
|
28
|
+
}
|
|
29
|
+
if (commandId.length > COMMAND_ID_MAX) {
|
|
30
|
+
throw new Error(`--command-id must be at most ${COMMAND_ID_MAX} characters.`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
commandId,
|
|
34
|
+
text,
|
|
35
|
+
...(input.now ? { immediate: true } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** One human line for a send result; never claims dispatch the server did not report. */
|
|
39
|
+
function describeSendResult(result) {
|
|
40
|
+
const exchangeId = String(result.exchangeId ?? '?');
|
|
41
|
+
const replay = result.replay === true ? ' (replay of an earlier send)' : '';
|
|
42
|
+
if (result.alreadyClosed === true) {
|
|
43
|
+
return `Exchange ${exchangeId} was already closed${replay}.`;
|
|
44
|
+
}
|
|
45
|
+
if (result.materialized === false) {
|
|
46
|
+
return `Message recorded in exchange ${exchangeId}${replay}, but no run was started: ${String(result.reason ?? 'unknown reason')}.`;
|
|
47
|
+
}
|
|
48
|
+
if (result.materialized === true) {
|
|
49
|
+
const run = result.taskExecutionRunId ? ` as run ${String(result.taskExecutionRunId)}` : '';
|
|
50
|
+
return `Message sent in exchange ${exchangeId}${replay}; dispatched${run}.`;
|
|
51
|
+
}
|
|
52
|
+
const close = result.softCloseAt ? ` Batch closes at ${String(result.softCloseAt)}.` : '';
|
|
53
|
+
return `Message recorded in exchange ${exchangeId}${replay}; the batch is still collecting, not yet dispatched.${close} Use --now to dispatch immediately.`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `skrr tasks chat send <task-id> --text "..."` — post a message into a Task's
|
|
57
|
+
* chat (the Task Multi-Turn Conversation surface).
|
|
58
|
+
*
|
|
59
|
+
* By default the message joins the current batch and the agent answers when the
|
|
60
|
+
* batch closes (several messages, one response). `--now` closes the batch and
|
|
61
|
+
* dispatches in the same request.
|
|
62
|
+
*/
|
|
63
|
+
class TasksChatSend extends base_command_1.BaseCommand {
|
|
64
|
+
static description = "Send a message into a task's chat";
|
|
65
|
+
static examples = [
|
|
66
|
+
'<%= config.bin %> tasks chat send task_abc123 --text "Also cover the retry path"',
|
|
67
|
+
'<%= config.bin %> tasks chat send OSK-42 --text "Go ahead" --now',
|
|
68
|
+
'<%= config.bin %> tasks chat send OSK-42 --text "Go ahead" --now --command-id my-retry-key-1',
|
|
69
|
+
];
|
|
70
|
+
static args = {
|
|
71
|
+
id: core_1.Args.string({ ignoreStdin: true, description: 'Task ID or identifier', required: true }),
|
|
72
|
+
};
|
|
73
|
+
static flags = {
|
|
74
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
75
|
+
text: core_1.Flags.string({ description: 'Message text', required: true }),
|
|
76
|
+
now: core_1.Flags.boolean({
|
|
77
|
+
description: 'Close the batch and dispatch immediately instead of waiting for it to close',
|
|
78
|
+
}),
|
|
79
|
+
'command-id': core_1.Flags.string({
|
|
80
|
+
description: `Actor-scoped command id (${COMMAND_ID_MIN}-${COMMAND_ID_MAX} chars). Reuse it to retry safely; generated fresh when omitted`,
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
async run() {
|
|
84
|
+
this.requireAuth();
|
|
85
|
+
const { args, flags } = await this.parse(TasksChatSend);
|
|
86
|
+
let body;
|
|
87
|
+
try {
|
|
88
|
+
body = buildTaskChatSendBody({
|
|
89
|
+
text: flags.text,
|
|
90
|
+
commandId: flags['command-id'],
|
|
91
|
+
now: flags.now,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
this.error(err.message, { exit: 2 });
|
|
96
|
+
}
|
|
97
|
+
const taskId = await this.resolveTaskReference(args.id);
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = await task_extras_1.taskChatApi.sendExchangeMessage(taskId, body);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
this.handleApiError(err);
|
|
104
|
+
}
|
|
105
|
+
if (flags.json) {
|
|
106
|
+
this.log(JSON.stringify({ commandId: body.commandId, ...result }, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.log(describeSendResult(result));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.default = TasksChatSend;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DedicatedRuntimePowerSummary, type DedicatedPowerPolicy, type DedicatedRuntimePowerPolicyState, type OnDemandIneligibleError, type SetDedicatedRuntimePowerPolicyResponse } from '@skrr-ai/data-provider';
|
|
1
|
+
import { type DedicatedRuntimePowerSummary, type DedicatedRuntimeWaitingWork, type DedicatedPowerPolicy, type DedicatedRuntimePowerPolicyState, type OnDemandIneligibleError, type SetDedicatedRuntimePowerPolicyResponse } from '@skrr-ai/data-provider';
|
|
2
2
|
import { type WhenOptions } from './format';
|
|
3
3
|
import { type DedicatedRuntimeScope } from './dedicated-machines';
|
|
4
4
|
/**
|
|
@@ -33,6 +33,16 @@ export declare function describePolicyWords(policy: DedicatedPowerPolicy, idleAf
|
|
|
33
33
|
export declare function describePowerPolicy(power: DedicatedRuntimePowerPolicyState, autoStopMode?: 'off' | 'canary' | 'on'): string;
|
|
34
34
|
/** `GET /api/machines/dedicated/:leaseId/power` — the read-only power card. */
|
|
35
35
|
export declare function getDedicatedPowerSummary(leaseId: string, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimePowerSummary>;
|
|
36
|
+
/** `GET /api/machines/dedicated/:leaseId/power/waiting` — work waiting for this machine. */
|
|
37
|
+
export declare function getDedicatedWaitingWork(leaseId: string, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimeWaitingWork>;
|
|
38
|
+
/** The lines of `power waiting`. An unreadable source is said, never folded into "nothing waiting". */
|
|
39
|
+
export declare function describeWaitingWork(waiting: DedicatedRuntimeWaitingWork, when?: WhenOptions): string[];
|
|
40
|
+
/** Last wake timing. A wake that never became assignable shows the phases it reached and no latency. */
|
|
41
|
+
export declare function describeLastWakeTiming(wake: DedicatedRuntimePowerSummary['lastWake']): string[];
|
|
42
|
+
/** on_demand availability, so a plan does not need a failed `power set` to find out. */
|
|
43
|
+
export declare function describeOnDemandAvailability(onDemand: DedicatedRuntimePowerSummary['onDemand']): string[];
|
|
44
|
+
/** Pre-warm lines: only a PAUSED pre-warm is in the summary, so silence here is not "pre-warm is on". */
|
|
45
|
+
export declare function describePrewarm(prewarm: DedicatedRuntimePowerSummary['prewarm'], when?: WhenOptions): string[];
|
|
36
46
|
type PowerDecisionView = NonNullable<DedicatedRuntimePowerSummary['decision']>;
|
|
37
47
|
/** The stay/stop/wake decision in words: the shared reason copy backs the server's detail. */
|
|
38
48
|
export declare function describePowerDecisionLines(decision: PowerDecisionView): {
|
|
@@ -4,6 +4,11 @@ exports.setDedicatedPowerPolicy = setDedicatedPowerPolicy;
|
|
|
4
4
|
exports.describePolicyWords = describePolicyWords;
|
|
5
5
|
exports.describePowerPolicy = describePowerPolicy;
|
|
6
6
|
exports.getDedicatedPowerSummary = getDedicatedPowerSummary;
|
|
7
|
+
exports.getDedicatedWaitingWork = getDedicatedWaitingWork;
|
|
8
|
+
exports.describeWaitingWork = describeWaitingWork;
|
|
9
|
+
exports.describeLastWakeTiming = describeLastWakeTiming;
|
|
10
|
+
exports.describeOnDemandAvailability = describeOnDemandAvailability;
|
|
11
|
+
exports.describePrewarm = describePrewarm;
|
|
7
12
|
exports.describePowerDecisionLines = describePowerDecisionLines;
|
|
8
13
|
exports.describeLastAutoStopLines = describeLastAutoStopLines;
|
|
9
14
|
exports.describeLastWakeDecisionLines = describeLastWakeDecisionLines;
|
|
@@ -83,6 +88,118 @@ function describePowerPolicy(power, autoStopMode) {
|
|
|
83
88
|
function getDedicatedPowerSummary(leaseId, scope = {}) {
|
|
84
89
|
return (0, api_fetch_1.apiFetch)((0, dedicated_machines_1.withDedicatedScope)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/power`, scope));
|
|
85
90
|
}
|
|
91
|
+
/** `GET /api/machines/dedicated/:leaseId/power/waiting` — work waiting for this machine. */
|
|
92
|
+
function getDedicatedWaitingWork(leaseId, scope = {}) {
|
|
93
|
+
return (0, api_fetch_1.apiFetch)((0, dedicated_machines_1.withDedicatedScope)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/power/waiting`, scope));
|
|
94
|
+
}
|
|
95
|
+
const WAITING_SOURCE_LABELS = {
|
|
96
|
+
dispatch_intent: 'just dispatched',
|
|
97
|
+
parked_fire: 'parked trigger fire',
|
|
98
|
+
parked_run: 'parked run',
|
|
99
|
+
queued_run: 'queued run',
|
|
100
|
+
agent_queued_run: 'queued run (agent)',
|
|
101
|
+
expected_firing: 'expected firing',
|
|
102
|
+
};
|
|
103
|
+
/** The lines of `power waiting`. An unreadable source is said, never folded into "nothing waiting". */
|
|
104
|
+
function describeWaitingWork(waiting, when = {}) {
|
|
105
|
+
const lines = [`Dedicated Runtime ${waiting.leaseId} (${waiting.state})`];
|
|
106
|
+
if (waiting.items.length === 0 && waiting.unreadable.length === 0) {
|
|
107
|
+
lines.push('Waiting work: none');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
lines.push(waiting.items.length === 0
|
|
111
|
+
? 'Waiting work: none seen'
|
|
112
|
+
: `Waiting work: ${waiting.items.length} item${waiting.items.length === 1 ? '' : 's'}`);
|
|
113
|
+
for (const item of waiting.items) {
|
|
114
|
+
const label = WAITING_SOURCE_LABELS[item.source] ?? item.source;
|
|
115
|
+
lines.push(` - ${label} ${item.ref}${item.since ? ` · since ${(0, format_1.formatWhen)(item.since, when)}` : ''}`);
|
|
116
|
+
if (item.detail)
|
|
117
|
+
lines.push(` ${item.detail}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (waiting.truncated.length > 0) {
|
|
121
|
+
lines.push(`More exists than is listed (${waiting.truncated.join(', ')}).`);
|
|
122
|
+
}
|
|
123
|
+
for (const u of waiting.unreadable) {
|
|
124
|
+
lines.push(`Cannot see ${u.source} (${u.reason}) — treat the list as incomplete.`);
|
|
125
|
+
}
|
|
126
|
+
const up = waiting.upcoming;
|
|
127
|
+
if (up) {
|
|
128
|
+
if (up.items.length > 0) {
|
|
129
|
+
lines.push(`Scheduled to fire: ${up.items.length}${up.truncated ? '+' : ''}`);
|
|
130
|
+
for (const f of up.items) {
|
|
131
|
+
lines.push(` - ${f.source} ${f.title ?? f.ref}${f.at ? ` · ${(0, format_1.formatWhen)(f.at, when)}` : ''}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (up.unreadable.length === 0) {
|
|
135
|
+
lines.push('Scheduled to fire: none');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
lines.push('Scheduled to fire: none seen');
|
|
139
|
+
}
|
|
140
|
+
if (up.overdue > 0) {
|
|
141
|
+
lines.push(`${up.overdue} overdue firing${up.overdue === 1 ? '' : 's'} not yet fired.`);
|
|
142
|
+
}
|
|
143
|
+
for (const u of up.unreadable) {
|
|
144
|
+
lines.push(`Cannot see ${u.source} (${u.reason}) — scheduled firings may be missing.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (waiting.state === 'stopped' && waiting.items.length > 0) {
|
|
148
|
+
lines.push('The machine is stopped. `skrr machines dedicated start <lease> --wait` starts it; work on an on_demand machine wakes it by itself.');
|
|
149
|
+
}
|
|
150
|
+
return lines;
|
|
151
|
+
}
|
|
152
|
+
const PHASE_ORDER = [
|
|
153
|
+
'start_requested',
|
|
154
|
+
'provider_start_accepted',
|
|
155
|
+
'instance_running',
|
|
156
|
+
'daemon_connected',
|
|
157
|
+
'first_heartbeat',
|
|
158
|
+
'assignable',
|
|
159
|
+
];
|
|
160
|
+
function formatMs(ms) {
|
|
161
|
+
return ms >= 1000 ? `${Math.round(ms / 100) / 10}s` : `${Math.round(ms)}ms`;
|
|
162
|
+
}
|
|
163
|
+
/** Last wake timing. A wake that never became assignable shows the phases it reached and no latency. */
|
|
164
|
+
function describeLastWakeTiming(wake) {
|
|
165
|
+
if (!wake)
|
|
166
|
+
return [];
|
|
167
|
+
const known = Object.keys(wake.phases ?? {});
|
|
168
|
+
const names = [
|
|
169
|
+
...PHASE_ORDER.filter((p) => known.includes(p)),
|
|
170
|
+
...known.filter((p) => !PHASE_ORDER.includes(p)),
|
|
171
|
+
].filter((p) => p !== 'start_requested');
|
|
172
|
+
const phases = names.map((p) => `${p} +${formatMs(wake.phases[p])}`).join(', ');
|
|
173
|
+
const total = wake.latencyMs === null || wake.latencyMs === undefined
|
|
174
|
+
? 'not ready yet (no latency recorded)'
|
|
175
|
+
: `ready after ${formatMs(wake.latencyMs)}`;
|
|
176
|
+
return [`Wake timing: ${total}${phases ? ` — ${phases}` : ''}`];
|
|
177
|
+
}
|
|
178
|
+
/** on_demand availability, so a plan does not need a failed `power set` to find out. */
|
|
179
|
+
function describeOnDemandAvailability(onDemand) {
|
|
180
|
+
if (!onDemand)
|
|
181
|
+
return [];
|
|
182
|
+
if (onDemand.eligible)
|
|
183
|
+
return ['on_demand: available'];
|
|
184
|
+
return [
|
|
185
|
+
`on_demand: not available (${onDemand.reason})`,
|
|
186
|
+
` ${onDemand.detail}`,
|
|
187
|
+
` Fix: ${onDemand.fix}`,
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
/** Pre-warm lines: only a PAUSED pre-warm is in the summary, so silence here is not "pre-warm is on". */
|
|
191
|
+
function describePrewarm(prewarm, when = {}) {
|
|
192
|
+
if (!prewarm?.paused)
|
|
193
|
+
return [];
|
|
194
|
+
const lines = [
|
|
195
|
+
`Pre-warm: paused${prewarm.since ? ` since ${(0, format_1.formatWhen)(prewarm.since, when)}` : ''} (${prewarm.idleStreak} early start${prewarm.idleStreak === 1 ? '' : 's'} in a row went unused)`,
|
|
196
|
+
];
|
|
197
|
+
if (prewarm.decision?.detail)
|
|
198
|
+
lines.push(` ${prewarm.decision.detail}`);
|
|
199
|
+
if (prewarm.decision?.fix)
|
|
200
|
+
lines.push(` ${prewarm.decision.fix}`);
|
|
201
|
+
return lines;
|
|
202
|
+
}
|
|
86
203
|
/** The stay/stop/wake decision in words: the shared reason copy backs the server's detail. */
|
|
87
204
|
function describePowerDecisionLines(decision) {
|
|
88
205
|
const copy = (0, data_provider_1.describePowerReason)(decision.reason);
|
|
@@ -205,6 +322,10 @@ function describePowerSummary(summary, when = {}) {
|
|
|
205
322
|
lines.push(`Last wake: ${summary.lastWake
|
|
206
323
|
? `${summary.lastWake.at ? (0, format_1.formatWhen)(summary.lastWake.at, when) : 'unknown time'}${summary.lastWake.source ? ` (${summary.lastWake.source})` : ''}`
|
|
207
324
|
: 'none'}`);
|
|
325
|
+
lines.push(...describeLastWakeTiming(summary.lastWake));
|
|
326
|
+
lines.push(...describePrewarm(summary.prewarm, when));
|
|
327
|
+
if (power.policy !== 'on_demand')
|
|
328
|
+
lines.push(...describeOnDemandAvailability(summary.onDemand));
|
|
208
329
|
return lines;
|
|
209
330
|
}
|
|
210
331
|
/** The lines of `power explain`; the fix is always last. */
|
|
@@ -229,6 +350,8 @@ function describePowerExplanation(summary, when = {}) {
|
|
|
229
350
|
const stop = describeLastAutoStopLines(summary.lastAutoStop, d.detail, when);
|
|
230
351
|
lines.push(...stop.lines);
|
|
231
352
|
lines.push(...wakeLines);
|
|
353
|
+
lines.push(...describeLastWakeTiming(summary.lastWake));
|
|
354
|
+
lines.push(...describePrewarm(summary.prewarm, when));
|
|
232
355
|
// A false stop's fix replaces nothing: the decision's own fix is still last-but-one.
|
|
233
356
|
if (text.fix && text.fix !== stop.fix)
|
|
234
357
|
lines.push(`Fix: ${text.fix}`);
|
|
@@ -45462,6 +45462,10 @@ var vM = Object.freeze({ __proto__: null, acceptAgentIntuition: function(e3, t2,
|
|
|
45462
45462
|
return gf.get((function(e4) {
|
|
45463
45463
|
return "".concat(nn, "/api/machines/dedicated/").concat(encodeURIComponent(e4), "/power");
|
|
45464
45464
|
})(e3));
|
|
45465
|
+
}, getDedicatedRuntimeWaitingWork: function(e3) {
|
|
45466
|
+
return gf.get((function(e4) {
|
|
45467
|
+
return "".concat(nn, "/api/machines/dedicated/").concat(encodeURIComponent(e4), "/power/waiting");
|
|
45468
|
+
})(e3));
|
|
45465
45469
|
}, getDeviceTokens: function() {
|
|
45466
45470
|
return gf.get(nr());
|
|
45467
45471
|
}, getDirectChatWithFriend: function(e3) {
|