Commit 3964b9f
pilotctl: render next-steps after every appstore call (#396)
* pilotctl: render next-steps after every appstore call
An agent installs an app and stops caring: nothing in a call's output says
where the flow goes next. The product demo covers install->first-call, once.
This covers every call after it.
Each `appstore call` now ends by naming the small set of recommended next
commands for where the agent stands: the next step in the flow on success,
and the specific fix on failure (402 -> top up, needs_signup -> the gateway,
missing param -> a corrected call).
The graph is authored in submission.json, rides into the catalogue's
metadata.json (pinned by metadata_sha256 under the catalogue signature), and
is cached at install to $APP/next-steps.json. The call path is therefore a
local file read: no fetch, no DNS, no added latency, works offline.
Matching is over the outcome PAYLOAD, not just the outcome: the error text on
failure, the result body on success. That is load-bearing. The scaffold's
requireKey wrapper soft-fails an unauthenticated call with exit 0 and
{"needs_signup":true,...}, so "you must sign up first" is not an error at all
-- an outcome-only matcher would stay silent for precisely the cold agent who
most needs the gateway named.
Output goes to stderr, never stdout: stdout is the JSON result agents pipe
into jq, and one line of prose there breaks the workflow this exists to
encourage. --json callers get a structured next_steps object instead.
Additive and fail-silent throughout: absent, malformed, future-schema or
unmatched means the call behaves exactly as before. A hint is a nicety; a
call is not.
- appstore_nextsteps.go: mirrored wire contract, resolver, renderer, cache
- appstore.go: hooks on both outcomes; success resolves before the review
prompt can replace the body
- main.go: fatalHint drains exitNextSteps so steps land after the error they
fix (same pattern motd's importantUpdate already uses)
- PILOT_NEXT_STEPS=off opts out
* pilotctl: a discriminated edge must beat a bare exact-from edge
Ports the precedence fix from app-template's nextsteps.Resolve, which validates
the graphs this resolves. The two had drifted: this side still ranked from-exact
above a match, so for any method with both a flow edge and a soft-failing
gateway, the flow edge won — a cold agent calling primitive.send_email was told
to read its inbox instead of to sign up.
The parity test passed through the drift because it never covered exact-bare vs
wildcard-match, which is precisely the shadowing case. Added it; verified by
mutation that re-introducing the old scoring now fails the suite.
Proven end-to-end against the real app with the real authored graph: a cold
send_email now renders the signup gateway, marked (required first).
* pilotctl: confine the next-steps cache to the install root
gosec flagged both ends as G703 path traversal, correctly. appID arrives
straight from argv, so filepath.Join alone is not containment:
`pilotctl appstore call ../../etc/x ...` resolves outside the install root and
we would read a file there and print pieces of it back as "next steps".
Use resolveUnder on both the read and the write — the same guard install and the
supervisor already rely on, whose own comment says filepath.Join does NOT block
traversal.
The regression test needed a second pass. Its first draft passed even with the
naive join restored: the graph's app-id-mismatch check rejected the planted file
first, so the test was green for the wrong reason and proved nothing. The
planted graph's `app` now equals the traversing id, leaving containment as the
only thing that can stop the load — verified by mutation that removing the guard
fails the suite.
---------
Co-authored-by: Alexgodoroja <alex@vulturelabs.io>1 parent 926b4be commit 3964b9f
5 files changed
Lines changed: 832 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1274 | 1274 | | |
1275 | 1275 | | |
1276 | 1276 | | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
1277 | 1284 | | |
1278 | 1285 | | |
1279 | 1286 | | |
| |||
2198 | 2205 | | |
2199 | 2206 | | |
2200 | 2207 | | |
| 2208 | + | |
| 2209 | + | |
| 2210 | + | |
| 2211 | + | |
| 2212 | + | |
| 2213 | + | |
| 2214 | + | |
2201 | 2215 | | |
2202 | 2216 | | |
2203 | 2217 | | |
| |||
2212 | 2226 | | |
2213 | 2227 | | |
2214 | 2228 | | |
| 2229 | + | |
| 2230 | + | |
| 2231 | + | |
| 2232 | + | |
| 2233 | + | |
| 2234 | + | |
| 2235 | + | |
| 2236 | + | |
| 2237 | + | |
| 2238 | + | |
| 2239 | + | |
| 2240 | + | |
| 2241 | + | |
| 2242 | + | |
| 2243 | + | |
| 2244 | + | |
2215 | 2245 | | |
2216 | 2246 | | |
2217 | 2247 | | |
2218 | 2248 | | |
2219 | 2249 | | |
2220 | 2250 | | |
2221 | 2251 | | |
| 2252 | + | |
| 2253 | + | |
| 2254 | + | |
| 2255 | + | |
| 2256 | + | |
| 2257 | + | |
| 2258 | + | |
| 2259 | + | |
| 2260 | + | |
| 2261 | + | |
| 2262 | + | |
| 2263 | + | |
| 2264 | + | |
| 2265 | + | |
| 2266 | + | |
| 2267 | + | |
2222 | 2268 | | |
2223 | 2269 | | |
2224 | 2270 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
65 | 72 | | |
66 | 73 | | |
67 | 74 | | |
| |||
0 commit comments