@anchrd/intel-ui 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/package.json +2 -9
  2. package/src/app/app-tree/app-tree.tsx +59 -147
  3. package/src/app/settings-dialog/settings-dialog.tsx +10 -32
  4. package/src/data/intel-data-provider/intel-data-provider.ts +0 -163
  5. package/src/data/intel-data-provider/intel-data-provider.types.ts +0 -100
  6. package/src/entry-picker/entry-picker.tsx +6 -6
  7. package/src/i18n/de.json +6 -217
  8. package/src/i18n/en.json +6 -217
  9. package/src/i18n/es.json +6 -217
  10. package/src/import-bundle/import-bundle.tsx +152 -0
  11. package/src/kind-icon.ts +1 -15
  12. package/src/main.tsx +7 -10
  13. package/src/node-table/node-table.tsx +2 -2
  14. package/src/nodes/nodes.tsx +0 -34
  15. package/src/resource-menu/resource-menu.tsx +42 -14
  16. package/src/section-hint/section-hint.tsx +2 -2
  17. package/src/title-row/title-row.tsx +5 -22
  18. package/src/tools/tools.tsx +0 -2
  19. package/vite.config.ts +0 -4
  20. package/src/agent/agent-avatar/agent-avatar.tsx +0 -34
  21. package/src/agent/agent-calendar/agent-calendar.tsx +0 -160
  22. package/src/agent/agent-chat/agent-chat.tsx +0 -118
  23. package/src/agent/agent-costs/agent-costs.ts +0 -95
  24. package/src/agent/agent-cron/agent-cron.ts +0 -68
  25. package/src/agent/agent-definition/agent-definition.ts +0 -96
  26. package/src/agent/agent-delegation-notice/agent-delegation-notice.ts +0 -48
  27. package/src/agent/agent-entry-title/agent-entry-title.ts +0 -72
  28. package/src/agent/agent-log/agent-log.tsx +0 -308
  29. package/src/agent/agent-models/agent-models.ts +0 -166
  30. package/src/agent/agent-models/agent-models.types.ts +0 -24
  31. package/src/agent/agent-profile/agent-profile.tsx +0 -1006
  32. package/src/agent/agent-state/agent-state.ts +0 -85
  33. package/src/agent/agent-status-dot/agent-status-dot.ts +0 -73
  34. package/src/agent/agent.tsx +0 -507
  35. package/src/board/board-calendar/board-calendar.tsx +0 -89
  36. package/src/board/board-card/board-card.tsx +0 -106
  37. package/src/board/board-data/board-data.ts +0 -241
  38. package/src/board/board-data/board-data.types.ts +0 -63
  39. package/src/board/board-detail/board-detail.tsx +0 -629
  40. package/src/board/board-gantt/board-gantt.ts +0 -545
  41. package/src/board/board-gantt/board-gantt.tsx +0 -286
  42. package/src/board/board-graph/board-graph.ts +0 -174
  43. package/src/board/board-graph/board-graph.tsx +0 -168
  44. package/src/board/board-items/board-items.ts +0 -183
  45. package/src/board/board-kanban/board-kanban.ts +0 -137
  46. package/src/board/board-kanban/board-kanban.tsx +0 -257
  47. package/src/board/board-status/board-status.ts +0 -59
  48. package/src/board/board-statuses/board-statuses.ts +0 -63
  49. package/src/board/board-statuses/board-statuses.tsx +0 -228
  50. package/src/board/board-table/board-table.ts +0 -33
  51. package/src/board/board-table/board-table.tsx +0 -413
  52. package/src/board/board-views/board-views.tsx +0 -68
  53. package/src/board/board-views/board-views.types.ts +0 -29
  54. package/src/board/board.tsx +0 -251
  55. package/src/components/ui/item-calendar.tsx +0 -181
  56. package/src/components/ui/item-gantt.tsx +0 -463
  57. package/src/components/ui/kanban.tsx +0 -282
  58. package/src/data/agent-runtime/agent-runtime.ts +0 -131
  59. package/src/hooks/use-capabilities.ts +0 -22
  60. package/src/hooks/use-model-catalog.ts +0 -26
  61. package/src/timezone/timezone-combobox/timezone-combobox.tsx +0 -149
  62. package/src/timezone/timezone-context.tsx +0 -65
  63. package/src/timezone/timezone.ts +0 -174
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-ui",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -33,27 +33,20 @@
33
33
  "typecheck": "tsc --noEmit"
34
34
  },
