@anchrd/intel-api 0.14.0 → 0.16.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.
Files changed (60) hide show
  1. package/dist/adapters/cloudflare/cloudflare.js +0 -68
  2. package/dist/adapters/cloudflare/cloudflare.types.d.ts +0 -39
  3. package/dist/adapters/db/db-flows.js +3 -2
  4. package/dist/adapters/db/db-grants.d.ts +1 -1
  5. package/dist/adapters/db/db-grants.js +1 -1
  6. package/dist/adapters/db/db.js +16 -113
  7. package/dist/adapters/openid/openid.js +1 -1
  8. package/dist/adapters/remote-tools/remote-tools.js +1 -1
  9. package/dist/bundle/bundle.js +31 -141
  10. package/dist/bundle/bundle.types.d.ts +1 -1
  11. package/dist/cli/cli.js +18 -11
  12. package/dist/flows/flows.d.ts +1 -1
  13. package/dist/flows/flows.js +1 -1
  14. package/dist/flows/flows.types.d.ts +4 -1
  15. package/dist/http/http.js +43 -238
  16. package/dist/http/http.types.d.ts +0 -8
  17. package/dist/indexing/indexing.js +18 -89
  18. package/dist/intel/intel.js +4 -9
  19. package/dist/intel/intel.types.d.ts +0 -6
  20. package/dist/mcp/mcp.js +84 -361
  21. package/dist/mcp/mcp.types.d.ts +2 -7
  22. package/dist/nodes/document-links/document-links.d.ts +6 -8
  23. package/dist/nodes/document-links/document-links.js +8 -31
  24. package/dist/nodes/nodes.js +24 -890
  25. package/dist/nodes/nodes.types.d.ts +14 -173
  26. package/dist/tools/tool-servers/tool-servers.d.ts +1 -1
  27. package/dist/tools/tool-servers/tool-servers.js +1 -1
  28. package/dist/tools/tools.js +37 -148
  29. package/dist/tools/tools.types.d.ts +1 -22
  30. package/migrations/0018_no_context_policy_at_last.sql +13 -6
  31. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  32. package/package.json +2 -2
  33. package/dist/adapters/cloudflare-api/cloudflare-api.d.ts +0 -22
  34. package/dist/adapters/cloudflare-api/cloudflare-api.js +0 -306
  35. package/dist/adapters/cloudflare-api/cloudflare-api.types.d.ts +0 -64
  36. package/dist/adapters/cloudflare-api/cloudflare-api.types.js +0 -1
  37. package/dist/adapters/gate-applications/gate-applications.d.ts +0 -23
  38. package/dist/adapters/gate-applications/gate-applications.js +0 -88
  39. package/dist/adapters/tool-delegation/tool-delegation.d.ts +0 -22
  40. package/dist/adapters/tool-delegation/tool-delegation.js +0 -90
  41. package/dist/agent-costs/agent-costs.d.ts +0 -16
  42. package/dist/agent-costs/agent-costs.js +0 -105
  43. package/dist/agent-costs/agent-costs.types.d.ts +0 -30
  44. package/dist/agent-costs/agent-costs.types.js +0 -1
  45. package/dist/agent-runtime/agent-runtime.d.ts +0 -16
  46. package/dist/agent-runtime/agent-runtime.js +0 -150
  47. package/dist/agent-runtime/agent-runtime.types.d.ts +0 -122
  48. package/dist/agent-runtime/agent-runtime.types.js +0 -1
  49. package/dist/model-catalog/model-catalog.d.ts +0 -2
  50. package/dist/model-catalog/model-catalog.js +0 -99
  51. package/dist/model-catalog/model-catalog.types.d.ts +0 -15
  52. package/dist/model-catalog/model-catalog.types.js +0 -1
  53. package/dist/nodes/board/board.d.ts +0 -61
  54. package/dist/nodes/board/board.js +0 -826
  55. package/dist/nodes/board/board.types.d.ts +0 -38
  56. package/dist/nodes/board/board.types.js +0 -1
  57. package/migrations/0013_agents_in_the_tree.sql +0 -76
  58. package/migrations/0014_agent_applications.sql +0 -25
  59. package/migrations/0015_tools_delegated_from_a_connection.sql +0 -15
  60. package/migrations/0016_boards_in_the_tree.sql +0 -80
