Commit Graph
24 Commits
Author SHA1 Message Date
KRATOS 58e209d021 Sound / completion notifier: terminal bell + OSC-9 on turn completion & awaiting-input (#149)
* notify: dep-free terminal bell / OSC-9 completion notifier

* config: notify.mode + notify.focusMode (validated, default off)

* exec: --notify/--no-notify + completion notify on stderr

* tui: focus reporting + completion/awaiting-input notifications

* notify: fire on exec --use-spec completion; headless ignores focus; gate focus-reporting on enabled

* notify: validate --notify value, skip notify on empty ask_user, deterministic resolver tests

Addresses #149 review (CodeRabbit + @Vasanthdev2004):
- parseExecArgs rejects an invalid --notify value (only off/bell/notify/both),
  so a CLI typo errors early instead of silently running with no notification.
- TUI OnAskUser fires AwaitingInput only when len(request.Questions) > 0 — a
  request with no questions auto-resolves without prompting, so the bell/desktop
  notification was a false positive.
- notify resolver tests pass Env: map[string]string{} so Resolve does not fall
  back to os.Getenv (deterministic, not ambient-env dependent).
2026-06-09 14:34:24 +05:30
KRATOS 2ea36cf138 Deferred (lazy) tool loading: tool_search + auto threshold for large MCP tool sets (#148)
* Deferred (lazy) tool loading: tool_search + auto threshold for large MCP tool sets

Adds deferred tool loading so a large MCP tool set no longer sends every tool's full JSON schema each turn. When the number of visible MCP tools crosses a threshold (config tools.deferThreshold, default 10; 0 disables), their schemas are withheld and advertised compactly in a per-turn system-reminder; the model pulls a tool's full schema on demand via a new tool_search tool. Strictly additive: below the threshold behaviour is byte-identical to today. No new module dependencies.

- Eligibility: MCP tools opt in via an optional Deferred() bool interface (tools.IsDeferred); built-ins stay eager. No change to the core Tool interface.
- Formatting (internal/tools/deferred.go): compact one-liners wrapped in a <system-reminder> by BuildDeferredReminder; server label via MCPServerName().
- tool_search (internal/tools/tool_search.go): SideEffectNone/PermissionAllow/AdvertiseInAuto; select:Name1,Name2 (exact) or keyword (ranked); returns full schemas and signals the loop via Meta["load_tools"]. Operator-filter-aware (never lists tools hidden by --enabled-tools/--disabled-tools).
- Agent loop: partitionTools is the single source of truth; eligible counts only ToolVisible deferred tools; Meta["load_tools"] lifts to ToolResult.LoadedTools and unions into a per-run loaded-set advertised next turn. Reminder appended only to the per-turn request copy (never persisted), including both reactive-compaction retry paths. tool_search stays reachable when deferral is active (exempt from the EnabledTools allowlist).
- Config + wiring: tools.deferThreshold (default 10; 0 disables; negative rejected); tool_search registered and DeferThreshold threaded in exec and TUI, gated on the same visible-deferred count the loop uses.

Recreated on top of current main; resolves the SideEffectNone clash with #146 (escalate_model). Supersedes #147.

* deferred-tools: activate deferral only when tool_search is runnable; drop dead deferredTools()

Addresses #148 review:
- partitionTools gates 'active' on a registered, non-disabled, mode-advertised
  tool_search (mirrors the executeToolCall dispatch gate). When the loader is
  unavailable (explicitly disabled, missing, or not advertised e.g. spec-draft),
  fall back to the eager/inactive path instead of hiding deferred tools behind a
  loader the model can never call (the dead-end).
- Flip TestDisabledToolSearch... to assert the eager fallback; register a real
  tool_search in the active-path tests that previously relied on activation
  without a usable loader.
- Remove unused toolSearchTool.deferredTools().

* deferred-tools: disable deferral in exec when tool_search disabled; drop unused toolDefinitions shim

Further #148 review:
- exec computes one effective DeferThreshold, forced to 0 when tool_search is in
  --disabled-tools, and feeds it to both registerToolSearchIfEligible and
  agent.Options.DeferThreshold — so the registration gate and the loop partition
  gate agree, and a disabled loader never enables deferral.
- Remove the unused toolDefinitions shim (golangci-lint unused; no callers).
2026-06-09 12:20:22 +05:30
Anandan b83367e9d3 Add spec mode review flow (#145) 2026-06-08 21:07:57 +05:30
KRATOSandGnanam 69810d5ab0 Mid-run model escalation: escalate_model tool + --allow-escalation (#146)
* modelregistry: add UpgradeTargetID and Registry.UpgradeTarget escalation lookup

* modelregistry: seed escalation chains in catalog decoration

* modelregistry: verification gate for escalation upgrade map

* agent: add ModelSwitcher option and ToolResult.RequestedModel field

* agent: lift escalate_to_model meta into ToolResult.RequestedModel

* agent: perform at-most-one mid-run model switch in the turn loop

* tools: add SideEffectNone for control-only tools

* tools: add escalate_model control tool (metadata + args)

* cli: parse --allow-escalation exec flag

* cli: document --allow-escalation in exec help

* cli: register escalate_model tool under --allow-escalation

* cli: wire model switcher for exec escalation

* cli: guard byte-identical exec run without escalation flag

* cli: attribute post-switch usage to the escalated model

* tools: accept an explicit empty escalate_model reason

reason is an optional, informational-only arg, but the previous arg
parsing rejected an explicit reason:"" (non-empty-string check), failing
the whole escalation. Switch to stringArgWithEmpty so an absent OR empty
reason is accepted while a non-string value is still rejected. Add a test
covering both the empty and absent forms.

* agent: test (nil,nil) escalation switcher keeps original provider

When a ModelSwitcher returns (nil, nil) — no error, no replacement — the
loop's `else if newProvider != nil` guard already keeps the active
provider, so the run continues on the original model. Add an agent-layer
test that proves this (it panics if the nil guard is dropped), closing the
gap the adversarial review flagged.

Also note the deferred compaction limitation near the switch: the
compactor's context-window budget is fixed at run start and not updated on
a mid-run switch (needs a ModelSwitcher contract change to fix).

* cli: gate usage model attribution on the escalation flag and harden tests

FIX A (back-compat): the OnUsage closure wrote a "model" key into every
persisted EventUsage payload unconditionally, diverging a non-escalation
run from the byte-identical origin/main baseline. The model can only
change mid-run under --allow-escalation, so include the "model" key only
then. A new flag-OFF test asserts the persisted usage payload carries no
"model" key (mutation-proven).

Test hardening for the CLI escalation boundary:
- Wire test: each newProvider build returns a DISTINCT provider with its
  own turn counter; assert the source provider served exactly the
  escalation turn and the escalated provider served exactly the
  post-switch answer. Fails if `provider = newProvider` is dropped.
- Mixed usage attribution: emit usage pre- and post-switch and assert the
  first attributes to the original model, the second to the escalated
  target (the loop's ordering guarantee).
- Switch-error path: a failed rebuild is non-fatal and post-error usage
  stays attributed to the original model (currentModel unchanged).
- Top-tier decline end-to-end: a flag-on run that escalates while already
  top-tier performs no switch (newProvider built exactly once) and
  succeeds.

* modelregistry: validate UpgradeTargetID resolves at registry construction

* cli: update currentModel only when the switched provider is non-nil

* sandbox: make 'none' a first-class side effect (control-only tools)

---------

Co-authored-by: Gnanam <[email protected]>
2026-06-08 20:50:32 +05:30
68facda077 Image (multimodal) input: exec --image, stream-json images, TUI /image across all 3 providers (#143)
* zeroruntime: add ImageBlock type and NormalizeImageMediaType

* zeroruntime: add optional Images field to Message

* zeroruntime: add SeedMessagesWithImages, delegate SeedMessages with nil

* Emit Anthropic image source blocks for user image turns

* Add inlineData part type to Gemini request schema

* Serialize user image attachments as Gemini inlineData parts

* test(openai): pin text-only chat request serialization before content refactor

* feat(openai): allow structured multimodal content parts in chat request

* feat(openai): map image attachments to image_url content parts

* test(openai): assert image content parts serialize over the request body

* modelregistry: add SupportsVision capability gate

* Add shared image-file loader package

* cli: parse repeatable exec --image flag into imagePaths

* cli: resolve exec image attachments via shared loader with sniff and size cap

* agent: thread image attachments through Options into the seeded user turn

* cli: resolve and vision-gate exec image attachments into the agent run

* cli: document exec --image flag in help output

* streamjson: add InputImage type and InputEvent.Images field

* streamjson: whitelist images field on message events only

* streamjson: allow empty content on message events that carry images

* streamjson: add ResolveImages to decode, normalize and cap input images

* streamjson: cover image parse-and-resolve round-trip and text-only invariance

* Add /image command definition to TUI

* Add synchronous vision check for TUI model

* Implement /image attach, clear, and non-vision refusal in TUI

* Render pending image chips above the input in both skins

* Thread pending images into the agent turn and clear on submit

* Wire stream-json images through to the agent run

Stream-json message images were parsed and validated but never reached
agent.Options.Images: streamjson.ResolveImages had no production caller,
so images sent over stream-json input were silently dropped.

resolveExecPrompt now also returns the resolved stream-json images
(nil for text input and for image-free stream-json input). The exec
runner merges them with --image attachments before the vision gate, so
both input sources flow through the same drop+warn and the same
agent.Options.Images wiring.

Adds an exec-level end-to-end test that feeds a base64 image on a
stream-json message event via stdin on a vision model and asserts the
image reaches the agent run (the provider request's user turn).

* Re-check TUI vision support at submit, not only at /image attach

A user could attach an image on a vision model and then switch to a
non-vision model via /model before submitting; the images were threaded
unconditionally into the agent turn, reaching a model that rejects them.

The commandPrompt submit path now re-runs the vision check against the
current effective model. When the active model can't accept images, the
pending images are dropped (not threaded) and an inline notice mirroring
the headless drop+warn wording is appended. Pending image state is still
cleared after submit either way.

Adds a test that attaches on a vision model, switches the model id to a
non-vision one, submits, and asserts the run received no images, an
inline notice was appended, and pending state was cleared.

* Bound memory with an os.Stat size pre-check in imageinput.LoadFile

LoadFile read the whole file into memory before checking the size cap,
so a multi-gigabyte file allocated a huge buffer only to be discarded.

Add an os.Stat size pre-check before os.ReadFile: a file whose size
exceeds MaxImageBytes is rejected with the same 10 MiB-limit error
before any read; a missing file surfaces the same not-found error. The
existing post-read len(data) check is kept as a TOCTOU backstop in case
the file grows between Stat and ReadFile.

* OpenAI: only the user role emits image content-parts

The Anthropic and Gemini providers only attach images in their user
branches, but OpenAI funnels every role through a single mapMessage,
which would emit image content-parts for any role carrying Images.

Guard the content-parts path to the user role: a non-user message that
happens to carry Images keeps the plain string/nil content path. Pins
the invariant with TestMapMessageNonUserRolesNeverCarryImages, asserting
assistant/tool/system messages with Images set still serialize plain
content (no content-parts array).

* Deep-copy image buffers so attachments are never aliased

Add a shared CloneImageBlocks helper in zeroruntime that deep-copies a
slice of ImageBlock including each Data byte slice (returning nil for
nil/empty input). Use it in SeedMessagesWithImages so a seeded user turn
owns an independent copy of the caller's image bytes, and in the agent
loop's copyMessages so image bytes are deep-copied alongside ToolCalls
instead of staying aliased across history/request/result copies.

Tests assert that mutating the caller's original Data bytes after seeding
leaves the seeded message unchanged, and that copyMessages produces
independent image bytes.

* Accept image-only stream-json turns and bound base64 image size

Resolve stream-json images before the prompt in the exec input path and
tolerate an empty prompt when images are present, so an image-only
message event (empty content + one image) proceeds with prompt="" and the
image reaches the agent run instead of being rejected. A turn carrying
neither text nor images still errors.

In ResolveImages, reject an oversized payload from its ENCODED length
(DecodedLen) BEFORE the base64 decode so a huge blob never allocates a
decode buffer just to be capped afterward; the post-decode length check
stays as a backstop.

Tests cover the image-only exec turn reaching the agent and the
pre-decode oversize rejection (asserting the size gate fires before any
decode).

* Bound the image read in imageinput.LoadFile with a LimitReader

Replace os.ReadFile with os.Open + io.ReadAll over an
io.LimitReader(f, MaxImageBytes+1). The os.Stat pre-check is only a
fast-path hint (a non-regular file reports a misleading size and the file
can grow between stat and read); the LimitReader is the real bound, so at
most one byte past the cap is ever buffered and an oversized or unbounded
source can never allocate a multi-gigabyte buffer. The file is closed via
defer and the existing size message is reused.

* Fix RenderChat frame-height off-by-one with image chips

cmdRegion emits TWO rows when ImageChips is set (a chip row above the
input line), but the body/overlay height accounting assumed a one-row
command region, overflowing the fixed frame by one line. Compute the
fixed-row count from the actual command-region height (1 or 2 rows) so
the composed frame stays exactly Height rows in both cases.

A RenderChat test asserts the rendered frame line-count equals the
requested height both with and without ImageChips set.

* Reject an empty inline --image= value

Route the inline --image= form through requiredInlineFlagValue, the same
empty-rejection the other inline flags use, so --image= errors with
"--image requires a value" instead of appending an empty image path. A
parse test covers the empty inline value.

* imageinput: reject non-regular files (FIFO/dir) before opening

---------

Co-authored-by: KRATOS <[email protected]>
Co-authored-by: Gnanam <[email protected]>
2026-06-08 18:49:09 +05:30
9c2e14272b CLI: doctor config validation, autonomy ceiling, changes --base, usage report (#142)
* config: add ValidateFile for structured config diagnostics

* doctor: add offsetToLineCol helper for JSON error positions

* doctor: validate config files in config.validation check

* cli: thread config paths into doctor and degrade on bad JSON

* doctor: inline read, single-parse config validation + utf8 col test

- Remove the osReadFile wrapper; call os.ReadFile directly at its one call site
- Add config.ValidateBytes([]byte) so each config file is read and JSON-parsed
  once in configValidationCheck instead of twice (once for position, once via
  ValidateFile); ValidateFile now reads then delegates to its own unmarshal for
  the path-prefixed error message, keeping its public signature unchanged
- Fix the misleading skip comment in configValidationCheck: configFilesCheck
  only checks path-string presence; the skip is a defensive guard for the
  unreachable case where DefaultResolveOptions passed a non-empty path that
  still fails to read
- Add utf8 multibyte case to TestOffsetToLineCol documenting byte-column
  semantics (column counts bytes, not runes)

* sandbox: add Policy.MaxAutonomy ceiling defaulting to high

* sandbox: normalize both operands in autonomyAllowed to fail closed

* sandbox: clamp grant-allow and unsafe escalation to policy autonomy ceiling

* sandbox: add RequiredAutonomyForBatch helper with fail-closed mapping

* config: add sandbox.maxAutonomy field to file and resolved config

* config: merge sandbox.maxAutonomy across layers, env, and overrides

* cli: thread configured autonomy ceiling into sandbox engine construction

* cli: surface max_autonomy in sandbox policy formatters

* zerocommands: carry max autonomy in sandbox policy snapshot

* sandbox: default empty policy ceiling to high + reason const

Evaluate now treats an empty Policy.MaxAutonomy (from a directly-constructed
Policy that bypasses DefaultPolicy) as High instead of letting it normalize to
Low, which would silently clamp every Medium/High decision to Prompt. The
ceiling is a no-op unless explicitly configured.

Also extracts the "above policy ceiling" clamp reason into a package const
referenced at both engine clamp sites and the related tests, and adds a
non-vacuous engine test proving the empty-ceiling trap is closed.

* zerogit: add base-ref three-dot diff to changes inspect

* zerogit: cover base-ref backward-compat and real-git diff

* zerogit: propagate base ref through change snapshot

* cli: parse changes --base ref and reject on commit

* cli: thread base ref into changes inspect output and help

* changes: redact base in cli summary + guard --base= + rename test

- redactChangeSummary now redacts the Base field alongside Root/Branch/Commit/etc.
- --base= equals-form rejects leading-dash values via flagValueLooksLikeOption, closing the option-smuggling gap
- TestParseNameStatusRenameAndCopy asserts rename/copy three-field lines use the new/destination path with correct status, and confirms two-field modify is unaffected

* Add ParseDiffStat helper for git diff --stat summaries

* Add usage report aggregation over persisted session usage events

* Add zero usage report command over persisted session usage

* Wire usage command into CLI dispatch and help

* usage: validate --since format, fix redaction comments, add tests

Fix 1 (BLOCKING): parseUsageArgs now validates --since with time.Parse
against YYYY-MM-DD; returns exitUsage + "invalid --since %q: expected
YYYY-MM-DD" for bare strings like "foo", unpadded "2026-6-1", or
wrong-separator "06/01/2026". Valid dates are stored unchanged for the
existing lexical comparison.

Fix 2: Replace false "pre-redaction stat" claims in diffstat.go and
usage.go with accurate comments: the --stat summary line carries no
secret-bearing tokens so parsing the already-redacted DiffStat is safe.

Fix 3: Add three tests to usage_test.go —
  - TestRunUsageDaysFilter: stubs deps.now, seeds events on two dates,
    asserts --days 3 includes the recent date and excludes the old one.
  - TestRunUsageInvalidSince: table-driven; verifies "foo", "2026-6-1",
    "06/01/2026" each return exitUsage + validation message, while
    "2026-06-01" returns exitSuccess.
  - TestRunUsageEmptyStore: zero usage events → exitSuccess, output
    contains header and "total" row, no panic.

* doctor: surface type-mismatch offsets and drop colliding flat line/col keys

Probe config validation against config.FileConfig instead of a bare any so
a structurally-valid document with a wrong field type (e.g. maxTurns as a
string) surfaces a *json.UnmarshalTypeError carrying the offset, instead of
losing line/col. Remove the flat top-level details["line"]/["col"] keys
that were overwritten by the last malformed file when several were present;
keep only the unambiguous per-path map entry. Tighten the existing malformed
test to assert concrete per-path line/col and add a type-mismatch test that
exercises the previously-dead UnmarshalTypeError branch.

* sandbox: fail closed on invalid configured autonomy ceiling

An invalid non-empty sandbox.maxAutonomy (e.g. "moderate") previously
survived Resolve (only trimmed, never validated), failed to normalize at the
sandbox bridge, and left the policy unchanged at the default High ceiling.
A typo therefore silently disabled the admin's intended ceiling: fail-open on
a security boundary.

Validate at resolve time (fail loud): Resolve now rejects a non-empty
maxAutonomy that does not normalize, returning a clear error. As defense in
depth, applyConfiguredAutonomyCeiling now clamps an unrecognised non-empty
value to the most restrictive ceiling (low) instead of returning the policy
unchanged, so a bad value can never widen the ceiling even via direct use.
Empty/unset stays valid and keeps the default High ceiling.

Add resolver tests for invalid-fails / valid-resolves and bridge tests for
the fail-closed clamp and valid mappings.

* sandbox: fail closed on malformed autonomy and surface policy resolve errors

Treat a genuinely-invalid request autonomy as the highest tier in the engine
so it exceeds a Medium/Low ceiling and clamps to Prompt instead of being
sanitized to Low and auto-allowing on the grant/unsafe path. Surface
resolveConfig failures in the policy command instead of silently falling back
to the permissive DefaultPolicy, which would misreport trust posture.

* usage: bucket and filter by UTC calendar day, reject empty --session

Normalize RFC3339 timestamps to their UTC calendar date before day bucketing
(report) and before the --since/--days cutoff comparison (CLI) so offset
timestamps land on the correct UTC day and the bucket and cutoff agree;
malformed timestamps fall back to the leading-10 slice. Reject empty
--session/--session-id values with a value-required error matching the --since
validation style.

* doctor: fail on unreadable config files and stop fabricating positions

Surface a present-but-unreadable config path (permissions, is-a-directory) as
a failing per-path validation detail instead of silently skipping it; a
genuinely-missing path stays a skip. Return ok=false for a JSON parse error
that carries no offset so it routes through the no-position ValidateBytes path
rather than fabricating a (1,1) line/col. Assert empty stderr on the JSON
doctor failure path.

* config: make sandbox maxAutonomy resolver tests hermetic

Pass an explicit empty Env to the maxAutonomy Resolve tests so host
environment variables cannot leak in through the nil-Env os.Getenv fallback.

* sandbox: preserve raw autonomy for ceiling check so invalid clamps under default ceiling

Defaulting invalid request.Autonomy to High only clamped under a Medium/Low
ceiling; under the default High ceiling autonomyAllowed(High, High) returned true
and still auto-allowed (fail-open). Keep the raw requested value for the ceiling
check so autonomyAllowed's unknown-tier guard fails it closed under ANY ceiling.
Adds default-High-ceiling regression tests for the unsafe and grant-allow paths.

---------

Co-authored-by: KRATOS <[email protected]>
Co-authored-by: Vasanthdev2004 <[email protected]>
2026-06-08 17:15:57 +05:30
Vasanth T 2a8dc75628 Add provider catalog foundation (#141)
* feat: add provider catalog foundation

* feat: add provider onboarding checks

* fix: address provider readiness review

* fix: clear provider catalog review threads

* fix: accept direct auth values in provider checks

* fix: harden provider catalog config

* fix: guard project provider credential binding
2026-06-08 16:37:29 +05:30
KRATOSandKRATOS 99a0bea90b Real-world hardening: graceful shutdown + crash capture (#136)
* [LOCAL PR-Y] CLI: graceful shutdown of headless exec runs (Ctrl+C)

zero exec wrapped agent.Run in context.Background(), so Ctrl+C/SIGTERM killed the process abruptly — leaking the in-flight provider request and any spawned children. runExec now wraps the run in a signal-aware context (signalContext): a signal cancels the run, the loop returns cleanly, and exec reports 'Interrupted.' with exit 130 (stream-json: an 'interrupted' run-end). Scoped to runExec — no dispatch-wide signature churn. NOTE: SIGINT runtime delivery needs a manual Ctrl+C check before PR; unit tests cover the helper + the no-regression of the exec suite.

* [LOCAL PR-Y] Observability: crash capture (panic -> saved report)

New internal/observability package: a recovered top-level panic is written to a local crash report (timestamp, label, full stack) under ~/.zero/crashes and surfaced as a brief notice instead of a raw stack trace. Wired into runWithDeps via a named-return defer. Fail-open and dependency-free; this is the hook a Sentry/OTEL adapter attaches to for remote reporting (deferred, needs a real endpoint to verify). Tested: report formatting/writing, panic capture sets the crash exit code + writes one report, and the no-panic path is a no-op.

---------

Co-authored-by: KRATOS <[email protected]>
2026-06-08 08:55:36 +05:30
KRATOSandKRATOS 07d8ffc524 CLI wiring: skills/zeroline commands, --mode presets, exec checkpoints, sandbox --effective, sessions rewind (#128)
* CLI wiring: skills/zeroline commands, --mode presets, exec checkpoints, sandbox --effective, sessions rewind

Final module of the runtime-core split. Wires the new runtime features into the cli, PRESERVING main's merged specialist cli (the source branch predates specialist and drops it; this keeps it via a 3-way merge from the common base, adding the reskin features as ours/theirs-only changes).

- skills command (internal/cli/skills.go) listing internal/skills; zeroline-skin TUI launcher (internal/cli/zeroline.go) via runInteractiveTUIWithSkin.

- exec: --mode presets (smart/deep/fast/large/precise) via applyExecMode; model-registry alias resolution + deprecation/effort notices; ContextWindow sizing (modelContextWindow); before-mutation checkpoint recording in OnToolCall; enhanced tool-result output (ChangedFiles/Redacted/Display); permission-mode validation fix.

- sandbox policy --effective (resolved guards); sessions rewind (executes the rewind plan); interactive TUI defaults to Ask mode (advertises write/edit/bash + gates via prompts).

- internal/streamjson: EventCheckpoint/EventRestore + CheckpointInfo for structured checkpoint output.

Specialist cli preserved (specialist.go + flags + runtime + tests all intact). build/vet/-race/full-suite + GOOS=windows build green; no new deps.

* Address #128 review: make sessions rewind reachable + honor --exclude-target, mode/notice/redaction fixes

- sessions: add "rewind" to the parser whitelist (isSessionsCommand) — it was wired in dispatch + help but parseSessionsArgs rejected it as 'unknown sessions command', so it was unreachable. Regression test TestRunSessionsRewindIsReachableAndHonorsExcludeTarget.

- sessions rewind: honor --exclude-target — ApplyRewind keeps THROUGH a sequence, so when KeepTarget is false apply through TargetSequence-1, matching rewind-plan (previously rewind always applied TO the target, disagreeing with the plan). The test asserts --exclude-target keeps fewer events than the default.

- sessions rewind: redact the session id in the success + not-found messages (consistent with every other human-readable path).

- exec_parse: reject empty inline --mode= via requiredInlineFlagValue (was a silent no-op running defaults).

- exec: notice writes (model-deprecation + reasoning-effort advisories) now treat write failures as exitCrash, consistent with the rest of the command.

build/vet/-race/full-suite + GOOS=windows build green.

---------

Co-authored-by: KRATOS <[email protected]>
2026-06-07 23:42:10 +05:30
Anandan 27f0fe8f30 Harden specialist background lifecycle (#116)
* Harden specialist background lifecycle

* Preserve killed background task state
2026-06-07 14:18:43 +05:30
Anandan a60154b70a Add specialist Task runtime (#113)
* Add specialist Task runtime

* Harden specialist resume identity
2026-06-07 12:39:29 +05:30
Anandan 561fe0b7ad Add specialist lifecycle metadata (#106) 2026-06-07 11:06:30 +05:30
Anandan ae6c9595db feat(specialist): add exec metadata flags (#104)
* Add specialist exec metadata flags

* Address exec metadata review
2026-06-07 10:40:16 +05:30
Vasanth T 52fbb6d9e7 feat: add runtime permission decisions (#95) 2026-06-06 15:26:33 +05:30
KRATOSandKRATOS 34ad21a551 Add runtime permission and verification event contracts (#82)
* feat: add runtime permission event contract

* feat: stream permission events from exec

* feat: add runtime command snapshots

* feat: add session rewind compaction plans

* feat: add verification event contracts

* fix: harden runtime contract review gaps

* test: assert approved exec stderr contract

* fix: address CodeRabbit CHANGES_REQUESTED items for #82

- Make OnSandboxDecision callback async + panic-safe (non-blocking, recovered) in tools/registry.go per CR.
- Add explicit regression test for ConfigSnapshot when ResolveRuntimeMetadata errors (no secret leak in Message/BaseURL).
- (Many prior items like shapedPayloadPreview for permission/tool events, dual-target conflict test, stderr empty on approved path, Kind/Framework preservation, redactCommand empty slice, etc. were already landed on this branch.)

All relevant tests now pass.

* fix(agent): make sandbox decision available synchronously on Result for permission events

The recent change to invoke OnSandboxDecision asynchronously (to satisfy non-blocking requirement) broke the agent's immediate capture of the decision for building PermissionEvent after registry.RunWithOptions.

- Add SandboxDecision *sandbox.Decision to tools.Result (not serialized).
- Populate it on all sandbox-involved return paths in registry.RunWithOptions (sync, before/while firing async callback for observers).
- In agent executeToolCall, use result.SandboxDecision for buildPermissionEvent (reliable ordering) instead of side-effect capture via the callback.
- The async callback is still supported for any external OnSandboxDecision consumers.

This fixes the two failing agent tests that were causing the cross-platform 'Smoke' CI jobs to fail on this PR:
- TestRunEmitsPermissionEventForPersistentSandboxGrant
- TestRunAppliesSandboxEvenInUnsafeMode

All go tests now pass.

---------

Co-authored-by: KRATOS <[email protected]>
2026-06-06 13:13:08 +05:30
KRATOS 623c53e324 feat: harden sandbox command execution (#79)
* feat: add sandbox command runner

* feat: route shell tools through sandbox engine

* test: cover sandboxed shell execution
2026-06-06 09:20:25 +05:30
KRATOS 29aec1b190 feat: add M6 sandbox backend (#77)
* feat: add sandbox policy backend

* feat: enforce sandbox decisions in tool runtime

* feat: add sandbox policy and grants CLI

* fix: honor sandbox grants in registry
2026-06-05 19:25:49 +05:30
Vasanth T 43c256068a feat: make Go runtime the app path (#71)
* feat: make go runtime the app path

* fix: harden update semver parsing
2026-06-05 17:24:58 +05:30
KRATOS a58e6c4f57 feat: add Go worktree and verification backend (#70)
* feat: add Zero worktree isolation backend

* feat: add Zero verification runner backend

* feat: wire worktree and verification commands

* fix: address worktree verification review feedback

* test: tighten worktree base dir assertion

* fix: redact workflow CLI output paths

* fix: keep workflow diagnostics useful while redacted
2026-06-05 16:39:01 +05:30
gnanam1990 4e19bd992f fix: address MCP review feedback 2026-06-05 13:43:51 +05:30
gnanam1990 e8e77c8a8f [codex] expose MCP tools in Go CLI 2026-06-05 13:43:51 +05:30
Vasanth T a710b0e5f5 feat: add Go headless protocol sessions (#60)
* feat: add Go headless protocol sessions

* test: harden stream-json tool list filtering

* fix: resolve stream-json review followups

* chore: enable coderabbit review verdicts
2026-06-05 10:41:38 +05:30
Vasanth T 37f3cc17a5 feat: advance Go-first TUI and npm wrapper (#57)
* feat: advance Go-first TUI and npm wrapper

* fix: address wrapper and footer review feedback

* feat: deepen Go TUI model and exec controls

* fix: address coderabbit exec and model list feedback
2026-06-04 20:59:06 +05:30
Vasanth T 95700c0898 feat: wire Go CLI to real provider flow (#54)
* feat: wire go cli to real provider flow

* fix: address exec review feedback

* test: harden exec provider assertions

* chore: prevent coderabbit blocking reviews
2026-06-04 19:45:21 +05:30