diff --git a/README.md b/README.md index 1f8a83d..44bcf15 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,11 @@ the other provider is reported as missing without blocking single-provider use. Use `iii-code setup --coding-full` when you want the richer coding profile from the public registry. It installs the base harness stack plus `mcp`, `iii-lsp`, and `iii-database@1.0.4`, then verifies those configured workers during health -checks. The database worker is pinned because the current public registry has -no `latest` tag for `iii-database`. For read-only verification later, run: +checks. `doctor --coding-full` also checks that the MCP and database functions +are live on the engine, so a listed-but-unusable worker no longer passes the +profile check. The database worker is pinned because the current public +registry has no `latest` tag for `iii-database`. For read-only verification +later, run: ```bash iii-code doctor --coding-full @@ -135,7 +138,10 @@ from the repo root so `shell::fs::*` can read and write project files. If process, confirm the engine was started from the repo root, and start it again so it picks up the current config. The shell allowlist includes common repo-inspection and validation commands such as `rg`, `git`, `cargo`, `npm`, -`pnpm`, `bun`, `node`, `python`, and `make`; approval policy still lives in the +`npx`, `pnpm`, `bun`, `node`, `python`, `pip`, `pytest`, `uv`, `go`, `find`, +`sed`, `awk`, and `make`. It permits normal coding flows like `npm run build` +and short `node -e` or `python -c` smoke checks while keeping destructive +patterns and sensitive host paths blocked. Approval policy still lives in the worker stack. Secrets are intentionally not accepted through CLI flags because argv can leak diff --git a/config.example.yaml b/config.example.yaml index 5783c55..d78177a 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -83,11 +83,17 @@ workers: - hostname - which - jq + - sed + - awk + - perl + - find + - xargs - uname - df - du - ps - printenv + - env - basename - dirname - rg @@ -95,14 +101,32 @@ workers: - cargo - rustc - npm + - npx - pnpm - bun - node - python - python3 + - pip + - pip3 + - pytest + - uv + - go - make + - sh + - bash - mkdir - touch + - cp + - mv + - rm + - chmod + - ln + - sleep + - true + - false + - curl + - wget default_timeout_ms: 10000 denylist_patterns: - rm\s+-rf\s+/ @@ -113,13 +137,8 @@ workers: - reboot - /etc/passwd - /etc/shadow - - \bfind\b[^|;&]*-exec(dir)?\b - - \bawk\b[^|;&]*system\s*\( - - \bsed\b[^|;&]*(-i\b|\be\b) - \bcurl\b[^|;&]*(file://|-o\s|--output-dir\b|-F\s+@) - \bgit\b[^|;&]*(--upload-pack|--receive-pack|core\.pager|core\.hooksPath|GIT_SSH_COMMAND) - - \b(node|python3?)\b[^|;&]*\s-(e|c)\b - - \bnpm\b[^|;&]*\brun\b fs: allow_unjailed: false denylist_paths: diff --git a/docs/feature-parity-gaps.md b/docs/feature-parity-gaps.md index a9d29b9..9dbcc8e 100644 --- a/docs/feature-parity-gaps.md +++ b/docs/feature-parity-gaps.md @@ -1,7 +1,8 @@ # Feature Parity Gaps -This document tracks product behavior `iii-code` should add while staying a -thin terminal CLI on top of the upstream iii harness stack. +This document tracks product behavior `iii-code` should add to reach practical +Pi/Kimchi coding-agent parity while staying on top of the upstream iii harness +stack. ## Sources @@ -10,8 +11,10 @@ thin terminal CLI on top of the upstream iii harness stack. ## Boundary -`iii-code` is a thin Rust CLI around the installed `iii` binary and the public -worker registry. Setup must install the upstream harness with: +`iii-code` is a Rust terminal coding agent around the installed `iii` binary +and the public worker registry. The target is not a minimal wrapper; it should +feel like a complete coding agent when the iii workers provide the underlying +capability. Setup must install the upstream harness with: ```bash iii worker add harness @@ -24,15 +27,17 @@ The harness worker declares the core stack in `harness/iii.worker.yaml`: `provider-anthropic`, `provider-openai`, `auth-credentials`, `llm-budget`, `skills`, `approval-gate`, and `iii-sandbox`. -`iii-code` should add terminal UX and payload construction around those workers. -It should not publish a competing harness or checked-in worker lockfile. If the -harness artifact is temporarily unavailable, the CLI may install the same core -workers from the public registry as a fallback. +`iii-code` should add terminal UX, parity-oriented setup defaults, diagnostics, +and payload construction around those workers. It should not publish a +competing harness or checked-in worker lockfile. If the harness artifact is +temporarily unavailable, the CLI may install the same core workers from the +public registry as a fallback. For a fuller coding profile, `iii-code setup --coding-full` additionally installs public registry workers `mcp`, `iii-lsp`, and `iii-database@1.0.4`. -The CLI only installs and verifies that profile; the model still discovers -usable functions from the running engine. +The CLI installs that profile and verifies both configured workers and live +runtime functions for MCP and database access; the model still discovers usable +functions from the running engine. ## Covered By Existing Workers @@ -55,7 +60,10 @@ usable functions from the running engine. and falls back to the core worker stack from the public registry. - `setup --coding-full` installs `mcp`, `iii-lsp`, and `iii-database@1.0.4`; `doctor --coding-full` verifies those workers are - configured. + configured and that the MCP/database functions are actually registered. +- `config.example.yaml` uses coding-agent defaults for the shell worker: + normal validation commands, `npm run ...`, and short `node -e`/`python -c` + checks are allowed while destructive patterns remain blocked. - Provider credentials are read from `OPENAI_API_KEY` and `ANTHROPIC_API_KEY` and stored through `auth::set_token`; argv secret flags are not supported. - `run` and `resume` construct the current `turn-orchestrator` payload, @@ -82,31 +90,38 @@ usable functions from the running engine. ## Parity Gaps -Features that map cleanly to existing iii workers: +Highest-priority Kimchi/Pi parity gaps that map cleanly to existing iii +workers or CLI behavior: - MCP server configuration import/export. The `mcp` worker is now part of the optional coding profile; the missing piece is a setup helper around existing worker config surfaces. - Model switching and model metadata. `models::list` is already the read path; the missing piece is better CLI formatting and defaults. -- Permission presets. This should compile to `approval_required` values and - policy worker configuration. +- Permission presets. This should compile to `approval_required` values, shell + policy worker configuration, and named modes that match common coding-agent + expectations. - Continue/resume ergonomics. The shell and session-tree commands exist; next - work is a richer selector over entries and branches. + work is a richer selector over entries and branches, plus automatic recovery + when a turn times out before emitting a terminal event. - Session audit and benchmark smoke runs. These should use `run::start_and_wait` and stored `agent` state. +- Prompt recovery parity. Kimchi injects continuation nudges and strips stale + prompt scaffolding; equivalent behavior should live in the orchestrator or a + stable worker contract, with the CLI only surfacing controls. -Features that need more design before adding: +Features that need a worker or orchestrator contract before adding: -- Multi-model orchestration and subagents. That belongs in a worker or - orchestrator contract, not in the thin CLI. +- Multi-model orchestration and subagents. Kimchi/Pi provide this through + extensions; iii-code should expose it once the iii worker contract exists. - Tags and cost attribution. Likely should become metadata passed through the run payload and consumed by `llm-budget`, but there is no stable public contract in the current worker stack. - Project-mode execution. This is a separate project-state machine and should be a new worker if adopted, with the CLI only issuing commands. - Clipboard/image paste, web fetch/search, themes, and custom TUI affordances. - These are useful terminal UX features, but v1 stays plain streaming output. + These are useful parity features, not out-of-scope forever; they need clear + worker/function contracts or terminal UX designs. - ACP/editor mode. The upstream `acp` worker exists separately; `iii-code` should not bundle it unless the product target changes from terminal CLI to editor integration. diff --git a/src/app.rs b/src/app.rs index 2474ff5..c470848 100644 --- a/src/app.rs +++ b/src/app.rs @@ -46,6 +46,7 @@ const CORE_RUNTIME_FUNCTIONS: &[&str] = &[ "approval::list_pending", "sandbox::create", ]; +const CODING_FULL_RUNTIME_FUNCTIONS: &[&str] = &["mcp::handler", "iii-database::query"]; const AUTH_PROVIDERS: &[&str] = &["openai", "anthropic"]; #[cfg(test)] @@ -1149,15 +1150,44 @@ fn report_coding_full_profile( match client.worker_list() { Ok(worker_list) => { let missing = missing_configured_workers(&worker_list, CODING_FULL_WORKER_STACK); + if !missing.is_empty() { + let missing = missing.join(", "); + writeln!(out, "coding profile: error: missing {missing}")?; + return Ok(Some(ProbeFailure { + label: "coding profile".to_string(), + error: format!("missing configured workers: {missing}"), + })); + } + } + Err(err) => { + let error = err.to_string(); + writeln!(out, "coding profile: error: {error}")?; + return Ok(Some(ProbeFailure { + label: "coding profile".to_string(), + error, + })); + } + } + + match client.trigger( + "engine::functions::list", + build_functions_payload(false), + DOCTOR_PROBE_TIMEOUT_MS, + ) { + Ok(value) => { + let missing = missing_function_ids(&value, CODING_FULL_RUNTIME_FUNCTIONS); if missing.is_empty() { writeln!(out, "coding profile: ok")?; Ok(None) } else { let missing = missing.join(", "); - writeln!(out, "coding profile: error: missing {missing}")?; + writeln!( + out, + "coding profile: error: missing runtime functions {missing}" + )?; Ok(Some(ProbeFailure { label: "coding profile".to_string(), - error: format!("missing configured workers: {missing}"), + error: format!("missing runtime functions: {missing}"), })) } } @@ -1337,8 +1367,12 @@ fn report_harness_or_core( } fn missing_core_runtime_functions(value: &Value) -> Vec<&'static str> { + missing_function_ids(value, CORE_RUNTIME_FUNCTIONS) +} + +fn missing_function_ids<'a>(value: &Value, required: &'a [&'a str]) -> Vec<&'a str> { let ids = function_ids_from_value(value); - CORE_RUNTIME_FUNCTIONS + required .iter() .copied() .filter(|required| !ids.iter().any(|id| id == required)) @@ -2110,6 +2144,32 @@ mod tests { assert!(err.contains("coding profile")); } + #[test] + fn doctor_checks_coding_full_runtime_functions_when_workers_are_present() { + let workers = "mcp binary running\niii-lsp binary running\niii-database binary running\n"; + let runner = MockRunner::new(vec![ + MockRunner::ok("0.11.6\n"), + MockRunner::ok(workers), + MockRunner::ok(r#"{"ok":true}"#), + MockRunner::ok(r#"{"entries":[]}"#), + MockRunner::ok(r#"{"models":[]}"#), + MockRunner::ok(r#"{"configured":true}"#), + MockRunner::ok(r#"{"configured":true}"#), + MockRunner::ok(workers), + MockRunner::ok(r#"{"functions":[{"function_id":"mcp::handler"}]}"#), + ]); + let cli = Cli::try_parse_from(["iii-code", "doctor", "--coding-full"]).unwrap(); + let mut out = Vec::new(); + + let err = run(cli, &runner, &mut out).unwrap_err().to_string(); + let text = String::from_utf8(out).unwrap(); + + assert!( + text.contains("coding profile: error: missing runtime functions iii-database::query") + ); + assert!(err.contains("missing runtime functions")); + } + #[test] fn doctor_fails_when_workspace_fs_is_not_jailed_to_cwd() { let runner = MockRunner::new(vec![ diff --git a/src/cli.rs b/src/cli.rs index dec2eae..c354635 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -196,7 +196,7 @@ pub struct RunArgs { #[arg(long, default_value_t = 750)] pub poll_interval_ms: u64, - #[arg(long, default_value_t = 600_000)] + #[arg(long, default_value_t = 1_800_000)] pub stream_timeout_ms: u64, #[arg(long)] @@ -239,7 +239,7 @@ pub struct ResumeArgs { #[arg(long, default_value_t = 750)] pub poll_interval_ms: u64, - #[arg(long, default_value_t = 600_000)] + #[arg(long, default_value_t = 1_800_000)] pub stream_timeout_ms: u64, #[arg(long)] @@ -562,6 +562,7 @@ mod tests { assert!(args.approval_required.is_empty()); assert_eq!(args.image, "python"); assert_eq!(args.idle_timeout_secs, 300); + assert_eq!(args.stream_timeout_ms, 1_800_000); } _ => panic!("expected run command"), } @@ -638,7 +639,10 @@ mod tests { fn parses_resume_followup_and_session_tree_commands() { let resume = Cli::try_parse_from(["iii-code", "resume", "s1", "continue"]).unwrap(); match resume.command.unwrap() { - Command::Resume(args) => assert_eq!(args.prompt.as_deref(), Some("continue")), + Command::Resume(args) => { + assert_eq!(args.prompt.as_deref(), Some("continue")); + assert_eq!(args.stream_timeout_ms, 1_800_000); + } _ => panic!("expected resume command"), } diff --git a/src/payload.rs b/src/payload.rs index 3d00195..4783ad1 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -15,6 +15,10 @@ iii-code client context: - Installed iii workers and live iii functions are the capability surface. - Before saying a tool is unavailable, inspect live functions with agent_call to engine::functions::list and fetch relevant skill docs with skill::fetch. - Prefer installed worker functions for shell, filesystem, sandbox, approval, MCP, LSP, database, session, state, stream, and queue work. +- Behave like a full coding agent: inspect, edit, test, and keep going until the user's request is handled. +- If you say you will inspect, read, run, or edit something, call the matching worker function in the same turn. +- If a file read returns a channel or streaming handle instead of content, follow that handle or use shell::exec to retrieve the content before concluding. +- After two failed attempts with the same tool shape, change approach and state the pivot briefly. - If a needed capability is missing, name the worker or function that should be installed with iii worker add."; #[derive(Debug, Clone)] @@ -443,6 +447,8 @@ mod tests { assert!(text.contains("Installed iii workers")); assert!(text.contains("engine::functions::list")); assert!(text.contains("skill::fetch")); + assert!(text.contains("full coding agent")); + assert!(text.contains("same turn")); } #[test]