package/dist/http/http.js CHANGED
@@ -1,4 +1,9 @@
1
- import { AddBoardTaskInput, AppendTableRowsInput, ArchiveFlowInput, ArchiveNodeInput, CancelFlowRunInput, CompleteFlowRunStepInput, ConfigureBoardInput, CreateAgentInput, CreateFlowInput, CreateNodeInput, DefineTableInput, DeleteBoardTaskInput, DeleteTableRowsInput, ExecuteToolInput, GetFlowInput, GetFlowRunInput, GetFlowVersionInput, GetNodeInput, GetNodeVersionInput, ListAgentsInput, ListFlowRunsInput, ListFlowsInput, ListNodesInput, MoveBoardTaskInput, NodeGraphInput, PreviewFlowPublishInput, PublishFlowInput, RedefineTableInput, RelationGraphInput, ResolveNodeLinksInput, RevokeGrantInput, RotateAgentKeyInput, SaveAgentDefinitionInput, SaveAttachmentInput, SaveFlowVersionInput, SaveNodeVersionInput, SearchInput, ShareInput, StartFlowRunInput, TestToolInput, UnpublishFlowInput, UpdateBoardTaskInput, UpdateFlowInput, UpdateNodeInput, UpdateTableRowsInput, } from "@anchrd/intel-contract";
1
+ import { ArchiveFlowInput, CreateFlowInput, GetFlowInput, GetFlowVersionInput, ListFlowsInput, PreviewFlowPublishInput, PublishFlowInput, RelationGraphInput, SaveFlowVersionInput, UnpublishFlowInput, UpdateFlowInput, } from "@anchrd/intel-contract/flow";
2
+ import { CancelFlowRunInput, CompleteFlowRunStepInput, GetFlowRunInput, ListFlowRunsInput, StartFlowRunInput, } from "@anchrd/intel-contract/flow-run";
3
+ import { ArchiveNodeInput, CreateNodeInput, GetNodeInput, GetNodeVersionInput, ListNodesInput, NodeGraphInput, ResolveNodeLinksInput, SaveAttachmentInput, SaveNodeVersionInput, SearchInput, UpdateNodeInput, } from "@anchrd/intel-contract/node";
4
+ import { RevokeGrantInput, ShareInput } from "@anchrd/intel-contract/share";
5
+ import { AppendTableRowsInput, DefineTableInput, DeleteTableRowsInput, RedefineTableInput, UpdateTableRowsInput, } from "@anchrd/intel-contract/table";
6
+ import { ExecuteToolInput, TestToolInput } from "@anchrd/intel-contract/tool";
2
7
  import { Hono } from "hono";
3
8
  import { z } from "zod";
4
9
  import { authorizeBearer, bearer, permits, } from "../shared/gate-authorization/gate-authorization.js";
@@ -78,16 +83,6 @@ function asFlowActor(authorization) {
78
83
  isAdmin: authorization.can("intel", "admin"),
79
84
  };
80
85
  }
