@hue-run/sdk 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLI.md +240 -49
- package/ENVIRONMENTS.md +48 -9
- package/EVALUATIONS.md +41 -21
- package/README.md +70 -30
- package/dist/cli/eval-direct.d.ts +12 -1
- package/dist/cli/eval-direct.js +145 -33
- package/dist/cli/eval.d.ts +28 -1
- package/dist/cli/eval.js +504 -81
- package/dist/cli/listen.d.ts +76 -0
- package/dist/cli/listen.js +816 -0
- package/dist/cli/mcp.d.ts +65 -7
- package/dist/cli/mcp.js +271 -73
- package/dist/client.d.ts +7 -4
- package/dist/client.js +36 -10
- package/dist/evals/checkpoint.d.ts +15 -1
- package/dist/evals/checkpoint.js +34 -3
- package/dist/evals/client.d.ts +24 -4
- package/dist/evals/client.js +92 -38
- package/dist/evals/environment-target.d.ts +18 -3
- package/dist/evals/environment-target.js +22 -10
- package/dist/evals/exit-cleanup.d.ts +4 -0
- package/dist/evals/exit-cleanup.js +26 -0
- package/dist/evals/files.d.ts +69 -3
- package/dist/evals/files.js +255 -42
- package/dist/evals/json.js +116 -9
- package/dist/evals/local-worker.d.ts +21 -3
- package/dist/evals/local-worker.js +14 -0
- package/dist/evals/runner.d.ts +45 -0
- package/dist/evals/runner.js +199 -21
- package/dist/evals/scenarios.d.ts +7 -5
- package/dist/evals/scenarios.js +71 -14
- package/dist/evals/scorer-publication.js +58 -3
- package/dist/evals/simulation.d.ts +17 -4
- package/dist/evals/simulation.js +8 -0
- package/dist/evals/types.d.ts +48 -0
- package/dist/evals/verdicts.d.ts +26 -2
- package/dist/evals/verdicts.js +72 -9
- package/dist/evals.d.ts +4 -3
- package/dist/evals.js +2 -2
- package/dist/inline-files.d.ts +4 -0
- package/dist/inline-files.js +48 -17
- package/dist/provider-tools.d.ts +12 -1
- package/dist/provider-tools.js +192 -106
- package/dist/safety.d.ts +7 -1
- package/dist/safety.js +9 -7
- package/dist/setup/cli.js +31 -8
- package/dist/setup/peers.d.ts +13 -0
- package/dist/setup/peers.js +44 -0
- package/dist/snapshot.d.ts +2 -2
- package/dist/snapshot.js +47 -12
- package/dist/tool-definitions.d.ts +18 -0
- package/dist/tool-definitions.js +232 -0
- package/dist/transport.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CLI.md
CHANGED
|
@@ -42,7 +42,7 @@ project manifest are refused because managers can update ancestor locks; Python
|
|
|
42
42
|
The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
|
|
43
43
|
`capture_content=False`. For a supported application, setup installs the dependency and adds the
|
|
44
44
|
managed import and middleware registration to the existing entrypoint; an unreferenced helper is
|
|
45
|
-
not a completed integration. TypeScript uses `@hue-run/sdk@0.
|
|
45
|
+
not a completed integration. TypeScript uses `@hue-run/sdk@0.11.0`, `@opentelemetry/api@1.9.1` and
|
|
46
46
|
`@opentelemetry/context-async-hooks@2.11.0`; Python setup uses its separately tested package pin.
|
|
47
47
|
Content capture requires an ordinary account-managed key and a later explicit application decision.
|
|
48
48
|
|
|
@@ -113,6 +113,7 @@ hue setup --format human # explicit append-only terminal rendering
|
|
|
113
113
|
hue setup --origin http://127.0.0.1:PORT # isolated loopback tests only
|
|
114
114
|
hue login # validate keys created in Hue and store them in ./.env.hue
|
|
115
115
|
hue mcp install --client claude-code # write the Hue MCP configuration for a coding agent
|
|
116
|
+
hue listen --subscription <id> --forward-to http://localhost:3000/slack/events # deliver events to a local bot
|
|
116
117
|
```
|
|
117
118
|
|
|
118
119
|
Hosted setup accepts HTTPS origins only. HTTP is accepted solely for `localhost`, `127.0.0.1` and
|
|
@@ -176,30 +177,130 @@ warns; `--gitignore` appends the file name to the `.gitignore` next to it. Exit
|
|
|
176
177
|
|
|
177
178
|
## Install the MCP for your coding agent
|
|
178
179
|
|
|
179
|
-
`hue mcp install --client <name>` writes, runs or prints the configuration for Hue's MCP server
|
|
180
|
-
|
|
181
|
-
`https://mcp.hue.run/mcp`; `--url https://mcp.staging.hue.run/mcp`
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
180
|
+
`hue mcp install --client <name>` writes, runs or prints the configuration for Hue's MCP server,
|
|
181
|
+
in the shapes of the [connection guide](https://docs.hue.run/agents/mcp-server): server name
|
|
182
|
+
`hue` and the endpoint (default `https://mcp.hue.run/mcp`; `--url https://mcp.staging.hue.run/mcp`
|
|
183
|
+
for staging). Hue's Settings page does not show these snippets; this command keeps its own copy.
|
|
184
|
+
|
|
185
|
+
`--auth` chooses how the client authenticates:
|
|
186
|
+
|
|
187
|
+
- `key` (the default, except for `conductor`) references the `HUE_MCP_KEY` environment variable,
|
|
188
|
+
which `hue login` stores in `.env.hue`. A key value is never written.
|
|
189
|
+
- `oauth` configures the URL only. The client opens Hue in a browser, where you sign in and
|
|
190
|
+
approve the connection; no key or header is involved. A new sign-in connection has **Read and
|
|
191
|
+
write** access to every active project in the organization you choose, within your role, so an
|
|
192
|
+
agent first calls `list_projects` and passes the chosen project's id as `project_id`. It is available
|
|
193
|
+
for `claude-code`, `codex` and `conductor`. Hue does not yet accept Cursor's sign-in callback, so
|
|
194
|
+
`cursor` and the other clients use a key.
|
|
195
|
+
|
|
196
|
+
`--read-only` adds `?read_only=true` to a key configuration's URL, so Hue hides and rejects write
|
|
197
|
+
tools whatever the key allows. A project key, with or without it, keeps reaching its one project.
|
|
198
|
+
With `--auth oauth` it is refused: a sign-in connection has **Read and write** access, so use a
|
|
199
|
+
**Read** project key for read-only access.
|
|
200
|
+
|
|
201
|
+
`--toolsets <names>` lists only the named tools, for clients with a tool-count limit or agents that
|
|
202
|
+
only read production: `observe` (the production reads: projects, traces, spans and their recorded
|
|
203
|
+
content, sessions, trace checks, intents and documentation), `all`, or the groups `project`, `traces`, `evals`, `environments`,
|
|
204
|
+
`intents` and `docs`, comma-separated. A key configuration adds `?toolsets=<names>` to the URL. A
|
|
205
|
+
sign-in configuration keeps the URL bare and sends the `X-Hue-MCP-Toolsets` header instead,
|
|
206
|
+
because toolsets are not part of the connection's identity: Claude Code stores the header, and for
|
|
207
|
+
Codex the command prints the `http_headers` line to add to `~/.codex/config.toml`, since
|
|
208
|
+
`codex mcp add` stores none. `--toolsets` replaces a selection already in `--url`, and a selection in `--url` is kept without
|
|
209
|
+
it; otherwise `cursor` selects `observe`. `--toolsets all` lists every tool. Unknown names are refused rather than passed on, because Hue
|
|
210
|
+
ignores them and would list every tool. `get_project_context` is always listed and reports the
|
|
211
|
+
selection.
|
|
212
|
+
|
|
213
|
+
| Client | Key (`--auth key`) | Sign-in (`--auth oauth`) |
|
|
214
|
+
| --- | --- | --- |
|
|
215
|
+
| `claude-code` | Merges `mcpServers.hue` into `./.mcp.json`. `--scope user` runs `claude mcp add --transport http --scope user hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'` when `claude` is on `PATH`, otherwise prints it. | Merges `mcpServers.hue` with only `type` and `url` into `./.mcp.json`; `--scope user` runs `claude mcp add --transport http --scope user hue URL`. Then run `/mcp` in Claude Code, select `hue` and choose **Authenticate**. |
|
|
216
|
+
| `codex` | Runs `codex mcp add hue --url URL --bearer-token-env-var HUE_MCP_KEY`, or prints the `[mcp_servers.hue]` TOML block for `~/.codex/config.toml`. | Runs `codex mcp add hue --url URL`, which can start sign-in right away; `codex mcp login hue` starts or repeats it. |
|
|
217
|
+
| `conductor` | Runs the Claude Code user-scope and Codex key commands above. | The default: runs `claude mcp add --transport http --scope user hue URL` and `codex mcp add hue --url URL`. Then use `hue`'s authentication action in Conductor's MCP status (the plug icon or `/mcp-status`). |
|
|
218
|
+
| `cursor` | Merges `mcpServers.hue` into `./.cursor/mcp.json` with `${env:HUE_MCP_KEY}`. | Refused. |
|
|
219
|
+
| `vscode` | Merges `servers.hue` and the `hue-mcp-key` password input into `./.vscode/mcp.json`. | Refused. |
|
|
220
|
+
| `windsurf` | Prints the `serverUrl` snippet for `~/.codeium/windsurf/mcp_config.json`; nothing is written to the home directory. | Refused. |
|
|
221
|
+
| `gemini` | Runs `gemini mcp add --scope user --transport http hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'`, or prints it. | Refused. |
|
|
222
|
+
|
|
223
|
+
Conductor has no MCP configuration of its own: its Claude Code and Codex agents read their user
|
|
224
|
+
configuration in every workspace, so `conductor` registers the server at user scope with each CLI
|
|
225
|
+
on `PATH` and prints the command for a CLI that is missing. A failing CLI does not stop the other;
|
|
226
|
+
the command then exits `1`. Hue has not yet verified the complete signed-in Conductor flow (consent
|
|
227
|
+
through a trace read) end to end; if Hue's tools do not load after signing in, run the command
|
|
228
|
+
again with `--auth key`.
|
|
192
229
|
|
|
193
230
|
JSON files are parsed and merged: other servers, inputs and top-level fields are kept, only the
|
|
194
231
|
`hue` entry is replaced, and invalid JSON (including comments) is refused together with the snippet
|
|
195
232
|
to add by hand. Files are written with mode `0644` through a temporary file and an atomic rename;
|
|
196
|
-
symlinks are refused. `--dry-run` prints the resulting file content or
|
|
233
|
+
symlinks are refused. `--dry-run` prints the resulting file content or commands without writing or
|
|
197
234
|
running; `--print` prints only the snippet. Client CLIs run without a shell, so the
|
|
198
|
-
`${HUE_MCP_KEY}`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
235
|
+
`${HUE_MCP_KEY}` reference reaches them literally; the printed commands use single quotes for the
|
|
236
|
+
same reason, and quote a URL with a query such as `?read_only=true`, whose `?` is a zsh glob.
|
|
237
|
+
|
|
238
|
+
After a key installation the command reminds you to export `HUE_MCP_KEY` in the shell that starts
|
|
239
|
+
the client (VS Code prompts for the key instead). A desktop app started from the Dock or a launcher
|
|
240
|
+
does not see that shell's variables, and Conductor's agents read the login-shell environment that
|
|
241
|
+
Conductor captures, so sign-in is the simpler choice there. After a sign-in installation it names
|
|
242
|
+
the client's authentication action. Both end with the prompt that verifies the connection:
|
|
243
|
+
`Use the Hue MCP: call list_projects and confirm which project to inspect. Then call get_project_context and show that project's traces from the last 24 hours that need attention or have errors, with links; if there are none, show its 5 most recent traces. For an organization connection, pass the project's id as project_id on each call after list_projects.` `list_projects` returns a project key's one project too. Exit codes:
|
|
244
|
+
`0` done or printed, `1` failed, `2` usage error.
|
|
245
|
+
|
|
246
|
+
## Deliver simulated events to a local bot
|
|
247
|
+
|
|
248
|
+
`hue listen` delivers a simulated world's events to a receiver on your machine, so an event-driven
|
|
249
|
+
agent such as a Slack bot can be tested without a public URL, the way `stripe listen --forward-to`
|
|
250
|
+
delivers webhooks. Hue signs each request as the provider does (Slack Events API requests today);
|
|
251
|
+
the command pulls them, forwards each one unchanged to `--forward-to` and reports the bot's answer
|
|
252
|
+
to Hue, which settles or retries the event on the provider's schedule. It needs a **listen** event
|
|
253
|
+
subscription on the world or on a connection key; the subscription's id and signing secret are shown
|
|
254
|
+
once when it is created. Configure the bot with that signing secret where `SLACK_SIGNING_SECRET`
|
|
255
|
+
went, then start the command: Hue offers the URL verification first, and events follow once the bot
|
|
256
|
+
has answered it. Event subscriptions are not yet available on `https://app.hue.run`; until the
|
|
257
|
+
origin offers them, the command stops with Hue's refusal.
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
hue listen --subscription <id> --forward-to http://localhost:3000/slack/events --env-path .env.world
|
|
261
|
+
```
|
|
262
|
+
The credential is the subscription's own, read from the environment (or `--env-path`, loaded first;
|
|
263
|
+
a variable already set keeps its value): `HUE_WORLD_TOKEN` for a world's subscription or
|
|
264
|
+
`HUE_CONNECTION_KEY` for a connection key's, chosen with `--credential world-token|connection-key`
|
|
265
|
+
when both are set. It is never taken from the command line, where other local processes and shell
|
|
266
|
+
history can read it. A project key is refused: a value equal to `HUE_API_KEY` or `HUE_MCP_KEY` exits
|
|
267
|
+
2 before any request, and Hue answers any credential that is not the subscription's with `401`.
|
|
268
|
+
`--origin` (default `HUE_BASE_URL`, then `https://app.hue.run`) must be HTTPS, or plain HTTP on a
|
|
269
|
+
loopback test server.
|
|
270
|
+
|
|
271
|
+
Each request reaches the bot with its body bytes and headers unchanged, so the provider's signature
|
|
272
|
+
verifies as it would in production; only `Host`, `Content-Length` and connection headers are the
|
|
273
|
+
local request's own, and Hue's credential is never forwarded. No redirect is followed: a `3xx` is
|
|
274
|
+
reported as the answer. An answer counts only within three seconds, Slack's acknowledgement window;
|
|
275
|
+
a later one is a timeout. A failing answer with `x-slack-no-retry: 1` is reported so Hue stops
|
|
276
|
+
retrying it. Only a successful URL verification answer's body (at most 4 KiB, for its challenge) is
|
|
277
|
+
sent to Hue; any other answer body stays on this machine. Each pull waits for at most 20 seconds and
|
|
278
|
+
leases up to `--max` deliveries (1 to 10, default 10), which are forwarded concurrently; the next
|
|
279
|
+
pull starts once they are acknowledged, and a delivery beyond `--max` is never forwarded. A provider
|
|
280
|
+
retry of an event is a new delivery and is forwarded again with its `X-Slack-Retry-Num` and
|
|
281
|
+
`X-Slack-Retry-Reason`, so the bot deduplicates by `event_id` as it does with Slack. A delivery Hue
|
|
282
|
+
hands out a second time is not sent again; its recorded answer is repeated. An acknowledgement Hue
|
|
283
|
+
did not answer is repeated until the delivery's lease ends, as Hue stated it (30 seconds from the
|
|
284
|
+
lease).
|
|
285
|
+
|
|
286
|
+
`--forward-to` must name this machine: `localhost`, a `.localhost` name or a loopback address, and a
|
|
287
|
+
name is refused unless every address it resolves to is a loopback address. `--allow-remote-forward`
|
|
288
|
+
permits another host over HTTPS only, because every request carries the subscription's verification
|
|
289
|
+
token and a valid signature; plain HTTP still reaches only loopback addresses. Credentials and
|
|
290
|
+
fragments in the URL are refused. Output is one line per delivery (time, event id, retry number, the
|
|
291
|
+
bot's status and duration, and the state Hue recorded), never a body, a signature or a credential;
|
|
292
|
+
credentials in error messages are replaced with `[redacted]`.
|
|
293
|
+
|
|
294
|
+
Ctrl+C or `SIGTERM` stops pulling (a waiting pull is cancelled), finishes the deliveries in flight
|
|
295
|
+
and acknowledges them, then exits `0`; a second Ctrl+C abandons the forwards and acknowledgements
|
|
296
|
+
still in flight and exits `130`. Nothing is acknowledged before the bot answered or its window
|
|
297
|
+
closed, and a lease left unacknowledged lapses into a timeout that Hue retries on the provider's
|
|
298
|
+
schedule, so no event is lost and none is marked delivered unanswered. Network errors, `429`, `5xx`
|
|
299
|
+
and another open pull for the same subscription (a second `hue listen`) are retried with backoff up
|
|
300
|
+
to 30 seconds; a `Retry-After` can lengthen a wait to at most 60 seconds, never shorten it. Exit
|
|
301
|
+
codes: `0` stopped, `1` Hue refused the credential, the subscription (unknown, revoked, or one that
|
|
302
|
+
delivers to a request URL) or the pull itself (a redirect, another refusal or an unreadable answer),
|
|
303
|
+
`2` usage error, `130` interrupted twice.
|
|
203
304
|
|
|
204
305
|
## Local state and conflicts
|
|
205
306
|
|
|
@@ -305,7 +406,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
|
|
|
305
406
|
# Set project to an existing supported fixture; use the same directory on resume.
|
|
306
407
|
project=/absolute/path/to/supported-fixture
|
|
307
408
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
308
|
-
--archive .artifacts/typescript/hue-run-sdk-0.
|
|
409
|
+
--archive .artifacts/typescript/hue-run-sdk-0.11.0.tgz \
|
|
309
410
|
--origin https://STAGING_ORIGIN \
|
|
310
411
|
--project "$project" --command setup \
|
|
311
412
|
--evidence .context/setup-staging-before-claim.json
|
|
@@ -316,7 +417,7 @@ the private local handoff and finish the real browser claim, then reconcile the
|
|
|
316
417
|
|
|
317
418
|
```sh
|
|
318
419
|
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
319
|
-
--archive .artifacts/typescript/hue-run-sdk-0.
|
|
420
|
+
--archive .artifacts/typescript/hue-run-sdk-0.11.0.tgz \
|
|
320
421
|
--origin https://STAGING_ORIGIN \
|
|
321
422
|
--project "$project" --command claim \
|
|
322
423
|
--evidence .context/setup-staging-after-claim.json
|
|
@@ -351,8 +452,11 @@ the CLI never prints it. The optional `zod` peer of `@hue-run/sdk/evals` must be
|
|
|
351
452
|
Write an adapter module that hands the case inputs and the world's tools or MCP connection to
|
|
352
453
|
the real agent. The module exports `default` or `runMyAgent`; `context` is the SDK's
|
|
353
454
|
`SimulationTargetContext` (`world`, `mcp`, `tools`, `config`, `item`, `executionId`,
|
|
354
|
-
`environmentRunId`, `signal`; `world` carries the provider mirror URLs,
|
|
355
|
-
`mcpConfig` where Hue's simulation gateway serves the world)
|
|
455
|
+
`environmentRunId`, `files`, `outputDirectory`, `signal`; `world` carries the provider mirror URLs,
|
|
456
|
+
the world token, `env` and `mcpConfig` where Hue's simulation gateway serves the world). When the
|
|
457
|
+
case also pins input files, `files` holds verified copies of its agent-visible ones (see
|
|
458
|
+
[Evaluate a document eval set](#evaluate-a-document-eval-set)); return
|
|
459
|
+
`withFiles(output, files)` from `@hue-run/sdk/evals` to upload documents the agent produced:
|
|
356
460
|
|
|
357
461
|
```ts
|
|
358
462
|
// hue-agent.ts: erasable TypeScript only; Node.js 24 and Bun strip the types natively.
|
|
@@ -368,66 +472,136 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
|
|
|
368
472
|
```
|
|
369
473
|
|
|
370
474
|
```sh
|
|
371
|
-
hue eval --case "Refund an eligible charge" ./hue-agent.ts --env-file .env.hue
|
|
372
|
-
hue eval --case https://app.hue.run/projects/demo/scenarios/<id> ./hue-agent.ts --baseline <run id>
|
|
373
|
-
hue eval --set "Billing regressions" --scorer-version <id> ./hue-agent.ts --save-version
|
|
475
|
+
hue eval --case "Refund an eligible charge" ./hue-agent.ts --content --env-file .env.hue
|
|
476
|
+
hue eval --case https://app.hue.run/projects/demo/scenarios/<id> ./hue-agent.ts --content --baseline <run id>
|
|
477
|
+
hue eval --set "Billing regressions" --scorer-version <id> ./hue-agent.ts --content --save-version
|
|
374
478
|
hue eval --case "Refund an eligible charge" --command "python agent.py" --timeout 120 --content
|
|
375
|
-
hue eval --worker ./hue-agent.ts --agent-key support-agent --env-file .env.hue
|
|
479
|
+
hue eval --worker ./hue-agent.ts --agent-key support-agent --content --env-file .env.hue
|
|
376
480
|
```
|
|
377
481
|
|
|
378
482
|
`--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
|
|
379
483
|
|
|
380
|
-
The one-shot mode resolves the selection (`--case` by name, ID or URL
|
|
381
|
-
|
|
484
|
+
The one-shot mode resolves the selection (`--case` by name, or by the ID or URL of the published
|
|
485
|
+
case or of the case conversion that published it, among the project's first 1,000 Scenarios;
|
|
486
|
+
`--set` by name, ID or URL with explicit `--scorer-version` pins; or `--dataset-version` with `--scorer-version`),
|
|
382
487
|
creates a fresh run from those immutable pins named `<agent key> @ <revision>` (a commit hash is
|
|
383
488
|
shortened to 7 characters)
|
|
384
489
|
(`--name` overrides), prints `Run: <url>` and `Experiment: <id>` as soon as the experiment exists,
|
|
385
490
|
one line per case event (world created, agent started, world sealed), then
|
|
386
491
|
`Waiting for Hue checks...` and a table with one row per case: boolean metrics as `PASS`/`FAIL`,
|
|
387
492
|
numbers as values, text and category metrics as-is, an overall result per case and a pass count.
|
|
388
|
-
Failing cases print the scorer explanation.
|
|
493
|
+
Failing cases print the scorer explanation. An evaluator Hue records as not applicable to a case
|
|
494
|
+
(its outcome scoring has no outcome criteria or conversion rubric to grade there) shows `n/a` in
|
|
495
|
+
that case's columns and neither passes nor fails it, so in a mixed eval set a case is decided by
|
|
496
|
+
the evaluators that apply to it; the pass line counts the not-applicable results. A case that no
|
|
497
|
+
pinned evaluator applies to is an error that names what they need. A result of a pinned Hue judge
|
|
498
|
+
(an evaluator of kind `world_judge`) that Hue marks advisory, as it does every judge's today, is
|
|
499
|
+
shown in its column, marked `(advisory)`, but never decides a case or the exit code; the pass line
|
|
500
|
+
counts advisory failures as not counted, and a case only advisory results ran for is an error. Any
|
|
501
|
+
other evaluator's result, an error, or a metric that carries `passed` is never advisory, whatever
|
|
502
|
+
its evidence says. A metric name that
|
|
503
|
+
several evaluators report, such as two judges' `verdict`, gets a column per evaluator, labelled
|
|
504
|
+
with the version's first eight characters. `--baseline <experiment id|url>` adds improvement,
|
|
389
505
|
regression and unchanged counts with per-case deltas. `--json` prints one JSON document
|
|
390
506
|
(`experimentId`, `runId`, `runUrl`, `complete`, `cases`, `totals`, optional `baseline`) on stdout
|
|
391
|
-
and sends progress to stderr
|
|
507
|
+
and sends progress to stderr; each case lists its not-applicable evaluator versions in
|
|
508
|
+
`notApplicable` and its advisory ones in `advisory`, and `totals.notApplicable` counts the
|
|
509
|
+
former; a case failed for its telemetry has `state: "error"`,
|
|
510
|
+
`passed: false` and
|
|
511
|
+
`telemetry: { code: "telemetry_not_accepted", issues: [{ signal, kind, status?, count }] }`. `--wait <seconds>` (default 300) bounds the verdict wait because
|
|
392
512
|
Hue-owned `world_outcome` checks are graded after the world seals. An experiment always covers
|
|
393
513
|
every case of the saved version; there is no case subset.
|
|
394
514
|
|
|
395
515
|
`--command "<shell command>"` spawns the command once per case with the world's environment
|
|
396
516
|
(`HUE_WORLD_ID`, `HUE_WORLD_TOKEN`, one `HUE_SIM_<SURFACE ID>_URL` per provider mirror,
|
|
397
517
|
`HUE_MCP_CONFIG` naming an owner-only `mcpServers` file that is removed after the case, and
|
|
398
|
-
`HUE_MCP_URL`, `HUE_MCP_TOKEN`, `HUE_MCP_EXPIRES_AT` for the first MCP mirror
|
|
518
|
+
`HUE_MCP_URL`, `HUE_MCP_TOKEN`, `HUE_MCP_EXPIRES_AT` for the first MCP mirror; these name the
|
|
519
|
+
world's simulated MCP server and replace a `HUE_MCP_URL` that `hue login` stored for Hue's project
|
|
520
|
+
MCP server), plus
|
|
399
521
|
`HUE_EXECUTION_ID`, `HUE_ENVIRONMENT_RUN_ID`, `HUE_CASE_ID` and `HUE_CASE_KEY`, and
|
|
400
522
|
`{"inputs": ..., "config": ...}` on stdin. The child does not receive `HUE_API_KEY`, `HUE_MCP_KEY`
|
|
401
523
|
or any other Hue control-plane credential unless `--allow-hue-credentials` is passed; the rest of
|
|
402
|
-
the parent environment (model keys, application settings) is inherited.
|
|
524
|
+
the parent environment (model keys, application settings) is inherited. `HUE_CASE_DIR`,
|
|
525
|
+
`HUE_CASE_INPUTS` and `HUE_CASE_OUTPUT_DIR` name a private case directory with the layout direct
|
|
526
|
+
cases use: the case inputs, verified copies of the case's agent-visible pinned files under
|
|
527
|
+
`files/<role>/`, and `output/`, whose files are uploaded after the world seals (the command still
|
|
528
|
+
starts in the current directory). The case directory never holds the world token and is removed
|
|
529
|
+
after the case. Its stdout is the answer
|
|
403
530
|
(JSON when it parses, otherwise trimmed text; empty means no output); a non-zero exit or the
|
|
404
|
-
per-case `--timeout` (default 600 seconds) is a target failure.
|
|
531
|
+
per-case `--timeout` (default 600 seconds) is a target failure. A timed-out or interrupted command
|
|
532
|
+
is stopped as a whole process group on macOS and Linux: SIGTERM, then SIGKILL for anything still
|
|
533
|
+
running 5 seconds later, including an agent a compound command started after its shell exited;
|
|
534
|
+
the case settles only once nothing in the group is left. A command that exits normally but leaves
|
|
535
|
+
processes in its group has them stopped the same way before its answer and files are read. A
|
|
536
|
+
second Ctrl+C during that grace kills the group at once, removes the world case's files, the MCP
|
|
537
|
+
configuration file and the checkpoint locks, and exits with 130. The world token is never logged.
|
|
405
538
|
A world created while the deployment's simulation gateway is off gets the legacy `hue_sim_`
|
|
406
539
|
capability under the same `HUE_MCP_*` names. The agent key defaults to the slug of the command's
|
|
407
540
|
script name; `--revision` is sent to Hue as the agent revision of every world.
|
|
408
541
|
|
|
409
542
|
`--worker` registers the adapter through `runLocalAgent()` with key `--agent-key` (default: the
|
|
410
543
|
adapter filename slug), name `--agent-name` (default: the key), revision `--revision` (default:
|
|
411
|
-
`AGENT_REVISION`, then the Git `HEAD` short hash, then `dev`) and capability `environment:v1
|
|
412
|
-
prints the registration and each claimed run with its
|
|
413
|
-
Ctrl+C or `--max-runs <n>`, and prints the verdict table
|
|
414
|
-
apply; Hue chooses the pinned experiment. The worker exits 0
|
|
544
|
+
`AGENT_REVISION`, then the Git `HEAD` short hash, then `dev`) and capability `environment:v1`
|
|
545
|
+
plus any repeated `--capability <value>`, prints the registration and each claimed run with its
|
|
546
|
+
URL, executes runs launched from Hue until Ctrl+C or `--max-runs <n>`, and prints the verdict table
|
|
547
|
+
after each run. Selection flags do not apply; Hue chooses the pinned experiment. The worker exits 0
|
|
548
|
+
when it stops normally. Hue offers a case whose world comes with agent-visible files only to a
|
|
549
|
+
registration that declares `environment-files:v1` and `input:<extension>` for each file type, for
|
|
550
|
+
example `--capability environment-files:v1 --capability input:pdf`; the adapter or command then
|
|
551
|
+
receives the files as described above. Hue keeps each registered revision's capabilities fixed,
|
|
552
|
+
so declare new ones under a new `--revision`. A world pinned to provider profiles, a Gmail world
|
|
553
|
+
for example, needs a reviewed `attemptBaselineV2` (see
|
|
554
|
+
[Pinned provider-profile preflight](ENVIRONMENTS.md#pinned-provider-profile-preflight)); `--worker`
|
|
555
|
+
registers none, so put `"attemptBaselineV2"` in the run's configuration JSON when you launch the
|
|
556
|
+
run from Hue.
|
|
415
557
|
|
|
416
558
|
A Scenario or eval set whose dataset version is not saved cannot back an experiment: the command
|
|
417
559
|
exits 1 and asks for **Save eval-set version** in Hue or `--save-version`, which freezes that
|
|
418
560
|
version at its current revision. Connection settings are `HUE_API_KEY` and `HUE_BASE_URL`
|
|
419
561
|
(default `https://app.hue.run`), loaded from `--env-file <path>` (or its alias `--env-path`) first when given; `--origin`
|
|
420
|
-
overrides the origin. Telemetry content capture stays off unless `--content` is passed;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
562
|
+
overrides the origin. Telemetry content capture stays off unless `--content` is passed; the
|
|
563
|
+
examples pass it so the run's case spans carry content. Model and tool spans inside the agent come
|
|
564
|
+
only from the agent's own instrumentation. Case outputs, error messages and explanations are
|
|
565
|
+
stored in Hue whether or not `--content` is passed, so a case's answer can be graded and read on
|
|
566
|
+
its run page. A `--command`'s stdout is its answer unless it writes a result file, and is stored,
|
|
567
|
+
so it must not print credentials or debug logs there; before storing, `hue eval` replaces the
|
|
568
|
+
credentials it handed the case (the world token and MCP headers, a legacy MCP token, attempt
|
|
569
|
+
bearers) and every Hue control-plane credential in its environment with `[redacted]`, in the
|
|
570
|
+
answer, an adapter's thrown message and name, generated file names and the local checkpoint.
|
|
571
|
+
`--no-output` keeps outputs, error messages and explanations out of a one-shot run's stored
|
|
572
|
+
results; with `--content` the case span still carries the output. `--worker` always stores them, because a run launched from Hue is read
|
|
573
|
+
on its run page (that is `runLocalAgent()`'s contract), and refuses `--no-output`. An interrupted
|
|
574
|
+
one-shot run keeps the choice it started with, so one run never mixes stored and unstored outputs:
|
|
575
|
+
rerunning it with other `--no-output` or `--content` flags is refused with the flags it started
|
|
576
|
+
with. Resume a run that an earlier SDK started without `--content` by passing `--no-output`.
|
|
577
|
+
|
|
578
|
+
Files the agent writes to `output/` are uploaded as generated documents with or without
|
|
579
|
+
`--no-output`, and they are not redacted the way the answer is: `hue eval` replaces the same
|
|
580
|
+
credentials only in the UTF-8 `.txt`, `.csv` and `.json` documents it collects, while PDF, Office
|
|
581
|
+
and image documents, and files an adapter returns by `path`, are uploaded as written. A direct
|
|
582
|
+
case also keeps the agent's raw `output/` files on disk under its checkpoint directory
|
|
583
|
+
(`.hue/eval/<agent-key>/<project>/direct/<experiment>/files/`). Never write credentials to
|
|
584
|
+
`output/`.
|
|
585
|
+
|
|
586
|
+
Trace evidence is required for every case: when Hue does not accept a case's traces or logs, the
|
|
587
|
+
case is completed as failed (error `TelemetryNotAccepted`, evidence omitted as
|
|
588
|
+
`telemetry_not_accepted`, no output or generated files attached) instead of being left started,
|
|
589
|
+
and the run goes on. Each export of the CLI's own telemetry may take 30 seconds, retries
|
|
590
|
+
included: a slow acknowledgement is waited for, and a 429, 502, 503, 504 or network error is
|
|
591
|
+
retried within that time, honouring `Retry-After`; any other response, or rejected records, fails
|
|
592
|
+
the case at once. Stderr names the case as it completes, with the export
|
|
593
|
+
issue counts, for example
|
|
594
|
+
`[refund] telemetry not accepted, case failed: telemetry_not_accepted: traces failed 1 (HTTP 400)`;
|
|
595
|
+
the case counts as an error in the table and JSON whatever its scores, and the command exits 1.
|
|
596
|
+
Counts carry signals, kinds, HTTP statuses and record numbers only, never content or credentials.
|
|
425
597
|
Resumable checkpoints live in `.hue/eval/<agent-key>/<project id>/` (a `.gitignore` is written
|
|
426
598
|
inside `.hue/eval/`); `--checkpoint-dir` overrides the root. Rerunning the same selection resumes
|
|
427
599
|
an interrupted run without invoking the agent again; a different selection is refused until the
|
|
428
600
|
unfinished one is resumed or its directory is removed.
|
|
429
601
|
|
|
430
|
-
Exit codes: `0` every case passed
|
|
602
|
+
Exit codes: `0` every case passed the evaluators that apply to it (advisory ones never count),
|
|
603
|
+
`1` a case failed, errored (including one no pinned evaluator applies to or decides), was skipped
|
|
604
|
+
or Hue's checks were
|
|
431
605
|
still pending at `--wait`, `2` usage or configuration error (including a missing key), `130`
|
|
432
606
|
interrupted. On Node.js 22, load TypeScript adapters with `NODE_OPTIONS=--experimental-strip-types`;
|
|
433
607
|
non-erasable syntax (enums, parameter properties, namespaces) needs a loader such as `--import tsx`
|
|
@@ -445,8 +619,8 @@ published version, beside or instead of explicit `--scorer-version` IDs.
|
|
|
445
619
|
```sh
|
|
446
620
|
hue eval --set gia-d1-citation --scorer gia-d1-citation \
|
|
447
621
|
--command "pnpm --filter @august/frontend run hue:gia-agent" \
|
|
448
|
-
--revision prompt-v10 --wait 1800 --json --env-file .env.hue
|
|
449
|
-
hue eval --set gia-d1-citation --set-version 1 --scorer gia-d1-citation ./hue-agent.ts --baseline <experiment id>
|
|
622
|
+
--revision prompt-v10 --wait 1800 --content --json --env-file .env.hue
|
|
623
|
+
hue eval --set gia-d1-citation --set-version 1 --scorer gia-d1-citation ./hue-agent.ts --content --baseline <experiment id>
|
|
450
624
|
```
|
|
451
625
|
|
|
452
626
|
For each case the command is spawned once **inside a private case directory** with
|
|
@@ -460,6 +634,11 @@ For each case the command is spawned once **inside a private case directory** wi
|
|
|
460
634
|
<case dir>/output/ write the generated documents here
|
|
461
635
|
```
|
|
462
636
|
|
|
637
|
+
Hue verifies each generated document before the case completes. Once a document is reserved and
|
|
638
|
+
its bytes are uploaded, a verification that outlasts a request is waited out for up to three
|
|
639
|
+
minutes, so a completion that cannot reach Hue takes that long to fail; a reservation or upload
|
|
640
|
+
that cannot reach Hue still fails at once.
|
|
641
|
+
|
|
463
642
|
Every regular file the command leaves under `output/` is uploaded as a generated document
|
|
464
643
|
(accepted: `.pdf .docx .pptx .xlsx .json .txt .csv .png .jpg .jpeg .webp`; another extension or an
|
|
465
644
|
empty file is the case's error). Optional helpers: `manifest.json` (`{"primary": "<filename>",
|
|
@@ -469,6 +648,17 @@ the command's stdout is the output (JSON when it parses). A single generated fil
|
|
|
469
648
|
document by default. Model and provider credentials stay in the command's own environment; the
|
|
470
649
|
scoped case files are copies under `--checkpoint-dir` (default `.hue/eval/<agent-key>/<project>/direct/<experiment>`).
|
|
471
650
|
|
|
651
|
+
The same collection serves world cases, and it does not trust paths the agent controls. `output/`
|
|
652
|
+
must be a real directory, and a helper name, when present, a regular file; a symlink, FIFO or
|
|
653
|
+
directory in either place is the case's error. Symlinks and other special files among the
|
|
654
|
+
documents are skipped, as are hidden and lock entries. Every file is opened without following a
|
|
655
|
+
final symlink or blocking, must be the regular file the listing saw and within its limit (4 MiB
|
|
656
|
+
for a helper, 25 MiB for a document, checked before reading), and its upload is staged from the
|
|
657
|
+
bytes read. A file or directory that vanished or changed while it was collected, more than 32
|
|
658
|
+
documents or more than 1024 entries is the case's error too. A hard link is an ordinary file and
|
|
659
|
+
is collected like one: an agent running as your user could copy the file just as well, so run an
|
|
660
|
+
agent that must not reach your files under another account or in a sandbox.
|
|
661
|
+
|
|
472
662
|
An adapter file works too: it is called with `(inputs, context)` where `context.mode` is
|
|
473
663
|
`"direct"` and `context` carries `config`, `item`, `executionId`, `files` (agent-visible pinned
|
|
474
664
|
files on disk), `outputDirectory` and `signal`. Return a JSON value, or `withFiles(output, files)`
|
|
@@ -478,5 +668,6 @@ Code evaluators pinned to the run are **not** executed on your machine: the CLI
|
|
|
478
668
|
deferred (`deferUnboundLocalScorers`) and Hue's grading executor scores the uploaded documents;
|
|
479
669
|
evaluator-only pinned files such as a legal corpus are never downloaded. `Waiting for Hue
|
|
480
670
|
checks...` then covers that grading, so size `--wait` to the evaluator's runtime. The
|
|
481
|
-
`--json` document gains `"mode": "direct"` and `"deferredScorerVersionIds"`. Exit codes, `--baseline
|
|
482
|
-
and checkpoints behave as for Scenarios
|
|
671
|
+
`--json` document gains `"mode": "direct"` and `"deferredScorerVersionIds"`. Exit codes, `--baseline`,
|
|
672
|
+
`--content`, `--no-output` and checkpoints behave as for Scenarios; generated documents are
|
|
673
|
+
uploaded with or without them.
|
package/ENVIRONMENTS.md
CHANGED
|
@@ -62,7 +62,7 @@ to the callback and is never written to checkpoints.
|
|
|
62
62
|
|
|
63
63
|
### Published pins and verdicts
|
|
64
64
|
|
|
65
|
-
`
|
|
65
|
+
`definition: { kind: "pins", datasetVersionId, scorerVersionIds, config?, name? }` runs already
|
|
66
66
|
published immutable pins, such as a Scenario's frozen case and Hue-owned outcome checks or a saved
|
|
67
67
|
eval set with explicitly chosen scorer versions. `resolveScenarioPins(client, selector)` reads
|
|
68
68
|
those pins from a Scenario ID, its Hue URL or its name (`listScenarios` and `getScenario` expose
|
|
@@ -76,9 +76,15 @@ bound `local_code` pins.
|
|
|
76
76
|
Hue grades `world_outcome` pins after the world seals, so the runner's report precedes the
|
|
77
77
|
verdicts. `waitForResults(client, { runId, scorerVersionIds, subjectIds, timeoutMillis })` polls
|
|
78
78
|
until every item has a terminal result for every pin or the budget elapses (`complete: false`);
|
|
79
|
-
`summarizeVerdicts` turns results into per-case rows with `passed` and totals;
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
`summarizeVerdicts` turns results into per-case rows with `passed` and totals; a result Hue marks
|
|
80
|
+
`notApplicable` (the evaluator has no outcome criteria or conversion rubric to grade in that case)
|
|
81
|
+
neither passes nor fails its case and is listed in the row's `notApplicable`, and a case no pinned
|
|
82
|
+
evaluator applies to is an error. A result of a Hue judge listed in `waitForResults`'
|
|
83
|
+
`judgeScorerVersionIds` (`collectExperimentVerdicts` passes the experiment's `world_judge` pins)
|
|
84
|
+
whose evidence says `advisory: true` keeps its metrics in the row and is listed in its
|
|
85
|
+
`advisory`, but never decides the case; a case only advisory results ran for is an error. Any
|
|
86
|
+
other result, an error, or one with a metric carrying `passed` is never advisory. `compareVerdicts` diffs two summaries by case key, and
|
|
87
|
+
`collectExperimentVerdicts` combines those reads for one experiment. The `hue eval` command uses the same path; see
|
|
82
88
|
[Evaluate an agent against a case](CLI.md#evaluate-an-agent-against-a-case).
|
|
83
89
|
|
|
84
90
|
### Pinned provider-profile preflight
|
|
@@ -223,6 +229,8 @@ its own Gmail MCP or REST client, the mirror URL, the world token where the Goog
|
|
|
223
229
|
import {
|
|
224
230
|
agentEnvironment,
|
|
225
231
|
createEnvironmentClient,
|
|
232
|
+
HueEnvironmentError,
|
|
233
|
+
isTransientEnvironmentError,
|
|
226
234
|
worldHandoff,
|
|
227
235
|
writeMcpConfig,
|
|
228
236
|
} from "@hue-run/sdk/environment";
|
|
@@ -252,12 +260,22 @@ try {
|
|
|
252
260
|
);
|
|
253
261
|
const sealDeadline = Date.now() + 30_000;
|
|
254
262
|
let sealed = false;
|
|
255
|
-
while (Date.now() < sealDeadline) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
263
|
+
while (!sealed && Date.now() < sealDeadline) {
|
|
264
|
+
let wait = 250;
|
|
265
|
+
try {
|
|
266
|
+
// The deadline also ends a read that hangs, with the client's retries inside it.
|
|
267
|
+
const state = await environmentClient.getRun(run.id, {
|
|
268
|
+
signal: AbortSignal.timeout(Math.max(0, sealDeadline - Date.now())),
|
|
269
|
+
});
|
|
270
|
+
sealed = state.status !== "open";
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (!isTransientEnvironmentError(error)) throw error; // a refusal such as 404 is final
|
|
273
|
+
wait = Math.max(wait, (error as HueEnvironmentError).retryAfterMs ?? 0);
|
|
259
274
|
}
|
|
260
|
-
|
|
275
|
+
if (!sealed)
|
|
276
|
+
await new Promise((resolve) =>
|
|
277
|
+
setTimeout(resolve, Math.min(wait, Math.max(0, sealDeadline - Date.now()))),
|
|
278
|
+
);
|
|
261
279
|
}
|
|
262
280
|
if (!sealed) throw new Error("World was not sealed after its completion grace");
|
|
263
281
|
}
|
|
@@ -272,6 +290,27 @@ world token, so an adapter that read `HUE_MCP_URL` and `HUE_MCP_TOKEN` keeps wor
|
|
|
272
290
|
no Hue-native `tools` (Hue refuses them); a world created while the gateway is off keeps its tools
|
|
273
291
|
and the `hue_sim_` capability and emits a one-time `DeprecationWarning`.
|
|
274
292
|
|
|
293
|
+
A case pinned to a world may also pin input files. Before the case's execution starts, the
|
|
294
|
+
helpers download each agent-visible file (`source`, `attached_template`, `attached_reference`,
|
|
295
|
+
`original`), verify its size and SHA-256 against the case manifest and hand the verified copies to
|
|
296
|
+
the callback as `context.files` beside `context.world`, in a private directory (mode 0700, files
|
|
297
|
+
0600), with a private `context.outputDirectory`. Evaluator-only files (`org_template`,
|
|
298
|
+
`evaluator_reference`) never reach the agent; a local code evaluator's copies are checked before
|
|
299
|
+
the execution starts but saved only after the agent finished. Bytes that differ from the manifest raise
|
|
300
|
+
`CaseFileError` with code `case_file_mismatch`; a name that is not one safe file name (a path
|
|
301
|
+
separator, `.` or `..`, a C0 or C1 control character, a character Windows reserves such as `:` or
|
|
302
|
+
`?`, a Windows device name such as `CON`, a trailing dot or space, more than 200 bytes) raises
|
|
303
|
+
`case_file_name_refused`. Either stops the run before an
|
|
304
|
+
execution or world exists for the case. Return `withFiles(output, files)` to upload what the agent
|
|
305
|
+
produced: the files are published and linked to the execution as `artifactIds` and
|
|
306
|
+
`primaryArtifactId`, within the limits of direct cases (at most 32 files, 25 MiB each, the
|
|
307
|
+
accepted document types). The world token is never written into the case directory, which is
|
|
308
|
+
removed when the case ends; only staged outputs an interrupted upload resumes from are kept until
|
|
309
|
+
it does. `hue eval --command` receives the same files through
|
|
310
|
+
`HUE_CASE_DIR`, `HUE_CASE_INPUTS` and `HUE_CASE_OUTPUT_DIR`. A `runLocalAgent` worker is offered
|
|
311
|
+
such cases only when it declares `environment-files:v1` and `input:<extension>` for each file
|
|
312
|
+
type; see [EVALUATIONS.md](EVALUATIONS.md#direct-cases-and-files).
|
|
313
|
+
|
|
275
314
|
A refusal such as 409 `simulation_gateway_required` exposes its validated server code as `HueEnvironmentError.diagnostic`.
|
|
276
315
|
|
|
277
316
|
`agentEnvironment` removes Hue control-plane credentials from the child by default: `HUE_API_KEY`,
|