From ce84ac01d7a75dd2c1d411c53cb0a6b26cf9531e Mon Sep 17 00:00:00 2001 From: "Ragdoll-Opus-4.7" Date: Mon, 29 Jun 2026 09:50:01 +0800 Subject: [PATCH] =?UTF-8?q?docs(F241):=20completion=20audit=20=E2=80=94=20?= =?UTF-8?q?F241=20closed=20as=20of=202026-06-29?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walks every AC across Phase A / B 2a / B 2b / C against shipped commits + test evidence, lists shipped Phase C PRs (#36 L0 fix, #38 cliProbe, #39 manifest claims + hash v: 2, #42 Hub UI for owner approval) + Phase C demo hotfix (plugin.yaml --dangerously-skip-permissions), and records 5 known limitations carried out of F241 for future follow-on: 1. cli-jsonl resume + non-empty systemPrompt are mutually exclusive (silent cold-start every turn, `session_continuity_degraded` warning). 2. clowder-code --non-interactive rejects every tool request โ†’ exit 1; workaround is --dangerously-skip-permissions; real fix is a host-streaming permission protocol (separate feature anchor). 3. Plugin/package fingerprint not in descriptor hash (already in 2b Residual risk; not blocking for git-tracked in-tree plugin). 4. ACP transport for plugins (cli-jsonl is the only proven transport; F161 ACP carrier work is the natural integration point). 5. `outputProfile` accepts only `clowder-code-turn-result-v1` (second runtime would need parser registry extension). Doc frontmatter status flipped from `accepted feature anchor` to `shipped + closed 2026-06-29`. Operator outcome: install a plugin via `plugins//plugin.yaml`, hit `/api/plugins//enable` + the Hub-rendered approval form, and have a routeable `@` that streams replies back into a thread โ€” no core code edits, the original "I have my own agent" requirement. [ๅฎชๅฎช/opus claude-opus-4-7๐Ÿพ] --- docs/features/F241-agent-provider-plugin.md | 69 ++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/docs/features/F241-agent-provider-plugin.md b/docs/features/F241-agent-provider-plugin.md index 2b72851ce4..d86ffe5a56 100644 --- a/docs/features/F241-agent-provider-plugin.md +++ b/docs/features/F241-agent-provider-plugin.md @@ -8,7 +8,7 @@ created: 2026-06-18 # F241: Agent Provider Plugin / Hostable Provider Runtime -> **Status**: accepted feature anchor | **Owner**: Community (ๅฝญๆฝ‡ / `bouillipx`) + Cat Cafe maintainer guard | **Priority**: P1 +> **Status**: shipped + closed 2026-06-29 (Phase A / B 2a / B 2b / C delivered; see ยง F241 close) | **Owner**: Community (ๅฝญๆฝ‡ / `bouillipx`) + Cat Cafe maintainer guard | **Priority**: P1 > **Source**: operator request 2026-06-18 โ€” "ๆˆ‘ๆœ‰่‡ชๅทฑ็š„ agent ้œ€่ฆๆŽฅๅ…ฅ่ฟ›ๆฅ"; community architecture discussion [clowder-ai#941](https://github.com/zts212653/clowder-ai/issues/941); maintainer decision [#941 comment 4739146327](https://github.com/zts212653/clowder-ai/issues/941#issuecomment-4739146327). > **Decision**: accepted as **F241: Agent Provider Plugin / Hostable Provider Runtime**. This is a new provider-extension feature anchor, **not** "F202 Phase 3" by default, and **not** a rename of F143. It is the F143 host-contract lineage plus the F202 plugin discovery/config surface, composed into a provider-as-plugin product capability. @@ -338,3 +338,70 @@ Plugin/package fingerprint: if a fingerprint is available (npm tarball hash, git - 2026-06-22: Phase B Slice 2b design gate passed โ€” 6-step routeable gate, three-field state model, `RoutingAdmissionService` + descriptor-hash invalidation + serialized sync coordinator (opus driving; codex + gpt52 design review). - 2026-06-23: Phase B Slice 2b Q3 TTL convergence round (codex + gpt52) โ€” both converged on `S1 No / S2 No / S3 Yes`; locked shared invariant that `routeable: false โ†’ true` is the exclusive domain of explicit synchronous paths. Background actors may refresh telemetry and degrade, never promote. Design notes amended. - 2026-06-23: Phase B Slice 2b end-to-end shipped on `feat/F241-phase-b-slice2b-routeable-gate` โ€” shared types widening, `RoutingAdmissionService`, descriptor-hash + activator integration, approval orchestration + HTTP route, post-approval sync hook, routeable binding + projection, end-to-end integration test. Health executor ships as transport-availability probe; real `acpInitialize` (runtime initialize handshake) and `cliProbe` (bounded spawn + exit-code check) probes are tracked as **Slice 2c follow-on hardening** โ€” the executor is a drop-in DI swap (`AgentProviderHealthExecutor` interface in `agent-provider-health-executor.ts`), no further redesign needed. +- 2026-06-28: Phase C reference-runtime E2E spike (ๅธƒๅถ็Œซ/ๅฎชๅฎช driving in dispatch sub-thread `thread_mqxva63tuebxj9sn`). Reverse-engineered the cli-jsonl real wire shape against `clowder-code/cli/dist/bin.js`, drafted the production `plugins/clowder-code/plugin.yaml`, enabled + approved end-to-end, surfaced P0 split-brain between API in-memory `catRegistry` (saw plugin projection) and the L0 system-prompt subprocess (did not). Spike report: `/tmp/clowder-spike/F241-phase-c-spike-report.md`. +- 2026-06-28: PR #36 `79e73cb8` shipped P0 fix โ€” `scripts/compile-system-prompt-l0.mjs` `bootstrapCatRegistry` reads `.cat-cafe/capabilities.json` + applies the F241 routeable projection so the L0 subprocess sees the same catId universe the runtime routes. Authored by ็ผ…ๅ› ็Œซ/็ š็ š, reviewed by ๅธƒๅถ็Œซ/opus47. +- 2026-06-28: PR #38 `efe91cda` shipped real `cliProbe` health executor โ€” bounded spawn of `command --version` + exit-code + timeout, replaces 2b transport-availability stub. DI swap wired into both `syncAgentRegistry` refresh path AND `AgentProviderApprovalService` approval path (no split-brain). Probe uses `resolveCliCommand` (same resolver as real cli-jsonl invocation) per @codex round-1 review. +- 2026-06-28: PR #39 `7ecb3e4d` shipped manifest identity claims โ€” `PluginAgentProviderResource` gains optional `providerId / displayName / mentionPatterns`. Descriptor hash bumped `v: 1 โ†’ v: 2` so upgrade forces re-approval (matches the existing descriptor-delta contract). Parser rejects bare `@`, whitespace, path separators, case-insensitive duplicates (per @codex round-1 + round-2 review). +- 2026-06-28: PR #42 `71584bf6` shipped Hub UI for owner approval โ€” `PluginResourceStatus` exposes `capId / routeable / approved / binding / claims / descriptorHash / healthFailureReason / lastSyncError`; React `AgentProviderApprovalSection` renders state chip + binding summary + approval form (prefilled from PR #39 claims) + dual failure chips ("ๆŽข้’ˆๅคฑ่ดฅ" + "ๅŒๆญฅๅคฑ่ดฅ"). The `lastSyncError` projection landed in round-2 per @codex review (without it operators couldn't diagnose `approved=true / healthy / routeable=false`). +- 2026-06-28: Phase C demo hotfix โ€” `plugins/clowder-code/plugin.yaml` `startupArgs` adds `--dangerously-skip-permissions`. Root cause (diagnosed by parallel ๅธƒๅถ็Œซ/ๅฎชๅฎช in `thread_mqyj529p7hn1ughj`): `clowder-code` `--non-interactive` mode unconditionally rejects every tool request via `PermissionRequiredError`, exiting code 1. Operator-visible after the 5th E2E probe (`@clowder-cat ่ฏท็”จ bash ่ท‘ pwd`) returned the correct cwd `/Users/xxx/workspace/AI/clowder-labs/clowder-code`. Tracked as a F241 known-limitation (see ยง F241 close). + +## F241 close โ€” completion audit (2026-06-29) + +### Acceptance criteria audit + +**Phase A โ€” Host transport intake slice** (all ACs satisfied): +- โœ… One host-owned transport path registered and selected by data/config โ€” `cli-jsonl` via `ProviderTransportRegistry` (`a95384eb / e787dcd3 / 8f1d56c9`). +- โœ… One external runtime invoked as already-installed command โ€” `clowder-code/cli/dist/bin.js`, declared in `plugins/clowder-code/plugin.yaml`. +- โœ… Streaming output maps to `AgentMessage` / thread-visible events โ€” `CliJsonlAgentService.invoke` yields `session_init / agent_loop / text / done`; verified across 5 E2E probes in the Phase C spike thread. +- โœ… Cancel, timeout, startup-failure, no-event-failure visible โ€” `cli-spawn.ts` SIGTERM/SIGKILL + `__cliTimeout` / `__cliError` events; `cli-jsonl-agent-service.test.js` covers `emits a visible error when the CLI exits without a turn_result`. +- โœ… cwd/workspace/sandbox policy host-controlled โ€” `sandbox: workspace-write` in plugin manifest, host-enforced via existing F036 sandbox infra. +- โœ… Callback/MCP injection host-owned + test-covered โ€” `CliJsonlAgentService` test fixture asserts `CAT_CAFE_API_URL` is injected by host, never by plugin. +- โœ… Session-chain + audit metadata written โ€” `cliSessionId` bound on `session_init` (logged in every E2E probe), `AuditEventTypes.CONFIG_UPDATED` written on approve-routeable. + +**Phase B Slice 2a** (shipped `b4a87e3c`): +- โœ… `agentProvider` manifest descriptor activates as `transportReady` (non-routeable). +- โœ… Fail-closed when host transport not registered (`Unknown agentProvider transport`). + +**Phase B Slice 2b** (shipped `486fc5f8` โ€” PR #18): +- โœ… 6-step routeable gate enforced (manifest valid / transport exists / admission / approval / health pass / sync) โ€” `RoutingAdmissionService` + approval orchestration + post-approval sync hook. +- โœ… Three-field state model (`routeableApproved` / `health` / `routeable` computed) โ€” `agent-provider-2b-e2e.test.js` happy-path test asserts. +- โœ… Descriptor-hash invalidation on manifest delta โ€” `agent-provider-descriptor-hash.test.js` sensitivity matrix. +- โœ… Background actors may degrade, never promote โ€” Q3 convergence locked invariant. +- โœ… Plugin admission re-runs at projection time (no parsing-order self-exemption) โ€” `agent-provider-projection.ts` RED LINE. + +**Phase C โ€” Reference runtime** (all ACs satisfied): +- โœ… Routeable cat invokes external runtime from a normal thread โ€” proven across 5 E2E probes (`I am clowder-code AI cat`, `I am alive`, `Probe received hash verified`, etc.). +- โœ… Runtime streams reply back into the thread โ€” `OutboundDeliveryHook deliver()` confirmed in API logs each probe. +- โœ… Session chain, audit, cancel, timeout, failure inspectable: + - Session chain: `cliSessionId` bound to invocation, visible in `[invoke] Session init: binding session` logs. + - Audit: `CONFIG_UPDATED` events on enable / approve / disable. + - Cancel / timeout / failure: `cli-spawn.ts` + `CliJsonlAgentService` + Hub UI `agentProviderHealthFailureReason` + `agentProviderLastSyncError` chips. +- โœ… A2A handoff and @-mention routing cannot target the provider until identity governance has approved it โ€” pre-approve, `clowder-cat` is not in `catRegistry` (no projection without `routeableBinding`); `@clowder-cat` returns "ๆœชๆ‰พๅˆฐ่ฏฅ cat" routing failure. +- โœ… E2E proof includes at least one denied capability / denied namespace case โ€” `agent-provider-2b-e2e.test.js:208` `approval is rejected when operator picks a catId that collides with a reserved baseline` covers the denied-namespace case. +- โœ… Hub admin UI for owner approval (2b Non-goals #3 promoted to Phase C scope) โ€” PR #42 `71584bf6`. + +**Phase C 2c hardening** (all delivered): +- โœ… Real `cliProbe` health executor โ€” PR #38 `efe91cda`. +- โœ… Manifest identity claims (`providerId / displayName / mentionPatterns`) โ€” PR #39 `7ecb3e4d`, with `mentionPatterns` case-insensitive dedup + bare `@` rejection per @codex review. +- โœ… Descriptor hash `v: 1 โ†’ v: 2` forces re-approval on upgrade โ€” verified live by triggering enable + approve cycle (`574a68e4โ€ฆ โ†’ 00fb6ae1โ€ฆ โ†’ 4acdffb7โ€ฆ` across the schema bumps). + +**Safety boundaries** (all preserved): +- โœ… No arbitrary same-power JS factory โ€” plugin manifest only declares data + a constrained transport binding. +- โœ… No plugin-provided install / uninstall scripts in Phase A โ€” `plugin.yaml` is data only. +- โœ… No plugin code receives callback tokens / session tokens / JWTs / MCP credentials โ€” `agent-provider-health-executor.ts` strips env to `PATH` only; host injects callback env into the actual invocation, never into the probe. +- โœ… No plugin activation writes runtime config such as `~/.clowder-code/config.json` โ€” activator writes to `.cat-cafe/capabilities.json` only. +- โœ… No external archive install/update/uninstall โ€” out of scope per design. + +### Known limitations carried out of F241 (tracked for follow-on, not blockers) + +1. **`cli-jsonl` resume + non-empty systemPrompt are mutually exclusive** โ€” `CliJsonlAgentService.buildPrompt` concatenates `${systemPrompt}\n\n${prompt}` which trips the `containsLineBreak()` check in `sessionPolicy: resume`, silently falling back to a cold session every turn. `session_continuity_degraded: cli_jsonl_resume_requires_single_line_prompt` warning fires every invocation. Working as designed but operator-confusing; a future slice could either: (a) special-case the system prompt line-break detection, or (b) declare cli-jsonl resume + systemPrompt incompatible and document in the manifest schema. +2. **`clowder-code --non-interactive` rejects every tool request** โ€” `createNonInteractiveApprovalHandler` in `clowder-code/cli/src/runner.ts:3005` throws `PermissionRequiredError` on any tool call, exit code 1. Current workaround: `startupArgs` includes `--dangerously-skip-permissions`. The real fix is a host-streaming permission protocol that lets the cat-cafe API mediate per-tool approval (likely a separate feature anchor, e.g. `F241 follow-on: ACP permission streaming` or absorbed by F161 ACP carrier work). +3. **Plugin/package fingerprint not in descriptor hash** โ€” already tracked in ยง Phase B 2b Residual risk. If only a directory path is available (rather than an npm tarball SHA / git SHA), a plugin can mutate its body silently without re-approval. Not blocking for the in-tree reference plugin since edits are git-tracked. +4. **ACP transport for plugins** โ€” `cli-jsonl` is the only proven plugin transport. F161 ACP carrier work is the natural integration point when ACP plugins arrive. +5. **`outputProfile` accepts only `clowder-code-turn-result-v1`** โ€” a second cli-jsonl runtime would need parser registry extension. Tracked as a small additive change when the second runtime lands. + +### Status + +**F241 โ€” accepted, shipped, closed as of 2026-06-29.** + +Phase A / B 2a / B 2b / C all delivered with ACs satisfied and cross-family review (็ š็ š/codex + opus47/sonnet). Live in `develop`. Operator can install a plugin via `plugins//plugin.yaml`, hit `/api/plugins//enable` and the Hub-rendered approval form, and have a routeable `@` that streams replies back into a thread โ€” all without core code edits, the original "I have my own agent" requirement.