35
35
  "dependencies": {
36
- "@anchrd/intel-contract": "^0.12.0",
37
- "@assistant-ui/react": "^0.15.4",
38
- "@assistant-ui/react-ai-sdk": "^1.4.4",
36
+ "@anchrd/intel-contract": "^0.13.0",
39
37
  "@blocknote/core": "^0.52.1",
40
38
  "@blocknote/react": "^0.52.1",
41
39
  "@blocknote/shadcn": "^0.52.1",
42
- "@dnd-kit/core": "^6.3.1",
43
- "@dnd-kit/sortable": "^10.0.0",
44
- "@dnd-kit/utilities": "^3.2.2",
45
40
  "@react-sigma/core": "^5.0.6",
46
41
  "@tailwindcss/vite": "^4.3.3",
47
42
  "@tanstack/react-query": "^5.101.4",
48
43
  "@tanstack/react-router": "^1.170.18",
49
- "@tanstack/react-table": "^9.1.0",
50
44
  "@vitejs/plugin-react": "^6.0.4",
51
45
  "@xyflow/react": "^12.11.2",
52
46
  "class-variance-authority": "^0.7.1",
53
47
  "clsx": "^2.1.1",
54
48
  "cmdk": "^1.1.1",
55
49
  "colorjs.io": "^0.7.1",
56
- "croner": "^10.0.1",
57
50
  "fflate": "^0.8.3",
58
51
  "graphology": "^0.26.0",
59
52
  "lucide-react": "^1.25.0",
@@ -1,21 +1,15 @@
1
1
  import { useMutation, useQueries, useQueryClient } from "@tanstack/react-query";
2
2
  import { useNavigate, useRouterState } from "@tanstack/react-router";
3
- import { zipSync } from "fflate";
4
3
  import {
5
4
  ChevronRight,
6
5
  CornerLeftUp,
7
6
  FileArchive,
8
- FileText,
9
- Folder,
10
7
  FolderOpen,
11
8
  FolderUp,
12
9
  Plus,
13
- Table,
14
10
  Upload,
15
- Workflow,
16
11
  } from "lucide-react";
17
12
  import { useRef, useState } from "react";
18
- import { defaultModel } from "@/agent/agent-models/agent-models.ts";
19
13
  import {
20
14
  type MoveDestination,
21
15
  moveErrorKey,
@@ -28,6 +22,7 @@ import {
28
22
  DropdownMenu,
29
23
  DropdownMenuContent,
30
24
  DropdownMenuItem,
25
+ DropdownMenuSeparator,
31
26
  DropdownMenuTrigger,
32
27
  } from "@/components/ui/dropdown-menu";
33
28
  import {
@@ -39,40 +34,19 @@ import {
39
34
  } from "@/components/ui/sidebar";
40
35
  import { flowEntry } from "@/data/intel-data-provider/intel-data-provider.ts";
41
36
  import type { TreeEntry } from "@/data/intel-data-provider/intel-data-provider.types.ts";
42
- import { useCapabilities } from "@/hooks/use-capabilities.ts";
43
37
  import { useI18n } from "@/i18n/i18n-context.tsx";
38
+ import { type ImportKind, useBundleImport } from "@/import-bundle/import-bundle.tsx";
44
39
  import { kindIcons } from "@/kind-icon.ts";
45
40
  import { Modal } from "@/modal/modal.tsx";
46
41
  import { useIntelRouterContext } from "@/router/router-context.ts";
47
42
  import { selectedFrom } from "@/router/selection-search.ts";
48
43
 
49
- type NewKind = "folder" | "document" | "table" | "flow" | "agent";
50
- // The two ways a bundle arrives (#137): an existing zip, or a picked folder the browser hands over
51
- // file by file and this component zips before it travels — one wire format, one server door.
52
- type ImportKind = "importZip" | "importFolder";
44
+ type NewKind = "folder" | "document" | "table" | "flow";
53
45
  type Creating = { parentId: string | null; kind: NewKind };
54
46
 
55
47
  // Attachments are inlined as base64, so the browser holds the file twice while it uploads.
56
48
  const MaxUploadBytes = 15_000_000;
57
49
 
58
- // The server refuses anything larger anyway (#137); refusing here saves uploading it first.
59
- const MaxImportBytes = 64 * 1024 * 1024;
60
-
61
- // A picked directory as the zip the import endpoint reads. `webkitRelativePath` carries the folder
62
- // structure, with the picked folder itself as the first segment — kept, so the folder lands as a
63
- // folder rather than spilling its contents into the target.
64
- async function zipOfDirectory(files: readonly File[]): Promise<Blob> {
65
- const entries: Record<string, Uint8Array> = {};
66
- let total = 0;
67
- for (const file of files) {
68
- const path = file.webkitRelativePath || file.name;
69
- total += file.size;
70
- if (total > MaxImportBytes) throw new Error("Import exceeds the 64 MB limit");
71
- entries[path] = new Uint8Array(await file.arrayBuffer());
72
- }
73
- return new Blob([zipSync(entries).slice().buffer], { type: "application/zip" });
74
- }
75
-
76
50
  async function fileBase64(file: File): Promise<string> {
77
51
  if (file.size > MaxUploadBytes) throw new Error("Attachment exceeds the 15 MB upload limit");
78
52
  return await new Promise((resolve, reject) => {
@@ -115,9 +89,6 @@ export function AppTree() {
115
89
  const [creating, setCreating] = useState<Creating | null>(null);
116
90
  const [uploadTo, setUploadTo] = useState<string | null>(null);
117
91
  const uploadInput = useRef<HTMLInputElement>(null);
118
- const [importTo, setImportTo] = useState<string | null>(null);
119
- const importZipInput = useRef<HTMLInputElement>(null);
120
- const importFolderInput = useRef<HTMLInputElement>(null);
121
92
  const [dragged, setDragged] = useState<TreeEntry | null>(null);
122
93
  const draggedRef = useRef<TreeEntry | null>(null);
123
94
  // Which target the pointer is over: `undefined` for none, `null` for the root strip, an id for a
@@ -131,6 +102,14 @@ export function AppTree() {
131
102
  if (destination.id !== null) toggle({ id: destination.id, type: "folder" }, true);
132
103
  },
133
104
  });
105
+ // ⚠️ The root's import, and only the root's. A folder imports through its own three-dot menu
106
+ // (#346); the tree keeps this one because the top level has no resource whose menu could hold it.
107
+ const bundleImport = useBundleImport({
108
+ where: i18n.t("tree.move.root"),
109
+ onImported: (parentId) => {
110
+ if (parentId !== null) toggle({ id: parentId, type: "folder" }, true);
111
+ },
112
+ });
134
113
 
135
114
  const location = useRouterState({
136
115
  select: (state) => ({
@@ -207,26 +186,6 @@ export function AppTree() {
207
186
  });
208
187
  return { area: "/flows" as const, id: flow.id, parentId, collection: "flows" as const };
209
188
  }
210
- // ⚠️ Its own endpoint, not `createNodes`: an agent is created WITH its first definition
211
- // version, because a definition names the model and there is no agent without one. The
212
- // create dialog does not ask for it — `defaultModel()` answers, and the profile changes it.
213
- if (kind === "agent") {
214
- const created = await data.createAgent({
215
- parentId,
216
- title,
217
- description: null,
218
- // A new agent has no tools: giving it any is a choice made in the profile, out of what
219
- // this person reaches in the portal (D30).
220
- definition: { references: [], schedules: [], model: defaultModel(), tools: null },
221
- idempotencyKey: crypto.randomUUID(),
222
- });
223
- return {
224
- area: "/nodes" as const,
225
- id: created.node.id,
226
- parentId,
227
- collection: "node-graph" as const,
228
- };
229
- }
230
189
  const node = await data.createNodes({
231
190
  parentId,
232
191
  kind,
@@ -301,26 +260,6 @@ export function AppTree() {
301
260
  },
302
261
  });
303
262
 
304
- // One import, one request, one answer (#137): the server creates the whole subtree or nothing,
305
- // so this mutation never has a partial state to clean up after — unlike the attachment upload
306
- // above, whose two steps can strand a node.
307
- const importBundle = useMutation({
308
- mutationFn: async ({ parentId, zip }: { parentId: string | null; zip: Blob }) => {
309
- if (zip.size > MaxImportBytes) throw new Error("Import exceeds the 64 MB limit");
310
- const result = await data.importNodeBundle({
311
- nodeId: parentId,
312
- zip,
313
- idempotencyKey: crypto.randomUUID(),
314
- });
315
- return { parentId, result };
316
- },
317
- onSuccess: async (imported) => {
318
- // A bundle can carry flows, so the flow collections go stale alongside the node ones.
319
- await reveal(imported.parentId, "node-graph");
320
- await queryClient.invalidateQueries({ queryKey: ["flows"] });
321
- },
322
- });
323
-
324
263
  // What a drop on this target would do. `undefined` means nothing is being dragged, so the target
325
264
  // is not a target at all.
326
265
  function verdictFor(
@@ -376,11 +315,6 @@ export function AppTree() {
376
315
  uploadInput.current?.click();
377
316
  }
378
317
 
379
- function startImport(parentId: string | null, kind: ImportKind) {
380
- setImportTo(parentId);
381
- (kind === "importZip" ? importZipInput : importFolderInput).current?.click();
382
- }
383
-
384
318
  function renderLevel(parent: Level, ancestors: ReadonlySet<string>, label?: string) {
385
319
  const level = levelFor(parent);
386
320
  if (!level || level.isPending) {
@@ -566,7 +500,6 @@ export function AppTree() {
566
500
  label={i18n.t("tree.add", { title: entry.title })}
567
501
  onSelect={(kind) => {
568
502
  if (kind === "upload") startUpload(target);
569
- else if (kind === "importZip" || kind === "importFolder") startImport(target, kind);
570
503
  else setCreating({ parentId: target, kind });
571
504
  }}
572
505
  />
@@ -621,9 +554,9 @@ export function AppTree() {
621
554
  {/* The tree is the navigation and carries no heading of its own (ADR-0004); the list is
622
555
  named for assistive technology instead. */}
623
556
  {renderLevel({ id: null, type: "folder" }, new Set(), i18n.t("tree.label"))}
624
- {create.isError || upload.isError || importBundle.isError ? (
557
+ {create.isError || upload.isError || bundleImport.isError ? (
625
558
  <p role="alert" className="px-2 py-1.5 text-sm text-destructive">
626
- {importBundle.isError
559
+ {bundleImport.isError
627
560
  ? i18n.t("tree.importFailed")
628
561
  : upload.isError
629
562
  ? i18n.t("tree.uploadFailed")
@@ -651,7 +584,8 @@ export function AppTree() {
651
584
  variant="row"
652
585
  onSelect={(kind) => {
653
586
  if (kind === "upload") startUpload(null);
654
- else if (kind === "importZip" || kind === "importFolder") startImport(null, kind);
587
+ else if (kind === "importZip" || kind === "importFolder")
588
+ bundleImport.start(null, kind);
655
589
  else setCreating({ parentId: null, kind });
656
590
  }}
657
591
  />
@@ -668,38 +602,7 @@ export function AppTree() {
668
602
  event.currentTarget.value = "";
669
603
  }}
670
604
  />
671
- <input
672
- ref={importZipInput}
673
- type="file"
674
- accept=".zip,application/zip"
675
- className="sr-only"
676
- aria-label={i18n.t("tree.new.importZip")}
677
- onChange={(event) => {
678
- const file = event.target.files?.[0];
679
- if (file) importBundle.mutate({ parentId: importTo, zip: file });
680
- event.currentTarget.value = "";
681
- }}
682
- />
683
- {/* `webkitdirectory` is the one way a browser hands over a folder; React does not know the
684
- attribute, so it is spread past the type checker rather than invented as a prop. */}
685
- <input
686
- ref={importFolderInput}
687
- type="file"
688
- multiple
689
- {...({ webkitdirectory: "" } as object)}
690
- className="sr-only"
691
- aria-label={i18n.t("tree.new.importFolder")}
692
- onChange={(event) => {
693
- const files = Array.from(event.target.files ?? []);
694
- const parentId = importTo;
695
- if (files.length > 0) {
696
- void zipOfDirectory(files)
697
- .then((zip) => importBundle.mutate({ parentId, zip }))
698
- .catch(() => importBundle.mutate({ parentId, zip: new Blob([]) }));
699
- }
700
- event.currentTarget.value = "";
701
- }}
702
- />
605
+ {bundleImport.inputs}
703
606
  {creating ? (
704
607
  <Modal title={i18n.t(`tree.new.${creating.kind}`)} close={() => setCreating(null)}>
705
608
  <CreateForm
@@ -719,40 +622,27 @@ export function AppTree() {
719
622
  //
720
623
  // `row` is the root's form: a full-width row that says what it does, because the root has no title
721
624
  // beside which a bare icon would mean anything.
722
- function AddMenu({
723
- label,
724
- variant = "icon",
725
- onSelect,
726
- }: {
727
- label: string;
728
- variant?: "icon" | "row";
729
- onSelect(kind: NewKind | "upload" | ImportKind): void;
730
- }) {
625
+ //
626
+ // ⚠️ The two variants do not offer the same things, and the type says so rather than a comment: only
627
+ // the root can emit an import (#346), because only the root has no three-dot menu to hold one. A
628
+ // single wide signature would leave the folder row with a branch nothing can reach.
629
+ type AddMenuProps =
630
+ | { label: string; variant: "row"; onSelect(kind: NewKind | "upload" | ImportKind): void }
631
+ | { label: string; variant?: "icon"; onSelect(kind: NewKind | "upload"): void };
632
+
633
+ function AddMenu(props: AddMenuProps) {
634
+ const { label, onSelect } = props;
635
+ const variant = props.variant ?? "icon";
731
636
  const i18n = useI18n();
732
- // Whether this deployment runs an agent runtime (#190). Until the answer arrives the item is
733
- // absent rather than provisional: a menu entry that vanished after being seen was a lie, one that
734
- // appears a beat late was merely loading.
735
- const agentRuntime = useCapabilities().data?.agentRuntime === true;
736
- const items: Array<{
737
- kind: NewKind | "upload" | ImportKind;
738
- labelKey: string;
739
- Icon: typeof Plus;
740
- }> = [
741
- { kind: "folder", labelKey: "tree.new.folder", Icon: Folder },
742
- { kind: "document", labelKey: "tree.new.document", Icon: FileText },
743
- { kind: "table", labelKey: "tree.new.table", Icon: Table },
744
- { kind: "upload", labelKey: "tree.new.upload", Icon: Upload },
745
- { kind: "flow", labelKey: "tree.new.flow", Icon: Workflow },
746
- // ⚠️ The same icon the tree draws for an agent row, taken from the one kind-icon map rather
747
- // than named again here — a second `Bot` beside it is how the two start disagreeing.
748
- // Offered only where a runtime exists to run what would be created (#190).
749
- ...(agentRuntime
750
- ? [{ kind: "agent" as const, labelKey: "tree.new.agent", Icon: kindIcons.agent }]
751
- : []),
752
- // The bundle round trip's second half (#137): what the export produced, or a plain folder from
753
- // the machine, lands here as a new subtree.
754
- { kind: "importZip", labelKey: "tree.new.importZip", Icon: FileArchive },
755
- { kind: "importFolder", labelKey: "tree.new.importFolder", Icon: FolderUp },
637
+ // ⚠️ Three groups, and the separators are the answer to what the plus promises: four kinds are
638
+ // made here, an upload brings one thing in from the machine, an import brings a whole subtree
639
+ // (#346). The icons come from the one kind-icon map rather than being named again — a second
640
+ // `Folder` beside it is how the tree row and this menu start disagreeing.
641
+ const kinds: Array<{ kind: NewKind; labelKey: string; Icon: typeof Plus }> = [
642
+ { kind: "folder", labelKey: "tree.new.folder", Icon: kindIcons.folder },
643
+ { kind: "document", labelKey: "tree.new.document", Icon: kindIcons.document },
644
+ { kind: "table", labelKey: "tree.new.table", Icon: kindIcons.table },
645
+ { kind: "flow", labelKey: "tree.new.flow", Icon: kindIcons.flow },
756
646
  ];
757
647
  return (
758
648
  <DropdownMenu>
@@ -768,12 +658,34 @@ function AddMenu({
768
658
  {variant === "row" ? <span className="truncate">{label}</span> : null}
769
659
  </DropdownMenuTrigger>
770
660
  <DropdownMenuContent align="start" className="w-44">
771
- {items.map((item) => (
661
+ {kinds.map((item) => (
772
662
  <DropdownMenuItem key={item.kind} onSelect={() => onSelect(item.kind)}>
773
663
  <item.Icon aria-hidden="true" />
774
664
  {i18n.t(item.labelKey)}
775
665
  </DropdownMenuItem>
776
666
  ))}
667
+ <DropdownMenuSeparator />
668
+ <DropdownMenuItem onSelect={() => onSelect("upload")}>
669
+ <Upload aria-hidden="true" />
670
+ {i18n.t("tree.new.upload")}
671
+ </DropdownMenuItem>
672
+ {/* ⚠️ The root only, and that is not an omission at a folder row: a folder imports through
673
+ its own three-dot menu, beside the export it is the other half of (#136/#137). The root
674
+ has no resource and therefore no such menu, so dropping it here would put the top level
675
+ out of reach of an import altogether (#346). */}
676
+ {props.variant === "row" ? (
677
+ <>
678
+ <DropdownMenuSeparator />
679
+ <DropdownMenuItem onSelect={() => props.onSelect("importZip")}>
680
+ <FileArchive aria-hidden="true" />
681
+ {i18n.t("tree.new.importZip")}
682
+ </DropdownMenuItem>
683
+ <DropdownMenuItem onSelect={() => props.onSelect("importFolder")}>
684
+ <FolderUp aria-hidden="true" />
685
+ {i18n.t("tree.new.importFolder")}
686
+ </DropdownMenuItem>
687
+ </>
688
+ ) : null}
777
689
  </DropdownMenuContent>
778
690
  </DropdownMenu>
779
691
  );
@@ -14,16 +14,19 @@ import {
14
14
  } from "@/components/ui/select";
15
15
  import { useI18n, useLanguage } from "@/i18n/i18n-context.tsx";
16
16
  import { languageName } from "@/i18n/i18n-languages/i18n-languages.ts";
17
- import { SectionHint } from "@/section-hint/section-hint.tsx";
18
17
  import { type ThemeChoice, ThemeChoices } from "@/theme/theme.ts";
19
18
  import { useTheme } from "@/theme/theme-context.tsx";
20
- import { TimezoneCombobox } from "@/timezone/timezone-combobox/timezone-combobox.tsx";
21
- import { useTimezoneSelection } from "@/timezone/timezone-context.tsx";
22
19
 
23
- // The settings that belong to the person rather than to the installation: language, appearance and
24
- // timezone. All three are client state — they live in localStorage, not in the Intel HTTP surface —
25
- // so they do not belong on a screen with a save button. A choice applies at once; a dialog that
26
- // previews and only commits on "Save" would carry three states too many for three dropdowns.
20
+ // The settings that belong to the person rather than to the installation: language and appearance.
21
+ // Both are client state — they live in localStorage, not in the Intel HTTP surface — so they do not
22
+ // belong on a screen with a save button. A choice applies at once; a dialog that previews and only
23
+ // commits on "Save" would carry two states too many for two dropdowns.
24
+ //
25
+ // ⚠️ There was a third, the reader's timezone, and it left with the Agents (#388). It was a
26
+ // suggestion for a schedule and the lens its calendar was read through (#228); with both gone
27
+ // nothing read the value, and a preference that changes nothing on screen is worse than none —
28
+ // it invites the reader to set it and then contradicts them. Reading times in a chosen zone is a
29
+ // feature, and it would come back as one.
27
30
  //
28
31
  // The dialog is opened from outside (the user footer holds the state), because its trigger is a
29
32
  // DropdownMenuItem: Radix unmounts the menu content when an item is chosen and would take a dialog
@@ -37,7 +40,6 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
37
40
  const i18n = useI18n();
38
41
  const language = useLanguage();
39
42
  const theme = useTheme();
40
- const timezone = useTimezoneSelection();
41
43
 
42
44
  return (
43
45
  <Dialog open={open} onOpenChange={onOpenChange}>
@@ -93,30 +95,6 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
93
95
  </Select>
94
96
  </div>
95
97
  ) : null}
96
- {timezone ? (
97
- <div className="grid gap-2">
98
- {/* ⚠️ Behind the ⓘ (#249), and the SAME component the agent profile uses — three
99
- lines of prose under a dropdown explained something once and then pushed the
100
- dialog apart on every open. The `sr-only` copy below is not a duplicate: Radix
101
- puts a tooltip's content in the accessibility tree only while it is open, so the
102
- field would silently lose the description `aria-describedby` points at. */}
103
- <div className="flex items-center gap-1.5">
104
- <label className="text-sm font-medium" htmlFor="settings-timezone">
105
- {i18n.t("settings.timezone")}
106
- </label>
107
- <SectionHint hint={i18n.t("settings.timezoneHint")} />
108
- </div>
109
- <TimezoneCombobox
110
- id="settings-timezone"
111
- value={timezone.current}
112
- onChange={timezone.select}
113
- describedBy="settings-timezone-hint"
114
- />
115
- <p id="settings-timezone-hint" className="sr-only">
116
- {i18n.t("settings.timezoneHint")}
117
- </p>
118
- </div>
119
- ) : null}
120
98
  </div>
121
99
  </DialogContent>
122
100
  </Dialog>
@@ -1,23 +1,13 @@
1
1
  import {
2
- AddBoardTaskInput,
3
- AgentCosts,
4
- AgentKeyRotated,
5
2
  AppendTableRowsInput,
6
3
  AppendTableRowsResult,
7
4
  ArchiveFlowInput,
8
5
  ArchiveNodeInput,
9
- BoardTaskResult,
10
6
  BundleImportResult,
11
7
  CompleteFlowRunStepInput,
12
- ConfigureBoardInput,
13
- ConfigureBoardResult,
14
- CreateAgentInput,
15
- CreatedAgent,
16
8
  CreateFlowInput,
17
9
  CreateNodeInput,
18
10
  DefineTableInput,
19
- DeleteBoardTaskInput,
20
- DeleteBoardTaskResult,
21
11
  Flow,
22
12
  FlowDocument,
23
13
  FlowList,
@@ -27,15 +17,10 @@ import {
27
17
  FlowRunList,
28
18
  FlowRunStep,
29
19
  FlowValidation,
30
- IntelCapabilities,
31
20
  ListFlowRunsInput,
32
21
  ListFlowsInput,
33
22
  ListNodesInput,
34
- ModelCatalog,
35
- MoveBoardTaskInput,
36
23
  Node,
37
- NodeAgent,
38
- NodeBoard,
39
24
  NodeDocument,
40
25
  NodeGraph,
41
26
  NodeLinkList,
@@ -52,7 +37,6 @@ import {
52
37
  ResourceGrantList,
53
38
  RevokeGrantInput,
54
39
  RevokeGrantResult,
55
- SaveAgentDefinitionInput,
56
40
  SaveAttachmentInput,
57
41
  SaveFlowVersionInput,
58
42
  SaveNodeVersionInput,
@@ -64,18 +48,10 @@ import {
64
48
  StartFlowRunInput,
65
49
  ToolCatalog,
66
50
  ToolServerCatalog,
67
- UpdateBoardTaskInput,
68
51
  UpdateFlowInput,
69
52
  UpdateNodeInput,
70
53
  } from "@anchrd/intel-contract";
71
54
  import type { z } from "zod";
72
- import {
73
- AgentManualRun,
74
- AgentRunDetail,
75
- AgentRunList,
76
- AgentState,
77
- agentRuntimePath,
78
- } from "@/data/agent-runtime/agent-runtime.ts";
79
55
  import { createBrowserSignIn } from "@/data/sign-in/sign-in.ts";
80
56
  import type { IntelDataProvider, TreeEntry } from "./intel-data-provider.types.ts";
81
57
 
@@ -211,19 +187,6 @@ export function createIntelDataProvider(
211
187
  async getSession() {
212
188
  return await request("/session", SessionUser);
213
189
  },
214
- async getCapabilities() {
215
- return await request("/capabilities", IntelCapabilities);
216
- },
217
- /**
218
- * What the offered models cost and hold (#257).
219
- *
220
- * ⚠️ Asked of intel, never of Cloudflare. The account endpoint needs a token and a token does
221
- * not belong in a SPA — which was the stated reason the price table stayed hard-coded in this
222
- * package until now. The figures cross the wire; the credential does not.
223
- */
224
- async listModels() {
225
- return await request("/models", ModelCatalog);
226
- },
227
190
  listNodes,
228
191
  async listTreeChildren(parentId) {
229
192
  // Both sides of one folder, asked for in parallel and merged here rather than on the server:
@@ -336,49 +299,6 @@ export function createIntelDataProvider(
336
299
  { method: "POST", body: JSON.stringify(parsed) },
337
300
  );
338
301
  },
339
- async getBoard(nodeId) {
340
- return await request(`/nodes/${encodeURIComponent(nodeId)}/board`, NodeBoard);
341
- },
342
- async configureBoard(input) {
343
- const parsed = ConfigureBoardInput.parse(input);
344
- return await request(
345
- `/nodes/${encodeURIComponent(parsed.nodeId)}/board/configure`,
346
- ConfigureBoardResult,
347
- { method: "POST", body: JSON.stringify(parsed) },
348
- );
349
- },
350
- async addBoardTask(input) {
351
- const parsed = AddBoardTaskInput.parse(input);
352
- return await request(
353
- `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks`,
354
- BoardTaskResult,
355
- { method: "POST", body: JSON.stringify(parsed) },
356
- );
357
- },
358
- async updateBoardTask(input) {
359
- const parsed = UpdateBoardTaskInput.parse(input);
360
- return await request(
361
- `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/update`,
362
- BoardTaskResult,
363
- { method: "POST", body: JSON.stringify(parsed) },
364
- );
365
- },
366
- async moveBoardTask(input) {
367
- const parsed = MoveBoardTaskInput.parse(input);
368
- return await request(
369
- `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/move`,
370
- BoardTaskResult,
371
- { method: "POST", body: JSON.stringify(parsed) },
372
- );
373
- },
374
- async deleteBoardTask(input) {
375
- const parsed = DeleteBoardTaskInput.parse(input);
376
- return await request(
377
- `/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/delete`,
378
- DeleteBoardTaskResult,
379
- { method: "POST", body: JSON.stringify(parsed) },
380
- );
381
- },
382
302
  async listNodeVersions(nodeId) {
383
303
  return await request(`/nodes/${encodeURIComponent(nodeId)}/versions`, NodeVersionList);
384
304
  },
@@ -533,89 +453,6 @@ export function createIntelDataProvider(
533
453
  async listToolServers() {
534
454
  return await request("/tools/servers", ToolServerCatalog);
535
455
  },
536
- async getAgent(nodeId) {
537
- return await request(`/nodes/${encodeURIComponent(nodeId)}/agent`, NodeAgent);
538
- },
539
- // ⚠️ `CreatedAgent` is `NodeAgent` since D29, and that is the point rather than a leftover: the
540
- // create answer used to carry the Gate application key once (#182), and the browser had to be
541
- // given a credential it could not do anything with. It now goes from Gate into the agent runtime
542
- // inside the request, so the create answers exactly what a read answers.
543
- async createAgent(input) {
544
- return await request("/nodes/agents", CreatedAgent, {
545
- method: "POST",
546
- body: JSON.stringify(CreateAgentInput.parse(input)),
547
- });
548
- },
549
- /**
550
- * Replace the key of the Gate Application an agent runs as (D29, #207).
551
- *
552
- * ⚠️ The repair path for `agent_key_missing`, and the answer carries no key — intel asks Gate
553
- * and hands the new one to the runtime itself. Nothing here has a credential to hold.
554
- */
555
- async rotateAgentKey(nodeId) {
556
- return await request(
557
- `/nodes/agents/${encodeURIComponent(nodeId)}/rotate-key`,
558
- AgentKeyRotated,
559
- { method: "POST" },
560
- );
561
- },
562
- async saveAgentDefinition(input) {
563
- const parsed = SaveAgentDefinitionInput.parse(input);
564
- return await request(`/nodes/${encodeURIComponent(parsed.nodeId)}/agent`, NodeAgent, {
565
- method: "POST",
566
- body: JSON.stringify(parsed),
567
- });
568
- },
569
- /**
570
- * The agent runtime, asked through intel (#178).
571
- *
572
- * ⚠️ Plain `request`, with no second origin and no bearer of its own — that is the whole change.
573
- * The runtime used to be a Worker this page called directly, which meant CORS it does not
574
- * answer and a Gate token the browser cannot have. Intel now proxies `/agents/:agentId/*` and
575
- * attaches the caller's own token server-side, so a 401 from here really is intel's session
576
- * expiring and the sign-in that follows is the right one.
577
- *
578
- * ⚠️ A 403 is NOT that. It means the reader may not drive agents, which the agent page renders
579
- * as an answer rather than as a failure.
580
- */
581
- async listAgentRuns(agentId) {
582
- return await request(agentRuntimePath(agentId, "/runs"), AgentRunList);
583
- },
584
- /**
585
- * ⚠️ The one path under `/agents/:agentId` that intel answers ITSELF (#251). The runtime has no
586
- * idea what anything costs — the figures come out of Cloudflare's AI Gateway log, read with a
587
- * token that lives in intel and never in the browser.
588
- */
589
- async getAgentCosts(agentId) {
590
- return await request(agentRuntimePath(agentId, "/costs"), AgentCosts);
591
- },
592
- async getAgentRun(agentId, runId) {
593
- return await request(
594
- agentRuntimePath(agentId, `/runs/${encodeURIComponent(runId)}`),
595
- AgentRunDetail,
596
- );
597
- },
598
- async getAgentState(agentId) {
599
- return await request(agentRuntimePath(agentId, "/state"), AgentState);
600
- },
601
- async pauseAgent(agentId) {
602
- return await request(agentRuntimePath(agentId, "/pause"), AgentState, { method: "POST" });
603
- },
604
- async resumeAgent(agentId) {
605
- return await request(agentRuntimePath(agentId, "/resume"), AgentState, { method: "POST" });
606
- },
607
- async runAgentNow(agentId, target) {
608
- return await request(agentRuntimePath(agentId, "/run"), AgentManualRun, {
609
- method: "POST",
610
- body: JSON.stringify({ target }),
611
- });
612
- },
613
- agentChatApi(agentId) {
614
- // assistant-ui dials this itself, so it needs the path a browser can use — the same one every
615
- // other call above goes to, intel's own `/api/v1`.
616
- return `${baseUrl}/api/v1${agentRuntimePath(agentId, "/chat")}`;
617
- },
618
-
619
456
  // The Worker serves the connect route, so this navigation belongs to the data layer for the
620
457
  // same reason `loginPath` does. `silent=1` is what makes it a redirect nobody has to watch.
621
458
  startPortalSignIn(returnTo = "/tools") {