Commit cf29386
feat(control-plane): agent-mode steering verbs, events query resource, and --json lifecycle output (#751)
* feat(control-plane): authenticated approval resolution endpoint by execution ID
Approvals could previously only be resolved through the HMAC-signed
webhook (POST /api/v1/webhooks/approval-response), whose secret is held
by the external approval service. Extract the resolution core
(idempotency, state transitions, approval bookkeeping, events, agent
callback) into webhookApprovalController.applyApprovalDecision and add
POST /api/v1/executions/:execution_id/approval-response under the
authenticated agentAPI group, so operators and CLIs holding an API key
can approve/reject/request_changes directly. Webhook behavior is
unchanged; 'expired' stays webhook-only since expiry is not an operator
decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(control-plane): af agent exec steering verbs in agent-mode CLI
Add 'af agent exec pause|resume|cancel|restart|approval-status|approve'
as thin wrappers over the existing /api/v1/executions/:execution_id/*
endpoints, emitting the standard AgentResponse envelope with non-zero
exit on error. approve wires --decision approved|rejected|
request_changes [--reason] to the new authenticated approval-response
endpoint.
proxyToServer now normalizes legacy string errors ({"error":"..."},
optionally with a sibling message) into the structured envelope error
object {code,message,hint}, deriving the code from the HTTP status when
the handler didn't provide one — so agents scripting against 'af agent'
always get {ok:false,error:{...}} on failures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(control-plane): events resource in agentic unified query
Expose persisted execution lifecycle events (workflow_execution_events)
through POST /api/v1/agentic/query as resource=events so agents can
poll event history from a shell loop instead of consuming SSE. Queries
require filters.execution_id (direct per-execution listing) or
filters.run_id (fan-out over the run's execution records); results are
sorted by emitted_at ascending with sequence tie-breaking, honor
since/until RFC3339 bounds, and paginate with limit/offset (total is
the pre-pagination count). No new persistence layer or storage
interface methods — this composes the existing QueryExecutionRecords
and ListWorkflowExecutionEvents queries.
CLI: 'af agent query -r events --execution-id X | --run-id Y' with a
new --execution-id filter flag; help documents that this is a pollable
snapshot of the SSE stream, not a live subscription.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(control-plane): --json envelopes for node lifecycle commands
Add a --json flag to af list, af install, af run, af stop, and af logs
that emits the agent-mode {ok,data,error:{code,message,hint}} envelope
on stdout with non-zero exit on error. Default human output is
unchanged.
- list: {nodes:[{name,version,status,port,description}], total}
- install/run: envelope on the real stdout; service-layer progress
prints are redirected to stderr for the duration
- stop: {node, status: stopped|not_running}; progress output is
suppressed via a Quiet flag on AgentNodeStopper (printf wrapper only,
no changes to the signal/liveness logic)
- logs: {node, log_path, lines:[...]} honoring --tail via a Go-native
tail (no external tail binary); --follow with --json is rejected as
invalid_flags since a stream cannot be a JSON snapshot
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(control-plane): persist recorded_at on workflow execution events
The raw INSERT in storeWorkflowExecutionEventTx omitted recorded_at
(the GORM model's autoCreateTime does not apply to raw SQL), leaving
NULL in SQLite/Postgres. ListWorkflowExecutionEvents scanned the column
into a non-nullable time.Time, so listing any stored event failed with
'unsupported Scan, storing driver.Value type <nil>'. Nothing exercised
this listing path end-to-end until the agentic events query resource;
the existing storage test masked the bug with a manual UPDATE of
recorded_at before listing.
Write recorded_at in the INSERT and scan it through sql.NullTime,
falling back to emitted_at for legacy NULL rows so old databases keep
listing cleanly. Found via runtime verification of
'af agent query -r events' against a local control plane.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(control-plane): in-process unit tests for proxy error normalization
structuredErrorFromString and defaultCodeForStatus were exercised only
through subprocess exit-path tests, which record no coverage. Add
direct table-driven tests so the patch-coverage gate sees these lines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(control-plane): list approval-response endpoint in discover catalog
Register POST /api/v1/executions/:execution_id/approval-response in the
agentic API catalog so 'af agent discover' surfaces the new resolution
endpoint alongside the other approval routes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(storage): persist recorded_at on the execution_logs raw INSERT too
storeExecutionLogEntryTx — the single writer behind both
StoreExecutionLogEntry and StoreExecutionLogEntries — omitted recorded_at
from its raw INSERT, so every execution-log row landed NULL and every read
silently took the legacy emitted_at fallback: the server-ingestion timestamp
was never actually stored. It also stamped entry.RecordedAt only AFTER the
insert, mutating the caller's struct with a value that never reached the
database. This is the same bug this branch already fixed for
workflow_execution_events (the GORM model's autoCreateTime does not apply to
raw INSERTs); the sibling path was missed.
The default is now stamped before the query is built and recorded_at is
bound explicitly, mirroring storeWorkflowExecutionEventTx. The regression
test uses distinct emitted_at/recorded_at values so the NULL-read fallback
cannot masquerade as persistence, and covers the batch path's zero-value
stamping; it fails against the previous INSERT.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(storage): align clinch execution-log test with persisted recorded_at
The recorded_at-persistence fix (raw INSERT no longer leaves the column
NULL) made the legacy assertion here fail: it expected the NULL-read
fallback (recorded_at -> emitted_at) for a freshly stored entry. Give
the entry an explicit RecordedAt and assert it round-trips; the NULL
fallback branch is still covered by the forced-NULL UPDATE above.
Latent in the branch before the main merge - conflict state had kept
full CI from running on the previous head.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent 47b8711 commit cf29386
22 files changed
Lines changed: 2198 additions & 47 deletions
File tree
- control-plane/internal
- cli
- commands
- handlers
- agentic
- server
- apicatalog
- storage
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
73 | 74 | | |
74 | 75 | | |
75 | 76 | | |
| |||
169 | 170 | | |
170 | 171 | | |
171 | 172 | | |
| 173 | + | |
172 | 174 | | |
173 | 175 | | |
174 | 176 | | |
| |||
178 | 180 | | |
179 | 181 | | |
180 | 182 | | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
181 | 192 | | |
182 | 193 | | |
183 | 194 | | |
184 | | - | |
| 195 | + | |
185 | 196 | | |
186 | 197 | | |
187 | 198 | | |
| |||
194 | 205 | | |
195 | 206 | | |
196 | 207 | | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
197 | 211 | | |
198 | 212 | | |
199 | 213 | | |
| |||
225 | 239 | | |
226 | 240 | | |
227 | 241 | | |
228 | | - | |
| 242 | + | |
229 | 243 | | |
230 | 244 | | |
231 | 245 | | |
| 246 | + | |
232 | 247 | | |
233 | 248 | | |
234 | 249 | | |
| |||
587 | 602 | | |
588 | 603 | | |
589 | 604 | | |
| 605 | + | |
| 606 | + | |
590 | 607 | | |
591 | 608 | | |
592 | 609 | | |
| |||
633 | 650 | | |
634 | 651 | | |
635 | 652 | | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
636 | 691 | | |
637 | 692 | | |
638 | 693 | | |
| |||
707 | 762 | | |
708 | 763 | | |
709 | 764 | | |
710 | | - | |
711 | | - | |
| 765 | + | |
| 766 | + | |
712 | 767 | | |
713 | 768 | | |
714 | 769 | | |
715 | 770 | | |
716 | 771 | | |
| 772 | + | |
717 | 773 | | |
718 | 774 | | |
719 | 775 | | |
720 | 776 | | |
721 | 777 | | |
722 | 778 | | |
723 | 779 | | |
| 780 | + | |
724 | 781 | | |
725 | 782 | | |
726 | 783 | | |
| |||
736 | 793 | | |
737 | 794 | | |
738 | 795 | | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
739 | 856 | | |
740 | 857 | | |
741 | 858 | | |
| |||
797 | 914 | | |
798 | 915 | | |
799 | 916 | | |
800 | | - | |
| 917 | + | |
801 | 918 | | |
802 | 919 | | |
803 | 920 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
0 commit comments