Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,42 @@ Changes are tracked via git tags. Each release tag corresponds to an entry here.

## [Unreleased]

_No changes yet._
### Fixed — Item 6.34: Help Assistant + Product Architect launchers now accept `claude-code-ollama`

The agent picker in `cfcf init` and the web Settings page surfaces every
detected adapter for every role — but the Help Assistant and Product
Architect have their **own** launchers (separate from the standard
`spawnProcess` path because they need true TUI takeover via
`stdio: "inherit"`) with hardcoded `switch` statements for argv
composition. The new adapters from item 6.28 (`opencode`,
`claude-code-ollama`, `opencode-ollama`) were never wired up to those
switches, so picking `claude-code-ollama` for HA or PA at `cfcf init`
would save fine but throw at launch time:

```
[ha] failed to launch: Help Assistant doesn't support adapter
"claude-code-ollama" yet. Supported: claude-code, codex.
```

- Added a `case "claude-code-ollama"` arm to both launchers. Wraps the
existing claude-code argv with `ollama launch claude --model
<ollama-model> --yes -- <claude-flags>`. For HA: no
`--dangerously-skip-permissions` (HA is interactive, user reviews
tool calls). For PA: `--dangerously-skip-permissions` flows through
in non-safe mode (matches the direct claude-code behaviour).
- The error message for the still-unsupported variants
(`opencode`, `opencode-ollama`) now lists `claude-code-ollama` as
the supported ollama path, so users on `opencode-ollama` see the
alternative immediately.
- 7 new unit tests across `help-assistant/launcher.test.ts` and
`product-architect/launcher.test.ts` (argv shape, model omission,
safe-mode behaviour for PA, helpful-error-message check).
- **Out of scope for this fix**: `opencode` and `opencode-ollama`
interactive support — opencode's interactive default reads
`AGENTS.md` from cwd, which doesn't fit cf²'s ephemeral-tempfile
pattern. Needs investigation into opencode's runtime
system-prompt-injection mechanism. Tracked as the deferred half of
item 6.34 in `docs/plan.md`.

## [0.22.0] -- 2026-05-09

