@anchrd/intel-contract 0.5.0 → 0.7.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/contract/contract.d.ts +176 -43
- package/dist/contract/contract.js +162 -35
- package/package.json +1 -1
|
@@ -496,18 +496,53 @@ export declare const AgentModel: z.ZodObject<{
|
|
|
496
496
|
}, z.core.$strict>;
|
|
497
497
|
export type AgentModel = z.infer<typeof AgentModel>;
|
|
498
498
|
/**
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
* runtime configuration, and the tool catalog is a live `tools/list` that is never mirrored.
|
|
499
|
+
* One MCP server as the portal names it. The handle is what the portal puts in front of every tool
|
|
500
|
+
* that server offers (`notion_notion-search` belongs to `notion`), and it is the only identifier
|
|
501
|
+
* Intel can both store and recognise again in a live `tools/list`.
|
|
503
502
|
*
|
|
504
|
-
* ⚠️
|
|
505
|
-
* (`
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* tomorrow. The asymmetry is the point, not an oversight.
|
|
503
|
+
* ⚠️ A handle is never invented from a tool name. Which servers exist is the portal's answer
|
|
504
|
+
* (`portal_list_servers`), and the prefix is only used to attribute a tool to a server that answer
|
|
505
|
+
* already named — see `packages/api/src/tools/tool-servers` for why splitting on the underscore
|
|
506
|
+
* alone would be ambiguous.
|
|
509
507
|
*/
|
|
508
|
+
export declare const ToolServerHandle: z.ZodString;
|
|
509
|
+
export type ToolServerHandle = z.infer<typeof ToolServerHandle>;
|
|
510
|
+
/**
|
|
511
|
+
* What a caller may ASK for: whole MCP servers, and nothing about who delegates them (D30).
|
|
512
|
+
*
|
|
513
|
+
* ⚠️ The absence of `delegatedBy` is the point, and it is why the write shape differs from the read
|
|
514
|
+
* shape at all. Intel writes that field from the session it authorized; a caller who could name
|
|
515
|
+
* somebody else would be handing an agent a portal connection they do not have, and the agent would
|
|
516
|
+
* act on it unattended. Leaving the field out of the input makes that structural instead of a
|
|
517
|
+
* runtime overwrite: a body carrying it is a parse error at the boundary, on every surface, and no
|
|
518
|
+
* screen ever has to invent a value it has no business knowing.
|
|
519
|
+
*/
|
|
520
|
+
export declare const AgentToolSelection: z.ZodObject<{
|
|
521
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
522
|
+
}, z.core.$strict>;
|
|
523
|
+
export type AgentToolSelection = z.infer<typeof AgentToolSelection>;
|
|
524
|
+
/**
|
|
525
|
+
* What is STORED and read back: the selection plus whose portal connection it came from (D30).
|
|
526
|
+
*
|
|
527
|
+
* ⚠️ This is a selection, not a permission. Nothing here grants anything: whether a server is
|
|
528
|
+
* reachable is still decided by one live `tools/list` with `delegatedBy`'s own portal token, so a
|
|
529
|
+
* delegator who loses the server or the connection takes it away from the agent at the next run
|
|
530
|
+
* with no edit to this document.
|
|
531
|
+
*
|
|
532
|
+
* ⚠️ Read shape only. It appears in `AgentDefinition` and never in an input — see
|
|
533
|
+
* `AgentToolSelection` for why the two are deliberately different documents rather than one schema
|
|
534
|
+
* with an optional field.
|
|
535
|
+
*/
|
|
536
|
+
export declare const AgentToolDelegation: z.ZodObject<{
|
|
537
|
+
delegatedBy: z.ZodString;
|
|
538
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
539
|
+
}, z.core.$strict>;
|
|
540
|
+
export type AgentToolDelegation = z.infer<typeof AgentToolDelegation>;
|
|
510
541
|
export declare const AgentDefinition: z.ZodObject<{
|
|
542
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
543
|
+
delegatedBy: z.ZodString;
|
|
544
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
545
|
+
}, z.core.$strict>>>;
|
|
511
546
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
512
547
|
nodeId: z.ZodString;
|
|
513
548
|
role: z.ZodEnum<{
|
|
@@ -535,10 +570,55 @@ export declare const AgentDefinition: z.ZodObject<{
|
|
|
535
570
|
}, z.core.$strict>;
|
|
536
571
|
}, z.core.$strict>;
|
|
537
572
|
export type AgentDefinition = z.infer<typeof AgentDefinition>;
|
|
573
|
+
/**
|
|
574
|
+
* The same document as `AgentDefinition`, minus the one field a caller may not write.
|
|
575
|
+
*
|
|
576
|
+
* ⚠️ Two schemas rather than one, and the split is load-bearing (#208, D30). Everything an agent IS
|
|
577
|
+
* comes from whoever edits it; **whose portal connection it acts on** does not, because that is an
|
|
578
|
+
* authority the editor would be granting to themselves. So the write shape simply has no place to
|
|
579
|
+
* put it: `{ tools: { servers: [...] } }` is what a screen or an MCP client sends, Intel adds
|
|
580
|
+
* `delegatedBy` from the session, and a body that tries to name one is refused by the strict object
|
|
581
|
+
* before any of it is read. The reading shape keeps the field because a reader must be able to see
|
|
582
|
+
* whose connection an agent runs on.
|
|
583
|
+
*/
|
|
584
|
+
export declare const AgentDefinitionInput: z.ZodObject<{
|
|
585
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
586
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
587
|
+
}, z.core.$strict>>>;
|
|
588
|
+
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
589
|
+
nodeId: z.ZodString;
|
|
590
|
+
role: z.ZodEnum<{
|
|
591
|
+
"system-message": "system-message";
|
|
592
|
+
"semantic-context": "semantic-context";
|
|
593
|
+
memory: "memory";
|
|
594
|
+
}>;
|
|
595
|
+
}, z.core.$strict>>>;
|
|
596
|
+
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
597
|
+
cron: z.ZodString;
|
|
598
|
+
target: z.ZodObject<{
|
|
599
|
+
kind: z.ZodEnum<{
|
|
600
|
+
document: "document";
|
|
601
|
+
flow: "flow";
|
|
602
|
+
}>;
|
|
603
|
+
id: z.ZodString;
|
|
604
|
+
}, z.core.$strict>;
|
|
605
|
+
}, z.core.$strict>>>;
|
|
606
|
+
model: z.ZodObject<{
|
|
607
|
+
provider: z.ZodEnum<{
|
|
608
|
+
"workers-ai": "workers-ai";
|
|
609
|
+
anthropic: "anthropic";
|
|
610
|
+
}>;
|
|
611
|
+
model: z.ZodString;
|
|
612
|
+
}, z.core.$strict>;
|
|
613
|
+
}, z.core.$strict>;
|
|
614
|
+
export type AgentDefinitionInput = z.infer<typeof AgentDefinitionInput>;
|
|
538
615
|
export declare const SaveAgentDefinitionInput: z.ZodObject<{
|
|
539
616
|
nodeId: z.ZodString;
|
|
540
617
|
baseVersionId: z.ZodNullable<z.ZodString>;
|
|
541
618
|
definition: z.ZodObject<{
|
|
619
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
620
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
621
|
+
}, z.core.$strict>>>;
|
|
542
622
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
543
623
|
nodeId: z.ZodString;
|
|
544
624
|
role: z.ZodEnum<{
|
|
@@ -597,6 +677,9 @@ export declare const CreateAgentInput: z.ZodObject<{
|
|
|
597
677
|
title: z.ZodString;
|
|
598
678
|
description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
599
679
|
definition: z.ZodObject<{
|
|
680
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
681
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
682
|
+
}, z.core.$strict>>>;
|
|
600
683
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
601
684
|
nodeId: z.ZodString;
|
|
602
685
|
role: z.ZodEnum<{
|
|
@@ -662,6 +745,10 @@ export declare const NodeAgent: z.ZodObject<{
|
|
|
662
745
|
createdAt: z.ZodISODateTime;
|
|
663
746
|
}, z.core.$strict>>;
|
|
664
747
|
definition: z.ZodNullable<z.ZodObject<{
|
|
748
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
749
|
+
delegatedBy: z.ZodString;
|
|
750
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
751
|
+
}, z.core.$strict>>>;
|
|
665
752
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
666
753
|
nodeId: z.ZodString;
|
|
667
754
|
role: z.ZodEnum<{
|
|
@@ -692,36 +779,19 @@ export declare const NodeAgent: z.ZodObject<{
|
|
|
692
779
|
}, z.core.$strict>;
|
|
693
780
|
export type NodeAgent = z.infer<typeof NodeAgent>;
|
|
694
781
|
/**
|
|
695
|
-
*
|
|
782
|
+
* ⚠️ There is NO key field in this file, and adding one back would be the regression (D29, #207).
|
|
696
783
|
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
784
|
+
* Until #207 the create answer carried the Application key in plain text, once, and a person had to
|
|
785
|
+
* carry it into a Worker secret by hand — which is why an agent created through the screen could
|
|
786
|
+
* never run (#200). The key now goes from Gate straight into the agent runtime over Intel's service
|
|
787
|
+
* binding and is encrypted into that agent's Durable Object; it reaches no browser, no MCP tool
|
|
788
|
+
* result and no response body at all. `NodeAgent` is a `z.strictObject`, so a field named `key`
|
|
789
|
+
* added anywhere in this file is a parse error at the boundary rather than a leak somebody has to
|
|
790
|
+
* spot in review.
|
|
703
791
|
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*/
|
|
708
|
-
export declare const AgentApplicationKey: z.ZodObject<{
|
|
709
|
-
agentId: z.ZodString;
|
|
710
|
-
applicationId: z.ZodString;
|
|
711
|
-
key: z.ZodString;
|
|
712
|
-
notice: z.ZodString;
|
|
713
|
-
}, z.core.$strict>;
|
|
714
|
-
export type AgentApplicationKey = z.infer<typeof AgentApplicationKey>;
|
|
715
|
-
/**
|
|
716
|
-
* What `POST /nodes/agents` and `agent_create` answer, and the only shape carrying a key. Every
|
|
717
|
-
* other agent read answers with the plain `NodeAgent` above.
|
|
718
|
-
*
|
|
719
|
-
* ⚠️ `applicationKey` is `null` on a REPLAY, and that is the honest answer rather than a gap. An
|
|
720
|
-
* idempotency key repeated after the first response was lost still returns the agent that exists —
|
|
721
|
-
* the promise every create in Intel makes — but the key belonged to the one response that created
|
|
722
|
-
* it and is gone from Gate. Minting a second principal to fill this field would leave the
|
|
723
|
-
* installation with two machine accounts for one agent, one of which nobody would ever switch off.
|
|
724
|
-
* `application_rotate_key` in Gate is the way to a new key, and it is a deliberate act.
|
|
792
|
+
* What `POST /nodes/agents` and `agent_create` answer is therefore exactly what every read answers:
|
|
793
|
+
* the node, its first definition, and the `applicationId` that NAMES the principal without
|
|
794
|
+
* authenticating it.
|
|
725
795
|
*/
|
|
726
796
|
export declare const CreatedAgent: z.ZodObject<{
|
|
727
797
|
node: z.ZodObject<{
|
|
@@ -758,6 +828,10 @@ export declare const CreatedAgent: z.ZodObject<{
|
|
|
758
828
|
createdAt: z.ZodISODateTime;
|
|
759
829
|
}, z.core.$strict>>;
|
|
760
830
|
definition: z.ZodNullable<z.ZodObject<{
|
|
831
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
832
|
+
delegatedBy: z.ZodString;
|
|
833
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
834
|
+
}, z.core.$strict>>>;
|
|
761
835
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
762
836
|
nodeId: z.ZodString;
|
|
763
837
|
role: z.ZodEnum<{
|
|
@@ -785,14 +859,26 @@ export declare const CreatedAgent: z.ZodObject<{
|
|
|
785
859
|
}, z.core.$strict>;
|
|
786
860
|
}, z.core.$strict>>;
|
|
787
861
|
applicationId: z.ZodNullable<z.ZodString>;
|
|
788
|
-
applicationKey: z.ZodNullable<z.ZodObject<{
|
|
789
|
-
agentId: z.ZodString;
|
|
790
|
-
applicationId: z.ZodString;
|
|
791
|
-
key: z.ZodString;
|
|
792
|
-
notice: z.ZodString;
|
|
793
|
-
}, z.core.$strict>>;
|
|
794
862
|
}, z.core.$strict>;
|
|
795
863
|
export type CreatedAgent = z.infer<typeof CreatedAgent>;
|
|
864
|
+
export declare const RotateAgentKeyInput: z.ZodObject<{
|
|
865
|
+
nodeId: z.ZodString;
|
|
866
|
+
}, z.core.$strict>;
|
|
867
|
+
export type RotateAgentKeyInput = z.infer<typeof RotateAgentKeyInput>;
|
|
868
|
+
/**
|
|
869
|
+
* What replacing an agent's key answers.
|
|
870
|
+
*
|
|
871
|
+
* ⚠️ No key, and that is the whole shape of D29: Intel asks Gate for a new one, hands it to the
|
|
872
|
+
* runtime over the service binding, and forgets it inside the same call. What the caller gets is
|
|
873
|
+
* the fact that it happened, so a screen can say so — `applicationId` names the principal whose key
|
|
874
|
+
* was replaced, which is a name and not a credential.
|
|
875
|
+
*/
|
|
876
|
+
export declare const AgentKeyRotated: z.ZodObject<{
|
|
877
|
+
nodeId: z.ZodString;
|
|
878
|
+
applicationId: z.ZodString;
|
|
879
|
+
rotatedAt: z.ZodISODateTime;
|
|
880
|
+
}, z.core.$strict>;
|
|
881
|
+
export type AgentKeyRotated = z.infer<typeof AgentKeyRotated>;
|
|
796
882
|
export declare const AgentList: z.ZodObject<{
|
|
797
883
|
items: z.ZodArray<z.ZodObject<{
|
|
798
884
|
id: z.ZodString;
|
|
@@ -1141,6 +1227,53 @@ export declare const ToolCatalog: z.ZodObject<{
|
|
|
1141
1227
|
}, z.core.$strict>>;
|
|
1142
1228
|
}, z.core.$strict>;
|
|
1143
1229
|
export type ToolCatalog = z.infer<typeof ToolCatalog>;
|
|
1230
|
+
/**
|
|
1231
|
+
* One MCP server the asking user reaches right now, as the portal itself names it (D30).
|
|
1232
|
+
*
|
|
1233
|
+
* ⚠️ `toolCount` is a fact about this moment and this user, not a size. It exists so a picker can
|
|
1234
|
+
* say "9 tools" instead of showing a handle alone, and it must never be read as what an agent will
|
|
1235
|
+
* get: the delegated run asks the portal again, with the delegator's token.
|
|
1236
|
+
*/
|
|
1237
|
+
export declare const ToolServer: z.ZodObject<{
|
|
1238
|
+
handle: z.ZodString;
|
|
1239
|
+
name: z.ZodString;
|
|
1240
|
+
toolCount: z.ZodNumber;
|
|
1241
|
+
}, z.core.$strict>;
|
|
1242
|
+
export type ToolServer = z.infer<typeof ToolServer>;
|
|
1243
|
+
export declare const ToolServerCatalog: z.ZodObject<{
|
|
1244
|
+
portalConnected: z.ZodBoolean;
|
|
1245
|
+
items: z.ZodArray<z.ZodObject<{
|
|
1246
|
+
handle: z.ZodString;
|
|
1247
|
+
name: z.ZodString;
|
|
1248
|
+
toolCount: z.ZodNumber;
|
|
1249
|
+
}, z.core.$strict>>;
|
|
1250
|
+
}, z.core.$strict>;
|
|
1251
|
+
export type ToolServerCatalog = z.infer<typeof ToolServerCatalog>;
|
|
1252
|
+
/**
|
|
1253
|
+
* Which of the named servers a tool belongs to, or `null` for none of them.
|
|
1254
|
+
*
|
|
1255
|
+
* ⚠️ THE TRAP: a tool name does not say where its server name ends.
|
|
1256
|
+
*
|
|
1257
|
+
* The portal writes `<server>_<tool>`, and both halves may contain underscores — `intel_flow_get`
|
|
1258
|
+
* reads equally well as server `intel` with tool `flow_get` and as a server called `intel_flow`
|
|
1259
|
+
* with tool `get`. Splitting on the first underscore is therefore a guess that is wrong the day
|
|
1260
|
+
* somebody adds a server whose name contains one, and on the API side being wrong means an agent
|
|
1261
|
+
* delegated server A quietly reaching server B.
|
|
1262
|
+
*
|
|
1263
|
+
* So the prefix is never split. It is only ever MATCHED against handles the portal itself named,
|
|
1264
|
+
* and the longest match wins: with `intel` and `intel_flow` both declared, `intel_flow_get` belongs
|
|
1265
|
+
* to `intel_flow`, which is the only reading in which both declarations stay true.
|
|
1266
|
+
*
|
|
1267
|
+
* ⚠️ This lives in the contract because HOW A NAME IS READ is a property of the wire, and both
|
|
1268
|
+
* surfaces read the same wire: `packages/api` cuts a delegation with it, `packages/ui` groups the
|
|
1269
|
+
* tools screen with it (#212). A second implementation in the browser would be the third answer to
|
|
1270
|
+
* one question — the underscore rule has already been answered differently in two places once
|
|
1271
|
+
* (#106, #107), and the copies disagreed. What deliberately stays OUT of here is everything about
|
|
1272
|
+
* reach: which handles are declared, which are enabled, which may be delegated and which one owns
|
|
1273
|
+
* the portal's own management tools are decisions with consequences, and they belong to
|
|
1274
|
+
* `packages/api/src/tools/tool-servers`. This function only reads a name.
|
|
1275
|
+
*/
|
|
1276
|
+
export declare function serverOf(toolName: string, handles: Iterable<string>): string | null;
|
|
1144
1277
|
export declare const TestToolInput: z.ZodObject<{
|
|
1145
1278
|
name: z.ZodString;
|
|
1146
1279
|
arguments: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -292,10 +292,61 @@ export const AgentModel = z.strictObject({
|
|
|
292
292
|
model: z.string().trim().min(1).max(120),
|
|
293
293
|
});
|
|
294
294
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
295
|
+
* One MCP server as the portal names it. The handle is what the portal puts in front of every tool
|
|
296
|
+
* that server offers (`notion_notion-search` belongs to `notion`), and it is the only identifier
|
|
297
|
+
* Intel can both store and recognise again in a live `tools/list`.
|
|
298
|
+
*
|
|
299
|
+
* ⚠️ A handle is never invented from a tool name. Which servers exist is the portal's answer
|
|
300
|
+
* (`portal_list_servers`), and the prefix is only used to attribute a tool to a server that answer
|
|
301
|
+
* already named — see `packages/api/src/tools/tool-servers` for why splitting on the underscore
|
|
302
|
+
* alone would be ambiguous.
|
|
303
|
+
*/
|
|
304
|
+
export const ToolServerHandle = z
|
|
305
|
+
.string()
|
|
306
|
+
.trim()
|
|
307
|
+
.min(1)
|
|
308
|
+
.max(120)
|
|
309
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "A server handle is the portal's own identifier");
|
|
310
|
+
const ToolServerHandles = z.array(ToolServerHandle).max(32).default([]);
|
|
311
|
+
/**
|
|
312
|
+
* What a caller may ASK for: whole MCP servers, and nothing about who delegates them (D30).
|
|
313
|
+
*
|
|
314
|
+
* ⚠️ The absence of `delegatedBy` is the point, and it is why the write shape differs from the read
|
|
315
|
+
* shape at all. Intel writes that field from the session it authorized; a caller who could name
|
|
316
|
+
* somebody else would be handing an agent a portal connection they do not have, and the agent would
|
|
317
|
+
* act on it unattended. Leaving the field out of the input makes that structural instead of a
|
|
318
|
+
* runtime overwrite: a body carrying it is a parse error at the boundary, on every surface, and no
|
|
319
|
+
* screen ever has to invent a value it has no business knowing.
|
|
320
|
+
*/
|
|
321
|
+
export const AgentToolSelection = z.strictObject({ servers: ToolServerHandles });
|
|
322
|
+
/**
|
|
323
|
+
* What is STORED and read back: the selection plus whose portal connection it came from (D30).
|
|
324
|
+
*
|
|
325
|
+
* ⚠️ This is a selection, not a permission. Nothing here grants anything: whether a server is
|
|
326
|
+
* reachable is still decided by one live `tools/list` with `delegatedBy`'s own portal token, so a
|
|
327
|
+
* delegator who loses the server or the connection takes it away from the agent at the next run
|
|
328
|
+
* with no edit to this document.
|
|
329
|
+
*
|
|
330
|
+
* ⚠️ Read shape only. It appears in `AgentDefinition` and never in an input — see
|
|
331
|
+
* `AgentToolSelection` for why the two are deliberately different documents rather than one schema
|
|
332
|
+
* with an optional field.
|
|
333
|
+
*/
|
|
334
|
+
export const AgentToolDelegation = z.strictObject({
|
|
335
|
+
delegatedBy: IntelId,
|
|
336
|
+
servers: ToolServerHandles,
|
|
337
|
+
});
|
|
338
|
+
/**
|
|
339
|
+
* ⚠️ No accounts, no secrets and no channels — and the reason is mechanical rather than tidy
|
|
340
|
+
* (ADR-0005 §4): this body is read, shared, exported and put into model context, so a secret in it
|
|
341
|
+
* is a secret in a citation. Identity is Gate's, accounts are the portal's, channels are runtime
|
|
342
|
+
* configuration.
|
|
343
|
+
*
|
|
344
|
+
* ⚠️ `tools` is the one correction to that list (D30), and it is narrower than it looks. What is
|
|
345
|
+
* stored is a **selection of whole servers plus who delegated them**, never a mirrored permission
|
|
346
|
+
* and never a catalog: the catalog stays a live `tools/list` made with the delegator's token at the
|
|
347
|
+
* moment the agent runs. ADR-0005 §4's "no tools in the definition" forbade the mirror, and the
|
|
348
|
+
* mirror is still forbidden — a tool name, a schema or an account in here would be the thing that
|
|
349
|
+
* line was written against.
|
|
299
350
|
*
|
|
300
351
|
* ⚠️ Strict on purpose, and deliberately stricter than the runtime's own reader
|
|
301
352
|
* (`packages/agent/src/definition/definition.ts`, which is `z.object`). Intel is the writer: an
|
|
@@ -303,15 +354,37 @@ export const AgentModel = z.strictObject({
|
|
|
303
354
|
* reader and released separately, so it must keep starting agents when Intel adds a field
|
|
304
355
|
* tomorrow. The asymmetry is the point, not an oversight.
|
|
305
356
|
*/
|
|
306
|
-
|
|
357
|
+
const AgentBody = {
|
|
307
358
|
references: z.array(AgentReference).max(200).default([]),
|
|
308
359
|
schedules: z.array(AgentSchedule).max(50).default([]),
|
|
309
360
|
model: AgentModel,
|
|
361
|
+
};
|
|
362
|
+
export const AgentDefinition = z.strictObject({
|
|
363
|
+
...AgentBody,
|
|
364
|
+
// `null` is "this agent has no tools", and it is also what every definition written before D30
|
|
365
|
+
// parses to. An empty `servers` list means the same thing and is kept as its own state so
|
|
366
|
+
// removing the last server does not have to erase who was delegating.
|
|
367
|
+
tools: AgentToolDelegation.nullable().default(null),
|
|
368
|
+
});
|
|
369
|
+
/**
|
|
370
|
+
* The same document as `AgentDefinition`, minus the one field a caller may not write.
|
|
371
|
+
*
|
|
372
|
+
* ⚠️ Two schemas rather than one, and the split is load-bearing (#208, D30). Everything an agent IS
|
|
373
|
+
* comes from whoever edits it; **whose portal connection it acts on** does not, because that is an
|
|
374
|
+
* authority the editor would be granting to themselves. So the write shape simply has no place to
|
|
375
|
+
* put it: `{ tools: { servers: [...] } }` is what a screen or an MCP client sends, Intel adds
|
|
376
|
+
* `delegatedBy` from the session, and a body that tries to name one is refused by the strict object
|
|
377
|
+
* before any of it is read. The reading shape keeps the field because a reader must be able to see
|
|
378
|
+
* whose connection an agent runs on.
|
|
379
|
+
*/
|
|
380
|
+
export const AgentDefinitionInput = z.strictObject({
|
|
381
|
+
...AgentBody,
|
|
382
|
+
tools: AgentToolSelection.nullable().default(null),
|
|
310
383
|
});
|
|
311
384
|
export const SaveAgentDefinitionInput = z.strictObject({
|
|
312
385
|
nodeId: IntelId,
|
|
313
386
|
baseVersionId: IntelId.nullable(),
|
|
314
|
-
definition:
|
|
387
|
+
definition: AgentDefinitionInput,
|
|
315
388
|
idempotencyKey: z.string().min(8).max(200),
|
|
316
389
|
});
|
|
317
390
|
export const GetAgentInput = z.strictObject({ nodeId: IntelId });
|
|
@@ -339,7 +412,7 @@ export const CreateAgentInput = z.strictObject({
|
|
|
339
412
|
parentId: IntelId.nullable().default(null),
|
|
340
413
|
title: z.string().trim().min(1).max(240),
|
|
341
414
|
description: z.string().trim().max(2_000).nullable().default(null),
|
|
342
|
-
definition:
|
|
415
|
+
definition: AgentDefinitionInput,
|
|
343
416
|
idempotencyKey: z.string().min(8).max(200),
|
|
344
417
|
});
|
|
345
418
|
// The ID of the Gate Application an agent runs as. Deliberately NOT an `IntelId`: it is Better
|
|
@@ -362,38 +435,37 @@ export const NodeAgent = z.strictObject({
|
|
|
362
435
|
applicationId: GateApplicationId.nullable(),
|
|
363
436
|
});
|
|
364
437
|
/**
|
|
365
|
-
*
|
|
438
|
+
* ⚠️ There is NO key field in this file, and adding one back would be the regression (D29, #207).
|
|
366
439
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
440
|
+
* Until #207 the create answer carried the Application key in plain text, once, and a person had to
|
|
441
|
+
* carry it into a Worker secret by hand — which is why an agent created through the screen could
|
|
442
|
+
* never run (#200). The key now goes from Gate straight into the agent runtime over Intel's service
|
|
443
|
+
* binding and is encrypted into that agent's Durable Object; it reaches no browser, no MCP tool
|
|
444
|
+
* result and no response body at all. `NodeAgent` is a `z.strictObject`, so a field named `key`
|
|
445
|
+
* added anywhere in this file is a parse error at the boundary rather than a leak somebody has to
|
|
446
|
+
* spot in review.
|
|
373
447
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
448
|
+
* What `POST /nodes/agents` and `agent_create` answer is therefore exactly what every read answers:
|
|
449
|
+
* the node, its first definition, and the `applicationId` that NAMES the principal without
|
|
450
|
+
* authenticating it.
|
|
377
451
|
*/
|
|
378
|
-
export const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
});
|
|
452
|
+
export const CreatedAgent = NodeAgent;
|
|
453
|
+
// Which agent's key is being replaced. `nodeId` and not the Application ID: this addresses an agent
|
|
454
|
+
// in Intel's tree, and the Application behind it is Intel's to look up — a caller naming the
|
|
455
|
+
// principal directly would be rotating a key for an agent nobody checked they may edit.
|
|
456
|
+
export const RotateAgentKeyInput = z.strictObject({ nodeId: IntelId });
|
|
384
457
|
/**
|
|
385
|
-
* What
|
|
386
|
-
* other agent read answers with the plain `NodeAgent` above.
|
|
458
|
+
* What replacing an agent's key answers.
|
|
387
459
|
*
|
|
388
|
-
* ⚠️
|
|
389
|
-
*
|
|
390
|
-
* the
|
|
391
|
-
*
|
|
392
|
-
* installation with two machine accounts for one agent, one of which nobody would ever switch off.
|
|
393
|
-
* `application_rotate_key` in Gate is the way to a new key, and it is a deliberate act.
|
|
460
|
+
* ⚠️ No key, and that is the whole shape of D29: Intel asks Gate for a new one, hands it to the
|
|
461
|
+
* runtime over the service binding, and forgets it inside the same call. What the caller gets is
|
|
462
|
+
* the fact that it happened, so a screen can say so — `applicationId` names the principal whose key
|
|
463
|
+
* was replaced, which is a name and not a credential.
|
|
394
464
|
*/
|
|
395
|
-
export const
|
|
396
|
-
|
|
465
|
+
export const AgentKeyRotated = z.strictObject({
|
|
466
|
+
nodeId: IntelId,
|
|
467
|
+
applicationId: GateApplicationId,
|
|
468
|
+
rotatedAt: IsoDateTime,
|
|
397
469
|
});
|
|
398
470
|
export const AgentList = z.strictObject({ items: z.array(Node) });
|
|
399
471
|
// ⚠️ Kept for what is already stored, not for what is written. Relations were picked in a dialog
|
|
@@ -568,8 +640,11 @@ export const ToolSourceUrl = z.url().refine((value) => {
|
|
|
568
640
|
return false;
|
|
569
641
|
}
|
|
570
642
|
}, "The portal must use an approved public HTTPS host without embedded credentials");
|
|
571
|
-
// The portal namespaces every upstream tool, so the name alone identifies the target server.
|
|
572
|
-
//
|
|
643
|
+
// The portal namespaces every upstream tool, so the name alone identifies the target server. The
|
|
644
|
+
// portal is still the one that resolves it and attaches the credentials — Intel never holds an
|
|
645
|
+
// upstream credential. Since D30 Intel does read the namespace for one purpose: attributing a tool
|
|
646
|
+
// to a server the portal's own `portal_list_servers` already named, so a delegation can be cut to
|
|
647
|
+
// whole servers. That is attribution, not routing.
|
|
573
648
|
export const ToolName = z.string().min(1).max(240);
|
|
574
649
|
export const ToolAnnotations = z.strictObject({
|
|
575
650
|
title: z.string().max(240).optional(),
|
|
@@ -593,6 +668,58 @@ export const ToolCatalog = z.strictObject({
|
|
|
593
668
|
portalConnected: z.boolean(),
|
|
594
669
|
items: z.array(ToolCapability),
|
|
595
670
|
});
|
|
671
|
+
/**
|
|
672
|
+
* One MCP server the asking user reaches right now, as the portal itself names it (D30).
|
|
673
|
+
*
|
|
674
|
+
* ⚠️ `toolCount` is a fact about this moment and this user, not a size. It exists so a picker can
|
|
675
|
+
* say "9 tools" instead of showing a handle alone, and it must never be read as what an agent will
|
|
676
|
+
* get: the delegated run asks the portal again, with the delegator's token.
|
|
677
|
+
*/
|
|
678
|
+
export const ToolServer = z.strictObject({
|
|
679
|
+
handle: ToolServerHandle,
|
|
680
|
+
name: z.string().min(1).max(240),
|
|
681
|
+
toolCount: z.number().int().min(0),
|
|
682
|
+
});
|
|
683
|
+
// The same live-query rule as the tool catalog, one level up. `portalConnected: false` is the state
|
|
684
|
+
// of somebody who has not signed into the portal yet, and it is not an error.
|
|
685
|
+
export const ToolServerCatalog = z.strictObject({
|
|
686
|
+
portalConnected: z.boolean(),
|
|
687
|
+
items: z.array(ToolServer),
|
|
688
|
+
});
|
|
689
|
+
/**
|
|
690
|
+
* Which of the named servers a tool belongs to, or `null` for none of them.
|
|
691
|
+
*
|
|
692
|
+
* ⚠️ THE TRAP: a tool name does not say where its server name ends.
|
|
693
|
+
*
|
|
694
|
+
* The portal writes `<server>_<tool>`, and both halves may contain underscores — `intel_flow_get`
|
|
695
|
+
* reads equally well as server `intel` with tool `flow_get` and as a server called `intel_flow`
|
|
696
|
+
* with tool `get`. Splitting on the first underscore is therefore a guess that is wrong the day
|
|
697
|
+
* somebody adds a server whose name contains one, and on the API side being wrong means an agent
|
|
698
|
+
* delegated server A quietly reaching server B.
|
|
699
|
+
*
|
|
700
|
+
* So the prefix is never split. It is only ever MATCHED against handles the portal itself named,
|
|
701
|
+
* and the longest match wins: with `intel` and `intel_flow` both declared, `intel_flow_get` belongs
|
|
702
|
+
* to `intel_flow`, which is the only reading in which both declarations stay true.
|
|
703
|
+
*
|
|
704
|
+
* ⚠️ This lives in the contract because HOW A NAME IS READ is a property of the wire, and both
|
|
705
|
+
* surfaces read the same wire: `packages/api` cuts a delegation with it, `packages/ui` groups the
|
|
706
|
+
* tools screen with it (#212). A second implementation in the browser would be the third answer to
|
|
707
|
+
* one question — the underscore rule has already been answered differently in two places once
|
|
708
|
+
* (#106, #107), and the copies disagreed. What deliberately stays OUT of here is everything about
|
|
709
|
+
* reach: which handles are declared, which are enabled, which may be delegated and which one owns
|
|
710
|
+
* the portal's own management tools are decisions with consequences, and they belong to
|
|
711
|
+
* `packages/api/src/tools/tool-servers`. This function only reads a name.
|
|
712
|
+
*/
|
|
713
|
+
export function serverOf(toolName, handles) {
|
|
714
|
+
let best = null;
|
|
715
|
+
for (const handle of handles) {
|
|
716
|
+
if (!toolName.startsWith(`${handle}_`))
|
|
717
|
+
continue;
|
|
718
|
+
if (best === null || handle.length > best.length)
|
|
719
|
+
best = handle;
|
|
720
|
+
}
|
|
721
|
+
return best;
|
|
722
|
+
}
|
|
596
723
|
export const TestToolInput = z.strictObject({
|
|
597
724
|
name: ToolName,
|
|
598
725
|
arguments: z.record(z.string(), z.unknown()).default({}),
|