@crewhaus/hangar-ui 0.5.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/README.md +309 -0
- package/assets/hangar.css +1726 -0
- package/assets/index.html +21 -0
- package/assets/js/api.js +368 -0
- package/assets/js/app.js +734 -0
- package/assets/js/dom.js +228 -0
- package/assets/js/keys.js +159 -0
- package/assets/js/markdown.js +131 -0
- package/assets/js/notify.js +152 -0
- package/assets/js/omnibox.js +307 -0
- package/assets/js/router.js +280 -0
- package/assets/js/routes.js +793 -0
- package/assets/js/shapes.js +36 -0
- package/assets/js/supervision.js +1334 -0
- package/assets/js/util.js +426 -0
- package/assets/js/views/activity.js +140 -0
- package/assets/js/views/approvals.js +155 -0
- package/assets/js/views/channels.js +455 -0
- package/assets/js/views/control.js +253 -0
- package/assets/js/views/costs.js +103 -0
- package/assets/js/views/creds.js +610 -0
- package/assets/js/views/data.js +456 -0
- package/assets/js/views/deploy.js +106 -0
- package/assets/js/views/evals-lab.js +1029 -0
- package/assets/js/views/evals.js +342 -0
- package/assets/js/views/feedback.js +555 -0
- package/assets/js/views/health.js +176 -0
- package/assets/js/views/inbox.js +103 -0
- package/assets/js/views/inspect.js +751 -0
- package/assets/js/views/jobs.js +110 -0
- package/assets/js/views/library.js +605 -0
- package/assets/js/views/memory-fabric.js +2026 -0
- package/assets/js/views/memory.js +228 -0
- package/assets/js/views/onboarding.js +253 -0
- package/assets/js/views/overview.js +196 -0
- package/assets/js/views/panes.js +132 -0
- package/assets/js/views/proc.js +280 -0
- package/assets/js/views/review.js +164 -0
- package/assets/js/views/runs.js +452 -0
- package/assets/js/views/runtime.js +450 -0
- package/assets/js/views/schedulers.js +133 -0
- package/assets/js/views/security.js +925 -0
- package/assets/js/views/sessions.js +265 -0
- package/assets/js/views/settings.js +489 -0
- package/assets/js/views/spec-edit.js +1289 -0
- package/assets/js/views/spec.js +66 -0
- package/assets/js/views/thredz.js +1853 -0
- package/assets/js/views/token.js +49 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +145 -0
- package/package.json +36 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route contract as pure data — the ONE place client code names a
|
|
3
|
+
* server path. `api.js` builds every request from this map, and the hangar
|
|
4
|
+
* server's contract test (`packages/hangar-server/src/contract.test.ts`)
|
|
5
|
+
* imports the same map and drives every route here against a live fixture
|
|
6
|
+
* server: writes must 2xx with a visible effect, reads must 2xx with every
|
|
7
|
+
* field the views consume present. A route drifting from the server fails
|
|
8
|
+
* that test — the alarm that keeps this file honest.
|
|
9
|
+
*
|
|
10
|
+
* Each entry: `{ method, path }` where `path` is a template whose `:name`
|
|
11
|
+
* segments `buildPath` fills (values URI-encoded). Writes carry `body`, the
|
|
12
|
+
* name of their request-body shape (documentation for humans + the contract
|
|
13
|
+
* test; the shapes themselves are defined by the server handlers).
|
|
14
|
+
* `stream: "sse"` marks a route whose response is `text/event-stream`, not
|
|
15
|
+
* JSON — `api.js` must not `res.json()` it.
|
|
16
|
+
*
|
|
17
|
+
* M1 was read-only over harness state. M2 makes the console a DRIVER: the
|
|
18
|
+
* process routes start/stop/restart/drain a supervised harness, the control
|
|
19
|
+
* routes proxy `crewhaus.control.v1` (the token is read server-side and
|
|
20
|
+
* never crosses this boundary), and the approvals/review routes settle
|
|
21
|
+
* parked work through the same stores the CLI writes through.
|
|
22
|
+
*
|
|
23
|
+
* M3 is the DETAIL surface, and every M3 entry carries one extra field:
|
|
24
|
+
* `group`, naming the area that owns it (spec / memory / evals / data /
|
|
25
|
+
* feedback / creds / channels / security / thredz / inspect / runtime).
|
|
26
|
+
* That single field does a lot of work — it is what lets `api.js` generate
|
|
27
|
+
* the client wrappers instead of hand-writing 180 of them, what the views
|
|
28
|
+
* read to render their own surface honestly while the handlers are still
|
|
29
|
+
* stubs, and what the server's dispatch table is asserted equal to. A route
|
|
30
|
+
* WITHOUT a group is M1/M2 and has a hand-written wrapper.
|
|
31
|
+
*
|
|
32
|
+
* The M3 handlers currently answer `501 not implemented (M3)`. That is a
|
|
33
|
+
* deliberate state, not an oversight: the contract is frozen first so six
|
|
34
|
+
* implementers can fill the bodies in parallel without colliding, and the
|
|
35
|
+
* contract test accepts a 501 today and demands the view's fields the moment
|
|
36
|
+
* a route goes real.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export const ROUTES = {
|
|
40
|
+
// cross-cutting
|
|
41
|
+
version: { method: "GET", path: "/api/version" },
|
|
42
|
+
|
|
43
|
+
// fleet feed + registry CRUD (the ONLY writes in M1 — manager state)
|
|
44
|
+
harnesses: { method: "GET", path: "/api/harnesses" },
|
|
45
|
+
addHarness: { method: "POST", path: "/api/harnesses", body: "AddHarness" }, // {dir}
|
|
46
|
+
scan: { method: "POST", path: "/api/scan", body: "Empty" }, // {}
|
|
47
|
+
groups: { method: "GET", path: "/api/registry/groups" },
|
|
48
|
+
addGroup: { method: "POST", path: "/api/registry/groups", body: "GroupCreate" }, // {name, color?}
|
|
49
|
+
addScanRoot: { method: "POST", path: "/api/registry/scan-roots", body: "ScanRootCreate" }, // {dir}
|
|
50
|
+
removeHarness: { method: "DELETE", path: "/api/h/:id" },
|
|
51
|
+
relocate: { method: "POST", path: "/api/h/:id/relocate", body: "Relocate" }, // {newDir}
|
|
52
|
+
setGroups: { method: "PUT", path: "/api/h/:id/groups", body: "SetGroups" }, // {groups}
|
|
53
|
+
setTags: { method: "PUT", path: "/api/h/:id/tags", body: "SetTags" }, // {tags}
|
|
54
|
+
setPin: { method: "PUT", path: "/api/h/:id/pin", body: "SetPin" }, // {pinned}
|
|
55
|
+
setNotes: { method: "PUT", path: "/api/h/:id/notes", body: "SetNotes" }, // {notes}
|
|
56
|
+
|
|
57
|
+
// per-harness reads
|
|
58
|
+
harness: { method: "GET", path: "/api/h/:id" },
|
|
59
|
+
spec: { method: "GET", path: "/api/h/:id/spec" },
|
|
60
|
+
preflight: { method: "GET", path: "/api/h/:id/preflight" },
|
|
61
|
+
sessions: { method: "GET", path: "/api/h/:id/sessions" },
|
|
62
|
+
session: { method: "GET", path: "/api/h/:id/sessions/:sess" },
|
|
63
|
+
evals: { method: "GET", path: "/api/h/:id/evals" },
|
|
64
|
+
evalRun: { method: "GET", path: "/api/h/:id/evals/:runId" },
|
|
65
|
+
evalSample: { method: "GET", path: "/api/h/:id/evals/:runId/:sampleId" },
|
|
66
|
+
memory: { method: "GET", path: "/api/h/:id/memory/:area" },
|
|
67
|
+
wikiArticle: { method: "GET", path: "/api/h/:id/memory/wiki/:slug" },
|
|
68
|
+
costs: { method: "GET", path: "/api/h/:id/costs" },
|
|
69
|
+
|
|
70
|
+
// ---- M2: the process layer -------------------------------------------
|
|
71
|
+
// `start` runs the preflight gate FIRST and answers the typed refusal
|
|
72
|
+
// instead of spawning; `{force}` waves through every forceable blocking
|
|
73
|
+
// item and `{acknowledge:[id]}` waves through named ones — neither can
|
|
74
|
+
// clear an UNFORCEABLE finding (missing channel secrets), because the
|
|
75
|
+
// compiled daemon exits 2 on exactly that set.
|
|
76
|
+
proc: { method: "GET", path: "/api/h/:id/proc" },
|
|
77
|
+
procStart: { method: "POST", path: "/api/h/:id/proc/start", body: "ProcStart" }, // {force?, acknowledge?}
|
|
78
|
+
procStop: { method: "POST", path: "/api/h/:id/proc/stop", body: "Empty" }, // {}
|
|
79
|
+
procRestart: { method: "POST", path: "/api/h/:id/proc/restart", body: "ProcStart" }, // {force?, acknowledge?}
|
|
80
|
+
procDrain: { method: "POST", path: "/api/h/:id/proc/drain", body: "Empty" }, // {}
|
|
81
|
+
|
|
82
|
+
// run history + the live feed. `runEvents` is SSE: it opens with a
|
|
83
|
+
// `replay` frame of durable history and ALWAYS ends with `done`.
|
|
84
|
+
runs: { method: "GET", path: "/api/h/:id/runs" },
|
|
85
|
+
run: { method: "GET", path: "/api/h/:id/runs/:runId" },
|
|
86
|
+
runEvents: { method: "GET", path: "/api/h/:id/runs/:runId/events", stream: "sse" },
|
|
87
|
+
|
|
88
|
+
// ---- M2: crewhaus.control.v1 proxy -----------------------------------
|
|
89
|
+
// Always 200 with a typed envelope: `{ok:true,…}` or `{ok:false, code,
|
|
90
|
+
// reason, retryable, expected}`. `no_control_port` (pre-0.5.0 bundle) and
|
|
91
|
+
// `lane_not_armed` are FACTS, not errors — render disabled-with-reason.
|
|
92
|
+
// The two 409s differ: `tick_in_flight` retries, `draining` does not.
|
|
93
|
+
controlStatus: { method: "GET", path: "/api/h/:id/control/status" },
|
|
94
|
+
controlWake: { method: "POST", path: "/api/h/:id/control/wake", body: "ControlWake" }, // {lane, reason?}
|
|
95
|
+
controlDrain: { method: "POST", path: "/api/h/:id/control/drain", body: "Empty" }, // {}
|
|
96
|
+
|
|
97
|
+
// the four-lane timeline: spec cadence (offline) merged with control.v1
|
|
98
|
+
// phase (online only), plus dream state and janitor rows
|
|
99
|
+
schedulers: { method: "GET", path: "/api/h/:id/schedulers" },
|
|
100
|
+
|
|
101
|
+
// ---- M2: fleet inboxes ------------------------------------------------
|
|
102
|
+
approvals: { method: "GET", path: "/api/approvals" },
|
|
103
|
+
grantApproval: {
|
|
104
|
+
method: "POST",
|
|
105
|
+
path: "/api/h/:id/approvals/:apprId/grant",
|
|
106
|
+
body: "ApprovalDecision", // {by?}
|
|
107
|
+
},
|
|
108
|
+
denyApproval: {
|
|
109
|
+
method: "POST",
|
|
110
|
+
path: "/api/h/:id/approvals/:apprId/deny",
|
|
111
|
+
body: "ApprovalDecision", // {by?}
|
|
112
|
+
},
|
|
113
|
+
review: { method: "GET", path: "/api/review" },
|
|
114
|
+
adjudicateReview: {
|
|
115
|
+
method: "POST",
|
|
116
|
+
path: "/api/h/:id/review/:itemId",
|
|
117
|
+
body: "ReviewVerdict", // {verdict: up|down|pass|fail, note?}
|
|
118
|
+
},
|
|
119
|
+
activity: { method: "GET", path: "/api/activity" }, // ?since=<iso|epochMs|7d>
|
|
120
|
+
|
|
121
|
+
// ---- M2: jobs + the remaining action faces ----------------------------
|
|
122
|
+
jobs: { method: "GET", path: "/api/jobs" },
|
|
123
|
+
submitJob: { method: "POST", path: "/api/h/:id/jobs", body: "JobSubmit" }, // {kind, dataset?, graders?}
|
|
124
|
+
deployments: { method: "GET", path: "/api/h/:id/deployments" },
|
|
125
|
+
pinSession: { method: "POST", path: "/api/h/:id/sessions/:sess/pin", body: "PinSession" }, // {pinned}
|
|
126
|
+
pinBaseline: { method: "POST", path: "/api/h/:id/evals/baseline", body: "PinBaseline" }, // {runId}
|
|
127
|
+
|
|
128
|
+
// ---- M4: polish, fleet ops, notifications -----------------------------
|
|
129
|
+
// Real handlers, not stubs — so these carry NO `group` and are driven by
|
|
130
|
+
// the contract test like any M1/M2 route.
|
|
131
|
+
//
|
|
132
|
+
// `health` is the explained score (HM-11): never a bare number, always the
|
|
133
|
+
// deductions that produced it, each naming the tab that fixes it.
|
|
134
|
+
// `fleetHealth` runs a preflight per harness, so it is the "needs
|
|
135
|
+
// attention" board an operator opens — never the Library's first paint.
|
|
136
|
+
health: { method: "GET", path: "/api/h/:id/health" },
|
|
137
|
+
fleetHealth: { method: "GET", path: "/api/health" },
|
|
138
|
+
|
|
139
|
+
// First boot (HM-12). `demoInstall` copies a starter out of a LOCAL demos
|
|
140
|
+
// checkout; with none configured it answers 409 naming the repo, the env
|
|
141
|
+
// var and the CLI verb — it never fetches code from the network.
|
|
142
|
+
onboarding: { method: "GET", path: "/api/onboarding" },
|
|
143
|
+
demoInstall: { method: "POST", path: "/api/onboarding/demo", body: "DemoInstall" }, // {starter, dir}
|
|
144
|
+
|
|
145
|
+
// ⌘K (HM-189). `?q=` + optional `?limit=`. The response's `actions` are
|
|
146
|
+
// PROPOSALS carrying a route key and a CLI twin; the console executes them
|
|
147
|
+
// through the ordinary route after an explicit confirm, so an action is
|
|
148
|
+
// never a side effect of typing.
|
|
149
|
+
search: { method: "GET", path: "/api/search" },
|
|
150
|
+
|
|
151
|
+
// Notification rules (HM-183). The GET is the badge poll AND the
|
|
152
|
+
// evaluation pass (the manager runs no timer of its own).
|
|
153
|
+
notifications: { method: "GET", path: "/api/notifications" },
|
|
154
|
+
setNotifications: { method: "PUT", path: "/api/notifications", body: "NotificationRules" },
|
|
155
|
+
clearNotifications: { method: "POST", path: "/api/notifications/clear", body: "Empty" }, // {}
|
|
156
|
+
|
|
157
|
+
// Read-only mode (HM-187) — enforced SERVER-side ahead of every handler.
|
|
158
|
+
readOnly: { method: "GET", path: "/api/read-only" },
|
|
159
|
+
setReadOnly: { method: "PUT", path: "/api/read-only", body: "ReadOnly" }, // {enabled}
|
|
160
|
+
|
|
161
|
+
// Plugin SDK minimal wiring (HM-179): the inventory (which extension
|
|
162
|
+
// points are wired and which are declared-but-deferred), one pane document
|
|
163
|
+
// with the sandbox + CSP that must accompany it, and the panes a given
|
|
164
|
+
// harness shows after the fail-closed fs permission is evaluated.
|
|
165
|
+
plugins: { method: "GET", path: "/api/plugins" },
|
|
166
|
+
pluginPane: { method: "GET", path: "/api/plugins/:plugin/panes/:pane" },
|
|
167
|
+
panes: { method: "GET", path: "/api/h/:id/panes" },
|
|
168
|
+
|
|
169
|
+
// ======================================================================
|
|
170
|
+
// M3 — the detail surface. Every entry carries `group`.
|
|
171
|
+
// ======================================================================
|
|
172
|
+
|
|
173
|
+
// ---- group "spec": structured editing, trust tiers, versions, builders
|
|
174
|
+
// Writes go through @crewhaus/spec-patch `applySpecEdits`, never by
|
|
175
|
+
// templating YAML. Auto-tunable paths (OPTIMIZABLE_PATHS) apply directly;
|
|
176
|
+
// human-owned paths need the redacted diff + typed confirm, or `propose`.
|
|
177
|
+
specEdit: { method: "PUT", path: "/api/h/:id/spec", body: "SpecEdits", group: "spec" },
|
|
178
|
+
specPatch: { method: "POST", path: "/api/h/:id/spec/patch", body: "SpecPatch", group: "spec" },
|
|
179
|
+
specDiff: { method: "POST", path: "/api/h/:id/spec/diff", body: "SpecEdits", group: "spec" },
|
|
180
|
+
specSchema: { method: "GET", path: "/api/h/:id/spec/schema", group: "spec" },
|
|
181
|
+
specTrust: { method: "GET", path: "/api/h/:id/spec/trust", group: "spec" },
|
|
182
|
+
specVersions: { method: "GET", path: "/api/h/:id/spec/versions", group: "spec" },
|
|
183
|
+
specVersion: { method: "GET", path: "/api/h/:id/spec/versions/:version", group: "spec" },
|
|
184
|
+
specVersionDiff: { method: "GET", path: "/api/h/:id/spec/versions/:version/diff", group: "spec" },
|
|
185
|
+
specPin: { method: "POST", path: "/api/h/:id/spec/pin", body: "SpecPin", group: "spec" },
|
|
186
|
+
specRollback: {
|
|
187
|
+
method: "POST",
|
|
188
|
+
path: "/api/h/:id/spec/rollback",
|
|
189
|
+
body: "SpecRollback",
|
|
190
|
+
group: "spec",
|
|
191
|
+
},
|
|
192
|
+
specPropose: {
|
|
193
|
+
method: "POST",
|
|
194
|
+
path: "/api/h/:id/spec/propose",
|
|
195
|
+
body: "SpecPropose",
|
|
196
|
+
group: "spec",
|
|
197
|
+
},
|
|
198
|
+
wizardTemplates: { method: "GET", path: "/api/builders/templates", group: "spec" },
|
|
199
|
+
wizardCreate: { method: "POST", path: "/api/builders/spec", body: "WizardSpec", group: "spec" },
|
|
200
|
+
mcpCatalog: { method: "GET", path: "/api/builders/mcp-catalog", group: "spec" },
|
|
201
|
+
graderCatalog: { method: "GET", path: "/api/h/:id/builders/graders", group: "spec" },
|
|
202
|
+
graderWrite: {
|
|
203
|
+
method: "POST",
|
|
204
|
+
path: "/api/h/:id/builders/graders",
|
|
205
|
+
body: "GraderBuild",
|
|
206
|
+
group: "spec",
|
|
207
|
+
},
|
|
208
|
+
datasetBuilder: { method: "GET", path: "/api/h/:id/builders/dataset", group: "spec" },
|
|
209
|
+
datasetBuilderStep: {
|
|
210
|
+
method: "POST",
|
|
211
|
+
path: "/api/h/:id/builders/dataset",
|
|
212
|
+
body: "DatasetBuildStep",
|
|
213
|
+
group: "spec",
|
|
214
|
+
},
|
|
215
|
+
mcpConnectors: { method: "GET", path: "/api/h/:id/builders/mcp", group: "spec" },
|
|
216
|
+
mcpConnectorWrite: {
|
|
217
|
+
method: "POST",
|
|
218
|
+
path: "/api/h/:id/builders/mcp",
|
|
219
|
+
body: "McpConnector",
|
|
220
|
+
group: "spec",
|
|
221
|
+
},
|
|
222
|
+
mcpConnectorRemove: { method: "DELETE", path: "/api/h/:id/builders/mcp/:name", group: "spec" },
|
|
223
|
+
|
|
224
|
+
// ---- group "memory": facts, continuity, wiki, watchme, learning -------
|
|
225
|
+
// NO HARD DELETE ANYWHERE: forget = a supersede tombstone with a reason,
|
|
226
|
+
// continuity clear = trash/<ts>/ with restore, wiki = archived status.
|
|
227
|
+
memoryFacts: { method: "GET", path: "/api/h/:id/memory/facts/:spec", group: "memory" },
|
|
228
|
+
memoryForget: {
|
|
229
|
+
method: "POST",
|
|
230
|
+
path: "/api/h/:id/memory/facts/:spec/forget",
|
|
231
|
+
body: "MemoryForget",
|
|
232
|
+
group: "memory",
|
|
233
|
+
},
|
|
234
|
+
memorySweep: {
|
|
235
|
+
method: "POST",
|
|
236
|
+
path: "/api/h/:id/memory/facts/:spec/sweep",
|
|
237
|
+
body: "DryRun",
|
|
238
|
+
group: "memory",
|
|
239
|
+
},
|
|
240
|
+
memoryRecall: {
|
|
241
|
+
method: "POST",
|
|
242
|
+
path: "/api/h/:id/memory/recall",
|
|
243
|
+
body: "RecallQuery",
|
|
244
|
+
group: "memory",
|
|
245
|
+
},
|
|
246
|
+
memoryMigrate: {
|
|
247
|
+
method: "POST",
|
|
248
|
+
path: "/api/h/:id/memory/migrate",
|
|
249
|
+
body: "DryRun",
|
|
250
|
+
group: "memory",
|
|
251
|
+
},
|
|
252
|
+
continuity: { method: "GET", path: "/api/h/:id/memory/continuity", group: "memory" },
|
|
253
|
+
continuityTrash: { method: "GET", path: "/api/h/:id/memory/continuity/trash", group: "memory" },
|
|
254
|
+
continuityRestore: {
|
|
255
|
+
method: "POST",
|
|
256
|
+
path: "/api/h/:id/memory/continuity/restore",
|
|
257
|
+
body: "ContinuityRestore",
|
|
258
|
+
group: "memory",
|
|
259
|
+
},
|
|
260
|
+
learning: { method: "GET", path: "/api/h/:id/memory/learning", group: "memory" },
|
|
261
|
+
knowledge: { method: "GET", path: "/api/h/:id/memory/knowledge", group: "memory" },
|
|
262
|
+
knowledgeSync: {
|
|
263
|
+
method: "POST",
|
|
264
|
+
path: "/api/h/:id/memory/knowledge/sync",
|
|
265
|
+
body: "KnowledgeSync",
|
|
266
|
+
group: "memory",
|
|
267
|
+
},
|
|
268
|
+
dreamScaffold: { method: "GET", path: "/api/h/:id/memory/dream/scaffold", group: "memory" },
|
|
269
|
+
// Wiki writes carry `expectedVersion`; a stale version is a re-read-retry
|
|
270
|
+
// state, not an error — identical UX for the local and Thredz backends.
|
|
271
|
+
wikiWrite: {
|
|
272
|
+
method: "PUT",
|
|
273
|
+
path: "/api/h/:id/memory/wiki/:slug",
|
|
274
|
+
body: "WikiWrite",
|
|
275
|
+
group: "memory",
|
|
276
|
+
},
|
|
277
|
+
wikiVersions: { method: "GET", path: "/api/h/:id/memory/wiki/:slug/versions", group: "memory" },
|
|
278
|
+
wikiVersion: {
|
|
279
|
+
method: "GET",
|
|
280
|
+
path: "/api/h/:id/memory/wiki/:slug/versions/:version",
|
|
281
|
+
group: "memory",
|
|
282
|
+
},
|
|
283
|
+
wikiLinks: { method: "GET", path: "/api/h/:id/memory/wiki/:slug/links", group: "memory" },
|
|
284
|
+
wikiSignals: {
|
|
285
|
+
method: "POST",
|
|
286
|
+
path: "/api/h/:id/memory/wiki/:slug/signals",
|
|
287
|
+
body: "WikiSignals",
|
|
288
|
+
group: "memory",
|
|
289
|
+
},
|
|
290
|
+
wikiArchive: {
|
|
291
|
+
method: "POST",
|
|
292
|
+
path: "/api/h/:id/memory/wiki/:slug/archive",
|
|
293
|
+
body: "WikiArchive",
|
|
294
|
+
group: "memory",
|
|
295
|
+
},
|
|
296
|
+
wikiReflect: { method: "GET", path: "/api/h/:id/memory/reflect", group: "memory" },
|
|
297
|
+
watchmeAnalytics: { method: "GET", path: "/api/h/:id/memory/watchme/analytics", group: "memory" },
|
|
298
|
+
watchmeReports: { method: "GET", path: "/api/h/:id/memory/watchme/reports", group: "memory" },
|
|
299
|
+
watchmeReport: {
|
|
300
|
+
method: "GET",
|
|
301
|
+
path: "/api/h/:id/memory/watchme/reports/:stamp",
|
|
302
|
+
group: "memory",
|
|
303
|
+
},
|
|
304
|
+
watchmeIntents: { method: "GET", path: "/api/h/:id/memory/watchme/intents", group: "memory" },
|
|
305
|
+
watchmeToggle: {
|
|
306
|
+
method: "POST",
|
|
307
|
+
path: "/api/h/:id/memory/watchme/toggle",
|
|
308
|
+
body: "WatchmeToggle",
|
|
309
|
+
group: "memory",
|
|
310
|
+
},
|
|
311
|
+
watchmeSynthesized: {
|
|
312
|
+
method: "GET",
|
|
313
|
+
path: "/api/h/:id/memory/watchme/synthesized",
|
|
314
|
+
group: "memory",
|
|
315
|
+
},
|
|
316
|
+
watchmeApply: {
|
|
317
|
+
method: "POST",
|
|
318
|
+
path: "/api/h/:id/memory/watchme/synthesized/:stamp/apply",
|
|
319
|
+
body: "SynthesizeApply",
|
|
320
|
+
group: "memory",
|
|
321
|
+
},
|
|
322
|
+
watchmePublish: {
|
|
323
|
+
method: "POST",
|
|
324
|
+
path: "/api/h/:id/memory/watchme/publish",
|
|
325
|
+
body: "DryRun",
|
|
326
|
+
group: "memory",
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
// ---- group "evals": the quality lab -----------------------------------
|
|
330
|
+
evalLaunch: { method: "POST", path: "/api/h/:id/evals/run", body: "EvalLaunch", group: "evals" },
|
|
331
|
+
evalMatrix: { method: "GET", path: "/api/h/:id/evals/matrix", group: "evals" },
|
|
332
|
+
evalMatrixCell: { method: "GET", path: "/api/h/:id/evals/matrix/:cell", group: "evals" },
|
|
333
|
+
evalSuites: { method: "GET", path: "/api/h/:id/evals/suites", group: "evals" },
|
|
334
|
+
evalSuiteRun: {
|
|
335
|
+
method: "POST",
|
|
336
|
+
path: "/api/h/:id/evals/suites",
|
|
337
|
+
body: "SuiteRun",
|
|
338
|
+
group: "evals",
|
|
339
|
+
},
|
|
340
|
+
evalTrends: { method: "GET", path: "/api/h/:id/evals/trends", group: "evals" },
|
|
341
|
+
evalPlan: { method: "POST", path: "/api/h/:id/evals/plan", body: "EvalPlan", group: "evals" },
|
|
342
|
+
judgeCalibration: { method: "GET", path: "/api/h/:id/evals/judge", group: "evals" },
|
|
343
|
+
judgeCalibrate: {
|
|
344
|
+
method: "POST",
|
|
345
|
+
path: "/api/h/:id/evals/judge",
|
|
346
|
+
body: "JudgeCalibrate",
|
|
347
|
+
group: "evals",
|
|
348
|
+
},
|
|
349
|
+
graderCards: { method: "GET", path: "/api/h/:id/evals/graders", group: "evals" },
|
|
350
|
+
gradersSuggest: {
|
|
351
|
+
method: "POST",
|
|
352
|
+
path: "/api/h/:id/evals/graders/suggest",
|
|
353
|
+
body: "Empty",
|
|
354
|
+
group: "evals",
|
|
355
|
+
},
|
|
356
|
+
gradersTest: {
|
|
357
|
+
method: "POST",
|
|
358
|
+
path: "/api/h/:id/evals/graders/test",
|
|
359
|
+
body: "GradersTest",
|
|
360
|
+
group: "evals",
|
|
361
|
+
},
|
|
362
|
+
redteam: { method: "GET", path: "/api/h/:id/evals/redteam", group: "evals" },
|
|
363
|
+
redteamGenerate: {
|
|
364
|
+
method: "POST",
|
|
365
|
+
path: "/api/h/:id/evals/redteam",
|
|
366
|
+
body: "RedteamGenerate",
|
|
367
|
+
group: "evals",
|
|
368
|
+
},
|
|
369
|
+
evalCoverage: { method: "GET", path: "/api/h/:id/evals/coverage", group: "evals" },
|
|
370
|
+
sentinel: { method: "GET", path: "/api/h/:id/evals/sentinel", group: "evals" },
|
|
371
|
+
sentinelRun: {
|
|
372
|
+
method: "POST",
|
|
373
|
+
path: "/api/h/:id/evals/sentinel",
|
|
374
|
+
body: "SentinelRun",
|
|
375
|
+
group: "evals",
|
|
376
|
+
},
|
|
377
|
+
voiceEvals: { method: "GET", path: "/api/h/:id/evals/voice", group: "evals" },
|
|
378
|
+
optimizer: { method: "GET", path: "/api/h/:id/evals/optimize", group: "evals" },
|
|
379
|
+
optimizerRun: {
|
|
380
|
+
method: "POST",
|
|
381
|
+
path: "/api/h/:id/evals/optimize",
|
|
382
|
+
body: "OptimizeRun",
|
|
383
|
+
group: "evals",
|
|
384
|
+
},
|
|
385
|
+
optimizerArtifacts: {
|
|
386
|
+
method: "GET",
|
|
387
|
+
path: "/api/h/:id/evals/optimize/:optRunId",
|
|
388
|
+
group: "evals",
|
|
389
|
+
},
|
|
390
|
+
flywheel: { method: "GET", path: "/api/h/:id/evals/flywheel", group: "evals" },
|
|
391
|
+
flywheelRun: {
|
|
392
|
+
method: "POST",
|
|
393
|
+
path: "/api/h/:id/evals/flywheel",
|
|
394
|
+
body: "FlywheelRun",
|
|
395
|
+
group: "evals",
|
|
396
|
+
},
|
|
397
|
+
experiments: { method: "GET", path: "/api/h/:id/evals/experiments", group: "evals" },
|
|
398
|
+
experimentRecord: {
|
|
399
|
+
method: "POST",
|
|
400
|
+
path: "/api/h/:id/evals/experiments",
|
|
401
|
+
body: "ExperimentRecord",
|
|
402
|
+
group: "evals",
|
|
403
|
+
},
|
|
404
|
+
annotations: { method: "GET", path: "/api/h/:id/evals/annotations", group: "evals" },
|
|
405
|
+
annotateSample: {
|
|
406
|
+
method: "POST",
|
|
407
|
+
path: "/api/h/:id/evals/:runId/:sampleId/annotate",
|
|
408
|
+
body: "SampleAnnotation",
|
|
409
|
+
group: "evals",
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
// ---- group "data": the dataset registry, hygiene, growth ---------------
|
|
413
|
+
// The dataset NAME travels in the BODY for every write: an operator-chosen
|
|
414
|
+
// string must not sit where a literal route keyword lives.
|
|
415
|
+
datasets: { method: "GET", path: "/api/h/:id/data/datasets", group: "data" },
|
|
416
|
+
dataset: { method: "GET", path: "/api/h/:id/data/datasets/:name", group: "data" },
|
|
417
|
+
datasetStatus: { method: "GET", path: "/api/h/:id/data/status", group: "data" },
|
|
418
|
+
datasetQuarantine: { method: "GET", path: "/api/h/:id/data/quarantine", group: "data" },
|
|
419
|
+
datasetVerify: {
|
|
420
|
+
method: "POST",
|
|
421
|
+
path: "/api/h/:id/data/verify",
|
|
422
|
+
body: "DatasetName",
|
|
423
|
+
group: "data",
|
|
424
|
+
},
|
|
425
|
+
datasetAudit: { method: "POST", path: "/api/h/:id/data/audit", body: "Empty", group: "data" },
|
|
426
|
+
datasetLint: { method: "POST", path: "/api/h/:id/data/lint", body: "Empty", group: "data" },
|
|
427
|
+
datasetMine: { method: "POST", path: "/api/h/:id/data/mine", body: "DatasetMine", group: "data" },
|
|
428
|
+
datasetSynthesize: {
|
|
429
|
+
method: "POST",
|
|
430
|
+
path: "/api/h/:id/data/synthesize",
|
|
431
|
+
body: "DatasetSynthesize",
|
|
432
|
+
group: "data",
|
|
433
|
+
},
|
|
434
|
+
datasetRefreshGoldens: {
|
|
435
|
+
method: "POST",
|
|
436
|
+
path: "/api/h/:id/data/refresh-goldens",
|
|
437
|
+
body: "DatasetName",
|
|
438
|
+
group: "data",
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// ---- group "feedback": the growth loops --------------------------------
|
|
442
|
+
feedback: { method: "GET", path: "/api/h/:id/feedback", group: "feedback" },
|
|
443
|
+
distillRun: {
|
|
444
|
+
method: "POST",
|
|
445
|
+
path: "/api/h/:id/feedback/distill",
|
|
446
|
+
body: "DistillRun",
|
|
447
|
+
group: "feedback",
|
|
448
|
+
},
|
|
449
|
+
fewshot: { method: "GET", path: "/api/h/:id/feedback/fewshot", group: "feedback" },
|
|
450
|
+
fewshotHarvest: {
|
|
451
|
+
method: "POST",
|
|
452
|
+
path: "/api/h/:id/feedback/fewshot",
|
|
453
|
+
body: "Empty",
|
|
454
|
+
group: "feedback",
|
|
455
|
+
},
|
|
456
|
+
faq: { method: "GET", path: "/api/h/:id/feedback/faq", group: "feedback" },
|
|
457
|
+
faqDistill: { method: "POST", path: "/api/h/:id/feedback/faq", body: "Empty", group: "feedback" },
|
|
458
|
+
lessons: { method: "GET", path: "/api/h/:id/feedback/lessons", group: "feedback" },
|
|
459
|
+
lessonsUpdate: {
|
|
460
|
+
method: "POST",
|
|
461
|
+
path: "/api/h/:id/feedback/lessons",
|
|
462
|
+
body: "Empty",
|
|
463
|
+
group: "feedback",
|
|
464
|
+
},
|
|
465
|
+
advice: { method: "GET", path: "/api/h/:id/feedback/advice", group: "feedback" },
|
|
466
|
+
adviceRun: {
|
|
467
|
+
method: "POST",
|
|
468
|
+
path: "/api/h/:id/feedback/advice",
|
|
469
|
+
body: "AdviseRun",
|
|
470
|
+
group: "feedback",
|
|
471
|
+
},
|
|
472
|
+
adviceApply: {
|
|
473
|
+
method: "POST",
|
|
474
|
+
path: "/api/h/:id/feedback/advice/:adviceId/apply",
|
|
475
|
+
body: "Confirm",
|
|
476
|
+
group: "feedback",
|
|
477
|
+
},
|
|
478
|
+
reactions: { method: "GET", path: "/api/h/:id/feedback/reactions", group: "feedback" },
|
|
479
|
+
feedbackFleet: { method: "GET", path: "/api/feedback", group: "feedback" },
|
|
480
|
+
|
|
481
|
+
// ---- group "creds": env, the fleet matrix, doctor, secrets, mcp lint ---
|
|
482
|
+
// Values go IN and never come back: every read here is presence booleans.
|
|
483
|
+
env: { method: "GET", path: "/api/h/:id/env", group: "creds" },
|
|
484
|
+
envSet: { method: "POST", path: "/api/h/:id/env", body: "EnvSet", group: "creds" },
|
|
485
|
+
envUnset: { method: "DELETE", path: "/api/h/:id/env/:key", group: "creds" },
|
|
486
|
+
credentialsMatrix: { method: "GET", path: "/api/credentials", group: "creds" },
|
|
487
|
+
credentialsSetAcross: {
|
|
488
|
+
method: "POST",
|
|
489
|
+
path: "/api/credentials/set",
|
|
490
|
+
body: "SetAcross",
|
|
491
|
+
group: "creds",
|
|
492
|
+
},
|
|
493
|
+
doctor: { method: "GET", path: "/api/h/:id/doctor", group: "creds" },
|
|
494
|
+
doctorRun: { method: "POST", path: "/api/h/:id/doctor", body: "DoctorRun", group: "creds" },
|
|
495
|
+
secrets: { method: "GET", path: "/api/h/:id/secrets", group: "creds" },
|
|
496
|
+
secretsDoctor: { method: "GET", path: "/api/h/:id/secrets/doctor", group: "creds" },
|
|
497
|
+
secretsRotate: {
|
|
498
|
+
method: "POST",
|
|
499
|
+
path: "/api/h/:id/secrets/:name/rotate",
|
|
500
|
+
body: "SecretRotate",
|
|
501
|
+
group: "creds",
|
|
502
|
+
},
|
|
503
|
+
mcpLint: { method: "GET", path: "/api/h/:id/mcp/lint", group: "creds" },
|
|
504
|
+
|
|
505
|
+
// ---- group "channels": provisioning, verify, the two test tiers --------
|
|
506
|
+
channels: { method: "GET", path: "/api/h/:id/channels", group: "channels" },
|
|
507
|
+
channelVerify: {
|
|
508
|
+
method: "POST",
|
|
509
|
+
path: "/api/h/:id/channels/verify",
|
|
510
|
+
body: "ChannelVerify",
|
|
511
|
+
group: "channels",
|
|
512
|
+
},
|
|
513
|
+
channelProvision: {
|
|
514
|
+
method: "GET",
|
|
515
|
+
path: "/api/h/:id/channels/:channel/provision",
|
|
516
|
+
group: "channels",
|
|
517
|
+
},
|
|
518
|
+
channelProvisionRun: {
|
|
519
|
+
method: "POST",
|
|
520
|
+
path: "/api/h/:id/channels/:channel/provision",
|
|
521
|
+
body: "Confirm",
|
|
522
|
+
group: "channels",
|
|
523
|
+
},
|
|
524
|
+
channelProbe: {
|
|
525
|
+
method: "POST",
|
|
526
|
+
path: "/api/h/:id/channels/:channel/probe",
|
|
527
|
+
body: "Empty",
|
|
528
|
+
group: "channels",
|
|
529
|
+
},
|
|
530
|
+
// Tier 2 — Slack/Telegram/WhatsApp only. Discord is asymmetric Ed25519
|
|
531
|
+
// (the harness holds only the public key), iMessage has no inbound webhook.
|
|
532
|
+
channelSynthetic: {
|
|
533
|
+
method: "POST",
|
|
534
|
+
path: "/api/h/:id/channels/:channel/synthetic",
|
|
535
|
+
body: "SyntheticInbound",
|
|
536
|
+
group: "channels",
|
|
537
|
+
},
|
|
538
|
+
gateway: { method: "GET", path: "/api/h/:id/gateway", group: "channels" },
|
|
539
|
+
|
|
540
|
+
// ---- group "security": audit, egress, pii, onchain, retention, slo -----
|
|
541
|
+
audit: { method: "GET", path: "/api/h/:id/audit", group: "security" },
|
|
542
|
+
auditVerify: {
|
|
543
|
+
method: "POST",
|
|
544
|
+
path: "/api/h/:id/audit/verify",
|
|
545
|
+
body: "Empty",
|
|
546
|
+
group: "security",
|
|
547
|
+
},
|
|
548
|
+
egress: { method: "GET", path: "/api/h/:id/security/egress", group: "security" },
|
|
549
|
+
egressReview: {
|
|
550
|
+
method: "POST",
|
|
551
|
+
path: "/api/h/:id/security/egress/:decisionId",
|
|
552
|
+
body: "EgressVerdict",
|
|
553
|
+
group: "security",
|
|
554
|
+
},
|
|
555
|
+
pii: { method: "GET", path: "/api/h/:id/security/pii", group: "security" },
|
|
556
|
+
piiTune: {
|
|
557
|
+
method: "POST",
|
|
558
|
+
path: "/api/h/:id/security/pii",
|
|
559
|
+
body: "PolicyTune",
|
|
560
|
+
group: "security",
|
|
561
|
+
},
|
|
562
|
+
justification: { method: "GET", path: "/api/h/:id/security/justification", group: "security" },
|
|
563
|
+
justificationCalibrate: {
|
|
564
|
+
method: "POST",
|
|
565
|
+
path: "/api/h/:id/security/justification/calibrate",
|
|
566
|
+
body: "Calibrate",
|
|
567
|
+
group: "security",
|
|
568
|
+
},
|
|
569
|
+
justificationPreflight: {
|
|
570
|
+
method: "POST",
|
|
571
|
+
path: "/api/h/:id/security/justification/preflight",
|
|
572
|
+
body: "JustificationPreflight",
|
|
573
|
+
group: "security",
|
|
574
|
+
},
|
|
575
|
+
securityCorpus: { method: "GET", path: "/api/h/:id/security/corpus", group: "security" },
|
|
576
|
+
securityCorpusCheck: {
|
|
577
|
+
method: "POST",
|
|
578
|
+
path: "/api/h/:id/security/corpus",
|
|
579
|
+
body: "Empty",
|
|
580
|
+
group: "security",
|
|
581
|
+
},
|
|
582
|
+
sandboxDoctor: { method: "GET", path: "/api/h/:id/security/sandbox", group: "security" },
|
|
583
|
+
onchain: { method: "GET", path: "/api/h/:id/security/onchain", group: "security" },
|
|
584
|
+
onchainTune: {
|
|
585
|
+
method: "POST",
|
|
586
|
+
path: "/api/h/:id/security/onchain/tune",
|
|
587
|
+
body: "PolicyTune",
|
|
588
|
+
group: "security",
|
|
589
|
+
},
|
|
590
|
+
onchainSentinel: {
|
|
591
|
+
method: "GET",
|
|
592
|
+
path: "/api/h/:id/security/onchain/sentinel",
|
|
593
|
+
group: "security",
|
|
594
|
+
},
|
|
595
|
+
compliance: { method: "GET", path: "/api/h/:id/security/compliance", group: "security" },
|
|
596
|
+
complianceEvidence: {
|
|
597
|
+
method: "POST",
|
|
598
|
+
path: "/api/h/:id/security/compliance",
|
|
599
|
+
body: "ComplianceEvidence",
|
|
600
|
+
group: "security",
|
|
601
|
+
},
|
|
602
|
+
retention: { method: "GET", path: "/api/h/:id/security/retention", group: "security" },
|
|
603
|
+
// Dry-run first, then typed-confirm — the top rung of the ladder.
|
|
604
|
+
retentionSweep: {
|
|
605
|
+
method: "POST",
|
|
606
|
+
path: "/api/h/:id/security/retention/sweep",
|
|
607
|
+
body: "RetentionRun",
|
|
608
|
+
group: "security",
|
|
609
|
+
},
|
|
610
|
+
retentionPurge: {
|
|
611
|
+
method: "POST",
|
|
612
|
+
path: "/api/h/:id/security/retention/purge",
|
|
613
|
+
body: "RetentionRun",
|
|
614
|
+
group: "security",
|
|
615
|
+
},
|
|
616
|
+
slo: { method: "GET", path: "/api/h/:id/slo", group: "security" },
|
|
617
|
+
|
|
618
|
+
// ---- group "thredz": the server-side proxied explorer ------------------
|
|
619
|
+
// The API key lives in the harness and is read server-side per request.
|
|
620
|
+
// It never reaches this file, this browser, or manager state.
|
|
621
|
+
thredzStatus: { method: "GET", path: "/api/h/:id/thredz", group: "thredz" },
|
|
622
|
+
thredzWiki: { method: "GET", path: "/api/h/:id/thredz/wiki", group: "thredz" },
|
|
623
|
+
thredzWikiArticle: { method: "GET", path: "/api/h/:id/thredz/wiki/:slug", group: "thredz" },
|
|
624
|
+
thredzWikiWrite: {
|
|
625
|
+
method: "PUT",
|
|
626
|
+
path: "/api/h/:id/thredz/wiki/:slug",
|
|
627
|
+
body: "ThredzWikiWrite",
|
|
628
|
+
group: "thredz",
|
|
629
|
+
},
|
|
630
|
+
thredzWikiVersions: {
|
|
631
|
+
method: "GET",
|
|
632
|
+
path: "/api/h/:id/thredz/wiki/:slug/versions",
|
|
633
|
+
group: "thredz",
|
|
634
|
+
},
|
|
635
|
+
thredzWikiRollback: {
|
|
636
|
+
method: "POST",
|
|
637
|
+
path: "/api/h/:id/thredz/wiki/:slug/rollback",
|
|
638
|
+
body: "ThredzRollback",
|
|
639
|
+
group: "thredz",
|
|
640
|
+
},
|
|
641
|
+
thredzRecords: { method: "GET", path: "/api/h/:id/thredz/records", group: "thredz" },
|
|
642
|
+
thredzRecordCreate: {
|
|
643
|
+
method: "POST",
|
|
644
|
+
path: "/api/h/:id/thredz/records",
|
|
645
|
+
body: "ThredzRecord",
|
|
646
|
+
group: "thredz",
|
|
647
|
+
},
|
|
648
|
+
thredzRecord: { method: "GET", path: "/api/h/:id/thredz/records/:recordId", group: "thredz" },
|
|
649
|
+
// Soft delete only — every Thredz delete has a restore.
|
|
650
|
+
thredzRecordDelete: {
|
|
651
|
+
method: "DELETE",
|
|
652
|
+
path: "/api/h/:id/thredz/records/:recordId",
|
|
653
|
+
group: "thredz",
|
|
654
|
+
},
|
|
655
|
+
thredzRecordRestore: {
|
|
656
|
+
method: "POST",
|
|
657
|
+
path: "/api/h/:id/thredz/records/:recordId/restore",
|
|
658
|
+
body: "Empty",
|
|
659
|
+
group: "thredz",
|
|
660
|
+
},
|
|
661
|
+
thredzSchemas: { method: "GET", path: "/api/h/:id/thredz/schemas", group: "thredz" },
|
|
662
|
+
thredzGoals: { method: "GET", path: "/api/h/:id/thredz/goals", group: "thredz" },
|
|
663
|
+
thredzTasks: { method: "GET", path: "/api/h/:id/thredz/tasks", group: "thredz" },
|
|
664
|
+
thredzTaskUpdate: {
|
|
665
|
+
method: "POST",
|
|
666
|
+
path: "/api/h/:id/thredz/tasks/:taskId",
|
|
667
|
+
body: "ThredzTaskUpdate",
|
|
668
|
+
group: "thredz",
|
|
669
|
+
},
|
|
670
|
+
thredzViews: { method: "GET", path: "/api/h/:id/thredz/views", group: "thredz" },
|
|
671
|
+
thredzViewExecute: {
|
|
672
|
+
method: "POST",
|
|
673
|
+
path: "/api/h/:id/thredz/views/:viewId/execute",
|
|
674
|
+
body: "ThredzViewExec",
|
|
675
|
+
group: "thredz",
|
|
676
|
+
},
|
|
677
|
+
thredzDashboards: { method: "GET", path: "/api/h/:id/thredz/dashboards", group: "thredz" },
|
|
678
|
+
thredzDashboard: {
|
|
679
|
+
method: "GET",
|
|
680
|
+
path: "/api/h/:id/thredz/dashboards/:dashboardId",
|
|
681
|
+
group: "thredz",
|
|
682
|
+
},
|
|
683
|
+
thredzCardCreate: {
|
|
684
|
+
method: "POST",
|
|
685
|
+
path: "/api/h/:id/thredz/dashboards/:dashboardId/cards",
|
|
686
|
+
body: "ThredzCard",
|
|
687
|
+
group: "thredz",
|
|
688
|
+
},
|
|
689
|
+
thredzListeners: { method: "GET", path: "/api/h/:id/thredz/listeners", group: "thredz" },
|
|
690
|
+
thredzListenerCreate: {
|
|
691
|
+
method: "POST",
|
|
692
|
+
path: "/api/h/:id/thredz/listeners",
|
|
693
|
+
body: "ThredzListener",
|
|
694
|
+
group: "thredz",
|
|
695
|
+
},
|
|
696
|
+
thredzWebhooks: { method: "GET", path: "/api/h/:id/thredz/webhooks", group: "thredz" },
|
|
697
|
+
thredzConnectors: { method: "GET", path: "/api/h/:id/thredz/connectors", group: "thredz" },
|
|
698
|
+
thredzActivity: { method: "GET", path: "/api/h/:id/thredz/activity", group: "thredz" },
|
|
699
|
+
thredzTraverse: {
|
|
700
|
+
method: "POST",
|
|
701
|
+
path: "/api/h/:id/thredz/traverse",
|
|
702
|
+
body: "ThredzTraverse",
|
|
703
|
+
group: "thredz",
|
|
704
|
+
},
|
|
705
|
+
thredzKeys: { method: "GET", path: "/api/h/:id/thredz/keys", group: "thredz" },
|
|
706
|
+
thredzKeyCreate: {
|
|
707
|
+
method: "POST",
|
|
708
|
+
path: "/api/h/:id/thredz/keys",
|
|
709
|
+
body: "ThredzKeyCreate",
|
|
710
|
+
group: "thredz",
|
|
711
|
+
},
|
|
712
|
+
thredzKeyRotate: {
|
|
713
|
+
method: "POST",
|
|
714
|
+
path: "/api/h/:id/thredz/keys/:keyId/rotate",
|
|
715
|
+
body: "Confirm",
|
|
716
|
+
group: "thredz",
|
|
717
|
+
},
|
|
718
|
+
thredzGlobal: { method: "GET", path: "/api/thredz", group: "thredz" },
|
|
719
|
+
|
|
720
|
+
// ---- group "inspect": the raw browsers + settings.json -----------------
|
|
721
|
+
// secrets/, the raw audit files and .env are excluded server-side.
|
|
722
|
+
inspectIndex: { method: "GET", path: "/api/h/:id/inspect", group: "inspect" },
|
|
723
|
+
inspectRaw: { method: "GET", path: "/api/h/:id/inspect/raw", group: "inspect" }, // ?path=
|
|
724
|
+
inspectStore: { method: "GET", path: "/api/h/:id/inspect/:store", group: "inspect" },
|
|
725
|
+
inspectEntry: { method: "GET", path: "/api/h/:id/inspect/:store/:name", group: "inspect" },
|
|
726
|
+
settingsWrite: {
|
|
727
|
+
method: "PUT",
|
|
728
|
+
path: "/api/h/:id/inspect/settings",
|
|
729
|
+
body: "SettingsWrite",
|
|
730
|
+
group: "inspect",
|
|
731
|
+
},
|
|
732
|
+
|
|
733
|
+
// ---- group "runtime": the mcp-server + dev run classes -----------------
|
|
734
|
+
// Both are PROCESSES: they get runfiles, ledger rows and ledger-claimed
|
|
735
|
+
// ports, and their live output rides the ONE existing SSE feed
|
|
736
|
+
// (`runEvents`) — M3 adds no second streaming mechanism.
|
|
737
|
+
mcpServers: { method: "GET", path: "/api/h/:id/mcp-servers", group: "runtime" },
|
|
738
|
+
mcpServerStart: {
|
|
739
|
+
method: "POST",
|
|
740
|
+
path: "/api/h/:id/mcp-servers/start",
|
|
741
|
+
body: "McpServerStart",
|
|
742
|
+
group: "runtime",
|
|
743
|
+
},
|
|
744
|
+
mcpServerStop: {
|
|
745
|
+
method: "POST",
|
|
746
|
+
path: "/api/h/:id/mcp-servers/stop",
|
|
747
|
+
body: "Empty",
|
|
748
|
+
group: "runtime",
|
|
749
|
+
},
|
|
750
|
+
dev: { method: "GET", path: "/api/h/:id/dev", group: "runtime" },
|
|
751
|
+
devStart: { method: "POST", path: "/api/h/:id/dev/start", body: "DevStart", group: "runtime" },
|
|
752
|
+
devStop: { method: "POST", path: "/api/h/:id/dev/stop", body: "Empty", group: "runtime" },
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
/** The M3 area groups, in tab order. A route's `group` names the module that
|
|
756
|
+
* owns it on the server and the view that renders it in the console. */
|
|
757
|
+
export const M3_GROUPS = [
|
|
758
|
+
"spec",
|
|
759
|
+
"memory",
|
|
760
|
+
"evals",
|
|
761
|
+
"data",
|
|
762
|
+
"feedback",
|
|
763
|
+
"creds",
|
|
764
|
+
"channels",
|
|
765
|
+
"security",
|
|
766
|
+
"thredz",
|
|
767
|
+
"inspect",
|
|
768
|
+
"runtime",
|
|
769
|
+
];
|
|
770
|
+
|
|
771
|
+
/** Every route key belonging to `group`, in map order. The views render
|
|
772
|
+
* their own surface from this while the handlers are still stubs, so a
|
|
773
|
+
* "not built yet" screen still tells the truth about what is coming. */
|
|
774
|
+
export function routeKeysInGroup(group) {
|
|
775
|
+
return Object.keys(ROUTES).filter((key) => ROUTES[key].group === group);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** Every M3 route key (any route carrying a `group`). */
|
|
779
|
+
export function m3RouteKeys() {
|
|
780
|
+
return Object.keys(ROUTES).filter((key) => typeof ROUTES[key].group === "string");
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** Fill a path template's `:name` segments from `params`, URI-encoding each
|
|
784
|
+
* value. Throws on a missing param — a build bug, never a user input. */
|
|
785
|
+
export function buildPath(template, params = {}) {
|
|
786
|
+
return template.replace(/:([A-Za-z][A-Za-z0-9]*)/g, (_match, name) => {
|
|
787
|
+
const value = params[name];
|
|
788
|
+
if (value === undefined || value === null) {
|
|
789
|
+
throw new Error(`missing route param "${name}" for ${template}`);
|
|
790
|
+
}
|
|
791
|
+
return encodeURIComponent(String(value));
|
|
792
|
+
});
|
|
793
|
+
}
|