Expand Down
1 change: 1 addition & 0 deletions docs/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ The tables below are the authoritative view of iteration progress. The **Notes**
| 6.29 | ❌ | macOS notification fix: per-app bundle ID via shim or `terminal-notifier` | **Surfaced 2026-05-08** during iter-6 dogfood. Symptom: macOS desktop notifications from cfcf events (`loop.paused`, `loop.completed`, `agent.failed`) appear with severe delays — sometimes minutes-to-hours late, often batched. Root cause: cfcf uses `osascript -e 'display notification …'` which macOS attributes to **"Script Editor"** with no separate bundle ID. Three downstream consequences: (1) per-app rate-limiting kicks in around ~5 notifications/min sustained — beyond that, macOS silently queues + batches; (2) DND / Focus Mode queues all of them until DND ends, causing the "minutes-to-hours-later dump" pattern; (3) without a bundle ID, macOS can't merge cfcf events into a coordinated stream — each notification is independent, hitting quotas faster. **Three implementation options**: *(option 1, preferred)* Bundle a tiny `.app` shim (`cfcf-notifier.app/Contents/{Info.plist,MacOS/cfcf-notifier}`) with its own bundle ID + a tiny shell or Swift wrapper that calls `display notification`. cfcf invokes the shim instead of `osascript`. ~50 LOC of plist + a small wrapper script + updates to the install pipeline so the shim ships with cfcf. *(option 2)* Detect `terminal-notifier` if installed (community tool, has its own bundle ID + a `-group` flag for grouping). Use it when present, fall back to `osascript`. Document as a recommended optional dep for macOS users. ~10 LOC of detection + wrapper. *(option 3)* Switch to a Swift / ObjC `NSUserNotification` shim. Overkill compared to option 1; bundle-app shim is simpler. **Out of scope**: rewriting the notification dispatcher or adding new channels — only the macos channel needs the fix. **Alternative if neither option lands by end of iter-6**: remove the macos channel entirely + document terminal-bell as the primary on-the-machine notification path. Linux / cross-platform users already use terminal-bell via the existing dispatcher. **Tests**: a smoke test that confirms the shim binary is reachable post-install (`cfcf doctor` check); a CI-skipped integration test that fires a notification and asserts the shim's path was used. The actual delivery is hard to assert in CI — that part stays manual. **Cross-refs**: `packages/core/src/notifications/channels/macos.ts` is the current implementation; `cfcf-notifier.app` would live under a new `scripts/macos-notifier/` directory + be staged into dist by `scripts/stage-dist.sh`. **Effort estimate**: 0.5-1 session for option 1 (bundle shim); 0.25 session for option 2 (terminal-notifier detection). **Surfaced 2026-05-08 during iter-6 dogfood.** |
| 6.30 | ✅ | API parse errors with non-coder ollama models on claude-code-ollama (refined: opencode-ollama is the fall-back, not "use coder-tuned models" alone) | **Shipped 2026-05-08**. **Refined finding from re-test**: original framing "gemma4 is bad at tool calls" was wrong. Same gemma4:31b model, two routes: `claude-code-ollama + gemma4:31b` → fails with `API Error: Content block not found` (Anthropic-strict Messages API parser rejects); `opencode-ollama + gemma4:31b` → wrote all four documenter files cleanly. The model IS capable; it's the strict-Anthropic-shape translation layer that rejects its output. The OpenAI-compatible endpoint used by opencode-ollama is more tolerant. **Re-confirmed after the v0.20.0 process-tree-kill fix landed** (so it's not a queue-starvation confound). **Shipped scope**: (a) **`isApiParseRisk()` helper** in `packages/core/src/adapters/index.ts` (sister to `isClaudeCodeHarnessRisk`); 4 unit tests in `adapters.test.ts`. (b) **New blue info callout** in `<HarnessPolicyWarning>` (web Settings + workspace Config) explaining the parse-error symptom and recommending `opencode-ollama` as the fall-back; positioned between the existing yellow policy callout and the blue log-visibility callout. (c) **Inline ⚠ row indicator** next to the adapter selector for any unattended row on `claude-code` (direct) — visual link to the policy callout below the table. Scoped via a new `isUnattendedRole` check in ServerInfo so PA / HA rows don't flag (they're allowed-interactive). (d) **Architect always counted as unattended** for these warnings (`UNATTENDED_ROLE_NAMES` updated in `@cfcf/core`): the loop invokes architect on `refine_plan` resume and judge `NEEDS_REFINEMENT` verdicts as well as the pre-loop `autoReviewSpecs=true` path; the same adapter setting drives all three loop paths AND the manual `cfcf review` path, so the warning has to reflect the worst case. Drops the previous `(autoReviewSpecs=true)` qualifier on the architect role label. (e) **CLI `cfcf init` banner** updated to mirror the new web callouts — same three notices in the same order. (f) **`docs/guides/anthropic-policy.md`** documenter row updated with the refined finding: two workable paths (coder-tuned model on claude-code-ollama, OR any model on opencode-ollama). **Out of scope (deferred)**: tolerant retry logic in the iteration-loop spawn (option b from original framing — defer to iter-7 if a similar failure pattern shows up with non-gemma models); deeper investigation of ollama's Anthropic-API translation layer (option c — would belong upstream in ollama, not cfcf). **Cross-refs**: `~/.cfcf/logs/calc-04c553/documenter-001.log` + `documenter-004.log` (the 56-byte claude-code-ollama failure logs); `documenter-005.log` (864-byte opencode-ollama success log on the same model). |
| 6.31 | ✅ | Orphan agent-process cleanup on `cfcf server stop` / `start` / interactive reap | **Surfaced 2026-05-08** during 6.28's opencode-ollama dogfood. When the user stops + starts the cfcf server while a loop iteration is in flight, the spawned agent processes (`claude` / `codex` / `opencode` / `ollama launch <agent>`) are NOT terminated. They keep running until they finish, fail, or are manually killed. **Failure mode observed**: 4 orphans from earlier loop runs (2 claude documenter+architect, 2 opencode architects) accumulated across server restarts and serialized on ollama's model runner — each held the qwen3-coder model busy with up-to-10-minute timed-out inference requests. The new opencode iteration's `/v1/chat/completions` call queued behind them and starved. From the user's POV: loop "hangs" with no clear error, log file is 40 bytes, no obvious culprit. Required `pgrep -f \"ollama launch\" \| xargs kill` to recover. **Scope**: (a) **[SHIPPED in v0.20.0]** `start.ts` `gracefulShutdown()` enumerates every active spawn from the in-memory `activeProcesses` registry (`packages/core/src/active-processes.ts`) and sends SIGTERM to the **process group** (`process.kill(-pid, "SIGTERM")` — agents are now spawned with `detached: true`), then schedules SIGKILL 1.5s later via `setTimeout(...).unref()`. The shutdown handler awaits a 2-second grace window before `process.exit()` so the SIGKILL timer has time to fire — without that wait, the timer dies with the parent and orphans of `init` accumulate. **(b) [SHIPPED 2026-05-08, post-v0.20.0]** On `startServer()` boot, `start.ts` calls `findOrphanAgentProcesses()` from the new `packages/core/src/orphan-reaper.ts` module. Three conjoined filters (PPID==1 + same effective user + cfcf-spawned command shape) identify orphans confidently — false positives near-zero. Auto-reaps via `reapOrphans()` (group-SIGTERM → 1.5s grace → SIGKILL); logs `[server] Reaping N stale agent process(es) from a previous server PID:` followed by one line per orphan. Best-effort: a scan failure never blocks server boot. **(c) [SHIPPED 2026-05-08, post-v0.20.0]** New `cfcf server reap` subcommand uses the same matcher and prints candidates with `pid=… kind=… elapsed=… <command>` lines, then prompts `Kill these N process(es)? [y/N]:`. On `y`: reap. On `N` or empty: `Aborted. No processes killed.`. Empty list: `No zombie agent processes detected.`. Supports `-y / --yes` for non-interactive use. Pure system call — does NOT require the cfcf server to be running. **Tests shipped** (25 in `packages/core/src/orphan-reaper.test.ts`): the classifier covers every cfcf-spawn pattern + negative cases (interactive claude, non-cfcf opencode, ollama serve/pull, unrelated commands like node/python); the parser handles standard `ps` output, header-only input, and malformed lines without throwing; the orphan filter validates each of the three filters in isolation (PPID, user, command shape); `reapOrphans` covers empty input, the SIGTERM-then-SIGKILL flow with mocked `process.kill`, the group-then-direct fallback when group-kill throws ESRCH, and the failed-count when both kills fail. **Cross-refs**: `packages/core/src/orphan-reaper.ts` (matcher + reaper), `packages/server/src/start.ts` (boot-time hook), `packages/cli/src/commands/server.ts` (`reap` subcommand). |
| 6.34 | 🔄 | Help Assistant + Product Architect launchers — ollama-routed adapter support | **Round 1 shipped 2026-05-09, post-v0.22.0**. **Surfaced 2026-05-09**: a user with `helpAssistantAgent.adapter = "claude-code-ollama"` (configured at `cfcf init` since 6.28 surfaced the new adapters in the picker) hit `Help Assistant doesn't support adapter "claude-code-ollama" yet. Supported: claude-code, codex.` when running `cfcf help assistant`. **Root cause**: the picker is general-purpose and offers every detected adapter for every role, but the PA + HA launchers (`packages/core/src/{product-architect,help-assistant}/launcher.ts`) have hardcoded `switch` statements for argv composition because they need true TUI takeover via `Bun.spawn(... { stdio: "inherit" })` — the standard `AgentAdapter.buildCommand()` pipeline assumes headless `-p` and isn't a fit. The new adapters from 6.28 (`opencode`, `claude-code-ollama`, `opencode-ollama`) were never added to those switches. **Shipped (round 1)**: added a `case "claude-code-ollama"` arm to both the HA and PA launcher switches that wraps the existing claude-code argv composition with `ollama launch claude --model <ollama-model> --yes -- <claude-flags>`. The `--model` flag is the ollama-side model name (claude itself doesn't get `--model` — ollama serves whichever local model). For HA: no `--dangerously-skip-permissions` (interactive Q&A — user reviews tool calls); for PA: `--dangerously-skip-permissions` flows through after `--` in non-safe mode (mirrors the direct claude-code path). 7 new unit tests across both launcher test files (argv shape, model omission, safe-mode behaviour for PA, helpful error message for opencode-ollama). The error message for the still-unsupported adapters now lists `claude-code-ollama` so users on `opencode-ollama` see the alternative, and points at this plan row. **Out of scope for round 1 (deferred)**: `opencode` and `opencode-ollama` interactive support. opencode's interactive default is to read `AGENTS.md` from cwd, which doesn't fit cf²'s ephemeral-tempfile pattern (would either pollute the user's repo or sandbox opencode away from the user's working tree). Needs investigation into opencode's runtime system-prompt-injection flag (`--prompt`? `-c instructions=...`? config-file-only?) before we can wire it cleanly. **Cross-refs**: `packages/core/src/help-assistant/launcher.ts`, `packages/core/src/product-architect/launcher.ts`. |
| 6.33 | ✅ | Auto-refresh `availableOllamaModels` on server boot + manual refresh button | **Shipped 2026-05-08, post-v0.21.0**. **Surfaced 2026-05-08** during dogfood: user pulled a new ollama model, restarted the cfcf server, and the new model didn't show up in the role-picker dropdowns — because `listOllamaModels()` was only invoked at `cfcf init` (interactive setup) and `cfcf doctor` (read-only display); neither the server nor the web UI ever re-detected. **Shipped scope**: (a) **`refreshOllamaModelsInConfig()` helper** in `packages/core/src/ollama-detection.ts` — detects ollama, lists models, persists to `availableOllamaModels` if the live list differs from saved (order-insensitive comparison since `ollama list` reorders by mtime). Returns `{ models, updated, error? }`. Best-effort: never throws; surfaces the missing-ollama case via `error` field. (b) **Boot-time auto-refresh** in `packages/server/src/start.ts` — runs after the orphan reaper, single log line if list changed. (c) **`POST /api/agents/refresh-ollama-models`** endpoint — returns the same shape as the helper. Always 200 (the most common error case is "ollama not installed", which isn't an HTTP failure for this endpoint). (d) **"Refresh ollama models" button** in the Agent roles section of both web Settings (`ServerInfo.tsx`) and per-workspace Config (`ConfigDisplay.tsx`). Clicking calls the endpoint, displays a status message, and bumps a `modelsRev` counter that triggers a re-fetch of `/api/agents/models` so the `*-ollama` dropdowns pick up new entries. **Tests added**: 4 new unit tests in `ollama-detection.test.ts` (shape, no-config-no-write, list-equality persistence guard, ordering insensitivity); 2 new endpoint tests in `agent-models.test.ts` (shape, no-500-on-missing-ollama). **Cross-refs**: `packages/core/src/ollama-detection.ts`, `packages/server/src/start.ts`, `packages/server/src/routes/agent-models.ts`, `packages/web/src/api.ts`, `packages/web/src/pages/ServerInfo.tsx`, `packages/web/src/components/ConfigDisplay.tsx`. |
| 6.32 | ❌ | Opencode-ollama hang-detection + reduced-deadlock surface | **Surfaced 2026-05-08** during 6.28 dogfood. Despite the documented `opencode run` "scriptable" contract + `--dangerously-skip-permissions` flag, opencode-ollama silently hangs in cf²'s harness pattern when (a) ollama's model runner is busy / has dead orphan requests in queue, (b) the prior session's hardcoded permission denies trip an internal stdin-prompt code path, or (c) opencode's stream-json over the OpenAI-compatible `/v1/chat/completions` API gets aborted server-side (500) and opencode doesn't surface the error loudly. **Symptom**: opencode process at 0% CPU, no TCP connection to ollama, log file frozen mid-session at "service=llm ... stream", cfcf log file 40 bytes. No timeout, no error, no exit. **Three angles to investigate**: *(a)* hard timeout on agent spawns in `process-manager.ts` (e.g. 15-min default per role, configurable per role) so a hung agent eventually kills itself + cfcf marks the iteration failed instead of the loop hanging forever. *(b)* Detect opencode's specific failure modes by following `~/.local/share/opencode/log/<timestamp>.log` in addition to stdout — opencode's internal log captures the full session lifecycle including provider errors. The cfcf log writer could optionally tail this file as a side-channel. *(c)* Test whether `--format json` on `opencode run` produces cleaner streaming events than the default formatted output (might help with the buffering UX too — sister concern to the claude-code-ollama buffering problem). **Recommendation in the meantime**: prefer `claude-code-ollama` over `opencode-ollama` for unattended roles until opencode's stability matures (when running against a local ollama backend). Update `anthropic-policy.md` with this caveat. **Cross-refs**: github/anomalyco/opencode#13851 (the permission-deny-causes-cancel-state issue we already knew about); the 2026-05-08 calc-workspace dogfood log session at `~/.local/share/opencode/log/2026-05-08T082733.log` is the canonical reproduction. **Effort**: 0.25 session for the docs caveat (immediately useful); 1–2 sessions for the spawn-timeout + opencode-log-tail investigation (defer until 6.31 ships, since the orphan cleanup is the more-bang-for-buck blocker). |

