@hue-run/sdk 0.3.2 → 0.4.1
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 +270 -47
- package/ENVIRONMENTS.md +10 -0
- package/README.md +19 -3
- package/dist/client.d.ts +5 -5
- package/dist/client.js +13 -6
- package/dist/environment/tools.d.ts +6 -1
- package/dist/environment/tools.js +7 -1
- package/dist/environment/types.d.ts +6 -1
- package/dist/receipt.js +36 -8
- package/dist/setup/application.d.ts +74 -0
- package/dist/setup/application.js +766 -0
- package/dist/setup/backend.d.ts +229 -0
- package/dist/setup/backend.js +855 -0
- package/dist/setup/checkpoint.js +100 -30
- package/dist/setup/cli.js +20 -4
- package/dist/setup/configure.d.ts +13 -0
- package/dist/setup/configure.js +454 -0
- package/dist/setup/credential.d.ts +2 -0
- package/dist/setup/credential.js +9 -0
- package/dist/setup/detect.js +4 -1
- package/dist/setup/installation.d.ts +118 -0
- package/dist/setup/installation.js +605 -0
- package/dist/setup/lock.d.ts +2 -0
- package/dist/setup/lock.js +38 -0
- package/dist/setup/machine.d.ts +1 -10
- package/dist/setup/machine.js +8 -7
- package/dist/setup/render.d.ts +3 -1
- package/dist/setup/render.js +209 -6
- package/dist/setup/runner.d.ts +26 -76
- package/dist/setup/runner.js +320 -45
- package/dist/setup/socket.d.ts +7 -0
- package/dist/setup/socket.js +144 -0
- package/dist/setup/source.d.ts +9 -0
- package/dist/setup/source.js +269 -0
- package/dist/setup/types.d.ts +16 -9
- package/dist/setup/types.js +1 -1
- package/dist/setup.d.ts +6 -2
- package/dist/setup.js +3 -0
- package/dist/types.d.ts +24 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/setup-events.schema.json +16 -9
package/CLI.md
CHANGED
|
@@ -1,52 +1,275 @@
|
|
|
1
|
-
# Hue setup
|
|
1
|
+
# Hue setup CLI
|
|
2
2
|
|
|
3
|
-
The local setup-session CLI core shipped in TypeScript `0.3.1`
|
|
3
|
+
The local setup-session CLI core shipped in TypeScript `0.3.1`. TypeScript `0.4.0` is an unreleased,
|
|
4
|
+
activation-gated candidate for the real one-command flow:
|
|
4
5
|
|
|
5
6
|
```sh
|
|
6
|
-
hue setup
|
|
7
|
-
|
|
8
|
-
hue
|
|
9
|
-
hue status
|
|
10
|
-
hue claim # reports account attachment unavailable in this local core
|
|
7
|
+
npx --yes @hue-run/sdk@latest setup --agent
|
|
8
|
+
# Default Terminal mode, when a person is present:
|
|
9
|
+
npx @hue-run/sdk@latest setup
|
|
11
10
|
```
|
|
12
11
|
|
|
13
|
-
These commands
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
makes
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
not
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
the
|
|
52
|
-
|
|
12
|
+
These public commands are not working onboarding promises until candidate publication, Fern
|
|
13
|
+
activation, production acceptance and `latest` promotion all pass. The automatic matrix is limited
|
|
14
|
+
to Express with npm, Express with Bun, and Flask with uv in a single application package with one recognizable
|
|
15
|
+
entrypoint, a literal existing GET route and `PORT` supplied by the environment. The command uses the
|
|
16
|
+
detected manager to install an exact runtime, inserts two owned middleware marker blocks without
|
|
17
|
+
rewriting business logic, starts that exact entrypoint without a shell, authenticates its loopback
|
|
18
|
+
socket before sending HTTP, makes one request on that same socket, flushes, and verifies its exact trace/span receipt.
|
|
19
|
+
A delayed listener, failing handler, missing telemetry or receipt failure never authorizes replay of
|
|
20
|
+
business work. A setup probe is separate transport evidence and cannot establish application
|
|
21
|
+
instrumentation.
|
|
22
|
+
|
|
23
|
+
Workspace roots, mixed languages/managers, custom Hue versions, edited marker blocks, unfamiliar
|
|
24
|
+
entrypoints and ambiguous routes stop with `action.required`. Follow its specific local action and
|
|
25
|
+
rerun the same installation; select the intended application package with `--project` when needed.
|
|
26
|
+
Setup does not choose a monorepo package or silently broaden its supported shapes. Preserve the
|
|
27
|
+
application's business logic when completing an integration manually.
|
|
28
|
+
|
|
29
|
+
The bounded fixture contract is ESM JavaScript/TypeScript Express with `const app = express();`,
|
|
30
|
+
one literal `app.get(...)`, an environment-selected listen port, and a start script containing only
|
|
31
|
+
`node path/to/server.mjs` (or `.js`, `.ts`, `.mts`) or `bun path/to/server.mjs`. A Bun start script
|
|
32
|
+
requires the Bun manager. Plain JavaScript does not need a TypeScript dependency. Flask uses root
|
|
33
|
+
`app.py`, `app = Flask(__name__)`, one literal `@app.get(...)` and a port read from
|
|
34
|
+
`os.environ["PORT"]`. Routes cannot contain network-path prefixes, escapes or dynamic segments.
|
|
35
|
+
JavaScript/TypeScript shebang/BOM entrypoints require manual integration. Python UTF-8/ASCII
|
|
36
|
+
shebang, encoding, comments, module docstring (including parenthesized forms) and future imports
|
|
37
|
+
are preserved using an isolated stdlib Python 3 syntax parser. Other encodings and ambiguous
|
|
38
|
+
statement layouts are refused. Package roots nested in an npm/Bun workspace or beneath any Python
|
|
39
|
+
project manifest are refused because managers can update ancestor locks; Python build-system/custom-source projects are also outside this matrix.
|
|
40
|
+
`uv` uses binary-only dependency installation and starts with no implicit sync/build.
|
|
41
|
+
|
|
42
|
+
The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
|
|
43
|
+
`capture_content=False`. For a supported application, setup installs the dependency and adds the
|
|
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.4.1`, `@opentelemetry/api@1.9.1` and
|
|
46
|
+
`@opentelemetry/context-async-hooks@2.11.0`; Python uses `hue-run==0.2.2`.
|
|
47
|
+
Content capture requires an ordinary account-managed key and a later explicit application decision.
|
|
48
|
+
|
|
49
|
+
The generated bootstrap supplies standard active SERVER-span context across asynchronous/streaming
|
|
50
|
+
handlers; the core SDK's global-provider/context ownership is unchanged. It preserves a working
|
|
51
|
+
caller context manager and disposes only its own instance. Unknown local imports, custom/late manager
|
|
52
|
+
bootstraps, runtime preloads and conflicting OTel dependencies require manual review before any
|
|
53
|
+
mutation or provisioning. Automatic Express entrypoints import only Express, the standard OTel API,
|
|
54
|
+
or the supported `node:fs`, `node:fs/promises`, `node:timers/promises` and `node:stream` modules.
|
|
55
|
+
Syntax parsing, not matches inside comments/strings/templates/regular expressions, establishes the
|
|
56
|
+
constructor and literal route. Custom middleware, alternate route methods, app aliases/escapes and
|
|
57
|
+
extra Flask handler decorators are outside the automatic matrix. The one selected request must finish with a 2xx response; a 404 or
|
|
58
|
+
telemetry failure never authorizes a business retry.
|
|
59
|
+
|
|
60
|
+
Bun runtime checks and application launch use an explicit empty config and disable dotenv loading.
|
|
61
|
+
Local runtime/preload settings, dotenv files and a global `.bunfig.toml` require manual review before
|
|
62
|
+
any Bun invocation. The only supported local `bunfig.toml` is an `[install]` section with one
|
|
63
|
+
credential-free registry origin for package installation; this does not become runtime config.
|
|
64
|
+
|
|
65
|
+
Automatic Express listeners must be the single top-level
|
|
66
|
+
`app.listen(Number(process.env.PORT), "127.0.0.1")` (the direct `process.env.PORT` argument
|
|
67
|
+
is also recognized). Flask permits only `app.run(port=int(os.environ["PORT"]))`, optionally
|
|
68
|
+
with `host="127.0.0.1"`, at module level or under the usual main guard. Custom callbacks,
|
|
69
|
+
server handles, socket metadata access, reloaders, workers and listener options require manual
|
|
70
|
+
integration. Flask dotenv/runtime bootstrap configuration also requires review.
|
|
71
|
+
Before dependency or credential changes, an isolated compile-only check validates the selected
|
|
72
|
+
Node/Bun entrypoint without executing it; Node-unsupported TypeScript syntax is refused.
|
|
73
|
+
|
|
74
|
+
During this setup-owned invocation only, a bounded server-first proof authenticates the retained
|
|
75
|
+
loopback connection. HTTP uses that exact socket once, with no redirect, redial or retry. Express
|
|
76
|
+
uses a private inner listener and a one-connection forwarding listener; Flask uses its standard
|
|
77
|
+
request-handler seam. The per-attempt secret stays in memory/private child transport, never in
|
|
78
|
+
events, checkpoints or receipts. Ordinary app starts have no handshake and retain their original
|
|
79
|
+
listener behavior. This prevents accidental requests to an unrelated listener, not access by
|
|
80
|
+
malicious code running as the same local user.
|
|
81
|
+
|
|
82
|
+
Setup creates no Scenario, Hue Run, evaluation, source capture, worker or remote execution. Package
|
|
83
|
+
manager lifecycle scripts are disabled. The supported existing application entrypoint is executed
|
|
84
|
+
directly with fixed argv solely for its bounded local HTTP verification; no shell command is accepted.
|
|
85
|
+
|
|
86
|
+
## Technical preflight and privacy disclosure
|
|
87
|
+
|
|
88
|
+
Read-only project detection and conflict checks precede project mutation. Public
|
|
89
|
+
`GET /api/v1/setup/preflight` reports `available` or `inactive`, `metadata-only-v1`, trial limits and
|
|
90
|
+
lifetime, and the published privacy and security information. An inactive deployment remains
|
|
91
|
+
unverified. An available deployment proceeds through runtime installation, private installation and
|
|
92
|
+
credential provisioning, managed application wiring, one application request and exact receipt
|
|
93
|
+
verification.
|
|
94
|
+
|
|
95
|
+
Before telemetry, `privacy.notice` presents `https://hue.run/privacy`, effective date `2026-08-24`,
|
|
96
|
+
and `https://trust.hue.run/`. This is a non-blocking disclosure. Setup has no acceptance step or
|
|
97
|
+
acceptance fields and does not infer a person's acknowledgement. It does not invent notice content.
|
|
98
|
+
|
|
99
|
+
The anonymous ingestion window is 24 hours with lifetime limits of 100 traces, 1,000 spans and 2 MiB
|
|
100
|
+
of sanitized stored data. Unclaimed data is purged seven days after expiry. Claim preserves the
|
|
101
|
+
project and its data; the setup-issued replacement key remains metadata-only.
|
|
102
|
+
|
|
103
|
+
## Commands and modes
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
hue setup # provision/configure/verify, then prepare a private owner handoff
|
|
107
|
+
hue resume # continue the same project/origin installation
|
|
108
|
+
hue status # read status; after claim, reconcile generation 1 if necessary
|
|
109
|
+
hue claim # open the owner-only handoff, or reconcile after browser claim
|
|
110
|
+
hue claim --restart # explicit owner recovery; requires an interactive local terminal
|
|
111
|
+
hue setup --agent # noninteractive version-2 JSONL events
|
|
112
|
+
hue setup --format human # explicit append-only terminal rendering
|
|
113
|
+
hue setup --origin http://127.0.0.1:PORT # isolated loopback tests only
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Hosted setup accepts HTTPS origins only. HTTP is accepted solely for `localhost`, `127.0.0.1` and
|
|
117
|
+
`[::1]` test origins. Origins with credentials, paths, queries or fragments are refused, and
|
|
118
|
+
redirects are never followed.
|
|
119
|
+
|
|
120
|
+
`--agent` never reads stdin or opens a browser. It emits exactly one terminal `run.completed` or
|
|
121
|
+
`run.failed` event and only a generic owner action. No setup mode prints a bearer claim URL or token.
|
|
122
|
+
The CLI saves the capability in an ignored owner-only local handoff and human Terminal mode opens
|
|
123
|
+
only that local file; the browser consumes the short-lived one-time capability. A repository-reading
|
|
124
|
+
agent can read local project files, so this boundary prevents durable transcript/log disclosure, not
|
|
125
|
+
a malicious local process. The browser owns the claim cookie; the CLI never reads it. After claim
|
|
126
|
+
completes, rerun `hue claim` or `hue status` to fetch credential generation 1, atomically replace the
|
|
127
|
+
locally managed key, verify the preserved original receipt and confirm the old key receives `401`.
|
|
128
|
+
The superseded key is retained only in private managed state until that check succeeds, so an
|
|
129
|
+
interruption does not lose the revocation check. Reconciliation does not replay the application
|
|
130
|
+
request or require a second one.
|
|
131
|
+
|
|
132
|
+
A pending handoff expires after at most ten minutes and is exchanged once for a distinct browser
|
|
133
|
+
session lasting at most thirty minutes; both deadlines are bounded by trial expiry. Status polling
|
|
134
|
+
never creates or rotates a handoff. Retrying or reopening a pending handoff keeps its original ID
|
|
135
|
+
and expiry; a consumed handoff with an active browser session remains intact. Only the owner can
|
|
136
|
+
explicitly replace an expired or lost handoff with `hue claim --restart`; this persists a new ID and
|
|
137
|
+
uses the observed predecessor for compare-and-swap. A conflict refreshes status once and stops.
|
|
138
|
+
At most 32 distinct handoff IDs exist per installation; `SETUP_HANDOFF_LIMIT` is terminal. Replacing a
|
|
139
|
+
handoff does not create a trial, reset quota or rerun business work.
|
|
140
|
+
|
|
141
|
+
## Local state and conflicts
|
|
142
|
+
|
|
143
|
+
Before its first network write, setup creates a lowercase UUIDv4 and a 32-byte random installation
|
|
144
|
+
secret. They live with the current telemetry credential in `.hue/installation-<origin-hash>.json`.
|
|
145
|
+
The file is added to `.gitignore`, written atomically with mode `0600`, and scoped to this exact
|
|
146
|
+
project and Hue origin. A pending browser capability is stored only in
|
|
147
|
+
`.hue/claim-handoff-<origin-hash>.html`, also ignored and mode `0600`; `.hue` uses mode `0700`.
|
|
148
|
+
Setup rejects symlinks, unsafe paths, oversized or invalid state, custom files at its managed config
|
|
149
|
+
paths, and unexpected edits to files it previously managed. It preserves unrelated `.gitignore`,
|
|
150
|
+
manifests, lockfiles and project files.
|
|
151
|
+
|
|
152
|
+
Secret-free checkpoints remain in the operating system's per-user state directory and support
|
|
153
|
+
interruption/resume. Claim capabilities never enter checkpoints or public events. Credential retries are idempotent; a lost
|
|
154
|
+
credential response is recovered by asking for the same generation. `SETUP_CHANGED` triggers one
|
|
155
|
+
status refresh. `SETUP_REVOKED`, expired/purged installations, custom credential conflicts and
|
|
156
|
+
exhausted lifetime quota fail closed instead of silently replacing an installation or key.
|
|
157
|
+
|
|
158
|
+
Commands serialize all origins for one canonical project and reload current state under that lock.
|
|
159
|
+
Every invocation re-detects current manifests, managers and managed blocks before mutation. Normal
|
|
160
|
+
interruptions release the lock; after a forcible process kill, the owner must inspect the stale
|
|
161
|
+
non-secret lock under canonical `/tmp` on Linux/macOS
|
|
162
|
+
(`hue-setup-locks-<uid>/<sha256(canonical-project-root)>`) and remove only that lock after confirming
|
|
163
|
+
no setup command remains active. Its location does not depend on `TMPDIR`, `HOME` or state-directory
|
|
164
|
+
overrides; project aliases and different Hue origins share ownership. Other operating systems fail
|
|
165
|
+
closed for automatic setup. Setup never breaks an unexplained lock automatically or retries
|
|
166
|
+
business work.
|
|
167
|
+
|
|
168
|
+
Both setup credential generations use exactly
|
|
169
|
+
`^hue_setup_(live|test)_setup-([a-f0-9]{24})_([A-Za-z0-9_-]{43})$`, with `keyId` equal to
|
|
170
|
+
`setup-` plus the token's 24-character identifier, fixed kind `anonymous_trial`, and the sole
|
|
171
|
+
capability `setup_telemetry_write`.
|
|
172
|
+
Normal project credentials and unknown token shapes are refused in setup responses and private
|
|
173
|
+
managed state. These credentials authorize metadata-only OTLP at `/api/v1/otlp/v1/traces` and exact
|
|
174
|
+
content-free receipt verification at `/api/v1/setup/traces/{traceId}/receipt`. They do not authorize
|
|
175
|
+
generic project, receipt, evaluation, log or browsing APIs. Python `0.2.2` can export with the setup
|
|
176
|
+
credential; the CLI verifies the dedicated setup receipt instead of Python's generic receipt helper.
|
|
177
|
+
|
|
178
|
+
Each command uses bounded timeouts and retries. Provisioning records at most five attempts per local
|
|
179
|
+
installation in an hour and makes at most two attempts in one invocation; the live service's stricter
|
|
180
|
+
per-network admission remains authoritative. Receipt polling honors the SDK's bounded deadline and
|
|
181
|
+
`Retry-After`. A missing or late receipt is reported as resumable and unverified, never as success.
|
|
182
|
+
|
|
183
|
+
The TypeScript event union is exported from `@hue-run/sdk/setup`; its JSON Schema is exported as
|
|
184
|
+
`@hue-run/sdk/setup-events.schema.json`. Event `run.*` names describe only CLI invocations, not Hue
|
|
185
|
+
Runs.
|
|
186
|
+
|
|
187
|
+
## Public machine events and actions
|
|
188
|
+
|
|
189
|
+
Events carry `contractVersion: 2`, a non-secret invocation ID, sequence and timestamp. The closed
|
|
190
|
+
event set is `run.started`, `project.detected`, `plan.ready`, `step.started`, `step.completed`,
|
|
191
|
+
`file.changed`, `diagnostic`, `privacy.notice`, `action.required`, `trial.created`,
|
|
192
|
+
`receipt.verified`, `claim.required`, `claim.completed`, `run.completed` and `run.failed`.
|
|
193
|
+
|
|
194
|
+
Event version 1 shipped in TypeScript `0.3.1` and `0.3.2`; its public claim URL, action set and receipt
|
|
195
|
+
step are incompatible with this flow. The v2 schema shipped in TypeScript `0.4.0` with identity
|
|
196
|
+
`https://hue.run/schemas/setup-events-v2.json`. Event version 2 is independent of Setup HTTP
|
|
197
|
+
`protocolVersion: 1` and the checkpoint format.
|
|
198
|
+
|
|
199
|
+
`privacy.notice` has only `privacyUrl`, `effectiveDate` and `securityUrl` beyond the event envelope,
|
|
200
|
+
with the literal values above. `receipt.verified` carries non-secret `receiptId`, `traceId` and
|
|
201
|
+
`source: "repository-http-boundary"`. It confirms the exercised application boundary and every
|
|
202
|
+
expected span, positive stored span count, empty missing IDs and absent input/output content.
|
|
203
|
+
It does not establish instrumentation of unexercised application paths. `claim.required` and
|
|
204
|
+
`claim.completed` carry only the non-secret `claimId`; neither contains a URL or capability.
|
|
205
|
+
|
|
206
|
+
The closed `action.required.action` set is:
|
|
207
|
+
|
|
208
|
+
| Action | Caller response |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `claim-project` | Defer account linkage to the project owner. |
|
|
211
|
+
| `configure` | Resolve the reported availability or managed-configuration issue. |
|
|
212
|
+
| `select-project` | Select one supported application package explicitly with `--project`. |
|
|
213
|
+
| `integrate-application` | Resolve the reported unsupported or ambiguous integration safely. |
|
|
214
|
+
| `run-instrumented-request` | Inspect the prior attempt and missing evidence; do not automatically replay business work. |
|
|
215
|
+
| `open-claim-handoff` | Ask the owner to run `hue claim` in an interactive local terminal. |
|
|
216
|
+
| `restart-claim-handoff` | Ask the owner to explicitly run `hue claim --restart` if recovery is needed. |
|
|
217
|
+
|
|
218
|
+
A coding agent follows the non-secret message and optional command, reports unverified steps, and
|
|
219
|
+
leaves browser account linkage for the human. It must not read or copy the private handoff into a
|
|
220
|
+
transcript. `run.completed` with `action_required` is a successful pause, not proof that all setup
|
|
221
|
+
steps completed. `claim.completed` requires reconciliation and the retained evidence checks.
|
|
222
|
+
|
|
223
|
+
## Staging/live acceptance runner
|
|
224
|
+
|
|
225
|
+
Fern owns hosted acceptance and its private browser/inbox orchestration. SDK verification builds and
|
|
226
|
+
retains the exact tarball; it does not spend a live admission or claim hosted success. The Fern job
|
|
227
|
+
first accepts the reviewed PR archive, then after merge downloads the exact `publish=false` Release
|
|
228
|
+
SDK archive without repacking and repeats acceptance. It uses two persistent installations and at
|
|
229
|
+
most four provisioning admissions. Local compatibility fixtures do not spend hosted admissions.
|
|
230
|
+
|
|
231
|
+
The local/manual diagnostic runner accepts an already prepared supported application fixture. Its `--language`
|
|
232
|
+
scaffolding does not create such an application, and its human-output option does not allocate a PTY.
|
|
233
|
+
It records CLI outcomes, not independent original-handler span binding or server evidence. It always
|
|
234
|
+
exits nonzero: exit `2` means the diagnostic finished but acceptance remains unverified; malformed
|
|
235
|
+
events, private output and child failures also fail closed. Even a valid version-2 application receipt
|
|
236
|
+
event followed by `ready` cannot make this runner an acceptance gate. Do not use it to authorize a
|
|
237
|
+
release, count it as hosted acceptance, or suppress its exit code in a release gate:
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifacts/typescript
|
|
241
|
+
# Set project to an existing supported fixture; use the same directory on resume.
|
|
242
|
+
project=/absolute/path/to/supported-fixture
|
|
243
|
+
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
244
|
+
--archive .artifacts/typescript/hue-run-sdk-0.4.1.tgz \
|
|
245
|
+
--origin https://STAGING_ORIGIN \
|
|
246
|
+
--project "$project" --command setup \
|
|
247
|
+
--evidence .context/setup-staging-before-claim.json
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
After the approved backend is available, run `hue claim` from an interactive owner terminal to open
|
|
251
|
+
the private local handoff and finish the real browser claim, then reconcile the same project:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
node packages/sdk-typescript/scripts/verify-setup-live.mjs \
|
|
255
|
+
--archive .artifacts/typescript/hue-run-sdk-0.4.1.tgz \
|
|
256
|
+
--origin https://STAGING_ORIGIN \
|
|
257
|
+
--project "$project" --command claim \
|
|
258
|
+
--evidence .context/setup-staging-after-claim.json
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Repeat from an existing supported Flask/uv fixture when that diagnostic pass is budgeted. Evidence files explicitly
|
|
262
|
+
record `purpose: "diagnostic-only"`, `independentlyVerifiedApplication: false` and `accepted: false`,
|
|
263
|
+
with the archive hash, bounded event names and terminal outcome—never the claim capability, installation proof,
|
|
264
|
+
telemetry key, cookie, verification URL, project path or trace/span IDs. The runner installs and
|
|
265
|
+
executes the exact tarball; it does not establish hosted acceptance, registry publication or
|
|
266
|
+
production activation.
|
|
267
|
+
|
|
268
|
+
Hosted acceptance separately executes installed Express/npm, Express/Bun and Flask/uv fixture
|
|
269
|
+
coverage, with Agent JSONL and a real default-mode PTY. It independently verifies exact stored IDs,
|
|
270
|
+
the privacy disclosure, one handler invocation, verified-account adoption and owner transfer,
|
|
271
|
+
preserved project/data, replacement receipt access and old-key `401`. Browser orchestration opens
|
|
272
|
+
only the private local file and produces no secret-bearing screenshots, traces, logs or artifacts.
|
|
273
|
+
Publication under a candidate dist-tag, registry acceptance, production activation, promotion of
|
|
274
|
+
the same version to `latest`, and clean-project literal `@latest` smoke remain separate gates; see
|
|
275
|
+
[RELEASING.md](../../RELEASING.md).
|
package/ENVIRONMENTS.md
CHANGED
|
@@ -161,6 +161,16 @@ await tools.refund_charge!.execute({ charge_id: "ch_2" });
|
|
|
161
161
|
await client.finishRun(run.id, { idempotencyKey: randomUUID(), status: "completed" });
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
Each bound call is an ordinary `hue.tool` span. When the catalog names an MCP server, the span
|
|
165
|
+
also carries `mcp.server.name`. Wrap any MCP client the same way, using `serverInfo` from
|
|
166
|
+
`initialize` — this is not specific to Hue-hosted Gmail or Slack:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
await hue.tool(name, args, () => client.callTool({ name, arguments: args }), {
|
|
170
|
+
mcp: client.getServerVersion(),
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
164
174
|
An observation with `status: "error"` is a recorded world answer, not a transport exception.
|
|
165
175
|
Run mutations retry with stable invocation/idempotency identities. Registry writes do not retry
|
|
166
176
|
automatically because identity creation and publication have no request key.
|
package/README.md
CHANGED
|
@@ -65,6 +65,18 @@ HTTPS is required except for loopback HTTP or the explicit
|
|
|
65
65
|
[`allowInsecureHttp`](#local-development-without-a-hue-account) opt-in. Redirects are refused for both
|
|
66
66
|
project checks and exports.
|
|
67
67
|
|
|
68
|
+
The setup CLI shipped in TypeScript `0.4.0` also prepares the `hue` executable. The unreleased
|
|
69
|
+
`npx --yes @hue-run/sdk@latest setup --agent` path supports Express with npm, Express with Bun, and
|
|
70
|
+
Flask with uv in one application package with an unambiguous entrypoint and existing GET route. It
|
|
71
|
+
installs the runtime, wires the application, makes one request and verifies that request's exact
|
|
72
|
+
trace/span receipt. Unsupported or ambiguous repositories receive a structured action. Technical
|
|
73
|
+
preflight checks availability and presents the published [privacy notice](https://hue.run/privacy)
|
|
74
|
+
and [security information](https://trust.hue.run/) before telemetry. Anonymous trials last 24 hours
|
|
75
|
+
and are limited to 100 traces, 1,000 spans and 2 MiB. A private owner-only browser handoff supports
|
|
76
|
+
account linkage; the original request evidence is retained and business work is never replayed on
|
|
77
|
+
claim. Setup never enables content capture or creates a Scenario, Hue Run, evaluation, source capture
|
|
78
|
+
or remote execution. See the [setup CLI contract](./CLI.md) for the supported shapes and release gates.
|
|
79
|
+
|
|
68
80
|
`checkConnection()` rejects with `HueConnectionError`: its fixed message is safe to log, `status`
|
|
69
81
|
carries the HTTP status when Hue answered, and `cause` carries the underlying network, timeout or
|
|
70
82
|
parsing error. `serviceVersion` and `resourceAttributes` (for example
|
|
@@ -120,7 +132,10 @@ options come after the callback and also accept `name`, `sessionId`, `userId`, `
|
|
|
120
132
|
nonnegative integer `gen_ai.usage.input_tokens` / `output_tokens`; other values are omitted and
|
|
121
133
|
counted as instrumentation failures. Unknown usage stays absent. `hue.tool(name, input, execute)`
|
|
122
134
|
creates an `execute_tool {name}` span with `gen_ai.tool.name`, arguments and result; an optional
|
|
123
|
-
fourth argument `{ callId }` records the provider's tool call id as `gen_ai.tool.call.id`.
|
|
135
|
+
fourth argument `{ callId }` records the provider's tool call id as `gen_ai.tool.call.id`. When the
|
|
136
|
+
tool came from an MCP server, pass `{ mcp: client.getServerVersion() }` (the MCP `initialize`
|
|
137
|
+
`serverInfo`) to record `mcp.server.name` and `mcp.server.version` so a generic verb such as
|
|
138
|
+
`get_thread` is attributed to that server. Content
|
|
124
139
|
helpers (`setInput`, `setOutput`, `tool` arguments and results, `recordMessages`,
|
|
125
140
|
`SpanOptions.input`) accept any value and encode plain JSON data (`JsonValue`) at runtime; a value
|
|
126
141
|
that is not JSON, such as a `Date` or a class instance, is omitted with an instrumentation failure
|
|
@@ -412,8 +427,9 @@ Scorer deferral shipped in TypeScript `0.3.1`. Only built-ins
|
|
|
412
427
|
and bound `local_code` callbacks run here; other pins remain pending for their authorized executor.
|
|
413
428
|
See [scorer execution](EVALUATIONS.md#hosted-and-manual-scorer-pins).
|
|
414
429
|
|
|
415
|
-
The [setup CLI](CLI.md) is a resumable local inspection core
|
|
416
|
-
|
|
430
|
+
The published [setup CLI](CLI.md) is a resumable local inspection core; the unreleased `0.4.0`
|
|
431
|
+
candidate adds the bounded application onboarding flow described above. Existing customers connect
|
|
432
|
+
their agents with `runLocalAgent()`; setup does not register workers or launch Scenarios.
|
|
417
433
|
|
|
418
434
|
## Managed targets
|
|
419
435
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Context, type Span, type Tracer } from "@opentelemetry/api";
|
|
2
2
|
import { HueTransport } from "./transport.js";
|
|
3
|
-
import type { ExportReport, FlushableLoggerProvider, FlushableTracerProvider, HueOptions, HueSpan, ModelOptions, ProjectConnection, SpanOptions, VerifyTraceOptions, TraceVerification, SafeLifecycleOptions, SafeLifecycleResult } from "./types.js";
|
|
3
|
+
import type { ExportReport, FlushableLoggerProvider, FlushableTracerProvider, HueOptions, HueSpan, ModelOptions, ProjectConnection, SpanOptions, ToolOptions, VerifyTraceOptions, TraceVerification, SafeLifecycleOptions, SafeLifecycleResult } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Attach mode: the application owns its OpenTelemetry providers and passes the transport whose
|
|
6
6
|
* processors it attached to them. The client flushes these providers but never shuts them down.
|
|
@@ -73,11 +73,11 @@ export declare class HueClient {
|
|
|
73
73
|
* are recorded as `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result` when `captureContent`
|
|
74
74
|
* is true; values that are not JSON-encodable are omitted with an instrumentation failure.
|
|
75
75
|
* `options.callId` is recorded as `gen_ai.tool.call.id`, like the Python `call_id=` keyword.
|
|
76
|
+
* `options.mcp` records the MCP `initialize` `serverInfo` as `mcp.server.name` /
|
|
77
|
+
* `mcp.server.version` so a generic tool name can be attributed to the server that
|
|
78
|
+
* handled it. Pass `client.getServerVersion()`.
|
|
76
79
|
*/
|
|
77
|
-
tool<T>(name: string, input: unknown, execute: () => Promise<T> | T, options?:
|
|
78
|
-
/** Provider-issued identifier of this tool call, recorded as `gen_ai.tool.call.id`. */
|
|
79
|
-
callId?: string;
|
|
80
|
-
}): Promise<T>;
|
|
80
|
+
tool<T>(name: string, input: unknown, execute: () => Promise<T> | T, options?: ToolOptions): Promise<T>;
|
|
81
81
|
/**
|
|
82
82
|
* Runs `callback` inside a GenAI client span for one direct provider call, named
|
|
83
83
|
* `{operation} {model}` unless `options.name` is given and carrying `gen_ai.operation.name`,
|
package/dist/client.js
CHANGED
|
@@ -298,20 +298,27 @@ export class HueClient {
|
|
|
298
298
|
* are recorded as `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result` when `captureContent`
|
|
299
299
|
* is true; values that are not JSON-encodable are omitted with an instrumentation failure.
|
|
300
300
|
* `options.callId` is recorded as `gen_ai.tool.call.id`, like the Python `call_id=` keyword.
|
|
301
|
+
* `options.mcp` records the MCP `initialize` `serverInfo` as `mcp.server.name` /
|
|
302
|
+
* `mcp.server.version` so a generic tool name can be attributed to the server that
|
|
303
|
+
* handled it. Pass `client.getServerVersion()`.
|
|
301
304
|
*/
|
|
302
305
|
async tool(name, input, execute, options = {}) {
|
|
303
306
|
const attributes = {
|
|
304
307
|
"gen_ai.operation.name": "execute_tool",
|
|
305
308
|
"gen_ai.tool.name": name,
|
|
306
309
|
};
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
const stamp = (key, value) => {
|
|
311
|
+
if (value === undefined)
|
|
312
|
+
return;
|
|
313
|
+
// A blank or non-string label is omitted and counted; the tool call itself still runs.
|
|
314
|
+
if (isLabel(value))
|
|
315
|
+
attributes[key] = value;
|
|
312
316
|
else if (this.enabled && !this.closed)
|
|
313
317
|
this.transport.instrumentationFailure();
|
|
314
|
-
}
|
|
318
|
+
};
|
|
319
|
+
stamp("gen_ai.tool.call.id", options.callId);
|
|
320
|
+
stamp("mcp.server.name", options.mcp?.name);
|
|
321
|
+
stamp("mcp.server.version", options.mcp?.version);
|
|
315
322
|
return this.withSpan(`execute_tool ${name}`, async ({ span }) => {
|
|
316
323
|
this.setContent(span, "gen_ai.tool.call.arguments", input);
|
|
317
324
|
const result = await execute();
|
|
@@ -26,5 +26,10 @@ export interface BindEnvironmentToolsOptions {
|
|
|
26
26
|
/** Optional durable invocation-ID factory for caller-owned resume state. */
|
|
27
27
|
invocationId?(action: string): string;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Binds a run's generated catalog to plain local callables without changing the agent
|
|
31
|
+
* framework. Each call is recorded through {@link HueClient.tool}. Catalog entries that
|
|
32
|
+
* include `mcp` stamp `mcp.server.name` / `mcp.server.version` so a generic verb is
|
|
33
|
+
* attributed to that MCP server.
|
|
34
|
+
*/
|
|
30
35
|
export declare function bindEnvironmentTools(options: BindEnvironmentToolsOptions): Record<string, EnvironmentTool>;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Binds a run's generated catalog to plain local callables without changing the agent
|
|
4
|
+
* framework. Each call is recorded through {@link HueClient.tool}. Catalog entries that
|
|
5
|
+
* include `mcp` stamp `mcp.server.name` / `mcp.server.version` so a generic verb is
|
|
6
|
+
* attributed to that MCP server.
|
|
7
|
+
*/
|
|
3
8
|
export function bindEnvironmentTools(options) {
|
|
4
9
|
const tools = {};
|
|
5
10
|
for (const action of options.run.actions) {
|
|
@@ -16,6 +21,7 @@ export function bindEnvironmentTools(options) {
|
|
|
16
21
|
})).observation;
|
|
17
22
|
return options.hue.tool(action.name, args, execute, {
|
|
18
23
|
parentContext: options.parentContext,
|
|
24
|
+
...(action.mcp === undefined ? {} : { mcp: action.mcp }),
|
|
19
25
|
});
|
|
20
26
|
},
|
|
21
27
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JsonValue } from "../types.js";
|
|
1
|
+
import type { JsonValue, McpServerInfo } from "../types.js";
|
|
2
2
|
export type { JsonValue } from "../types.js";
|
|
3
3
|
/** Human-readable identity used when creating an environment. */
|
|
4
4
|
export interface EnvironmentIdentity {
|
|
@@ -251,6 +251,11 @@ export interface ActionDefinition {
|
|
|
251
251
|
description?: string;
|
|
252
252
|
/** Generated input contract. */
|
|
253
253
|
inputSchema: ActionSchema;
|
|
254
|
+
/**
|
|
255
|
+
* MCP `initialize` identity when this action is served by one MCP surface.
|
|
256
|
+
* Bound environment tools record it on the tool span as `mcp.server.name`.
|
|
257
|
+
*/
|
|
258
|
+
mcp?: McpServerInfo;
|
|
254
259
|
}
|
|
255
260
|
/** Recorded answer returned by the simulated world. */
|
|
256
261
|
export interface Observation {
|
package/dist/receipt.js
CHANGED
|
@@ -31,7 +31,7 @@ function validId(value, length) {
|
|
|
31
31
|
function record(value) {
|
|
32
32
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
33
33
|
}
|
|
34
|
-
function parseReceipt(value, traceId, expected, origin) {
|
|
34
|
+
function parseReceipt(value, traceId, expected, origin, setup = false) {
|
|
35
35
|
if (!record(value) ||
|
|
36
36
|
value.traceId !== traceId ||
|
|
37
37
|
!Number.isSafeInteger(value.spanCount) ||
|
|
@@ -52,6 +52,24 @@ function parseReceipt(value, traceId, expected, origin) {
|
|
|
52
52
|
}
|
|
53
53
|
if (traceUrl.origin !== origin || traceUrl.username || traceUrl.password)
|
|
54
54
|
invalidResponse();
|
|
55
|
+
if (setup) {
|
|
56
|
+
const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u;
|
|
57
|
+
if (traceUrl.href !== value.traceUrl ||
|
|
58
|
+
traceUrl.hash ||
|
|
59
|
+
!traceUrl.pathname.startsWith("/traces/") ||
|
|
60
|
+
!uuid.test(traceUrl.pathname.slice(8)))
|
|
61
|
+
invalidResponse();
|
|
62
|
+
const queryNames = [];
|
|
63
|
+
traceUrl.searchParams.forEach((value, name) => {
|
|
64
|
+
if (!["projectId", "organizationId"].includes(name) ||
|
|
65
|
+
queryNames.includes(name) ||
|
|
66
|
+
!uuid.test(value))
|
|
67
|
+
invalidResponse();
|
|
68
|
+
queryNames.push(name);
|
|
69
|
+
});
|
|
70
|
+
if (queryNames.length !== 2)
|
|
71
|
+
invalidResponse();
|
|
72
|
+
}
|
|
55
73
|
const matched = value.matchedSpanIds, missing = value.missingSpanIds;
|
|
56
74
|
if (!Array.isArray(matched) ||
|
|
57
75
|
!Array.isArray(missing) ||
|
|
@@ -128,6 +146,13 @@ async function pause(milliseconds, signal) {
|
|
|
128
146
|
}
|
|
129
147
|
/** Observe persisted evidence after the application and its exporter have finished. */
|
|
130
148
|
export async function verifyTrace(connection, traceId, options = {}) {
|
|
149
|
+
return verifyTraceAtPath(connection, traceId, options, false, fetch);
|
|
150
|
+
}
|
|
151
|
+
/** @internal Dedicated receipt path for setup credentials; ordinary clients stay unchanged. */
|
|
152
|
+
export async function verifySetupTrace(connection, traceId, options, fetcher, signal) {
|
|
153
|
+
return verifyTraceAtPath(connection, traceId, options, true, fetcher, signal);
|
|
154
|
+
}
|
|
155
|
+
async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, signal) {
|
|
131
156
|
if (!validId(traceId, 32))
|
|
132
157
|
throw new TypeError("traceId must be a nonzero lowercase 32-character OpenTelemetry trace ID");
|
|
133
158
|
if (options === null || typeof options !== "object" || Array.isArray(options))
|
|
@@ -148,27 +173,28 @@ export async function verifyTrace(connection, traceId, options = {}) {
|
|
|
148
173
|
throw new TypeError("timeoutMillis must be greater than zero and at most 60000");
|
|
149
174
|
// Snapshot caller arrays so concurrent mutation cannot alter the verification criteria.
|
|
150
175
|
const expectedIds = [...expected], requiredFields = [...required];
|
|
151
|
-
const url = new URL(`/api/v1/traces/${traceId}/receipt`, connection.baseUrl);
|
|
176
|
+
const url = new URL(`/api/v1/${setup ? "setup/" : ""}traces/${traceId}/receipt`, connection.baseUrl);
|
|
152
177
|
for (const id of expectedIds)
|
|
153
178
|
url.searchParams.append("expectedSpanId", id);
|
|
154
179
|
const controller = new AbortController();
|
|
155
180
|
const deadline = performance.now() + timeout;
|
|
156
181
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
182
|
+
const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
157
183
|
let receipt = null;
|
|
158
184
|
let delay = 250;
|
|
159
185
|
try {
|
|
160
|
-
while (!
|
|
161
|
-
const response = await
|
|
186
|
+
while (!requestSignal.aborted && performance.now() < deadline) {
|
|
187
|
+
const response = await fetcher(url, {
|
|
162
188
|
headers: { Authorization: `Bearer ${connection.apiKey}`, Accept: "application/json" },
|
|
163
189
|
redirect: "manual",
|
|
164
190
|
credentials: "omit",
|
|
165
191
|
cache: "no-store",
|
|
166
|
-
signal:
|
|
192
|
+
signal: requestSignal,
|
|
167
193
|
});
|
|
168
194
|
let retryAfter = 0;
|
|
169
195
|
if (response.status === 200) {
|
|
170
|
-
receipt = parseReceipt(await readJson(response), traceId, expectedIds, url.origin);
|
|
171
|
-
if (!
|
|
196
|
+
receipt = parseReceipt(await readJson(response), traceId, expectedIds, url.origin, setup);
|
|
197
|
+
if (!requestSignal.aborted &&
|
|
172
198
|
performance.now() < deadline &&
|
|
173
199
|
receipt.missingSpanIds.length === 0 &&
|
|
174
200
|
requiredFields.every((field) => receipt.fields[field]))
|
|
@@ -195,7 +221,7 @@ export async function verifyTrace(connection, traceId, options = {}) {
|
|
|
195
221
|
if (remaining <= 0)
|
|
196
222
|
break;
|
|
197
223
|
const wait = Math.max(delay, retryAfter);
|
|
198
|
-
await pause(Math.min(wait, remaining),
|
|
224
|
+
await pause(Math.min(wait, remaining), requestSignal);
|
|
199
225
|
// A truncated backoff exhausts this call even if a timer wakes just early.
|
|
200
226
|
if (wait >= remaining)
|
|
201
227
|
break;
|
|
@@ -203,6 +229,8 @@ export async function verifyTrace(connection, traceId, options = {}) {
|
|
|
203
229
|
}
|
|
204
230
|
}
|
|
205
231
|
catch (error) {
|
|
232
|
+
if (signal?.aborted)
|
|
233
|
+
throw new Error("Setup interrupted");
|
|
206
234
|
if (!controller.signal.aborted && performance.now() < deadline) {
|
|
207
235
|
if (error instanceof HueTraceVerificationError)
|
|
208
236
|
throw error;
|