@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.
Files changed (116) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +286 -218
  3. package/agents/templates/.ai-council.yml.example +35 -0
  4. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +83 -12
  5. package/dist/agent-src/rules/roadmap-progress-sync.md +48 -4
  6. package/dist/agent-src/scripts/resume_probe.ts +154 -9
  7. package/dist/agent-src/scripts/roadmap_gates.ts +210 -6
  8. package/dist/agent-src/skills/roadmap-management/SKILL.md +9 -4
  9. package/dist/agent-src/templates/agent-settings.md +70 -0
  10. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  11. package/dist/agent-src/templates/scripts/telemetry/remote.ts +571 -0
  12. package/dist/agent-src/templates/scripts/telemetry/settings.ts +222 -44
  13. package/dist/cli/python/workspace_drive.js +8 -3
  14. package/dist/cli/python/workspace_drive.js.map +1 -1
  15. package/dist/cli/python/workspace_hosts.js +16 -12
  16. package/dist/cli/python/workspace_hosts.js.map +1 -1
  17. package/dist/cli/registry.js +2 -0
  18. package/dist/cli/registry.js.map +1 -1
  19. package/dist/cli-delegate/{chunk-F3YUF7SA.js → chunk-3UCJWMMP.js} +1 -1
  20. package/dist/cli-delegate/chunk-F7DQWYR5.js +69 -0
  21. package/dist/cli-delegate/{chunk-7PZ4FSZB.js → chunk-FBKL6JWT.js} +61 -2
  22. package/dist/cli-delegate/{chunk-MNWRDMR5.js → chunk-PKOBQADP.js} +1 -1
  23. package/dist/cli-delegate/cmd_conformance.js +26 -11
  24. package/dist/cli-delegate/cmd_doctor.js +2 -2
  25. package/dist/cli-delegate/cmd_handoff.js +3 -3
  26. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  27. package/dist/cli-delegate/cmd_settings_set.js +17 -3
  28. package/dist/discovery/deprecation-report.md +1 -1
  29. package/dist/discovery/discovery-manifest.json +4 -4
  30. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  31. package/dist/discovery/discovery-manifest.summary.md +1 -1
  32. package/dist/discovery/dormancy-report.md +1 -1
  33. package/dist/discovery/orphan-report.md +1 -1
  34. package/dist/discovery/packs.json +2 -2
  35. package/dist/discovery/trust-report.md +1 -1
  36. package/dist/discovery/workspaces.json +2 -2
  37. package/dist/hooks/dispatch.js +3684 -1719
  38. package/dist/mcp/registry-manifest.json +2 -2
  39. package/dist/mcp/server.json +2 -2
  40. package/dist/mcp/server.mjs +1070 -408
  41. package/dist/scripts/_lib/host_launch.js +66 -0
  42. package/dist/scripts/_lib/host_launch.js.map +1 -0
  43. package/dist/server/io/yamlIO.js +175 -3
  44. package/dist/server/io/yamlIO.js.map +1 -1
  45. package/dist/server/routes/wizard.js +20 -1
  46. package/dist/server/routes/wizard.js.map +1 -1
  47. package/dist/shared/settingsConsent.js +25 -0
  48. package/dist/shared/settingsConsent.js.map +1 -1
  49. package/docs/contracts/settings-classes.md +11 -0
  50. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +2 -1
  51. package/package.json +1 -1
  52. package/src/agent-src/scripts/resume_probe.ts +154 -9
  53. package/src/agent-src/scripts/roadmap_gates.ts +210 -6
  54. package/src/agent-src/templates/scripts/telemetry/remote.ts +571 -0
  55. package/src/agent-src/templates/scripts/telemetry/settings.ts +222 -44
  56. package/src/config/estate-count-budget.json +120 -0
  57. package/src/config/evaluator-budgets.json +4 -4
  58. package/src/config/gate-coverage.yml +32 -4
  59. package/src/config/gate-violation-baselines.json +63 -63
  60. package/src/config/gitignore-block.txt +5 -0
  61. package/src/config/hook-latency-budget.json +40 -1
  62. package/src/config/hook-token-budget.json +16 -0
  63. package/src/config/rule-stub-ceilings.json +11 -3
  64. package/src/scripts/_dispatch.bash +42 -1
  65. package/src/scripts/_lib/council_fallback_posture.ts +81 -0
  66. package/src/scripts/_lib/council_fallback_wiring.ts +126 -0
  67. package/src/scripts/_lib/council_settings_block.ts +152 -0
  68. package/src/scripts/_lib/headless_invocation.ts +285 -0
  69. package/src/scripts/_lib/host_launch.ts +72 -0
  70. package/src/scripts/_lib/map_to_object.ts +16 -0
  71. package/src/scripts/_lib/roadmap_checkboxes.ts +88 -0
  72. package/src/scripts/_lib/run_checkpoint.ts +409 -0
  73. package/src/scripts/_lib/session_register.ts +32 -1
  74. package/src/scripts/_lib/stdin.ts +35 -1
  75. package/src/scripts/_lib/turn_end_refusals.ts +12 -4
  76. package/src/scripts/_lib/unattended_guard.ts +371 -0
  77. package/src/scripts/ai_council/config.ts +27 -1
  78. package/src/scripts/ai_council/events_log.ts +12 -1
  79. package/src/scripts/ai_council/fallback_config.ts +114 -0
  80. package/src/scripts/ai_council/mid_flight_fallback.ts +295 -0
  81. package/src/scripts/ai_council/orchestrator.ts +251 -292
  82. package/src/scripts/ai_council/py_parity.ts +139 -0
  83. package/src/scripts/ai_council/response_render.ts +99 -0
  84. package/src/scripts/ai_council/spend_gate.ts +123 -0
  85. package/src/scripts/ai_council/transport_resolver.ts +81 -13
  86. package/src/scripts/bench_hook_injection.ts +100 -6
  87. package/src/scripts/bench_hook_latency.ts +442 -16
  88. package/src/scripts/check_estate_count.ts +924 -0
  89. package/src/scripts/compile_hook_manifest.ts +67 -0
  90. package/src/scripts/council_cli.ts +100 -125
  91. package/src/scripts/decision_memo.ts +325 -0
  92. package/src/scripts/hook_manifest.json +1 -0
  93. package/src/scripts/hook_manifest.yaml +172 -25
  94. package/src/scripts/hooks/concern_registry.ts +6 -0
  95. package/src/scripts/hooks/dispatch_hook.ts +183 -10
  96. package/src/scripts/hooks/dispatch_issues.ts +26 -0
  97. package/src/scripts/hooks/end_review_nudge_hook.ts +97 -24
  98. package/src/scripts/hooks/hook_stdin.ts +111 -5
  99. package/src/scripts/hooks/injection_budget.ts +595 -0
  100. package/src/scripts/hooks/payload_stub.ts +446 -0
  101. package/src/scripts/hooks/run_continuation_hook.ts +627 -0
  102. package/src/scripts/hooks/session_eol_hook.ts +52 -0
  103. package/src/scripts/hooks/telemetry_usage_hook.ts +339 -0
  104. package/src/scripts/hooks/tool_result_bytes_hook.ts +27 -0
  105. package/src/scripts/hooks/turn_end_gate_hook.ts +45 -7
  106. package/src/scripts/hooks_doctor.ts +16 -2
  107. package/src/scripts/interruption_report.ts +330 -13
  108. package/src/scripts/lint_hook_manifest.ts +167 -0
  109. package/src/scripts/roadmap_progress_hook.ts +254 -46
  110. package/src/scripts/run_supervise.ts +623 -0
  111. package/src/scripts/session_register_hook.ts +108 -8
  112. package/src/scripts/sessions_cli.ts +18 -6
  113. package/src/scripts/telemetry_disclosure_hook.ts +244 -0
  114. package/src/server/io/yamlIO.ts +167 -3
  115. package/src/shared/settingsConsent.ts +49 -2
  116. package/dist/cli-delegate/chunk-MMJL4TIX.js +0 -18
