Skip to content

DreadGOAD console and cloud operations - #463

Open
mkultraWasHere wants to merge 86 commits into
mainfrom
feature/web-app
Open

DreadGOAD console and cloud operations#463
mkultraWasHere wants to merge 86 commits into
mainfrom
feature/web-app

Conversation

@mkultraWasHere

Copy link
Copy Markdown
Contributor

Adds an agentic browser workflow while hardening the CLI and cloud paths it drives.

Added

  • Agentic web console for creating or attaching ranges, streaming commands, inspecting topology, and managing persistent sessions.
  • Provider-neutral exec, report fetching, structured status and health output, and per-host lifecycle controls.
  • Environment and variant scaffolding, cloud region and capacity guidance, and optional Kali attack-box infrastructure.

Changed

  • Provisioning and inventory flows now validate live addresses and credentials, repair generated variants, and retry transient cloud and AD failures.
  • Console state now preserves threads and layouts while bounding event history, command progress, backups, and runtime cleanup.

Breaking

  • diagnose is removed; use exec --hosts <hosts> --cmd <script> for control-plane diagnostics and recovery.
  • infra destroy now confirms and defaults to a 20-minute timeout; automation must pass --auto-approve and may pass --timeout 0 for no limit.
  • Azure up now deploys the billed Bastion and controller prerequisites; use infra apply directly when managing a custom subset.
  • Generated deployments now use dreadgoad instead of goad; keep existing environment and state naming aligned before applying.

Fixed

  • Azure ranges no longer retain placeholder inventory addresses or mismatched generated credentials.
  • Cross-domain group provisioning and PSRP recovery now wait through transient trust-channel and transport failures.
  • Console cancellation, destructive-command confirmation, stale layout writes, and session deletion no longer leave misleading or orphaned state.

