@edgehero/pi-dispatch-admin 4.3.0 → 4.3.1

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/README.md CHANGED
@@ -129,7 +129,7 @@ Four things to know before you build on it:
129
129
  One command puts a live terminal view over the whole deployment:
130
130
 
131
131
  <p align="center">
132
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-dashboard.png?v=2026-10-13" alt="The /dispatch panel: status, spend meters, dollar windows, triggers with a portfolio trigger, pause windows, scoped limits with their dollar caps, held and failed jobs, runs with their projects and the u hosts key, and settings" width="820">
132
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-dashboard.png?v=2026-10-14" alt="The /dispatch panel: status, spend meters, dollar windows, triggers with a portfolio trigger, pause windows, scoped limits with their dollar caps, held and failed jobs, runs with their projects and the u hosts key, and settings" width="820">
133
133
  </p>
134
134
 
135
135
  - **MCP servers, read-only.** A trigger that names MCP servers says so on its row (`[mcp ...]`), and its
@@ -146,7 +146,7 @@ One command puts a live terminal view over the whole deployment:
146
146
  always marked as one ([`docs/insights.md`](https://github.com/edgehero/pi-dispatch/blob/main/docs/insights.md)).
147
147
 
148
148
  <p align="center">
149
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/insights-view.png?v=2026-10-13" alt="The insights page: KPI tiles, budget dials, the budget split, a plan verdict, spend charts, the five breakdowns, how busy each host was, and the topology with spend badges" width="820">
149
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/insights-view.png?v=2026-10-14" alt="The insights page: KPI tiles, budget dials, the budget split, a plan verdict, spend charts, the five breakdowns, how busy each host was, and the topology with spend badges" width="820">
150
150
  </p>
151
151
 
152
152
  - **Triggers, editable live.** Add, edit and delete triggers without a restart. Drill-ins show what
@@ -184,7 +184,7 @@ One command puts a live terminal view over the whole deployment:
184
184
  It counts jobs only: a machine busy with other work reads as idle.
185
185
 
186
186
  <p align="center">
187
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-hosts.png?v=2026-10-09" alt="The panel's hosts view: three machines, each with its slots in use, the memory and CPU its budget has promised, its running jobs with project, size and age, a stale host with an orphaned container, and the last 7 days as busy share, jobs at once, time full, the wait and the top projects" width="820">
187
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-hosts.png?v=2026-10-14" alt="The panel's hosts view: three machines, each with its slots in use, the memory and CPU its budget has promised, its running jobs with project, size and age, a stale host with an orphaned container, and the last 7 days as busy share, jobs at once, time full, the wait and the top projects" width="820">
188
188
  </p>
189
189
 
190
190
  - **The budget split.** With an allocation envelope (a dollar total per window and a floor per project), `b`
@@ -198,7 +198,7 @@ One command puts a live terminal view over the whole deployment:
198
198
  plan. A run's detail names the plan a portfolio run wrote and the dollars it settled.
199
199
 
200
200
  <p align="center">
201
- <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-allocation.png?v=2026-10-05" alt="The panel's budget split view: the weekly envelope, each project's floor, weight, share and spend, the headroom, the applied plan clamped by the step with its reasons, and the history across two hosts with a revert" width="820">
201
+ <img src="https://raw.githubusercontent.com/edgehero/pi-dispatch/main/docs/images/dispatch-allocation.png?v=2026-10-14" alt="The panel's budget split view: the weekly envelope, each project's floor, weight, share and spend, the headroom, the applied plan with its reasons, and the history across two hosts with a revert and a refused plan" width="820">
202
202
  </p>
203
203
 
204
204
  - **The portfolio manager.** A weekly flow you own reads the budget and writes a plan of weights. The
package/dist/index.mjs CHANGED
@@ -8810,7 +8810,7 @@ function isForgeKind(kind) {
8810
8810
  function forgeSpec(kind) {
8811
8811
  return isForgeKind(kind) ? FORGES[kind] : void 0;
8812
8812
  }
8813
- var FORGES, FORGE_KINDS, RUN_KINDS, MINTED_TOKEN_VARS, FORGE_HOST_VARS;
8813
+ var FORGES, FORGE_KINDS, RUN_KINDS, MINTED_TOKEN_VARS, FORGE_HOST_VARS, RECEIVER_ONLY_VARS;
8814
8814
  var init_forges = __esm({
8815
8815
  "../worker/src/forges.mjs"() {
8816
8816
  FORGES = {
@@ -8819,7 +8819,8 @@ var init_forges = __esm({
8819
8819
  deliveryIdName: "X-GitHub-Delivery GUID",
8820
8820
  pullRequestSep: "#",
8821
8821
  tokenVars: ["GITHUB_TOKEN", "GH_TOKEN"],
8822
- hostVar: null,
8822
+ hostVars: [],
8823
+ receiverOnlyVars: [],
8823
8824
  prLabelAction: "labeled"
8824
8825
  },
8825
8826
  gitlab: {
@@ -8827,7 +8828,8 @@ var init_forges = __esm({
8827
8828
  deliveryIdName: "webhook-id / Idempotency-Key",
8828
8829
  pullRequestSep: "!",
8829
8830
  tokenVars: ["GITLAB_TOKEN", "GL_TOKEN"],
8830
- hostVar: "GITLAB_HOST",
8831
+ hostVars: ["GITLAB_HOST"],
8832
+ receiverOnlyVars: [],
8831
8833
  // A label added to a merge request arrives as a plain `update`, indistinguishable from any other
8832
8834
  // edit, so there is no action for a positive-selector rule to attach to. (Separately, a GitLab label
8833
8835
  // is not an approval at all -- a Guest can set one at issue creation -- which is why every gitlab
@@ -8844,9 +8846,23 @@ var init_forges = __esm({
8844
8846
  // Forgejo numbers issues and pull requests from ONE per-repository sequence, exactly as GitHub does
8845
8847
  // -- an issue and a PR cannot share an index. So `#` names one thing and no discriminator is needed.
8846
8848
  pullRequestSep: "#",
8847
- // `tea` reads GITEA_SERVER_TOKEN; the unprefixed name is what the API examples and most scripts use.
8848
- tokenVars: ["FORGEJO_TOKEN", "GITEA_SERVER_TOKEN"],
8849
- hostVar: "FORGEJO_HOST",
8849
+ // `tea` logs in from the environment ONLY through GITEA_TOKEN plus GITEA_INSTANCE_URL. Measured on the
8850
+ // pinned tea 0.15.0 in the 4.3.0 job image against a live Forgejo 14.0.5: that pair lists issues, while
8851
+ // GITEA_SERVER_URL + GITEA_SERVER_TOKEN (with or without GITEA_LOGIN_VIA_ENV=1), FORGEJO_HOST +
8852
+ // GITEA_SERVER_TOKEN, and GITEA_TOKEN alone inside a clone all fail "Error: no available login". So
8853
+ // from be1cc42e until this row changed, every `tea` command the forgejo envelope tells the agent to run
8854
+ // failed. GITEA_SERVER_TOKEN stays: it is the default of `tea login add --token` (its --help says
8855
+ // `[$GITEA_SERVER_TOKEN]`), so an agent that adds a login explicitly still gets the job's token, and it
8856
+ // stays reserved. FORGEJO_TOKEN is what the API examples and most scripts use.
8857
+ // worker/test/forges.test.mjs pins these names to the tea version image/Dockerfile installs.
8858
+ tokenVars: ["FORGEJO_TOKEN", "GITEA_TOKEN", "GITEA_SERVER_TOKEN"],
8859
+ // GITEA_INSTANCE_URL is tea's, and must be the full URL with its scheme (FORGEJO_URL is). FORGEJO_HOST
8860
+ // is read by no CLI; it is kept because it has been in every forgejo job since #61 and a skill may use it.
8861
+ hostVars: ["GITEA_INSTANCE_URL", "FORGEJO_HOST"],
8862
+ // The author gate's own lookup token (OQ-043): Forgejo answers a lookup of another user's permission only to a
8863
+ // repository admin, so this is a read:repository token of an account that is one, and the job's FORGEJO_TOKEN
8864
+ // can stay a write collaborator's. A job holding it would hold an admin's token, which is the whole point lost.
8865
+ receiverOnlyVars: ["FORGEJO_PERMISSION_TOKEN"],
8850
8866
  // Forgejo names the action, so the positive-selector rule applies exactly as it does on GitHub.
8851
8867
  // Note this is HYGIENE, not the gate: unlike GitHub, every forgejo trigger is additionally gated on
8852
8868
  // the actor's resolved repository permission -- see filter-forgejo.mjs for why that is not
@@ -8867,7 +8883,8 @@ var init_forges = __esm({
8867
8883
  pullRequestSep: "!",
8868
8884
  // `az repos` reads AZURE_DEVOPS_EXT_PAT; SYSTEM_ACCESSTOKEN is what a pipeline-shaped script expects.
8869
8885
  tokenVars: ["AZURE_DEVOPS_EXT_PAT", "SYSTEM_ACCESSTOKEN"],
8870
- hostVar: "AZURE_DEVOPS_ORG_URL",
8886
+ hostVars: ["AZURE_DEVOPS_ORG_URL"],
8887
+ receiverOnlyVars: [],
8871
8888
  // Azure attaches no labels to a pull request at all, so there is no action to attach the rule to and
8872
8889
  // a predicated PR rule could never match. The loader refuses one rather than letting it load dead.
8873
8890
  prLabelAction: null
@@ -8876,7 +8893,8 @@ var init_forges = __esm({
8876
8893
  FORGE_KINDS = Object.freeze(Object.keys(FORGES));
8877
8894
  RUN_KINDS = Object.freeze(["local", ...FORGE_KINDS]);
8878
8895
  MINTED_TOKEN_VARS = new Set(FORGE_KINDS.flatMap((kind) => FORGES[kind].tokenVars));
8879
- FORGE_HOST_VARS = new Set(FORGE_KINDS.map((kind) => FORGES[kind].hostVar).filter((name) => typeof name === "string"));
8896
+ FORGE_HOST_VARS = new Set(FORGE_KINDS.flatMap((kind) => FORGES[kind].hostVars));
8897
+ RECEIVER_ONLY_VARS = new Set(FORGE_KINDS.flatMap((kind) => FORGES[kind].receiverOnlyVars));
8880
8898
  }
8881
8899
  });
8882
8900
 
@@ -9473,6 +9491,12 @@ function forwardEnvList(raw, egressArmed2 = false) {
9473
9491
  `PI_FORWARD_ENV must not forward ${minted.join(", ")} -- the worker mints a per-job token (CONST-TOKEN-SCOPED-PER-JOB) and a forwarded operator token would silently override it`
9474
9492
  );
9475
9493
  }
9494
+ const receiverOnly = names.filter((n) => RECEIVER_ONLY_VARS.has(n));
9495
+ if (receiverOnly.length > 0) {
9496
+ throw configError4(
9497
+ `PI_FORWARD_ENV must not forward ${receiverOnly.join(", ")} -- only the receiver reads it, for the author gate's permission lookup, and it belongs to an account that is an admin of the repositories, which is exactly what a job must not hold (OQ-043)`
9498
+ );
9499
+ }
9476
9500
  const workerOnly = names.filter((n) => WORKER_ONLY_SECRET_VARS.has(n));
9477
9501
  if (workerOnly.length > 0) {
9478
9502
  throw configError4(
@@ -17363,7 +17387,7 @@ var init_triggers = __esm({
17363
17387
  REPLICAS_MAX = 3;
17364
17388
  SECRETS_MAX = 16;
17365
17389
  ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
17366
- RESERVED_ENV_NAMES = /* @__PURE__ */ new Set([...MINTED_TOKEN_VARS, ...FORGE_HOST_VARS, ...WORKER_ONLY_SECRET_VARS, ...EGRESS_ENV_VARS, ...CONTAINER_ENV_NAMES, ...RUNNER_ENV_NAMES, ...PROVIDER_STEERING_VARS]);
17390
+ RESERVED_ENV_NAMES = /* @__PURE__ */ new Set([...MINTED_TOKEN_VARS, ...FORGE_HOST_VARS, ...RECEIVER_ONLY_VARS, ...WORKER_ONLY_SECRET_VARS, ...EGRESS_ENV_VARS, ...CONTAINER_ENV_NAMES, ...RUNNER_ENV_NAMES, ...PROVIDER_STEERING_VARS]);
17367
17391
  INSTRUCTIONS_MAX = 2e3;
17368
17392
  RUN_MODEL_NEAR_MISS_TARGETS = Object.freeze({
17369
17393
  provider: "run.provider",
@@ -131864,8 +131888,8 @@ function runRow(row, sel, inner, styler) {
131864
131888
  return fitLine(`${cursor} ${tree}${rep}${cells.join(sep4)}`, inner, styler);
131865
131889
  }
131866
131890
  function outcomeColored(outcome, reason, styler) {
131867
- if (outcome === "completed") return styler.fg("success", "\u2714 done");
131868
131891
  const shown2 = (v) => scrubControl(String(v));
131892
+ if (outcome === "completed") return reason ? styler.fg("warning", `\u2714 ${shown2(reason)}`) : styler.fg("success", "\u2714 done");
131869
131893
  if (outcome === "policy") return styler.fg("warning", `\u26A0 ${shown2(reason ?? "policy")}`);
131870
131894
  return styler.fg("error", `\u2718 ${shown2(reason ?? outcome ?? "failed")}`);
131871
131895
  }
@@ -133219,8 +133243,8 @@ var init_setup_wizard = __esm({
133219
133243
  init_connection();
133220
133244
  init_service_env();
133221
133245
  init_index();
133222
- RUNTIME_VERSION = "4.3.0";
133223
- RECEIVER_VERSION = "4.3.0";
133246
+ RUNTIME_VERSION = "4.3.1";
133247
+ RECEIVER_VERSION = "4.3.1";
133224
133248
  RUNTIME_PKG = "@edgehero/pi-dispatch";
133225
133249
  RECEIVER_PKG = "@edgehero/pi-dispatch-receiver";
133226
133250
  NUDGE_MARKER_BASENAME = "pi-dispatch-setup.nudged";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgehero/pi-dispatch-admin",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "Operator console for pi-dispatch, as a pi extension: a /dispatch panel over the queue, spend caps, scoped limits, run history and insights, editable cron and GitHub, GitLab, Forgejo and Azure DevOps triggers, pause windows, and human-confirmed tools your AI can operate.",
5
5
  "keywords": [
6
6
  "pi-package",