@event4u/agent-config 9.28.0 → 9.29.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.
Files changed (147) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +6 -6
  3. package/CHANGELOG.md +66 -0
  4. package/README.md +1 -1
  5. package/agents/templates/.ai-council.yml.example +55 -24
  6. package/dist/agent-src/commands/cost/report.md +2 -2
  7. package/dist/agent-src/commands/team/adversarial.md +8 -7
  8. package/dist/agent-src/commands/team/delegate.md +13 -12
  9. package/dist/agent-src/commands/team/review.md +9 -8
  10. package/dist/agent-src/commands/team/status.md +9 -8
  11. package/dist/agent-src/commands/team.md +29 -19
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
  13. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
  14. package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
  15. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +18 -0
  16. package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
  17. package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
  18. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
  19. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
  20. package/dist/agent-src/rules/delegation-policy.md +11 -7
  21. package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
  22. package/dist/agent-src/rules/token-budget-discipline.md +1 -1
  23. package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
  24. package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
  25. package/dist/agent-src/skills/premortem/SKILL.md +30 -1
  26. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
  27. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -5
  28. package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
  29. package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
  30. package/dist/agent-src/templates/AGENTS.md +4 -4
  31. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  32. package/dist/agent-src/templates/roadmaps.md +23 -0
  33. package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
  34. package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
  35. package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
  36. package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
  37. package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
  38. package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-NZ5N2XQL.js} +3 -3
  39. package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-OJM3MBJM.js} +1 -1
  40. package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-RCIA67XN.js} +111 -90
  41. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  42. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  43. package/dist/cli-delegate/cmd_conformance.js +6 -43
  44. package/dist/cli-delegate/cmd_doctor.js +9 -5
  45. package/dist/cli-delegate/cmd_explain.js +2 -2
  46. package/dist/cli-delegate/cmd_export.js +4 -4
  47. package/dist/cli-delegate/cmd_handoff.js +3 -3
  48. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  49. package/dist/cli-delegate/cmd_migrate.js +2 -2
  50. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  51. package/dist/cli-delegate/cmd_prune.js +4 -4
  52. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  53. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  54. package/dist/cli-delegate/cmd_settings_get.js +1 -7
  55. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  56. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  57. package/dist/cli-delegate/cmd_sync.js +4 -4
  58. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  59. package/dist/cli-delegate/cmd_update.js +1 -1
  60. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  61. package/dist/cli-delegate/cmd_validate.js +4 -4
  62. package/dist/cli-delegate/cmd_versions.js +1 -1
  63. package/dist/discovery/deprecation-report.md +1 -1
  64. package/dist/discovery/discovery-manifest.json +69 -20
  65. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  66. package/dist/discovery/discovery-manifest.summary.md +3 -2
  67. package/dist/discovery/orphan-report.md +1 -1
  68. package/dist/discovery/packs.json +39 -2
  69. package/dist/discovery/trust-report.md +2 -2
  70. package/dist/discovery/workspaces.json +12 -4
  71. package/dist/hooks/dispatch.js +373 -35
  72. package/dist/install/install.mjs +42 -17
  73. package/dist/mcp/registry-manifest.json +2 -2
  74. package/dist/mcp/server.json +2 -2
  75. package/dist/mcp/server.mjs +651 -262
  76. package/dist/router.json +1 -1
  77. package/dist/scripts/_lib/agent_settings.js +54 -0
  78. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  79. package/dist/scripts/condense.js +147 -2
  80. package/dist/scripts/condense.js.map +1 -1
  81. package/dist/server/schemas/settings.js +6 -7
  82. package/dist/server/schemas/settings.js.map +1 -1
  83. package/dist/shared/settingsCarveOut.js +0 -9
  84. package/dist/shared/settingsCarveOut.js.map +1 -1
  85. package/dist/shared/settingsClasses.js +11 -9
  86. package/dist/shared/settingsClasses.js.map +1 -1
  87. package/dist/ui/assets/{index-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
  88. package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
  89. package/dist/ui/index.html +1 -1
  90. package/docs/contracts/settings-classes.md +39 -9
  91. package/package.json +1 -1
  92. package/src/agent-src/templates/AGENTS.md +4 -4
  93. package/src/config/agent-settings.template.yml +52 -66
  94. package/src/config/discovery/packs.yml +11 -0
  95. package/src/config/discovery/workspaces.yml +1 -1
  96. package/src/config/gate-violation-baselines.json +3 -3
  97. package/src/scripts/_cli/cmd_conformance.ts +14 -42
  98. package/src/scripts/_cli/cmd_doctor.ts +65 -48
  99. package/src/scripts/_lib/agent_settings.ts +58 -0
  100. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
  101. package/src/scripts/_lib/auto_dispatch.ts +23 -18
  102. package/src/scripts/_lib/capsule_trigger.ts +124 -0
  103. package/src/scripts/_lib/host_capability.ts +63 -6
  104. package/src/scripts/_lib/judgment_ladder.ts +382 -0
  105. package/src/scripts/_lib/orchestration_gate.ts +8 -0
  106. package/src/scripts/_lib/orchestration_record.ts +37 -0
  107. package/src/scripts/_lib/orchestration_savings.ts +1 -1
  108. package/src/scripts/_lib/self_repair.ts +160 -13
  109. package/src/scripts/_lib/self_repair_store.ts +28 -0
  110. package/src/scripts/_lib/subagent_capsule.ts +182 -0
  111. package/src/scripts/_lib/subagent_response.ts +19 -0
  112. package/src/scripts/_lib/subagent_spawn.ts +13 -0
  113. package/src/scripts/_lib/subagent_steering.ts +9 -4
  114. package/src/scripts/_lib/tier_budget_routing.ts +6 -1
  115. package/src/scripts/_lib/worker_budget.ts +37 -2
  116. package/src/scripts/ai_council/clients.ts +76 -54
  117. package/src/scripts/ai_council/config.ts +94 -6
  118. package/src/scripts/ai_council/handoff.ts +113 -0
  119. package/src/scripts/ai_council/modes.ts +19 -4
  120. package/src/scripts/ai_council/orchestrator.ts +105 -0
  121. package/src/scripts/ai_council/quorum.ts +84 -0
  122. package/src/scripts/ai_council/session.ts +44 -0
  123. package/src/scripts/ai_council/transport_resolver.ts +127 -1
  124. package/src/scripts/ai_team/availability.ts +110 -0
  125. package/src/scripts/ai_team/config.ts +34 -14
  126. package/src/scripts/ai_team/team_dispatch.ts +71 -29
  127. package/src/scripts/bench_critic_protocol.ts +396 -0
  128. package/src/scripts/check_detector_corpus.ts +202 -0
  129. package/src/scripts/check_hook_bundle_freshness.ts +4 -1
  130. package/src/scripts/condense.ts +162 -2
  131. package/src/scripts/council_cli.ts +401 -25
  132. package/src/scripts/forensics_report.ts +333 -0
  133. package/src/scripts/hooks/delegation_nudge_hook.ts +69 -35
  134. package/src/scripts/lint_discovery_vocabulary.ts +2 -0
  135. package/src/scripts/lint_never_silent.ts +276 -0
  136. package/src/scripts/lint_no_activation_gates.ts +271 -0
  137. package/src/scripts/orchestration_record.ts +8 -0
  138. package/src/scripts/rebuild_hook_bundle.ts +177 -0
  139. package/src/scripts/release.ts +74 -2
  140. package/src/scripts/routing_doctor.ts +21 -36
  141. package/src/scripts/score_skill_selection.ts +19 -5
  142. package/src/scripts/self_repair_cli.ts +287 -57
  143. package/src/scripts/team_events_spike.ts +271 -0
  144. package/src/scripts/team_review_gate_hook.ts +31 -9
  145. package/src/server/schemas/settings.ts +10 -17
  146. package/src/shared/settingsCarveOut.ts +0 -10
  147. package/src/shared/settingsClasses.ts +11 -9
@@ -472,8 +472,32 @@ function load_agent_settings(options = {}) {
472
472
  _deep_merge(merged, layer);
473
473
  }
474
474
  }
475
+ _warn_removed_always_on_keys(merged);
475
476
  return merged;
476
477
  }
