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
2 changes: 2 additions & 0 deletions CHAGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Keep proposed and unmerged changes out of released sections.

## [Unreleased]

- Proposed web terminal integration-test repair ([#1135](https://github.com/BitterSecurity/Decepticon/pull/1135)): wait for the rendered session/composer and verify real PTY draft persistence plus no agent-run submission, instead of waiting forever for the retired `connected` footer label. Keep all timeouts and the actual terminal/server flow. No product behavior change or release.

- Proposed workflow declared-edge commit validation ([#1131](https://github.com/BitterSecurity/Decepticon/pull/1131)): reject OPPLAN commits whose executable leaves declare no DAG edge (blocked_by, any_of or required_fact_ids) into stages named by the workflow's `depends_on` closure. The proof gate's reachable-leaf rule can never be satisfied for such edge-less plans, so they committed successfully and then blocked every objective transition and specialist dispatch with `workflow prerequisite stage incomplete on dependency path: <stage>` — observed live on 2.2.0 where an edge-less red-team plan completed its scope objective and then could not start the discovery stage, could not repair itself through `commit_opplan` (completed objectives are immutable) and burned the recursion budget in a rejection loop. The gate's proof semantics are unchanged; the failure now surfaces at commit time with an actionable message naming the objective, its stage and the missing prerequisite stages.

- Proposed step-budget async middleware fix ([#1130](https://github.com/BitterSecurity/Decepticon/pull/1130)): add `awrap_model_call` to `StepBudgetMiddleware` with a shared `_budget_note` helper so the sync and async paths stay identical. On 2.2.0, any engagement run invoked asynchronously (LangGraph background runs) crashed in the `model` node with `NotImplementedError: Asynchronous implementation of awrap_model_call is not available`, because `decepticon/middleware/step_budget.py` defined only the sync `wrap_model_call` and langchain's `AgentMiddleware` base raises on the missing async variant. Verified against a live 2.2.0 stack: an MCP `start_engagement` run failed in ~1.2 s before the first model call; with the patch the same call reaches the model and runs normally. Add async unit tests mirroring the sync budget cases (warn at 80%, hard stop at 100%, silent under budget, sync/async parity).
Expand Down
6 changes: 3 additions & 3 deletions clients/web/src/lib/terminal-cli-boot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";
import { stripVTControlCharacters } from "node:util";
import { WebSocket } from "ws";

test("web terminal boots the real CLI and renders its connected composer", { timeout: 120_000 }, async () => {
test("web terminal boots the real CLI and persists PTY input without starting a run", { timeout: 120_000 }, async () => {
const root = await mkdtemp(join(tmpdir(), "terminal-cli-boot-"));
await mkdir(join(root, "workspace", "loader-test", "plan"), { recursive: true });
await writeFile(join(root, "workspace", "loader-test", "plan", "roe.json"), JSON.stringify({ machine_enforcement: { in_scope: ["example.com"] } }));
Expand Down Expand Up @@ -60,10 +60,10 @@ test("web terminal boots the real CLI and renders its connected composer", { tim
socket!.on("message", data => {
output += data.toString();
const plain = stripVTControlCharacters(output);
if (plain.includes("connected") && plain.includes("Enter send")) { clearTimeout(timer); resolve(plain); }
if (plain.includes("loader-test") && plain.includes("Enter send")) { clearTimeout(timer); resolve(plain); }
});
socket!.once("error", error => { clearTimeout(timer); reject(error); });
socket!.once("close", () => { clearTimeout(timer); reject(new Error(stripVTControlCharacters(output).slice(-1200))); });
socket!.once("close", (code, reason) => { clearTimeout(timer); reject(new Error(`Terminal closed (${code}: ${reason.toString()}): ${stripVTControlCharacters(output).slice(-1200) || serverOutput}`)); });
});
assert.match(text, /loader-test/);
assert.doesNotMatch(text, /SyntaxError|Unexpected token|EACCES/);
Expand Down
Loading