Expand Down
47 changes: 47 additions & 0 deletions packages/core/src/help-assistant/launcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,51 @@ describe("buildLaunchArgs", () => {
buildLaunchArgs({ adapter: "fake-agent" as "claude-code" }, "x", "Hi"),
).toThrow(/Help Assistant doesn't support adapter "fake-agent"/);
});

// claude-code-ollama (item 6.34)

it("claude-code-ollama: wraps with `ollama launch claude --model X --yes --` then claude's flags", () => {
const { command, args, tempPromptFile } = buildLaunchArgs(
{ adapter: "claude-code-ollama", model: "qwen3-coder:latest" },
"HA system prompt",
"Hello",
);
expect(command).toBe("ollama");
// ollama-side flags
expect(args[0]).toBe("launch");
expect(args[1]).toBe("claude");
const modelIdx = args.indexOf("--model");
expect(modelIdx).toBeGreaterThanOrEqual(0);
expect(args[modelIdx + 1]).toBe("qwen3-coder:latest");
expect(args).toContain("--yes");
// mandatory `--` separator
const sepIdx = args.indexOf("--");
expect(sepIdx).toBeGreaterThanOrEqual(0);
// claude's flags AFTER the separator
expect(args.slice(sepIdx + 1)).toContain("--append-system-prompt");
expect(args.slice(sepIdx + 1)).toContain("HA system prompt");
// No --dangerously-skip-permissions (HA is interactive)
expect(args).not.toContain("--dangerously-skip-permissions");
// firstUserMessage must be the LAST positional (Flavour A)
expect(args[args.length - 1]).toBe("Hello");
expect(tempPromptFile).toBeNull();
});

it("claude-code-ollama: omits --model when no agent.model is set", () => {
const { args } = buildLaunchArgs(
{ adapter: "claude-code-ollama" },
"x",
"Hi",
);
expect(args).not.toContain("--model");
// Still has `--yes --`
expect(args).toContain("--yes");
expect(args).toContain("--");
});

it("error message lists claude-code-ollama as supported (helps users on opencode-ollama)", () => {
expect(() =>
buildLaunchArgs({ adapter: "opencode-ollama" as "claude-code" }, "x", "Hi"),
).toThrow(/claude-code, codex, claude-code-ollama/);
});
});
Loading
Loading