@anchrd/intel-contract 0.5.0 → 0.8.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 +185 -43
- package/dist/contract/contract.js +204 -35
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const IntelId: z.ZodString;
|
|
3
3
|
export declare const IsoDateTime: z.ZodISODateTime;
|
|
4
|
+
export declare const UI_LANGUAGES: readonly ["en", "de", "es"];
|
|
5
|
+
export type UiLanguage = (typeof UI_LANGUAGES)[number];
|
|
4
6
|
export declare const ProblemDetails: z.ZodObject<{
|
|
5
7
|
type: z.ZodString;
|
|
6
8
|
status: z.ZodNumber;
|
|
@@ -478,6 +480,7 @@ export declare const AgentScheduleTarget: z.ZodObject<{
|
|
|
478
480
|
export type AgentScheduleTarget = z.infer<typeof AgentScheduleTarget>;
|
|
479
481
|
export declare const AgentSchedule: z.ZodObject<{
|
|
480
482
|
cron: z.ZodString;
|
|
483
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
481
484
|
target: z.ZodObject<{
|
|
482
485
|
kind: z.ZodEnum<{
|
|
483
486
|
document: "document";
|
|
@@ -496,18 +499,53 @@ export declare const AgentModel: z.ZodObject<{
|
|
|
496
499
|
}, z.core.$strict>;
|
|
497
500
|
export type AgentModel = z.infer<typeof AgentModel>;
|
|
498
501
|
/**
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
* runtime configuration, and the tool catalog is a live `tools/list` that is never mirrored.
|
|
502
|
+
* One MCP server as the portal names it. The handle is what the portal puts in front of every tool
|
|
503
|
+
* that server offers (`notion_notion-search` belongs to `notion`), and it is the only identifier
|
|
504
|
+
* Intel can both store and recognise again in a live `tools/list`.
|
|
503
505
|
*
|
|
504
|
-
* ⚠️
|
|
505
|
-
* (`
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* tomorrow. The asymmetry is the point, not an oversight.
|
|
506
|
+
* ⚠️ A handle is never invented from a tool name. Which servers exist is the portal's answer
|
|
507
|
+
* (`portal_list_servers`), and the prefix is only used to attribute a tool to a server that answer
|
|
508
|
+
* already named — see `packages/api/src/tools/tool-servers` for why splitting on the underscore
|
|
509
|
+
* alone would be ambiguous.
|
|
509
510
|
*/
|
|
511
|
+
export declare const ToolServerHandle: z.ZodString;
|
|
512
|
+
export type ToolServerHandle = z.infer<typeof ToolServerHandle>;
|
|
513
|
+
/**
|
|
514
|
+
* What a caller may ASK for: whole MCP servers, and nothing about who delegates them (D30).
|
|
515
|
+
*
|
|
516
|
+
* ⚠️ The absence of `delegatedBy` is the point, and it is why the write shape differs from the read
|
|
517
|
+
* shape at all. Intel writes that field from the session it authorized; a caller who could name
|
|
518
|
+
* somebody else would be handing an agent a portal connection they do not have, and the agent would
|
|
519
|
+
* act on it unattended. Leaving the field out of the input makes that structural instead of a
|
|
520
|
+
* runtime overwrite: a body carrying it is a parse error at the boundary, on every surface, and no
|
|
521
|
+
* screen ever has to invent a value it has no business knowing.
|
|
522
|
+
*/
|
|
523
|
+
export declare const AgentToolSelection: z.ZodObject<{
|
|
524
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
525
|
+
}, z.core.$strict>;
|
|
526
|
+
export type AgentToolSelection = z.infer<typeof AgentToolSelection>;
|
|
527
|
+
/**
|
|
528
|
+
* What is STORED and read back: the selection plus whose portal connection it came from (D30).
|
|
529
|
+
*
|
|
530
|
+
* ⚠️ This is a selection, not a permission. Nothing here grants anything: whether a server is
|
|
531
|
+
* reachable is still decided by one live `tools/list` with `delegatedBy`'s own portal token, so a
|
|
532
|
+
* delegator who loses the server or the connection takes it away from the agent at the next run
|
|
533
|
+
* with no edit to this document.
|
|
534
|
+
*
|
|
535
|
+
* ⚠️ Read shape only. It appears in `AgentDefinition` and never in an input — see
|
|
536
|
+
* `AgentToolSelection` for why the two are deliberately different documents rather than one schema
|
|
537
|
+
* with an optional field.
|
|
538
|
+
*/
|
|
539
|
+
export declare const AgentToolDelegation: z.ZodObject<{
|
|
540
|
+
delegatedBy: z.ZodString;
|
|
541
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
542
|
+
}, z.core.$strict>;
|
|
543
|
+
export type AgentToolDelegation = z.infer<typeof AgentToolDelegation>;
|
|
510
544
|
export declare const AgentDefinition: z.ZodObject<{
|
|
545
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
546
|
+
delegatedBy: z.ZodString;
|
|
547
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
548
|
+
}, z.core.$strict>>>;
|
|
511
549
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
512
550
|
nodeId: z.ZodString;
|
|
513
551
|
role: z.ZodEnum<{
|
|
@@ -518,6 +556,7 @@ export declare const AgentDefinition: z.ZodObject<{
|
|
|
518
556
|
}, z.core.$strict>>>;
|
|
519
557
|
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
520
558
|
cron: z.ZodString;
|
|
559
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
521
560
|
target: z.ZodObject<{
|
|
522
561
|
kind: z.ZodEnum<{
|
|
523
562
|
document: "document";
|
|
@@ -535,10 +574,56 @@ export declare const AgentDefinition: z.ZodObject<{
|
|
|
535
574
|
}, z.core.$strict>;
|
|
536
575
|
}, z.core.$strict>;
|
|
537
576
|
export type AgentDefinition = z.infer<typeof AgentDefinition>;
|
|
577
|
+
/**
|
|
578
|
+
* The same document as `AgentDefinition`, minus the one field a caller may not write.
|
|
579
|
+
*
|
|
580
|
+
* ⚠️ Two schemas rather than one, and the split is load-bearing (#208, D30). Everything an agent IS
|
|
581
|
+
* comes from whoever edits it; **whose portal connection it acts on** does not, because that is an
|
|
582
|
+
* authority the editor would be granting to themselves. So the write shape simply has no place to
|
|
583
|
+
* put it: `{ tools: { servers: [...] } }` is what a screen or an MCP client sends, Intel adds
|
|
584
|
+
* `delegatedBy` from the session, and a body that tries to name one is refused by the strict object
|
|
585
|
+
* before any of it is read. The reading shape keeps the field because a reader must be able to see
|
|
586
|
+
* whose connection an agent runs on.
|
|
587
|
+
*/
|
|
588
|
+
export declare const AgentDefinitionInput: z.ZodObject<{
|
|
589
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
590
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
591
|
+
}, z.core.$strict>>>;
|
|
592
|
+
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
593
|
+
nodeId: z.ZodString;
|
|
594
|
+
role: z.ZodEnum<{
|
|
595
|
+
"system-message": "system-message";
|
|
596
|
+
"semantic-context": "semantic-context";
|
|
597
|
+
memory: "memory";
|
|
598
|
+
}>;
|
|
599
|
+
}, z.core.$strict>>>;
|
|
600
|
+
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
601
|
+
cron: z.ZodString;
|
|
602
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
603
|
+
target: z.ZodObject<{
|
|
604
|
+
kind: z.ZodEnum<{
|
|
605
|
+
document: "document";
|
|
606
|
+
flow: "flow";
|
|
607
|
+
}>;
|
|
608
|
+
id: z.ZodString;
|
|
609
|
+
}, z.core.$strict>;
|
|
610
|
+
}, z.core.$strict>>>;
|
|
611
|
+
model: z.ZodObject<{
|
|
612
|
+
provider: z.ZodEnum<{
|
|
613
|
+
"workers-ai": "workers-ai";
|
|
614
|
+
anthropic: "anthropic";
|
|
615
|
+
}>;
|
|
616
|
+
model: z.ZodString;
|
|
617
|
+
}, z.core.$strict>;
|
|
618
|
+
}, z.core.$strict>;
|
|
619
|
+
export type AgentDefinitionInput = z.infer<typeof AgentDefinitionInput>;
|
|
538
620
|
export declare const SaveAgentDefinitionInput: z.ZodObject<{
|
|
539
621
|
nodeId: z.ZodString;
|
|
540
622
|
baseVersionId: z.ZodNullable<z.ZodString>;
|
|
541
623
|
definition: z.ZodObject<{
|
|
624
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
625
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
626
|
+
}, z.core.$strict>>>;
|
|
542
627
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
543
628
|
nodeId: z.ZodString;
|
|
544
629
|
role: z.ZodEnum<{
|
|
@@ -549,6 +634,7 @@ export declare const SaveAgentDefinitionInput: z.ZodObject<{
|
|
|
549
634
|
}, z.core.$strict>>>;
|
|
550
635
|
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
551
636
|
cron: z.ZodString;
|
|
637
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
552
638
|
target: z.ZodObject<{
|
|
553
639
|
kind: z.ZodEnum<{
|
|
554
640
|
document: "document";
|
|
@@ -597,6 +683,9 @@ export declare const CreateAgentInput: z.ZodObject<{
|
|
|
597
683
|
title: z.ZodString;
|
|
598
684
|
description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
599
685
|
definition: z.ZodObject<{
|
|
686
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
687
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
688
|
+
}, z.core.$strict>>>;
|
|
600
689
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
601
690
|
nodeId: z.ZodString;
|
|
602
691
|
role: z.ZodEnum<{
|
|
@@ -607,6 +696,7 @@ export declare const CreateAgentInput: z.ZodObject<{
|
|
|
607
696
|
}, z.core.$strict>>>;
|
|
608
697
|
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
609
698
|
cron: z.ZodString;
|
|
699
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
610
700
|
target: z.ZodObject<{
|
|
611
701
|
kind: z.ZodEnum<{
|
|
612
702
|
document: "document";
|
|
@@ -662,6 +752,10 @@ export declare const NodeAgent: z.ZodObject<{
|
|
|
662
752
|
createdAt: z.ZodISODateTime;
|
|
663
753
|
}, z.core.$strict>>;
|
|
664
754
|
definition: z.ZodNullable<z.ZodObject<{
|
|
755
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
756
|
+
delegatedBy: z.ZodString;
|
|
757
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
758
|
+
}, z.core.$strict>>>;
|
|
665
759
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
666
760
|
nodeId: z.ZodString;
|
|
667
761
|
role: z.ZodEnum<{
|
|
@@ -672,6 +766,7 @@ export declare const NodeAgent: z.ZodObject<{
|
|
|
672
766
|
}, z.core.$strict>>>;
|
|
673
767
|
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
674
768
|
cron: z.ZodString;
|
|
769
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
675
770
|
target: z.ZodObject<{
|
|
676
771
|
kind: z.ZodEnum<{
|
|
677
772
|
document: "document";
|
|
@@ -692,36 +787,19 @@ export declare const NodeAgent: z.ZodObject<{
|
|
|
692
787
|
}, z.core.$strict>;
|
|
693
788
|
export type NodeAgent = z.infer<typeof NodeAgent>;
|
|
694
789
|
/**
|
|
695
|
-
*
|
|
790
|
+
* ⚠️ There is NO key field in this file, and adding one back would be the regression (D29, #207).
|
|
696
791
|
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
792
|
+
* Until #207 the create answer carried the Application key in plain text, once, and a person had to
|
|
793
|
+
* carry it into a Worker secret by hand — which is why an agent created through the screen could
|
|
794
|
+
* never run (#200). The key now goes from Gate straight into the agent runtime over Intel's service
|
|
795
|
+
* binding and is encrypted into that agent's Durable Object; it reaches no browser, no MCP tool
|
|
796
|
+
* result and no response body at all. `NodeAgent` is a `z.strictObject`, so a field named `key`
|
|
797
|
+
* added anywhere in this file is a parse error at the boundary rather than a leak somebody has to
|
|
798
|
+
* spot in review.
|
|
703
799
|
*
|
|
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.
|
|
800
|
+
* What `POST /nodes/agents` and `agent_create` answer is therefore exactly what every read answers:
|
|
801
|
+
* the node, its first definition, and the `applicationId` that NAMES the principal without
|
|
802
|
+
* authenticating it.
|
|
725
803
|
*/
|
|
726
804
|
export declare const CreatedAgent: z.ZodObject<{
|
|
727
805
|
node: z.ZodObject<{
|
|
@@ -758,6 +836,10 @@ export declare const CreatedAgent: z.ZodObject<{
|
|
|
758
836
|
createdAt: z.ZodISODateTime;
|
|
759
837
|
}, z.core.$strict>>;
|
|
760
838
|
definition: z.ZodNullable<z.ZodObject<{
|
|
839
|
+
tools: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
840
|
+
delegatedBy: z.ZodString;
|
|
841
|
+
servers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
842
|
+
}, z.core.$strict>>>;
|
|
761
843
|
references: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
762
844
|
nodeId: z.ZodString;
|
|
763
845
|
role: z.ZodEnum<{
|
|
@@ -768,6 +850,7 @@ export declare const CreatedAgent: z.ZodObject<{
|
|
|
768
850
|
}, z.core.$strict>>>;
|
|
769
851
|
schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
770
852
|
cron: z.ZodString;
|
|
853
|
+
timezone: z.ZodDefault<z.ZodString>;
|
|
771
854
|
target: z.ZodObject<{
|
|
772
855
|
kind: z.ZodEnum<{
|
|
773
856
|
document: "document";
|
|
@@ -785,14 +868,26 @@ export declare const CreatedAgent: z.ZodObject<{
|
|
|
785
868
|
}, z.core.$strict>;
|
|
786
869
|
}, z.core.$strict>>;
|
|
787
870
|
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
871
|
}, z.core.$strict>;
|
|
795
872
|
export type CreatedAgent = z.infer<typeof CreatedAgent>;
|
|
873
|
+
export declare const RotateAgentKeyInput: z.ZodObject<{
|
|
874
|
+
nodeId: z.ZodString;
|
|
875
|
+
}, z.core.$strict>;
|
|
876
|
+
export type RotateAgentKeyInput = z.infer<typeof RotateAgentKeyInput>;
|
|
877
|
+
/**
|
|
878
|
+
* What replacing an agent's key answers.
|
|
879
|
+
*
|
|
880
|
+
* ⚠️ No key, and that is the whole shape of D29: Intel asks Gate for a new one, hands it to the
|
|
881
|
+
* runtime over the service binding, and forgets it inside the same call. What the caller gets is
|
|
882
|
+
* the fact that it happened, so a screen can say so — `applicationId` names the principal whose key
|
|
883
|
+
* was replaced, which is a name and not a credential.
|
|
884
|
+
*/
|
|
885
|
+
export declare const AgentKeyRotated: z.ZodObject<{
|
|
886
|
+
nodeId: z.ZodString;
|
|
887
|
+
applicationId: z.ZodString;
|
|
888
|
+
rotatedAt: z.ZodISODateTime;
|
|
889
|
+
}, z.core.$strict>;
|
|
890
|
+
export type AgentKeyRotated = z.infer<typeof AgentKeyRotated>;
|
|
796
891
|
export declare const AgentList: z.ZodObject<{
|
|
797
892
|
items: z.ZodArray<z.ZodObject<{
|
|
798
893
|
id: z.ZodString;
|
|
@@ -1141,6 +1236,53 @@ export declare const ToolCatalog: z.ZodObject<{
|
|
|
1141
1236
|
}, z.core.$strict>>;
|
|
1142
1237
|
}, z.core.$strict>;
|
|
1143
1238
|
export type ToolCatalog = z.infer<typeof ToolCatalog>;
|
|
1239
|
+
/**
|
|
1240
|
+
* One MCP server the asking user reaches right now, as the portal itself names it (D30).
|
|
1241
|
+
*
|
|
1242
|
+
* ⚠️ `toolCount` is a fact about this moment and this user, not a size. It exists so a picker can
|
|
1243
|
+
* say "9 tools" instead of showing a handle alone, and it must never be read as what an agent will
|
|
1244
|
+
* get: the delegated run asks the portal again, with the delegator's token.
|
|
1245
|
+
*/
|
|
1246
|
+
export declare const ToolServer: z.ZodObject<{
|
|
1247
|
+
handle: z.ZodString;
|
|
1248
|
+
name: z.ZodString;
|
|
1249
|
+
toolCount: z.ZodNumber;
|
|
1250
|
+
}, z.core.$strict>;
|
|
1251
|
+
export type ToolServer = z.infer<typeof ToolServer>;
|
|
1252
|
+
export declare const ToolServerCatalog: z.ZodObject<{
|
|
1253
|
+
portalConnected: z.ZodBoolean;
|
|
1254
|
+
items: z.ZodArray<z.ZodObject<{
|
|
1255
|
+
handle: z.ZodString;
|
|
1256
|
+
name: z.ZodString;
|
|
1257
|
+
toolCount: z.ZodNumber;
|
|
1258
|
+
}, z.core.$strict>>;
|
|
1259
|
+
}, z.core.$strict>;
|
|
1260
|
+
export type ToolServerCatalog = z.infer<typeof ToolServerCatalog>;
|
|
1261
|
+
/**
|
|
1262
|
+
* Which of the named servers a tool belongs to, or `null` for none of them.
|
|
1263
|
+
*
|
|
1264
|
+
* ⚠️ THE TRAP: a tool name does not say where its server name ends.
|
|
1265
|
+
*
|
|
1266
|
+
* The portal writes `<server>_<tool>`, and both halves may contain underscores — `intel_flow_get`
|
|
1267
|
+
* reads equally well as server `intel` with tool `flow_get` and as a server called `intel_flow`
|
|
1268
|
+
* with tool `get`. Splitting on the first underscore is therefore a guess that is wrong the day
|
|
1269
|
+
* somebody adds a server whose name contains one, and on the API side being wrong means an agent
|
|
1270
|
+
* delegated server A quietly reaching server B.
|
|
1271
|
+
*
|
|
1272
|
+
* So the prefix is never split. It is only ever MATCHED against handles the portal itself named,
|
|
1273
|
+
* and the longest match wins: with `intel` and `intel_flow` both declared, `intel_flow_get` belongs
|
|
1274
|
+
* to `intel_flow`, which is the only reading in which both declarations stay true.
|
|
1275
|
+
*
|
|
1276
|
+
* ⚠️ This lives in the contract because HOW A NAME IS READ is a property of the wire, and both
|
|
1277
|
+
* surfaces read the same wire: `packages/api` cuts a delegation with it, `packages/ui` groups the
|
|
1278
|
+
* tools screen with it (#212). A second implementation in the browser would be the third answer to
|
|
1279
|
+
* one question — the underscore rule has already been answered differently in two places once
|
|
1280
|
+
* (#106, #107), and the copies disagreed. What deliberately stays OUT of here is everything about
|
|
1281
|
+
* reach: which handles are declared, which are enabled, which may be delegated and which one owns
|
|
1282
|
+
* the portal's own management tools are decisions with consequences, and they belong to
|
|
1283
|
+
* `packages/api/src/tools/tool-servers`. This function only reads a name.
|
|
1284
|
+
*/
|
|
1285
|
+
export declare function serverOf(toolName: string, handles: Iterable<string>): string | null;
|
|
1144
1286
|
export declare const TestToolInput: z.ZodObject<{
|
|
1145
1287
|
name: z.ZodString;
|
|
1146
1288
|
arguments: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const IntelId = z.string().min(1).max(128);
|
|
3
3
|
export const IsoDateTime = z.iso.datetime({ offset: true });
|
|
4
|
+
// The languages whose UI catalog ships inside intel-ui. They live here rather than only in the UI
|
|
5
|
+
// because `intel build` has to know them: a built-in language may be chosen as `ui.defaultLanguage`
|
|
6
|
+
// without the customer listing a copy under `ui.languages` that would rot at every UI update.
|
|
7
|
+
//
|
|
8
|
+
// ⚠️ The list is a claim about the contents of packages/ui/src/i18n and cannot be checked here.
|
|
9
|
+
// `i18n.unit.ts` compares it against the catalogs actually built in; a language listed here without
|
|
10
|
+
// a file gives a red run there instead of a UI that starts on a catalog which does not exist.
|
|
11
|
+
export const UI_LANGUAGES = ["en", "de", "es"];
|
|
4
12
|
export const ProblemDetails = z.strictObject({
|
|
5
13
|
type: z.string(),
|
|
6
14
|
status: z.number().int().min(400).max(599),
|
|
@@ -283,8 +291,42 @@ export const AgentScheduleTarget = z.strictObject({
|
|
|
283
291
|
kind: z.enum(["document", "flow"]),
|
|
284
292
|
id: IntelId,
|
|
285
293
|
});
|
|
294
|
+
/**
|
|
295
|
+
* ⚠️ `timezone` is what the cron expression is READ IN, and it belongs to the schedule rather than
|
|
296
|
+
* to whoever is looking at it (#228). "Every morning at eight" means eight o'clock where the person
|
|
297
|
+
* who wrote it sits — in Berlin that is 06:00 UTC in summer and 07:00 in winter, and a field that
|
|
298
|
+
* does not carry the zone cannot express that difference. A UTC cron is an hour wrong twice a year
|
|
299
|
+
* and nobody sees why.
|
|
300
|
+
*
|
|
301
|
+
* The UI suggests the reader's own zone when a schedule is created, but it is not a per-user
|
|
302
|
+
* setting: an agent's schedule would otherwise move whenever its owner travelled, and it would mean
|
|
303
|
+
* different times to two people reading the same definition. What is stored is the answer.
|
|
304
|
+
*
|
|
305
|
+
* ⚠️ The default is `"UTC"`, and it is load-bearing rather than tidy: every definition written
|
|
306
|
+
* before this field parses to it and therefore keeps firing exactly when it did. A default of
|
|
307
|
+
* "whatever the writer's browser says" would silently move every existing schedule at the next save.
|
|
308
|
+
*
|
|
309
|
+
* The name is validated against this runtime's own tz database rather than a pattern. A regular
|
|
310
|
+
* expression would accept `Mars/Olympus`, and the failure would surface inside a Durable Object
|
|
311
|
+
* alarm — the place where nobody is watching.
|
|
312
|
+
*/
|
|
313
|
+
const IanaTimezone = z
|
|
314
|
+
.string()
|
|
315
|
+
.trim()
|
|
316
|
+
.min(1)
|
|
317
|
+
.max(64)
|
|
318
|
+
.refine((zone) => {
|
|
319
|
+
try {
|
|
320
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}, { message: "must be an IANA timezone name this runtime knows, for example Europe/Berlin" });
|
|
286
327
|
export const AgentSchedule = z.strictObject({
|
|
287
328
|
cron: z.string().trim().min(1).max(120),
|
|
329
|
+
timezone: IanaTimezone.default("UTC"),
|
|
288
330
|
target: AgentScheduleTarget,
|
|
289
331
|
});
|
|
290
332
|
export const AgentModel = z.strictObject({
|
|
@@ -292,10 +334,61 @@ export const AgentModel = z.strictObject({
|
|
|
292
334
|
model: z.string().trim().min(1).max(120),
|
|
293
335
|
});
|
|
294
336
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
337
|
+
* One MCP server as the portal names it. The handle is what the portal puts in front of every tool
|
|
338
|
+
* that server offers (`notion_notion-search` belongs to `notion`), and it is the only identifier
|
|
339
|
+
* Intel can both store and recognise again in a live `tools/list`.
|
|
340
|
+
*
|
|
341
|
+
* ⚠️ A handle is never invented from a tool name. Which servers exist is the portal's answer
|
|
342
|
+
* (`portal_list_servers`), and the prefix is only used to attribute a tool to a server that answer
|
|
343
|
+
* already named — see `packages/api/src/tools/tool-servers` for why splitting on the underscore
|
|
344
|
+
* alone would be ambiguous.
|
|
345
|
+
*/
|
|
346
|
+
export const ToolServerHandle = z
|
|
347
|
+
.string()
|
|
348
|
+
.trim()
|
|
349
|
+
.min(1)
|
|
350
|
+
.max(120)
|
|
351
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "A server handle is the portal's own identifier");
|
|
352
|
+
const ToolServerHandles = z.array(ToolServerHandle).max(32).default([]);
|
|
353
|
+
/**
|
|
354
|
+
* What a caller may ASK for: whole MCP servers, and nothing about who delegates them (D30).
|
|
355
|
+
*
|
|
356
|
+
* ⚠️ The absence of `delegatedBy` is the point, and it is why the write shape differs from the read
|
|
357
|
+
* shape at all. Intel writes that field from the session it authorized; a caller who could name
|
|
358
|
+
* somebody else would be handing an agent a portal connection they do not have, and the agent would
|
|
359
|
+
* act on it unattended. Leaving the field out of the input makes that structural instead of a
|
|
360
|
+
* runtime overwrite: a body carrying it is a parse error at the boundary, on every surface, and no
|
|
361
|
+
* screen ever has to invent a value it has no business knowing.
|
|
362
|
+
*/
|
|
363
|
+
export const AgentToolSelection = z.strictObject({ servers: ToolServerHandles });
|
|
364
|
+
/**
|
|
365
|
+
* What is STORED and read back: the selection plus whose portal connection it came from (D30).
|
|
366
|
+
*
|
|
367
|
+
* ⚠️ This is a selection, not a permission. Nothing here grants anything: whether a server is
|
|
368
|
+
* reachable is still decided by one live `tools/list` with `delegatedBy`'s own portal token, so a
|
|
369
|
+
* delegator who loses the server or the connection takes it away from the agent at the next run
|
|
370
|
+
* with no edit to this document.
|
|
371
|
+
*
|
|
372
|
+
* ⚠️ Read shape only. It appears in `AgentDefinition` and never in an input — see
|
|
373
|
+
* `AgentToolSelection` for why the two are deliberately different documents rather than one schema
|
|
374
|
+
* with an optional field.
|
|
375
|
+
*/
|
|
376
|
+
export const AgentToolDelegation = z.strictObject({
|
|
377
|
+
delegatedBy: IntelId,
|
|
378
|
+
servers: ToolServerHandles,
|
|
379
|
+
});
|
|
380
|
+
/**
|
|
381
|
+
* ⚠️ No accounts, no secrets and no channels — and the reason is mechanical rather than tidy
|
|
382
|
+
* (ADR-0005 §4): this body is read, shared, exported and put into model context, so a secret in it
|
|
383
|
+
* is a secret in a citation. Identity is Gate's, accounts are the portal's, channels are runtime
|
|
384
|
+
* configuration.
|
|
385
|
+
*
|
|
386
|
+
* ⚠️ `tools` is the one correction to that list (D30), and it is narrower than it looks. What is
|
|
387
|
+
* stored is a **selection of whole servers plus who delegated them**, never a mirrored permission
|
|
388
|
+
* and never a catalog: the catalog stays a live `tools/list` made with the delegator's token at the
|
|
389
|
+
* moment the agent runs. ADR-0005 §4's "no tools in the definition" forbade the mirror, and the
|
|
390
|
+
* mirror is still forbidden — a tool name, a schema or an account in here would be the thing that
|
|
391
|
+
* line was written against.
|
|
299
392
|
*
|
|
300
393
|
* ⚠️ Strict on purpose, and deliberately stricter than the runtime's own reader
|
|
301
394
|
* (`packages/agent/src/definition/definition.ts`, which is `z.object`). Intel is the writer: an
|
|
@@ -303,15 +396,37 @@ export const AgentModel = z.strictObject({
|
|
|
303
396
|
* reader and released separately, so it must keep starting agents when Intel adds a field
|
|
304
397
|
* tomorrow. The asymmetry is the point, not an oversight.
|
|
305
398
|
*/
|
|
306
|
-
|
|
399
|
+
const AgentBody = {
|
|
307
400
|
references: z.array(AgentReference).max(200).default([]),
|
|
308
401
|
schedules: z.array(AgentSchedule).max(50).default([]),
|
|
309
402
|
model: AgentModel,
|
|
403
|
+
};
|
|
404
|
+
export const AgentDefinition = z.strictObject({
|
|
405
|
+
...AgentBody,
|
|
406
|
+
// `null` is "this agent has no tools", and it is also what every definition written before D30
|
|
407
|
+
// parses to. An empty `servers` list means the same thing and is kept as its own state so
|
|
408
|
+
// removing the last server does not have to erase who was delegating.
|
|
409
|
+
tools: AgentToolDelegation.nullable().default(null),
|
|
410
|
+
});
|
|
411
|
+
/**
|
|
412
|
+
* The same document as `AgentDefinition`, minus the one field a caller may not write.
|
|
413
|
+
*
|
|
414
|
+
* ⚠️ Two schemas rather than one, and the split is load-bearing (#208, D30). Everything an agent IS
|
|
415
|
+
* comes from whoever edits it; **whose portal connection it acts on** does not, because that is an
|
|
416
|
+
* authority the editor would be granting to themselves. So the write shape simply has no place to
|
|
417
|
+
* put it: `{ tools: { servers: [...] } }` is what a screen or an MCP client sends, Intel adds
|
|
418
|
+
* `delegatedBy` from the session, and a body that tries to name one is refused by the strict object
|
|
419
|
+
* before any of it is read. The reading shape keeps the field because a reader must be able to see
|
|
420
|
+
* whose connection an agent runs on.
|
|
421
|
+
*/
|
|
422
|
+
export const AgentDefinitionInput = z.strictObject({
|
|
423
|
+
...AgentBody,
|
|
424
|
+
tools: AgentToolSelection.nullable().default(null),
|
|
310
425
|
});
|
|
311
426
|
export const SaveAgentDefinitionInput = z.strictObject({
|
|
312
427
|
nodeId: IntelId,
|
|
313
428
|
baseVersionId: IntelId.nullable(),
|
|
314
|
-
definition:
|
|
429
|
+
definition: AgentDefinitionInput,
|
|
315
430
|
idempotencyKey: z.string().min(8).max(200),
|
|
316
431
|
});
|
|
317
432
|
export const GetAgentInput = z.strictObject({ nodeId: IntelId });
|
|
@@ -339,7 +454,7 @@ export const CreateAgentInput = z.strictObject({
|
|
|
339
454
|
parentId: IntelId.nullable().default(null),
|
|
340
455
|
title: z.string().trim().min(1).max(240),
|
|
341
456
|
description: z.string().trim().max(2_000).nullable().default(null),
|
|
342
|
-
definition:
|
|
457
|
+
definition: AgentDefinitionInput,
|
|
343
458
|
idempotencyKey: z.string().min(8).max(200),
|
|
344
459
|
});
|
|
345
460
|
// The ID of the Gate Application an agent runs as. Deliberately NOT an `IntelId`: it is Better
|
|
@@ -362,38 +477,37 @@ export const NodeAgent = z.strictObject({
|
|
|
362
477
|
applicationId: GateApplicationId.nullable(),
|
|
363
478
|
});
|
|
364
479
|
/**
|
|
365
|
-
*
|
|
480
|
+
* ⚠️ There is NO key field in this file, and adding one back would be the regression (D29, #207).
|
|
366
481
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
482
|
+
* Until #207 the create answer carried the Application key in plain text, once, and a person had to
|
|
483
|
+
* carry it into a Worker secret by hand — which is why an agent created through the screen could
|
|
484
|
+
* never run (#200). The key now goes from Gate straight into the agent runtime over Intel's service
|
|
485
|
+
* binding and is encrypted into that agent's Durable Object; it reaches no browser, no MCP tool
|
|
486
|
+
* result and no response body at all. `NodeAgent` is a `z.strictObject`, so a field named `key`
|
|
487
|
+
* added anywhere in this file is a parse error at the boundary rather than a leak somebody has to
|
|
488
|
+
* spot in review.
|
|
373
489
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
490
|
+
* What `POST /nodes/agents` and `agent_create` answer is therefore exactly what every read answers:
|
|
491
|
+
* the node, its first definition, and the `applicationId` that NAMES the principal without
|
|
492
|
+
* authenticating it.
|
|
377
493
|
*/
|
|
378
|
-
export const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
});
|
|
494
|
+
export const CreatedAgent = NodeAgent;
|
|
495
|
+
// Which agent's key is being replaced. `nodeId` and not the Application ID: this addresses an agent
|
|
496
|
+
// in Intel's tree, and the Application behind it is Intel's to look up — a caller naming the
|
|
497
|
+
// principal directly would be rotating a key for an agent nobody checked they may edit.
|
|
498
|
+
export const RotateAgentKeyInput = z.strictObject({ nodeId: IntelId });
|
|
384
499
|
/**
|
|
385
|
-
* What
|
|
386
|
-
* other agent read answers with the plain `NodeAgent` above.
|
|
500
|
+
* What replacing an agent's key answers.
|
|
387
501
|
*
|
|
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.
|
|
502
|
+
* ⚠️ No key, and that is the whole shape of D29: Intel asks Gate for a new one, hands it to the
|
|
503
|
+
* runtime over the service binding, and forgets it inside the same call. What the caller gets is
|
|
504
|
+
* the fact that it happened, so a screen can say so — `applicationId` names the principal whose key
|
|
505
|
+
* was replaced, which is a name and not a credential.
|
|
394
506
|
*/
|
|
395
|
-
export const
|
|
396
|
-
|
|
507
|
+
export const AgentKeyRotated = z.strictObject({
|
|
508
|
+
nodeId: IntelId,
|
|
509
|
+
applicationId: GateApplicationId,
|
|
510
|
+
rotatedAt: IsoDateTime,
|
|
397
511
|
});
|
|
398
512
|
export const AgentList = z.strictObject({ items: z.array(Node) });
|
|
399
513
|
// ⚠️ Kept for what is already stored, not for what is written. Relations were picked in a dialog
|
|
@@ -568,8 +682,11 @@ export const ToolSourceUrl = z.url().refine((value) => {
|
|
|
568
682
|
return false;
|
|
569
683
|
}
|
|
570
684
|
}, "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
|
-
//
|
|
685
|
+
// The portal namespaces every upstream tool, so the name alone identifies the target server. The
|
|
686
|
+
// portal is still the one that resolves it and attaches the credentials — Intel never holds an
|
|
687
|
+
// upstream credential. Since D30 Intel does read the namespace for one purpose: attributing a tool
|
|
688
|
+
// to a server the portal's own `portal_list_servers` already named, so a delegation can be cut to
|
|
689
|
+
// whole servers. That is attribution, not routing.
|
|
573
690
|
export const ToolName = z.string().min(1).max(240);
|
|
574
691
|
export const ToolAnnotations = z.strictObject({
|
|
575
692
|
title: z.string().max(240).optional(),
|
|
@@ -593,6 +710,58 @@ export const ToolCatalog = z.strictObject({
|
|
|
593
710
|
portalConnected: z.boolean(),
|
|
594
711
|
items: z.array(ToolCapability),
|
|
595
712
|
});
|
|
713
|
+
/**
|
|
714
|
+
* One MCP server the asking user reaches right now, as the portal itself names it (D30).
|
|
715
|
+
*
|
|
716
|
+
* ⚠️ `toolCount` is a fact about this moment and this user, not a size. It exists so a picker can
|
|
717
|
+
* say "9 tools" instead of showing a handle alone, and it must never be read as what an agent will
|
|
718
|
+
* get: the delegated run asks the portal again, with the delegator's token.
|
|
719
|
+
*/
|
|
720
|
+
export const ToolServer = z.strictObject({
|
|
721
|
+
handle: ToolServerHandle,
|
|
722
|
+
name: z.string().min(1).max(240),
|
|
723
|
+
toolCount: z.number().int().min(0),
|
|
724
|
+
});
|
|
725
|
+
// The same live-query rule as the tool catalog, one level up. `portalConnected: false` is the state
|
|
726
|
+
// of somebody who has not signed into the portal yet, and it is not an error.
|
|
727
|
+
export const ToolServerCatalog = z.strictObject({
|
|
728
|
+
portalConnected: z.boolean(),
|
|
729
|
+
items: z.array(ToolServer),
|
|
730
|
+
});
|
|
731
|
+
/**
|
|
732
|
+
* Which of the named servers a tool belongs to, or `null` for none of them.
|
|
733
|
+
*
|
|
734
|
+
* ⚠️ THE TRAP: a tool name does not say where its server name ends.
|
|
735
|
+
*
|
|
736
|
+
* The portal writes `<server>_<tool>`, and both halves may contain underscores — `intel_flow_get`
|
|
737
|
+
* reads equally well as server `intel` with tool `flow_get` and as a server called `intel_flow`
|
|
738
|
+
* with tool `get`. Splitting on the first underscore is therefore a guess that is wrong the day
|
|
739
|
+
* somebody adds a server whose name contains one, and on the API side being wrong means an agent
|
|
740
|
+
* delegated server A quietly reaching server B.
|
|
741
|
+
*
|
|
742
|
+
* So the prefix is never split. It is only ever MATCHED against handles the portal itself named,
|
|
743
|
+
* and the longest match wins: with `intel` and `intel_flow` both declared, `intel_flow_get` belongs
|
|
744
|
+
* to `intel_flow`, which is the only reading in which both declarations stay true.
|
|
745
|
+
*
|
|
746
|
+
* ⚠️ This lives in the contract because HOW A NAME IS READ is a property of the wire, and both
|
|
747
|
+
* surfaces read the same wire: `packages/api` cuts a delegation with it, `packages/ui` groups the
|
|
748
|
+
* tools screen with it (#212). A second implementation in the browser would be the third answer to
|
|
749
|
+
* one question — the underscore rule has already been answered differently in two places once
|
|
750
|
+
* (#106, #107), and the copies disagreed. What deliberately stays OUT of here is everything about
|
|
751
|
+
* reach: which handles are declared, which are enabled, which may be delegated and which one owns
|
|
752
|
+
* the portal's own management tools are decisions with consequences, and they belong to
|
|
753
|
+
* `packages/api/src/tools/tool-servers`. This function only reads a name.
|
|
754
|
+
*/
|
|
755
|
+
export function serverOf(toolName, handles) {
|
|
756
|
+
let best = null;
|
|
757
|
+
for (const handle of handles) {
|
|
758
|
+
if (!toolName.startsWith(`${handle}_`))
|
|
759
|
+
continue;
|
|
760
|
+
if (best === null || handle.length > best.length)
|
|
761
|
+
best = handle;
|
|
762
|
+
}
|
|
763
|
+
return best;
|
|
764
|
+
}
|
|
596
765
|
export const TestToolInput = z.strictObject({
|
|
597
766
|
name: ToolName,
|
|
598
767
|
arguments: z.record(z.string(), z.unknown()).default({}),
|