@edgehero/pi-dispatch 4.0.1 → 4.2.0
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/.env.example +2 -1
- package/package.json +6 -1
- package/src/capacity-cli.mjs +375 -0
- package/src/capacity-records.mjs +209 -0
- package/src/capacity.mjs +654 -0
- package/src/cli.mjs +9 -0
- package/src/config.mjs +4 -20
- package/src/connection.mjs +4 -2
- package/src/doctor.mjs +399 -49
- package/src/egress-allowlist.mjs +81 -0
- package/src/env-allowlist.mjs +5 -1
- package/src/host-budget.mjs +6 -0
- package/src/host-registry.mjs +20 -2
- package/src/image-preflight.mjs +25 -1
- package/src/import-pi.mjs +113 -8
- package/src/index.mjs +97 -6
- package/src/job-size.mjs +16 -0
- package/src/live-jobs.mjs +135 -0
- package/src/mcp-config.mjs +466 -0
- package/src/mcp-preflight.mjs +262 -0
- package/src/model-endpoints.mjs +8 -3
- package/src/outbox.mjs +7 -1
- package/src/prepare.mjs +13 -13
- package/src/processor.mjs +36 -2
- package/src/queue.mjs +4 -2
- package/src/repeat-slot.mjs +13 -0
- package/src/reserved-env.mjs +1 -0
- package/src/run-container.mjs +2 -0
- package/src/run-earlier.mjs +29 -0
- package/src/run-history.mjs +123 -6
- package/src/run-mirror.mjs +161 -12
- package/src/schedules.mjs +1 -1
- package/src/size-suggest.mjs +34 -14
- package/src/start.mjs +79 -5
- package/src/triggers-file.mjs +3 -0
- package/src/triggers.mjs +123 -7
- package/src/wait-for.mjs +7 -0
- package/src/worker-name.mjs +25 -0
package/src/cli.mjs
CHANGED
|
@@ -52,6 +52,9 @@ const USAGE = `pi-dispatch — run pi coding-agent flows on your own folders
|
|
|
52
52
|
pi-dispatch pause stop taking new jobs (durable; survives worker restart)
|
|
53
53
|
pi-dispatch resume resume taking jobs
|
|
54
54
|
pi-dispatch status show paused state + job counts
|
|
55
|
+
pi-dispatch capacity [--since 24h|7d|30d] [--host <name>] [--json] [--valkey-url <url>]
|
|
56
|
+
how busy each host was: busy and idle time, slots in use, memory and CPU
|
|
57
|
+
promised and used, waits and projects, read from the run records (jobs only)
|
|
55
58
|
pi-dispatch cancel <jobId> stop one job: a queued or held job is removed (the line says whether it had
|
|
56
59
|
made attempts; cancel records nothing), a running one is aborted on whichever
|
|
57
60
|
host owns it (its record says operator-cancel)
|
|
@@ -235,6 +238,12 @@ export async function main(argv = process.argv.slice(2), env = process.env, { wr
|
|
|
235
238
|
return code;
|
|
236
239
|
}
|
|
237
240
|
|
|
241
|
+
if (cmd === "capacity") {
|
|
242
|
+
// Read-only, and on the kill switch's footing: the Valkey URL and the logs directory, never loadConfig (issue #599).
|
|
243
|
+
const { runCapacity } = await import("./capacity-cli.mjs");
|
|
244
|
+
return runCapacity(argv.slice(1), { env, write, valkeyRefusal, deploymentEnv: cliDeploymentEnv });
|
|
245
|
+
}
|
|
246
|
+
|
|
238
247
|
if (cmd === "cancel") {
|
|
239
248
|
// The kill switch's doctrine (issue #287): VALKEY_URL only, never loadConfig, so one misbehaving
|
|
240
249
|
// job can be stopped even when everything else about the deployment is misconfigured. On a shell/.env
|
package/src/config.mjs
CHANGED
|
@@ -19,6 +19,9 @@ import { jobSizeDefaults } from "./job-size.mjs";
|
|
|
19
19
|
import { hostBudgetSettings } from "./host-budget.mjs";
|
|
20
20
|
import { CONTAINER_ENV_NAMES, KEYLESS_ENV_NAME, RUNNER_ENV_NAMES } from "./reserved-env.mjs";
|
|
21
21
|
import { modelListProblem } from "./model-ref.mjs";
|
|
22
|
+
import { WORKER_NAME_RE } from "./worker-name.mjs";
|
|
23
|
+
|
|
24
|
+
export { WORKER_NAME_RE };
|
|
22
25
|
import { DOLLAR_ENV_NAMES, DOLLAR_WINDOW_KEYS, checkDollarInvariant, optionalUsdMicros } from "./money.mjs";
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -1146,26 +1149,7 @@ export function underOsTempDir(candidate, env = process.env, { realpath = realpa
|
|
|
1146
1149
|
return false;
|
|
1147
1150
|
}
|
|
1148
1151
|
|
|
1149
|
-
|
|
1150
|
-
* What a worker may call itself (issue #57). The CHARACTER CLASS is `sanitizeJobId`'s
|
|
1151
|
-
* (`[A-Za-z0-9._-]`), reused rather than invented so this project has one name-safe alphabet -- but that
|
|
1152
|
-
* function is a REPLACER, not a validator, so the three rules around the class are NEW and are claimed
|
|
1153
|
-
* as new here rather than borrowed:
|
|
1154
|
-
*
|
|
1155
|
-
* - a leading alphanumeric, which is what refuses `..` and a leading `-` that reads as a flag;
|
|
1156
|
-
* - a 64-character ceiling, because the name is a Valkey key segment and a log field on every line;
|
|
1157
|
-
* - no `.json`/`.log` tail, which is not decoration. The class contains the dot, so `prod.json` is
|
|
1158
|
-
* otherwise a legal name -- and a later slice writes a per-host marker file into `PI_LOGS_DIR`,
|
|
1159
|
-
* where `<something>.json` is parsed as a run record by the admin and DELETED by the log reaper.
|
|
1160
|
-
* A name is refused here rather than escaped there, because the escape would have to be remembered
|
|
1161
|
-
* at every site that ever composes a filename from this value.
|
|
1162
|
-
*
|
|
1163
|
-
* The class is `:`-free, `,`-free and `#`-free, which is what lets the name be a Valkey key segment
|
|
1164
|
-
* UNHASHED. That is the point of validating instead of hashing (`scopeKeyPrefix` does the opposite for
|
|
1165
|
-
* a folder path, which was never chosen for key-safety and cannot be refused): the whole value of a host
|
|
1166
|
-
* registry is that `HGETALL host:h:mac-mini-1` is readable by a human.
|
|
1167
|
-
*/
|
|
1168
|
-
export const WORKER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
1152
|
+
// WORKER_NAME_RE lives in worker-name.mjs (a leaf, for the capacity report) and is re-exported above; its reasons are there.
|
|
1169
1153
|
|
|
1170
1154
|
/** True when the name would collide with the run-history filename namespace. See WORKER_NAME_RE. */
|
|
1171
1155
|
const RESERVED_NAME_TAIL = /\.(json|log)$/i;
|
package/src/connection.mjs
CHANGED
|
@@ -189,8 +189,10 @@ export function parseConnection(url, { failFast = false, servername = null, cont
|
|
|
189
189
|
* `parseConnection`'s options, so it connects through `JudgedConnector` like every other client. `lazyConnect` and
|
|
190
190
|
* `failFast` as ioredis and `parseConnection` read them.
|
|
191
191
|
*/
|
|
192
|
-
export function makeRedisClient(url, { servername = null, context = null, failFast = false, lazyConnect = false, judge = null, withoutPassword = false } = {}) {
|
|
193
|
-
|
|
192
|
+
export function makeRedisClient(url, { servername = null, context = null, failFast = false, lazyConnect = false, judge = null, withoutPassword = false, disconnectTimeoutMs = null } = {}) {
|
|
193
|
+
// `disconnectTimeoutMs` (issue #599): how long ioredis's `disconnect()` waits for a socket to close before it destroys
|
|
194
|
+
// it (its default is 2 s, on a ref'd timer). A one-shot read that must not hold its process passes 0.
|
|
195
|
+
return new Redis({ ...parseConnection(url, { failFast, servername, context, judge, withoutPassword }), ...(lazyConnect ? { lazyConnect: true } : {}), ...(Number.isSafeInteger(disconnectTimeoutMs) && disconnectTimeoutMs >= 0 ? { disconnectTimeout: disconnectTimeoutMs } : {}) });
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
/**
|