mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
master
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cdceede33d |
fix(deepseek): atomic shim write, honest attribution comment, name-fallback profile classifier
The three smaller review nits, plus the first real test coverage for the status shim (it had none: it is emitted as a STRING, so tsc never sees it). 1. The shim was written with a plain writeFileSync. The TUI can be exec'ing that exact path while an upgraded Codeman refreshes it, and a reader catching a half-written file gets a syntax error, exits non-zero, and is retried four times per state change for a file that will never parse. Now temp + rename (atomic within the directory), with the temp chmod'ed before the rename since writeFileSync's mode only applies on create, and removed if the write throws. SHIM_VERSION bumped to 2, because SHIM_SOURCE changed and an existing v1 shim would otherwise keep matching the embedded marker and never be refreshed. 2. The pane-id comment claimed the ambient env "cannot be spoofed by an argument the agent itself could influence". The agent runs IN that pane and can invoke the shim with CODEMAN_SESSION_ID unset and any argv it likes. It buys nothing it did not already have (the hook-secret file is readable from the same pane, so it can POST /api/hook-event directly), but the comment read like a security boundary. Rewritten to say what the preference actually buys: correct attribution when a TUI mangles or re-uses the pane argument. Accidents, not adversaries. 3. classifyProfile() folded the directory name into the same haystack as the bundles, but only the TUI arm could match a bare name, so a stock profile whose package.json has no dsh.profile.bundles (hand-edited, older layout, mid-install) classified as `unknown` -> launchable -> eligible as the DEFAULT pick, which is exactly the pane-dies-on-arrival failure the two-part availability gate exists to prevent. The stock names are now a LAST-resort fallback consulted after the bundle patterns, so real bundle evidence still wins over a name the user chose. The loose `tui` arm gained word boundaries: it decides which profile boots by default, and matching the middle of `intuition` is not a rule anyone could predict. New test/deepseek-status-shim.test.ts runs the generated script the way the harness does -- real node process, real argv, real env, real listener -- and covers the exit-code contract that makes the retry behaviour safe: mapped states post and exit 0, an unknown verb or unmapped state exits 0 WITHOUT posting (a non-zero there would be four HTTP requests per state change forever), a rejecting server or an unreachable one exits non-zero so the caller retries, the hook secret is read at execution time, and `node --check` parses the file (a template-literal typo in SHIM_SOURCE is invisible to tsc). Trap worth recording, hit while writing it: the tests must spawn the shim ASYNCHRONOUSLY. The listener lives in the test process, so spawnSync blocks the event loop that has to accept the connection, the shim waits out its own 1500ms socket timeout and exits 1, and it reads exactly like a broken shim (measured: Socket._onTimeout in its --trace-exit output, server logging nothing). Verified: full gate green (6142 passed, +10), typecheck/lint/format clean. |
||
|
|
4cda150493 |
feat(deepseek): add DeepSeek Harness (dsh) as a ninth CLI run mode
Adds `mode: 'deepseek'` alongside claude/shell/opencode/codex/gemini/ antigravity/pi/grok, plus a shortcut that opens the harness's own browser UI as a Codeman web tab. DeepSeek is wired unlike its siblings in three ways, each of which is the reason for a design decision rather than an accident: 1. The agent is a PROFILE, not the binary. `dsh` is a launcher over $DSH_HOME/profiles/<name>, and DeepSeek ships only `web`, `headless` and `base` -- the interactive terminal front door is always a third-party plugin. So availability is two questions: `isDeepSeekAvailable()` (binary) and `isDeepSeekRunnable()` (binary AND a pane-capable profile). The Run button gates on the latter, because reporting only the binary would spawn a pane that dies on arrival. When the binary is present but no profile is, the run menu offers to install one (POST /api/deepseek/install-profile). 2. The permission switch is an env var, not a flag. The harness has no command-line permission option; its sandbox/approval rows read DSH_PERMISSION_MODE (read-only / workspace-write / danger-full-access). Exported via `tmux setenv`, never on the spawn line. Absent = the harness's own workspace-write, which still asks, so the multi-user clamp is the only-if-sent branch and clamps to workspace-write, never read-only. 3. It is the only non-claude mode that passes hooksAvailableForMode(), and it earned that. The terminal front door reports idle/working/blocked to a supervising process over a generic env-gated contract; a generated shim (deepseek-status-shim.ts) makes Codeman that supervisor and forwards each report to /api/hook-event as stop / agent_working / permission_prompt. So a dsh session gets definitive respawn triggers, real wait-endpoint signals and real Approvals Inbox items instead of output-stabilization guesswork. `agent_working` is new (157th SSE constant) and joins APPROVAL_RESOLVING_EVENTS so a dialog answered in the terminal clears its alert at once. The resolver needs the strictest identity probe of the family: `dsh` is not merely a squattable npm name, Debian ships an unrelated `dsh` (dancer's shell), so `dsh --help` must print the harness's own banner before a candidate is handed a spawn line. Model is deliberately not a session field -- it is a composition entry in the profile's config tree. Env allowlist gains DSH_* and DEEPSEEK_* only; provider keys named by a settings-file `apiKeyEnv` stay out, which is pi's 34-provider-key problem in a new shape. Verified live against dsh 0.1.1-rc.2 and @deepseek-harness-tui/dsh-tui: the status endpoint's two-part answer, the no-profile refusal, the profile bootstrap, a real session whose pane runs `dsh --profile dsh-tui` with the permission mode injected via setenv, and the full status bridge -- a send-and-wait returned signal "stop" from a real turn, and blocked/working created and cleared an Approvals Inbox item. Docs: docs/deepseek-integration.md (guide), docs/deepseek-integration-plan.md (decisions + honest gaps). Tests: test/deepseek-mode.test.ts, test/deepseek-cli-resolver.test.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |