Plan CC1.
apps/daemon/src/server.ts: new `GET /api/applied-plugins/:snapshotId/canon`
returns the canonical `## Active plugin` block this snapshot
splices into the system prompt. Two response modes:
default \u2192 { snapshotId, pluginId, block }
Accept: text/plain \u2192 raw block body for shell pipes
Powered by the same renderPluginBlock() composeSystemPrompt() uses,
so the route output is byte-equal to what the agent reads. Useful
for:
- debugging 'why is the agent ignoring my plugin' (read what's
actually injected),
- locking byte-equality regression fixtures against the daemon's
renderPluginBlock() output,
- eyeballing what the prompt block looks like before applying.
CLI: `od plugin canon <snapshotId> [--json]`. Default output is
plain text suitable for piping; --json wraps the block in
{ snapshotId, pluginId, block }. printPluginHelp() updated.
Daemon tests: 1737 \u2192 1744 (+7 cases on plugins-canon: id +
version when title absent, pluginTitle wins when present, plugin
description appended, sorted alphabetic inputs block, byte-equal
output across calls with same snapshot (replay invariance check),
inputs block omitted when no inputs, query echoed as stylized
brief).
Co-authored-by: Tom Huang <1043269994@qq.com>