@huynle/hub 0.6.1 → 0.7.2
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/CHANGELOG.md +870 -0
- package/Dockerfile +33 -0
- package/README.md +1007 -66
- package/package.json +47 -2
- package/packages/claude-code/package.json +12 -0
- package/packages/claude-code/src/bridge.ts +79 -0
- package/packages/claude-code/src/daemon.ts +339 -0
- package/packages/claude-code/src/hook-input.ts +44 -0
- package/packages/claude-code/src/ids.ts +27 -0
- package/packages/claude-code/src/index.ts +18 -0
- package/packages/claude-code/src/launcher.ts +265 -0
- package/packages/claude-code/src/main.ts +127 -0
- package/packages/claude-code/src/mcp.ts +109 -0
- package/packages/claude-code/src/paths.ts +34 -0
- package/packages/claude-code/src/plugin.ts +99 -0
- package/packages/claude-code/src/references.ts +51 -0
- package/packages/claude-code/src/sessions.ts +600 -0
- package/packages/claude-code/src/tools.ts +66 -0
- package/packages/claude-code/src/wire.ts +71 -0
- package/packages/claude-code/src/wrapper.ts +156 -0
- package/packages/cli/dist/hub-cli.js +21647 -0
- package/packages/cli/package.json +11 -0
- package/packages/cli/src/acting.ts +70 -0
- package/packages/cli/src/args.ts +91 -0
- package/packages/cli/src/bootstrap/adapters/archive-stream.ts +276 -0
- package/packages/cli/src/bootstrap/adapters/claude/README.md +63 -0
- package/packages/cli/src/bootstrap/adapters/claude/adapter.ts +250 -0
- package/packages/cli/src/bootstrap/adapters/claude/index.ts +42 -0
- package/packages/cli/src/bootstrap/adapters/claude/node.ts +99 -0
- package/packages/cli/src/bootstrap/adapters/claude/openpgp.ts +221 -0
- package/packages/cli/src/bootstrap/adapters/claude/ports.ts +44 -0
- package/packages/cli/src/bootstrap/adapters/claude/source.ts +160 -0
- package/packages/cli/src/bootstrap/adapters/claude/verify.ts +165 -0
- package/packages/cli/src/bootstrap/adapters/codex/archive.ts +103 -0
- package/packages/cli/src/bootstrap/adapters/codex/index.ts +60 -0
- package/packages/cli/src/bootstrap/adapters/codex/plan.ts +129 -0
- package/packages/cli/src/bootstrap/adapters/codex/sigstore.ts +298 -0
- package/packages/cli/src/bootstrap/adapters/codex/source.ts +102 -0
- package/packages/cli/src/bootstrap/adapters/codex/verify.ts +298 -0
- package/packages/cli/src/bootstrap/adapters/inflate.ts +433 -0
- package/packages/cli/src/bootstrap/adapters/node-services/control.ts +392 -0
- package/packages/cli/src/bootstrap/adapters/node-services/index.ts +23 -0
- package/packages/cli/src/bootstrap/adapters/node-services/launch.ts +223 -0
- package/packages/cli/src/bootstrap/adapters/node-services/plan.ts +222 -0
- package/packages/cli/src/bootstrap/adapters/node-services/render.ts +183 -0
- package/packages/cli/src/bootstrap/adapters/node-services/validate.ts +197 -0
- package/packages/cli/src/bootstrap/adapters/opencode/README.md +204 -0
- package/packages/cli/src/bootstrap/adapters/opencode/apply.ts +205 -0
- package/packages/cli/src/bootstrap/adapters/opencode/archive.ts +91 -0
- package/packages/cli/src/bootstrap/adapters/opencode/config.ts +212 -0
- package/packages/cli/src/bootstrap/adapters/opencode/index.ts +81 -0
- package/packages/cli/src/bootstrap/adapters/opencode/plan.ts +266 -0
- package/packages/cli/src/bootstrap/adapters/opencode/verify.ts +208 -0
- package/packages/cli/src/bootstrap/adapters/readiness.ts +19 -0
- package/packages/cli/src/bootstrap/ansible/index.ts +2 -0
- package/packages/cli/src/bootstrap/ansible/render.ts +279 -0
- package/packages/cli/src/bootstrap/contracts/actions.ts +470 -0
- package/packages/cli/src/bootstrap/contracts/artifact.ts +323 -0
- package/packages/cli/src/bootstrap/contracts/canonical.ts +54 -0
- package/packages/cli/src/bootstrap/contracts/credentials.ts +108 -0
- package/packages/cli/src/bootstrap/contracts/discovery.ts +215 -0
- package/packages/cli/src/bootstrap/contracts/envelope.ts +143 -0
- package/packages/cli/src/bootstrap/contracts/exec.ts +74 -0
- package/packages/cli/src/bootstrap/contracts/harness.ts +202 -0
- package/packages/cli/src/bootstrap/contracts/index.ts +28 -0
- package/packages/cli/src/bootstrap/contracts/journal.ts +198 -0
- package/packages/cli/src/bootstrap/contracts/paths.ts +91 -0
- package/packages/cli/src/bootstrap/contracts/plan.ts +155 -0
- package/packages/cli/src/bootstrap/contracts/platform.ts +62 -0
- package/packages/cli/src/bootstrap/contracts/preview.ts +190 -0
- package/packages/cli/src/bootstrap/contracts/profile.ts +105 -0
- package/packages/cli/src/bootstrap/contracts/secrets.ts +52 -0
- package/packages/cli/src/bootstrap/contracts/validate.ts +212 -0
- package/packages/cli/src/bootstrap/discovery/discover.ts +146 -0
- package/packages/cli/src/bootstrap/discovery/executables.ts +134 -0
- package/packages/cli/src/bootstrap/discovery/host.ts +96 -0
- package/packages/cli/src/bootstrap/discovery/index.ts +6 -0
- package/packages/cli/src/bootstrap/discovery/node-probe.ts +97 -0
- package/packages/cli/src/bootstrap/discovery/probe.ts +41 -0
- package/packages/cli/src/bootstrap/discovery/util.ts +13 -0
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +226 -0
- package/packages/cli/src/bootstrap/enrollment/index.ts +7 -0
- package/packages/cli/src/bootstrap/enrollment/policy.ts +54 -0
- package/packages/cli/src/bootstrap/enrollment/result.ts +63 -0
- package/packages/cli/src/bootstrap/enrollment/status.ts +119 -0
- package/packages/cli/src/bootstrap/enrollment/store.ts +173 -0
- package/packages/cli/src/bootstrap/preview/index.ts +5 -0
- package/packages/cli/src/bootstrap/preview/links.ts +31 -0
- package/packages/cli/src/bootstrap/preview/plan.ts +242 -0
- package/packages/cli/src/bootstrap/preview/preview.ts +104 -0
- package/packages/cli/src/bootstrap/preview/render.ts +30 -0
- package/packages/cli/src/bootstrap/profiles/audit.ts +87 -0
- package/packages/cli/src/bootstrap/profiles/catalog.ts +49 -0
- package/packages/cli/src/bootstrap/profiles/desired.ts +96 -0
- package/packages/cli/src/bootstrap/profiles/drift.ts +286 -0
- package/packages/cli/src/bootstrap/profiles/history.ts +263 -0
- package/packages/cli/src/bootstrap/profiles/index.ts +11 -0
- package/packages/cli/src/bootstrap/profiles/upgrade.ts +276 -0
- package/packages/cli/src/bootstrap/profiles/versions.ts +50 -0
- package/packages/cli/src/bootstrap/release/README.md +115 -0
- package/packages/cli/src/bootstrap/release/assets.ts +39 -0
- package/packages/cli/src/bootstrap/release/cli.ts +332 -0
- package/packages/cli/src/bootstrap/release/harness-pins.json +94 -0
- package/packages/cli/src/bootstrap/release/manifest.ts +118 -0
- package/packages/cli/src/bootstrap/release/pins.ts +192 -0
- package/packages/cli/src/bootstrap/release/text-assets.d.ts +9 -0
- package/packages/cli/src/bootstrap/release/trust-root.json +13 -0
- package/packages/cli/src/bootstrap/release/trust-root.ts +39 -0
- package/packages/cli/src/bootstrap/release/verify.ts +88 -0
- package/packages/cli/src/bootstrap/transaction/builtin.ts +312 -0
- package/packages/cli/src/bootstrap/transaction/engine.ts +994 -0
- package/packages/cli/src/bootstrap/transaction/executor.ts +112 -0
- package/packages/cli/src/bootstrap/transaction/fs.ts +194 -0
- package/packages/cli/src/bootstrap/transaction/index.ts +12 -0
- package/packages/cli/src/bootstrap/transaction/json-merge.ts +403 -0
- package/packages/cli/src/bootstrap/transaction/lock.ts +132 -0
- package/packages/cli/src/bootstrap/transaction/resolve.ts +48 -0
- package/packages/cli/src/bootstrap/transaction/result.ts +60 -0
- package/packages/cli/src/bootstrap/transaction/state.ts +88 -0
- package/packages/cli/src/bootstrap/transaction/store.ts +189 -0
- package/packages/cli/src/bootstrap/transaction/stream.ts +254 -0
- package/packages/cli/src/cli.ts +814 -0
- package/packages/cli/src/credentials.ts +75 -0
- package/packages/cli/src/decisions.ts +106 -0
- package/packages/cli/src/format.ts +185 -0
- package/packages/cli/src/grants.ts +105 -0
- package/packages/cli/src/http.ts +86 -0
- package/packages/cli/src/launches.ts +157 -0
- package/packages/cli/src/login.ts +160 -0
- package/packages/cli/src/main.ts +63 -0
- package/packages/cli/src/mcp.ts +879 -0
- package/packages/cli/src/messaging.ts +108 -0
- package/packages/cli/src/node.ts +279 -0
- package/packages/cli/src/openshell.ts +166 -0
- package/packages/cli/src/setup/commands.ts +809 -0
- package/packages/cli/src/setup/contributors.ts +171 -0
- package/packages/cli/src/setup/dispatch.ts +74 -0
- package/packages/cli/src/setup/launch-driver.ts +109 -0
- package/packages/cli/src/setup/runtime.ts +456 -0
- package/packages/cli/src/setup/spoke-probe.ts +120 -0
- package/packages/cli/src/setup/state.ts +244 -0
- package/packages/cli/src/setup/user-bin.ts +39 -0
- package/packages/cli/src/spoke-services.ts +109 -0
- package/packages/cli/src/watch.ts +28 -0
- package/packages/codex/package.json +12 -0
- package/packages/codex/src/app-server.ts +44 -0
- package/packages/codex/src/ids.ts +28 -0
- package/packages/codex/src/index.ts +11 -0
- package/packages/codex/src/jsonrpc.ts +168 -0
- package/packages/codex/src/main.ts +123 -0
- package/packages/codex/src/paths.ts +65 -0
- package/packages/codex/src/sessions.ts +578 -0
- package/packages/codex/src/spoke.ts +252 -0
- package/packages/codex/src/stdio.ts +101 -0
- package/packages/codex/src/websocket.ts +206 -0
- package/packages/codex/src/wrapper.ts +104 -0
- package/packages/node/package.json +12 -0
- package/packages/node/src/config.ts +184 -0
- package/packages/node/src/connection.ts +283 -0
- package/packages/node/src/daemon.ts +281 -0
- package/packages/node/src/git.ts +107 -0
- package/packages/node/src/harnesses.ts +95 -0
- package/packages/node/src/host.ts +319 -0
- package/packages/node/src/index.ts +17 -0
- package/packages/node/src/main.ts +11 -0
- package/packages/node/src/openshell.ts +73 -0
- package/packages/node/src/run.ts +106 -0
- package/packages/preset/package.json +12 -0
- package/packages/preset/presets/aider.json +8 -0
- package/packages/preset/presets/goose.json +8 -0
- package/packages/preset/src/index.ts +2 -0
- package/packages/preset/src/main.ts +195 -0
- package/packages/preset/src/presets.ts +87 -0
- package/packages/preset/src/spoke.ts +251 -0
- package/packages/preset/src/tmux.ts +160 -0
- package/packages/protocol/package.json +12 -0
- package/packages/protocol/src/harness-events.ts +207 -0
- package/packages/protocol/src/index.ts +1215 -0
- package/packages/protocol/src/launch.ts +172 -0
- package/packages/protocol/src/node.ts +409 -0
- package/packages/protocol/src/preset.ts +144 -0
- package/packages/protocol/src/webhook.ts +62 -0
- package/packages/protocol/src/wire.ts +54 -0
- package/packages/runtime-openshell/image/Dockerfile +36 -0
- package/packages/runtime-openshell/image/README.md +78 -0
- package/packages/runtime-openshell/image/build.ts +203 -0
- package/packages/runtime-openshell/image/entry.sh +163 -0
- package/packages/runtime-openshell/image/image.ts +119 -0
- package/packages/runtime-openshell/image/start.sh +24 -0
- package/packages/runtime-openshell/package.json +12 -0
- package/packages/runtime-openshell/src/cli.ts +584 -0
- package/packages/runtime-openshell/src/index.ts +6 -0
- package/packages/runtime-openshell/src/policy.ts +155 -0
- package/packages/runtime-openshell/src/profiles.ts +181 -0
- package/packages/runtime-openshell/src/runtime.ts +475 -0
- package/packages/runtime-sdk/package.json +12 -0
- package/packages/runtime-sdk/src/index.ts +87 -0
- package/packages/sdk/package.json +12 -0
- package/packages/sdk/src/address.ts +25 -0
- package/packages/sdk/src/client.ts +553 -0
- package/packages/sdk/src/envelope.ts +108 -0
- package/packages/sdk/src/errors.ts +107 -0
- package/packages/sdk/src/index.ts +10 -0
- package/packages/sdk/src/sse.ts +70 -0
- package/packages/sdk/src/types.ts +517 -0
- package/packages/sdk/src/webhook.ts +102 -0
- package/packages/spoke-sdk/package.json +12 -0
- package/packages/spoke-sdk/src/activity.ts +3 -0
- package/packages/spoke-sdk/src/connection.ts +295 -0
- package/packages/spoke-sdk/src/delivery.ts +119 -0
- package/packages/spoke-sdk/src/enroll.ts +101 -0
- package/packages/spoke-sdk/src/harness-hello.ts +79 -0
- package/packages/spoke-sdk/src/harness-spoke.ts +263 -0
- package/packages/spoke-sdk/src/index.ts +12 -0
- package/packages/spoke-sdk/src/prompt-refs.ts +154 -0
- package/packages/spoke-sdk/src/prompt-source.ts +8 -0
- package/packages/spoke-sdk/src/session-ids.ts +49 -0
- package/packages/spoke-sdk/src/session-tokens.ts +78 -0
- package/scripts/hub-claude-code.service +34 -0
- package/scripts/hub-codex-spoke.service +34 -0
- package/scripts/hub-node.service +35 -0
- package/scripts/io.github.huynle.hub-claude-code.plist +53 -0
- package/scripts/io.github.huynle.hub-codex-spoke.plist +53 -0
- package/scripts/io.github.huynle.hub-node.plist +52 -0
- package/src/central/actions.ts +233 -0
- package/src/central/agent/acting-routes.ts +133 -0
- package/src/central/agent/acting-switch.ts +93 -0
- package/src/central/agent/acting.ts +599 -0
- package/src/central/agent/actor.ts +193 -0
- package/src/central/agent/app-tokens.ts +152 -0
- package/src/central/agent/archive-inactive.ts +66 -0
- package/src/central/agent/audit.ts +185 -0
- package/src/central/agent/decisions.ts +530 -0
- package/src/central/agent/delivery.ts +678 -0
- package/src/central/agent/directory-routes.ts +328 -0
- package/src/central/agent/directory.ts +473 -0
- package/src/central/agent/envelope.ts +51 -0
- package/src/central/agent/errors.ts +74 -0
- package/src/central/agent/escalation.ts +219 -0
- package/src/central/agent/event-cursor.ts +49 -0
- package/src/central/agent/event-writers.ts +181 -0
- package/src/central/agent/events-routes.ts +148 -0
- package/src/central/agent/events.ts +277 -0
- package/src/central/agent/grant-requests.ts +221 -0
- package/src/central/agent/grant-routes.ts +70 -0
- package/src/central/agent/grants.ts +235 -0
- package/src/central/agent/launch-engine.ts +1025 -0
- package/src/central/agent/launch-routes.ts +252 -0
- package/src/central/agent/launch-spec.ts +289 -0
- package/src/central/agent/launch-store.ts +264 -0
- package/src/central/agent/launch-tokens.ts +206 -0
- package/src/central/agent/message-limits.ts +123 -0
- package/src/central/agent/message-routes.ts +385 -0
- package/src/central/agent/messages.ts +442 -0
- package/src/central/agent/names.ts +86 -0
- package/src/central/agent/placement.ts +419 -0
- package/src/central/agent/policy.ts +378 -0
- package/src/central/agent/read-routes.ts +385 -0
- package/src/central/agent/routes.ts +281 -0
- package/src/central/agent/session-tokens.ts +227 -0
- package/src/central/agent/versioned.ts +26 -0
- package/src/central/agent/watch.ts +112 -0
- package/src/central/agent/webhook-routes.ts +86 -0
- package/src/central/agent/webhooks.ts +493 -0
- package/src/central/agent-data.ts +177 -0
- package/src/central/auth.ts +350 -121
- package/src/central/backend/file-state.ts +240 -0
- package/src/central/backend/index.ts +116 -0
- package/src/central/backend/memory-bus.ts +93 -0
- package/src/central/backend/memory-catalog.ts +90 -0
- package/src/central/backend/memory-counters.ts +102 -0
- package/src/central/backend/memory-events.ts +45 -0
- package/src/central/backend/memory-kv.ts +38 -0
- package/src/central/backend/memory-state.ts +77 -0
- package/src/central/backend/postgres-migrate.ts +100 -0
- package/src/central/backend/postgres-state.ts +197 -0
- package/src/central/backend/types.ts +246 -0
- package/src/central/backend/valkey-bus.ts +425 -0
- package/src/central/backend/valkey-catalog.ts +190 -0
- package/src/central/backend/valkey-client.ts +36 -0
- package/src/central/backend/valkey-counters.ts +38 -0
- package/src/central/backend/valkey-events.ts +168 -0
- package/src/central/backend/valkey-kv.ts +54 -0
- package/src/central/backend/valkey-locks.ts +92 -0
- package/src/central/cluster/directory.ts +141 -0
- package/src/central/cluster/feed.ts +84 -0
- package/src/central/cluster/leader.ts +232 -0
- package/src/central/cluster/live.ts +151 -0
- package/src/central/cluster/recommend.ts +59 -0
- package/src/central/cluster/rpc.ts +150 -0
- package/src/central/cluster/view.ts +232 -0
- package/src/central/config.ts +197 -3
- package/src/central/done.ts +96 -0
- package/src/central/events-log.ts +100 -0
- package/src/central/http.ts +1149 -170
- package/src/central/import.ts +318 -0
- package/src/central/main.ts +211 -12
- package/src/central/mcp-http.ts +116 -0
- package/src/central/nodes.ts +314 -0
- package/src/central/oidc.ts +34 -14
- package/src/central/push-target.ts +118 -18
- package/src/central/push.ts +256 -82
- package/src/central/registry.ts +902 -92
- package/src/central/say-stream.ts +128 -0
- package/src/central/session-token-push.ts +233 -0
- package/src/central/speech/openai.ts +21 -14
- package/src/central/speech/openrouter.ts +27 -10
- package/src/central/speech/provider.ts +54 -0
- package/src/central/speech/service.ts +22 -7
- package/src/central/spoke-ws.ts +522 -44
- package/src/central/store-shared.ts +518 -0
- package/src/central/store.ts +625 -189
- package/src/central/token-keys.ts +165 -0
- package/src/config.ts +118 -7
- package/src/index.ts +195 -43
- package/src/protocol.ts +3 -650
- package/src/rpc.ts +27 -2
- package/src/spoke/client.ts +3 -272
- package/src/spoke/conversation.ts +16 -12
- package/src/spoke/deliver.ts +75 -0
- package/src/spoke/enroll.ts +2 -79
- package/src/spoke/env-inject.ts +160 -0
- package/src/spoke/hub-rpc.ts +115 -0
- package/src/spoke/hub-tool.ts +409 -0
- package/src/spoke/launch-source.ts +22 -0
- package/src/spoke/launchers.ts +174 -0
- package/src/spoke/locations.ts +3 -2
- package/src/spoke/node-roots.ts +61 -0
- package/src/spoke/prompt-refs.ts +116 -0
- package/src/spoke/session-tokens.ts +2 -0
- package/src/spoke/spoke.ts +465 -28
- package/src/spoke/taskboard.ts +243 -0
- package/src/spoke/timeline.ts +184 -6
- package/src/spoke/tracker.ts +79 -4
- package/src/status.ts +203 -10
- package/src/tui-commands.ts +13 -6
- package/src/tui-messaging.ts +93 -0
- package/src/tui.ts +15 -0
- package/web/dist/apple-touch-icon.png +0 -0
- package/web/dist/assets/highlight-y4rJE6Tt.js +5 -0
- package/web/dist/assets/index-DWnMhXk9.js +20 -0
- package/web/dist/assets/index-DmUXEnw9.css +1 -0
- package/web/dist/assets/ort-wasm-simd-threaded-CxTQ5xH-.wasm +0 -0
- package/web/dist/assets/silero-worker-C8-8vLVS.js +11 -0
- package/web/dist/assets/silero_vad-Dt9-bTDn.onnx +0 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/icon-192.png +0 -0
- package/web/dist/icon-512.png +0 -0
- package/web/dist/icon-maskable-512.png +0 -0
- package/web/dist/icon.svg +5 -5
- package/web/dist/index.html +17 -4
- package/web/dist/manifest.webmanifest +2 -2
- package/web/dist/sw.js +79 -15
- package/web/dist/assets/index-DClBvfLL.css +0 -1
- package/web/dist/assets/index-Djo_yF0a.js +0 -12
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
// The shapes of the hub's /api/agent answers (src/central/agent/*). Fields are those the hub sends today; clients
|
|
2
|
+
// should ignore unknown ones, and later hubs may add fields.
|
|
3
|
+
|
|
4
|
+
/** The caller, as whoami and status (`you`) report it. */
|
|
5
|
+
export type Actor =
|
|
6
|
+
| { readonly kind: "user"; readonly userID: string; readonly deviceID: string; readonly via: "cookie" | "device-token" }
|
|
7
|
+
| {
|
|
8
|
+
readonly kind: "session"
|
|
9
|
+
readonly userID: string
|
|
10
|
+
readonly machineID: string
|
|
11
|
+
readonly sessionID: string
|
|
12
|
+
/** Which of the session's tokens: its shell token, or the act token only the hub tool has (missing: shell). */
|
|
13
|
+
readonly via?: "shell" | "act"
|
|
14
|
+
}
|
|
15
|
+
| { readonly kind: "app"; readonly userID: string; readonly tokenID: string; readonly scopes: readonly string[]; readonly label?: string }
|
|
16
|
+
|
|
17
|
+
export interface UserView {
|
|
18
|
+
readonly id: string
|
|
19
|
+
readonly name?: string
|
|
20
|
+
readonly email?: string
|
|
21
|
+
readonly role?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Whoami {
|
|
25
|
+
readonly actor: Actor
|
|
26
|
+
readonly user: UserView
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Blocker {
|
|
30
|
+
readonly kind: string
|
|
31
|
+
readonly id?: string
|
|
32
|
+
readonly summary: string
|
|
33
|
+
readonly sessionID?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One session in status and sessions answers. */
|
|
37
|
+
export interface SessionView {
|
|
38
|
+
readonly id: string
|
|
39
|
+
readonly name?: string
|
|
40
|
+
readonly roles?: readonly string[]
|
|
41
|
+
readonly title?: string
|
|
42
|
+
readonly state: string
|
|
43
|
+
readonly harness?: string
|
|
44
|
+
readonly parentID?: string
|
|
45
|
+
readonly busySince?: number
|
|
46
|
+
readonly now?: string
|
|
47
|
+
readonly blockers?: readonly Blocker[]
|
|
48
|
+
readonly children?: number
|
|
49
|
+
readonly labels?: Readonly<Record<string, string>>
|
|
50
|
+
/** In sessions answers. */
|
|
51
|
+
readonly machineID?: string
|
|
52
|
+
readonly project?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AttentionItem {
|
|
56
|
+
readonly kind: string
|
|
57
|
+
readonly sessionID: string
|
|
58
|
+
readonly name?: string
|
|
59
|
+
readonly id?: string
|
|
60
|
+
readonly summary: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** GET /api/agent/status: machines → projects → sessions, and what needs attention. */
|
|
64
|
+
export interface Status {
|
|
65
|
+
readonly machines: readonly {
|
|
66
|
+
readonly id: string
|
|
67
|
+
readonly name?: string
|
|
68
|
+
readonly online: boolean
|
|
69
|
+
readonly harnesses?: readonly string[]
|
|
70
|
+
readonly projects: readonly { readonly root: string; readonly name: string; readonly sessions: readonly SessionView[] }[]
|
|
71
|
+
}[]
|
|
72
|
+
readonly attention: readonly AttentionItem[]
|
|
73
|
+
readonly you?: Actor
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** GET /api/agent/sessions/:addr: one session in detail. */
|
|
77
|
+
export interface SessionDetail extends SessionView {
|
|
78
|
+
readonly online?: boolean
|
|
79
|
+
readonly directory?: string
|
|
80
|
+
readonly lastSeen?: number
|
|
81
|
+
readonly summary?: {
|
|
82
|
+
readonly branch?: string
|
|
83
|
+
readonly context?: { readonly used: number; readonly limit?: number; readonly percent?: number; readonly model?: string }
|
|
84
|
+
readonly changes?: { readonly additions: number; readonly deletions: number; readonly totalFiles: number }
|
|
85
|
+
readonly mcp?: readonly { readonly name: string; readonly status: string }[]
|
|
86
|
+
}
|
|
87
|
+
readonly unsupported?: readonly { readonly feature?: string; readonly harness?: string }[]
|
|
88
|
+
readonly failed?: readonly { readonly feature?: string }[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** One timeline row (newest first). */
|
|
92
|
+
export interface LogRow {
|
|
93
|
+
readonly kind: string
|
|
94
|
+
readonly text?: string
|
|
95
|
+
readonly name?: string
|
|
96
|
+
readonly state?: string
|
|
97
|
+
readonly title?: string
|
|
98
|
+
readonly at?: number
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface RoleView {
|
|
102
|
+
readonly role: string
|
|
103
|
+
readonly project?: string
|
|
104
|
+
readonly generation?: number
|
|
105
|
+
readonly exclusive?: boolean
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** One session in directory answers (who). */
|
|
109
|
+
export interface DirectoryEntry {
|
|
110
|
+
readonly sessionID: string
|
|
111
|
+
readonly name?: string
|
|
112
|
+
readonly aliases?: readonly string[]
|
|
113
|
+
readonly machineID?: string
|
|
114
|
+
readonly parentID?: string
|
|
115
|
+
readonly project?: string
|
|
116
|
+
readonly title?: string
|
|
117
|
+
readonly state?: string
|
|
118
|
+
readonly online: boolean
|
|
119
|
+
readonly lastSeen?: number
|
|
120
|
+
readonly archived?: boolean
|
|
121
|
+
readonly roles: readonly RoleView[]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** One compact event of GET /api/agent/events (no free text). Unknown types are valid: ignore them. */
|
|
125
|
+
export interface AgentEvent {
|
|
126
|
+
readonly id: string
|
|
127
|
+
readonly at: number
|
|
128
|
+
readonly type: string
|
|
129
|
+
readonly subject: Readonly<Record<string, string>>
|
|
130
|
+
readonly data: Readonly<Record<string, string | number | boolean>>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A page of GET /api/agent/events: pass `cursor` to the next call. `more`: records remain unread. `reset`: the
|
|
135
|
+
* cursor was no longer covered (expired, trimmed, hub restarted); re-read the `refresh` snapshots, then go on.
|
|
136
|
+
*/
|
|
137
|
+
export interface EventsPage {
|
|
138
|
+
readonly events: AgentEvent[]
|
|
139
|
+
readonly cursor: string
|
|
140
|
+
readonly more: boolean
|
|
141
|
+
readonly reset: boolean
|
|
142
|
+
readonly refresh?: string[]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface Archived {
|
|
146
|
+
readonly sessionID: string
|
|
147
|
+
readonly archived: boolean
|
|
148
|
+
readonly changed: boolean
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface Pinned {
|
|
152
|
+
readonly sessionID: string
|
|
153
|
+
readonly pinned: boolean
|
|
154
|
+
readonly changed: boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type ArchiveSkipReason = "active" | "pinned" | "open" | "archived" | "not_found"
|
|
158
|
+
export interface ArchiveInactivePreview { readonly candidates: readonly string[]; readonly previewToken: string; readonly expiresAt: number }
|
|
159
|
+
export interface ArchiveInactiveResult { readonly changed: readonly string[]; readonly skipped: readonly { readonly sessionID: string; readonly reason: ArchiveSkipReason }[] }
|
|
160
|
+
|
|
161
|
+
export interface Named {
|
|
162
|
+
readonly sessionID: string
|
|
163
|
+
readonly name: string
|
|
164
|
+
/** The previous name, kept as an alias for 24 h. */
|
|
165
|
+
readonly alias?: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface RoleClaim extends RoleView {
|
|
169
|
+
readonly sessionID: string
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface RoleRelease {
|
|
173
|
+
readonly sessionID: string
|
|
174
|
+
readonly role: string
|
|
175
|
+
readonly project?: string
|
|
176
|
+
readonly released?: boolean
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface AppTokenView {
|
|
180
|
+
readonly id: string
|
|
181
|
+
readonly label?: string
|
|
182
|
+
readonly scopes: readonly string[]
|
|
183
|
+
readonly created?: number
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** POST /api/agent/app-tokens: the token is shown once. */
|
|
187
|
+
export interface CreatedAppToken extends AppTokenView {
|
|
188
|
+
readonly token: string
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Who sends or receives a message: a session, the user (`@me`), or an app token. */
|
|
192
|
+
export type Party =
|
|
193
|
+
| { readonly kind: "session"; readonly sessionID: string; readonly name?: string; readonly harness?: string; readonly machine?: string }
|
|
194
|
+
| { readonly kind: "user" }
|
|
195
|
+
| { readonly kind: "app"; readonly tokenID: string; readonly label?: string }
|
|
196
|
+
|
|
197
|
+
export type MessageKind = "msg" | "ask" | "reply" | "decline"
|
|
198
|
+
/** `sent` for msg/reply/decline; asks are open until answered, declined or expired. */
|
|
199
|
+
export type MessageState = "sent" | "open" | "answered" | "declined" | "expired"
|
|
200
|
+
/** How a message reached its recipient session. */
|
|
201
|
+
export type MessageDelivery = "steer" | "turn" | "queued" | "retained" | "pending"
|
|
202
|
+
|
|
203
|
+
/** A stored agent message. */
|
|
204
|
+
export interface AgentMessage {
|
|
205
|
+
readonly id: string
|
|
206
|
+
readonly kind: MessageKind
|
|
207
|
+
/** The authenticated sender. */
|
|
208
|
+
readonly from: Party
|
|
209
|
+
/** The address as the sender wrote it. */
|
|
210
|
+
readonly to: string
|
|
211
|
+
/** The party it reached. */
|
|
212
|
+
readonly recipient: Party
|
|
213
|
+
/** The role holder generation it reached (role addresses). */
|
|
214
|
+
readonly generation?: number
|
|
215
|
+
readonly text: string
|
|
216
|
+
readonly replyTo?: string
|
|
217
|
+
readonly state: MessageState
|
|
218
|
+
readonly at: number
|
|
219
|
+
readonly hop: number
|
|
220
|
+
readonly expiresAt?: number
|
|
221
|
+
readonly delivery?: MessageDelivery
|
|
222
|
+
readonly deliveredAt?: number
|
|
223
|
+
/** The reply or decline that closed an ask. */
|
|
224
|
+
readonly answer?: { readonly id: string; readonly at: number }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** POST /api/agent/msg and /ask: the stored message and how it was delivered (none: stored only). */
|
|
228
|
+
export interface Sent {
|
|
229
|
+
readonly message: AgentMessage
|
|
230
|
+
readonly delivery?: MessageDelivery
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** POST /api/agent/msg or /ask with all: one per session. */
|
|
234
|
+
export interface SentAll {
|
|
235
|
+
readonly sent: readonly Sent[]
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** POST /api/agent/ask (after any wait) and GET /api/agent/messages/:id: the message and, once closed, its answer. */
|
|
239
|
+
export interface Asked extends Sent {
|
|
240
|
+
readonly answer?: AgentMessage
|
|
241
|
+
/** An ask to @me: the Decision it opened. */
|
|
242
|
+
readonly decision?: Decision
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** One choice a Decision offers; onTimeout may pick only a safe one. */
|
|
246
|
+
export interface DecisionOption {
|
|
247
|
+
readonly id: string
|
|
248
|
+
readonly label: string
|
|
249
|
+
readonly safe?: boolean
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type DecisionUrgency = "low" | "normal" | "high"
|
|
253
|
+
/** confirm: any of the user's devices; tap: only the signed-in console. */
|
|
254
|
+
export type DecisionTier = "confirm" | "tap"
|
|
255
|
+
/** closed: ended without an answer because what it asked about went away (answered elsewhere, ended). */
|
|
256
|
+
export type DecisionState = "open" | "answered" | "expired" | "closed"
|
|
257
|
+
|
|
258
|
+
/** What an ask to @me opens (design §12.1); only the user answers it. */
|
|
259
|
+
export interface Decision {
|
|
260
|
+
readonly id: string
|
|
261
|
+
/** The ask (to @me) carrying it. */
|
|
262
|
+
readonly messageID: string
|
|
263
|
+
readonly from: Party
|
|
264
|
+
/** Session IDs it concerns. */
|
|
265
|
+
readonly about: readonly string[]
|
|
266
|
+
readonly sessionID?: string
|
|
267
|
+
readonly question: string
|
|
268
|
+
readonly options: readonly DecisionOption[]
|
|
269
|
+
readonly recommendation?: { readonly option: string; readonly why?: string }
|
|
270
|
+
readonly urgency: DecisionUrgency
|
|
271
|
+
readonly tier: DecisionTier
|
|
272
|
+
readonly deadline?: number
|
|
273
|
+
readonly onTimeout?: string
|
|
274
|
+
readonly state: DecisionState
|
|
275
|
+
readonly at: number
|
|
276
|
+
readonly expiresAt: number
|
|
277
|
+
readonly escalatedAt?: number
|
|
278
|
+
readonly answer?: { readonly option?: string; readonly text?: string; readonly answeredBy: "user" | "timeout"; readonly at: number; readonly deviceID?: string }
|
|
279
|
+
/** Why it closed without an answer (state closed). */
|
|
280
|
+
readonly closed?: { readonly at: number; readonly reason: string }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** POST /api/decisions/:id/answer. */
|
|
284
|
+
export interface DecisionAnswered {
|
|
285
|
+
readonly decision: Decision
|
|
286
|
+
/** The user's reply to the asker. */
|
|
287
|
+
readonly reply?: AgentMessage
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** POST /api/agent/reply and /decline: the answer and the message it answered. */
|
|
291
|
+
export interface Replied extends Sent {
|
|
292
|
+
readonly original: AgentMessage
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** GET /api/agent/inbox. */
|
|
296
|
+
export interface Inbox {
|
|
297
|
+
/** Open asks to the caller, newest first. */
|
|
298
|
+
readonly asks: readonly AgentMessage[]
|
|
299
|
+
/** The caller's own open asks. */
|
|
300
|
+
readonly waiting: readonly AgentMessage[]
|
|
301
|
+
/** Recent messages to the caller. */
|
|
302
|
+
readonly messages: readonly AgentMessage[]
|
|
303
|
+
/** Session IDs the caller muted. */
|
|
304
|
+
readonly muted: readonly string[]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** One server-sent event of GET /api/agent/watch (data is the raw text, JSON for hub events). */
|
|
308
|
+
export interface WatchEvent {
|
|
309
|
+
readonly event: string
|
|
310
|
+
readonly data: string
|
|
311
|
+
/** The stream's cursor at this event (GET /api/agent/watch?cursor= resumes after it). */
|
|
312
|
+
readonly id?: string
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Acting (POST /api/agent/sessions/:addr/{approve,deny,answer,prompt,stop}; src/central/agent/acting.ts).
|
|
316
|
+
|
|
317
|
+
/** OpenCode's reply to a permission request. */
|
|
318
|
+
export type PermissionDecision = "once" | "always" | "reject"
|
|
319
|
+
|
|
320
|
+
/** How an agent's prompt enters a busy session (the spoke picks when unset). */
|
|
321
|
+
export type PromptDelivery = "queue" | "steer"
|
|
322
|
+
|
|
323
|
+
/** An answer to a form (question): field key → value. */
|
|
324
|
+
export type FormAnswer = Readonly<Record<string, string | number | boolean | readonly string[]>>
|
|
325
|
+
|
|
326
|
+
/** Why an act verb needs the user (the needs_user envelope's `evaluation`). */
|
|
327
|
+
export interface Evaluation {
|
|
328
|
+
readonly verdict: "outside"
|
|
329
|
+
readonly reason: string
|
|
330
|
+
/** Set by the hub: confirm (any of the user's devices) or tap (the signed-in console). */
|
|
331
|
+
readonly tier: DecisionTier
|
|
332
|
+
/** The usable grants it was evaluated against. */
|
|
333
|
+
readonly grants: readonly string[]
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface Approved {
|
|
337
|
+
readonly sessionID: string
|
|
338
|
+
readonly requestID: string
|
|
339
|
+
readonly decision: PermissionDecision
|
|
340
|
+
/** The grant it acted under (agents). */
|
|
341
|
+
readonly grant?: string
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface Denied {
|
|
345
|
+
readonly sessionID: string
|
|
346
|
+
readonly requestID: string
|
|
347
|
+
readonly grant?: string
|
|
348
|
+
/** With a why: whether the harness got the reason. */
|
|
349
|
+
readonly reasonSent?: boolean
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface FormAnswered {
|
|
353
|
+
readonly sessionID: string
|
|
354
|
+
readonly formID: string
|
|
355
|
+
readonly grant?: string
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export interface Stopped {
|
|
359
|
+
readonly sessionID: string
|
|
360
|
+
readonly grant?: string
|
|
361
|
+
/** The spoke's answer. */
|
|
362
|
+
readonly result: unknown
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** An agent's prompt (delivered under its provenance), or the user's (steered into the tree, or a new turn). */
|
|
366
|
+
export type Prompted =
|
|
367
|
+
| { readonly sessionID: string; readonly messageID: string; readonly delivered: string; readonly inboxID?: string; readonly grant: string }
|
|
368
|
+
| { readonly sessionID: string; readonly mode: "steer"; readonly inboxID: string }
|
|
369
|
+
| { readonly sessionID: string; readonly mode: "turn" }
|
|
370
|
+
|
|
371
|
+
// Grants (/api/agent/grants; src/central/agent/grants.ts): created, edited and revoked only by the user.
|
|
372
|
+
|
|
373
|
+
export type GrantVerb = "approve" | "deny" | "answer" | "prompt" | "stop" | "roles" | "launch"
|
|
374
|
+
export type GrantState = "active" | "expired" | "revoked"
|
|
375
|
+
|
|
376
|
+
/** What the user grants (create and update bodies). */
|
|
377
|
+
export interface GrantSpec {
|
|
378
|
+
/** A session (ses_… or @name, pinned at creation), a reserved role (@@orchestrator…), or app:<token id>. */
|
|
379
|
+
readonly holder: string
|
|
380
|
+
readonly scope: { readonly projects: readonly string[] }
|
|
381
|
+
readonly allow: readonly GrantVerb[]
|
|
382
|
+
readonly permissions?: { readonly allow?: readonly string[]; readonly never?: readonly string[] }
|
|
383
|
+
readonly allowAlways?: boolean
|
|
384
|
+
/** Seconds, or 30m, 8h, 2d (1 minute to 30 days). */
|
|
385
|
+
readonly expires?: number | string
|
|
386
|
+
readonly maxActionsPerHour?: number
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface Grant {
|
|
390
|
+
readonly id: string
|
|
391
|
+
readonly holder: string
|
|
392
|
+
readonly holderSessionID?: string
|
|
393
|
+
readonly scope: { readonly projects: readonly string[] }
|
|
394
|
+
readonly allow: readonly GrantVerb[]
|
|
395
|
+
readonly permissions: { readonly allow: readonly string[]; readonly never: readonly string[] }
|
|
396
|
+
readonly allowAlways: boolean
|
|
397
|
+
readonly maxActionsPerHour: number
|
|
398
|
+
readonly created: number
|
|
399
|
+
readonly expiresAt: number
|
|
400
|
+
readonly updated?: number
|
|
401
|
+
readonly revokedAt?: number
|
|
402
|
+
readonly state: GrantState
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** POST /api/agent/grants/requests: what an agent asks the user for (the holder defaults to the requester). */
|
|
406
|
+
export type GrantRequestSpec = Omit<GrantSpec, "holder"> & {
|
|
407
|
+
/** Only the requester itself (ses_…, its @name, app:<own token id>) or a reserved @@role the session holds. */
|
|
408
|
+
readonly holder?: string
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** POST /api/agent/grants/requests: the ask to @me and the tap Decision it opened (approve creates the grant). */
|
|
412
|
+
export interface GrantRequested {
|
|
413
|
+
readonly message: AgentMessage
|
|
414
|
+
readonly decision: Decision
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Launches (design §6.1; packages/protocol/src/launch.ts is the wire source of truth, kept in step by the tests).
|
|
418
|
+
|
|
419
|
+
export type LaunchState = "queued" | "placing" | "provisioning" | "running" | "ended"
|
|
420
|
+
export type LaunchOutcome = "idle" | "needs_user" | "failed" | "timeout" | "stopped" | "lost"
|
|
421
|
+
export type WorkspaceMode = "worktree" | "checkout" | "clone"
|
|
422
|
+
export type RuntimeKind = "host" | "openshell"
|
|
423
|
+
export type PlacementMode = "require" | "prefer"
|
|
424
|
+
export type LaunchEndWhen = "idle" | "asked" | "never"
|
|
425
|
+
export type CleanupPolicy = "keep" | "remove"
|
|
426
|
+
|
|
427
|
+
/** POST /api/agent/launches: what to start, where, and when it ends. Unset fields take the hub's defaults. */
|
|
428
|
+
export interface LaunchSpec {
|
|
429
|
+
/** Idempotency key: the same key and body return the same launch (another body: invalid). */
|
|
430
|
+
readonly key?: string
|
|
431
|
+
/** The session's @name (without the @). */
|
|
432
|
+
readonly name?: string
|
|
433
|
+
readonly roles?: readonly string[]
|
|
434
|
+
/** Default opencode. */
|
|
435
|
+
readonly harness?: string
|
|
436
|
+
readonly agent?: string
|
|
437
|
+
/** "provider/model". */
|
|
438
|
+
readonly model?: string
|
|
439
|
+
readonly workspace: {
|
|
440
|
+
/** Absolute path on the node: the repository (worktree, checkout) or where the clone goes. */
|
|
441
|
+
readonly repo: string
|
|
442
|
+
/** Default worktree (`<repo>/.worktrees/<branch>`; needs branch). */
|
|
443
|
+
readonly mode?: WorkspaceMode
|
|
444
|
+
readonly branch?: string
|
|
445
|
+
readonly base?: string
|
|
446
|
+
/** https or ssh; required for clone. */
|
|
447
|
+
readonly remote?: string
|
|
448
|
+
}
|
|
449
|
+
readonly runtime?: { readonly kind: RuntimeKind; readonly profile?: string }
|
|
450
|
+
readonly placement?: { readonly labels?: Readonly<Record<string, string>>; readonly machine?: string; readonly mode?: PlacementMode }
|
|
451
|
+
readonly prompt?: string
|
|
452
|
+
/** Free-form; never interpreted by the hub (find launches and sessions by them). */
|
|
453
|
+
readonly labels?: Readonly<Record<string, string>>
|
|
454
|
+
readonly end?: { readonly when?: LaunchEndWhen; readonly timeout?: string; readonly cleanup?: CleanupPolicy }
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export interface LaunchStep {
|
|
458
|
+
readonly state: LaunchState
|
|
459
|
+
readonly reason: string
|
|
460
|
+
readonly at: number
|
|
461
|
+
readonly outcome?: LaunchOutcome
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export interface LaunchArtifacts {
|
|
465
|
+
readonly branch?: string
|
|
466
|
+
readonly base?: string
|
|
467
|
+
readonly head?: string
|
|
468
|
+
readonly commits?: number
|
|
469
|
+
readonly dirty?: boolean
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export interface WebhookView {
|
|
473
|
+
readonly id: string
|
|
474
|
+
readonly url: string
|
|
475
|
+
readonly events: readonly string[]
|
|
476
|
+
readonly by: string
|
|
477
|
+
readonly label?: string
|
|
478
|
+
readonly created: number
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export interface WebhookDelivery {
|
|
482
|
+
readonly id: string
|
|
483
|
+
readonly event: string
|
|
484
|
+
readonly type: string
|
|
485
|
+
readonly state: "pending" | "delivered" | "failed"
|
|
486
|
+
readonly attempts: number
|
|
487
|
+
readonly created: number
|
|
488
|
+
readonly nextAt?: number
|
|
489
|
+
readonly last?: { readonly at: number; readonly status?: number; readonly error?: string }
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** A launch as the hub reports it (the spec with its defaults filled in). */
|
|
493
|
+
export interface LaunchView {
|
|
494
|
+
readonly id: string
|
|
495
|
+
readonly state: LaunchState
|
|
496
|
+
readonly reason: string
|
|
497
|
+
readonly spec: LaunchSpec
|
|
498
|
+
/** ms epoch */
|
|
499
|
+
readonly created: number
|
|
500
|
+
/** When it entered its state. */
|
|
501
|
+
readonly updated: number
|
|
502
|
+
/** Who launched it: `user`, `app:<token id>` or a session ID. */
|
|
503
|
+
readonly by?: string
|
|
504
|
+
readonly machineID?: string
|
|
505
|
+
readonly directory?: string
|
|
506
|
+
readonly sessionID?: string
|
|
507
|
+
readonly name?: string
|
|
508
|
+
readonly outcome?: LaunchOutcome
|
|
509
|
+
readonly artifacts?: LaunchArtifacts
|
|
510
|
+
readonly history: readonly LaunchStep[]
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** POST /api/agent/launches: the launch, and whether it is new (false: the same key again). */
|
|
514
|
+
export interface LaunchCreated {
|
|
515
|
+
readonly launch: LaunchView
|
|
516
|
+
readonly created: boolean
|
|
517
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Webhook verification for receivers (design §6.1; decision znbg8xa5 #8). The hub signs every webhook POST with
|
|
2
|
+
// Hub-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
|
|
3
|
+
// (format: packages/protocol/src/webhook.ts). verifyWebhook checks it against the receiver's secret(s) in constant
|
|
4
|
+
// time and refuses timestamps outside the tolerance (replay). Verify the raw body bytes as received, before parsing.
|
|
5
|
+
import { createHmac, timingSafeEqual } from "node:crypto"
|
|
6
|
+
|
|
7
|
+
// The format, as in packages/protocol/src/webhook.ts (the SDK imports nothing outside its package; the tests check
|
|
8
|
+
// the two agree).
|
|
9
|
+
export const WEBHOOK_SIGNATURE_HEADER = "Hub-Signature"
|
|
10
|
+
export const WEBHOOK_EVENT_HEADER = "Hub-Event"
|
|
11
|
+
export const WEBHOOK_DELIVERY_HEADER = "Hub-Delivery"
|
|
12
|
+
/** Largest gap, either way, between a signature's timestamp and the receiver's clock. */
|
|
13
|
+
export const WEBHOOK_TOLERANCE_SECONDS = 300
|
|
14
|
+
const SCHEME = "v1"
|
|
15
|
+
const ALGORITHM = "sha256"
|
|
16
|
+
const HEADER_MAX = 2048
|
|
17
|
+
const HEX_SHA256 = /^[0-9a-f]{64}$/
|
|
18
|
+
|
|
19
|
+
/** A Hub-Signature header's timestamp (unix seconds) and v1 signatures, or undefined when it is malformed. */
|
|
20
|
+
function parseSignature(value: string): { t: number; v1: string[] } | undefined {
|
|
21
|
+
if (value.length > HEADER_MAX) return undefined
|
|
22
|
+
let t: number | undefined
|
|
23
|
+
const v1: string[] = []
|
|
24
|
+
for (const part of value.split(",")) {
|
|
25
|
+
const eq = part.indexOf("=")
|
|
26
|
+
if (eq < 0) return undefined
|
|
27
|
+
const key = part.slice(0, eq).trim()
|
|
28
|
+
const v = part.slice(eq + 1).trim()
|
|
29
|
+
if (key === "t") {
|
|
30
|
+
if (t !== undefined || !/^\d{1,12}$/.test(v)) return undefined
|
|
31
|
+
t = Number(v)
|
|
32
|
+
} else if (key === SCHEME) {
|
|
33
|
+
if (!HEX_SHA256.test(v)) return undefined
|
|
34
|
+
v1.push(v)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return t !== undefined && v1.length > 0 ? { t, v1 } : undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Request headers: a Headers, or a plain object such as node's IncomingMessage.headers (any key case). */
|
|
41
|
+
export type WebhookHeaders = Headers | Readonly<Record<string, string | readonly string[] | undefined>>
|
|
42
|
+
|
|
43
|
+
export interface VerifyWebhookInput {
|
|
44
|
+
/** The raw request body, exactly as received. */
|
|
45
|
+
readonly body: string | Uint8Array
|
|
46
|
+
/** The Hub-Signature header value (or give `headers`). */
|
|
47
|
+
readonly signature?: string | null
|
|
48
|
+
/** The request's headers: Hub-Signature, and Hub-Event / Hub-Delivery for the answer. */
|
|
49
|
+
readonly headers?: WebhookHeaders
|
|
50
|
+
/** The registration's secret; several while one rotates (any may match). */
|
|
51
|
+
readonly secret: string | readonly string[]
|
|
52
|
+
/** The receiver's clock in ms (default Date.now()). */
|
|
53
|
+
readonly now?: number
|
|
54
|
+
/** Seconds a timestamp may be off either way (default WEBHOOK_TOLERANCE_SECONDS). */
|
|
55
|
+
readonly tolerance?: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type WebhookVerification =
|
|
59
|
+
| { readonly ok: true; readonly timestamp: number; readonly event?: string; readonly delivery?: string }
|
|
60
|
+
| { readonly ok: false; readonly reason: "missing" | "malformed" | "stale" | "mismatch" }
|
|
61
|
+
|
|
62
|
+
function header(headers: WebhookHeaders | undefined, name: string): string | undefined {
|
|
63
|
+
if (!headers) return undefined
|
|
64
|
+
if (typeof (headers as Headers).get === "function") return (headers as Headers).get(name) ?? undefined
|
|
65
|
+
const wanted = name.toLowerCase()
|
|
66
|
+
for (const [key, value] of Object.entries(headers as Record<string, string | readonly string[] | undefined>)) {
|
|
67
|
+
if (key.toLowerCase() !== wanted || value === undefined) continue
|
|
68
|
+
return typeof value === "string" ? value : value[0]
|
|
69
|
+
}
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const text = (body: string | Uint8Array) => (typeof body === "string" ? body : new TextDecoder().decode(body))
|
|
74
|
+
|
|
75
|
+
const hmac = (secret: string, t: number, body: string): Buffer => createHmac(ALGORITHM, secret).update(`${t}.${body}`).digest()
|
|
76
|
+
|
|
77
|
+
/** Whether a Hub-Signature matches the body under one of the secrets, and is fresh. */
|
|
78
|
+
export function verifyWebhook(input: VerifyWebhookInput): WebhookVerification {
|
|
79
|
+
const raw = input.signature ?? header(input.headers, WEBHOOK_SIGNATURE_HEADER)
|
|
80
|
+
if (raw === undefined || raw === null || raw === "") return { ok: false, reason: "missing" }
|
|
81
|
+
const parsed = parseSignature(raw)
|
|
82
|
+
if (!parsed) return { ok: false, reason: "malformed" }
|
|
83
|
+
if (Math.abs((input.now ?? Date.now()) / 1000 - parsed.t) > (input.tolerance ?? WEBHOOK_TOLERANCE_SECONDS)) return { ok: false, reason: "stale" }
|
|
84
|
+
const body = text(input.body)
|
|
85
|
+
const secrets = (typeof input.secret === "string" ? [input.secret] : input.secret).filter((s) => s.length > 0)
|
|
86
|
+
const given = parsed.v1.map((hex) => Buffer.from(hex, "hex"))
|
|
87
|
+
// Every comparison runs (no early exit), each in constant time.
|
|
88
|
+
let match = false
|
|
89
|
+
for (const secret of secrets) {
|
|
90
|
+
const expected = hmac(secret, parsed.t, body)
|
|
91
|
+
for (const signature of given) if (signature.length === expected.length && timingSafeEqual(signature, expected)) match = true
|
|
92
|
+
}
|
|
93
|
+
if (!match) return { ok: false, reason: "mismatch" }
|
|
94
|
+
const event = header(input.headers, WEBHOOK_EVENT_HEADER)
|
|
95
|
+
const delivery = header(input.headers, WEBHOOK_DELIVERY_HEADER)
|
|
96
|
+
return { ok: true, timestamp: parsed.t, ...(event !== undefined ? { event } : {}), ...(delivery !== undefined ? { delivery } : {}) }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The Hub-Signature header the hub would send for `body` (for testing receivers); `t` in unix seconds, default now. */
|
|
100
|
+
export function signWebhook(body: string | Uint8Array, secret: string, t: number = Math.floor(Date.now() / 1000)): string {
|
|
101
|
+
return `t=${t},${SCHEME}=${hmac(secret, t, text(body)).toString("hex")}`
|
|
102
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@huynle/hub-spoke-sdk",
|
|
3
|
+
"version": "0.7.2",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Harness-neutral spoke building blocks: hub connection, enrollment, tokens, idempotent deliver, prompt references, HarnessSpoke (internal; shipped inside @huynle/hub, not published on its own).",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Activity compaction (tool inputs cut to short summaries, no outputs) is part of the wire format and lives in the
|
|
2
|
+
// protocol package; the spoke SDK re-exports it so a spoke needs one import.
|
|
3
|
+
export { compactActivity, compactStatus, jobFromInfo, summarizeToolInput } from "../../protocol/src/index.ts"
|