478
+ function _readDottedSettingsPath(root, dotted) {
479
+ let node = root;
480
+ for (const part of dotted.split(".")) {
481
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
482
+ return void 0;
483
+ }
484
+ node = node[part];
485
+ }
486
+ return node;
487
+ }
488
+ function _warn_removed_always_on_keys(merged) {
489
+ for (const key of REMOVED_ALWAYS_ON_KEYS) {
490
+ if (_warnedRemovedAlwaysOnKeys.has(key)) {
491
+ continue;
492
+ }
493
+ if (_readDottedSettingsPath(merged, key) === void 0) {
494
+ continue;
495
+ }
496
+ _warnedRemovedAlwaysOnKeys.add(key);
497
+ process.stderr.write(`${key} was removed (always-on orchestration); ignored.
498
+ `);
499
+ }
500
+ }
477
501
  function get_modules_config(options = {}) {
478
502
  const project_root = options.project_root ?? null;
479
503
  const team_path = options.team_path ?? null;
@@ -753,7 +777,7 @@ function _relative_to(child, root) {
753
777
  function _posix_join(a, b) {
754
778
  return `${a.split(path3.sep).join("/")}/${b}`;
755
779
  }
756
- var _require, Logger, logger, DEFAULT_PROJECT_FILE, LOCAL_PROJECT_FILE, LOCAL_PROJECT_SUBDIR, DEFAULT_TEAM_FILE, USER_GLOBAL_FILENAME, USER_GLOBAL_CANONICAL_RELATIVE, MERGEABLE_KEYS, _DEFAULTS, MODULES_DEFAULTS, ANCHOR_AGENT_SETTINGS, ANCHOR_AGENTS_DIR, ANCHOR_GIT, _AGENTS_DIR_MARKERS, _LEGACY_ANCHOR_ENV, ORIGIN_ROOT_FLAG, ORIGIN_EXPLICIT, ORIGIN_ENV, ORIGIN_CWD_FALLBACK, PROJECT_ROOT_ENV, ROOT_OVERRIDE_ENV, ProjectRootError;
780
+ var _require, Logger, logger, DEFAULT_PROJECT_FILE, LOCAL_PROJECT_FILE, LOCAL_PROJECT_SUBDIR, DEFAULT_TEAM_FILE, USER_GLOBAL_FILENAME, USER_GLOBAL_CANONICAL_RELATIVE, MERGEABLE_KEYS, _DEFAULTS, MODULES_DEFAULTS, ANCHOR_AGENT_SETTINGS, ANCHOR_AGENTS_DIR, ANCHOR_GIT, _AGENTS_DIR_MARKERS, _LEGACY_ANCHOR_ENV, ORIGIN_ROOT_FLAG, ORIGIN_EXPLICIT, ORIGIN_ENV, ORIGIN_CWD_FALLBACK, PROJECT_ROOT_ENV, ROOT_OVERRIDE_ENV, ProjectRootError, REMOVED_ALWAYS_ON_KEYS, _warnedRemovedAlwaysOnKeys;
757
781
  var init_agent_settings = __esm({
758
782
  "src/scripts/_lib/agent_settings.ts"() {
759
783
  "use strict";
@@ -843,6 +867,14 @@ var init_agent_settings = __esm({
843
867
  this.name = "ProjectRootError";
844
868
  }
845
869
  };
870
+ REMOVED_ALWAYS_ON_KEYS = [
871
+ "subagents.enabled",
872
+ "subagents.auto",
873
+ "subagents.host_capabilities",
874
+ "subagents.budget_routing",
875
+ "ai_team.enabled"
876
+ ];
877
+ _warnedRemovedAlwaysOnKeys = /* @__PURE__ */ new Set();
846
878
  }
847
879
  });
848
880
 
@@ -23841,8 +23873,19 @@ ${user_prompt}`;
23841
23873
  "429",
23842
23874
  "usage limit"
23843
23875
  ];
23844
- constructor(opts) {
23876
+ /**
23877
+ * `identity` carries the subclass constants (`name`, `default_binary`)
23878
+ * that construction validates against. They are passed explicitly rather
23879
+ * than read off `this.name` / `this.default_binary` because subclass
23880
+ * instance-field initializers (`override default_binary = 'claude'`,
23881
+ * declared in the subclass body) run only AFTER this `super()` call
23882
+ * returns — reading them here, before that happens, would always see the
23883
+ * base class's own field defaults (`''`), not the subclass override.
23884
+ */
23885
+ constructor(opts, identity) {
23845
23886
  super();
23887
+ this.name = identity.name;
23888
+ this.default_binary = identity.default_binary;
23846
23889
  this.model = opts.model;
23847
23890
  this.timeout_seconds = opts.timeout_seconds ?? DEFAULT_CLI_TIMEOUT_SECONDS;
23848
23891
  this.max_calls_per_day = opts.max_calls_per_day ?? null;
@@ -24047,18 +24090,19 @@ ${user_prompt}`;
24047
24090
  }
24048
24091
  };
