@skrr-ai/cli 0.1.90 → 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/commands/tasks/start.d.ts +10 -0
- package/dist/commands/tasks/start.js +59 -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 +5 -1
- package/oclif.manifest.json +23689 -23534
- 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;
|
|
@@ -14,6 +14,16 @@ export default class TasksStart extends BaseCommand {
|
|
|
14
14
|
};
|
|
15
15
|
run(): Promise<void>;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* The server's `TASK_WAITING_FOR_MACHINE` answer to a start, or null for any other
|
|
19
|
+
* error. The code rides the response body (the route echoes it); the API client may
|
|
20
|
+
* also lift it onto `err.code`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function waitingForMachine(err: unknown): {
|
|
23
|
+
message?: string;
|
|
24
|
+
reason?: string;
|
|
25
|
+
retryable?: boolean;
|
|
26
|
+
} | null;
|
|
17
27
|
/**
|
|
18
28
|
* What to run next, printed after a start (OSK-12128). A start used to print
|
|
19
29
|
* two ids and stop, and the way to watch it was found by accident. Every
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.waitingForMachine = waitingForMachine;
|
|
3
4
|
exports.startFollowUpLines = startFollowUpLines;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
5
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
@@ -94,6 +95,30 @@ class TasksStart extends base_command_1.BaseCommand {
|
|
|
94
95
|
result = await data_provider_1.dataService.startTask(taskId, flags.skill ? { skillId: flags.skill } : undefined);
|
|
95
96
|
}
|
|
96
97
|
catch (err) {
|
|
98
|
+
// A stopped Dedicated machine is being started for this work. That is the
|
|
99
|
+
// wake WORKING, not a failure: the server re-drives the start by itself once
|
|
100
|
+
// the machine is assignable (OSK-14010), so report it and exit 0.
|
|
101
|
+
const waiting = waitingForMachine(err);
|
|
102
|
+
if (waiting) {
|
|
103
|
+
if (flags.json) {
|
|
104
|
+
this.log(JSON.stringify({
|
|
105
|
+
waiting: true,
|
|
106
|
+
code: 'TASK_WAITING_FOR_MACHINE',
|
|
107
|
+
taskId,
|
|
108
|
+
task: { ...taskIdentity, id: taskId },
|
|
109
|
+
...(branch ? { branch } : {}),
|
|
110
|
+
...waiting,
|
|
111
|
+
}, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (branch) {
|
|
115
|
+
this.log(`${branch.created ? 'Created' : 'Checked out'} branch: ${branch.branch}`);
|
|
116
|
+
}
|
|
117
|
+
this.log(`Task ${taskIdentity.id} is waiting for the machine to start; the task will run by itself once it is ready.`);
|
|
118
|
+
if (waiting.message)
|
|
119
|
+
this.log(waiting.message);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
97
122
|
// Name the task in the failure. `skrr tasks start` is the command people
|
|
98
123
|
// fan out — five concurrent starts share one stderr, and the server's
|
|
99
124
|
// "Task already has an active run; start rejected" identifies none of
|
|
@@ -119,6 +144,40 @@ class TasksStart extends base_command_1.BaseCommand {
|
|
|
119
144
|
}
|
|
120
145
|
}
|
|
121
146
|
exports.default = TasksStart;
|
|
147
|
+
/**
|
|
148
|
+
* The server's `TASK_WAITING_FOR_MACHINE` answer to a start, or null for any other
|
|
149
|
+
* error. The code rides the response body (the route echoes it); the API client may
|
|
150
|
+
* also lift it onto `err.code`.
|
|
151
|
+
*/
|
|
152
|
+
function waitingForMachine(err) {
|
|
153
|
+
if (!err || typeof err !== 'object')
|
|
154
|
+
return null;
|
|
155
|
+
const e = err;
|
|
156
|
+
let body = {};
|
|
157
|
+
if (typeof e.body === 'string') {
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(e.body);
|
|
160
|
+
if (parsed && typeof parsed === 'object')
|
|
161
|
+
body = parsed;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// not JSON: fall back to err.code
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (e.body && typeof e.body === 'object') {
|
|
168
|
+
body = e.body;
|
|
169
|
+
}
|
|
170
|
+
if (body.code !== 'TASK_WAITING_FOR_MACHINE' && e.code !== 'TASK_WAITING_FOR_MACHINE') {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const text = [body.error, body.message].find((v) => typeof v === 'string');
|
|
174
|
+
const message = text || undefined;
|
|
175
|
+
return {
|
|
176
|
+
...(message ? { message } : {}),
|
|
177
|
+
...(typeof body.reason === 'string' ? { reason: body.reason } : {}),
|
|
178
|
+
...(typeof body.retryable === 'boolean' ? { retryable: body.retryable } : {}),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
122
181
|
/**
|
|
123
182
|
* What to run next, printed after a start (OSK-12128). A start used to print
|
|
124
183
|
* two ids and stop, and the way to watch it was found by accident. Every
|
|
@@ -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}`);
|
|
@@ -40560,7 +40560,7 @@ var ry = Object.freeze(T(T(T({}, Object.fromEntries(ey.map(function(e3) {
|
|
|
40560
40560
|
}))), Object.fromEntries(ny.map(function(e3) {
|
|
40561
40561
|
return [e3, "wake"];
|
|
40562
40562
|
}))));
|
|
40563
|
-
var ay = { held_by_session: { detail: "An agent session or a person is using the machine, so it stays on.", fix: "It can be stopped once the session ends, or stop it yourself. Files are preserved; running processes are not." }, held_by_terminal: { detail: "A terminal or SSH connection is open or was used recently, so the machine stays on.", fix: "Close the terminal and it can stop when idle, or stop it yourself. Files are preserved; running processes are not." }, held_by_service: { detail: "A service you declared is running on the machine, so it stays on.", fix: "Stop the service, or stop the machine yourself if you do not need it. Files are preserved; running processes are not." }, held_by_pending_work: { detail: "Work is waiting for this machine, so it stays on.", fix: "It can stop once the waiting work has finished." }, keep_awake: { detail: "You asked to keep this machine awake, so it will not stop automatically for now.", fix: "Cancel keep-awake to let it stop when idle." }, policy_always_on: { detail: "This machine is set to always be on, so it does not stop automatically.", fix: "Change its power policy to on demand to let it stop when idle, or stop it yourself." }, activity_unreadable: { detail: "The machine activity could not be read, so it was left on to be safe. This does not mean it is idle.", fix: "Check that the machine is connected. It can stop once its activity can be read again, or stop it yourself." }, idle_auto_stop: { detail: $v.idle_auto_stop, fix: "Start it again when you need it." }, owner_stop: { detail: "Stopped by you. Your files are kept; running processes are not.", fix: "Start it again when you need it." }, billing_stop: { detail: "Stopped because the account is not in good billing standing. Your files are kept; running processes are not.", fix: "Resolve billing, then start the machine again." }, spend_cap_stop: { detail: "Stopped because the spend cap was reached. Your files are kept; running processes are not.", fix: "Raise the spend cap, then start the machine again." }, woken_by_schedule: { detail: "Started because a scheduled run was due.", fix: null }, woken_by_task: { detail: "Started because a task needed this machine.", fix: null }, woken_by_inbound: { detail: "Started because an incoming event needed this machine.", fix: null }, woken_by_person: { detail: "Started because you asked for this machine.", fix: null }, wake_refused_manual: { detail: "The machine was stopped by you and is set to manual, so it did not start for the waiting work.", fix: "Start it yourself, or change its power policy to on demand." }, wake_refused_spend_cap: { detail: "The machine did not start because the spend cap was reached.", fix: "Raise the spend cap, then start it again." }, wake_refused_entitlement: { detail: "The machine did not start because the plan no longer includes it.", fix: "Restore the plan, then start it again. Your files are preserved." }, wake_refused_provider_capacity: { detail: "The machine did not start because no capacity was available in its zone. It will be tried again.", fix: "Try again shortly or contact support." }, wake_refused_budget: { detail: "The machine did not start because its monthly compute-hour budget was reached.", fix: "Raise the budget, or start the machine manually." }, prewarm_paused_no_activity: { detail: "Starting this machine ahead of scheduled runs is off, because the last few early starts were not followed by any use. Scheduled work still starts it when it is due.", fix: "A start for real work, or a change to the power policy, turns early starts back on." } };
|
|
40563
|
+
var ay = { held_by_session: { detail: "An agent session or a person is using the machine, or did within the idle window, so it stays on.", fix: "It can be stopped once the session ends and the idle window passes, or stop it yourself. Files are preserved; running processes are not." }, held_by_terminal: { detail: "A terminal or SSH connection is open or was used recently, so the machine stays on.", fix: "Close the terminal and it can stop when idle, or stop it yourself. Files are preserved; running processes are not." }, held_by_service: { detail: "A service you declared is running on the machine, so it stays on.", fix: "Stop the service, or stop the machine yourself if you do not need it. Files are preserved; running processes are not." }, held_by_pending_work: { detail: "Work is waiting for this machine, so it stays on.", fix: "It can stop once the waiting work has finished." }, keep_awake: { detail: "You asked to keep this machine awake, so it will not stop automatically for now.", fix: "Cancel keep-awake to let it stop when idle." }, policy_always_on: { detail: "This machine is set to always be on, so it does not stop automatically.", fix: "Change its power policy to on demand to let it stop when idle, or stop it yourself." }, activity_unreadable: { detail: "The machine activity could not be read, so it was left on to be safe. This does not mean it is idle.", fix: "Check that the machine is connected. It can stop once its activity can be read again, or stop it yourself." }, idle_auto_stop: { detail: $v.idle_auto_stop, fix: "Start it again when you need it." }, owner_stop: { detail: "Stopped by you. Your files are kept; running processes are not.", fix: "Start it again when you need it." }, billing_stop: { detail: "Stopped because the account is not in good billing standing. Your files are kept; running processes are not.", fix: "Resolve billing, then start the machine again." }, spend_cap_stop: { detail: "Stopped because the spend cap was reached. Your files are kept; running processes are not.", fix: "Raise the spend cap, then start the machine again." }, woken_by_schedule: { detail: "Started because a scheduled run was due.", fix: null }, woken_by_task: { detail: "Started because a task needed this machine.", fix: null }, woken_by_inbound: { detail: "Started because an incoming event needed this machine.", fix: null }, woken_by_person: { detail: "Started because you asked for this machine.", fix: null }, wake_refused_manual: { detail: "The machine was stopped by you and is set to manual, so it did not start for the waiting work.", fix: "Start it yourself, or change its power policy to on demand." }, wake_refused_spend_cap: { detail: "The machine did not start because the spend cap was reached.", fix: "Raise the spend cap, then start it again." }, wake_refused_entitlement: { detail: "The machine did not start because the plan no longer includes it.", fix: "Restore the plan, then start it again. Your files are preserved." }, wake_refused_provider_capacity: { detail: "The machine did not start because no capacity was available in its zone. It will be tried again.", fix: "Try again shortly or contact support." }, wake_refused_budget: { detail: "The machine did not start because its monthly compute-hour budget was reached.", fix: "Raise the budget, or start the machine manually." }, prewarm_paused_no_activity: { detail: "Starting this machine ahead of scheduled runs is off, because the last few early starts were not followed by any use. Scheduled work still starts it when it is due.", fix: "A start for real work, or a change to the power policy, turns early starts back on." } };
|
|
40564
40564
|
var iy = { busy_report_unavailable: { detail: "The machine has not told the platform when it is busy, so the platform cannot tell whether stopping it would interrupt work.", fix: "Start the machine and update its image (`skrr machines dedicated update-image <lease> --wait`), then try again. Until then it stays always_on." }, daemon_version_unknown: { detail: "The platform could not read which daemon version this machine runs.", fix: "Start the machine so its daemon reports in, then try again." }, daemon_too_old: { detail: "The daemon on this machine is older than the version that can report when it is busy.", fix: "Update the machine image (`skrr machines dedicated update-image <lease> --wait`), then try again. Files are preserved." }, declared_services: { detail: "This machine has declared services. A stopped service is not visible as busy, so an automatic stop could end it without warning.", fix: "Keep this machine always_on, or remove its declared services (`skrr machines dedicated service ls <lease>`)." }, services_unreadable: { detail: "The check that shows whether this machine has declared services cannot be completed yet, so the platform cannot show that an automatic stop is safe.", fix: "There is nothing you can do about this, and trying again will not change it. The machine stays always_on until the check ships; contact support if you need on_demand sooner." } };
|
|
40565
40565
|
var sy = ["CONNECT_COUNTRY_REQUIRED", "CONNECT_COUNTRY_NOT_APPROVED", "CONNECT_COUNTRY_POLICY_NOT_CONFIGURED"];
|
|
40566
40566
|
var cy = { payout_window_not_open: "Skipped: a payout was already made in this payout window.", below_minimum_payout: "Skipped: the available balance was below the minimum payout.", payouts_not_enabled: "Skipped: this Stripe account cannot receive payouts yet.", payout_request_refused: "Skipped: the balance changed while the payout was being reserved. It will be tried again.", payout_request_failed: "Failed: the payout could not be completed because of a problem on our side. Contact support if this keeps happening." };
|
|
@@ -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) {
|