@hue-run/sdk 0.10.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 +148 -28
- package/ENVIRONMENTS.md +5 -1
- package/EVALUATIONS.md +2 -2
- package/README.md +19 -8
- package/dist/cli/eval.js +43 -17
- 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/client.d.ts +5 -1
- package/dist/evals/client.js +17 -11
- package/dist/evals/files.d.ts +10 -2
- package/dist/evals/files.js +68 -15
- package/dist/evals/json.js +116 -9
- package/dist/evals/runner.js +35 -8
- package/dist/evals/scorer-publication.js +58 -3
- package/dist/evals/types.d.ts +44 -0
- package/dist/evals/verdicts.d.ts +11 -0
- package/dist/evals/verdicts.js +33 -4
- package/dist/inline-files.js +11 -3
- 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/tool-definitions.d.ts +18 -0
- package/dist/tool-definitions.js +232 -0
- 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
|
|
@@ -393,11 +494,19 @@ Failing cases print the scorer explanation. An evaluator Hue records as not appl
|
|
|
393
494
|
(its outcome scoring has no outcome criteria or conversion rubric to grade there) shows `n/a` in
|
|
394
495
|
that case's columns and neither passes nor fails it, so in a mixed eval set a case is decided by
|
|
395
496
|
the evaluators that apply to it; the pass line counts the not-applicable results. A case that no
|
|
396
|
-
pinned evaluator applies to is an error that names what they need.
|
|
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,
|
|
397
505
|
regression and unchanged counts with per-case deltas. `--json` prints one JSON document
|
|
398
506
|
(`experimentId`, `runId`, `runUrl`, `complete`, `cases`, `totals`, optional `baseline`) on stdout
|
|
399
507
|
and sends progress to stderr; each case lists its not-applicable evaluator versions in
|
|
400
|
-
`notApplicable`, and `totals.notApplicable` counts
|
|
508
|
+
`notApplicable` and its advisory ones in `advisory`, and `totals.notApplicable` counts the
|
|
509
|
+
former; a case failed for its telemetry has `state: "error"`,
|
|
401
510
|
`passed: false` and
|
|
402
511
|
`telemetry: { code: "telemetry_not_accepted", issues: [{ signal, kind, status?, count }] }`. `--wait <seconds>` (default 300) bounds the verdict wait because
|
|
403
512
|
Hue-owned `world_outcome` checks are graded after the world seals. An experiment always covers
|
|
@@ -406,7 +515,9 @@ every case of the saved version; there is no case subset.
|
|
|
406
515
|
`--command "<shell command>"` spawns the command once per case with the world's environment
|
|
407
516
|
(`HUE_WORLD_ID`, `HUE_WORLD_TOKEN`, one `HUE_SIM_<SURFACE ID>_URL` per provider mirror,
|
|
408
517
|
`HUE_MCP_CONFIG` naming an owner-only `mcpServers` file that is removed after the case, and
|
|
409
|
-
`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
|
|
410
521
|
`HUE_EXECUTION_ID`, `HUE_ENVIRONMENT_RUN_ID`, `HUE_CASE_ID` and `HUE_CASE_KEY`, and
|
|
411
522
|
`{"inputs": ..., "config": ...}` on stdin. The child does not receive `HUE_API_KEY`, `HUE_MCP_KEY`
|
|
412
523
|
or any other Hue control-plane credential unless `--allow-hue-credentials` is passed; the rest of
|
|
@@ -475,7 +586,10 @@ case also keeps the agent's raw `output/` files on disk under its checkpoint dir
|
|
|
475
586
|
Trace evidence is required for every case: when Hue does not accept a case's traces or logs, the
|
|
476
587
|
case is completed as failed (error `TelemetryNotAccepted`, evidence omitted as
|
|
477
588
|
`telemetry_not_accepted`, no output or generated files attached) instead of being left started,
|
|
478
|
-
and the run goes on.
|
|
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
|
|
479
593
|
issue counts, for example
|
|
480
594
|
`[refund] telemetry not accepted, case failed: telemetry_not_accepted: traces failed 1 (HTTP 400)`;
|
|
481
595
|
the case counts as an error in the table and JSON whatever its scores, and the command exits 1.
|
|
@@ -485,8 +599,9 @@ inside `.hue/eval/`); `--checkpoint-dir` overrides the root. Rerunning the same
|
|
|
485
599
|
an interrupted run without invoking the agent again; a different selection is refused until the
|
|
486
600
|
unfinished one is resumed or its directory is removed.
|
|
487
601
|
|
|
488
|
-
Exit codes: `0` every case passed the evaluators that apply to it
|
|
489
|
-
(including one no pinned evaluator applies to), was skipped
|
|
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
|
|
490
605
|
still pending at `--wait`, `2` usage or configuration error (including a missing key), `130`
|
|
491
606
|
interrupted. On Node.js 22, load TypeScript adapters with `NODE_OPTIONS=--experimental-strip-types`;
|
|
492
607
|
non-erasable syntax (enums, parameter properties, namespaces) needs a loader such as `--import tsx`
|
|
@@ -519,6 +634,11 @@ For each case the command is spawned once **inside a private case directory** wi
|
|
|
519
634
|
<case dir>/output/ write the generated documents here
|
|
520
635
|
```
|
|
521
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
|
+
|
|
522
642
|
Every regular file the command leaves under `output/` is uploaded as a generated document
|
|
523
643
|
(accepted: `.pdf .docx .pptx .xlsx .json .txt .csv .png .jpg .jpeg .webp`; another extension or an
|
|
524
644
|
empty file is the case's error). Optional helpers: `manifest.json` (`{"primary": "<filename>",
|
package/ENVIRONMENTS.md
CHANGED
|
@@ -79,7 +79,11 @@ until every item has a terminal result for every pin or the budget elapses (`com
|
|
|
79
79
|
`summarizeVerdicts` turns results into per-case rows with `passed` and totals; a result Hue marks
|
|
80
80
|
`notApplicable` (the evaluator has no outcome criteria or conversion rubric to grade in that case)
|
|
81
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.
|
|
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
|
|
83
87
|
`collectExperimentVerdicts` combines those reads for one experiment. The `hue eval` command uses the same path; see
|
|
84
88
|
[Evaluate an agent against a case](CLI.md#evaluate-an-agent-against-a-case).
|
|
85
89
|
|
package/EVALUATIONS.md
CHANGED
|
@@ -159,7 +159,7 @@ built-ins keep their usual missing-output behavior. See
|
|
|
159
159
|
|
|
160
160
|
Scorer deferral shipped in TypeScript `0.3.1`: the local runner executes only the three known built-in entries and bound `local_code` scorers. It leaves every other pin pending and reports its ID in `deferredScorerVersionIds`, including kinds and built-in entries introduced by a newer server. It never uploads a placeholder result that would occupy the immutable result slot, including placeholders already saved in an older SDK's checkpoint. Direct `scoreLocally()` calls reject pins that require another executor.
|
|
161
161
|
|
|
162
|
-
`world_outcome` pins run inside Hue and need no local callback or executable source digest. A supporting server owns their execution from saved world evidence. Legacy `local_code` pins still require the exact registered callback; changing the worker cannot convert those immutable pins into hosted ones.
|
|
162
|
+
`world_outcome` pins (`hue.conversion_outcome.v1` and `.v2`, and the `hue.outcome_assertions.v2` and `.v3` case-outcome evaluators, `.v3` with its pinned judge in `config.judge`) run inside Hue and need no local callback or executable source digest. A supporting server owns their execution from saved world evidence. Legacy `local_code` pins still require the exact registered callback; changing the worker cannot convert those immutable pins into hosted ones.
|
|
163
163
|
|
|
164
164
|
Manual results require a human session. Hosted model-judge dispatch is an explicit separate API operation: inspect `getJudgeBudget()`, then call `createJudgeJobs(runId,{idempotencyKey,jobs:[{evaluationItemId,scorerVersionId}]})`. `listJudgeJobs`, `getJudgeJob` and `cancelJudgeJob` expose job progress and cancellation requests. These methods never claim that local execution has hosted provenance. Hosted job endpoints are covered by HTTP contract tests here; live hosted model execution is a separate platform acceptance phase. `listResults` and `getResult` read recorded local or hosted results.
|
|
165
165
|
|
|
@@ -183,7 +183,7 @@ An exclusive `.lock` prevents two processes from invoking targets through the sa
|
|
|
183
183
|
|
|
184
184
|
The runner saves a starting marker before `start`, and a running marker before invoking a target. If an outcome is not durably saved, resume throws `UncertainExecutionError` and never reruns the target. Replaying an original start key only recovers its execution ID. The low-level `startExecution` API requires an explicit `previousExecutionId`; replacing a still-started attempt additionally requires `allowUncertainRetry:true`. The convenience runner does not automatically adopt externally created attempts. Create a fresh experiment for a fresh target run after investigating side effects.
|
|
185
185
|
|
|
186
|
-
After a target completes and local scoring finishes, the runner saves the allowed completion/result payloads before uploading. Network/API errors leave those payloads and stable keys available for another call to `runExperiment` with the same directory. Saved receipt IDs prevent duplicate result writes.
|
|
186
|
+
After a target completes and local scoring finishes, the runner saves the allowed completion/result payloads before uploading. Network/API errors leave those payloads and stable keys available for another call to `runExperiment` with the same directory. Saved receipt IDs prevent duplicate result writes. An output beyond what Hue stores for one case (200,000 bytes of JSON, 20,000 values or 32 levels of nesting) is that case's own failure: the case completes as `error` with the error type `OutputTooLarge` (and, when result content is persisted, a message naming the bound), no output is stored and the other cases keep running; return a large result as a generated file instead. Output that is not JSON (a cycle, a class instance, a non-finite number) raises `OutcomeSerializationError` with the execution ID. The output is checked as it is read, and the first value that is not JSON or past the value or nesting bound decides which; the byte bound is checked last, so an output past it that also holds a value that is not JSON raises `OutcomeSerializationError`, and one too long for the runtime to serialize is refused before it is serialized. `OutcomeSerializationError` never relabels the target as failed or invokes it again. Scoring/upload failures never change target state.
|
|
187
187
|
|
|
188
188
|
A crash between target completion and saving its permitted result still leaves an uncertain outcome; metadata-only mode intentionally cannot reconstruct discarded output. Export acknowledgement is saved separately. If required telemetry export was not acknowledged, a fresh empty exporter is not evidence of prior receipt: automatic completion is refused. Inspect/export the original trace or use the low-level completion API with an explicit omission policy; do not rerun a known completed target to manufacture telemetry. API completion/results retain their own idempotency guarantees for explicit recovery.
|
|
189
189
|
|
package/README.md
CHANGED
|
@@ -83,8 +83,9 @@ claim. Setup never enables content capture or creates a simulation, Hue Run, eva
|
|
|
83
83
|
or remote execution. See the [setup CLI contract](./CLI.md) for the supported shapes and release gates.
|
|
84
84
|
|
|
85
85
|
For an existing account, `hue login` validates keys created in Hue Settings and stores them in
|
|
86
|
-
`.env.hue` without printing them, and `hue mcp install --client claude-code` (or `
|
|
87
|
-
`vscode`, `windsurf`, `gemini`) writes the Hue MCP configuration that
|
|
86
|
+
`.env.hue` without printing them, and `hue mcp install --client claude-code` (or `codex`,
|
|
87
|
+
`conductor`, `cursor`, `vscode`, `windsurf`, `gemini`) writes the Hue MCP configuration that
|
|
88
|
+
references `HUE_MCP_KEY`, or with `--auth oauth` only the URL, for signing in with Hue. See
|
|
88
89
|
[Sign in and store keys](./CLI.md#sign-in-and-store-keys) and
|
|
89
90
|
[Install the MCP for your coding agent](./CLI.md#install-the-mcp-for-your-coding-agent).
|
|
90
91
|
|
|
@@ -155,12 +156,22 @@ await hue.model(
|
|
|
155
156
|
|
|
156
157
|
Each OpenAI Responses `mcp_call`, `web_search_call`, `file_search_call` or `code_interpreter_call`
|
|
157
158
|
item, and each Anthropic `mcp_tool_use` or `server_tool_use` block with its result, becomes an
|
|
158
|
-
`execute_tool {name}` child span with `gen_ai.tool.type` `extension`, `gen_ai.tool.call.id
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
`
|
|
159
|
+
`execute_tool {name}` child span with `gen_ai.tool.type` `extension`, `gen_ai.tool.call.id`,
|
|
160
|
+
`hue.tool.call.position` (the 0-based position of its item in the response: the OpenAI `output`
|
|
161
|
+
index or the Anthropic `content` block index, in both capture modes) and, for MCP calls,
|
|
162
|
+
`mcp.server.name` (the provider's label). Arguments and results follow `captureContent`; a failed
|
|
163
|
+
call carries `error.type` (`mcp_error`, the provider's status or error code) and ERROR status. With
|
|
164
|
+
`captureContent: true`, a failed OpenAI MCP call's status description is the provider's error
|
|
165
|
+
text, credentials scrubbed (URL userinfo, query values and fragments, or the whole URL for a scheme
|
|
166
|
+
other than `http(s)`, `ws(s)` and `ftp`; tokens with a known credential prefix such as `hue_sk_`,
|
|
167
|
+
`sk-` or `xoxb-`; authorization-scheme credentials and an `Authorization` header's whole value;
|
|
168
|
+
the value of a credential-named `key=value` or `key: value` pair, quoted or not) and cut to 1,024
|
|
169
|
+
characters. An `mcp_list_tools`
|
|
170
|
+
item becomes a `tools/list` child span with that server's `gen_ai.tool.definitions`; with
|
|
171
|
+
`captureContent: false` it carries only `hue.tool.names` and `hue.tool.definitions.sha256`, the
|
|
172
|
+
summary described below. Pass the request so each server's host is recorded as `server.address`
|
|
173
|
+
(only server URLs are read, never credentials; a host name keeps its underscores, as WHATWG URL
|
|
174
|
+
parsing does), and `servers` to record a
|
|
164
175
|
server's real `name`, `version`, Hue `provider` and `surface` under its label. `provider` defaults
|
|
165
176
|
to the enclosing `model()` call's provider (`openai` or `anthropic`). The spans have no duration of
|
|
166
177
|
their own: the provider ran the tools inside the model request.
|
package/dist/cli/eval.js
CHANGED
|
@@ -446,6 +446,10 @@ function parseAnswer(text) {
|
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
448
|
const MIN_SECRET_LENGTH = 16;
|
|
449
|
+
/** Each export of the CLI's own telemetry, retries included, may take this long. A case's trace
|
|
450
|
+
* is required evidence, and Hue can acknowledge it slowly or ask for a retry after a few seconds,
|
|
451
|
+
* which the 10-second default leaves no room for. A refusal still fails at once. */
|
|
452
|
+
const CASE_TRACE_EXPORT_MILLIS = 30_000;
|
|
449
453
|
function caseSecrets(context) {
|
|
450
454
|
const values = new Set();
|
|
451
455
|
// Hue issues long credentials; a short value would redact ordinary text around it.
|
|
@@ -696,27 +700,38 @@ const STATE_LABEL = {
|
|
|
696
700
|
pending: "PENDING",
|
|
697
701
|
};
|
|
698
702
|
export function renderTable(verdicts, output) {
|
|
699
|
-
const
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
const
|
|
703
|
-
for (const item of
|
|
703
|
+
const cases = verdicts.summary.cases;
|
|
704
|
+
// A column per metric name; a name several evaluators report, such as two judges' `verdict`,
|
|
705
|
+
// gets a column per evaluator so each one's value shows.
|
|
706
|
+
const reporters = new Map();
|
|
707
|
+
for (const item of cases)
|
|
704
708
|
for (const metric of item.metrics) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
709
|
+
const versions = reporters.get(metric.name) ?? [];
|
|
710
|
+
if (!versions.includes(metric.scorerVersionId))
|
|
711
|
+
versions.push(metric.scorerVersionId);
|
|
712
|
+
reporters.set(metric.name, versions);
|
|
708
713
|
}
|
|
709
|
-
const
|
|
710
|
-
|
|
714
|
+
const columns = [...reporters].flatMap(([name, versions]) => versions.map((version) => ({
|
|
715
|
+
name,
|
|
716
|
+
version,
|
|
717
|
+
label: versions.length === 1 ? name : `${name} ${version.slice(0, 8)}`,
|
|
718
|
+
})));
|
|
719
|
+
// An advisory evaluator's values are shown, marked, and never decide a case.
|
|
720
|
+
const advisory = new Set(cases.flatMap((item) => item.advisory ?? []));
|
|
721
|
+
const header = [
|
|
722
|
+
"Case",
|
|
723
|
+
...columns.map((column) => advisory.has(column.version) ? `${column.label} (advisory)` : column.label),
|
|
724
|
+
"Result",
|
|
725
|
+
];
|
|
726
|
+
const rows = cases.map((item) => [
|
|
711
727
|
item.externalKey,
|
|
712
|
-
...
|
|
713
|
-
const metric = item.metrics.find((candidate) => candidate.name === name);
|
|
728
|
+
...columns.map((column) => {
|
|
729
|
+
const metric = item.metrics.find((candidate) => candidate.name === column.name && candidate.scorerVersionId === column.version);
|
|
714
730
|
if (metric)
|
|
715
731
|
return metricText(metric);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
: "-";
|
|
732
|
+
// A case the column's evaluator does not apply to shows n/a; one it applies to without
|
|
733
|
+
// reporting the metric shows "-".
|
|
734
|
+
return item.notApplicable?.includes(column.version) ? "n/a" : "-";
|
|
720
735
|
}),
|
|
721
736
|
STATE_LABEL[item.state],
|
|
722
737
|
]);
|
|
@@ -737,6 +752,8 @@ export function renderTable(verdicts, output) {
|
|
|
737
752
|
output.log(` ${item.externalKey}: ${detail}`);
|
|
738
753
|
}
|
|
739
754
|
const totals = verdicts.summary.totals;
|
|
755
|
+
const advisoryFailures = cases.reduce((count, item) => count +
|
|
756
|
+
item.metrics.filter((metric) => item.advisory?.includes(metric.scorerVersionId) && !metricPassed(metric)).length, 0);
|
|
740
757
|
const extra = [
|
|
741
758
|
totals.error ? `${totals.error} error` : "",
|
|
742
759
|
totals.skipped ? `${totals.skipped} skipped` : "",
|
|
@@ -744,6 +761,9 @@ export function renderTable(verdicts, output) {
|
|
|
744
761
|
totals.notApplicable
|
|
745
762
|
? `${totals.notApplicable} evaluator result${totals.notApplicable === 1 ? "" : "s"} not applicable`
|
|
746
763
|
: "",
|
|
764
|
+
advisoryFailures
|
|
765
|
+
? `${advisoryFailures} advisory failure${advisoryFailures === 1 ? "" : "s"} not counted`
|
|
766
|
+
: "",
|
|
747
767
|
].filter(Boolean);
|
|
748
768
|
output.log(`${totals.passed} of ${totals.cases} case${totals.cases === 1 ? "" : "s"} passed${extra.length ? ` (${extra.join(", ")})` : ""}`);
|
|
749
769
|
if (!verdicts.results.complete)
|
|
@@ -1426,7 +1446,13 @@ export async function runEvalCommand(argv) {
|
|
|
1426
1446
|
allowHueCredentials: values["allow-hue-credentials"],
|
|
1427
1447
|
})),
|
|
1428
1448
|
};
|
|
1429
|
-
hue = createHue({
|
|
1449
|
+
hue = createHue({
|
|
1450
|
+
apiKey,
|
|
1451
|
+
baseUrl,
|
|
1452
|
+
serviceName: key,
|
|
1453
|
+
captureContent: values.content,
|
|
1454
|
+
timeoutMillis: CASE_TRACE_EXPORT_MILLIS,
|
|
1455
|
+
});
|
|
1430
1456
|
return values.worker
|
|
1431
1457
|
? await runWorker(values, connection, agents.simulation, agent, hue, output, controller.signal)
|
|
1432
1458
|
: await runOnce(values, connection, agents, agent, hue, output, controller.signal);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { lookup as dnsLookup } from "node:dns";
|
|
2
|
+
/**
|
|
3
|
+
* `hue listen`: delivers a simulated world's events to a receiver on this machine, the way a
|
|
4
|
+
* provider would post them to a public request URL. The client pulls leased deliveries from Hue
|
|
5
|
+
* with the subscription's own credential (a world token or a connection key, never a project
|
|
6
|
+
* key), forwards each stored request unchanged to the local URL, and acknowledges it with the
|
|
7
|
+
* local answer. Every pull is one bounded request; nothing holds a connection open. A delivery is
|
|
8
|
+
* acknowledged only after the receiver answered or its window closed, so stopping never marks an
|
|
9
|
+
* event delivered that was not: an unacknowledged lease lapses and Hue retries it on the
|
|
10
|
+
* provider's schedule.
|
|
11
|
+
*/
|
|
12
|
+
/** Streams, environment, network and signal seams for {@link runListenCommand}; tests inject these. */
|
|
13
|
+
export interface ListenCommandIo {
|
|
14
|
+
stdout?: NodeJS.WritableStream;
|
|
15
|
+
stderr?: NodeJS.WritableStream;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
fetch?: typeof fetch;
|
|
19
|
+
/** Registers the stop request (SIGINT and SIGTERM by default) and returns its removal. */
|
|
20
|
+
onInterrupt?: (listener: () => void) => () => void;
|
|
21
|
+
/** The acknowledgement window a forward waits for; the provider's three seconds by default. */
|
|
22
|
+
forwardTimeoutMs?: number;
|
|
23
|
+
/** Delays between retries of a failed pull. */
|
|
24
|
+
retryDelaysMs?: readonly number[];
|
|
25
|
+
}
|
|
26
|
+
export declare const LISTEN_USAGE = "Usage: hue listen --subscription ID --forward-to URL [--origin URL] [--env-path PATH]\n [--credential world-token|connection-key] [--max N] [--allow-remote-forward]\n\nDeliver a simulated world's events to a receiver on this machine. Hue's signed requests (Slack\nEvents API requests today) are pulled with the subscription's own credential, forwarded unchanged\nto --forward-to, and acknowledged with the receiver's answer. Press Ctrl+C to stop: pulls stop,\ndeliveries in flight are finished and acknowledged, and nothing unanswered is acknowledged.\n\nThe credential is read from the environment, never from the command line:\n HUE_WORLD_TOKEN the world token, for a subscription on one world\n HUE_CONNECTION_KEY the connection key, for a subscription on a connection key\nA project key (HUE_API_KEY, HUE_MCP_KEY) is refused.\n\nOptions:\n --subscription ID The listen subscription to deliver (its id from Hue)\n --forward-to URL The local receiver, for example http://localhost:3000/slack/events\n --origin URL Hue origin (default HUE_BASE_URL, then https://app.hue.run)\n --env-path PATH Load a dotenv file first (--env-file also works when it exists)\n --credential KIND world-token or connection-key, when both variables are set\n --max N Deliveries leased per pull and forwarded at once, 1 to 10 (default 10)\n --allow-remote-forward Allow a --forward-to host other than this machine, over https only\n -h, --help Show this help";
|
|
27
|
+
/** A leased delivery: the exact request the provider would send. */
|
|
28
|
+
interface Delivery {
|
|
29
|
+
deliveryId: string;
|
|
30
|
+
eventId: string | null;
|
|
31
|
+
kind: "url_verification" | "event_callback";
|
|
32
|
+
retryNum: number;
|
|
33
|
+
/** The lease's end as the server stated it, or null when absent. */
|
|
34
|
+
leaseExpiresAt: number | null;
|
|
35
|
+
request: {
|
|
36
|
+
headers: Record<string, string>;
|
|
37
|
+
body: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The local answer, in the acknowledgement's own shape. */
|
|
41
|
+
interface LocalAnswer {
|
|
42
|
+
outcome: "response" | "timeout" | "connection_failed" | "tls_error";
|
|
43
|
+
status?: number;
|
|
44
|
+
durationMs: number;
|
|
45
|
+
noRetry?: boolean;
|
|
46
|
+
body?: string;
|
|
47
|
+
}
|
|
48
|
+
/** A loopback address: 127.0.0.0/8, ::1, or 127.0.0.0/8 carried in IPv4-mapped IPv6. */
|
|
49
|
+
export declare function isLoopbackAddress(address: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The receiver URL, or why it is refused: HTTP or HTTPS without credentials or fragment, on this
|
|
52
|
+
* machine (`localhost`, a `.localhost` name or a loopback address) unless `allowRemote`, and then
|
|
53
|
+
* HTTPS only off this machine.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseForwardTarget(value: string, allowRemote: boolean): URL | string;
|
|
56
|
+
/**
|
|
57
|
+
* Sends one delivery to the receiver and reports its answer, or null when `signal` abandoned it.
|
|
58
|
+
* The body bytes and every header go out unchanged (the provider's signature covers them), no
|
|
59
|
+
* redirect is followed, and the answer counts only inside `timeoutMs`. Only a URL verification's
|
|
60
|
+
* successful answer body is read.
|
|
61
|
+
*/
|
|
62
|
+
export declare function forwardDelivery(target: URL, delivery: Delivery, options?: {
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
allowRemote?: boolean;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
/** Resolves `--forward-to` names; tests stand in for DNS. */
|
|
67
|
+
resolver?: typeof dnsLookup;
|
|
68
|
+
}): Promise<LocalAnswer | null>;
|
|
69
|
+
/** One delivery from a pull answer, or null when it is not one this client can forward safely. */
|
|
70
|
+
export declare function parseDelivery(value: unknown, subscriptionId: string): Delivery | null;
|
|
71
|
+
/**
|
|
72
|
+
* Runs `hue listen` and returns the process exit code: 0 stopped, 1 failed, 2 usage error, 130
|
|
73
|
+
* interrupted twice (acknowledgements abandoned). `argv` may start with the `listen` command word.
|
|
74
|
+
*/
|
|
75
|
+
export declare function runListenCommand(argv: string[], io?: ListenCommandIo): Promise<number>;
|
|
76
|
+
export {};
|