@anchrd/intel-api 0.13.0 → 0.15.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/dist/adapters/cloudflare/cloudflare.js +1 -68
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +0 -39
- package/dist/adapters/db/db-flows.js +1 -1
- package/dist/adapters/db/db-grants.js +1 -1
- package/dist/adapters/db/db-indexing.js +79 -0
- package/dist/adapters/db/db.js +81 -139
- package/dist/adapters/semantic-index/semantic-index.js +97 -17
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +20 -1
- package/dist/bundle/bundle.js +42 -134
- package/dist/cli/cli.js +3 -9
- package/dist/http/http.js +5 -206
- package/dist/http/http.types.d.ts +0 -8
- package/dist/indexing/indexing.js +133 -55
- package/dist/indexing/indexing.types.d.ts +1 -0
- package/dist/intel/intel.js +4 -9
- package/dist/intel/intel.types.d.ts +0 -6
- package/dist/mcp/mcp.js +33 -308
- package/dist/mcp/mcp.types.d.ts +2 -7
- package/dist/nodes/document-links/document-links.d.ts +6 -8
- package/dist/nodes/document-links/document-links.js +8 -31
- package/dist/nodes/nodes.js +92 -826
- package/dist/nodes/nodes.types.d.ts +57 -158
- package/dist/tools/tools.js +37 -148
- package/dist/tools/tools.types.d.ts +0 -21
- package/migrations/0009_no_context_policy.sql +15 -0
- package/migrations/0017_a_vector_per_card.sql +38 -0
- package/migrations/0018_no_context_policy_at_last.sql +97 -0
- package/migrations/0019_one_name_for_the_grants.sql +52 -0
- package/package.json +2 -2
- package/dist/adapters/cloudflare-api/cloudflare-api.d.ts +0 -22
- package/dist/adapters/cloudflare-api/cloudflare-api.js +0 -214
- package/dist/adapters/cloudflare-api/cloudflare-api.types.d.ts +0 -64
- package/dist/adapters/cloudflare-api/cloudflare-api.types.js +0 -1
- package/dist/adapters/gate-applications/gate-applications.d.ts +0 -23
- package/dist/adapters/gate-applications/gate-applications.js +0 -88
- package/dist/adapters/tool-delegation/tool-delegation.d.ts +0 -22
- package/dist/adapters/tool-delegation/tool-delegation.js +0 -90
- package/dist/agent-costs/agent-costs.d.ts +0 -16
- package/dist/agent-costs/agent-costs.js +0 -105
- package/dist/agent-costs/agent-costs.types.d.ts +0 -30
- package/dist/agent-costs/agent-costs.types.js +0 -1
- package/dist/agent-runtime/agent-runtime.d.ts +0 -16
- package/dist/agent-runtime/agent-runtime.js +0 -150
- package/dist/agent-runtime/agent-runtime.types.d.ts +0 -122
- package/dist/agent-runtime/agent-runtime.types.js +0 -1
- package/dist/model-catalog/model-catalog.d.ts +0 -2
- package/dist/model-catalog/model-catalog.js +0 -99
- package/dist/model-catalog/model-catalog.types.d.ts +0 -15
- package/dist/model-catalog/model-catalog.types.js +0 -1
- package/dist/nodes/board/board.d.ts +0 -59
- package/dist/nodes/board/board.js +0 -528
- package/dist/nodes/board/board.types.d.ts +0 -31
- package/dist/nodes/board/board.types.js +0 -1
- package/migrations/0013_agents_in_the_tree.sql +0 -76
- package/migrations/0014_agent_applications.sql +0 -25
- package/migrations/0015_tools_delegated_from_a_connection.sql +0 -15
- package/migrations/0016_boards_in_the_tree.sql +0 -80
package/dist/bundle/bundle.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BlockNoteMediaType, BundleImportResult, BundleManifest, BundleManifestFilename, DocumentLinkInlineType, FlowGraph, TableMediaType, } from "@anchrd/intel-contract";
|
|
2
2
|
import { Unzip, UnzipInflate, Zip, ZipDeflate, ZipPassThrough } from "fflate";
|
|
3
|
-
import { repeatedBoardId, upgradeStoredBoard } from "../nodes/board/board.js";
|
|
4
3
|
import { documentLinkTargets } from "../nodes/document-links/document-links.js";
|
|
5
4
|
import { parseCsv } from "../shared/csv/csv.js";
|
|
6
5
|
import { IntelError } from "../shared/intel-error/intel-error.js";
|
|
@@ -217,13 +216,11 @@ function nakedKindOf(name) {
|
|
|
217
216
|
if (lower.endsWith(".csv")) {
|
|
218
217
|
return { kind: "table", mediaType: TableMediaType, title: name.slice(0, -4) };
|
|
219
218
|
}
|
|
220
|
-
// ⚠️
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// is
|
|
224
|
-
|
|
225
|
-
return { kind: "board", mediaType: BoardMediaType, title: name.slice(0, -11) };
|
|
226
|
-
}
|
|
219
|
+
// ⚠️ `.board.json` had its own rule here until #390 and deliberately does not any more. This is
|
|
220
|
+
// the NAKED-folder path — a directory somebody picked, with no manifest — so a file called
|
|
221
|
+
// `plan.board.json` is a JSON file on a disk, and keeping it as an attachment preserves it. A
|
|
222
|
+
// real export is a different matter: its manifest declares the kind, and that is where such an
|
|
223
|
+
// entry is refused by name.
|
|
227
224
|
return { kind: "attachment", mediaType: guessedMediaType(lower), title: name };
|
|
228
225
|
}
|
|
229
226
|
const KnownMediaTypes = {
|
|
@@ -402,41 +399,11 @@ export function createBundle(deps) {
|
|
|
402
399
|
},
|
|
403
400
|
};
|
|
404
401
|
}
|
|
405
|
-
// A
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
manifest: {
|
|
412
|
-
id: node.id,
|
|
413
|
-
kind: "board",
|
|
414
|
-
title: node.title,
|
|
415
|
-
description: node.description,
|
|
416
|
-
mediaType: version?.mediaType ?? BoardMediaType,
|
|
417
|
-
path,
|
|
418
|
-
},
|
|
419
|
-
content: version === null
|
|
420
|
-
? { type: "text", load: async () => "" }
|
|
421
|
-
: { type: "text", load: textLoader(version.contentKey) },
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
if (node.kind === "agent") {
|
|
425
|
-
const path = `${directory}${uniqueName(used, base, ".json")}`;
|
|
426
|
-
return {
|
|
427
|
-
manifest: {
|
|
428
|
-
id: node.id,
|
|
429
|
-
kind: "agent",
|
|
430
|
-
title: node.title,
|
|
431
|
-
description: node.description,
|
|
432
|
-
mediaType: version?.mediaType ?? "application/json",
|
|
433
|
-
path,
|
|
434
|
-
},
|
|
435
|
-
content: version === null
|
|
436
|
-
? { type: "text", load: async () => "" }
|
|
437
|
-
: { type: "text", load: textLoader(version.contentKey) },
|
|
438
|
-
};
|
|
439
|
-
}
|
|
402
|
+
// ⚠️ A row of a kind this build no longer makes — an `agent` or a `board` written before #390
|
|
403
|
+
// — falls through to the document branch and travels as its raw body. Deliberately forgiving:
|
|
404
|
+
// refusing here would make one leftover row enough to stop a whole subtree from being exported,
|
|
405
|
+
// and the file it produces is exactly the bytes that were stored. The IMPORT is where such a
|
|
406
|
+
// bundle is turned away, by name.
|
|
440
407
|
// A document: markdown stays `.md`, a BlockNote body is JSON and says so with its name.
|
|
441
408
|
const mediaType = version?.mediaType ?? "text/markdown";
|
|
442
409
|
const extension = mediaType === BlockNoteMediaType ? ".json" : ".md";
|
|
@@ -926,101 +893,42 @@ export function createBundle(deps) {
|
|
|
926
893
|
if (header.length === 0 || header.every((column) => column.trim().length === 0)) {
|
|
927
894
|
throw new IntelError(400, "import_invalid_table", `Bundle entry is not a table with a header row: ${entry.path}`);
|
|
928
895
|
}
|
|
896
|
+
// ⚠️ Distinct column names, read the same way `DefineTableInput` and
|
|
897
|
+
// `RedefineTableInput` read them — case-insensitively (#322). Both refuse a duplicate,
|
|
898
|
+
// and the import used to walk past both: a table could only enter the tree this way
|
|
899
|
+
// with two columns of one name.
|
|
900
|
+
//
|
|
901
|
+
// The consumer assumes the opposite and says so. `packages/ui/src/node-table` keys its
|
|
902
|
+
// header cells and every row cell with the column name and carries the comment
|
|
903
|
+
// "Column names are distinct by contract (`DefineTableInput`)" — true for the two write
|
|
904
|
+
// paths, and false for this one. Two equal names are two React keys with one value, in
|
|
905
|
+
// the header AND in every row.
|
|
906
|
+
const seen = new Set(header.map((column) => column.trim().toLowerCase()));
|
|
907
|
+
if (seen.size !== header.length) {
|
|
908
|
+
throw new IntelError(400, "import_invalid_table", `Bundle entry has two columns of the same name: ${entry.path}`);
|
|
909
|
+
}
|
|
929
910
|
// The whole file as one snapshot: the same shape a definition or a redefine writes
|
|
930
911
|
// (#135), so reading starts here and nothing older is expected to exist.
|
|
931
912
|
version = versionRowFor(entry, text, TableMediaType, await deps.hash(text), "snapshot");
|
|
932
913
|
}
|
|
933
914
|
}
|
|
934
|
-
else if (entry.kind === "board") {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
* truth here — everywhere else a task id is minted by `deps.id()` and a status list
|
|
952
|
-
* goes through `ConfigureBoardInput`, which has demanded distinct ids since #285. Two
|
|
953
|
-
* tasks under one id threw `graph.addNode` out of the board's graph view, and two
|
|
954
|
-
* statuses under one id drew the same cards in two lanes with a drop that had two
|
|
955
|
-
* targets meaning one.
|
|
956
|
-
*
|
|
957
|
-
* Outside the catch above on purpose: swallowed into `import_invalid_board` this would
|
|
958
|
-
* read as "that file is not a board", and whoever is holding the file would look for
|
|
959
|
-
* the wrong thing. `repeatedBoardId` names the id, which is the line they have to fix.
|
|
960
|
-
*/
|
|
961
|
-
const repeated = repeatedBoardId(document);
|
|
962
|
-
if (repeated !== null) {
|
|
963
|
-
throw new IntelError(400, "import_duplicate_board_id", repeated.list === "tasks"
|
|
964
|
-
? `Bundle entry names the task “${repeated.id}” twice: ${entry.path}`
|
|
965
|
-
: `Bundle entry names the status “${repeated.id}” twice: ${entry.path}`);
|
|
966
|
-
}
|
|
967
|
-
// ⚠️ `references` are remapped the way a document's inline links are, and `dependsOn`
|
|
968
|
-
// and `parentId` are NOT (#285). A task's references point at NODES, which the import
|
|
969
|
-
// renumbers; its dependencies and its parent point at tasks inside this very file,
|
|
970
|
-
// whose ids the import does not touch. Renumbering those would break every edge in the
|
|
971
|
-
// board for no reason.
|
|
972
|
-
const remapped = {
|
|
973
|
-
statuses: document.statuses,
|
|
974
|
-
tasks: document.tasks.map((task) => ({
|
|
975
|
-
...task,
|
|
976
|
-
references: task.references.map((reference) => idMap.get(reference) ?? reference),
|
|
977
|
-
})),
|
|
978
|
-
};
|
|
979
|
-
const body = JSON.stringify(remapped);
|
|
980
|
-
version = versionRowFor(entry, body, BoardMediaType, await deps.hash(body), null);
|
|
981
|
-
// Into the same link pass as an imported document: a board's references are `text`
|
|
982
|
-
// links, so they have to survive the import the same way.
|
|
983
|
-
documentBodies.set(entry.newId, { mediaType: BoardMediaType, content: body });
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
else if (entry.kind === "agent") {
|
|
987
|
-
const text = entry.body === null ? "" : decodeText(entry.body, entry.path);
|
|
988
|
-
if (text.trim().length > 0) {
|
|
989
|
-
let definition;
|
|
990
|
-
try {
|
|
991
|
-
definition = AgentDefinition.parse(JSON.parse(text));
|
|
992
|
-
}
|
|
993
|
-
catch {
|
|
994
|
-
throw new IntelError(400, "import_invalid_agent", `Bundle entry is not an agent definition: ${entry.path}`);
|
|
995
|
-
}
|
|
996
|
-
const remapped = {
|
|
997
|
-
...definition,
|
|
998
|
-
/**
|
|
999
|
-
* ⚠️ A delegation NEVER travels in a file (#208, D30). `tools.delegatedBy` names
|
|
1000
|
-
* whose portal connection an agent acts on, and the only thing that may write it is
|
|
1001
|
-
* a person saving the agent in a session Intel authorized — the save path proves
|
|
1002
|
-
* they reach every named server first. A bundle is a document somebody hands over;
|
|
1003
|
-
* carrying the field through would let a crafted file name a local user and hand an
|
|
1004
|
-
* imported agent that person's connection, with no act of theirs anywhere.
|
|
1005
|
-
*
|
|
1006
|
-
* The consequence is deliberate and mild: an imported agent arrives with no tools,
|
|
1007
|
-
* and whoever wants it to have some gives them in the profile — which is the same
|
|
1008
|
-
* act D30 made the only way in.
|
|
1009
|
-
*/
|
|
1010
|
-
tools: null,
|
|
1011
|
-
references: definition.references.map((reference) => idMap.has(reference.nodeId)
|
|
1012
|
-
? { ...reference, nodeId: idMap.get(reference.nodeId) }
|
|
1013
|
-
: reference),
|
|
1014
|
-
schedules: definition.schedules.map((schedule) => idMap.has(schedule.target.id)
|
|
1015
|
-
? {
|
|
1016
|
-
...schedule,
|
|
1017
|
-
target: { ...schedule.target, id: idMap.get(schedule.target.id) },
|
|
1018
|
-
}
|
|
1019
|
-
: schedule),
|
|
1020
|
-
};
|
|
1021
|
-
const body = JSON.stringify(remapped);
|
|
1022
|
-
version = versionRowFor(entry, body, AgentMediaType, await deps.hash(body), null);
|
|
1023
|
-
}
|
|
915
|
+
else if (entry.kind === "board" || entry.kind === "agent") {
|
|
916
|
+
/**
|
|
917
|
+
* ⚠️ Refused by name, and the name says WHY rather than what the parser tripped over.
|
|
918
|
+
*
|
|
919
|
+
* Agents and Boards were parked with #385; nothing in this build can hold either kind.
|
|
920
|
+
* A bundle that carries one is not malformed — it is a correct export of an installation
|
|
921
|
+
* that still had the feature, and the person holding it is usually moving a tree between
|
|
922
|
+
* two installations rather than the author of the file. Left to the schema they would get
|
|
923
|
+
* `unexpected enum value`, look for a broken file, and find nothing wrong with it.
|
|
924
|
+
*
|
|
925
|
+
* The refusal names the entry, the kind and the branch the feature is parked on, so the
|
|
926
|
+
* answer to "where did my board go" is in the message and not in somebody's memory.
|
|
927
|
+
*
|
|
928
|
+
* ⚠️ Only a MANIFEST can reach this, because only a manifest declares a kind. A naked
|
|
929
|
+
* folder holding a `plan.board.json` imports it as an attachment — see `kindOfFile`.
|
|
930
|
+
*/
|
|
931
|
+
throw new IntelError(400, "import_kind_parked", `Bundle entry “${entry.path}” is a ${entry.kind}, and ${entry.kind}s are not part of this installation any more (anchrd/intel#385). The bundle was exported from a version that still had them; the code is on the branch parked/agents-and-board. Remove the entry from the bundle to import the rest.`);
|
|
1024
932
|
}
|
|
1025
933
|
else if (entry.kind === "attachment") {
|
|
1026
934
|
const bytes = entry.body ?? new Uint8Array(0);
|
package/dist/cli/cli.js
CHANGED
|
@@ -12,15 +12,9 @@ const interfaces = [
|
|
|
12
12
|
functions: ["read", "create", "write", "publish", "run", "share"],
|
|
13
13
|
},
|
|
14
14
|
{ handle: "tools", functions: ["read", "test", "execute", "admin"] },
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// anywhere else would never appear in the rules that `can("agents", "run")` reads.
|
|
19
|
-
//
|
|
20
|
-
// Without it there is no state between "administrator" and "not at all": the runtime falls back to
|
|
21
|
-
// `intel:admin`, which is the first-grant exit in the same sense as everywhere else, not the way a
|
|
22
|
-
// team lets a colleague talk to an agent.
|
|
23
|
-
{ handle: "agents", functions: ["run"] },
|
|
15
|
+
// ⚠️ `agents:run` is deliberately NOT here any more (#390). `bootstrap` declares, it never
|
|
16
|
+
// revokes, so an installation that has the interface keeps it as a harmless leftover — but a new
|
|
17
|
+
// one is not asked to decide about a permission nothing reads.
|
|
24
18
|
// `mcp:connect` means the same thing at every MCP service of this installation. A separate name
|
|
25
19
|
// for the same thing forces the operator to check, service by service, which permission carries
|
|
26
20
|
// portal access.
|
package/dist/http/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppendTableRowsInput, ArchiveFlowInput, ArchiveNodeInput, CancelFlowRunInput, CompleteFlowRunStepInput, CreateFlowInput, CreateNodeInput, DefineTableInput, DeleteTableRowsInput, ExecuteToolInput, GetFlowInput, GetFlowRunInput, GetFlowVersionInput, GetNodeInput, GetNodeVersionInput, ListFlowRunsInput, ListFlowsInput, ListNodesInput, NodeGraphInput, PreviewFlowPublishInput, PublishFlowInput, RedefineTableInput, RelationGraphInput, ResolveNodeLinksInput, RevokeGrantInput, SaveAttachmentInput, SaveFlowVersionInput, SaveNodeVersionInput, SearchInput, ShareInput, StartFlowRunInput, TestToolInput, UnpublishFlowInput, UpdateFlowInput, UpdateNodeInput, UpdateTableRowsInput, } from "@anchrd/intel-contract";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { authorizeBearer, bearer, permits, } from "../shared/gate-authorization/gate-authorization.js";
|
|
@@ -78,16 +78,6 @@ function asFlowActor(authorization) {
|
|
|
78
78
|
isAdmin: authorization.can("intel", "admin"),
|
|
79
79
|
};
|
|
80
80
|
}
|
|
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
81
|
export function createHttp(deps) {
|
|
92
82
|
const app = new Hono();
|
|
93
83
|
async function authenticated(headers) {
|
|
@@ -136,94 +126,6 @@ export function createHttp(deps) {
|
|
|
136
126
|
name: identity.name ?? null,
|
|
137
127
|
});
|
|
138
128
|
});
|
|
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
129
|
app.get("/nodes", async (context) => {
|
|
228
130
|
const auth = requireCapability(context, "knowledge", "read");
|
|
229
131
|
const url = new URL(context.req.url);
|
|
@@ -251,51 +153,6 @@ export function createHttp(deps) {
|
|
|
251
153
|
});
|
|
252
154
|
return context.json(await deps.nodes.graph(asActor(auth), input));
|
|
253
155
|
});
|
|
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
156
|
// ⚠️ Before `/nodes/:nodeId`, or the router would read "export" as a node ID. The root has no
|
|
300
157
|
// node to address, so the whole installation — as this caller may read it — exports here (#136).
|
|
301
158
|
app.get("/nodes/export", async (context) => {
|
|
@@ -337,20 +194,6 @@ export function createHttp(deps) {
|
|
|
337
194
|
const input = GetNodeInput.parse({ nodeId: context.req.param("nodeId") });
|
|
338
195
|
return zipResponse(await deps.bundle.exportSubtree(asBundleActor(auth), input.nodeId));
|
|
339
196
|
});
|
|
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
197
|
app.get("/nodes/:nodeId/versions", async (context) => {
|
|
355
198
|
const auth = requireCapability(context, "knowledge", "read");
|
|
356
199
|
return context.json(await deps.nodes.listVersions(asActor(auth), context.req.param("nodeId")));
|
|
@@ -438,54 +281,10 @@ export function createHttp(deps) {
|
|
|
438
281
|
}
|
|
439
282
|
return context.json(await deps.nodes.redefineTable(asActor(auth), input), 201);
|
|
440
283
|
});
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
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
|
-
});
|
|
284
|
+
// ⚠️ There is deliberately no route for the sixth board operation. `board_repair_task_ids`
|
|
285
|
+
// (anchrd/intel#341) is MCP-only until a screen calls it: the routes here are what the browser
|
|
286
|
+
// reaches, nothing in `intel-data-provider` asks for it, and a route with no caller is the thing
|
|
287
|
+
// YAGNI is about. The screen is anchrd/intel#358, and it brings this route with it.
|
|
489
288
|
app.get("/nodes/:nodeId/links", async (context) => {
|
|
490
289
|
const auth = requireCapability(context, "knowledge", "read");
|
|
491
290
|
return context.json(await deps.nodes.listLinks(asActor(auth), context.req.param("nodeId")));
|
|
@@ -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">;
|