session-activity-tracker.js is correctly registered in
posttooluse-dispatcher.js's SYNC_HOOKS and fires on every tool call --
but buildActivityRow() only ever read the session id from
ECC_SESSION_ID/CLAUDE_SESSION_ID environment variables, which Claude
Code does not actually set for hook subprocesses. Every row silently
came back null and nothing was ever written to
~/.claude/metrics/tool-usage.jsonl, with no error anywhere.
The session id is available in the hook's own stdin JSON payload
(input.session_id) in real invocations. This prefers that, keeping the
env vars as a fallback. New test added; all 18 existing + new tests
pass.
Also adds a live Activity view to the capabilities dashboard
(scripts/dashboard-web.js) reading that same metrics file, so Skill
vs. Agent vs. raw-tool usage is visible in real time instead of
requiring a manual tail+jq -- verified live against a real session
before this commit. Documents the loopback-only, no-auth design and
the standard SSH-port-forwarding pattern for viewing it from another
device you control.
Adds /api/activity, reading ~/.claude/metrics/tool-usage.jsonl (the
session-activity-tracker hook wired in earlier), plus an Activity tab
polling it every 3s -- Skill/Agent calls visually distinct from raw
Bash/Edit/Write. Turns the tracker's log into an actual live proof
gate with a UI instead of a file someone has to tail and jq by hand.
Verified live: /api/activity already returns real entries from this
session before this commit was even pushed.
The guided installer asks how ECC hooks should run, but that consent
lived only in the wizard path. Running install-apply directly with a
profile that includes hooks-runtime still materialized the hook runtime
with no disclosure and no decision.
Gate the apply layer instead, so every entry point is covered:
- disclose the six hook capability groups when a plan would materialize
the hook runtime, and refuse to apply until the caller decides
- --enable-hooks confirms the hook runtime; --no-hooks installs the rest
of the selection without it and records the reduced module closure in
install-state
- surface the pending decision as a dry-run warning
- show the same capability disclosure in the guided installer's plan
preview, so the wizard's hook question states what it is asking about
Plans that never materialize hooks (Kimi, --profile minimal,
--without baseline:hooks) are unaffected and need no flag. Repair and
uninstall operate on already-recorded state and stay unchanged.
The capability taxonomy and the held-materialization behavior come from
Samarjeet Singh Tomar's PR #2634, reworked to fit the single-decision
consent model that shipped with the guided installer in #2649.
Co-Authored-By: Samarjeet Singh Tomar <samar_tomar@hotmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends scripts/ci/validate-skills.js to also scan docs/{locale}/skills/
translated mirrors, not just curated skills/. Adds detection for the
YAML defect classes from #2630 without a parser dependency: unquoted
values containing ": " (glued next key / dropped quoting), values
starting with the reserved '@'/'`' indicators, and missing frontmatter
blocks entirely (required only for docs mirrors; curated skills/ keeps
its existing tolerant behavior).
The context signal always rendered "N% of <window> window", including when
the window size was the assumed 200k default rather than a detected value.
On a 1M session whose transcript carries no [1m] marker, that produced
lines like:
[StrategicCompact] Context ~194k tokens (97% of 200k window)
while actual usage was ~19%. The user compacts on a false alarm, loses
context, and the resulting quality drop reads as a model regression.
The gap is structural: the context threshold defaults to 80% of the
window (160k on 200k), so the signal fires precisely in the 160k-200k
band where the size cannot be determined — above 200k the observed-tokens
fallback correctly infers 1M, and below 160k nothing fires.
Model id alone cannot close this. A tier may ship both a 200k and a 1M
variant under one id, so neither the known-family table nor a new entry
can distinguish them, and the transcript records no window field.
So stop asserting what isn't known: resolveContextWindow() now reports
whether the size was detected (env override, [1m] marker, known family,
or observed tokens > 200k) or assumed, and the hook omits the percentage
and window label when it was assumed. The token count, threshold, and
firing behaviour are unchanged.
resolveContextWindowTokens() keeps its existing signature and semantics.
Note: 3 pre-existing failures in tests/hooks/suggest-compact.test.js
reproduce identically on unmodified main and are untouched here.
Following the hermes/openclaw (#2433) and kimi (#2441) adapter recipe.
What's included (adal-project adapter, project kind, ./.adal root, same
shape as kimi-project/joycode-project):
- scripts/lib/install-targets/adal-project.js — 10-line project-kind
adapter targeting ./.adal
- Registry + helpers platform-ownership wiring
- adal target on the 5 shared modules (rules-core, agents-core,
commands-core, platform-configs, workflow-quality) +
SUPPORTED_INSTALL_TARGETS + legacy-compat module
- .adal in platform-configs paths
- Both schema enums (install-modules, ecc-install-config), npm files
allowlist, installer help text, .adal/README.md stub
AdaL (adalagent.ai) is a terminal-based AI coding agent (by SylphAI)
built on AdalFlow, with native MCP support and project-scoped config
under ./.adal/ (skills, custom tools, memory) plus a root-level
AGENTS.md instructions file — matching the shape ECC already installs
into other AGENTS.md-based harnesses (Codex, OpenCode, Kimi).
Verified: full suite matches main's baseline (3334 passed, same
pre-existing failures unrelated to this change — OpenCode build/npm-pack
surface tests requiring build tooling not present in this sandbox);
catalog check passes (67 agents / 94 commands / 281 skills); dry-run
resolves Target: adal / Adapter: adal-project / root ./.adal with all 5
modules planned; doctor reports OK after a real install; uninstall
cleanly reverses all 458 operations.
Co-Authored-By: AdaL <adal@sylph.ai>
Remove the redundant JSON-RPC initialize fallback from the consolidated MCP health-check batch. A routed 404 already proves the endpoint is reachable, and the real authenticated MCP call remains authoritative. Avoiding the fallback also prevents a stalled GET plus stalled POST from consuming twice the configured hook timeout.
The mcp-health-check preflight probes HTTP MCP servers with a bare GET.
Some Streamable HTTP servers route only POST /mcp and answer a bare GET
with 404 (Paper Desktop 0.5.3 is one). The probe scored that as down and
blocked every tool call for the server indefinitely, since the 30s
backoff just re-probes and re-fails.
A routed HTTP response of any status proves the endpoint is reachable,
which is all this preflight claims to check -- 400/401/403/405/406 are
already treated this way for the same reason. Add 404 to the set and let
the real MCP client validate the endpoint.
Adds a regression test that stands up a POST-only server (404 on GET,
200 on POST /mcp); it fails on the current code and passes with the fix.
The preflight probe in mcp-health-check only ever sent a bare GET to the
server URL. Some Streamable HTTP MCP servers route POST exclusively and
answer any GET with 404 — api.telnyx.com/v2/mcp is one — so the probe
failed permanently against a perfectly healthy server.
404 is not in HEALTHY_HTTP_CODES, so every probe failed, the backoff
compounded to the 10-minute ceiling, and the hook blocked every tool call
for that server before it left the machine while `claude mcp list` still
reported it Connected.
Replay a failed GET as a real JSON-RPC initialize POST and accept that as
proof of life. Whitelisting 404 was the alternative, but it would mask
genuine outages on every other server.
Adds a regression test with a POST-only server that 404s all GETs and
validates the initialize body; it fails without this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>