24049
24092
  AnthropicCliClient = class extends CliClient {
24050
- name = "anthropic";
24051
- default_binary = "claude";
24052
24093
  subscription_label = "claude-pro";
24053
24094
  constructor(opts = {}) {
24054
- super({
24055
- model: opts.model ?? DEFAULT_ANTHROPIC_CLI_MODEL,
24056
- binary: opts.binary,
24057
- timeout_seconds: opts.timeout_seconds,
24058
- max_calls_per_day: opts.max_calls_per_day,
24059
- warn_at: opts.warn_at,
24060
- cli_calls_path: opts.cli_calls_path
24061
- });
24095
+ super(
24096
+ {
24097
+ model: opts.model ?? DEFAULT_ANTHROPIC_CLI_MODEL,
24098
+ binary: opts.binary,
24099
+ timeout_seconds: opts.timeout_seconds,
24100
+ max_calls_per_day: opts.max_calls_per_day,
24101
+ warn_at: opts.warn_at,
24102
+ cli_calls_path: opts.cli_calls_path
24103
+ },
24104
+ { name: "anthropic", default_binary: "claude" }
24105
+ );
24062
24106
  }
24063
24107
  _build_command(system_prompt, user_prompt, max_tokens) {
24064
24108
  void user_prompt;
@@ -24118,8 +24162,6 @@ ${user_prompt}`;
24118
24162
  }
24119
24163
  };
24120
24164
  OpenAICliClient = class extends CliClient {
24121
- name = "openai";
24122
- default_binary = "codex";
24123
24165
  subscription_label = "chatgpt-plus";
24124
24166
  static _AUTH_FAILURE_PATTERNS = [
24125
24167
  ...CliClient._AUTH_FAILURE_PATTERNS,
@@ -24128,14 +24170,17 @@ ${user_prompt}`;
24128
24170
  "401"
24129
24171
  ];
24130
24172
  constructor(opts = {}) {
24131
- super({
24132
- model: opts.model ?? DEFAULT_OPENAI_CLI_MODEL,
24133
- binary: opts.binary,
24134
- timeout_seconds: opts.timeout_seconds,
24135
- max_calls_per_day: opts.max_calls_per_day,
24136
- warn_at: opts.warn_at,
24137
- cli_calls_path: opts.cli_calls_path
24138
- });
24173
+ super(
24174
+ {
24175
+ model: opts.model ?? DEFAULT_OPENAI_CLI_MODEL,
24176
+ binary: opts.binary,
24177
+ timeout_seconds: opts.timeout_seconds,
24178
+ max_calls_per_day: opts.max_calls_per_day,
24179
+ warn_at: opts.warn_at,
24180
+ cli_calls_path: opts.cli_calls_path
24181
+ },
24182
+ { name: "openai", default_binary: "codex" }
24183
+ );
24139
24184
  }
24140
24185
  _build_command(system_prompt, user_prompt, max_tokens) {
24141
24186
  void max_tokens;
@@ -24224,8 +24269,6 @@ ${user_prompt}`;
24224
24269
  }
24225
24270
  };
24226
24271
  GeminiCliClient = class extends CliClient {
24227
- name = "gemini";
24228
- default_binary = "gemini";
24229
24272
  subscription_label = "gemini-pro";
24230
24273
  static _AUTH_FAILURE_PATTERNS = [
24231
24274
  ...CliClient._AUTH_FAILURE_PATTERNS,
@@ -24234,14 +24277,17 @@ ${user_prompt}`;
24234
24277
  "oauth"
24235
24278
  ];
24236
24279
  constructor(opts = {}) {
24237
- super({
24238
- model: opts.model ?? DEFAULT_GEMINI_CLI_MODEL,
24239
- binary: opts.binary,
24240
- timeout_seconds: opts.timeout_seconds,
24241
- max_calls_per_day: opts.max_calls_per_day,
24242
- warn_at: opts.warn_at,
24243
- cli_calls_path: opts.cli_calls_path
24244
- });
24280
+ super(
24281
+ {
24282
+ model: opts.model ?? DEFAULT_GEMINI_CLI_MODEL,
24283
+ binary: opts.binary,
24284
+ timeout_seconds: opts.timeout_seconds,
24285
+ max_calls_per_day: opts.max_calls_per_day,
24286
+ warn_at: opts.warn_at,
24287
+ cli_calls_path: opts.cli_calls_path
24288
+ },
24289
+ { name: "gemini", default_binary: "gemini" }
24290
+ );
24245
24291
  }
24246
24292
  _build_command(system_prompt, user_prompt, max_tokens) {
24247
24293
  void user_prompt;
@@ -24316,8 +24362,6 @@ ${user_prompt}`;
24316
24362
  }
24317
24363
  };
24318
24364
  XAICliClient = class extends CliClient {
24319
- name = "xai";
24320
- default_binary = "grok";
24321
24365
  billable = true;
24322
24366
  // community CLI consumes an API key — billable applies
24323
24367
  static _AUTH_FAILURE_PATTERNS = [
@@ -24327,14 +24371,17 @@ ${user_prompt}`;
24327
24371
  "unauthorized"
24328
24372
  ];
24329
24373
  constructor(opts = {}) {
24330
- super({
24331
- model: opts.model ?? DEFAULT_XAI_MODEL,
24332
- binary: opts.binary,
24333
- timeout_seconds: opts.timeout_seconds,
24334
- max_calls_per_day: opts.max_calls_per_day,
24335
- warn_at: opts.warn_at,
24336
- cli_calls_path: opts.cli_calls_path
24337
- });
24374
+ super(
24375
+ {
24376
+ model: opts.model ?? DEFAULT_XAI_MODEL,
24377
+ binary: opts.binary,
24378
+ timeout_seconds: opts.timeout_seconds,
24379
+ max_calls_per_day: opts.max_calls_per_day,
24380
+ warn_at: opts.warn_at,
24381
+ cli_calls_path: opts.cli_calls_path
24382
+ },
24383
+ { name: "xai", default_binary: "grok" }
24384
+ );
24338
24385
  }
24339
24386
  _build_command(system_prompt, user_prompt, max_tokens) {
24340
24387
  void system_prompt;
@@ -24360,8 +24407,6 @@ ${user_prompt}`;
24360
24407
  }
24361
24408
  };
24362
24409
  PerplexityCliClient = class extends CliClient {
24363
- name = "perplexity";
24364
- default_binary = "perplexity";
24365
24410
  billable = true;
24366
24411
  // community CLI consumes an API key — billable applies
24367
24412
  static _AUTH_FAILURE_PATTERNS = [
@@ -24371,14 +24416,17 @@ ${user_prompt}`;
24371
24416
  "unauthorized"
24372
24417
  ];
24373
24418
  constructor(opts = {}) {
24374
- super({
24375
- model: opts.model ?? DEFAULT_PERPLEXITY_MODEL,
24376
- binary: opts.binary,
24377
- timeout_seconds: opts.timeout_seconds,
24378
- max_calls_per_day: opts.max_calls_per_day,
24379
- warn_at: opts.warn_at,
24380
- cli_calls_path: opts.cli_calls_path
24381
- });
24419
+ super(
24420
+ {
24421
+ model: opts.model ?? DEFAULT_PERPLEXITY_MODEL,
24422
+ binary: opts.binary,
24423
+ timeout_seconds: opts.timeout_seconds,
24424
+ max_calls_per_day: opts.max_calls_per_day,
24425
+ warn_at: opts.warn_at,
24426
+ cli_calls_path: opts.cli_calls_path
24427
+ },
24428
+ { name: "perplexity", default_binary: "perplexity" }
24429
+ );
24382
24430
  }
24383
24431
  _build_command(system_prompt, user_prompt, max_tokens) {
24384
24432
  void system_prompt;
@@ -24831,6 +24879,7 @@ function _build_config(raw, source_path) {
24831
24879
  const cli_call_budget = _build_cli_call_budget(
24832
24880
  _asDict(_getOr(raw, "cli_call_budget", {}))
24833
24881
  );
24882
+ const quorum = _build_quorum(_get(raw, "quorum", "majority"));
24834
24883
  const necessity_classifier = _build_necessity_classifier(
24835
24884
  _asDict(_getOr(raw, "necessity_classifier", {}))
24836
24885
  );
@@ -24856,6 +24905,7 @@ function _build_config(raw, source_path) {
24856
24905
  "debate_gates"
24857
24906
  );
24858
24907
  const restate = _build_restate(_asDict(_getOr(raw, "restate", {})), "restate");
24908
+ const critic_protocol = _build_critic_protocol(_getOr(raw, "critic_protocol", "legacy"));
24859
24909
  const decision_resolution = _build_decision_resolution(
24860
24910
  _asDict(_getOr(raw, "decision_resolution", {}))
24861
24911
  );
@@ -24877,6 +24927,7 @@ function _build_config(raw, source_path) {
24877
24927
  advisors,
24878
24928
  consensus_scoring: consensus,
24879
24929
  cli_call_budget,
24930
+ quorum,
24880
24931
  necessity_classifier,
24881
24932
  model_downgrade,
24882
24933
  debate,
@@ -24885,6 +24936,7 @@ function _build_config(raw, source_path) {
24885
24936
  chairman,
24886
24937
  debate_gates,
24887
24938
  restate,
24939
+ critic_protocol,
24888
24940
  decision_resolution,
24889
24941
  routing,
24890
24942
  low_impact,
@@ -25086,6 +25138,15 @@ function _build_restate(d, scope) {
25086
25138
  }
25087
25139
  return { enabled: Boolean(enabled) };
25088
25140
  }
25141
+ function _build_critic_protocol(v) {
25142
+ if (v === void 0 || v === null) return "legacy";
25143
+ if (!(_isStr(v) && _VALID_CRITIC_PROTOCOLS.has(v))) {
25144
+ throw new CouncilConfigError(
25145
+ `\`critic_protocol\`=${_pyRepr6(v)} not in ${_sortedListRepr(_VALID_CRITIC_PROTOCOLS)}.`
25146
+ );
25147
+ }
25148
+ return v;
25149
+ }
25089
25150
  function _build_decision_resolution(d) {
25090
25151
  if (!_isDict(d)) {
25091
25152
  throw new CouncilConfigError("`decision_resolution` must be a mapping.");
@@ -25337,7 +25398,7 @@ function _build_defaults(d) {
25337
25398
  if (!_isDict(d)) {
25338
25399
  throw new CouncilConfigError("`defaults` must be a mapping.");
25339
25400
  }
25340
- const mode = _get(d, "mode", "api");
25401
+ const mode = _get(d, "mode", "auto");
25341
25402
  if (!(_isStr(mode) && _VALID_MODES.has(mode))) {
25342
25403
  throw new CouncilConfigError(
25343
25404
  `defaults.mode=${_pyRepr6(mode)} not in ${_sortedListRepr(_VALID_MODES)}.`
@@ -25498,7 +25559,7 @@ function _build_cost_budget(d) {
25498
25559
  }
25499
25560
  return cb;
25500
25561
  }
25501
- function _build_member(name, cfg, default_mode = "api") {
25562
+ function _build_member(name, cfg, default_mode = "auto") {
25502
25563
  if (!_VALID_PROVIDERS.has(name)) {
25503
25564
  throw new CouncilConfigError(
25504
25565
  `members.${name}: unknown provider; valid: ${_sortedListRepr(_VALID_PROVIDERS)}.`
@@ -25626,6 +25687,9 @@ function _build_cli_call_budget(d) {
25626
25687
  );
25627
25688
  }
25628
25689
  const caps = /* @__PURE__ */ new Map();
25690
+ for (const provider of _VALID_PROVIDERS) {
25691
+ caps.set(provider, _DEFAULT_CLI_CALLS_PER_DAY);
25692
+ }
25629
25693
  for (const [provider, value] of Object.entries(raw_caps)) {
25630
25694
  if (!_VALID_PROVIDERS.has(provider)) {
25631
25695
  throw new CouncilConfigError(
@@ -25653,6 +25717,17 @@ function _build_cli_call_budget(d) {
25653
25717
  }
25654
25718
  return { max_calls_per_day: caps, warn_at };
25655
25719
  }
25720
+ function _build_quorum(raw) {
25721
+ if (raw === "majority") {
25722
+ return "majority";
25723
+ }
25724
+ if (_isInt(raw) && !_isBool(raw) && raw >= 1) {
25725
+ return raw;
25726
+ }
25727
+ throw new CouncilConfigError(
25728
+ `\`quorum\`=${_pyRepr6(raw)} must be 'majority' or an integer >= 1 (got ${_pyTypeName(raw)}).`
25729
+ );
25730
+ }
25656
25731
  function _build_advisor(name, cfg) {
25657
25732
  if (!_isDict(cfg)) {
25658
25733
  throw new CouncilConfigError(`advisors.${name}: must be a mapping.`);
@@ -25773,7 +25848,7 @@ function _pyTruthy3(value) {
25773
25848
  }
25774
25849
  return Object.keys(value).length > 0;
25775
25850
  }
25776
- var import_yaml5, _VALID_PROVIDERS, _VALID_MODES, _RAW_KEY_PREFIXES, CouncilConfigError, _Float, _VALID_NECESSITY_MODES, _VALID_DISCLOSURE_MODES, _VALID_CHAIRMAN_MODES, COUNCIL_CONFIG_RELNAME, COUNCIL_CONFIG_USER_GLOBAL_REL, COUNCIL_CONFIG_ENV, _VALID_RESOLUTION_MODES, _IMPACT_CLASSES, _LOCKED_IMPACT_CLASSES, _DEFAULT_RESOLUTION_MODES, _VALID_MEMBER_MODES, _VALID_DISPATCH_MODES;
25851
+ var import_yaml5, _VALID_PROVIDERS, _VALID_MODES, _RAW_KEY_PREFIXES, CouncilConfigError, _Float, _VALID_NECESSITY_MODES, _VALID_DISCLOSURE_MODES, _VALID_CHAIRMAN_MODES, COUNCIL_CONFIG_RELNAME, COUNCIL_CONFIG_USER_GLOBAL_REL, COUNCIL_CONFIG_ENV, _VALID_CRITIC_PROTOCOLS, _VALID_RESOLUTION_MODES, _IMPACT_CLASSES, _LOCKED_IMPACT_CLASSES, _DEFAULT_RESOLUTION_MODES, _VALID_MEMBER_MODES, _VALID_DISPATCH_MODES, _DEFAULT_CLI_CALLS_PER_DAY;
25777
25852
  var init_config = __esm({
25778
25853
  "src/scripts/ai_council/config.ts"() {
25779
25854
  "use strict";
@@ -25823,6 +25898,7 @@ var init_config = __esm({
25823
25898
  COUNCIL_CONFIG_RELNAME = ".ai-council.yml";
25824
25899
  COUNCIL_CONFIG_USER_GLOBAL_REL = "settings/.ai-council.yml";
25825
25900
  COUNCIL_CONFIG_ENV = "AI_COUNCIL_CONFIG";
25901
+ _VALID_CRITIC_PROTOCOLS = /* @__PURE__ */ new Set(["legacy", "load_bearing"]);
25826
25902
  _VALID_RESOLUTION_MODES = /* @__PURE__ */ new Set([
25827
25903
  "agent",
25828
25904
  "council",
@@ -25848,6 +25924,7 @@ var init_config = __esm({
25848
25924
  };
25849
25925
  _VALID_MEMBER_MODES = /* @__PURE__ */ new Set(["cli", "api"]);
25850
25926
  _VALID_DISPATCH_MODES = /* @__PURE__ */ new Set(["full", "single"]);
25927
+ _DEFAULT_CLI_CALLS_PER_DAY = 50;
25851
25928
  }
25852
25929
  });
25853
25930
 
@@ -26159,6 +26236,112 @@ var init_environment_detector = __esm({
26159
26236
  }
26160
26237
  });
26161
26238
 
26239
+ // src/scripts/ai_council/modes.ts
26240
+ function _sortedValidModes() {
26241
+ return Array.from(VALID_MODES).sort();
26242
+ }
26243
+ function _normalise(value) {
26244
+ if (value === null || value === void 0) {
26245
+ return null;
26246
+ }
26247
+ if (typeof value !== "string") {
26248
+ return null;
26249
+ }
26250
+ const s = value.trim().toLowerCase();
26251
+ return s || null;
26252
+ }
26253
+ function _validate2(mode, source) {
26254
+ if (mode === null) {
26255
+ return null;
26256
+ }
26257
+ if (!VALID_MODES.has(mode)) {
26258
+ throw new InvalidModeError(
26259
+ `${source} requested mode=${_pyRepr7(mode)}; expected one of: ${_pyReprList2(_sortedValidModes())}`
26260
+ );
26261
+ }
26262
+ return mode;
26263
+ }
26264
+ function _pyRepr7(s) {
26265
+ return `'${s}'`;
26266
+ }
26267
+ function _pyReprList2(items) {
26268
+ return `[${items.map((i) => _pyRepr7(i)).join(", ")}]`;
26269
+ }
26270
+ function resolve_global_mode(aiBlock) {
26271
+ if (aiBlock === null || aiBlock === void 0) {
26272
+ return null;
26273
+ }
26274
+ const flat = _normalise(_settingsGet(aiBlock, "mode"));
26275
+ if (flat !== null) {
26276
+ return flat;
26277
+ }
26278
+ const defaults = _settingsGet(aiBlock, "defaults");
26279
+ if (defaults === null || defaults === void 0) {
26280
+ return null;
26281
+ }
26282
+ if (!(defaults instanceof Map) && typeof defaults !== "object") {
26283
+ return null;
26284
+ }
26285
+ if (Array.isArray(defaults)) {
26286
+ return null;
26287
+ }
26288
+ return _normalise(
26289
+ _settingsGet(defaults, "mode")
26290
+ );
26291
+ }
26292
+ function _settingsGet(settings, key) {
26293
+ if (settings === null || settings === void 0) {
26294
+ return void 0;
26295
+ }
26296
+ if (settings instanceof Map) {
26297
+ return settings.get(key);
26298
+ }
26299
+ return settings[key];
26300
+ }
26301
+ function resolve_mode(memberName, opts = {}) {
26302
+ const invocationMode = opts.invocationMode ?? null;
26303
+ const memberSettings = opts.memberSettings ?? null;
26304
+ const globalMode = opts.globalMode ?? null;
26305
+ const inv = _validate2(
26306
+ _normalise(invocationMode),
26307
+ `/council mode= for ${_pyRepr7(memberName)}`
26308
+ );
26309
+ if (inv !== null) {
26310
+ return inv;
26311
+ }
26312
+ let memberModeRaw = null;
26313
+ if (memberSettings !== null) {
26314
+ const v = _settingsGet(memberSettings, "mode");
26315
+ memberModeRaw = v === void 0 ? null : v;
26316
+ }
26317
+ const member = _validate2(
26318
+ _normalise(memberModeRaw),
26319
+ `ai_council.members.${memberName}.mode`
26320
+ );
26321
+ if (member !== null) {
26322
+ return member;
26323
+ }
26324
+ const glob2 = _validate2(_normalise(globalMode), "ai_council.mode");
26325
+ if (glob2 !== null) {
26326
+ return glob2;
26327
+ }
26328
+ return DEFAULT_MODE;
26329
+ }
26330
+ var VALID_MODES, DEFAULT_MODE, InvalidModeError;
26331
+ var init_modes = __esm({
26332
+ "src/scripts/ai_council/modes.ts"() {
26333
+ "use strict";
26334
+ VALID_MODES = /* @__PURE__ */ new Set(["api", "manual", "cli", "auto"]);
26335
+ DEFAULT_MODE = "manual";
26336
+ InvalidModeError = class extends Error {
26337
+ constructor(message) {
26338
+ super(message);
26339
+ this.name = "InvalidModeError";
26340
+ }
26341
+ };
26342
+ }
26343
+ });
26344
+
26162
26345
  // src/scripts/ai_council/transport_resolver.ts
26163
26346
  function hasSource(report2, provider, allowed) {
26164
26347
  return report2.auth.some((a) => a.provider === provider && allowed.has(a.source));
@@ -26193,7 +26376,8 @@ function resolveTransport(opts) {
26193
26376
  billing,
26194
26377
  authSource,
26195
26378
  makesProviderCall: false,
26196
- reason: null
26379
+ reason: null,
26380
+ absentReason: null
26197
26381
  };
26198
26382
  }
26199
26383
  if (mode === "api" || mode === "cli") {
@@ -26203,7 +26387,8 @@ function resolveTransport(opts) {
26203
26387
  billing,
26204
26388
  authSource,
26205
26389
  makesProviderCall: true,
26206
- reason: null
26390
+ reason: null,
26391
+ absentReason: null
26207
26392
  };
26208
26393
  }
26209
26394
  if (mode !== "auto") {
@@ -26223,7 +26408,8 @@ function resolveTransport(opts) {
26223
26408
  billing,
26224
26409
  authSource,
26225
26410
  makesProviderCall: true,
26226
- reason: null
26411
+ reason: null,
26412
+ absentReason: null
26227
26413
  };
26228
26414
  }
26229
26415
  const keyPresent = opts.apiKeyPresent ?? hasSource(report2, provider, KEY_AUTH_SOURCES);
@@ -26234,12 +26420,36 @@ function resolveTransport(opts) {
26234
26420
  billing,
26235
26421
  authSource,
26236
26422
  makesProviderCall: true,
26237
- reason: null
26423
+ reason: null,
26424
+ absentReason: null
26238
26425
  };
26239
26426
  }
26240
- return unavailable(provider, billing, autoUnavailableReason(provider, binary, cliAuthed));
26427
+ return unavailable(
26428
+ provider,
26429
+ billing,
26430
+ autoUnavailableReason(provider, binary, cliAuthed),
26431
+ classifyStaticAbsentReason(binary)
26432
+ );
26241
26433
  }
26242
- function unavailable(_provider, billing, reason) {
26434
+ function resolveMemberTransport(opts) {
26435
+ const configuredMode = resolve_mode(opts.provider, {
26436
+ invocationMode: opts.invocationMode ?? null,
26437
+ memberSettings: opts.memberSettings ?? null,
26438
+ globalMode: opts.globalMode ?? null
26439
+ });
26440
+ const resolved = resolveTransport({
26441
+ provider: opts.provider,
26442
+ mode: configuredMode,
26443
+ report: opts.report,
26444
+ binaryOverride: opts.binaryOverride ?? null,
26445
+ // `exactOptionalPropertyTypes` rejects an explicit `undefined` for an
26446
+ // optional-boolean field, so the key is omitted entirely rather than
26447
+ // forwarded as `apiKeyPresent: undefined`.
26448
+ ...opts.apiKeyPresent !== void 0 ? { apiKeyPresent: opts.apiKeyPresent } : {}
26449
+ });
26450
+ return { ...resolved, configuredMode };
26451
+ }
26452
+ function unavailable(_provider, billing, reason, absentReason = null) {
26243
26453
  return {
26244
26454
  available: false,
26245
26455
  transport: null,
@@ -26248,9 +26458,13 @@ function unavailable(_provider, billing, reason) {
26248
26458
  billing,
26249
26459
  authSource: null,
26250
26460
  makesProviderCall: false,
26251
- reason
26461
+ reason,
26462
+ absentReason
26252
26463
  };
26253
26464
  }
26465
+ function classifyStaticAbsentReason(binary) {
26466
+ return binary === null ? "no_binary" : "no_auth";
26467
+ }
26254
26468
  function autoUnavailableReason(provider, binary, cliAuthed) {
26255
26469
  const expected = PROVIDER_CLI_META[provider]?.[0] ?? provider;
26256
26470
  if (binary === null && !cliAuthed) {
@@ -26261,11 +26475,40 @@ function autoUnavailableReason(provider, binary, cliAuthed) {
26261
26475
  }
26262
26476
  return `no transport available: \`${expected}\` is on PATH but not authenticated and no key resolves \u2014 log in to the CLI, or add an api_key_ref`;
26263
26477
  }
26478
+ function classifyCliFailure(raw) {
26479
+ const s = raw.trim().toLowerCase();
26480
+ if (s === "binary_missing" || s === "file_not_found") return "binary_missing";
26481
+ if (s === "auth_expired" || s === "auth_rejected" || s === "unauthorized") {
26482
+ return "auth_rejected";
26483
+ }
26484
+ if (s === "cli_unsupported" || s === "unsupported" || s === "parse_failed") {
26485
+ return "cli_unsupported";
26486
+ }
26487
+ if (s === "timeout") return "timeout";
26488
+ if (s === "cli_quota_exhausted" || s === "quota_exhausted") return "quota_exhausted";
26489
+ if (/^exit_5\d\d$/.test(s) || s === "server_error" || s === "os") return "server_error";
26490
+ return "other";
26491
+ }
26492
+ function absentReasonFromCliFailure(failure) {
26493
+ switch (failure) {
26494
+ case "binary_missing":
26495
+ return "no_binary";
26496
+ case "auth_rejected":
26497
+ return "no_auth";
26498
+ case "timeout":
26499
+ return "timeout";
26500
+ case "quota_exhausted":
26501
+ return "quota";
26502
+ default:
26503
+ return null;
26504
+ }
26505
+ }
26264
26506
  var VALID_TRANSPORT_MODES, CLI_AUTH_SOURCES, KEY_AUTH_SOURCES;
26265
26507
  var init_transport_resolver = __esm({
26266
26508
  "src/scripts/ai_council/transport_resolver.ts"() {
26267
26509
  "use strict";
26268
26510
  init_environment_detector();
26511
+ init_modes();
26269
26512
  VALID_TRANSPORT_MODES = /* @__PURE__ */ new Set([
26270
26513
  "api",
26271
26514
  "manual",
@@ -26539,7 +26782,6 @@ var init_config2 = __esm({
26539
26782
  max_consecutive_blocks: 3
26540
26783
  });
26541
26784
  AI_TEAM_DEFAULTS = Object.freeze({
26542
- enabled: false,
26543
26785
  model: "auto",
26544
26786
  allow_delegate: false,
26545
26787
  max_calls_per_day: 50,
@@ -26952,8 +27194,10 @@ __export(cmd_doctor_exports, {
26952
27194
  _scan_foreign: () => _scan_foreign,
26953
27195
  _settings_layer_chain: () => _settings_layer_chain,
26954
27196
  _skipped_manifest_check: () => _skipped_manifest_check,
27197
+ _team_codex_binary_name: () => _team_codex_binary_name,
26955
27198
  _wizard_state_path: () => _wizard_state_path,
26956
- main: () => main15
27199
+ main: () => main15,
27200
+ shutilWhich: () => shutilWhich
26957
27201
  });
26958
27202
  import * as crypto6 from "node:crypto";
26959
27203
  import * as fs45 from "node:fs";
@@ -28370,6 +28614,13 @@ function osErrorStr2(exc) {
28370
28614
  }
28371
28615
  return String(exc);
28372
28616
  }
28617
+ function _effectiveMemberMode(member, cfg) {
28618
+ return member.mode ?? cfg.defaults.mode;
28619
+ }
28620
+ function _mayRunOverCli(member, cfg) {
28621
+ const mode = _effectiveMemberMode(member, cfg);
28622
+ return mode === "cli" || mode === "auto";
28623
+ }
28373
28624
  function _check_council_cli(project_root) {
28374
28625
  const { load_cli_call_counts: load_cli_call_counts2 } = clients_exports;
28375
28626
  const { load_council_config: load_council_config2, resolve_config_path: resolve_config_path2 } = config_exports;
@@ -28395,7 +28646,7 @@ function _check_council_cli(project_root) {
28395
28646
  }
28396
28647
  const cli_members = [];
28397
28648
  for (const [name, m] of cfg.members.entries()) {
28398
- if (m.enabled && m.mode === "cli" && name in _CLI_PROVIDER_META) {
28649
+ if (m.enabled && _mayRunOverCli(m, cfg) && name in _CLI_PROVIDER_META) {
28399
28650
  cli_members.push([name, m]);
28400
28651
  }
28401
28652
  }
@@ -28494,18 +28745,11 @@ function _team_codex_binary_name(project_root) {
28494
28745
  return default_bin;
28495
28746
  }
28496
28747
  const member = cfg.members.get("openai");
28497
- if (member && member.enabled && member.mode === "cli" && member.binary) {
28748
+ if (member && member.enabled && _mayRunOverCli(member, cfg) && member.binary) {
28498
28749
  return member.binary;
28499
28750
  }
28500
28751
  return default_bin;
28501
28752
  }
28502
- function _settings_flag(val) {
28503
- if (typeof val === "boolean") return val;
28504
- if (typeof val === "string") {
28505
- return ["true", "yes", "on", "1"].includes(val.trim().toLowerCase());
28506
- }
28507
- return false;
28508
- }
28509
28753
  function _jwt_exp(token) {
28510
28754
  if (typeof token !== "string") return null;
28511
28755
  const parts = token.split(".");
@@ -28669,31 +28913,14 @@ function _check_detection(project_root) {
28669
28913
  }
28670
28914
  function _check_team(project_root) {
28671
28915
  const ai_team = _read_ai_team_block(project_root);
28672
- const enabled = _settings_flag(ai_team["enabled"]);
28673
28916
  const gateRaw = ai_team["review_gate"];
28674
28917
  const gate = typeof gateRaw === "object" && gateRaw !== null && !Array.isArray(gateRaw) ? gateRaw : {};
28675
- const gate_managed = _settings_flag(gate["managed"]);
28676
- if (!enabled) {
28677
- if (gate_managed) {
28678
- return {
28679
- id: "team",
28680
- status: "warn",
28681
- message: "half-configured: ai_team.review_gate.managed is on but ai_team.enabled is false \u2014 the gate never runs",
28682
- remedy: "set `ai_team.enabled: true` in .agent-settings.yml, or remove `ai_team.review_gate.managed`"
28683
- };
28684
- }
28685
- return {
28686
- id: "team",
28687
- status: "ok",
28688
- message: "team mode not configured (default-off)",
28689
- remedy: "opt in via `ai_team.enabled: true` in .agent-settings.yml, then re-run `agent-config doctor --check team`"
28690
- };
28691
- }
28692
28918
  const remedies = [];
28693
28919
  const binary_name = _team_codex_binary_name(project_root);
28694
28920
  const binary_resolved = shutilWhich(binary_name);
28695
28921
  const binary_glyph = binary_resolved !== null ? "\u2705" : "\u274C";
28696
- if (binary_resolved === null) {
28922
+ const binary_missing = binary_resolved === null;
28923
+ if (binary_missing) {
28697
28924
  remedies.push("install the codex CLI: `npm install -g @openai/codex`");
28698
28925
  }
28699
28926
  const auth_path = path43.join(_codex_home(), "auth.json");
@@ -28734,6 +28961,14 @@ function _check_team(project_root) {
28734
28961
  const gate_str = gate_signal.gate_str;
28735
28962
  remedies.push(...gate_signal.remedies);
28736
28963
  const detail = `codex binary ${binary_glyph} (${binary_name}) \xB7 ${auth_str} \xB7 ${plugin_str} \xB7 ${gate_str}`;
28964
+ if (binary_missing && !authed) {
28965
+ return {
28966
+ id: "team",
28967
+ status: "ok",
28968
+ message: `team unavailable (default) \u2014 codex CLI not installed \xB7 ${detail}`,
28969
+ remedy: remedies.join("; ")
28970
+ };
28971
+ }
28737
28972
  if (remedies.length > 0) {
28738
28973
  return {
28739
28974
  id: "team",
@@ -28745,7 +28980,7 @@ function _check_team(project_root) {
28745
28980
  return {
28746
28981
  id: "team",
28747
28982
  status: "ok",
28748
- message: `team mode enabled \xB7 ${detail}`,
28983
+ message: `team available \xB7 ${detail}`,
28749
28984
  remedy: ""
28750
28985
  };
28751
28986
  }
@@ -30010,7 +30245,7 @@ function _check_host_manifest(projectRoot) {
30010
30245
  return {
30011
30246
  id: "host-manifest",
30012
30247
  status: "ok",
30013
- message: `no .agent-settings.yml \u2014 safe-default manifest applies (${detectedNote})`,
30248
+ message: `no .agent-settings.yml \u2014 host capability is always probe/registry-resolved (${detectedNote})`,
30014
30249
  remedy: ""
30015
30250
  };
30016
30251
  }
@@ -30031,44 +30266,14 @@ function _check_host_manifest(projectRoot) {
30031
30266
  return {
30032
30267
  id: "host-manifest",
30033
30268
  status: "ok",
30034
- message: `no host_capabilities override \u2014 safe-default manifest applies (${detectedNote})`,
30269
+ message: `no host_capabilities override \u2014 host capability is always probe/registry-resolved (${detectedNote})`,
30035
30270
  remedy: ""
30036
30271
  };
30037
30272
  }
30038
- if (typeof manifest !== "object" || Array.isArray(manifest)) {
30039
- return {
30040
- id: "host-manifest",
30041
- status: "fail",
30042
- message: "subagents.host_capabilities is not a mapping",
30043
- remedy: "use the four boolean fields from the host-capability manifest contract"
30044
- };
30045
- }
30046
- const problems = [];
30047
- for (const [key, value] of Object.entries(manifest)) {
30048
- if (!HOST_MANIFEST_KEYS.has(key)) {
30049
- problems.push(`unknown key '${key}'`);
30050
- continue;
30051
- }
30052
- if (key === "schema_version") {
30053
- if (value !== 1) problems.push(`schema_version must be 1 (got ${JSON.stringify(value)})`);
30054
- continue;
30055
- }
30056
- if (typeof value !== "boolean") {
30057
- problems.push(`'${key}' must be boolean (got ${JSON.stringify(value)})`);
30058
- }
30059
- }
30060
- if (problems.length > 0) {
30061
- return {
30062
- id: "host-manifest",
30063
- status: "fail",
30064
- message: `host_capabilities malformed: ${problems.slice(0, 4).join("; ")}`,
30065
- remedy: "only schema_version:1 + four boolean capability fields are valid"
30066
- };
30067
- }
30068
30273
  return {
30069
30274
  id: "host-manifest",
30070
30275
  status: "ok",
30071
- message: `host_capabilities override parses strictly (${detectedNote})`,
30276
+ message: `subagents.host_capabilities is deprecated and ignored \u2014 host capability is always probe/registry-resolved (${detectedNote})`,
30072
30277
  remedy: ""
30073
30278
  };
30074
30279
  }
@@ -30225,7 +30430,7 @@ function _isCliEntry16() {
30225
30430
  return false;
30226
30431
  }
30227
30432
  }
30228
- var YAML6, _HERE13, PACKAGE_ROOT2, CONFORMANCE_CHECK_IDS, THIN_STUB_MARKER, HOST_MANIFEST_KEYS, USAGE3;
30433
+ var YAML6, _HERE13, PACKAGE_ROOT2, CONFORMANCE_CHECK_IDS, THIN_STUB_MARKER, USAGE3;
30229
30434
  var init_cmd_conformance = __esm({
30230
30435
  "src/scripts/_cli/cmd_conformance.ts"() {
30231
30436
  "use strict";
@@ -30247,13 +30452,6 @@ var init_cmd_conformance = __esm({
30247
30452
  "host-manifest"
30248
30453
  ];
30249
30454
  THIN_STUB_MARKER = "Routed rule \u2014 load the body on trigger-match";
30250
- HOST_MANIFEST_KEYS = /* @__PURE__ */ new Set([
30251
- "schema_version",
30252
- "subagent_spawn",
30253
- "parallel_spawn",
30254
- "status_polling",
30255
- "separate_quota_pool"
30256
- ]);
30257
30455
  USAGE3 = "usage: agent-config conformance [-h] [--project PROJECT] [--json]\n [--skip-dispatcher]\n";
30258
30456
  if (_isCliEntry16() || process16.argv[1] === _HERE13) {
30259
30457
  try {
@@ -31523,12 +31721,12 @@ function _enforceSize(text, mode) {
31523
31721
  const n = _utf8Len(text);
31524
31722
  if (n > MAX_BUNDLE_BYTES) {
31525
31723
  throw new BundleTooLarge(
31526
- `Bundle for ${_pyRepr7(mode)} mode is ${n} bytes (> ${MAX_BUNDLE_BYTES} hard ceiling). Narrow the scope (smaller diff, fewer files, shorter prompt).`
31724
+ `Bundle for ${_pyRepr8(mode)} mode is ${n} bytes (> ${MAX_BUNDLE_BYTES} hard ceiling). Narrow the scope (smaller diff, fewer files, shorter prompt).`
31527
31725
  );
31528
31726
  }
31529
31727
  return text;
31530
31728
  }
31531
- function _pyRepr7(s) {
31729
+ function _pyRepr8(s) {
31532
31730
  const hasSingle = s.includes("'");
31533
31731
  const hasDouble = s.includes('"');
31534
31732
  const quote = hasSingle && !hasDouble ? '"' : "'";
@@ -31678,7 +31876,7 @@ function _pyReprStr2(s) {
31678
31876
  }
31679
31877
  return `${quote}${body}${quote}`;
31680
31878
  }
31681
- function _pyRepr8(value) {
31879
+ function _pyRepr9(value) {
31682
31880
  return value === null ? "None" : _pyReprStr2(value);
31683
31881
  }
31684
31882
  function _splitlines8(text) {
@@ -31710,7 +31908,7 @@ function synthesis_template(mode) {
31710
31908
  Object.keys(_INPUT_MODE_TO_SYNTHESIS_KEY)
31711
31909
  );
31712
31910
  throw new Error(
31713
- `Unknown synthesis mode ${_pyRepr8(mode)}. Expected one of: [${expected.map((m) => _pyReprStr2(m)).join(", ")}]`
31911
+ `Unknown synthesis mode ${_pyRepr9(mode)}. Expected one of: [${expected.map((m) => _pyReprStr2(m)).join(", ")}]`
31714
31912
  );
31715
31913
  }
31716
31914
  function _strip_host_identity(text) {
@@ -31760,7 +31958,7 @@ function system_prompt_for(mode, opts = {}) {
31760
31958
  if (!_hasKey(_MODE_TABLE, mode)) {
31761
31959
  const expected = Object.keys(_MODE_TABLE).sort();
31762
31960
  throw new Error(
31763
- `Unknown council mode ${_pyRepr8(mode)}. Expected one of: [${expected.map((m) => _pyReprStr2(m)).join(", ")}]`
31961
+ `Unknown council mode ${_pyRepr9(mode)}. Expected one of: [${expected.map((m) => _pyReprStr2(m)).join(", ")}]`
31764
31962
  );
31765
31963
  }
31766
31964
  const head = handoff_preamble(project, original_ask);
@@ -32440,109 +32638,30 @@ var init_solo_dispatch = __esm({
32440
32638
  }
32441
32639
  });
32442
32640
 
32443
- // src/scripts/ai_council/modes.ts
32444
- function _sortedValidModes() {
32445
- return Array.from(VALID_MODES).sort();
32446
- }
32447
- function _normalise(value) {
32448
- if (value === null || value === void 0) {
32449
- return null;
32450
- }
32451
- if (typeof value !== "string") {
32452
- return null;
32453
- }
32454
- const s = value.trim().toLowerCase();
32455
- return s || null;
32456
- }
32457
- function _validate2(mode, source) {
32458
- if (mode === null) {
32459
- return null;
32460
- }
32461
- if (!VALID_MODES.has(mode)) {
32462
- throw new InvalidModeError(
32463
- `${source} requested mode=${_pyRepr9(mode)}; expected one of: ${_pyReprList2(_sortedValidModes())}`
32641
+ // src/scripts/ai_council/quorum.ts
32642
+ function resolveQuorumThreshold(total, setting = "majority") {
32643
+ if (!Number.isInteger(total) || total < 0) {
32644
+ throw new RangeError(
32645
+ `resolveQuorumThreshold: total must be a non-negative integer (got ${total}).`
32464
32646
  );
32465
32647
  }
32466
- return mode;
32467
- }
32468
- function _pyRepr9(s) {
32469
- return `'${s}'`;
32470
- }
32471
- function _pyReprList2(items) {
32472
- return `[${items.map((i) => _pyRepr9(i)).join(", ")}]`;
32473
- }
32474
- function resolve_global_mode(aiBlock) {
32475
- if (aiBlock === null || aiBlock === void 0) {
32476
- return null;
32477
- }
32478
- const flat = _normalise(_settingsGet(aiBlock, "mode"));
32479
- if (flat !== null) {
32480
- return flat;
32481
- }
32482
- const defaults = _settingsGet(aiBlock, "defaults");
32483
- if (defaults === null || defaults === void 0) {
32484
- return null;
32485
- }
32486
- if (!(defaults instanceof Map) && typeof defaults !== "object") {
32487
- return null;
32488
- }
32489
- if (Array.isArray(defaults)) {
32490
- return null;
32491
- }
32492
- return _normalise(
32493
- _settingsGet(defaults, "mode")
32494
- );
32495
- }
32496
- function _settingsGet(settings, key) {
32497
- if (settings === null || settings === void 0) {
32498
- return void 0;
32648
+ if (total === 0) {
32649
+ return 0;
32499
32650
  }
32500
- if (settings instanceof Map) {
32501
- return settings.get(key);
32651
+ if (setting === "majority") {
32652
+ return Math.ceil(total / 2);
32502
32653
  }
32503
- return settings[key];
32654
+ return Math.min(Math.max(setting, 1), total);
32504
32655
  }
32505
- function resolve_mode(memberName, opts = {}) {
32506
- const invocationMode = opts.invocationMode ?? null;
32507
- const memberSettings = opts.memberSettings ?? null;
32508
- const globalMode = opts.globalMode ?? null;
32509
- const inv = _validate2(
32510
- _normalise(invocationMode),
32511
- `/council mode= for ${_pyRepr9(memberName)}`
32512
- );
32513
- if (inv !== null) {
32514
- return inv;
32515
- }
32516
- let memberModeRaw = null;
32517
- if (memberSettings !== null) {
32518
- const v = _settingsGet(memberSettings, "mode");
32519
- memberModeRaw = v === void 0 ? null : v;
32520
- }
32521
- const member = _validate2(
32522
- _normalise(memberModeRaw),
32523
- `ai_council.members.${memberName}.mode`
32524
- );
32525
- if (member !== null) {
32526
- return member;
32527
- }
32528
- const glob2 = _validate2(_normalise(globalMode), "ai_council.mode");
32529
- if (glob2 !== null) {
32530
- return glob2;
32531
- }
32532
- return DEFAULT_MODE;
32656
+ function evaluateQuorum(total, present, setting = "majority") {
32657
+ const threshold = resolveQuorumThreshold(total, setting);
32658
+ const clampedPresent = Math.min(Math.max(present, 0), total);
32659
+ const status3 = clampedPresent >= threshold ? "concluded" : "inconclusive";
32660
+ return { status: status3, threshold, total, present: clampedPresent };
32533
32661
  }
32534
- var VALID_MODES, DEFAULT_MODE, InvalidModeError;
32535
- var init_modes = __esm({
32536
- "src/scripts/ai_council/modes.ts"() {
32662
+ var init_quorum = __esm({
32663
+ "src/scripts/ai_council/quorum.ts"() {
32537
32664
  "use strict";
32538
- VALID_MODES = /* @__PURE__ */ new Set(["api", "manual", "cli", "auto"]);
32539
- DEFAULT_MODE = "manual";
32540
- InvalidModeError = class extends Error {
32541
- constructor(message) {
32542
- super(message);
32543
- this.name = "InvalidModeError";
32544
- }
32545
- };
32546
32665
  }
32547
32666
  });
32548
32667
 
@@ -35004,6 +35123,37 @@ One sentence, and the single strongest reason.`;
35004
35123
  }
35005
35124
  });
35006
35125
 
35126
+ // src/scripts/ai_council/handoff.ts
35127
+ function isEmptyHandoff(h) {
35128
+ return h.decision === null && h.rejected_alternatives === null && h.constraints === null;
35129
+ }
35130
+ function buildHandoffFromStanceTally(tally) {
35131
+ if (tally === null || tally.consensus === null) {
35132
+ return EMPTY_HANDOFF;
35133
+ }
35134
+ const winner = tally.consensus;
35135
+ const rejected = tally.options.filter((o) => o !== winner).map((o) => ({
35136
+ option: o.label,
35137
+ reason: `backed by ${o.backers.length} member(s), weight ${o.weight.toFixed(2)} of ${tally.threshold.toFixed(2)} needed to conclude`
35138
+ }));
35139
+ return {
35140
+ decision: winner.label,
35141
+ rejected_alternatives: rejected.length > 0 ? rejected : null,
35142
+ constraints: null
35143
+ };
35144
+ }
35145
+ var EMPTY_HANDOFF;
35146
+ var init_handoff = __esm({
35147
+ "src/scripts/ai_council/handoff.ts"() {
35148
+ "use strict";
35149
+ EMPTY_HANDOFF = {
35150
+ decision: null,
35151
+ rejected_alternatives: null,
35152
+ constraints: null
35153
+ };
35154
+ }
35155
+ });
35156
+
35007
35157
  // src/scripts/ai_council/orchestrator.ts
35008
35158
  var orchestrator_exports = {};
35009
35159
  __export(orchestrator_exports, {
@@ -35983,11 +36133,58 @@ ${body}`;
35983
36133
  blocks.push(`## Convergence / Divergence
35984
36134
 
35985
36135
  ${body}`);
36136
+ if (opts.handoff && !isEmptyHandoff(opts.handoff)) {
36137
+ blocks.push(_render_handoff(opts.handoff));
36138
+ }
35986
36139
  if (blind !== null) {
35987
36140
  blocks.push(render_deanonymization_block("### De-anonymization map", blind.label_to_source));
35988
36141
  }
36142
+ if (opts.quorum) {
36143
+ blocks.push(_render_quorum_line(opts.quorum));
36144
+ }
36145
+ const absent = opts.absent_members ?? [];
36146
+ if (absent.length > 0) {
36147
+ blocks.push(_render_absent_members(absent));
36148
+ }
35989
36149
  return blocks.join("\n\n---\n\n");
35990
36150
  }
36151
+ function _render_quorum_line(q) {
36152
+ const verdict = q.status === "concluded" ? "concluded" : "INCONCLUSIVE \u2014 release gate holds";
36153
+ return `**Quorum:** ${q.present}/${q.total} present, needed ${q.threshold} \u2014 ${verdict}.`;
36154
+ }
36155
+ function _render_absent_members(absent) {
36156
+ const lines = ["### Absent Members", ""];
36157
+ for (const a of absent) {
36158
+ const tag = a.reason !== null ? ` (${a.reason})` : "";
36159
+ lines.push(`- **${a.member}**${tag} \u2014 ${a.detail}`);
36160
+ }
36161
+ return lines.join("\n");
36162
+ }
36163
+ function _render_handoff(h) {
36164
+ const lines = ["### Handoff", ""];
36165
+ lines.push(`**Decision:** ${h.decision ?? "none recorded \u2014 no structured verdict this pass."}`);
36166
+ lines.push("");
36167
+ if (h.rejected_alternatives !== null && h.rejected_alternatives.length > 0) {
36168
+ lines.push("**Rejected alternatives:**");
36169
+ lines.push("");
36170
+ for (const alt of h.rejected_alternatives) {
36171
+ lines.push(`- **${alt.option}** \u2014 ${alt.reason}`);
36172
+ }
36173
+ } else {
36174
+ lines.push("**Rejected alternatives:** none recorded.");
36175
+ }
36176
+ lines.push("");
36177
+ if (h.constraints !== null && h.constraints.length > 0) {
36178
+ lines.push("**Constraints:**");
36179
+ lines.push("");
36180
+ for (const c of h.constraints) {
36181
+ lines.push(`- ${c}`);
36182
+ }
36183
+ } else {
36184
+ lines.push("**Constraints:** none recorded.");
36185
+ }
36186
+ return lines.join("\n");
36187
+ }
35991
36188
  function _render_peer_review(peer_review) {
35992
36189
  const lines = ["## Peer-Review (Karpathy)"];
35993
36190
  let label_idx = 0;
@@ -36112,6 +36309,7 @@ var init_orchestrator = __esm({
36112
36309
  init_debate_gates();
36113
36310
  init_stance_tally();
36114
36311
  init_blind_review();
36312
+ init_handoff();
36115
36313
  CostBudget = class {
36116
36314
  max_input_tokens;
36117
36315
  max_output_tokens;
@@ -37696,6 +37894,7 @@ __export(council_cli_exports, {
37696
37894
  CouncilDisabledError: () => CouncilDisabledError,
37697
37895
  _parse_model_overrides: () => _parse_model_overrides,
37698
37896
  _parse_siblings_overrides: () => _parse_siblings_overrides,
37897
+ _postRunQuorum: () => _postRunQuorum,
37699
37898
  _synthesize_ai_council_block: () => _synthesize_ai_council_block,
37700
37899
  build_members: () => build_members,
37701
37900
  build_question: () => build_question,
@@ -37997,6 +38196,13 @@ function _synthesize_ai_council_block(cfg) {
37997
38196
  max_calls_per_day: _mapToObject(cfg.cli_call_budget.max_calls_per_day),
37998
38197
  warn_at: cfg.cli_call_budget.warn_at
37999
38198
  },
38199
+ // Phase 3.3 (road-to-always-on-orchestration): was validated by
38200
+ // `_build_config`/`_build_quorum` but never forwarded into the
38201
+ // synthesized block, so `build_members` always saw `undefined` and
38202
+ // silently fell back to `'majority'` regardless of the user's
38203
+ // configured `quorum: <k>`. Additive key — callers that never read
38204
+ // it observe no change.
38205
+ quorum: cfg.quorum,
38000
38206
  necessity_classifier: {
38001
38207
  enabled: cfg.necessity_classifier.enabled,
38002
38208
  mode: cfg.necessity_classifier.mode,
@@ -38035,11 +38241,59 @@ function _mapToObject(m) {
38035
38241
  }
38036
38242
  return out;
38037
38243
  }
38244
+ function _member_api_key_present(cfg) {
38245
+ const ref = cfg["api_key_ref"];
38246
+ if (typeof ref !== "string" || ref.trim() === "") {
38247
+ return void 0;
38248
+ }
38249
+ try {
38250
+ resolve_api_key(ref);
38251
+ return true;
38252
+ } catch {
38253
+ return false;
38254
+ }
38255
+ }
38256
+ function _quorum_setting_from(ai) {
38257
+ const raw = ai["quorum"];
38258
+ if (raw === "majority") {
38259
+ return "majority";
38260
+ }
38261
+ if (typeof raw === "number" && Number.isInteger(raw) && raw >= 1) {
38262
+ return raw;
38263
+ }
38264
+ return "majority";
38265
+ }
38266
+ function _postRunQuorum(members, responses, ai_cfg) {
38267
+ const absent = [];
38268
+ let present = 0;
38269
+ for (let i = 0; i < members.length; i++) {
38270
+ const m = members[i];
38271
+ const r = responses[i];
38272
+ if (r !== void 0 && !r.error) {
38273
+ present += 1;
38274
+ continue;
38275
+ }
38276
+ const raw = r?.error ?? "no response";
38277
+ const failure = classifyCliFailure(raw);
38278
+ absent.push({
38279
+ member: m.name,
38280
+ reason: absentReasonFromCliFailure(failure) ?? "unavailable",
38281
+ detail: raw
38282
+ });
38283
+ }
38284
+ return { quorum: evaluateQuorum(members.length, present, _quorum_setting_from(ai_cfg)), absent };
38285
+ }
38286
+ function _format_quorum_line(q) {
38287
+ const verdict = q.status === "concluded" ? "concluded" : "INCONCLUSIVE \u2014 release gate holds";
38288
+ return `council:quorum \xB7 ${q.present}/${q.total} present, needed ${q.threshold} \u2014 ${verdict}.`;
38289
+ }
38038
38290
  function build_members(settings, opts = {}) {
38039
38291
  const invocation_mode = opts.invocation_mode ?? null;
38040
38292
  const model_overrides = opts.model_overrides ?? null;
38041
38293
  const siblings_overrides = opts.siblings_overrides ?? null;
38042
38294
  const skipped = opts.skipped ?? null;
38295
+ const quorum_out = opts.quorum_out ?? null;
38296
+ const report2 = opts.environment_report ?? detectEnvironment();
38043
38297
  const ai = _isDict3(settings) ? settings["ai_council"] || {} : {};
38044
38298
  if (!_pyBool2(ai["enabled"])) {
38045
38299
  throw new CouncilDisabledError(
@@ -38073,6 +38327,8 @@ function build_members(settings, opts = {}) {
38073
38327
  );
38074
38328
  }
38075
38329
  const members = [];
38330
+ let total_enabled = 0;
38331
+ let absent_count = 0;
38076
38332
  for (const name of Object.keys(members_cfg)) {
38077
38333
  const cfg = members_cfg[name] || {};
38078
38334
  if (!_pyBool2(cfg["enabled"])) {
@@ -38083,15 +38339,24 @@ function build_members(settings, opts = {}) {
38083
38339
  }
38084
38340
  continue;
38085
38341
  }
38086
- const mode = resolve_mode(name, {
38342
+ total_enabled += 1;
38343
+ const api_key_present = _member_api_key_present(cfg);
38344
+ const resolved = resolveMemberTransport({
38345
+ provider: name,
38346
+ report: report2,
38087
38347
  invocationMode: invocation_mode,
38088
38348
  memberSettings: cfg,
38089
- globalMode: global_mode ?? null
38349
+ globalMode: global_mode ?? null,
38350
+ binaryOverride: cfg["binary"] ?? null,
38351
+ // `exactOptionalPropertyTypes` rejects an explicit `undefined` —
38352
+ // see `resolveMemberTransport`'s own forwarding call for the
38353
+ // same pattern.
38354
+ ...api_key_present !== void 0 ? { apiKeyPresent: api_key_present } : {}
38090
38355
  });
38091
38356
  if (name in siblings) {
38092
- if (mode !== "api") {
38357
+ if (resolved.transport !== "api") {
38093
38358
  throw new CouncilDisabledError(
38094
- `--siblings requires mode=api for member ${_pyReprStr5(name)} (got ${_pyReprStr5(mode)}).`
38359
+ `--siblings requires mode=api for member ${_pyReprStr5(name)} (configured mode=${_pyReprStr5(resolved.configuredMode)}, resolved transport=${_pyReprStr5(resolved.transport ?? "none")}).`
38095
38360
  );
38096
38361
  }
38097
38362
  const api_key_ref = cfg["api_key_ref"] ?? null;
@@ -38108,19 +38373,49 @@ function build_members(settings, opts = {}) {
38108
38373
  }
38109
38374
  continue;
38110
38375
  }
38376
+ if (resolved.transport === null) {
38377
+ const detail = resolved.reason ?? `member ${name} has no available transport`;
38378
+ const entry = {
38379
+ member: name,
38380
+ reason: resolved.absentReason ?? "unavailable",
38381
+ detail
38382
+ };
38383
+ absent_count += 1;
38384
+ if (skipped !== null) {
38385
+ skipped.push(entry);
38386
+ }
38387
+ _stderr2(`[council] SKIP ${name}: ${detail}
38388
+ `);
38389
+ continue;
38390
+ }
38111
38391
  const model = overrides[name] || cfg["model"] || null;
38112
- if (mode === "api" && _API_PROVIDERS.has(name)) {
38113
- members.push(
38114
- _construct_api_member(name, model, {
38115
- api_key_ref: cfg["api_key_ref"] ?? null,
38116
- // Council opts in explicitly (client-default OFF); on unless
38117
- // the operator sets `prompt_cache: false`.
38118
- enable_prompt_cache: cfg["prompt_cache"] !== false,
38119
- // road-to-cache-economy Phase 4 — see the siblings branch above.
38120
- prompt_cache_ttl: cfg["prompt_cache_ttl"] ?? void 0
38121
- })
38122
- );
38123
- } else if (mode === "cli" && _CLI_PROVIDERS.has(name)) {
38392
+ if (resolved.transport === "api" && _API_PROVIDERS.has(name)) {
38393
+ try {
38394
+ members.push(
38395
+ _construct_api_member(name, model, {
38396
+ api_key_ref: cfg["api_key_ref"] ?? null,
38397
+ // Council opts in explicitly (client-default OFF); on unless
38398
+ // the operator sets `prompt_cache: false`.
38399
+ enable_prompt_cache: cfg["prompt_cache"] !== false,
38400
+ // road-to-cache-economy Phase 4 — see the siblings branch above.
38401
+ prompt_cache_ttl: cfg["prompt_cache_ttl"] ?? void 0
38402
+ })
38403
+ );
38404
+ } catch (exc) {
38405
+ if (!(exc instanceof CouncilDisabledError)) {
38406
+ throw exc;
38407
+ }
38408
+ const detail = exc.message;
38409
+ const entry = { member: name, reason: "no_auth", detail };
38410
+ absent_count += 1;
38411
+ if (skipped !== null) {
38412
+ skipped.push(entry);
38413
+ }
38414
+ _stderr2(`[council] SKIP ${name}: ${detail}
38415
+ `);
38416
+ continue;
38417
+ }
38418
+ } else if (resolved.transport === "cli" && _CLI_PROVIDERS.has(name)) {
38124
38419
  try {
38125
38420
  members.push(
38126
38421
  _construct_cli_member(name, model, {
@@ -38140,6 +38435,7 @@ function build_members(settings, opts = {}) {
38140
38435
  reason: "binary_missing",
38141
38436
  detail
38142
38437
  };
38438
+ absent_count += 1;
38143
38439
  if (skipped !== null) {
38144
38440
  skipped.push(entry);
38145
38441
  }
@@ -38147,22 +38443,22 @@ function build_members(settings, opts = {}) {
38147
38443
  `);
38148
38444
  continue;
38149
38445
  }
38150
- } else if (mode === "cli") {
38446
+ } else if (resolved.transport === "cli") {
38151
38447
  throw new CouncilDisabledError(
38152
38448
  `member ${_pyReprStr5(name)} resolves to mode=cli but no CLI client is wired (known: ${_pyReprStrList(_pySortedStr(_CLI_PROVIDERS))}).`
38153
38449
  );
38154
- } else if (mode === "manual") {
38450
+ } else if (resolved.transport === "manual") {
38155
38451
  members.push(new ManualClient({ name, model: model || "manual" }));
38156
- } else if (mode === "playwright") {
38157
- throw new CouncilDisabledError(
38158
- `member ${_pyReprStr5(name)} resolves to mode=playwright (Phase 2c, not wired).`
38159
- );
38160
38452
  } else {
38161
38453
  throw new CouncilDisabledError(
38162
- `member ${_pyReprStr5(name)} has no transport \u2014 mode=${mode}, name not in ${_pyReprStrList(_pySortedStr(_API_PROVIDERS))}.`
38454
+ `member ${_pyReprStr5(name)} has no transport \u2014 resolved=${_pyReprStr5(resolved.transport)}, configured mode=${_pyReprStr5(resolved.configuredMode)}, name not in ${_pyReprStrList(_pySortedStr(_API_PROVIDERS))}.`
38163
38455
  );
38164
38456
  }
38165
38457
  }
38458
+ if (quorum_out !== null) {
38459
+ const present = total_enabled - absent_count;
38460
+ quorum_out.result = evaluateQuorum(total_enabled, present, _quorum_setting_from(ai));
38461
+ }
38166
38462
  if (members.length === 0) {
38167
38463
  if (skipped && skipped.length > 0) {
38168
38464
  const names = skipped.map((s) => String(s["member"])).join(", ");
@@ -38599,6 +38895,59 @@ function _objToMap(o) {
38599
38895
  }
38600
38896
  return m;
38601
38897
  }
38898
+ function _deserialise_handoff(data) {
38899
+ if (!_isDict3(data)) {
38900
+ return null;
38901
+ }
38902
+ const d = data;
38903
+ const decision = typeof d["decision"] === "string" ? d["decision"] : null;
38904
+ const rawAlts = d["rejected_alternatives"];
38905
+ const alts = Array.isArray(rawAlts) ? rawAlts.filter(
38906
+ (a) => _isDict3(a) && typeof a["option"] === "string" && typeof a["reason"] === "string"
38907
+ ).map((a) => ({ option: a["option"], reason: a["reason"] })) : [];
38908
+ const rawConstraints = d["constraints"];
38909
+ const constraints = Array.isArray(rawConstraints) ? rawConstraints.filter((c) => typeof c === "string") : [];
38910
+ return {
38911
+ decision,
38912
+ rejected_alternatives: alts.length > 0 ? alts : null,
38913
+ constraints: constraints.length > 0 ? constraints : null
38914
+ };
38915
+ }
38916
+ function _deserialise_absent_members(data) {
38917
+ if (!Array.isArray(data)) {
38918
+ return null;
38919
+ }
38920
+ const out = [];
38921
+ for (const entry of data) {
38922
+ if (!_isDict3(entry)) {
38923
+ continue;
38924
+ }
38925
+ const d = entry;
38926
+ const member = d["member"];
38927
+ const detail = d["detail"];
38928
+ if (typeof member !== "string" || typeof detail !== "string") {
38929
+ continue;
38930
+ }
38931
+ const raw = d["reason"];
38932
+ const reason = typeof raw === "string" && _VALID_ABSENT_REASONS.has(raw) ? raw : null;
38933
+ out.push({ member, reason, detail });
38934
+ }
38935
+ return out.length > 0 ? out : null;
38936
+ }
38937
+ function _deserialise_quorum(data) {
38938
+ if (!_isDict3(data)) {
38939
+ return null;
38940
+ }
38941
+ const d = data;
38942
+ const status3 = d["status"];
38943
+ const threshold = d["threshold"];
38944
+ const total = d["total"];
38945
+ const present = d["present"];
38946
+ if (status3 !== "concluded" && status3 !== "inconclusive" || typeof threshold !== "number" || typeof total !== "number" || typeof present !== "number") {
38947
+ return null;
38948
+ }
38949
+ return { status: status3, threshold, total, present };
38950
+ }
38602
38951
  function _resolve_rounds(args, ai_cfg) {
38603
38952
  if (_getattr3(args, "rounds", null) !== null) {
38604
38953
  return _pyInt6(args.rounds);
@@ -38636,12 +38985,14 @@ function cmd_estimate(args, opts = {}) {
38636
38985
  const advisor_plans = _build_advisor_plans(ai_cfg, REPO_ROOT3);
38637
38986
  const explicit_overrides = _parse_model_overrides(_getattr3(args, "model", null));
38638
38987
  const skipped = [];
38988
+ const quorum_out = { result: null };
38639
38989
  if (members === null) {
38640
38990
  members = build_members(settings, {
38641
38991
  invocation_mode: args.mode_override,
38642
38992
  model_overrides: _advisor_model_overrides(advisor_plans, explicit_overrides),
38643
38993
  siblings_overrides: _parse_siblings_overrides(_getattr3(args, "siblings", null)),
38644
- skipped
38994
+ skipped,
38995
+ quorum_out
38645
38996
  });
38646
38997
  }
38647
38998
  if (table === null) {
@@ -38677,6 +39028,9 @@ function cmd_estimate(args, opts = {}) {
38677
39028
  if (skipped.length > 0) {
38678
39029
  _stdout2(format_install_hints(skipped) + "\n");
38679
39030
  }
39031
+ if (quorum_out.result !== null) {
39032
+ _stdout2(_format_quorum_line(quorum_out.result) + "\n");
39033
+ }
38680
39034
  _stdout2(
38681
39035
  format_estimate_table(billable, estimates, {
38682
39036
  consensus_delta_usd: extra_usd,
@@ -39184,12 +39538,14 @@ function cmd_run(args, opts = {}) {
39184
39538
  const advisor_plans = _build_advisor_plans(ai_cfg, REPO_ROOT3);
39185
39539
  const explicit_overrides = _parse_model_overrides(_getattr3(args, "model", null));
39186
39540
  const skipped = [];
39541
+ const quorum_out = { result: null };
39187
39542
  if (members === null) {
39188
39543
  members = build_members(settings, {
39189
39544
  invocation_mode: args.mode_override,
39190
39545
  model_overrides: _advisor_model_overrides(advisor_plans, explicit_overrides),
39191
39546
  siblings_overrides: _parse_siblings_overrides(_getattr3(args, "siblings", null)),
39192
- skipped
39547
+ skipped,
39548
+ quorum_out
39193
39549
  });
39194
39550
  }
39195
39551
  if (_getattr3(args, "single", false)) {
@@ -39249,6 +39605,9 @@ function cmd_run(args, opts = {}) {
39249
39605
  if (skipped.length > 0) {
39250
39606
  _stdout2(format_install_hints(skipped) + "\n");
39251
39607
  }
39608
+ if (quorum_out.result !== null) {
39609
+ _stdout2(_format_quorum_line(quorum_out.result) + "\n");
39610
+ }
39252
39611
  _stdout2(
39253
39612
  format_estimate_table(billable, estimates, {
39254
39613
  consensus_delta_usd: extra_usd,
@@ -39323,6 +39682,14 @@ function cmd_run(args, opts = {}) {
39323
39682
  stance_repairs.push(r);
39324
39683
  }
39325
39684
  });
39685
+ const post_run = _postRunQuorum(members, responses, ai_cfg);
39686
+ quorum_out.result = post_run.quorum;
39687
+ skipped.push(...post_run.absent);
39688
+ const handoff = buildHandoffFromStanceTally(
39689
+ stance_tally_on ? tally_stances(
39690
+ responses.filter((r) => !r.error).map((r) => ({ member: `${r.provider}:${r.model}`, text: r.text }))
39691
+ ) : null
39692
+ );
39326
39693
  const persona_labels = build_persona_labels(advisor_plans, billable);
39327
39694
  const peer_review = _maybe_run_peer_review(
39328
39695
  ai_cfg,
@@ -39397,6 +39764,13 @@ function cmd_run(args, opts = {}) {
39397
39764
  if (blind_review_map !== null) {
39398
39765
  payload["blind_review_map"] = _mapToObject(blind_review_map);
39399
39766
  }
39767
+ if (quorum_out.result !== null) {
39768
+ payload["quorum"] = quorum_out.result;
39769
+ }
39770
+ if (skipped.length > 0) {
39771
+ payload["absent_members"] = skipped;
39772
+ }
39773
+ payload["handoff"] = handoff;
39400
39774
  const out_path = _validate_council_output_path(args.output, {
39401
39775
  kind: "responses",
39402
39776
  subcommand: "run"
@@ -39750,7 +40124,10 @@ function cmd_render(args) {
39750
40124
  stance_tally: payload["stance_tally"] === true,
39751
40125
  chairman_fields: payload["chairman_fields"] === true,
39752
40126
  blind: _isDict3(blind_map_raw) ? { label_to_source: _objToMap(blind_map_raw) } : null,
39753
- chairman: _isDict3(payload["chairman"]) ? payload["chairman"] : null
40127
+ chairman: _isDict3(payload["chairman"]) ? payload["chairman"] : null,
40128
+ handoff: _deserialise_handoff(payload["handoff"]),
40129
+ absent_members: _deserialise_absent_members(payload["absent_members"]),
40130
+ quorum: _deserialise_quorum(payload["quorum"])
39754
40131
  });
39755
40132
  if (_getattr3(args, "output", null)) {
39756
40133
  const out_path = _validate_council_output_path(args.output, {
@@ -40290,12 +40667,13 @@ function _isCliEntry17() {
40290
40667
  return false;
40291
40668
  }
40292
40669
  }
40293
- var _ArgExit2, ArgumentTypeError, _PROG9, SCHEMA_VERSION8, _API_PROVIDERS, _CLI_PROVIDERS, _HERE15, REPO_ROOT3, SETTINGS_FILE2, AI_COUNCIL_FILE, COUNCIL_CANONICAL_DIRS, CouncilDisabledError, _CLI_FACTORY, ValueError4, FileNotFoundError3, _SUBCOMMANDS, _TOP_USAGE2;
40670
+ var _ArgExit2, ArgumentTypeError, _PROG9, SCHEMA_VERSION8, _API_PROVIDERS, _CLI_PROVIDERS, _HERE15, REPO_ROOT3, SETTINGS_FILE2, AI_COUNCIL_FILE, COUNCIL_CANONICAL_DIRS, CouncilDisabledError, _CLI_FACTORY, ValueError4, _VALID_ABSENT_REASONS, FileNotFoundError3, _SUBCOMMANDS, _TOP_USAGE2;
40294
40671
  var init_council_cli = __esm({
40295
40672
  "src/scripts/council_cli.ts"() {
40296
40673
  "use strict";
40297
40674
  init_agent_settings();
40298
40675
  init_agent_settings();
40676
+ init_environment_detector();
40299
40677
  init_bundler();
40300
40678
  init_chairman();
40301
40679
  init_text_similarity();
@@ -40306,6 +40684,9 @@ var init_council_cli = __esm({
40306
40684
  init_config();
40307
40685
  init_solo_dispatch();
40308
40686
  init_modes();
40687
+ init_transport_resolver();
40688
+ init_transport_resolver();
40689
+ init_quorum();
40309
40690
  init_events_log();
40310
40691
  init_necessity();
40311
40692
  init_orchestrator();
@@ -40316,6 +40697,8 @@ var init_council_cli = __esm({
40316
40697
  init_shadow_dispatch();
40317
40698
  init_low_impact();
40318
40699
  init_blind_review();
40700
+ init_stance_tally();
40701
+ init_handoff();
40319
40702
  _ArgExit2 = class extends Error {
40320
40703
  };
40321
40704
  ArgumentTypeError = class extends Error {
@@ -40356,6 +40739,12 @@ var init_council_cli = __esm({
40356
40739
  };
40357
40740
  ValueError4 = class extends Error {
40358
40741
  };
40742
+ _VALID_ABSENT_REASONS = /* @__PURE__ */ new Set([
40743
+ "no_binary",
40744
+ "no_auth",
40745
+ "timeout",
40746
+ "quota"
40747
+ ]);
40359
40748
  FileNotFoundError3 = class extends Error {
40360
40749
  };
40361
40750
  _SUBCOMMANDS = ["estimate", "run", "debate", "render", "replay", "quota", "shadow-report", "status"];