Commit Graph

10 Commits

Author SHA1 Message Date
Hubert Gancarczyk bf16459a0e docs: cut the comments this branch added that the code already says
Delete the narration, restatement and prose-rationale comments the branch
introduced, keeping only what the code cannot carry: the prototype and
never-echo-a-value guards, Zod's own reporting quirks, the `error` field's
contract with a pre-`issues` CLI, the ordering and snapshot invariants, and
the deliberate choices (root-anchored cross-field rules, the unhinted
directive list, the Messages API's refusal of a top-level combinator).

No executable code changes.
2026-08-25 10:15:11 +02:00
Hubert Gancarczyk 06e0308009 fix(tools): drop the flow_name alias, and resolve the name from name alone
This PR added `flow_name` as an alias for `name` on flow-execute and
flow-read-prerequisite, on the grounds that it is the spelling callers reach for
when the tool is called `flow-execute`. But the reason the wrong spelling cost
whole turns was never the spelling — it was that Zod's raw issue array named the
parameter the tool WANTED and never the one the caller SENT. That is fixed, in
this same PR: the rejection now reads

  Pass exactly one flow source: name or flow_path. flow-execute needs the flow's
  name in `name` — it resolves <project_root>/.argent/flows/<name>.yaml.
  You sent: `flow_name`, `project_root`.

which names the parameter, says where it resolves, and echoes the caller's own
keys. One turn, no alias. A second accepted spelling on top of that buys nothing
and costs a duplicated enumeration in both tools' schemas, their file-input
specs, flow-add-step's nested-call validation, and every error string that had to
spell out both.

So: one parameter, one spelling. `unwrapWhenSet` goes back to a single key
(registry's file-inputs.ts is now identical to main), `flow_file` back to one
spec, and `resolveFlowName` — which existed only to fold the two together — is
gone; `resolveFlowSource` reads `params.name` as it did before.

One behaviour returns to main's: an empty `name` answers 500 rather than 400,
since it now reaches `assertSafeFlowName` (FLOW_NAME_INVALID, error_kind
"validation") instead of a leaf InvalidToolInputError. Mapping every validation
FailureError to 400 is a boundary-wide decision, not an alias one.

Verified end to end against a tool-server built from this branch: a `flow_name`
call answers 400 with the message above over HTTP and through the MCP stdio
adapter; `name`, `flow_path`, a client-derived `flow_file`, and a remote upload
by name all still run; `name` + an unresolvable `flow_path` still answers the
schema's 400 rather than a 422 about the file; and the recorder still captures a
nested flow-execute as `run: <name>.yaml`.
2026-08-25 09:52:48 +02:00
Hubert Gancarczyk bd9a9b1419 fix(server): keep the raw issue list in the 400's error field
A schema rejection now sends three fields: `error` carries the raw issue JSON,
`message` the prose, `issues` the machine-readable list.

Before this, `error` held the prose. Every CLI released before `issues` existed
reads that field and `JSON.parse`s it, so the parse threw and `argent run` fell
back to printing the sentence raw - losing the --flag attribution, the tool's
help block, exit 2, and the structured object a --json caller reads. Reachable
whenever `argent link` or ARGENT_TOOLS_URL points an older CLI at a newer
server, which carries no version handshake.

The tools-client takes the prose from `message` when `issues` is present, so an
agent still reads the sentence naming the key it sent.
2026-08-24 13:41:00 +02:00
Hubert Gancarczyk 9241af8a79 docs(test): cut this branch's comments in the tests
The branch added ~640 comment lines across these files, most of them restating
the test title or the assertion below. What is left records a fixture
invariant, a timing constraint or a trap that made an earlier version of the
test lie.
2026-08-24 12:14:54 +02:00
Hubert Gancarczyk 82c34bfac8 docs: shorten the comments this branch added
This branch added 2768 comment lines against 4814 lines of code — more
than a third of the diff, and several blocks ran to essays. The worst
was flow-add-step.ts at 723, where a 70-line docblock describing
`runnerSideReadClause` sat orphaned two doc-comments above
`retargetRemedy`, documenting neither.

Rewritten to the ASD-STE100 rules the request names: short sentences,
one topic each, active voice, and nothing that restates what the code
already says. 727 comment lines removed, no code touched.

Four things went, and they are the four that were not carrying facts:

- Restatement. `platformOf`'s four lines explained why a `typeof` guard
  needs no try/catch; `indeterminateReasonCaveat`, `SPELLING_CLAUSE` and
  `UNREADABLE_WAIT_WARNING` each paraphrased the very string printed
  beneath them.
- Changelog narration in the tests. "This test USED to serve an
  `alpha: 0` view", "left the whole suite green", "Nothing pinned it".
  What each was justifying stays, as a constraint on the fixture rather
  than a history of it — whether the AX tree reports a transparent view
  is a device question the suite cannot settle, so the fixture may not
  rest on it.
- Repetition across blocks. The MCP retry-storm story appeared in full
  twice in one file, at `PROBE_MAX_TREE_READ_MS` and at `longRunning`;
  it now sits at the flag it explains.
- Enumerations that had outgrown their point — the Chromium divergence
  arm spent 25 lines on two cases the code decides in one condition
  check.

What stayed is the rationale nothing else records: the two-read probe
budget and why a one-read ceiling loses the warning, the three-tier
`timeoutCause` classification, the two anchor checks and the edit that
defeats both, and every per-platform tree story. Those are shorter, not
gone.

The tool `description` strings and the skill markdown are untouched —
they are agent-facing output, and the docs tests assert on them.

Verified: `tsc --build` clean; `typecheck:tests` clean (skills and ui
have no such script, as before); prettier and eslint clean on all 29
files; tool-server 4461 passed / 1 skipped, registry 118, argent-cli
468, argent-tools-client 194.
2026-08-21 13:29:22 +02:00
Hubert Gancarczyk d3b1fb44b6 fix(tools): keep the client's derived flow_file out of "You sent:"
The boundary rendered the message against post-resolveFileInputs args,
so every flow-execute call built through `prepareFileInputs` closed with
`flow_file` — a key derived from project_root + name, whose own
`.describe()` tells callers to leave it unset. Naming it beside the
misspelling the clause exists to expose is the defect
`describeNestedParamError` already keeps the injected `udid` out of on
the other two dispatchers.

`resolveFileInputs` knows which targets it built, because a spec that
interpolates anything other than its own target was not carrying a value
the caller wrote. Report those, and drop them from the KEY LIST only —
the values stay `bodyArgs`, so every verdict is still read off what zod
parsed, and a `flow_path` the caller authored is still named.
2026-08-20 15:11:20 +02:00
Hubert Gancarczyk 5574e11249 test: close the coverage gaps the review found in the alias and error paths
- The two tests that said they covered resolveFlowName throwing an
  InvalidToolInputError never reached it: the schema's exactly-one-source rule
  fires first and `execute` is never entered, and the rule was deliberately
  given the same wording, so both passed over whatever resolveFlowName did.
  Swapping the class for a plain Error left them green. An EMPTY name is the one
  input that reaches the throw — covered now, with a spy proving execute ran and
  the class asserted on the cause the HTTP boundary maps.
- flow-read-prerequisite's alias was only ever exercised through a direct
  execute(), which bypasses zod. Covered through the registry, and the oneOf
  both tools advertise is pinned.
- Added the uncovered cases: an UPLOADED flow named through the alias, a
  name-only call now that a non-interpolating ${flow_name} spec sits ahead of
  its spec, and activeFlowState's unreadable-file branch.
2026-08-10 10:55:08 +02:00
Hubert Gancarczyk a710ee8fe4 fix(tools): answer a nested tool's schema miss with 400, not 500
The registry validates every dispatch path, so a mistyped argument to a
sub-tool — a flow-add-step command, a run-sequence step — is caught there
rather than by the HTTP layer's own copy, where the outer call's params parsed
fine. This branch gave that failure `error_kind: "validation"` but left the
boundary mapping it to 500, so the body contradicted its own status and the
same mistake read as a client error sent directly and an internal fault sent
one level in. The classification argument made for resolveFlowName, applied at
the boundary that reads the signal.
2026-08-10 10:55:08 +02:00
Hubert Gancarczyk d45479e663 fix(cli): restore argent run's server-side validation path
The CLI reads the tool-server's rejection as a Zod issue list, maps each issue
back to the flag the user typed, prints the tool's help block and exits 2. This
branch turned that body into prose and left the consumer alone, so the parse
failed and every server-side rejection fell through to a bare error dump: no
help block, exit 1 instead of 2, `--json` printing a sentence instead of the
{error, missing, issues} object it promises, the failure telemetried as a
generic tool-call failure, and `x` named where `--x` used to be.

The 400 body now carries `issues` beside its prose, ToolInvocationError
carries it through, and the CLI reads the structured field first — falling back
to parsing the message so an older tool-server still works. Recognition stays
structural: an issues field that is not a non-empty list of issues is still
left alone as an ordinary runtime failure.

The CLI fixtures that mocked the removed body format are rebuilt from the real
one; the old shape is kept as an explicit pre-prose-server case. Those fixtures
passing over the broken integration are why CI missed this.
2026-08-10 10:55:08 +02:00
Hubert Gancarczyk 7e8ceb60be fix(tools): return 400 for a name-less flow call, and name an omitted enum as missing
Review follow-ups on this PR's own additions, found while verifying it:

- resolveFlowName threw a plain Error, so a flow-execute / flow-read-prerequisite
  call with neither `name` nor `flow_name` returned HTTP 500 and logged
  ARGENT_UNCLASSIFIED_FAILURE: a client-input error dressed as an internal fault.
  Making `name` optional (for the alias) moved this check out of zod, so it now
  throws InvalidToolInputError, which the HTTP boundary maps to 400 and telemetry
  classifies as `validation`, matching its sibling validations (assertSafeFlowName,
  resolveFlowFilePath) and the pre-alias zod rejection it replaced.

- describeParamIssues rendered an OMITTED required enum/literal as
  "Invalid option: expected one of ..." (Zod emits invalid_value, not
  invalid_type, for a missing enum), reading as if a bad value had been sent.
  It now decides "missing" from the input, the value at the issue's path being
  undefined, instead of a locale-fragile match on Zod's English message: an
  omitted enum reads "is required", while a present-but-wrong value (a bad
  option, a null, a wrong type) still reads with its per-issue wording.

- The tap/launch/run directive hints promised the recorder "rewrites it into the
  `tap:` step for you", but every rewrite also gates on `delayMs === undefined`
  (a replay delay has no directive form). The rewrite hints now state that
  opt-out, so an author who sets delayMs is not promised a step the recorder
  then keeps as a raw `tool:` step.

- `name` going optional dropped the CLI `(required)` marker for the flow name;
  its schema description now states it is required (via `name` or the `flow_name`
  alias), which `tools describe` / `run --help` print.

Tests: the HTTP 400 status and prose body for a name-less flow-execute and for a
misspelled required key; the name-less call's `validation` classification;
omitted-enum-is-missing vs present-but-wrong-enum wording, and null-is-not-missing;
the delayMs opt-out in the tap rewrite hint.
2026-08-10 10:55:08 +02:00