@bivy/bivy 0.15.0-staging.50 → 0.15.0-staging.52

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.
@@ -386,7 +386,17 @@ export class ControlPlaneTaskPoller {
386
386
  ],
387
387
  });
388
388
  if (decision.action === "reroute") {
389
- current = { ...current, runtimeId: decision.routing.runtimeId, model: decision.routing.model };
389
+ // A candidate changes only the routing dimensions it names. Preserve
390
+ // the existing runtime/model for partial fallback routes (for example
391
+ // a model-only fallback must not silently reset the selected agent).
392
+ current = {
393
+ ...current,
394
+ ...(decision.routing.runtimeId !== undefined ? { runtimeId: decision.routing.runtimeId } : {}),
395
+ ...(decision.routing.model !== undefined ? { model: decision.routing.model } : {}),
396
+ // Account routing is not yet represented by WorkItem. Do not claim
397
+ // that an account fallback was applied until the queue wire shape
398
+ // and credential resolver support it end-to-end.
399
+ };
390
400
  rerouteCount = decision.rerouteCount;
391
401
  await report({ routingReason: `fallback: ${decision.ref}` });
392
402
  }
@@ -94,8 +94,25 @@ export const DEFAULT_RULESET = {
94
94
  * human-readable errors — never throws.
95
95
  */
96
96
  export function validateRuleset(value) {
97
- if (Check(RulesetSchema, value))
98
- return { ok: true, ruleset: value, errors: [] };
97
+ if (Check(RulesetSchema, value)) {
98
+ const ruleset = value;
99
+ const errors = [];
100
+ for (let ruleIndex = 0; ruleIndex < ruleset.rules.length; ruleIndex += 1) {
101
+ const rule = ruleset.rules[ruleIndex];
102
+ if (rule.action !== "reroute" || !rule.chain)
103
+ continue;
104
+ for (let candidateIndex = 0; candidateIndex < rule.chain.length; candidateIndex += 1) {
105
+ const candidate = rule.chain[candidateIndex];
106
+ const hasRoute = [candidate.runtimeId, candidate.model, candidate.account]
107
+ .some((field) => typeof field === "string" && field.trim().length > 0);
108
+ if (!hasRoute)
109
+ errors.push(`/rules/${ruleIndex}/chain/${candidateIndex}: fallback candidate must specify runtimeId, model, or account`);
110
+ }
111
+ }
112
+ if (errors.length)
113
+ return { ok: false, errors };
114
+ return { ok: true, ruleset, errors: [] };
115
+ }
99
116
  const errors = [...Errors(RulesetSchema, value)]
100
117
  .slice(0, 20)
101
118
  .map((e) => `${e.path || "/"}: ${e.message}`);
package/dist/server.js CHANGED
@@ -4592,11 +4592,58 @@ async function reconcileInterruptedSessions() {
4592
4592
  }
4593
4593
  }
4594
4594
  }
4595
+ /** Run the legacy direct GitHub queue through the same failure policy as the
4596
+ * hosted queue. There is no control-plane run/evidence endpoint on this path,
4597
+ * so the policy is applied locally and terminal failures retain the poller's
4598
+ * existing logging behavior. */
4599
+ async function runDirectGitHubIssueWithPolicy(cfg, issue) {
4600
+ let ruleset;
4601
+ try {
4602
+ // Repository policy has precedence, matching the hosted queue. An invalid
4603
+ // project policy must not disable the direct poller; fall back to the
4604
+ // node-global policy and leave the validation error visible in the log.
4605
+ ruleset = loadProjectPolicy(cfg.repoDir)?.ruleset;
4606
+ }
4607
+ catch (error) {
4608
+ console.warn(`[policy] ${error instanceof Error ? error.message : String(error)}`);
4609
+ }
4610
+ const policy = createRunPolicy({
4611
+ context: "queue",
4612
+ ruleset: ruleset ?? activeRulesetFor(rulesetsDir, "queue"),
4613
+ });
4614
+ let attempt = 1;
4615
+ let rerouteCount = 0;
4616
+ let routing = {};
4617
+ for (;;) {
4618
+ try {
4619
+ await runIssueTask(cfg, issue, { ...routing });
4620
+ return;
4621
+ }
4622
+ catch (error) {
4623
+ const decision = policy.decide({ routing, error, attempt, rerouteCount });
4624
+ if (decision.action !== "retry" && decision.action !== "reroute")
4625
+ throw error;
4626
+ attempt += 1;
4627
+ if (decision.action === "reroute") {
4628
+ routing = {
4629
+ ...(routing.runtimeId !== undefined ? { runtimeId: routing.runtimeId } : {}),
4630
+ ...(routing.model !== undefined ? { model: routing.model } : {}),
4631
+ ...(decision.routing.runtimeId !== undefined ? { runtimeId: decision.routing.runtimeId } : {}),
4632
+ ...(decision.routing.model !== undefined ? { model: decision.routing.model } : {}),
4633
+ };
4634
+ rerouteCount = decision.rerouteCount;
4635
+ }
4636
+ console.warn(`[github-tasks] issue #${issue.number} ${decision.action} (${decision.condition}): ${decision.summary}`);
4637
+ if (decision.delayMs > 0)
4638
+ await new Promise((resolve) => setTimeout(resolve, decision.delayMs));
4639
+ }
4640
+ }
4641
+ }
4595
4642
  async function startGitHubTasksIfConfigured() {
4596
4643
  const cfg = await resolveGitHubTaskConfig();
4597
4644
  if (!cfg)
4598
4645
  return;
4599
- githubPoller = new GitHubTaskPoller(cfg, (issue) => runIssueTask(cfg, issue), nodeGithubMaxConcurrent);
4646
+ githubPoller = new GitHubTaskPoller(cfg, (issue) => runDirectGitHubIssueWithPolicy(cfg, issue), nodeGithubMaxConcurrent);
4600
4647
  githubPoller.start();
4601
4648
  }
4602
4649
  let controlPlanePoller;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.15.0-staging.50",
3
+ "version": "0.15.0-staging.52",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",