Open Harness Server subcommands - #1853
Open
SSharma-10 wants to merge 175 commits into
Open
Conversation
anup-deka
reviewed
May 29, 2026
logwolvy
approved these changes
Jun 1, 2026
logwolvy
left a comment
There was a problem hiding this comment.
Assuming, relevant godo changes for sending agent spec yaml are done.
Please address the comments and we should be good to merge.
doctl agent attach connected but printed nothing: the harness-api SSE
stream emits the SPI canonical event envelope (type/data/timestamp/
tenant_id, dot-separated event names) while vendored godo decoded
kind/payload/at/team_id, so every event fell through renderEvent.
- godo HostedAgentEvent: decode the SPI wire via UnmarshalJSON and switch
the HostedAgentEventKind constants to the dot-separated SPI names.
- godo ErrorResponse: also read the nested {"error":{code,message}}
envelope so failed calls show the server's reason, not a bare status.
- agents: align event payload structs/rendering and HITL tracking to the
SPI data shapes, and acknowledge each submit with its run id so the
agent's startup latency isn't mistaken for a hang (which caused
duplicate submits).
- tests for the wire decode, rendering, nested-error surfacing, and ack.
The vendored godo edits mirror the fix being upstreamed to
digitalocean/godo and are in-tree until godo is re-vendored.
Co-authored-by: Cursor <cursoragent@cursor.com>
logwolvy
previously approved these changes
Jun 8, 2026
logwolvy
left a comment
There was a problem hiding this comment.
I will be raising a PR for stream related fixes on doctl and godo.
…endlier attach/start errors
…1866) * displayers: drop SandboxID column from hosted-agent session output * add vendor files --------- Co-authored-by: SSharma-10 <shivanisharma@digitalocean.com>
Mahki0412
reviewed
Jul 1, 2026
| @@ -0,0 +1,1215 @@ | |||
| /* | |||
| Copyright 2026 The Doctl Authors All rights reserved. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
Vendors glamour and its transitive deps (chroma, goldmark, bluemonday, etc.) and bumps lipgloss/termenv so `doctl agents` can render the agent's markdown replies with syntax-highlighted code blocks. Also re-syncs the pinned godo vendor tree (adds Session.SandboxID). Co-authored-by: Cursor <cursoragent@cursor.com>
agents: accept hermes as a known adapter
…1931) Starting an agent and then having to run a second command to talk to it inverted the common case: the reason to start a session is to use it. `start` now opens the chat once the session is ready, and `--detach` (-d) keeps the old behaviour of stopping at the ready summary. Two conditions imply detach so this stays safe for non-humans: -o json, and a stdin/stdout that is not a terminal. Without the second, every script and CI job already calling `agents start` would have gone from printing a summary and exiting to blocking forever in a TUI that nothing can drive. `--no-attach` keeps working as a hidden alias of --detach, so `run` callers are unaffected. `run` is now identical to `start` in behaviour; it shares the same decision helper and Codex-proxy tip rather than a second copy.
Attaching to a session that is still booting mishandled input in three ways. Follow-up to MARSOHS-972. The caret never moved while typing. The warm-up repaint computed the right caret column and then discarded it by ending with DECRC, which restored the cursor saved at the top of the same paint. Warm-up suppresses echo and repaints instead, so that paint was the only thing that could advance the caret. The banner now renders top-down from a known anchor and stops where the caret belongs. Submitted messages vanished. On Enter the input buffer is cleared first, then the commit to scrollback was skipped whenever the banner was visible, leaving no record of what had been queued. Submitted lines now land above the pinned banner. Only the first message was ever sent. The second was dropped outright: not sent, not shown, not returned to the input line. The guard was unnecessary, since a send only succeeds once the guest run accepts input, and from there OHR queues turns in order. Every message is now sent and counted, the banner reports the running total, and a failed send restores the text so nothing typed is lost. Also fixes stale spinner rows: plain writes ignored the banner's row accounting, landing on the prompt row and pushing the prompt down, so each later frame repainted the spinner one row lower. Scrollback writes now go above the banner, and clearing it deletes its rows rather than blanking them in place.
* agents: add --allow-grace to triggers rotate-secret rotate-secret now revokes the old webhook secret immediately by default (server-side MARSOHS-1078). Add --allow-grace to opt into the bounded grace window for routine key-hygiene rotations, where in-flight deliveries signed with the old secret need to keep verifying briefly. Never use it after a secret is compromised. When --allow-grace is set the response carries previous_secret_expires_at, which is surfaced in both text and json output so operators know when the old secret dies. Co-authored-by: Cursor <cursoragent@cursor.com> * agents: replace --allow-grace with --revoke-previous on rotate-secret Reverses the default this branch previously shipped. rotate-secret keeps its grace window: the provider side holds only one secret -- a GitHub webhook has a single Secret field -- so it keeps signing with the old value until a human pastes the new one in, and revoking on the call would turn every routine rotation into a delivery outage lasting as long as that handoff takes. --revoke-previous is the breach path instead. It retires the old secret at once and accepts that gap. Either way the command now states the outcome -- the expiry instant, or that the old secret is already dead -- because "rotated" alone leaves an operator unable to tell whether a leaked secret still works. Tracks the server-side contract in cthulhu (MARSOHS-1078), which is additive: ?revoke_previous=true, and previous_secret_revoked in the response. Co-authored-by: Cursor <cursoragent@cursor.com> * agents: match the upstream godo rotate-secret options struct Mirrors digitalocean/godo#1092, which takes an options pointer rather than a bare bool (consistent with the other List* calls in that file) and returns the expiry as *Timestamp. Vendor stays a patch only until that lands and we can bump the dependency. Co-authored-by: Cursor <cursoragent@cursor.com> * agents: read the rotate-secret outcome instead of inferring it The CLI treated a missing previous_secret_expires_at as proof the old secret was revoked. The API sets exactly one of the two fields, so the revoked flag can just be read; inferring it meant any other cause of an absent expiry would tell an operator a live secret was dead. When neither field arrives the CLI now says so rather than guessing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: SSharma-10 <shivanisharma@digitalocean.com>
…1932) Omit for the 5m default, --grace-period 0 to revoke immediately, or a positive seconds value for a custom handoff window (server max 1h). Co-authored-by: Cursor <cursoragent@cursor.com>
….yaml (#1930) Naming a manifest required a flag for the most common invocation, so the thing everyone types first -- `doctl agents start agents.yaml` -- was silently ignored and fell through to "one of --harness, --spec, or --config-id is required". start, run and validate now resolve the manifest most-explicit-first: --spec wins over a positional path, which wins over an agents.yaml discovered in the working directory. --spec stays canonical because it is doctl's word for a declarative file (apps, dedicated-inference); -f and --file are added as aliases for the kubectl/compose/planoai muscle memory. Implicit sources are never silent: a discovered file is announced, and suppressed under -o json so machine output stays parseable. Discovery only runs when no other source was selected, so --harness in a directory holding an agents.yaml stays unambiguous, and pairing a positional path with --harness or --config-id is a clear error rather than a silent winner. Co-authored-by: SSharma-10 <shivanisharma@digitalocean.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Surface harness-api create-time policy advisories after session create for manifest, config, and config-start paths; vendor godo Warnings field. Co-authored-by: Cursor <cursoragent@cursor.com>
…HS-1012) (#1938) translateEvent's TokenChunk case decoded only `text`, so a chunk marked is_reasoning was forwarded as item/agentMessage/delta exactly like the answer. The client concatenates that channel into the visible reply, so the model's thinking trace was displayed as the response. Reasoning cannot be re-routed here: item/agentMessage/delta is the only text channel the facade speaks, and the protocol has no reasoning item. Codex sessions never hit this path for reasoning -- raw.go forwards their native frames, which carry it as its own item -- so the fallback is what non-codex runtimes take, and dropping is the only faithful option until the protocol gains somewhere to put it. The skip sits ahead of the item/started block deliberately: a turn that leads with reasoning would otherwise announce an agentMessage item whose only content we then refuse to send. commands/agents.go already handles this properly via reasoningStreamer, which renders the trace in a separate muted channel. That is the better treatment and is left alone; only the facade, which has no such channel, is changed. Confirmed against production: a cursor session's live SSE carries is_reasoning on every token delta -- 4 true (source_event_type session/update:agent_thought_chunk) and 21 false (session/update:agent_message_chunk) on a single run. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve conflicts by keeping hosted-agents godo (v1.205.0-beta.3) and absorbing main's Gradient AI simulations surface (args + vendored APIs). Co-authored-by: Cursor <cursoragent@cursor.com>
Three verbs had converged into near-synonyms: `start` and `run` both created
a session and attached by default, leaving `--no-attach`/`--detach` as the only
thing distinguishing them, and `attach` was a third spelling of what `run`
already ended in. Two verbs now split on whether you end up looking at the
session:
create makes a session and leaves it alone. Never attaches.
launch puts you in one -- creating it first when given creation flags or a
manifest, attaching to an existing session when given its name or ID,
resuming it first if it is paused.
`attach`, `up` and `chat` are aliases of `launch`, so existing invocations keep
working. `--no-attach` and `--detach` are gone, since `create` is now what "do
not attach" means, and `-o json` moves to `create` alone because `launch` is
inherently interactive. A positional argument on `launch` is read as a manifest
when it names a readable file and as a session reference otherwise; creation
flags settle it before the filesystem is consulted, and an argument that is
neither reports both readings instead of guessing.
Also adds four flags the split made room for:
--from-config replaces --config-id and accepts a config name as well as an ID,
mirroring the courtesy resolveSessionRef already extends to sessions. Wired
into create, launch, triggers, and the positional argument on config
get/delete/list-sessions/start-session.
--secret NAME=VALUE (also NAME=@path, NAME=-) fills a secret slot the manifest
declares, so a checked-in agents.yaml can name the credentials it needs without
carrying them. There is no secret API to call -- a tenant secret only exists as
a write-only value on a manifest slot -- so this is manifest surgery. The
key=value/@file/- grammar moves out of `doctl secrets` into a shared helper so
both commands parse it identically.
--dry-run prints the fully resolved manifest with secrets redacted and no API
calls, which is what lets a working --harness invocation be promoted into a
config or a trigger without hand-writing YAML.
--on-hitl approve|reject|defer keeps create attached headlessly past ready,
resolving every approval with a fixed policy until the run finishes. Approvals
were the last thing in an unattended run that still required a human.
Two failures found while exercising the pipe end to end: --dry-run erred on a
${VAR} that was not set locally, which demanded a credential merely to print a
manifest whose purpose is to carry it elsewhere (it now stays a placeholder and
is reported on stderr, keeping stdout clean YAML); and a redacted manifest piped
onward without --secret stored the literal string "REDACTED" as the credential,
succeeding silently and failing much later inside a sandbox. Redacted
placeholders are now rejected on every path that sends a manifest.
Triggers take --from-config as a client-side copy, since
HostedAgentTriggerCreateRequest has no config field: the manifest is fetched and
sent as SessionTemplate. It is a copy rather than a link, and a config's stored
secret values never return to a client, so --secret must be supplied again at
trigger-create time. Both caveats are documented; a backend-side config
reference would remove them.
…1942) M0: captured a real `opencode serve` + `opencode attach` pair (1.18.25) through a logging relay. Findings encoded here: the TUI subscribes to /global/event (wrapped {payload:{id,type,properties}} envelope, not the bare instance-stream shape), and the attach burst is ~30 read-only bootstrap routes including a newer /api/* surface. M1: --type opencode behind start-proxy. New ServeHTTP/ServeHTTPListener transport (REST+SSE facades have no JSON-RPC framing to pump; BaseContext roots request contexts in the server ctx so a blocked SSE handler is released on shutdown). The opencode facade answers the captured burst, streams server.connected first, enforces one event-stream consumer, refuses /share, and 404s+logs unknown routes as the drift detector. A synthetic single provider/model catalog gives the TUI a selected model; real bridging lands in M2. Verified live: opencode 1.18.25 attaches and renders an empty ready session with zero unhandled routes. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
port-forward previously opened the local tunnel without waking a paused session, unlike launch/attach. Resume first and wait until ready so the tunnel works without a manual resume step. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.