@@ -0,0 +1,295 @@
1
+ /**
2
+ * mid_flight_fallback — the cli→api retry's types, its establish step, and its
3
+ * response stamp.
4
+ *
5
+ * Extracted from `orchestrator.ts` rather than written there, and the ordering
6
+ * is the point (`gate-violation-baselines.json` § check_source_size_budget:
7
+ * "extract, then measure"): the orchestrator sits far over the 1,500-line
8
+ * source ceiling, so the machinery of a new mechanism belongs beside it, not
9
+ * inside it. The extraction also removes a real duplication — the classify →
10
+ * eligibility → ledger → construct sequence ran twice, once in `_run_round`'s
11
+ * non-billable branch and once in its retry block.
12
+ *
13
+ * The full behavioural contract lives in
14
+ * `docs/contracts/ai-council-config.md` § "Mid-flight fallback is
15
+ * failure-class-gated". This file carries the reasons a reader needs AT the
16
+ * code and points at that section for the rest, rather than restating it.
17
+ */
18
+
19
+ import type { ExternalAIClient, CouncilResponse } from './clients.js';
20
+ import {
21
+ classifyCliFailure,
22
+ isFallbackEligibleUnder,
23
+ type FallbackPolicy,
24
+ type MidFlightFallback,
25
+ } from './transport_resolver.js';
26
+
27
+ /** One escalation's outcome, for the event sink. */
28
+ export type FallbackOutcome = 'retried' | 'no_twin' | 'cost_budget';
29
+
30
+ export interface FallbackEvent {
31
+ readonly provider: string;
32
+ readonly failure: string;
33
+ readonly outcome: FallbackOutcome;
34
+ readonly api_on_quota: boolean;
35
+ }
36
+
37
+ export interface CliFallbackOptions {
38
+ /**
39
+ * Build the api twin for `provider`, or `null` when it cannot be built —
40
+ * no api constructor, or its strict `api_key_ref` contract refuses.
41
+ * Called lazily, at most once per provider per invocation.
42
+ */
43
+ readonly construct: (provider: string) => ExternalAIClient | null;
44
+ /** `ai_council.fallback.api_on_quota` — see `FallbackPolicy.apiOnQuota`. */
45
+ readonly api_on_quota: boolean;
46
+ /**
47
+ * Called once per ESTABLISHING escalation, never per substituted call.
48
+ *
49
+ * The sink is the caller's: the orchestrator is a pure library with no
50
+ * stdout and no disk, and reaching for `events_log` there would be the
51
+ * first thing to break that. The CLI wires this to `appendEvent`.
52
+ */
53
+ readonly on_event?: ((e: FallbackEvent) => void) | null;
54
+ }
55
+
56
+ /** A provider's api twin, plus why it replaced the cli transport. */
57
+ export interface EstablishedTwin {
58
+ readonly client: ExternalAIClient;
59
+ readonly reason: string;
60
+ readonly original_error: string;
61
+ }
62
+
63
+ /**
64
+ * Why the twin map exists alongside the ledger.
65
+ *
66
+ * `MidFlightFallback` hands out `'api'` at most ONCE per provider. Without a
67
+ * substitution map an invocation-scoped ledger is therefore strictly WORSE
68
+ * than a per-round one: on round 2 the dead cli member is called again, fails
69
+ * again, and the ledger answers `'stop'` — the seat is lost for the rest of
70
+ * the pass, having fallen back exactly once. With the map, the provider is
71
+ * replaced BEFORE the call from the round after it fell back, so the dead
72
+ * binary is never spawned twice and nothing is constructed twice.
73
+ *
74
+ * The cost, stated rather than hidden: a TRANSIENT single-round failure moves
75
+ * the seat to the metered rung for the rest of the invocation. The mitigation
76
+ * is visibility, not a narrower scope — every fallen-back response carries the
77
+ * stamp `stampFallback` applies.
78
+ */
79
+ export type TwinMap = Map<string, EstablishedTwin>;
80
+
81
+ export interface EstablishInput {
82
+ readonly member: ExternalAIClient;
83
+ readonly error: string;
84
+ readonly fallback: CliFallbackOptions;
85
+ readonly ledger: MidFlightFallback;
86
+ }
87
+
88
+ /**
89
+ * Decide whether this cli failure establishes a twin, and build it if so.
90
+ *
91
+ * Returns `null` for every non-escalating outcome: an ineligible class, a
92
+ * ledger refusal, or no constructible twin.
93
+ *
94
+ * It emits ONLY the `no_twin` event, and that split is deliberate. `no_twin`
95
+ * is final here — the provider has no api rung and nothing downstream can
96
+ * change that. Whether a CONSTRUCTED twin ends as `retried` or `cost_budget`
97
+ * is not knowable at this point, because the caller's projected-spend gate has
98
+ * not run yet; emitting `retried` here would report an escalation that the
99
+ * budget then refused. An ineligible class emits nothing at all: there was no
100
+ * escalation to report.
101
+ */
102
+ export function establishTwin(input: EstablishInput): EstablishedTwin | null {
103
+ const { member, error, fallback, ledger } = input;
104
+ const failure = classifyCliFailure(error);
105
+ const policy: FallbackPolicy = { apiOnQuota: fallback.api_on_quota };
106
+ if (!isFallbackEligibleUnder(failure, policy)) return null;
107
+ if (ledger.attempt(member.name, failure, policy) !== 'api') return null;
108
+
109
+ const twin = fallback.construct(member.name);
110
+ if (twin === null) {
111
+ fallback.on_event?.({
112
+ provider: member.name,
113
+ failure,
114
+ outcome: 'no_twin',
115
+ api_on_quota: fallback.api_on_quota,
116
+ });
117
+ return null;
118
+ }
119
+ return { client: twin, reason: failure, original_error: error };
120
+ }
121
+
122
+ /** The caller's half of the event: the outcome only it can know. */
123
+ export function emitOutcome(
124
+ fallback: CliFallbackOptions,
125
+ provider: string,
126
+ twin: EstablishedTwin,
127
+ outcome: Exclude<FallbackOutcome, 'no_twin'>,
128
+ ): void {
129
+ fallback.on_event?.({
130
+ provider,
131
+ failure: twin.reason,
132
+ outcome,
133
+ api_on_quota: fallback.api_on_quota,
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Stamp a response that answered over a fallen-back transport.
139
+ *
140
+ * `sticky` separates the rounds that REUSED an established twin from the one
141
+ * round that established it — a reader counting escalations counts the latter,
142
+ * and two rendered lines must not read as two escalations.
143
+ */
144
+ export function stampFallback(
145
+ response: CouncilResponse,
146
+ twin: EstablishedTwin,
147
+ sticky: boolean,
148
+ ): void {
149
+ response.metadata = {
150
+ ...(response.metadata ?? {}),
151
+ fallback_from: 'cli',
152
+ fallback_reason: twin.reason,
153
+ fallback_original_error: twin.original_error,
154
+ ...(sticky ? { fallback_sticky: true } : {}),
155
+ };
156
+ }
157
+
158
+ /** True when this member is on the cli rung and its call failed. */
159
+ export function isFailedCliCall(
160
+ member: ExternalAIClient,
161
+ error: string | null,
162
+ transport: string,
163
+ ): boolean {
164
+ return error !== null && String(error) !== '' && transport === 'cli';
165
+ }
166
+
167
+ /** What the gated retry needs from the round it runs inside. */
168
+ export interface GatedRetryDeps {
169
+ /** Projected-spend verdict for one call by `client`. `true` → proceed. */
170
+ readonly gate: (client: ExternalAIClient) => boolean;
171
+ /** Perform the call, returning an error-tagged response rather than throwing. */
172
+ readonly call: (client: ExternalAIClient) => CouncilResponse;
173
+ }
174
+
175
+ export interface GatedRetryResult {
176
+ readonly response: CouncilResponse;
177
+ /** The member the round should bill and stamp — the twin, or the original. */
178
+ readonly effective: ExternalAIClient;
179
+ readonly retried: boolean;
180
+ }
181
+
182
+ /**
183
+ * Run the retry an established twin earns, behind its own spend gate.
184
+ *
185
+ * The gate is separate from the round's because the retry is METERED even when
186
+ * the failed cli call was not: an unmetered seat that falls back has never been
187
+ * priced against the running totals, so reusing the loop-head verdict would let
188
+ * it through unpriced.
189
+ *
190
+ * A refusal returns the ORIGINAL failure with `fallback_skipped: cost_budget`
191
+ * on it — named, so a reader can tell "not retried" from "retry refused".
192
+ */
193
+ export function runGatedRetry(
194
+ original: CouncilResponse,
195
+ member: ExternalAIClient,
196
+ twin: EstablishedTwin,
197
+ fallback: CliFallbackOptions,
198
+ deps: GatedRetryDeps,
199
+ ledger: MidFlightFallback | null = null,
200
+ ): GatedRetryResult {
201
+ if (!deps.gate(twin.client)) {
202
+ original.metadata = { ...(original.metadata ?? {}), fallback_skipped: 'cost_budget' };
203
+ emitOutcome(fallback, member.name, twin, 'cost_budget');
204
+ // The claim goes back, for the same reason it does on the unmetered
205
+ // path: the twin was built and never called, so the provider has not
206
+ // spent its one escalation. R2 round 3, finding 4 — the unmetered
207
+ // refusal released and this one did not, so the two paths this
208
+ // function's own docblock calls parallel behaved differently, and a
209
+ // billable community-cli seat was permanently lost after a single
210
+ // budget-refused escalation.
211
+ ledger?.release(member.name);
212
+ return { response: original, effective: member, retried: false };
213
+ }
214
+ const response = deps.call(twin.client);
215
+ stampFallback(response, twin, false);
216
+ emitOutcome(fallback, member.name, twin, 'retried');
217
+ return { response, effective: twin.client, retried: true };
218
+ }
219
+
220
+ /**
221
+ * The unmetered-seat escalation, as one call.
222
+ *
223
+ * A vendor-official CLI member is `billable = false` + `transport = 'cli'`,
224
+ * which is the shape the round's non-billable branch returns early for — so
225
+ * before this existed the retry could only ever fire for the two community
226
+ * subclasses that consume an API key. Returns the established twin, or `null`
227
+ * when nothing escalated.
228
+ */
229
+ export function escalateUnmetered(
230
+ member: ExternalAIClient,
231
+ response: CouncilResponse,
232
+ transport: string,
233
+ fallback: CliFallbackOptions | null,
234
+ ledger: MidFlightFallback | null,
235
+ ): EstablishedTwin | null {
236
+ if (fallback === null || ledger === null) return null;
237
+ if (!isFailedCliCall(member, response.error, transport)) return null;
238
+ const twin = establishTwin({
239
+ member,
240
+ error: String(response.error),
241
+ fallback,
242
+ ledger,
243
+ });
244
+ if (twin === null) return null;
245
+ // NOTHING is emitted here, and the previous comment justifying an emit at
246
+ // establishment is deleted rather than reworded because it stopped being
247
+ // true. It read: "on this path the twin goes through the round's ORDINARY
248
+ // spend gate, whose refusal surfaces as `cost_budget_exceeded` on the
249
+ // response rather than as a fallback event." Round 1's finding-3 fix made
250
+ // a refusal surface as exactly such an event, so an `retried` written here
251
+ // and a `cost_budget` written there put two contradictory lines in the log
252
+ // for one seat — and the first of them claims a seat was saved that was
253
+ // not. R2 round 2, finding 4.
254
+ //
255
+ // The caller emits once, on the outcome it actually observed:
256
+ // `refuseUnmeteredEscalation` for the refusal, `emitOutcome(…, 'retried')`
257
+ // immediately before the metered call for the acceptance.
258
+ return twin;
259
+ }
260
+
261
+ /**
262
+ * A budget-refused escalation of an UNMETERED seat, as one call.
263
+ *
264
+ * The seat degrades to its own original cli failure — never to an abort, and
265
+ * never to the round-wide short-circuit. Before the escalation existed a
266
+ * `billable: false` member returned before the spend gate and could not reach
267
+ * it at all, so a dead free CLI cost its own seat and nothing else; falling
268
+ * through priced as the metered twin put it one branch from aborting EVERY
269
+ * remaining member, which the shipped claim `council-fallback-loses-zero-seats`
270
+ * says cannot happen. R2 round 1, finding 3.
271
+ *
272
+ * The shape matches {@link runGatedRetry}'s refusal path exactly: the original
273
+ * response, `fallback_skipped: cost_budget` so a reader can tell "not retried"
274
+ * from "retry refused", and one outcome on the event log.
275
+ *
276
+ * Rolls the sticky substitution back — the twin was never called, so a later
277
+ * round must re-decide rather than inherit a substitution that never happened.
278
+ * `ledger.release` undoes the once-per-provider claim for the same reason: a
279
+ * twin that never ran must not consume the provider's one escalation.
280
+ */
281
+ export function refuseUnmeteredEscalation(args: {
282
+ readonly original: CouncilResponse;
283
+ readonly declared: ExternalAIClient;
284
+ readonly twin: EstablishedTwin;
285
+ readonly fallback: CliFallbackOptions | null;
286
+ readonly ledger: MidFlightFallback | null;
287
+ readonly twins: TwinMap | null;
288
+ }): CouncilResponse {
289
+ const { original, declared, twin, fallback, ledger, twins } = args;
290
+ original.metadata = { ...(original.metadata ?? {}), fallback_skipped: 'cost_budget' };
291
+ if (fallback !== null) emitOutcome(fallback, declared.name, twin, 'cost_budget');
292
+ twins?.delete(declared.name);
293
+ ledger?.release(declared.name);
294
+ return original;
295
+ }