mkultraWasHere and others added 30 commits August 3, 2026 19:45
Add webapp/ (FastAPI backend skeleton + Vite/React frontend skeleton + ./dreadgoad-web launcher) and a --json output mode for 'dreadgoad lab status' that the ingestion hook will consume. Includes Go unit tests for the JSON marshaller.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add db.py: async SQLite layer (sessions/ranges/events/meta, WAL) with a single-worker executor serializing all writes for concurrency safety. Event log assigns monotonic per-session seq. Standalone-runnable tests cover CRUD, seq/replay/kind-filter, and a 100-write concurrency check (no lost/duplicate seqs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add labconfig (snapshot derivation: provider/region file-level, variant/lab/network per-env; 3-way topology seeding from config.json; versioned yaml backup + write-new-env), SessionService (create/list/get/delete + create-new-env), and session lifecycle + RangeView REST endpoints. Unit tests for snapshot/seeding/backup and session service; TestClient tests for the REST layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the 14-command registry + provider-agnostic argv builder (injects --config/--env), a streaming CLI runner (cwd=repo root, SIGINT cancel), the per-session dreadgoad agent factory (Sonnet 5 via OpenRouter, fs-sandboxed, telemetry-free), and the multiplexed /ws/chat endpoint (direct-dispatch slash commands vs LLM free-text, event persistence + replay). Unit tests cover argv construction and the streaming runner; live chat is a manual test (needs OPENROUTER_API_KEY).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the post-command hook: map_range_status overlays live instance state (from lab status --json) onto config-seeded hosts — matched hosts get status/ip/cloud_id, unmatched config hosts go absent, unmatched instances ignored; infra nodes match via aliases (kali→attackbox). run_check wires it to CLI+DB with stale-preserving failure handling; fired after every command in the chat flow and surfaced as an inline check_run. 6 mapping unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
React SPA: two-pane shell + resizer, session tab bar + new-session modal, TerminalChat over a single multiplexed WebSocket (routes events per session_id, background tabs stay live), and RangeView (React Flow nodes by role + status/health badges, persisted layout). Adds the layout-persist REST endpoint. Builds clean under strict TS + vite; visual/interactive behavior is the manual test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Runner streams stdout lines live (async iterator) for a real-time tail; per-session cancel sends SIGINT (graceful unwind). Long-op status transitions (provisioning → running/destroyed/error), and a startup reconciliation that flips a crash-killed 'provisioning' session to 'interrupted'. WS-drop re-attach is via resume/replay. Tests cover live streaming and SIGINT-cancel-before-completion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lifecycle/verify commands work via the registry (Phase 3). Adds the /health overlay (range-level verdict from exit code; per-host --json is a follow-up) and topology re-seed after /extensions|/variant (merge_reseed preserves live state + layout while adding/removing nodes). /score builds score --report with the given path; remote SSM/SSH fetch of the report is a documented follow-up (needs cloud). merge_reseed unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
run_check merged stderr into stdout, so a stray log/warning line on an otherwise-successful 'lab status --json' would corrupt json.loads and the range would never update. Add cli.capture() (separate streams) and use it in the hook; parse clean stdout, use stderr only for error messages. Adds a regression test and gitignores the tsbuildinfo artifact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apply ruff formatting across the backend; resolve pyright errors (None-guards on Optional DB reads in tests, targeted type-ignore on the dreadnode Toolset async-CM protocol quirk in agent.py); guard a None session in chat direct-dispatch so a command for a deleted/unknown session returns an error instead of crashing the WebSocket. All tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Findings from the phase-by-phase deep review, all with tests + green ruff/pyright/go:
- P0: launcher pip works on uv venvs; lab status --json output documented (raw cloud fields).
- P1: SQLite doc accuracy (WAL/NORMAL), get_events empty-kinds, connect() leak guard, meta test.
- P2: REST create returns 400 on bad config/unknown env (was 500); derive_snapshot validates env.
- P3: multiplexed WS runs turns in per-session tasks (cancel reachable, tabs don't freeze); per-session lock; agent eviction; chat.py tests.
- P4: read-failure no longer clobbers session status; absent clears stale ip/cloud_id; run_check tests.
- P5: RangeView live-refreshes on check_run; destructive tab-close confirm; stable message keys.
- P6: cancel UI (Esc) + destructive-cancel warning; cancel->interrupted; process-group SIGINT; reconcile test.
- P7: extension list --json + reseed surfaces extension nodes; /score fetches remote report (SSM/SSH); apply_health/reseed tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split slash commands into two dispatch paths: deterministic reads (+
/destroy) run the CLI directly, while the arg-flexible/mutating commands
(/up, /provision, /reset, /variant, /extensions, /score) expand into a
structured prompt and run through a constrained run_dreadgoad tool. Both
paths share one run_cli pipeline, so agent-initiated ops get the same
streaming, status, ingestion hook, and cancel behavior. The agent can no
longer reach /destroy or raw cloud CLI (only run_dreadgoad + sandboxed fs).

Prompt content moves to editable markdown under prompts/: system.md as the
shared system prompt ($placeholder template) plus per-command guidance files
whose flags were pulled from the CLI source (no hallucinated flags). Adding
guidance to a command is now a drop-in file, no code change.

Also hardens the command layer: resolve_bin prefers the repo's freshly-built
binary over a possibly-stale PATH one (C3), direct commands reject stray args
instead of shelling out to a CLI error (C4), and parse_command uses shlex so
quoted paths survive (C5).

Co-Authored-By: Claude <noreply@anthropic.com>
Add an ALFRED-style command palette to the chat input. A GET /api/commands
endpoint serves the registry (name, description, dispatch, long_running,
takes_args); the frontend fetches it once and renders a filtered dropdown as
you type "/", with arrow/Tab/Enter navigation. Each row is tagged by dispatch
(agent vs direct) so it's clear which commands take free-form args.

Also retint the interactive accent from the inherited ALFRED gold (#f5c842)
to a coral tint of the brand orange (#ff7a52), keeping the palette in one hue.

Co-Authored-By: Claude <noreply@anthropic.com>
Two deferred command-review follow-ups:

/score: the attack box is only known post-deploy, which left report
fetching unusable. The ingestion hook now learns its cloud id from the
same `lab status --json` read (the box appears there like any instance)
and persists it to the session snapshot, so fetch has a target after any
deploy/check.

/health: add `health-check --json` emitting per-check results, and map
them to per-host verdicts (any FAIL -> unhealthy) with a fallback to the
old exit-code range verdict. A failing health check no longer marks the
session errored — that outcome lives per-host, so the lifecycle stays
running.

Co-Authored-By: Claude <noreply@anthropic.com>
Render /health as a structured table instead of a raw JSON dump: the
backend emits a health_report event (parsed from health-check --json,
robust to requireInfra's stdout prefix) and suppresses the raw progress
lines; the frontend shows per-check status with a pass/fail summary.

Fetch the /score report via a new `dreadgoad score fetch` command that
reuses the CLI's own connection machinery — SSM on AWS, Azure Bastion on
Azure (auto-discovering the Kali VM + SSH key) — instead of hand-rolling
scp in Python. This finally makes Azure report fetch work and stops the
web app from reimplementing cloud connectivity. Guards against SSM's
24k-char stdout cap so a truncated report can't be silently mis-scored.

Co-Authored-By: Claude <noreply@anthropic.com>
Document boot steps, env vars, the slash-command set (agent vs direct
dispatch), the prompts/ layout, and the backend module map. Move the
default port off 8420 to 7331 to reduce collisions with common local
services; update the launcher and the vite dev proxy in lockstep.

Co-Authored-By: Claude <noreply@anthropic.com>
Add an editable model field in the chat header so the operator can change
a session's agent model on the fly. The switch preserves the conversation:
under the session lock (so it can't race an in-flight turn), the agent is
rebuilt with the new model and the old thread's messages are grafted on, so
the dialogue continues on the new model. A status line records the change.

Backend: PUT /api/sessions/{id}/model + chat.swap_model; "status" added to
replayed event kinds. Frontend: ModelField, api.setModel, and a status
renderer. Per-session (not global), matching the multiplexed-session design.

Co-Authored-By: Claude <noreply@anthropic.com>
Let the operator set the LLM API key from the UI (⚙ in the tab bar) instead
of only exporting it before launch, so the app can boot keyless and start
agent turns without a restart. POST /api/settings writes the key into the
named env var in-memory only — never persisted (honors no-creds-in-DB) and
never returned. /api/config exposes api_key_set so the UI shows a "no key"
warning until one is provided.

Restrict api_key_env to key/token-shaped names so the endpoint can't
overwrite PATH/LD_PRELOAD and hijack the CLI's terraform/aws/az subprocesses.
Key stays per-process/global; model remains per-session.

Co-Authored-By: Claude <noreply@anthropic.com>
New Session UX:
- Empty state when no sessions exist (no chat/range panes until one opens).
- Environment field is now a dropdown fetched from the chosen config
  (GET /api/environments), refreshed when the config path changes.
- "New environment" tab creates an env in an existing config (mode="new")
  with collision + valid-config guards, then attaches.
- Fix chat-input alignment (the `>` inherited a larger font than the box).

Agent capability:
- The agent's run_dreadgoad may now run ANY registered command
  (AGENT_RUNNABLE = all): reads (/instances, /health, …) to answer
  questions, and actions to perform them — so it stops probing with a
  mutating command when asked something read-only. Operator-typed reads
  still take the fast direct path (dispatch unchanged).
- Reframe the system prompt: answer questions via the READ commands;
  confirm intent before state-changing ones (always before /destroy).
  Safety is by prompt (operator's choice), no hard block.
- Harden list_environments against non-mapping YAML (400, not 500) and
  drop the now-unused AGENT_COMMANDS constant.

Co-Authored-By: Claude <noreply@anthropic.com>
Health: `health-check --json` now streams NDJSON (one compact line per
check as it completes, then the report line), so the web app shows each
check live instead of a silent multi-minute wait; the report line drives
the table + per-host overlay.

Cancel: Esc now reliably stops a run. The web app SIGINTs the process
group, then escalates to SIGKILL after a grace if it hasn't exited (some
commands trapped SIGINT — /health never died). Grace is command-aware:
terraform/ansible ops get a long runway to unwind gracefully; reads get a
short one. health-check is also signal-aware (NotifyContext) so SIGINT
cancels its context promptly.

Co-Authored-By: Claude <noreply@anthropic.com>
- Unify model + API key into one gear settings panel (ALFRED-style); show
  the model name in the AGENT header (click → settings).
- RANGE header: orange label + labeled identity fields (env / provider /
  region) pulled from the session.
- DC tile icon → 🌐 (globe); connection indicator is now a colored dot
  (green/amber/red) like ALFRED; "agent working" uses the braille spinner.
- Empty state when no sessions; fix chat-input `>`/text alignment.

Co-Authored-By: Claude <noreply@anthropic.com>
Render /instances as a structured table (instances_report event) instead
of a greyed raw-JSON blob — mirrors the /health pipeline: raw lines are
suppressed and the parsed array is surfaced with per-state color dots.

UI: user messages get chat bubbles; the input auto-grows upward across
lines; the command popup follows keyboard nav (scrollIntoView) and shows
white descriptions; range header fields are larger with electric-blue
labels; the redundant settings gear is dropped (open via the model name
or the no-key indicator). Split heavy vendor libs into their own chunks
so the build no longer trips the 500 kB warning.

Co-Authored-By: Claude <noreply@anthropic.com>
…eports

The surface is a console, not a web app — renamed the directory, the
launcher (dreadgoad-web → dreadgoad-console), the env vars and the state
root, with the old DREADGOAD_WEBAPP_* names kept as fallbacks and
.dreadgoad/webapp/ migrated on first run.

Range identity is now provider-neutral. The Go CLI reports `account` and
`group` on lab status --json (AWS Reservation.OwnerId, Azure subscription
+ resource group from the ARM id), and the ingestion hook stores them at
the snapshot top level. Filing an AWS account under an Azure-shaped key
was the alternative, and it was already happening.

/validate and /scrub return structured reports rendered in chat instead
of raw CLI text, and /scrub now defaults to applying — someone typing
"clean the box" means clean it, and a command that silently changed
nothing was the more surprising behaviour. Pass 'dry' to preview.

Also here:
- fix a /health hang: Process.wait() blocks on inherited pipes, so the
  runner polls returncode and drains on a budget instead
- cancel is reported as cancelled, not as "exit -2"
- a WebSocket Origin check; browsers don't apply same-origin policy to
  handshakes, so any page could otherwise drive the console
- elapsed-time counter on the working indicator, surviving a reload
- RANGE header pills wrap rather than being clipped by the status block

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hell

/diagnose has never worked on this branch. It died on an undefined
`data_path` before reaching a host, and behind that failure sat a
playbook hardcoded to a different variant's domain names, AWS-only IP
discovery, and a CLI verb that never opened the Azure bastion tunnel it
would have needed. It cost a real troubleshooting cycle this week, so
it and its playbook are deleted rather than repaired.

/exec replaces it, built on RunCommandOnMultiple — the provider-agnostic
path that `ssm run` and `runcmd run` already use, minus their provider
gates. It goes through Azure Run Command / AWS SSM rather than WinRM,
which is the point: it reaches a host whose WinRM listener is down,
exactly when provision and health-check cannot connect at all.

Host matching is deliberately stricter than the ssm/runcmd helper it
sits beside. That one substring-matches, so `dc0` silently selects three
DCs; for a verb that mutates, an ambiguous or unmatched token is an
error and --hosts has no "all" default.

The agent drives it with a PowerShell payload and a host list, never a
raw cloud CLI, so both providers look the same to it and every
invocation stays inside the event pipeline. prompts/exec.md carries what
it can't infer: output is capped at 4096 bytes per stream, there is no
dry run, host output is untrusted data from a range other agents attack,
and /validate closes the loop after any change.

Also here:
- system.md gains region, variant name and CIDR, so the agent stops
  guessing at facts the config already knows. Hook-learned fields stay
  out: instructions render once and cache, so they would freeze empty.
- absent snapshot fields render "(not set)" rather than the string
  "None", which read to the model as a real value.
- summarize_exec flags stdout landing on the provider's cap, measuring
  before strip() — PowerShell output ends in a newline, so the common
  case was silently unflagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/exec claimed to reach hosts whose WinRM was down, and did not.
AzureProvider.RunCommand goes over WinRM through the bastion tunnel, so
the verb inherited the exact dependency it advertised avoiding and
failed against the first genuinely wedged host it met — a live session
shows it returning "winrm run on 10.1.1.7" while diagnosing a DC that
had stopped answering on 5985.

Execution now goes through provider.OutOfBandRunner (Azure Managed Run
Command, AWS SSM), and a provider that doesn't implement it is refused
rather than silently downgraded to an in-guest channel. RunCommand keeps
the WinRM fast path for the fan-out callers that want it: validate,
health-check, verify-trusts.

Two further defects the same session exposed:

- Success was counted as failure. exec compared Status against
  "Succeeded" while every provider in the tree emits "Success", so a
  healthy run would have reported "0 succeeded" and exited non-zero.
  Invisible until now only because the host under test never succeeded.
- Windows PowerShell writes its fatal banner as UTF-16LE, which reached
  the model and the chat pane as "W i n d o w s   P o w e r S h e l l".
  Decoded at the provider boundary, with the console repeating the check
  since it can be pointed at an older CLI binary than it shipped with.

/restart <host> exposes `lab restart-vm`, which existed in the CLI but
not the registry — so the agent inspected /stop and /start, found no
per-host flag, and told the operator a single-host reboot was
impossible. It is the fix when a host is too wedged to repair from the
inside.

RangeView node spacing was derived from HostNode's maxWidth, which is
the content box; the rendered node is 26px wider, so neighbours
overlapped by 16px and adjacent tiers by 8px. Spacing now comes from the
border box with a 28px gutter, and the hostname and role lines are
clamped to one line — unclamped they wrapped past the height the tier
spacing assumes, which is what made the bound false rather than merely
tight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…troy error

Three unrelated pieces of my own work; the backend changes in the tree
belong to another session and are deliberately left unstaged.

infra destroy reported "infra working directory not found", which reads
as "recreate the directory". Terraform state here is local and
gitignored, so a recreated directory starts from EMPTY state and would
plan to CREATE the range a second time rather than tear down the running
one. An operator following that hint goes looking for a directory that
would not have helped while the resources keep billing — which is what
happened. The error now says the state is missing, why recreating it
won't help, and where to go instead. It is action-aware: a first apply
has no state by definition and keeps the old scaffolding guidance, while
destroy on a scaffolded-but-never-applied directory is now refused too
rather than running and quietly doing nothing.

/help explains a range run end to end — deploy, verify, validate, fix,
score, reset, tear down — and renders in an empty chat pane so a new
session opens on the workflow instead of a blank screen. Command
descriptions come from the live registry rather than being written out
again, so a renamed command can't leave the guide lying. It is
client-side: it maps to no CLI verb, and the agent should not be able to
"run" a guide.

The range header cost two rows because spacing came from a segmented
pill that puts the label beside the value — "RESOURCE GROUP" spent
~110px before its value began. Stacking the label above reclaims that
for ~4px of height, and provider/region merge into one field since
neither is useful alone. Measured max-content: 519px for a normal Azure
range, so one row from ~700px up; a legal 90-char resource group still
wraps, and truncates rather than escaping the panel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…24749

Frontend polish plus one test fix. The backend files here are another
session's code; my changes to them are formatting and test-only, noted
below.

The "Agent working" label now carries a shimmer: a bright band clipped
to the glyphs and swept left to right. Only background-position moves
per frame, so nothing lays out or composites. The colour and opacity
moved out of the component's inline style, where an inline `color`
would have beaten the gradient outright and an inline `opacity` would
have faded the highlight along with the base, flattening the sweep.
Chrome and Safari paint -webkit-text-fill-color over `color`, so that
is cleared too or the glyphs stay opaque and hide the gradient — and
the braille spinner, a ::before, restates the fill colour so it stays
steady against the sweeping label. Reduced motion drops the gradient
as well as the animation: frozen, the band parks over one end of the
label and reads as a rendering fault.

The AGENT and RANGE banners sit at the top of the split and read as one
bar, but were misaligned by exactly one pixel — box-sizing is
border-box globally, so RANGE's 1px bottom border pushed it to 51 while
AGENT sat on 50. Both now share --dg-pane-header-h, as minHeight so the
range header can still grow when its fields wrap.

A new session opens on the workflow guide, which is taller than the
pane, so following the transcript to the end landed the reader on its
last line. The pane now pins to the top while it holds only the guide
and resumes following as soon as a turn produces output.

Port 7331 becomes 24749 == 0x60AD, "GOAD" in hex. Outside the Linux
ephemeral range (32768-60999) so the bind never races an outbound
connection, and unregistered with IANA.

test_cancelled_command_aborts_turn_before_agent_can_retry failed on
roughly half of all runs. FakeRC separates its lines with sleep(0), so
all 100 drain inside a single turn of the loop and the command joins
and leaves `running` almost instantaneously; whether the poll caught
that window came down to how many loop turns the dispatch path happened
to take. Widening the poll's delay alone made it fail every time, which
is what identified the cause. A local subclass with a real per-line
delay holds the command in `running` until the test cancels it — the
state actually under test. The neighbouring poll gets a real delay too;
its fake blocks, so it cannot overshoot.

Remaining backend edits are ruff format only, and two assertions that
pin dispatch's `Task | None` contract instead of awaiting it blindly.
console/backend/cli.py and sessions.py are also unformatted but are
left alone — no other change here touches them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mkultraWasHere and others added 9 commits August 21, 2026 13:19
Destroy now prompts "Type 'yes' to confirm" in Go, then always passes
--auto-approve to Terragrunt/OpenTofu, eliminating the downstream prompt
that hangs on EOF in non-interactive contexts (console, CI, piped input).

A --timeout flag (default 20 min) wraps the subprocess context so stuck
CustomScriptExtension deletes are detected and reported with the last 20
lines of output. The up command forwards the flag with a 0 (no limit)
default so TestUpInfraCommandForwardsEveryFlag remains green.

Co-Authored-By: Claude <noreply@anthropic.com>
Wire `dreadgoad score generate-key` into both session creation paths so
the agent has an answer key in its sandbox from the start. Previously
the key was never generated, leaving agents unable to score until someone
manually ran the CLI command.

Co-Authored-By: Claude <noreply@anthropic.com>
The groups_domains role adds cross-domain members immediately after
trusts are created, but the Netlogon secure channel that backs
cross-domain LDAP auth can take well past the task's 3-retry budget
(~3 min) to finish establishing — especially on Azure with WinRM/PSRP
over Bastion. This surfaces as "server has rejected the client
credentials" on Get-ADUser -Server <peer-domain>, which looks like a
permissions problem but is actually a not-yet-ready condition.

Add an nltest /sc_query gate that polls each peer domain's secure
channel (retries: 30, delay: 20s, ~10 min budget) before attempting
the cross-domain adds. Also bump the add task's own retries from 3→5
as a safety net for channels that pass nltest but still fail on the
first authenticated LDAP call.

Co-Authored-By: Claude <noreply@anthropic.com>
- scaffold.generate_answer_key: early-return when anchor has no
  config_path instead of passing str(None) to --config
- agent.create_agent: raise ValueError when session_dir is absent
  instead of silently sandboxing writes to the project root
- ruff format fixes for hostdetail.py, test_chat.py, test_hostdetail.py

Co-Authored-By: Claude <noreply@anthropic.com>
Backend: consolidate Capture type into cli.py, replace _suppress with
contextlib.suppress, remove frozen DEFAULT_MODEL, fix _runner exception
handler, move WebSocket unregister to finally, fix extension node fields,
fix test import namespaces, clean up __import__("re").

Frontend: fix RangeView/App.tsx race conditions with cancelled-signal
pattern, fix CopyableCommand timer leak, extract shared Modal component
from 4 duplicated sites (backdrop, escape, stopPropagation).

Co-Authored-By: Claude <noreply@anthropic.com>
CopyableCommand: deduplicate the copy-to-clipboard textarea from
ConnectModal (CommandField) and RangeView (CopyableCommand) into a
shared component with compact/full visual modes.

NewSessionModal + FormFields: move the 426-line NewSessionModal and
the shared Field/Select/btnStyle helpers out of App.tsx (897 → 366
lines) so each module has a single concern.

Progress cap: keep at most 200 consecutive command_progress lines
per run in the message array — a sliding window that drops the
oldest lines as new ones arrive. These events are already ephemeral
(persist=False) and were the only unbounded growth vector.

Co-Authored-By: Claude <noreply@anthropic.com>
- Prune events table (keep 2000) and cap replay to 500 chat events
- Rotate backup files, keeping only the 5 most recent
- Stabilise React keys on history replay using server-assigned seq
- Track and clear autoScroll / WebSocket reconnect timers on unmount
- Extract duplicate shortId/shortName to shared shortResourceId
- Fix module-level env var pollution between test files
- Clean up temp DB files in test teardown
- Remove unused RangeEdge type

Co-Authored-By: Claude <noreply@anthropic.com>
/copy [N|all] copies the last N agent messages to the clipboard,
client-side only. /status runs /instances then /health in one agent
turn so the operator doesn't have to type both.

Co-Authored-By: Claude <noreply@anthropic.com>
Static scoring misses live_auth credentials (password resets, shadow
credentials) — the agent kept omitting the flag because the prompt
listed it as optional rather than default.

Co-Authored-By: Claude <noreply@anthropic.com>
@dreadnode-renovate-bot dreadnode-renovate-bot Bot added lab/GOAD Changes made to GOAD lab area/playbooks Changes made to playbooks directory area/ad-labs Changes made to AD lab definitions area/roles Changes made to Ansible roles area/docs Changes made to documentation lab/GOAD-Light Changes made to GOAD-Light lab labels Aug 25, 2026
Resolve the test and Go module conflicts while retaining the feature branch's variant coverage and main's dependency updates.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the agentic web console and strengthens cross-cloud provisioning, diagnostics, lifecycle management, and recovery.

Changes:

  • Adds the FastAPI/React console with persistent sessions, topology, streaming commands, and reports.
  • Introduces provider-neutral execution, report fetching, Azure capacity checks, and safer infrastructure lifecycle handling.
  • Improves inventory repair, variant generation, retries, and Windows provisioning reliability.

Reviewed changes

Copilot reviewed 119 out of 188 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
.gitignore Ignores console and generated artifacts.
console/backend/* Implements console APIs, persistence, synchronization, prompts, and runtime lifecycle.
console/backend/tests/* Tests command cancellation and report fetching.
console/frontend/* Implements the React console, shared components, state handling, and tests.
docs/mkdocs/docs/* Documents exec and removes diagnose.
cli/cmd/* Adds execution, fetching, capacity, inventory, environment, and infrastructure behavior.
cli/internal/* Extends providers, Azure capacity checks, retries, configuration, and output normalization.
cli/go.mod Promotes mapstructure to a direct dependency.
modules/terraform-azure-* Updates Kali bootstrap and Windows extension execution.
modules/terraform-aws-kali/* Installs attack-box tooling.
infra/goad-deployment/* Aligns AWS deployment names and tags.
infra/azure/goad-deployment/* Aligns Azure names, tags, and bootstrap paths.
ansible/roles/* Improves PowerShell module installation and trust recovery.
ansible/plugins/modules/win_ad_object.ps1 Handles nullable AD attributes safely.
ad/GOAD*/scripts/* Normalizes constrained-delegation attribute values.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cli/cmd/infra_state.go Outdated
Comment thread cli/cmd/infra_state.go
Comment thread cli/cmd/doctor_capacity.go Outdated
Comment thread cli/cmd/exec.go Outdated
Comment thread cli/cmd/score_fetch.go Outdated
Comment thread console/frontend/src/components/CopyableCommand.tsx Outdated
Comment thread console/frontend/src/components/Modal.tsx Outdated
Comment thread modules/terraform-azure-instance-factory/main.tf Outdated
Comment thread cli/internal/ansible/errors.go Outdated
Comment thread console/backend/session_routes.py
Correct provider-aware state checks, preserve command cancellation, harden console lifecycle behavior, and tighten Windows bootstrap and retry handling.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 120 out of 189 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

console/backend/prompts/up.md:19

  • --from infra resumes the pipeline at infra and then continues through provisioning and health-check; it does not “just redo infra.” This guidance can make the agent rerun every playbook when the operator explicitly requested an infra-only action. Describe it as “resume from infra” and state that infra-only work must use infra apply directly.
    console/backend/prompts/exec.md:58
  • This recovery advice power-cycles the entire range, even though /exec targets individual hosts and the newly added /restart <host> command exists specifically for one wedged VM. Following the prompt could unnecessarily take every domain controller and server offline. Recommend /restart <host> here.

Comment thread modules/terraform-azure-instance-factory/main.tf Outdated
Remove decoded Azure bootstrap scripts even when execution fails and correct agent guidance for infra-only work and targeted host recovery.
@mkultraWasHere

Copy link
Copy Markdown
Contributor Author

Addressed the two actionable prompt findings from Copilot’s follow-up review in 73db004: /up --from infra is now described as resuming through provisioning/health-check with dreadgoad infra apply for infra-only work, and /exec recovery now recommends targeted /restart <host> instead of cycling the whole range.

mkultraWasHere and others added 2 commits August 26, 2026 11:18
…nd context

- /secure: network security audit (NSGs, public IPs, bastion) with live
  progress, structured report, and SecurityChecker provider interface
- ConfirmModal: replace all window.confirm() with DreadGOAD-themed modal
  dialogs for /destroy, session delete, cancel, and unverified commands
- Direct command thread injection: append a [System: ...] note to the
  agent thread when operators run commands directly, so the LLM knows
  what happened between its turns; system prompt updated to explain this

Co-Authored-By: Claude <noreply@anthropic.com>
@dreadnode-renovate-bot dreadnode-renovate-bot Bot added the area/pre-commit Changes made to pre-commit hooks label Aug 26, 2026
mkultraWasHere and others added 2 commits August 26, 2026 13:31
After /destroy, inventory sync marks hosts as absent but left the
health field at its last value — so destroyed VMs showed "healthy".

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ad-labs Changes made to AD lab definitions area/docs Changes made to documentation area/playbooks Changes made to playbooks directory area/pre-commit Changes made to pre-commit hooks area/roles Changes made to Ansible roles lab/GOAD Changes made to GOAD lab lab/GOAD-Light Changes made to GOAD-Light lab

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants