@event4u/agent-config 14.2.0 → 14.4.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +286 -218
- package/agents/templates/.ai-council.yml.example +35 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +83 -12
- package/dist/agent-src/rules/roadmap-progress-sync.md +48 -4
- package/dist/agent-src/scripts/resume_probe.ts +154 -9
- package/dist/agent-src/scripts/roadmap_gates.ts +210 -6
- package/dist/agent-src/skills/roadmap-management/SKILL.md +9 -4
- package/dist/agent-src/templates/agent-settings.md +70 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +571 -0
- package/dist/agent-src/templates/scripts/telemetry/settings.ts +222 -44
- package/dist/cli/python/workspace_drive.js +8 -3
- package/dist/cli/python/workspace_drive.js.map +1 -1
- package/dist/cli/python/workspace_hosts.js +16 -12
- package/dist/cli/python/workspace_hosts.js.map +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-F3YUF7SA.js → chunk-3UCJWMMP.js} +1 -1
- package/dist/cli-delegate/chunk-F7DQWYR5.js +69 -0
- package/dist/cli-delegate/{chunk-7PZ4FSZB.js → chunk-FBKL6JWT.js} +61 -2
- package/dist/cli-delegate/{chunk-MNWRDMR5.js → chunk-PKOBQADP.js} +1 -1
- package/dist/cli-delegate/cmd_conformance.js +26 -11
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +17 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +4 -4
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +3684 -1719
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1070 -408
- package/dist/scripts/_lib/host_launch.js +66 -0
- package/dist/scripts/_lib/host_launch.js.map +1 -0
- package/dist/server/io/yamlIO.js +175 -3
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/routes/wizard.js +20 -1
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/shared/settingsConsent.js +25 -0
- package/dist/shared/settingsConsent.js.map +1 -1
- package/docs/contracts/settings-classes.md +11 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +2 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/resume_probe.ts +154 -9
- package/src/agent-src/scripts/roadmap_gates.ts +210 -6
- package/src/agent-src/templates/scripts/telemetry/remote.ts +571 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +222 -44
- package/src/config/estate-count-budget.json +120 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +32 -4
- package/src/config/gate-violation-baselines.json +63 -63
- package/src/config/gitignore-block.txt +5 -0
- package/src/config/hook-latency-budget.json +40 -1
- package/src/config/hook-token-budget.json +16 -0
- package/src/config/rule-stub-ceilings.json +11 -3
- package/src/scripts/_dispatch.bash +42 -1
- package/src/scripts/_lib/council_fallback_posture.ts +81 -0
- package/src/scripts/_lib/council_fallback_wiring.ts +126 -0
- package/src/scripts/_lib/council_settings_block.ts +152 -0
- package/src/scripts/_lib/headless_invocation.ts +285 -0
- package/src/scripts/_lib/host_launch.ts +72 -0
- package/src/scripts/_lib/map_to_object.ts +16 -0
- package/src/scripts/_lib/roadmap_checkboxes.ts +88 -0
- package/src/scripts/_lib/run_checkpoint.ts +409 -0
- package/src/scripts/_lib/session_register.ts +32 -1
- package/src/scripts/_lib/stdin.ts +35 -1
- package/src/scripts/_lib/turn_end_refusals.ts +12 -4
- package/src/scripts/_lib/unattended_guard.ts +371 -0
- package/src/scripts/ai_council/config.ts +27 -1
- package/src/scripts/ai_council/events_log.ts +12 -1
- package/src/scripts/ai_council/fallback_config.ts +114 -0
- package/src/scripts/ai_council/mid_flight_fallback.ts +295 -0
- package/src/scripts/ai_council/orchestrator.ts +251 -292
- package/src/scripts/ai_council/py_parity.ts +139 -0
- package/src/scripts/ai_council/response_render.ts +99 -0
- package/src/scripts/ai_council/spend_gate.ts +123 -0
- package/src/scripts/ai_council/transport_resolver.ts +81 -13
- package/src/scripts/bench_hook_injection.ts +100 -6
- package/src/scripts/bench_hook_latency.ts +442 -16
- package/src/scripts/check_estate_count.ts +924 -0
- package/src/scripts/compile_hook_manifest.ts +67 -0
- package/src/scripts/council_cli.ts +100 -125
- package/src/scripts/decision_memo.ts +325 -0
- package/src/scripts/hook_manifest.json +1 -0
- package/src/scripts/hook_manifest.yaml +172 -25
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +183 -10
- package/src/scripts/hooks/dispatch_issues.ts +26 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +97 -24
- package/src/scripts/hooks/hook_stdin.ts +111 -5
- package/src/scripts/hooks/injection_budget.ts +595 -0
- package/src/scripts/hooks/payload_stub.ts +446 -0
- package/src/scripts/hooks/run_continuation_hook.ts +627 -0
- package/src/scripts/hooks/session_eol_hook.ts +52 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +339 -0
- package/src/scripts/hooks/tool_result_bytes_hook.ts +27 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +45 -7
- package/src/scripts/hooks_doctor.ts +16 -2
- package/src/scripts/interruption_report.ts +330 -13
- package/src/scripts/lint_hook_manifest.ts +167 -0
- package/src/scripts/roadmap_progress_hook.ts +254 -46
- package/src/scripts/run_supervise.ts +623 -0
- package/src/scripts/session_register_hook.ts +108 -8
- package/src/scripts/sessions_cli.ts +18 -6
- package/src/scripts/telemetry_disclosure_hook.ts +244 -0
- package/src/server/io/yamlIO.ts +167 -3
- package/src/shared/settingsConsent.ts +49 -2
- package/dist/cli-delegate/chunk-MMJL4TIX.js +0 -18
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* compile_hook_manifest — writes `src/scripts/hook_manifest.json` from
|
|
4
|
+
* `src/scripts/hook_manifest.yaml`.
|
|
5
|
+
*
|
|
6
|
+
* WHY the compiled sibling exists: `dispatch_hook._load_yaml` runs on EVERY
|
|
7
|
+
* hook dispatch, and parsing the ~61 kB YAML manifest measured 12 ms plus 8 ms
|
|
8
|
+
* to load the `yaml` module — a fifth of a ~103 ms dispatch spent re-deriving a
|
|
9
|
+
* table that does not change between runs. The compiled form is the same data
|
|
10
|
+
* with comments stripped (~15 kB) and parses in under a millisecond. Measured
|
|
11
|
+
* effect on `pre_tool_use` p50, one machine, n=50: 103 ms → 81 ms.
|
|
12
|
+
*
|
|
13
|
+
* The `fingerprint` field is what makes the fast path SAFE rather than merely
|
|
14
|
+
* fast, and it is deliberately content-derived. The first version compared
|
|
15
|
+
* mtimes and that was a measured defect: on a fresh `actions/checkout` both
|
|
16
|
+
* files carry the checkout timestamp in whatever order git wrote them, so
|
|
17
|
+
* whether the optimisation applied was a coin flip — it won on a PR run
|
|
18
|
+
* (p95 129 ms) and lost on the trunk (p95 186 ms) for the same commit.
|
|
19
|
+
*
|
|
20
|
+
* The fingerprint helper is IMPORTED from the reader rather than reimplemented
|
|
21
|
+
* here, so the writer and the reader cannot drift apart.
|
|
22
|
+
*
|
|
23
|
+
* Run after editing the manifest:
|
|
24
|
+
* ./scripts-run src/scripts/compile_hook_manifest
|
|
25
|
+
*
|
|
26
|
+
* `tests/hooks/hook_manifest_compiled.test.ts` fails if the committed JSON
|
|
27
|
+
* stops matching the YAML, so a forgotten run is caught rather than shipped.
|
|
28
|
+
*
|
|
29
|
+
* Exit codes: 0 written (or already current) · 2 internal error.
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
import { parse as parseYaml } from 'yaml';
|
|
35
|
+
|
|
36
|
+
import { _manifest_fingerprint } from './hooks/dispatch_hook.js';
|
|
37
|
+
|
|
38
|
+
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
39
|
+
const YAML_PATH = path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.yaml');
|
|
40
|
+
const JSON_PATH = path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.json');
|
|
41
|
+
|
|
42
|
+
export function compile(yamlText: string): string {
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
fingerprint: _manifest_fingerprint(yamlText),
|
|
45
|
+
manifest: parseYaml(yamlText, { version: '1.1' }) as unknown,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function main(): number {
|
|
50
|
+
const text = fs.readFileSync(YAML_PATH, 'utf-8');
|
|
51
|
+
const next = compile(text);
|
|
52
|
+
const current = fs.existsSync(JSON_PATH) ? fs.readFileSync(JSON_PATH, 'utf-8') : null;
|
|
53
|
+
if (current === next) {
|
|
54
|
+
process.stdout.write('compile_hook_manifest: already current\n');
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
fs.writeFileSync(JSON_PATH, next);
|
|
58
|
+
process.stdout.write(
|
|
59
|
+
`compile_hook_manifest: wrote ${path.relative(REPO_ROOT, JSON_PATH)} ` +
|
|
60
|
+
`(${String(next.length)} bytes, fingerprint ${_manifest_fingerprint(text)})\n`,
|
|
61
|
+
);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (process.argv[1] !== undefined && fs.realpathSync(process.argv[1]).includes('compile_hook_manifest')) {
|
|
66
|
+
process.exit(main());
|
|
67
|
+
}
|
|
@@ -98,6 +98,14 @@ import {
|
|
|
98
98
|
} from './ai_council/qualification_wiring.js';
|
|
99
99
|
import { PROBE_STORE_RELPATH, readProbeStore, type ProbeStore } from './ai_council/probe_store.js';
|
|
100
100
|
import { appendEvent, type QuorumCommand, type QuorumDispatch } from './ai_council/events_log.js';
|
|
101
|
+
import { _mapToObject } from './_lib/map_to_object.js';
|
|
102
|
+
import { synthesizeAiCouncilBlock as _synthesize_ai_council_block } from './_lib/council_settings_block.js';
|
|
103
|
+
import {
|
|
104
|
+
fallbackPostureFor,
|
|
105
|
+
renderPostureLines,
|
|
106
|
+
type FallbackPosture,
|
|
107
|
+
} from './_lib/council_fallback_posture.js';
|
|
108
|
+
import { buildFallbackOptions } from './_lib/council_fallback_wiring.js';
|
|
101
109
|
import {
|
|
102
110
|
type ClassificationResult,
|
|
103
111
|
type SizeFitVerdict,
|
|
@@ -110,6 +118,7 @@ import type {
|
|
|
110
118
|
DebateCheckpoint,
|
|
111
119
|
RenderAbsentMember} from './ai_council/orchestrator.js';
|
|
112
120
|
import {
|
|
121
|
+
type CliFallbackOptions,
|
|
113
122
|
ConsensusResult,
|
|
114
123
|
CostBudget,
|
|
115
124
|
CouncilQuestion,
|
|
@@ -458,131 +467,7 @@ function load_settings(
|
|
|
458
467
|
return settings;
|
|
459
468
|
}
|
|
460
469
|
|
|
461
|
-
function _synthesize_ai_council_block(cfg: CouncilConfig): Dict {
|
|
462
|
-
const members: Dict = {};
|
|
463
|
-
for (const [name, m] of cfg.members) {
|
|
464
|
-
const entry: Dict = { enabled: m.enabled, model: m.model };
|
|
465
|
-
if (m.api_key_ref !== null) {
|
|
466
|
-
entry['api_key_ref'] = m.api_key_ref;
|
|
467
|
-
}
|
|
468
|
-
if (m.mode !== null) {
|
|
469
|
-
entry['mode'] = m.mode;
|
|
470
|
-
}
|
|
471
|
-
if (m.binary !== null) {
|
|
472
|
-
entry['binary'] = m.binary;
|
|
473
|
-
}
|
|
474
|
-
if (m.model_ladder && m.model_ladder.length > 0) {
|
|
475
|
-
entry['model_ladder'] = [...m.model_ladder];
|
|
476
|
-
}
|
|
477
|
-
// road-to-cache-economy Phase 4: forward only an explicit override —
|
|
478
|
-
// the '5m' default needs no key, keeping the synthesized block
|
|
479
|
-
// byte-identical to pre-Phase-4 output when nobody opted in.
|
|
480
|
-
if (m.prompt_cache_ttl !== '5m') {
|
|
481
|
-
entry['prompt_cache_ttl'] = m.prompt_cache_ttl;
|
|
482
|
-
}
|
|
483
|
-
members[name] = entry;
|
|
484
|
-
}
|
|
485
|
-
const advisors: Dict = {};
|
|
486
|
-
for (const [name, a] of cfg.advisors) {
|
|
487
|
-
const entry: Dict = {
|
|
488
|
-
enabled: a.enabled,
|
|
489
|
-
member: a.member,
|
|
490
|
-
persona: a.persona,
|
|
491
|
-
};
|
|
492
|
-
if (a.model !== null) {
|
|
493
|
-
entry['model'] = a.model;
|
|
494
|
-
}
|
|
495
|
-
advisors[name] = entry;
|
|
496
|
-
}
|
|
497
|
-
const lensCostDisclosure: Dict = {};
|
|
498
|
-
for (const [lens, cd] of cfg.lens_overrides.cost_disclosure) {
|
|
499
|
-
lensCostDisclosure[lens] = {
|
|
500
|
-
mode: cd.mode,
|
|
501
|
-
threshold_usd: cd.threshold_usd,
|
|
502
|
-
show_per_member: cd.show_per_member,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
const lensModelDowngrade: Dict = {};
|
|
506
|
-
for (const [lens, md] of cfg.lens_overrides.model_downgrade) {
|
|
507
|
-
lensModelDowngrade[lens] = { enabled: md.enabled, auto_apply: md.auto_apply };
|
|
508
|
-
}
|
|
509
|
-
return {
|
|
510
|
-
enabled: cfg.enabled,
|
|
511
|
-
mode: cfg.defaults.mode,
|
|
512
|
-
min_rounds: cfg.defaults.min_rounds,
|
|
513
|
-
deep_min_rounds: cfg.defaults.deep_min_rounds,
|
|
514
|
-
max_output_tokens: cfg.defaults.max_output_tokens,
|
|
515
|
-
session_retention_days: cfg.defaults.session_retention_days,
|
|
516
|
-
debate_max_rounds: cfg.defaults.debate_max_rounds,
|
|
517
|
-
cost_budget: {
|
|
518
|
-
max_input_tokens: cfg.cost_budget.max_input_tokens,
|
|
519
|
-
max_output_tokens: cfg.cost_budget.max_output_tokens,
|
|
520
|
-
max_calls: cfg.cost_budget.max_calls,
|
|
521
|
-
max_total_usd: cfg.cost_budget.max_total_usd,
|
|
522
|
-
daily_limit_usd: cfg.cost_budget.daily_limit_usd,
|
|
523
|
-
},
|
|
524
|
-
consensus_scoring: {
|
|
525
|
-
enabled: cfg.consensus_scoring.enabled,
|
|
526
|
-
strong_threshold: cfg.consensus_scoring.strong_threshold,
|
|
527
|
-
minority_threshold: cfg.consensus_scoring.minority_threshold,
|
|
528
|
-
lenses: [...cfg.consensus_scoring.lenses],
|
|
529
|
-
},
|
|
530
|
-
cli_call_budget: {
|
|
531
|
-
max_calls_per_day: _mapToObject(cfg.cli_call_budget.max_calls_per_day),
|
|
532
|
-
warn_at: cfg.cli_call_budget.warn_at,
|
|
533
|
-
},
|
|
534
|
-
// Phase 3.3 (road-to-always-on-orchestration): was validated by
|
|
535
|
-
// `_build_config`/`_build_quorum` but never forwarded into the
|
|
536
|
-
// synthesized block, so `build_members` always saw `undefined` and
|
|
537
|
-
// silently fell back to `'majority'` regardless of the user's
|
|
538
|
-
// configured `quorum: <k>`. Additive key — callers that never read
|
|
539
|
-
// it observe no change.
|
|
540
|
-
quorum: cfg.quorum,
|
|
541
|
-
// Forwarded for the same reason `quorum` above had to be: a key that
|
|
542
|
-
// `_build_config` validates and this block drops is a key the loader
|
|
543
|
-
// enforces and the runtime never sees. That defect shipped once here
|
|
544
|
-
// already; repeating it for the shadow floor would silently pin every
|
|
545
|
-
// `floor_would_hold` to the default no matter what the operator set.
|
|
546
|
-
quorum_min_present: cfg.quorum_min_present,
|
|
547
|
-
necessity_classifier: {
|
|
548
|
-
enabled: cfg.necessity_classifier.enabled,
|
|
549
|
-
mode: cfg.necessity_classifier.mode,
|
|
550
|
-
user_explicit_mode: cfg.necessity_classifier.user_explicit_mode,
|
|
551
|
-
},
|
|
552
|
-
model_downgrade: {
|
|
553
|
-
enabled: cfg.model_downgrade.enabled,
|
|
554
|
-
auto_apply: cfg.model_downgrade.auto_apply,
|
|
555
|
-
},
|
|
556
|
-
debate: {
|
|
557
|
-
max_cost_usd: cfg.debate.max_cost_usd,
|
|
558
|
-
cost_disclosure: {
|
|
559
|
-
mode: cfg.debate.cost_disclosure.mode,
|
|
560
|
-
threshold_usd: cfg.debate.cost_disclosure.threshold_usd,
|
|
561
|
-
show_per_member: cfg.debate.cost_disclosure.show_per_member,
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
lens_overrides: {
|
|
565
|
-
necessity_classifier_mode: _mapToObject(
|
|
566
|
-
cfg.lens_overrides.necessity_classifier_mode,
|
|
567
|
-
),
|
|
568
|
-
necessity_classifier_user_explicit_mode: _mapToObject(
|
|
569
|
-
cfg.lens_overrides.necessity_classifier_user_explicit_mode,
|
|
570
|
-
),
|
|
571
|
-
model_downgrade: lensModelDowngrade,
|
|
572
|
-
cost_disclosure: lensCostDisclosure,
|
|
573
|
-
},
|
|
574
|
-
members,
|
|
575
|
-
advisors,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
470
|
|
|
579
|
-
function _mapToObject<V>(m: ReadonlyMap<string, V>): Record<string, V> {
|
|
580
|
-
const out: Record<string, V> = {};
|
|
581
|
-
for (const [k, v] of m) {
|
|
582
|
-
out[k] = v;
|
|
583
|
-
}
|
|
584
|
-
return out;
|
|
585
|
-
}
|
|
586
471
|
|
|
587
472
|
// ── member construction ─────────────────────────────────────────────
|
|
588
473
|
|
|
@@ -634,6 +519,12 @@ interface BuildMembersOptions {
|
|
|
634
519
|
* caller that passed no store means "not evaluated" — NOT "all qualified".
|
|
635
520
|
*/
|
|
636
521
|
qualification_out?: MemberQualification[] | null;
|
|
522
|
+
/**
|
|
523
|
+
* Out-param for the mid-flight fallback wiring (the `quorum_out`
|
|
524
|
+
* convention). Populated by `council_fallback_wiring.buildFallbackOptions`;
|
|
525
|
+
* stays `null` when the caller passes no ref.
|
|
526
|
+
*/
|
|
527
|
+
fallback_out?: { options: CliFallbackOptions | null } | null;
|
|
637
528
|
}
|
|
638
529
|
|
|
639
530
|
/**
|
|
@@ -666,6 +557,7 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
|
|
|
666
557
|
const quorum_out = opts.quorum_out ?? null;
|
|
667
558
|
const probe_store = opts.probe_store ?? null;
|
|
668
559
|
const qualification_out = opts.qualification_out ?? null;
|
|
560
|
+
const fallback_out = opts.fallback_out ?? null;
|
|
669
561
|
const qualifications: MemberQualification[] = [];
|
|
670
562
|
// Read-only, per-process-memoised (`detectEnvironment` caches the
|
|
671
563
|
// no-argument call) — cheap to call once per `build_members` invocation.
|
|
@@ -987,6 +879,19 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
|
|
|
987
879
|
'prints the resolved path.',
|
|
988
880
|
);
|
|
989
881
|
}
|
|
882
|
+
if (fallback_out !== null) {
|
|
883
|
+
// Absent/malformed block means `false`, never a throw. Why quota
|
|
884
|
+
// fall-through is opt-in: `FallbackPolicy.apiOnQuota`.
|
|
885
|
+
const fallback_cfg = _isDict(ai['fallback']) ? (ai['fallback'] as Dict) : {};
|
|
886
|
+
fallback_out.options = buildFallbackOptions({
|
|
887
|
+
apiOnQuota: _pyBool(fallback_cfg['api_on_quota']),
|
|
888
|
+
hasApiRung: (p) => _API_PROVIDERS.has(p),
|
|
889
|
+
memberConfig: (p) => ((members_cfg[p] as Dict) || {}) as Dict,
|
|
890
|
+
modelOverride: (p) => (overrides[p] as string | undefined) ?? null,
|
|
891
|
+
constructApi: _construct_api_member,
|
|
892
|
+
emit: appendEvent,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
990
895
|
return members;
|
|
991
896
|
}
|
|
992
897
|
|
|
@@ -1447,6 +1352,7 @@ function _maybe_run_chairman(
|
|
|
1447
1352
|
table: PriceTable,
|
|
1448
1353
|
project: unknown,
|
|
1449
1354
|
args: Args,
|
|
1355
|
+
cli_fallback: CliFallbackOptions | null = null,
|
|
1450
1356
|
): ChairmanResult | null {
|
|
1451
1357
|
const ch = (_isDict(ai_cfg) ? (ai_cfg['chairman'] as Dict) : null) || {};
|
|
1452
1358
|
const mode = typeof ch['mode'] === 'string' ? (ch['mode'] as string) : 'host';
|
|
@@ -1515,10 +1421,18 @@ function _maybe_run_chairman(
|
|
|
1515
1421
|
user_prompt: synth_prompt,
|
|
1516
1422
|
max_tokens: question.max_tokens,
|
|
1517
1423
|
});
|
|
1424
|
+
// The synthesis falls back like any other billable pass: an individual
|
|
1425
|
+
// seat has N−1 redundancy behind it, this has none, so a transport failure
|
|
1426
|
+
// loses the artefact the whole pass exists for. The counter — a larger
|
|
1427
|
+
// chairman model whose twin may fail differently — is bounded by
|
|
1428
|
+
// `model_unservable` being eligible and by the retry's spend gate, and the
|
|
1429
|
+
// annotation below still reports FAILED rather than degrading silently.
|
|
1430
|
+
// `consult` builds its own ledger: this is a separate invocation.
|
|
1518
1431
|
const out = consult([client], synthQ, budget, {
|
|
1519
1432
|
table,
|
|
1520
1433
|
project: project as never,
|
|
1521
1434
|
original_ask: args.original_ask,
|
|
1435
|
+
cli_fallback,
|
|
1522
1436
|
});
|
|
1523
1437
|
const r = out[0] ?? null;
|
|
1524
1438
|
if (r === null || r.error !== null || r.text.trim() === '') {
|
|
@@ -1725,6 +1639,12 @@ function _resolve_max_tokens(args: Args, ai_cfg: Dict): number {
|
|
|
1725
1639
|
|
|
1726
1640
|
// ── subcommands ─────────────────────────────────────────────────────
|
|
1727
1641
|
|
|
1642
|
+
/**
|
|
1643
|
+
* Cost preview only — it prices members and never calls one, so there is no
|
|
1644
|
+
* mid-flight failure to fall back from. `fallback_out` here is a DECIDED
|
|
1645
|
+
* non-goal: two of the three `build_members` sites pass it, and without this
|
|
1646
|
+
* note the third re-opens on every callsite census.
|
|
1647
|
+
*/
|
|
1728
1648
|
function cmd_estimate(
|
|
1729
1649
|
args: Args,
|
|
1730
1650
|
opts: { settings?: Dict | null; members?: ExternalAIClient[] | null; table?: PriceTable | null } = {},
|
|
@@ -2289,6 +2209,28 @@ function _resolvedTransportFor(
|
|
|
2289
2209
|
});
|
|
2290
2210
|
}
|
|
2291
2211
|
|
|
2212
|
+
/** Gather what `fallbackPostureFor` needs; the decision itself is pure. */
|
|
2213
|
+
function _posture(
|
|
2214
|
+
name: string,
|
|
2215
|
+
member: { readonly binary: string | null; readonly api_key_ref: string | null },
|
|
2216
|
+
): FallbackPosture {
|
|
2217
|
+
const t = _resolvedTransportFor(name, member);
|
|
2218
|
+
const keyless: Readonly<Record<string, () => boolean>> = {
|
|
2219
|
+
anthropic: () => Boolean(load_anthropic_key()),
|
|
2220
|
+
openai: () => Boolean(load_openai_key()),
|
|
2221
|
+
};
|
|
2222
|
+
return fallbackPostureFor({
|
|
2223
|
+
transport: t.available ? t.transport : null,
|
|
2224
|
+
hasApiRung: _API_PROVIDERS.has(name),
|
|
2225
|
+
apiKeyRef: member.api_key_ref,
|
|
2226
|
+
refResolves:
|
|
2227
|
+
member.api_key_ref === null
|
|
2228
|
+
? undefined
|
|
2229
|
+
: _member_api_key_present({ api_key_ref: member.api_key_ref }),
|
|
2230
|
+
keylessResolves: keyless[name],
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2292
2234
|
export function cmd_status(args: Args, opts: { env?: Record<string, string | undefined> } = {}): number {
|
|
2293
2235
|
const env = opts.env ?? process.env;
|
|
2294
2236
|
const override = env[COUNCIL_CONFIG_ENV];
|
|
@@ -2346,6 +2288,12 @@ export function cmd_status(args: Args, opts: { env?: Record<string, string | und
|
|
|
2346
2288
|
];
|
|
2347
2289
|
}),
|
|
2348
2290
|
),
|
|
2291
|
+
fallback: {
|
|
2292
|
+
api_on_quota: cfg?.fallback.api_on_quota ?? false,
|
|
2293
|
+
posture: Object.fromEntries(
|
|
2294
|
+
enabledMembers.map(([n, m]) => [n, _posture(n, m)]),
|
|
2295
|
+
),
|
|
2296
|
+
},
|
|
2349
2297
|
ignored_transport_keys: cfg?.ignored_transport_keys ?? [],
|
|
2350
2298
|
qualification: qualificationJson(qualifications),
|
|
2351
2299
|
qualified_members: countableSeats(qualifications),
|
|
@@ -2384,6 +2332,12 @@ export function cmd_status(args: Args, opts: { env?: Record<string, string | und
|
|
|
2384
2332
|
const detail = t.available ? t.transport : `unavailable — ${t.reason ?? 'no usable transport'}`;
|
|
2385
2333
|
_stdout(` transport ${name}: ${detail}${billing}\n`);
|
|
2386
2334
|
}
|
|
2335
|
+
for (const line of renderPostureLines(
|
|
2336
|
+
enabledMembers.map(([n, m]) => [n, _posture(n, m)] as const),
|
|
2337
|
+
cfg.fallback.api_on_quota,
|
|
2338
|
+
)) {
|
|
2339
|
+
_stdout(`${line}\n`);
|
|
2340
|
+
}
|
|
2387
2341
|
// The four-value verdict per seat, then the advice — TWO warnings, so
|
|
2388
2342
|
// an `unavailable` seat is not told to re-run (R2 finding 12).
|
|
2389
2343
|
for (const line of qualificationStatusLines(qualifications)) {
|
|
@@ -2483,6 +2437,7 @@ function cmd_run(
|
|
|
2483
2437
|
const explicit_overrides = _parse_model_overrides(_getattr<string[] | null>(args, 'model', null));
|
|
2484
2438
|
const skipped: Dict[] = [];
|
|
2485
2439
|
const quorum_out: { result: QuorumResult | null } = { result: null };
|
|
2440
|
+
const fallback_out: { options: CliFallbackOptions | null } = { options: null };
|
|
2486
2441
|
if (members === null) {
|
|
2487
2442
|
members = build_members(settings, {
|
|
2488
2443
|
invocation_mode: args.mode_override,
|
|
@@ -2491,6 +2446,7 @@ function cmd_run(
|
|
|
2491
2446
|
skipped,
|
|
2492
2447
|
quorum_out,
|
|
2493
2448
|
probe_store: readProbeStore(REPO_ROOT),
|
|
2449
|
+
fallback_out,
|
|
2494
2450
|
});
|
|
2495
2451
|
}
|
|
2496
2452
|
// Measured, never assumed: `_apply_solo_dispatch` escalates back to the
|
|
@@ -2672,6 +2628,10 @@ function cmd_run(
|
|
|
2672
2628
|
stance_tally: stance_tally_on,
|
|
2673
2629
|
member_prompt_suffix,
|
|
2674
2630
|
no_project_context_members,
|
|
2631
|
+
// Mid-flight cli→api fallback (ai-council-config.md § failure-class-
|
|
2632
|
+
// gated). `null` when the caller injected `members` directly — a
|
|
2633
|
+
// pre-built roster carries no config to derive the factory from.
|
|
2634
|
+
cli_fallback: fallback_out.options,
|
|
2675
2635
|
// Interactive one-line confirm (cmd_run has no --auto-continue); the
|
|
2676
2636
|
// repaired-call cost is collected so cost_usd_actual stays honest.
|
|
2677
2637
|
on_stance_repair: stance_tally_on
|
|
@@ -2762,7 +2722,17 @@ function cmd_run(
|
|
|
2762
2722
|
{ persona_labels },
|
|
2763
2723
|
);
|
|
2764
2724
|
const consensus = _maybe_run_consensus(ai_cfg, question, members, responses, budget, table, project, args);
|
|
2765
|
-
const chairman = _maybe_run_chairman(
|
|
2725
|
+
const chairman = _maybe_run_chairman(
|
|
2726
|
+
ai_cfg,
|
|
2727
|
+
question,
|
|
2728
|
+
members,
|
|
2729
|
+
responses,
|
|
2730
|
+
budget,
|
|
2731
|
+
table,
|
|
2732
|
+
project,
|
|
2733
|
+
args,
|
|
2734
|
+
fallback_out.options,
|
|
2735
|
+
);
|
|
2766
2736
|
// road-to-council-blind-review Phase 1 (Ü1), host-path only: when no
|
|
2767
2737
|
// member chairman ran (mode === 'host'), the blind mapping is computed
|
|
2768
2738
|
// here (seeded from the question text) and persisted for a later
|
|
@@ -3018,6 +2988,9 @@ function cmd_debate(
|
|
|
3018
2988
|
// gap is declared in quorum-attendance-budget.json rather than left for a
|
|
3019
2989
|
// reader to discover from a missing row.
|
|
3020
2990
|
const quorum_out: { result: QuorumResult | null } = { result: null };
|
|
2991
|
+
// `null` when `build_members` is never called (injected members in
|
|
2992
|
+
// tests) — `run_debate` then behaves byte-identically to before.
|
|
2993
|
+
const fallback_out: { options: CliFallbackOptions | null } = { options: null };
|
|
3021
2994
|
if (members === null) {
|
|
3022
2995
|
members = build_members(settings, {
|
|
3023
2996
|
invocation_mode: args.mode_override,
|
|
@@ -3027,6 +3000,7 @@ function cmd_debate(
|
|
|
3027
3000
|
quorum_out,
|
|
3028
3001
|
probe_store: readProbeStore(REPO_ROOT),
|
|
3029
3002
|
command: 'debate',
|
|
3003
|
+
fallback_out,
|
|
3030
3004
|
});
|
|
3031
3005
|
}
|
|
3032
3006
|
if (table === null) {
|
|
@@ -3225,6 +3199,7 @@ function cmd_debate(
|
|
|
3225
3199
|
on_restate: restate_on ? on_restate : null,
|
|
3226
3200
|
advisor_plans,
|
|
3227
3201
|
seed_round_1: seed,
|
|
3202
|
+
cli_fallback: fallback_out.options,
|
|
3228
3203
|
});
|
|
3229
3204
|
} catch (exc) {
|
|
3230
3205
|
if (exc instanceof DebateCapExceeded) {
|