@edgehero/pi-dispatch 4.3.1 → 5.0.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 +84 -3
- package/README.md +20 -7
- package/deploy/console.service +52 -0
- package/deploy/docker-compose.yml +3 -1
- package/deploy/pi-dispatch-valkey.container +4 -2
- package/deploy/sandbox-gateway.service +23 -0
- package/deploy/worker-env-wrapper.cmd +6 -0
- package/deploy/worker-env-wrapper.sh +10 -1
- package/package.json +117 -19
- package/src/agent-snapshot.mjs +49 -0
- package/src/answerer-runtime.mjs +194 -0
- package/src/answerer.mjs +178 -0
- package/src/approval-policy.mjs +76 -0
- package/src/approval-runtime.mjs +188 -0
- package/src/approval-session.mjs +137 -0
- package/src/approval-state.mjs +170 -0
- package/src/auth-cli.mjs +86 -0
- package/src/auth-connections.mjs +145 -0
- package/src/auth-policy.mjs +32 -0
- package/src/auth-store.mjs +97 -0
- package/src/azure-host.mjs +66 -4
- package/src/azure-prompt.mjs +5 -4
- package/src/backend-conformance.mjs +34 -2
- package/src/backend-local.mjs +103 -34
- package/src/backend-openshell.mjs +1540 -0
- package/src/backend-podman.mjs +63 -2
- package/src/backend-registry.mjs +61 -4
- package/src/backends.mjs +395 -6
- package/src/bullmq-internals.mjs +208 -0
- package/src/canary-cli.mjs +28 -0
- package/src/canary-lock.mjs +41 -0
- package/src/canary-runtime.mjs +237 -0
- package/src/canary.mjs +53 -0
- package/src/cancel-cli.mjs +47 -34
- package/src/cancel-state.mjs +213 -15
- package/src/capabilities.mjs +36 -1
- package/src/capacity-cli.mjs +28 -8
- package/src/capacity-records.mjs +75 -16
- package/src/capacity.mjs +65 -0
- package/src/chat-cli.mjs +288 -0
- package/src/chat-fork.mjs +547 -0
- package/src/chat-mode.mjs +156 -0
- package/src/chat-view.mjs +720 -0
- package/src/checkpoint-paths.mjs +113 -0
- package/src/cli.mjs +128 -41
- package/src/comment-tables.mjs +322 -0
- package/src/community-auth-adapters.mjs +87 -0
- package/src/community-auth-helper.mjs +76 -0
- package/src/community-auth-metadata.mjs +6 -0
- package/src/community-auth-output-pins.mjs +38 -0
- package/src/community-auth-patches.mjs +168 -0
- package/src/community-auth-pins.mjs +38 -0
- package/src/config.mjs +190 -29
- package/src/connection.mjs +83 -7
- package/src/console-answer.mjs +113 -0
- package/src/console-audit.mjs +604 -0
- package/src/console-bootstrap.mjs +159 -0
- package/src/console-client.mjs +848 -0
- package/src/console-probe.mjs +106 -0
- package/src/console-tokens.mjs +459 -0
- package/src/container-names.mjs +51 -0
- package/src/container-spec.mjs +20 -1
- package/src/cpu-reserve.mjs +4 -1
- package/src/defer-reasons.mjs +200 -0
- package/src/defer-write.mjs +208 -0
- package/src/deployment-venue.mjs +23 -6
- package/src/docker-run.mjs +11 -0
- package/src/doctor-bundles.mjs +130 -0
- package/src/doctor-model-proposals.mjs +109 -0
- package/src/doctor-openshell.mjs +238 -0
- package/src/doctor-results.mjs +602 -0
- package/src/doctor-spawner.mjs +215 -0
- package/src/doctor.mjs +1639 -196
- package/src/dollar-budget.mjs +212 -3
- package/src/endpoint-status.mjs +100 -0
- package/src/env-allowlist.mjs +96 -6
- package/src/env-file.mjs +6 -4
- package/src/file-expect.mjs +47 -0
- package/src/forgejo-host.mjs +42 -5
- package/src/forgejo-prompt.mjs +5 -4
- package/src/forges.mjs +33 -3
- package/src/fork-job.mjs +58 -0
- package/src/fork-policy.mjs +23 -0
- package/src/fork-store.mjs +338 -0
- package/src/get-token.mjs +1 -1
- package/src/git-dirty.mjs +119 -14
- package/src/git-hardening.mjs +85 -0
- package/src/github-host.mjs +31 -6
- package/src/github-prompt.mjs +7 -16
- package/src/gitlab-host.mjs +42 -3
- package/src/gitlab-prompt.mjs +5 -4
- package/src/held-document.mjs +23 -0
- package/src/held-replies.mjs +325 -0
- package/src/held-settle.mjs +266 -0
- package/src/hook-runs.mjs +188 -0
- package/src/host-budget.mjs +8 -5
- package/src/host-clone.mjs +72 -0
- package/src/host-loads.mjs +557 -0
- package/src/host-registry.mjs +66 -6
- package/src/host-telemetry.mjs +92 -0
- package/src/http-body.mjs +78 -0
- package/src/image-preflight.mjs +156 -13
- package/src/import-pi.mjs +60 -172
- package/src/inbox-relay.mjs +482 -0
- package/src/inbox-settle.mjs +196 -0
- package/src/index.mjs +734 -129
- package/src/init.mjs +35 -5
- package/src/job-data-version.mjs +75 -0
- package/src/job-id.mjs +30 -15
- package/src/job-layout.mjs +22 -0
- package/src/job-size.mjs +16 -0
- package/src/live-jobs.mjs +14 -6
- package/src/live-probes.mjs +6 -2
- package/src/live-transcript.mjs +872 -0
- package/src/loaded-record.mjs +609 -0
- package/src/mcp-config.mjs +1 -1
- package/src/mcp-preflight.mjs +36 -5
- package/src/model-attempts.mjs +84 -0
- package/src/model-catalog.mjs +83 -0
- package/src/model-cooldowns.mjs +22 -0
- package/src/model-failover-protocol.mjs +11 -0
- package/src/model-failover.mjs +134 -0
- package/src/model-observation.mjs +50 -0
- package/src/model-proposal-protocol.mjs +21 -0
- package/src/model-proposals.mjs +75 -0
- package/src/model-ref.mjs +67 -10
- package/src/model-roles-file.mjs +24 -0
- package/src/model-roles.mjs +73 -0
- package/src/model-status.mjs +34 -0
- package/src/models-json.mjs +4 -1
- package/src/money.mjs +1 -1
- package/src/oauth-openai.mjs +121 -0
- package/src/older-state.mjs +249 -0
- package/src/on-failure.mjs +17 -13
- package/src/open-browser.mjs +31 -2
- package/src/opener-cli.mjs +135 -0
- package/src/opener-protocol.mjs +27 -0
- package/src/opener-terminal.mjs +128 -0
- package/src/openshell-cli.mjs +155 -0
- package/src/openshell-policy.mjs +163 -0
- package/src/outbox.mjs +14 -0
- package/src/output-cap.mjs +1 -0
- package/src/package-canary-record.mjs +26 -0
- package/src/package-canary.mjs +135 -0
- package/src/package-names.mjs +9 -0
- package/src/package-stage.mjs +418 -0
- package/src/packages.mjs +24 -16
- package/src/parked.mjs +2 -1
- package/src/pause-windows.mjs +136 -17
- package/src/podman-stack.mjs +40 -14
- package/src/portfolio-snapshot.mjs +26 -7
- package/src/prepare-github.mjs +41 -77
- package/src/prepare-local.mjs +32 -2
- package/src/prepare.mjs +45 -11
- package/src/private-store-boundary.mjs +70 -0
- package/src/processor.mjs +1550 -173
- package/src/projects.mjs +23 -0
- package/src/prompt-fence.mjs +18 -0
- package/src/provider-key.mjs +7 -0
- package/src/provider-steering.mjs +1 -0
- package/src/question-context.mjs +35 -0
- package/src/question-group-runtime.mjs +91 -0
- package/src/question-groups.mjs +171 -0
- package/src/queue.mjs +264 -55
- package/src/reason-catalog.mjs +448 -0
- package/src/receiver-status.mjs +230 -0
- package/src/record-reason.mjs +37 -0
- package/src/recovery-queue.mjs +566 -0
- package/src/redact-secrets.mjs +219 -0
- package/src/reserve-estimate.mjs +101 -0
- package/src/reserved-env.mjs +36 -1
- package/src/retention.mjs +67 -0
- package/src/run-budget-ledger.mjs +283 -0
- package/src/run-checkpoint.mjs +627 -0
- package/src/run-container.mjs +749 -33
- package/src/run-history.mjs +791 -45
- package/src/run-mirror.mjs +75 -7
- package/src/run-progress.mjs +751 -0
- package/src/runtime-observations.mjs +3 -1
- package/src/runtime-settings.mjs +272 -37
- package/src/sandbox-cli.mjs +78 -2
- package/src/sandbox-deployment.mjs +51 -0
- package/src/sandbox-gateway-protocol.mjs +378 -0
- package/src/sandbox-hosts.mjs +100 -0
- package/src/sandbox-inventory.mjs +83 -0
- package/src/sandbox-policy.mjs +225 -0
- package/src/sandbox-prepare-requests.mjs +145 -0
- package/src/sandbox-prepare.mjs +285 -0
- package/src/sandbox-refs.mjs +70 -0
- package/src/sandbox-session-monitor.mjs +4 -0
- package/src/sandbox-session-watch.mjs +124 -0
- package/src/sandbox-sessions.mjs +161 -0
- package/src/sandbox-store.mjs +63 -6
- package/src/sandbox-workspace-guard.mjs +39 -0
- package/src/sandbox.mjs +27 -200
- package/src/scheduler-stall-guard.mjs +14 -8
- package/src/schedules.mjs +19 -6
- package/src/secret-bundles.mjs +245 -0
- package/src/secret-profiles.mjs +103 -3
- package/src/secrets.mjs +19 -9
- package/src/service-env.mjs +38 -5
- package/src/service.mjs +638 -106
- package/src/session-key.mjs +60 -0
- package/src/session-store.mjs +168 -10
- package/src/setup-contract.mjs +40 -0
- package/src/setup-egress.mjs +113 -0
- package/src/setup-host-entry.mjs +33 -0
- package/src/setup-host.mjs +242 -0
- package/src/setup-lock.mjs +41 -0
- package/src/size-records.mjs +15 -3
- package/src/start.mjs +1353 -135
- package/src/status-comment.mjs +415 -0
- package/src/stderr-transcript.mjs +318 -0
- package/src/steer-ack.mjs +122 -0
- package/src/steer-cli.mjs +213 -0
- package/src/steer-framing.mjs +185 -0
- package/src/steer-inbox.mjs +693 -0
- package/src/store-version.mjs +139 -0
- package/src/subscription-config.mjs +56 -0
- package/src/subscription-limits.mjs +3 -0
- package/src/subscription-quota.mjs +259 -0
- package/src/subscription-runtime.mjs +123 -0
- package/src/subscription-status.mjs +42 -0
- package/src/subscription-wait.mjs +11 -0
- package/src/subscriptions.mjs +7 -1
- package/src/thinking-level.mjs +19 -0
- package/src/touch-outbox.mjs +61 -0
- package/src/touch-state.mjs +133 -0
- package/src/triggers-file.mjs +299 -39
- package/src/triggers.mjs +780 -38
- package/src/trust-paths.mjs +171 -0
- package/src/up.mjs +398 -51
- package/src/updates-cli.mjs +61 -0
- package/src/updates-look.mjs +127 -0
- package/src/updates-reconcile.mjs +182 -0
- package/src/updates-subscription-adapters.mjs +8 -0
- package/src/updates.mjs +252 -0
- package/src/valkey-auth.mjs +32 -9
- package/src/valkey-endpoint.mjs +14 -4
- package/src/wait-for.mjs +1 -1
- package/src/wait-state.mjs +62 -3
- package/src/watch-board.mjs +38 -0
- package/src/watch-closer.mjs +1 -1
- package/src/watch-contract.mjs +102 -0
- package/src/watch-decision.mjs +78 -0
- package/src/watch-drainer.mjs +14 -0
- package/src/watch-issue-adapter.mjs +44 -0
- package/src/watch-issue.mjs +111 -0
- package/src/watch-record.mjs +10 -0
- package/src/watch-runtime.mjs +167 -0
- package/src/watch-store.mjs +183 -0
- package/src/watch-trace.mjs +34 -0
- package/src/workspace-checkpoint.mjs +1750 -0
- package/src/zero-touch-fixes.mjs +99 -0
- package/src/zero-touch.mjs +225 -0
- package/src/repeat-slot.mjs +0 -13
package/src/run-mirror.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UNREADABLE_RECORD } from "./run-history.mjs";
|
|
2
|
+
import { RUN_MIRROR_MAX_RUNS_DEFAULT } from "./retention.mjs";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A fleet-visible copy of the run history (issue #57, Gap 3).
|
|
@@ -201,13 +202,17 @@ export const runRecordKey = (sanitizedJobId) => `runs:rec:${sanitizedJobId}`;
|
|
|
201
202
|
export const MIRROR_MAX_DAYS = 92;
|
|
202
203
|
|
|
203
204
|
/**
|
|
204
|
-
* A hard ceiling on index members, independent of the time window
|
|
205
|
+
* A hard ceiling on index members, independent of the time window: the DEFAULT of `PI_RUN_MIRROR_MAX_RUNS`
|
|
206
|
+
* (issue #761), which the worker hands `makeRunMirror` as `indexMax`. Kept under this name for its importers (a
|
|
207
|
+
* reader's default, the admin's local index).
|
|
205
208
|
*
|
|
206
209
|
* The window alone does not bound memory: a deployment running thousands of jobs a day would hold a
|
|
207
210
|
* quarter of a million members for ninety-two days. This caps what the fleet view can cost at roughly the
|
|
208
|
-
* depth a panel can display, and the file on disk remains the complete history either way.
|
|
211
|
+
* depth a panel can display, and the file on disk remains the complete history either way. Every writer trims
|
|
212
|
+
* the SHARED index to its own cap, so in a fleet the lowest setting wins; each count trim raises the horizon, so
|
|
213
|
+
* a reader sees the cut, never idle time.
|
|
209
214
|
*/
|
|
210
|
-
export const RUNS_INDEX_MAX =
|
|
215
|
+
export const RUNS_INDEX_MAX = RUN_MIRROR_MAX_RUNS_DEFAULT;
|
|
211
216
|
|
|
212
217
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
213
218
|
const OP_TIMEOUT_MS = 2_000;
|
|
@@ -290,8 +295,13 @@ export function makeRunMirror({ redis, retentionDays, now = () => Date.now(), lo
|
|
|
290
295
|
*
|
|
291
296
|
* Two round trips regardless of how many runs come back: one `ZREVRANGEBYSCORE` for the ids, one `MGET`
|
|
292
297
|
* for the bodies. Never one read per run.
|
|
298
|
+
*
|
|
299
|
+
* `prune: false` makes the read READ-ONLY (issue #641): the console's pages read the mirror on every request, and a
|
|
300
|
+
* read that writes is a read that needs a writer's care (a page refresh that moves the horizon is a page refresh that
|
|
301
|
+
* changes what capacity reports). An expired body is then skipped and left in the index for a pruning reader or the
|
|
302
|
+
* writer's own trim; the row it stood for is not shown either way.
|
|
293
303
|
*/
|
|
294
|
-
export async function readMirroredRuns(redis, { limit = 50, sinceMs = 0, now = () => Date.now(), timeoutMs = OP_TIMEOUT_MS } = {}) {
|
|
304
|
+
export async function readMirroredRuns(redis, { limit = 50, sinceMs = 0, prune = true, now = () => Date.now(), timeoutMs = OP_TIMEOUT_MS } = {}) {
|
|
295
305
|
if (!redis) return { runs: [], degraded: "off" };
|
|
296
306
|
let ids;
|
|
297
307
|
try {
|
|
@@ -326,7 +336,7 @@ export async function readMirroredRuns(redis, { limit = 50, sinceMs = 0, now = (
|
|
|
326
336
|
stale.push(ids[i]); // unparseable is indistinguishable from gone, and equally not showable
|
|
327
337
|
}
|
|
328
338
|
}
|
|
329
|
-
if (stale.length > 0) {
|
|
339
|
+
if (stale.length > 0 && prune !== false) {
|
|
330
340
|
try {
|
|
331
341
|
// Removed with the cut recorded (`PRUNE_SCRIPT`): the time up to a pruned run is no longer whole.
|
|
332
342
|
await bounded(redis.eval(PRUNE_SCRIPT, 3, RUNS_INDEX, RUNS_HORIZON, RUNS_SINCE, mirrorWindowMs(0), RUNS_HORIZON_MEMBER, ...stale), timeoutMs);
|
|
@@ -337,6 +347,60 @@ export async function readMirroredRuns(redis, { limit = 50, sinceMs = 0, now = (
|
|
|
337
347
|
return { runs, degraded: runs.length >= limit ? "truncated" : "ok" };
|
|
338
348
|
}
|
|
339
349
|
|
|
350
|
+
/**
|
|
351
|
+
* One PAGE of the index below a score cursor, READ-ONLY (issue #641): the console's runs screen. Returns
|
|
352
|
+
* `{ runs, fetched, frontier, degraded }` and never throws.
|
|
353
|
+
*
|
|
354
|
+
* `before` is an EXCLUSIVE score (millis) or null for the newest; `offset` skips that many members under the same
|
|
355
|
+
* cursor, so a caller fetching in batches under ONE cursor reads a stable sequence while new runs land above it (a new
|
|
356
|
+
* run scores at its own end, which is after any cursor a page handed out). `fetched` is how many members the range
|
|
357
|
+
* returned, expired ones included, and `frontier` the lowest score among them (null when none): together they tell a
|
|
358
|
+
* caller how deep it has read, which the runs alone cannot, since an expired body leaves no run behind. Nothing is
|
|
359
|
+
* pruned and nothing is written: two reads, the range WITHSCORES and one MGET.
|
|
360
|
+
*/
|
|
361
|
+
export async function readMirroredPage(redis, { before = null, offset = 0, limit = 50, timeoutMs = OP_TIMEOUT_MS } = {}) {
|
|
362
|
+
if (!redis) return { runs: [], fetched: 0, frontier: null, degraded: "off" };
|
|
363
|
+
const max = Number.isFinite(before) ? `(${before}` : "+inf";
|
|
364
|
+
let flat;
|
|
365
|
+
try {
|
|
366
|
+
flat = await bounded(redis.zrevrangebyscore(RUNS_INDEX, max, "-inf", "WITHSCORES", "LIMIT", Math.max(0, Math.floor(offset) || 0), Math.max(1, Math.floor(limit) || 1)), timeoutMs);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
return { runs: [], fetched: 0, frontier: null, degraded: `unreachable (${err?.message ?? "?"})` };
|
|
369
|
+
}
|
|
370
|
+
const ids = [];
|
|
371
|
+
let frontier = null;
|
|
372
|
+
for (let i = 0; Array.isArray(flat) && i + 1 < flat.length; i += 2) {
|
|
373
|
+
ids.push(flat[i]);
|
|
374
|
+
const score = Number(flat[i + 1]);
|
|
375
|
+
if (Number.isFinite(score)) frontier = frontier === null ? score : Math.min(frontier, score);
|
|
376
|
+
}
|
|
377
|
+
if (ids.length === 0) return { runs: [], fetched: 0, frontier: null, degraded: "ok" };
|
|
378
|
+
let bodies;
|
|
379
|
+
try {
|
|
380
|
+
bodies = await bounded(redis.mget(...ids.map(runRecordKey)), timeoutMs);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
return { runs: [], fetched: 0, frontier: null, degraded: `unreachable (${err?.message ?? "?"})` };
|
|
383
|
+
}
|
|
384
|
+
const runs = [];
|
|
385
|
+
for (let i = 0; i < ids.length; i++) {
|
|
386
|
+
const raw = bodies?.[i];
|
|
387
|
+
if (typeof raw !== "string" || raw === "") continue;
|
|
388
|
+
try {
|
|
389
|
+
const rec = JSON.parse(raw);
|
|
390
|
+
if (rec && typeof rec === "object" && !Array.isArray(rec)) runs.push(rec);
|
|
391
|
+
} catch {
|
|
392
|
+
// unparseable is not showable, and this read prunes nothing
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { runs, fetched: ids.length, frontier, degraded: "ok" };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* What `readMirroredRecord` answers, when asked to tell it (`tellUnread`), for a GET that did not complete: the server
|
|
400
|
+
* unreachable, or no answer within the bound. Nothing was read, which is not the same as no record.
|
|
401
|
+
*/
|
|
402
|
+
export const UNREAD_RECORD = Object.freeze({ unread: true });
|
|
403
|
+
|
|
340
404
|
/**
|
|
341
405
|
* One run's mirrored record, by its sanitized id: the parsed object, `null`, or `UNREADABLE_RECORD`. Never throws,
|
|
342
406
|
* never rejects.
|
|
@@ -346,8 +410,12 @@ export async function readMirroredRuns(redis, { limit = 50, sinceMs = 0, now = (
|
|
|
346
410
|
* is there but is not a record (unparseable, empty, not an object) is `UNREADABLE_RECORD`, exactly as the local
|
|
347
411
|
* reader says it, so the check can log `unreadable` from `mirror`. Neither is ever accepted: the caller keeps
|
|
348
412
|
* today's path.
|
|
413
|
+
*
|
|
414
|
+
* `tellUnread: true` answers `UNREAD_RECORD` for a GET that did not complete instead of `null`, for a caller that must
|
|
415
|
+
* not take an unread record for an absent one: the console's run page, whose "no run with this id" is a claim that
|
|
416
|
+
* every place a run is kept was read. The worker's own reads keep `null`, which fails toward today's path.
|
|
349
417
|
*/
|
|
350
|
-
export async function readMirroredRecord(redis, sanitizedJobId, { timeoutMs = OP_TIMEOUT_MS } = {}) {
|
|
418
|
+
export async function readMirroredRecord(redis, sanitizedJobId, { timeoutMs = OP_TIMEOUT_MS, tellUnread = false } = {}) {
|
|
351
419
|
if (!redis || !sanitizedJobId) return null;
|
|
352
420
|
try {
|
|
353
421
|
const raw = await bounded(redis.get(runRecordKey(sanitizedJobId)), timeoutMs);
|
|
@@ -361,7 +429,7 @@ export async function readMirroredRecord(redis, sanitizedJobId, { timeoutMs = OP
|
|
|
361
429
|
}
|
|
362
430
|
return rec !== null && typeof rec === "object" && !Array.isArray(rec) ? rec : UNREADABLE_RECORD;
|
|
363
431
|
} catch {
|
|
364
|
-
return null; // unreachable or timed out: nothing was read
|
|
432
|
+
return tellUnread === true ? UNREAD_RECORD : null; // unreachable or timed out: nothing was read
|
|
365
433
|
}
|
|
366
434
|
}
|
|
367
435
|
|