main
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
52ff30de91 |
feat(llm): resume the Claude Code session across a kill-chain phase (#149)
* feat(llm): resume the Claude Code session across a kill-chain phase Every ReAct iteration flattens the growing transcript into a brand-new prompt and spawns a fresh `claude -p` process. Within one task that is a quadratic resend; across a mission it also means each spawn re-pays Claude Code's own CLAUDE.md/skills/MCP bootstrap tax from scratch, since T3MP3ST never told the CLI these calls belong to one session. LocalAgentAdapter now tracks the Claude Code session id from the JSON envelope and passes it back in with --resume on every later call, so the CLI carries the accumulated transcript itself and the repeated part is billed at cache-read pricing instead of resent. The session is scoped to one kill-chain phase: TempestCommand drops it for every operator right after advancePhase(), since local-agent operators are spawned once for the whole mission and would otherwise carry one session across all 7 phases. A stale or expired session id fails fast (confirmed against the real CLI: nonzero exit, no JSON on stdout) and localAgentChat retries once without --resume rather than failing the task over it. Verified against the real CLI: a resumed call recalled prior context correctly and dropped from $0.151 to $0.008 (cache_read_input_tokens absorbing the prior turn instead of cache_creation), and an invalid --resume id triggered the fallback and still succeeded. Ran live against an authorized target: the same session id held across all 4 recon tasks in one phase, then a newly spawned operator's first call in the next phase carried no --resume, confirming both the resume and the reset. Full suite: 764/764, no regressions. * fix(llm): send only the delta on a resumed Claude Code call jmagly's PR #149 review caught it: LocalAgentAdapter.chat() built its prompt from the full messages array unconditionally, so a resumed call sent the whole growing transcript again on top of a session that already had it. The resend the feature exists to remove was still happening, and the resumed session's own history now grew on top of it too. Existing tests only asserted that sessionId reached the CLI, never what was actually in the prompt, so this passed green. LocalAgentAdapter now tracks the exact messages array object and how many of its entries were already sent. A later call against the SAME array (AgentLoop growing one task's transcript via push) sends only the new tail. A DIFFERENT array (a new task) still sends everything, since the resumed session has never seen that task's content even though the CLI session itself carries over. The stale-session fallback in localAgentChat now takes a separate fallbackPrompt: a fresh session has no history, so its retry gets the full transcript, never the delta sized for the failed resumed attempt. Verified against the real CLI with a synthetic multi-round exchange (~750 tokens of padding added between each call): promptTokens went 19127 -> 19625 -> 20135 -> 20645, a flat ~510 tokens per round regardless of the accumulating transcript, instead of resending or compounding. Added the regression coverage the review asked for: a same-array resumed call excludes the prior turn's content while retaining the tool contract, a new-array call still gets the full prompt, and the fallback retry receives fallbackPrompt rather than the delta, checked both at the LocalAgentAdapter call-shape level and against a real spawned CLI script. Full suite: 769/769, no regressions. * fix(test): import LLMMessage in local-agent-tool-calling.test.ts Hosted CI caught it, not my local run: the previous commit used LLMMessage[] type annotations in two new tests without importing the type, so tsc failed with TS2304 while vitest itself stayed green (esbuild strips type annotations without checking them). I ran typecheck once before writing those tests and never reran it after, so this went out uncaught. Full suite: 769/769. npx tsc --noEmit: clean. * fix(llm): isolate per-operator Claude sessions and narrow the fallback A self-review after the last round turned up a more serious version of what jmagly caught: TempestCommand built exactly one LLMBackbone and handed the SAME instance to every operator's AgentLoop (and to each OperatorAgent's own decompose-on-failure path). LocalAgentAdapter's new session/delta state lived on that shared instance, so one operator's Claude Code session could get resumed by a completely different operator, with the second operator's unrelated task content sent into the first operator's conversation. Every test for the resume feature so far constructed independent LLMBackbone instances per test, which is not how spawnOperator() actually wires things, so nothing caught it. spawnOperator() now builds a fresh LLMBackbone per operator from the same config, used for both that operator's AgentLoop and its own decompose fallback, so state stays consistent within one operator and isolated from every other one. OperatorCell.spawnOperator() takes an optional llm override to carry that through; existing callers that don't pass one keep the cell's shared default. Two narrower issues from the same review: localAgentChat's stale-session fallback retried on ANY failure of a resumed call, not just a confirmed stale session. A real network error or auth failure would silently double latency on a retry that could not fix it, and compounded with LLMBackbone's own retry ladder into up to 6 CLI spawns for one failing turn. The fallback now only fires for the exact empirically-confirmed stale-session error text ("No conversation found with session ID"). The character-estimate usage fallback (used only when the JSON envelope fails to parse) estimated from the delta prompt instead of the full transcript. A resumed call's real promptTokens reflects the whole cached conversation, confirmed empirically in the last round (19127 -> 19625 -> 20135 -> 20645), so estimating from the small delta text instead undercounted the budget check by an order of magnitude in that one edge case. One finding from the review was deliberately left alone: resending the tool contract on every delta call is real overhead, but jmagly's review explicitly required the contract stay on every call ("retaining ... the tool contract") and the existing test already pins that. Cutting it would contradict that requirement for a small, bounded savings. New coverage: an isolation test that spawns two operators through a real TempestCommand and confirms neither's LocalAgentAdapter nor its session leaks into the other, a test proving a non-stale failure propagates without a fallback retry, and a test proving the usage estimate reflects the full transcript on a mid-session parse failure. Full suite: 772/772, no regressions. * fix(llm): reset the Claude session at every task boundary jmagly's third review: switching from delta to full-prompt sending on a new messages array wasn't enough, since LocalAgentAdapter.chat() still passed the OLD claudeSessionId either way. A second task on the same operator inherited the first task's persisted Claude session, carrying its system prompt, target data, and tool output (including anything attacker-controlled) across a boundary T3MP3ST already has a single sanctioned channel for: PackBoard. chat() now clears claudeSessionId whenever the incoming messages array is not the one it last saw, before building the request. A different array means AgentLoop.run() started a new task; the call after that reset carries no --resume, so it opens a genuinely fresh session instead of resuming the old one under a bigger prompt. Within one task the array is the same object across iterations (AgentLoop grows it via push), so resumption and the delta-only send from the last round are untouched. Rewrote the tests that asserted continuity across separate array literals, since that shape doesn't happen in AgentLoop and was masking this exact gap: they now grow one array via push to represent a real task, matching the pattern already used for the delta tests. Added the test jmagly asked for directly: one operator, two sequential tasks, task 2 has no session id, both at the LocalAgentAdapter level and through a real TempestCommand.spawnOperator(). Full suite: 773/773, no regressions. --------- Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com> |
||
|
|
e05bdc4311 |
feat(llm): add native Novita AI provider via OpenAI-compatible endpoint (#155)
* feat(llm): add native Novita AI provider via OpenAI-compatible endpoint Wire Novita AI (https://api.novita.ai/openai/v1) as a first-class LLM provider, following the same pattern used for DeepSeek/HuggingFace/NanoGPT: dispatched through the existing OpenAIAdapter, resolved via NOVITA_API_KEY, with a default model, model catalog entries, config/setup wiring, and regression tests. * fixup(llm): use deepseek/deepseek-v4-flash-0731 as the second Novita model Swap the curated Novita model catalog's second entry from qwen/qwen3-coder-30b-a3b-instruct to deepseek/deepseek-v4-flash-0731 per requested model selection. Verified live against api.novita.ai/openai/v1/models. * fixup(llm): replace superseded DeepSeek Flash id with current Pro flagship QA flagged that deepseek/deepseek-v4-flash-0731 has been superseded in the live Novita catalog; deepseek/deepseek-v4-pro-0813 is now the current serving DeepSeek flagship. Swap the second Novita model catalog entry accordingly. --------- Co-authored-by: jax-novita <jax-novita@users.noreply.github.com> Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com> |
||
|
|
8ccea7c82a |
feat(llm): add Hugging Face provider + selectable local Claude Code models (#127)
* feat(llm): add Hugging Face provider + selectable local Claude Code models Adds two operator-selectable LLM backbones, wired through both the backend and the War Room UI. Hugging Face (OpenAI-compatible Inference Providers router): - New `huggingface` provider reusing OpenAIAdapter (router.huggingface.co/v1) - HF_TOKEN / HUGGINGFACE_API_KEY / HUGGINGFACE_TOKEN env resolution, config, setup wizard, CLI, factory, best-available, and .env.example - Settings key card + 4 inference models in the picker + direct client routing - Tests: src/__tests__/huggingface-provider.test.ts Local Claude from Claude Code: - Explicit "active backbone" pin in Settings so a connected agent (Claude Code) can be chosen; a pin outranks stored API keys in both the client resolver and mission dispatch - Claude Code model picker (Account default / Opus / Sonnet / Haiku), threaded to the CLI's --model via an "agentId::model" encoding parsed by LocalAgentAdapter; requireLiveLocalAgent strips the suffix for connected-agent validation * fix(llm): address PR #127 review — self-contained keyless path + centralized pin/model routing Finding 1 — keyless mission path was not self-contained: - Add the `local-agent` case to ConfigManager.getLLMConfig so provider:'local-agent' resolves (keyless, agent id — optionally "agentId::model" — in `model`) instead of hitting the default "Unknown provider" throw. Finding 2 — inconsistent pin/model routing across dispatch surfaces: - Centralize backend agent resolution in window.t3mpDispatchAgent (single source of truth). It honors an explicit pin OVER a stored API key and encodes the picked local model as "agentId::model" so it is never dropped. - Route all three surfaces through it: mission dispatch, General (getGeneralConfig), and Admiral (_admBackbone). Previously General/Admiral only picked an agent when no key existed and sent the bare agent id, so a stored key overrode the pin and the selected Claude model was lost. Tests: - getLLMConfig('local-agent') regression coverage (self-contained keyless path) - LocalAgentAdapter "agentId::model" passthrough reaches the CLI --model flag - t3mpDispatchAgent behavior: pin>key precedence, model encoding, fallbacks (evaluated from the shipped docs/index.html) - centralization invariant: mission/General/Admiral all route through t3mpDispatchAgent - update the Op-Admiral keyless-routing static test to the centralized resolver --------- Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com> |
||
|
|
be3eb7cb44 |
Merge remote-tracking branch 'upstream/main' into pr-70
# Conflicts: # src/config/index.ts # src/llm/index.ts # src/server.ts # src/setup.ts # src/types/index.ts |
||
|
|
26fe8495b9 |
feat: allow origin-scoped target headers from env (#65)
Enable authenticated scans across the HTTP arsenal while binding configured credentials to an exact target origin. Includes case-insensitive per-request overrides, cross-origin redirect containment, protected curl configuration, result redaction, documentation, and regression coverage. Co-authored-by: Fred Blaise <fred.blaise@protonmail.com> |
||
|
|
df37e7c423 | Address upstream issues 60 and 62 | ||
|
|
184c7d4e59 | T3MP3ST public launch build |