81
- // The same `agents/run` the agent Worker asks for, resolved once at Intel's door. `intel/admin`
82
- // opens it for the same reason it opens every other one — `permits` is where that lives.
83
- function asAgentActor(authorization) {
84
- return {
85
- id: authorization.identity.id,
86
- email: authorization.identity.email,
87
- isAdmin: authorization.can("intel", "admin"),
88
- canRun: permits(authorization, "agents", "run"),
89
- };
90
- }
91
86
  export function createHttp(deps) {
92
87
  const app = new Hono();
93
88
  async function authenticated(headers) {
@@ -129,103 +124,20 @@ export function createHttp(deps) {
129
124
  // The caller's own identity, so the shell can name who is signed in. It needs no capability —
130
125
  // the answer is the token's own subject — and it returns nothing but id, email, and name.
131
126
  app.get("/session", (context) => {
132
- const { identity } = context.get("authorization");
127
+ const authorization = context.get("authorization");
128
+ const { identity } = authorization;
133
129
  return context.json({
134
130
  id: identity.id,
135
131
  email: identity.email,
136
132
  name: identity.name ?? null,
133
+ // ⚠️ What the shell may DRAW, never what it may do (#416). Every admin route below still asks
134
+ // `requireCapability` for itself, and a browser that lied here would gain nothing but a
135
+ // button that answers 403.
136
+ isAdmin: authorization.can("intel", "admin"),
137
137
  });
138
138
  });
139
- // What this deployment is equipped to do (#190) — facts about the installation, not about the
140
- // caller, which is why it needs no capability beyond being signed in. `/session` stays identity
141
- // only on purpose; this is the separate answer the UI asks for instead of reading a 503 as one.
142
- // It sits behind the auth middleware like everything else, so an anonymous probe still learns
143
- // nothing about whether a runtime exists.
144
- app.get("/capabilities", (context) => {
145
- return context.json({ agentRuntime: deps.agents.available() });
146
- });
147
- /**
148
- * What the models on offer cost and how much they hold (#257).
149
- *
150
- * ⚠️ Intel asks Cloudflare, never the browser. The account endpoint needs a token and a token does
151
- * not belong in a SPA — which is the sentence the hard-coded table in `packages/ui` used to carry
152
- * as the reason it stayed hard-coded. This route is the answer to it: the figures cross the wire,
153
- * the credential does not.
154
- *
155
- * ⚠️ No capability beyond being signed in. It is a fact about the installation and about nobody's
156
- * data — the same footing `/capabilities` stands on.
157
- *
158
- * ⚠️ The `cache-control` is the caching the ticket asks for, and it is the caching that matters:
159
- * the waste it names is "one fetch every time the select is opened", and that fetch is the
160
- * browser's. A Worker isolate has nowhere durable to keep a table between requests, and inventing
161
- * storage for a list of prices would cost more than the request it saved.
162
- */
163
- app.get("/models", async (context) => {
164
- context.header("cache-control", "private, max-age=3600");
165
- return context.json(await deps.models.list());
166
- });
167
- /**
168
- * The agent runtime, reached through Intel.
169
- *
170
- * ⚠️ This list IS the door. The runtime serves more than what stands here — `/mcp` is the agent's
171
- * own MCP surface, which the portal dials with its own credential (ADR-0005 §7), and
172
- * `/mail/inbound` is authorized by a webhook signature. Neither is a browser path, and neither
173
- * becomes one by being reachable: anything not named below is a 404 from Hono's router, so there
174
- * is no allow-list beside the routes that could go quietly out of step with them.
175
- *
176
- * ⚠️ `agents/run` — not `knowledge/read`. Reading an agent's page is a knowledge read; driving the
177
- * agent is the capability the runtime itself insists on, and Intel refuses on the same terms
178
- * BEFORE the binding is touched, so a denial never reaches the runtime at all.
179
- */
180
- const runtimeRoutes = [
181
- { method: "GET", pattern: "/state", path: () => "/state" },
182
- { method: "GET", pattern: "/runs", path: () => "/runs" },
183
- {
184
- method: "GET",
185
- pattern: "/runs/:runId",
186
- path: (context) => `/runs/${encodeURIComponent(context.req.param("runId") ?? "")}`,
187
- },
188
- { method: "GET", pattern: "/log", path: () => "/log" },
189
- { method: "POST", pattern: "/chat", path: () => "/chat" },
190
- { method: "POST", pattern: "/pause", path: () => "/pause" },
191
- { method: "POST", pattern: "/resume", path: () => "/resume" },
192
- { method: "POST", pattern: "/run", path: () => "/run" },
193
- ];
194
- /**
195
- * What this agent has cost — answered by Intel itself, not forwarded (#251).
196
- *
197
- * ⚠️ It stands under `/agents/:agentId` and is NOT in `runtimeRoutes`, which is the whole point of
198
- * writing it out here. The runtime has no idea what anything costs: the figures come from
199
- * Cloudflare's AI Gateway log, which needs an `AI Gateway: Read` token, and that token belongs in
200
- * Intel rather than in the Worker that executes model-driven tool calls — the runtime's own
201
- * `CLAUDE.md` keeps the number of credentials there deliberately small. The runtime's part is the
202
- * stamp on the model call and nothing more.
203
- *
204
- * ⚠️ `agents/run`, matching the run list this stands beside. What an agent spends is as sensitive
205
- * as what it did, and the two must not be reachable on different terms.
206
- */
207
- app.get("/agents/:agentId/costs", async (context) => {
208
- const authorization = context.get("authorization");
209
- return context.json(await deps.agentCosts.read(asAgentActor(authorization), context.req.param("agentId")));
210
- });
211
- for (const route of runtimeRoutes) {
212
- app.on(route.method, `/agents/:agentId${route.pattern}`, async (context) => {
213
- const authorization = context.get("authorization");
214
- // The runtime's answer is returned untouched, headers included: the chat is a stream, and a
215
- // body read here to inspect it would be a stream nobody could read afterwards.
216
- return await deps.agents.forward({
217
- actor: asAgentActor(authorization),
218
- token: context.get("token"),
219
- agentId: context.req.param("agentId"),
220
- method: route.method,
221
- path: route.path(context),
222
- ...(route.method === "POST" ? { body: await context.req.text() } : {}),
223
- signal: context.req.raw.signal,
224
- });
225
- });
226
- }
227
139
  app.get("/nodes", async (context) => {
228
- const auth = requireCapability(context, "knowledge", "read");
140
+ const auth = requireCapability(context, "nodes", "read");
229
141
  const url = new URL(context.req.url);
230
142
  const unknownQuery = [];
231
143
  url.searchParams.forEach((_value, key) => {
@@ -244,69 +156,24 @@ export function createHttp(deps) {
244
156
  return context.json(await deps.nodes.list(asActor(auth), input));
245
157
  });
246
158
  app.get("/nodes/graph", async (context) => {
247
- const auth = requireCapability(context, "knowledge", "read");
159
+ const auth = requireCapability(context, "nodes", "read");
248
160
  const url = new URL(context.req.url);
249
161
  const input = NodeGraphInput.parse({
250
162
  limit: url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined,
251
163
  });
252
164
  return context.json(await deps.nodes.graph(asActor(auth), input));
253
165
  });
254
- // ⚠️ Before `/nodes/:nodeId`, or the router would read "agents" as a node ID. Same order and same
255
- // reason as `/nodes/graph` above.
256
- app.get("/nodes/agents", async (context) => {
257
- const auth = requireCapability(context, "knowledge", "read");
258
- const url = new URL(context.req.url);
259
- requireKnownQuery(url, ["parentId", "includeArchived"]);
260
- const parentId = url.searchParams.get("parentId");
261
- // ⚠️ The key is only set when the caller sent it. Omitted means the whole tree and `null` means
262
- // the root level, so defaulting a missing parameter to `null` here would silently answer a
263
- // different question than the one asked.
264
- const input = ListAgentsInput.parse({
265
- ...(url.searchParams.has("parentId") ? { parentId: parentId === "" ? null : parentId } : {}),
266
- includeArchived: url.searchParams.get("includeArchived") === "true",
267
- });
268
- return context.json(await deps.nodes.listAgents(asActor(auth), input));
269
- });
270
- /**
271
- * ⚠️ NO response in Intel's HTTP surface carries a credential any more (D29, #207).
272
- *
273
- * Creating an agent still mints its Gate Application with the caller's own bearer — so this needs
274
- * `applications:write` in Gate on top of `knowledge:create` in Intel — but the key Gate issues
275
- * once goes straight into the agent runtime over the service binding, inside this request. The
276
- * answer is the same shape every agent read has. Until #207 it carried the key in plain text and
277
- * a person had to put it into a Worker secret by hand, which is why an agent created here could
278
- * never run (#200).
279
- */
280
- app.post("/nodes/agents", async (context) => {
281
- const auth = requireCapability(context, "knowledge", "create");
282
- const input = CreateAgentInput.parse(await context.req.json().catch(() => null));
283
- return context.json(await deps.nodes.createAgent(asActor(auth), input, { token: context.get("token") }), 201);
284
- });
285
- /**
286
- * The repair path, and the only one an agent that lost its key has (D29, #207).
287
- *
288
- * ⚠️ `knowledge/write`, not `agents/run`. Which principal an agent acts as is a property of the
289
- * agent, changed by whoever may change the agent; being allowed to press "Run now" is a different
290
- * question and answers a different one. The resource ACL on the node is checked by the service.
291
- *
292
- * ⚠️ Before `/nodes/:nodeId`, like every other literal segment under `/nodes`.
293
- */
294
- app.post("/nodes/agents/:nodeId/rotate-key", async (context) => {
295
- const auth = requireCapability(context, "knowledge", "write");
296
- const input = RotateAgentKeyInput.parse({ nodeId: context.req.param("nodeId") });
297
- return context.json(await deps.nodes.rotateAgentKey(asActor(auth), input, { token: context.get("token") }));
298
- });
299
166
  // ⚠️ Before `/nodes/:nodeId`, or the router would read "export" as a node ID. The root has no
300
167
  // node to address, so the whole installation — as this caller may read it — exports here (#136).
301
168
  app.get("/nodes/export", async (context) => {
302
- const auth = requireCapability(context, "knowledge", "read");
169
+ const auth = requireCapability(context, "nodes", "read");
303
170
  return zipResponse(await deps.bundle.exportSubtree(asBundleActor(auth), null));
304
171
  });
305
172
  // The reverse door (#137): a zip bundle — or a naked zipped folder — lands as a new subtree.
306
173
  // The body IS the zip, streamed to the service rather than read whole here; `knowledge/create`
307
174
  // like every other way of making nodes, and the resource-level write check is the service's.
308
175
  async function importBundle(context, targetNodeId) {
309
- const auth = requireCapability(context, "knowledge", "create");
176
+ const auth = requireCapability(context, "nodes", "create");
310
177
  const idempotencyKey = ImportIdempotencyKey.parse(context.req.header("idempotency-key"));
311
178
  const body = context.req.raw.body;
312
179
  if (body === null) {
@@ -324,7 +191,7 @@ export function createHttp(deps) {
324
191
  // ⚠️ Before `/nodes/:nodeId`, or the router would read "import" as a node ID.
325
192
  app.post("/nodes/import", async (context) => await importBundle(context, null));
326
193
  app.get("/nodes/:nodeId", async (context) => {
327
- const auth = requireCapability(context, "knowledge", "read");
194
+ const auth = requireCapability(context, "nodes", "read");
328
195
  return context.json(await deps.nodes.get(asActor(auth), context.req.param("nodeId")));
329
196
  });
330
197
  app.post("/nodes/:nodeId/import", async (context) => {
@@ -333,33 +200,19 @@ export function createHttp(deps) {
333
200
  });
334
201
  // One subtree — a folder with everything beneath it, or a single node — as a zip bundle (#136).
335
202
  app.get("/nodes/:nodeId/export", async (context) => {
336
- const auth = requireCapability(context, "knowledge", "read");
203
+ const auth = requireCapability(context, "nodes", "read");
337
204
  const input = GetNodeInput.parse({ nodeId: context.req.param("nodeId") });
338
205
  return zipResponse(await deps.bundle.exportSubtree(asBundleActor(auth), input.nodeId));
339
206
  });
340
- app.get("/nodes/:nodeId/agent", async (context) => {
341
- const auth = requireCapability(context, "knowledge", "read");
342
- return context.json(await deps.nodes.getAgent(asActor(auth), { nodeId: context.req.param("nodeId") }));
343
- });
344
- app.post("/nodes/:nodeId/agent", async (context) => {
345
- const auth = requireCapability(context, "knowledge", "write");
346
- const input = SaveAgentDefinitionInput.parse(await context.req.json().catch(() => null));
347
- if (input.nodeId !== context.req.param("nodeId")) {
348
- throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
349
- }
350
- // ⚠️ The caller's own bearer travels on, like it does when an agent is created: writing a
351
- // definition also arms what it schedules, and that call is made as the person saving (#214).
352
- return context.json(await deps.nodes.saveAgentDefinition(asActor(auth), input, { token: context.get("token") }), 201);
353
- });
354
207
  app.get("/nodes/:nodeId/versions", async (context) => {
355
- const auth = requireCapability(context, "knowledge", "read");
208
+ const auth = requireCapability(context, "nodes", "read");
356
209
  return context.json(await deps.nodes.listVersions(asActor(auth), context.req.param("nodeId")));
357
210
  });
358
211
  // The content behind one row of the list above (#147): what a version-pinned citation points at.
359
212
  // Read capability like every other node read; the resource ACL and the node/version pairing are
360
213
  // the service's to refuse.
361
214
  app.get("/nodes/:nodeId/versions/:versionId/content", async (context) => {
362
- const auth = requireCapability(context, "knowledge", "read");
215
+ const auth = requireCapability(context, "nodes", "read");
363
216
  const input = GetNodeVersionInput.parse({
364
217
  nodeId: context.req.param("nodeId"),
365
218
  versionId: context.req.param("versionId"),
@@ -371,13 +224,13 @@ export function createHttp(deps) {
371
224
  // resource address says nothing to an HTTP client, whose address for the bytes is the sibling
372
225
  // route below.
373
226
  app.get("/nodes/:nodeId/attachment/meta", async (context) => {
374
- const auth = requireCapability(context, "knowledge", "read");
227
+ const auth = requireCapability(context, "nodes", "read");
375
228
  const input = GetNodeInput.parse({ nodeId: context.req.param("nodeId") });
376
229
  const { node, version } = await deps.nodes.getAttachment(asActor(auth), input.nodeId);
377
230
  return context.json({ node, version });
378
231
  });
379
232
  app.get("/nodes/:nodeId/attachment", async (context) => {
380
- const auth = requireCapability(context, "knowledge", "read");
233
+ const auth = requireCapability(context, "nodes", "read");
381
234
  const { attachment, body } = await deps.nodes.readAttachment(asActor(auth), context.req.param("nodeId"));
382
235
  return new Response(body, {
383
236
  headers: {
@@ -389,13 +242,13 @@ export function createHttp(deps) {
389
242
  });
390
243
  });
391
244
  app.get("/nodes/:nodeId/table", async (context) => {
392
- const auth = requireCapability(context, "knowledge", "read");
245
+ const auth = requireCapability(context, "nodes", "read");
393
246
  return context.json(await deps.nodes.getTable(asActor(auth), context.req.param("nodeId")));
394
247
  });
395
248
  // Defining the header and appending rows are two routes because they are two decisions: the
396
249
  // header is written once and is the contract, an append is the everyday write (#40).
397
250
  app.post("/nodes/:nodeId/table", async (context) => {
398
- const auth = requireCapability(context, "knowledge", "write");
251
+ const auth = requireCapability(context, "nodes", "write");
399
252
  const input = DefineTableInput.parse(await context.req.json().catch(() => null));
400
253
  if (input.nodeId !== context.req.param("nodeId")) {
401
254
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
@@ -403,7 +256,7 @@ export function createHttp(deps) {
403
256
  return context.json(await deps.nodes.defineTable(asActor(auth), input), 201);
404
257
  });
405
258
  app.post("/nodes/:nodeId/table/rows", async (context) => {
406
- const auth = requireCapability(context, "knowledge", "write");
259
+ const auth = requireCapability(context, "nodes", "write");
407
260
  const input = AppendTableRowsInput.parse(await context.req.json().catch(() => null));
408
261
  if (input.nodeId !== context.req.param("nodeId")) {
409
262
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
@@ -415,7 +268,7 @@ export function createHttp(deps) {
415
268
  // the `baseVersionId` that says which state they mean. Each writes one snapshot version, so the
416
269
  // 201 is the same statement the append's 201 makes.
417
270
  app.post("/nodes/:nodeId/table/rows/update", async (context) => {
418
- const auth = requireCapability(context, "knowledge", "write");
271
+ const auth = requireCapability(context, "nodes", "write");
419
272
  const input = UpdateTableRowsInput.parse(await context.req.json().catch(() => null));
420
273
  if (input.nodeId !== context.req.param("nodeId")) {
421
274
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
@@ -423,75 +276,27 @@ export function createHttp(deps) {
423
276
  return context.json(await deps.nodes.updateTableRows(asActor(auth), input), 201);
424
277
  });
425
278
  app.post("/nodes/:nodeId/table/rows/delete", async (context) => {
426
- const auth = requireCapability(context, "knowledge", "write");
279
+ const auth = requireCapability(context, "nodes", "write");
427
280
  const input = DeleteTableRowsInput.parse(await context.req.json().catch(() => null));
428
281
  if (input.nodeId !== context.req.param("nodeId")) {
429
282
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
430
283
  }
431
284
  return context.json(await deps.nodes.deleteTableRows(asActor(auth), input), 201);
432
285
  });
433
- app.post("/nodes/:nodeId/table/redefine", async (context) => {
434
- const auth = requireCapability(context, "knowledge", "write");
286
+ app.patch("/nodes/:nodeId/table", async (context) => {
287
+ const auth = requireCapability(context, "nodes", "write");
435
288
  const input = RedefineTableInput.parse(await context.req.json().catch(() => null));
436
289
  if (input.nodeId !== context.req.param("nodeId")) {
437
290
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
438
291
  }
439
292
  return context.json(await deps.nodes.redefineTable(asActor(auth), input), 201);
440
293
  });
441
- // The board (#285). One read and five task operations, all action-style POSTs for the same reason
442
- // the table mutations are: what they address is a task ID in the body, and none of them has an
443
- // address of its own to PUT to. Every one writes a version, so they all answer 201 like the
444
- // table's do.
445
- app.get("/nodes/:nodeId/board", async (context) => {
446
- const auth = requireCapability(context, "knowledge", "read");
447
- return context.json(await deps.nodes.getBoard(asActor(auth), { nodeId: context.req.param("nodeId") }));
448
- });
449
- app.post("/nodes/:nodeId/board/configure", async (context) => {
450
- const auth = requireCapability(context, "knowledge", "write");
451
- const input = ConfigureBoardInput.parse(await context.req.json().catch(() => null));
452
- if (input.nodeId !== context.req.param("nodeId")) {
453
- throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
454
- }
455
- return context.json(await deps.nodes.configureBoard(asActor(auth), input), 201);
456
- });
457
- app.post("/nodes/:nodeId/board/tasks", async (context) => {
458
- const auth = requireCapability(context, "knowledge", "write");
459
- const input = AddBoardTaskInput.parse(await context.req.json().catch(() => null));
460
- if (input.nodeId !== context.req.param("nodeId")) {
461
- throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
462
- }
463
- return context.json(await deps.nodes.addBoardTask(asActor(auth), input), 201);
464
- });
465
- app.post("/nodes/:nodeId/board/tasks/update", async (context) => {
466
- const auth = requireCapability(context, "knowledge", "write");
467
- const input = UpdateBoardTaskInput.parse(await context.req.json().catch(() => null));
468
- if (input.nodeId !== context.req.param("nodeId")) {
469
- throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
470
- }
471
- return context.json(await deps.nodes.updateBoardTask(asActor(auth), input), 201);
472
- });
473
- app.post("/nodes/:nodeId/board/tasks/move", async (context) => {
474
- const auth = requireCapability(context, "knowledge", "write");
475
- const input = MoveBoardTaskInput.parse(await context.req.json().catch(() => null));
476
- if (input.nodeId !== context.req.param("nodeId")) {
477
- throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
478
- }
479
- return context.json(await deps.nodes.moveBoardTask(asActor(auth), input), 201);
480
- });
481
- app.post("/nodes/:nodeId/board/tasks/delete", async (context) => {
482
- const auth = requireCapability(context, "knowledge", "write");
483
- const input = DeleteBoardTaskInput.parse(await context.req.json().catch(() => null));
484
- if (input.nodeId !== context.req.param("nodeId")) {
485
- throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
486
- }
487
- return context.json(await deps.nodes.deleteBoardTask(asActor(auth), input), 201);
488
- });
489
294
  // ⚠️ There is deliberately no route for the sixth board operation. `board_repair_task_ids`
490
295
  // (anchrd/intel#341) is MCP-only until a screen calls it: the routes here are what the browser
491
296
  // reaches, nothing in `intel-data-provider` asks for it, and a route with no caller is the thing
492
297
  // YAGNI is about. The screen is anchrd/intel#358, and it brings this route with it.
493
298
  app.get("/nodes/:nodeId/links", async (context) => {
494
- const auth = requireCapability(context, "knowledge", "read");
299
+ const auth = requireCapability(context, "nodes", "read");
495
300
  return context.json(await deps.nodes.listLinks(asActor(auth), context.req.param("nodeId")));
496
301
  });
497
302
  // A document link's name, for whoever is reading the text (#41). A POST because the list of IDs
@@ -502,27 +307,27 @@ export function createHttp(deps) {
502
307
  // There is no route to create or delete a link any more. A relationship is written where it is
503
308
  // meant, in the text, and `POST /nodes/:nodeId/versions` is what records it.
504
309
  app.post("/nodes/links/resolve", async (context) => {
505
- const auth = requireCapability(context, "knowledge", "read");
310
+ const auth = requireCapability(context, "nodes", "read");
506
311
  const input = ResolveNodeLinksInput.parse(await context.req.json().catch(() => null));
507
312
  return context.json(await deps.nodes.resolveLinks(asActor(auth), input));
508
313
  });
509
314
  app.post("/nodes/search", async (context) => {
510
- const auth = requireCapability(context, "knowledge", "read");
315
+ const auth = requireCapability(context, "nodes", "read");
511
316
  const input = SearchInput.parse(await context.req.json().catch(() => null));
512
317
  return context.json(await deps.nodes.search(asActor(auth), input));
513
318
  });
514
- app.post("/search/reindex", async (context) => {
319
+ app.post("/nodes/reindex", async (context) => {
515
320
  const auth = requireCapability(context, "intel", "admin");
516
321
  z.strictObject({}).parse(await context.req.json().catch(() => null));
517
322
  return context.json(await deps.nodes.reindex(asActor(auth)), 202);
518
323
  });
519
324
  app.post("/nodes", async (context) => {
520
- const auth = requireCapability(context, "knowledge", "create");
325
+ const auth = requireCapability(context, "nodes", "create");
521
326
  const input = CreateNodeInput.parse(await context.req.json().catch(() => null));
522
327
  return context.json(await deps.nodes.create(asActor(auth), input), 201);
523
328
  });
524
329
  app.post("/nodes/:nodeId/versions", async (context) => {
525
- const auth = requireCapability(context, "knowledge", "write");
330
+ const auth = requireCapability(context, "nodes", "write");
526
331
  const input = SaveNodeVersionInput.parse(await context.req.json().catch(() => null));
527
332
  if (input.nodeId !== context.req.param("nodeId")) {
528
333
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
@@ -530,7 +335,7 @@ export function createHttp(deps) {
530
335
  return context.json(await deps.nodes.save(asActor(auth), input), 201);
531
336
  });
532
337
  app.post("/nodes/:nodeId/attachment", async (context) => {
533
- const auth = requireCapability(context, "knowledge", "write");
338
+ const auth = requireCapability(context, "nodes", "write");
534
339
  const input = SaveAttachmentInput.parse(await context.req.json().catch(() => null));
535
340
  if (input.nodeId !== context.req.param("nodeId")) {
536
341
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
@@ -538,7 +343,7 @@ export function createHttp(deps) {
538
343
  return context.json(await deps.nodes.saveAttachment(asActor(auth), input), 201);
539
344
  });
540
345
  app.patch("/nodes/:nodeId", async (context) => {
541
- const auth = requireCapability(context, "knowledge", "write");
346
+ const auth = requireCapability(context, "nodes", "write");
542
347
  const input = UpdateNodeInput.parse(await context.req.json().catch(() => null));
543
348
  if (input.nodeId !== context.req.param("nodeId")) {
544
349
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
@@ -546,21 +351,21 @@ export function createHttp(deps) {
546
351
  return context.json(await deps.nodes.update(asActor(auth), input));
547
352
  });
548
353
  app.post("/nodes/:nodeId/archive", async (context) => {
549
- const auth = requireCapability(context, "knowledge", "write");
354
+ const auth = requireCapability(context, "nodes", "write");
550
355
  const input = ArchiveNodeInput.parse(await context.req.json().catch(() => null));
551
356
  if (input.nodeId !== context.req.param("nodeId")) {
552
357
  throw new IntelError(400, "node_id_mismatch", "Path and body node IDs differ");
553
358
  }
554
359
  // The caller's own bearer, for the one thing archiving may have to do in Gate: switch off the
555
360
  // Application behind an agent (#182). Every other kind never reaches it.
556
- return context.json(await deps.nodes.archive(asActor(auth), input, { token: context.get("token") }));
361
+ return context.json(await deps.nodes.archive(asActor(auth), input));
557
362
  });
558
363
  app.get("/nodes/:nodeId/grants", async (context) => {
559
- const auth = requireCapability(context, "knowledge", "share");
364
+ const auth = requireCapability(context, "nodes", "share");
560
365
  return context.json(await deps.nodes.listGrants(asActor(auth), context.req.param("nodeId")));
561
366
  });
562
367
  app.post("/nodes/:nodeId/grants", async (context) => {
563
- const auth = requireCapability(context, "knowledge", "share");
368
+ const auth = requireCapability(context, "nodes", "share");
564
369
  const input = ShareInput.parse(await context.req.json().catch(() => null));
565
370
  if (input.resourceId !== context.req.param("nodeId")) {
566
371
  throw new IntelError(400, "node_id_mismatch", "Path and body resource IDs differ");
@@ -568,7 +373,7 @@ export function createHttp(deps) {
568
373
  return context.json(await deps.nodes.share(asActor(auth), input), 201);
569
374
  });
570
375
  app.post("/nodes/:nodeId/grants/:grantId/revoke", async (context) => {
571
- const auth = requireCapability(context, "knowledge", "share");
376
+ const auth = requireCapability(context, "nodes", "share");
572
377
  const input = RevokeGrantInput.parse(await context.req.json().catch(() => null));
573
378
  if (input.resourceId !== context.req.param("nodeId") ||
574
379
  input.grantId !== context.req.param("grantId")) {
@@ -757,7 +562,7 @@ export function createHttp(deps) {
757
562
  const input = GetFlowRunInput.parse({ runId: context.req.param("runId") });
758
563
  return context.json(await deps.flows.listRunSteps(asFlowActor(auth), input.runId));
759
564
  });
760
- app.post("/flow-runs/:runId/complete", async (context) => {
565
+ app.post("/flow-runs/:runId/steps/complete", async (context) => {
761
566
  const auth = requireCapability(context, "flows", "run");
762
567
  const input = CompleteFlowRunStepInput.parse(await context.req.json().catch(() => null));
763
568
  if (input.runId !== context.req.param("runId")) {
@@ -1,10 +1,7 @@
1
1
  import type { GateClient } from "@anchrd/gate-sdk";
2
- import type { AgentCostsService } from "../agent-costs/agent-costs.types.js";
3
- import type { AgentRuntimeService } from "../agent-runtime/agent-runtime.types.js";
4
2
  import type { BrowserAuth } from "../auth/auth.types.js";
5
3
  import type { BundleService } from "../bundle/bundle.types.js";
6
4
  import type { FlowService } from "../flows/flows.types.js";
7
- import type { ModelCatalogService } from "../model-catalog/model-catalog.types.js";
8
5
  import type { NodeService } from "../nodes/nodes.types.js";
9
6
  import type { ToolService } from "../tools/tools.types.js";
10
7
  export interface HttpDeps {
@@ -13,11 +10,6 @@ export interface HttpDeps {
13
10
  flows: FlowService;
14
11
  tools: ToolService;
15
12
  bundle: BundleService;
16
- agents: AgentRuntimeService;
17
- /** What an agent has cost, read out of the AI Gateway log rather than computed here (#251). */
18
- agentCosts: AgentCostsService;
19
- /** What the models on offer cost, read from Cloudflare rather than typed out here (#257). */
20
- models: ModelCatalogService;
21
13
  resource: string;
22
14
  resourceMetadataUrl: string;
23
15
  auth?: Pick<BrowserAuth, "resolve">;