@anchrd/intel 0.59.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/README.md +496 -0
- package/bin/intel.mjs +134 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
- package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
- package/dist/adapters/cloudflare/cloudflare.js +416 -0
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
- package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
- package/dist/adapters/content/content.d.ts +3 -0
- package/dist/adapters/content/content.js +128 -0
- package/dist/adapters/content/content.types.d.ts +38 -0
- package/dist/adapters/content/content.types.js +1 -0
- package/dist/adapters/db/db-audit.d.ts +62 -0
- package/dist/adapters/db/db-audit.js +150 -0
- package/dist/adapters/db/db-boards.d.ts +6 -0
- package/dist/adapters/db/db-boards.js +324 -0
- package/dist/adapters/db/db-feed.d.ts +30 -0
- package/dist/adapters/db/db-feed.js +276 -0
- package/dist/adapters/db/db-flows.d.ts +42 -0
- package/dist/adapters/db/db-flows.js +1311 -0
- package/dist/adapters/db/db-grants.d.ts +133 -0
- package/dist/adapters/db/db-grants.js +269 -0
- package/dist/adapters/db/db-indexing.d.ts +3 -0
- package/dist/adapters/db/db-indexing.js +200 -0
- package/dist/adapters/db/db-oauth.d.ts +3 -0
- package/dist/adapters/db/db-oauth.js +26 -0
- package/dist/adapters/db/db-prompts.d.ts +46 -0
- package/dist/adapters/db/db-prompts.js +144 -0
- package/dist/adapters/db/db.d.ts +6 -0
- package/dist/adapters/db/db.js +1687 -0
- package/dist/adapters/db/db.types.d.ts +16 -0
- package/dist/adapters/db/db.types.js +1 -0
- package/dist/adapters/document-converter/document-converter.d.ts +2 -0
- package/dist/adapters/document-converter/document-converter.js +26 -0
- package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
- package/dist/adapters/document-converter/document-converter.types.js +1 -0
- package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
- package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
- package/dist/adapters/index-queue/index-queue.d.ts +10 -0
- package/dist/adapters/index-queue/index-queue.js +12 -0
- package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
- package/dist/adapters/index-queue/index-queue.types.js +1 -0
- package/dist/adapters/json-schema/json-schema.d.ts +2 -0
- package/dist/adapters/json-schema/json-schema.js +77 -0
- package/dist/adapters/openid/openid.d.ts +5 -0
- package/dist/adapters/openid/openid.js +301 -0
- package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
- package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
- package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
- package/dist/adapters/remote-tools/remote-tools.js +129 -0
- package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
- package/dist/adapters/semantic-index/semantic-index.js +139 -0
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
- package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
- package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
- package/dist/adapters/session-cookie/session-cookie.js +87 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
- package/dist/audit/audit.d.ts +2 -0
- package/dist/audit/audit.js +87 -0
- package/dist/audit/audit.types.d.ts +23 -0
- package/dist/audit/audit.types.js +1 -0
- package/dist/auth/auth.d.ts +2 -0
- package/dist/auth/auth.js +284 -0
- package/dist/auth/auth.types.d.ts +108 -0
- package/dist/auth/auth.types.js +1 -0
- package/dist/boards/boards.d.ts +44 -0
- package/dist/boards/boards.js +447 -0
- package/dist/boards/boards.types.d.ts +117 -0
- package/dist/boards/boards.types.js +1 -0
- package/dist/build/build.d.ts +2 -0
- package/dist/build/build.js +192 -0
- package/dist/build/build.types.d.ts +17 -0
- package/dist/build/build.types.js +1 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.js +1194 -0
- package/dist/bundle/bundle.types.d.ts +33 -0
- package/dist/bundle/bundle.types.js +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.js +262 -0
- package/dist/cli/cli.types.d.ts +14 -0
- package/dist/cli/cli.types.js +1 -0
- package/dist/contract/audit.d.ts +50 -0
- package/dist/contract/audit.js +72 -0
- package/dist/contract/board.d.ts +150 -0
- package/dist/contract/board.js +262 -0
- package/dist/contract/bundle.d.ts +147 -0
- package/dist/contract/bundle.js +139 -0
- package/dist/contract/contract.d.ts +112 -0
- package/dist/contract/contract.js +143 -0
- package/dist/contract/feed.d.ts +126 -0
- package/dist/contract/feed.js +149 -0
- package/dist/contract/flow-run.d.ts +346 -0
- package/dist/contract/flow-run.js +194 -0
- package/dist/contract/flow.d.ts +1057 -0
- package/dist/contract/flow.js +552 -0
- package/dist/contract/node.d.ts +551 -0
- package/dist/contract/node.js +456 -0
- package/dist/contract/share.d.ts +216 -0
- package/dist/contract/share.js +134 -0
- package/dist/contract/table.d.ts +171 -0
- package/dist/contract/table.js +117 -0
- package/dist/contract/tool.d.ts +122 -0
- package/dist/contract/tool.js +172 -0
- package/dist/feed/feed.d.ts +2 -0
- package/dist/feed/feed.js +77 -0
- package/dist/feed/feed.types.d.ts +23 -0
- package/dist/feed/feed.types.js +1 -0
- package/dist/flows/flows.d.ts +41 -0
- package/dist/flows/flows.js +2216 -0
- package/dist/flows/flows.types.d.ts +339 -0
- package/dist/flows/flows.types.js +1 -0
- package/dist/http/http.d.ts +9 -0
- package/dist/http/http.js +874 -0
- package/dist/http/http.types.d.ts +22 -0
- package/dist/http/http.types.js +1 -0
- package/dist/indexing/indexing.d.ts +6 -0
- package/dist/indexing/indexing.js +239 -0
- package/dist/indexing/indexing.types.d.ts +11 -0
- package/dist/indexing/indexing.types.js +1 -0
- package/dist/intel/intel.d.ts +3 -0
- package/dist/intel/intel.js +194 -0
- package/dist/intel/intel.types.d.ts +24 -0
- package/dist/intel/intel.types.js +1 -0
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +1292 -0
- package/dist/mcp/mcp.types.d.ts +25 -0
- package/dist/mcp/mcp.types.js +1 -0
- package/dist/nodes/document-links/document-links.d.ts +20 -0
- package/dist/nodes/document-links/document-links.js +57 -0
- package/dist/nodes/nodes.d.ts +2 -0
- package/dist/nodes/nodes.js +1667 -0
- package/dist/nodes/nodes.types.d.ts +511 -0
- package/dist/nodes/nodes.types.js +1 -0
- package/dist/prepare/prepare.d.ts +4 -0
- package/dist/prepare/prepare.js +16 -0
- package/dist/prepare/prepare.types.d.ts +9 -0
- package/dist/prepare/prepare.types.js +1 -0
- package/dist/prompts/prompts.d.ts +2 -0
- package/dist/prompts/prompts.js +65 -0
- package/dist/prompts/prompts.types.d.ts +71 -0
- package/dist/prompts/prompts.types.js +1 -0
- package/dist/shared/callers/callers.d.ts +28 -0
- package/dist/shared/callers/callers.js +30 -0
- package/dist/shared/csv/csv.d.ts +13 -0
- package/dist/shared/csv/csv.js +85 -0
- package/dist/shared/document-text/document-text.d.ts +21 -0
- package/dist/shared/document-text/document-text.js +31 -0
- package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
- package/dist/shared/gate-authorization/gate-authorization.js +26 -0
- package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
- package/dist/shared/grant-expiry/grant-expiry.js +26 -0
- package/dist/shared/intel-error/intel-error.d.ts +22 -0
- package/dist/shared/intel-error/intel-error.js +27 -0
- package/dist/shared/plain-title/plain-title.d.ts +16 -0
- package/dist/shared/plain-title/plain-title.js +45 -0
- package/dist/shared/problem-details/problem-details.d.ts +30 -0
- package/dist/shared/problem-details/problem-details.js +18 -0
- package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
- package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
- package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
- package/dist/shared/safe-return-path/safe-return-path.js +11 -0
- package/dist/shared/sha256/sha256.d.ts +2 -0
- package/dist/shared/sha256/sha256.js +14 -0
- package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
- package/dist/tools/tool-servers/tool-servers.js +177 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +207 -0
- package/dist/tools/tools.types.d.ts +56 -0
- package/dist/tools/tools.types.js +1 -0
- package/examples/branding/favicon.svg +5 -0
- package/examples/branding/logo.svg +5 -0
- package/examples/branding/theme.css +16 -0
- package/examples/dev.vars.example +7 -0
- package/examples/intel.json +9 -0
- package/migrations/0000_intel_foundation.sql +203 -0
- package/migrations/0001_portal_native_tools.sql +21 -0
- package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
- package/migrations/0003_folder_permissions.sql +211 -0
- package/migrations/0004_subflow_runs.sql +14 -0
- package/migrations/0005_flow_node_cleanup.sql +28 -0
- package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
- package/migrations/0006_links_are_written_in_the_text.sql +20 -0
- package/migrations/0007_no_node_waits.sql +69 -0
- package/migrations/0008_three_layers.sql +130 -0
- package/migrations/0009_no_context_policy.sql +48 -0
- package/migrations/0010_no_run_waits_a_year.sql +27 -0
- package/migrations/0011_one_name_for_the_tree.sql +53 -0
- package/migrations/0012_table_snapshots.sql +29 -0
- package/migrations/0017_a_vector_per_card.sql +38 -0
- package/migrations/0018_no_context_policy_at_last.sql +97 -0
- package/migrations/0019_one_name_for_the_grants.sql +52 -0
- package/migrations/0020_cascade_purge_replay.sql +11 -0
- package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
- package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
- package/migrations/0023_a_board_and_its_tasks.sql +116 -0
- package/migrations/0024_the_archive_is_a_column.sql +33 -0
- package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
- package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
- package/migrations/0027_the_runs_of_every_flow.sql +27 -0
- package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
- package/package.json +118 -0
- package/src/contract/CLAUDE.md +152 -0
- package/src/contract/README.md +102 -0
- package/src/contract/audit.ts +85 -0
- package/src/contract/board.ts +306 -0
- package/src/contract/bundle.ts +157 -0
- package/src/contract/contract.ts +161 -0
- package/src/contract/feed.ts +165 -0
- package/src/contract/flow-run.ts +241 -0
- package/src/contract/flow.ts +675 -0
- package/src/contract/node.ts +583 -0
- package/src/contract/share.ts +196 -0
- package/src/contract/table.ts +178 -0
- package/src/contract/tool.ts +202 -0
- package/ui/README.md +110 -0
- package/ui/components.json +21 -0
- package/ui/index.html +13 -0
- package/ui/package.json +11 -0
- package/ui/scripts/lint-tokens.mjs +187 -0
- package/ui/src/access-summary/access-summary.tsx +385 -0
- package/ui/src/app/action-slot/action-slot.tsx +27 -0
- package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
- package/ui/src/app/app-tree/app-tree.tsx +1175 -0
- package/ui/src/app/app.tsx +87 -0
- package/ui/src/app/header-search/header-search.tsx +294 -0
- package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
- package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
- package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
- package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
- package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
- package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
- package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
- package/ui/src/app/tree-move/tree-move.tsx +427 -0
- package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
- package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
- package/ui/src/app/user-footer/user-footer.tsx +188 -0
- package/ui/src/app/view-menu/view-menu.tsx +115 -0
- package/ui/src/app-root/app-root.tsx +109 -0
- package/ui/src/app-root/app-root.types.ts +27 -0
- package/ui/src/archive/archive.tsx +417 -0
- package/ui/src/attachment-detail/attachment-detail.ts +52 -0
- package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
- package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
- package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
- package/ui/src/board/board-assignee/board-assignee.ts +48 -0
- package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
- package/ui/src/board/board-chip/board-chip.tsx +46 -0
- package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
- package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
- package/ui/src/board/board-data/board-data.ts +195 -0
- package/ui/src/board/board-data/board-data.types.ts +38 -0
- package/ui/src/board/board-dates/board-dates.ts +40 -0
- package/ui/src/board/board-dates/board-dates.tsx +105 -0
- package/ui/src/board/board-kanban/board-kanban.ts +162 -0
- package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
- package/ui/src/board/board-open-task/board-open-task.ts +32 -0
- package/ui/src/board/board-panel/board-panel.tsx +259 -0
- package/ui/src/board/board-settings/board-settings.tsx +226 -0
- package/ui/src/board/board-status/board-status.tsx +60 -0
- package/ui/src/board/board-stripes/board-stripes.ts +128 -0
- package/ui/src/board/board-table/board-table.ts +53 -0
- package/ui/src/board/board-table/board-table.tsx +610 -0
- package/ui/src/board/board-task/board-task.ts +108 -0
- package/ui/src/board/board-task/board-task.tsx +335 -0
- package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
- package/ui/src/branding/branding.tsx +17 -0
- package/ui/src/branding/custom-logo.ts +1 -0
- package/ui/src/branding/favicon.default.svg +5 -0
- package/ui/src/branding/favicon.svg +5 -0
- package/ui/src/components/ui/avatar.tsx +39 -0
- package/ui/src/components/ui/button.tsx +64 -0
- package/ui/src/components/ui/collapsible.tsx +20 -0
- package/ui/src/components/ui/command.tsx +160 -0
- package/ui/src/components/ui/dialog.tsx +143 -0
- package/ui/src/components/ui/dropdown-menu.tsx +187 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/popover.tsx +41 -0
- package/ui/src/components/ui/select.tsx +163 -0
- package/ui/src/components/ui/separator.tsx +26 -0
- package/ui/src/components/ui/sheet.tsx +136 -0
- package/ui/src/components/ui/sidebar.tsx +693 -0
- package/ui/src/components/ui/skeleton.tsx +13 -0
- package/ui/src/components/ui/switch.tsx +25 -0
- package/ui/src/components/ui/table.tsx +82 -0
- package/ui/src/components/ui/tabs.tsx +52 -0
- package/ui/src/components/ui/tooltip.tsx +51 -0
- package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
- package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
- package/ui/src/data/query-client.ts +18 -0
- package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
- package/ui/src/data/request-refusal/request-refusal.ts +51 -0
- package/ui/src/data/sign-in/sign-in.ts +97 -0
- package/ui/src/data/sign-in/sign-in.types.ts +15 -0
- package/ui/src/document-link/document-link.tsx +114 -0
- package/ui/src/document-markdown/document-markdown.ts +195 -0
- package/ui/src/editor-schema/editor-schema.ts +23 -0
- package/ui/src/entry-picker/entry-picker.tsx +238 -0
- package/ui/src/feed/feed.tsx +344 -0
- package/ui/src/file-preview/file-preview-view.tsx +496 -0
- package/ui/src/file-preview/file-preview.tsx +28 -0
- package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
- package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
- package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
- package/ui/src/file-preview/word-file-preview.tsx +5 -0
- package/ui/src/flow-runs/flow-runs.tsx +238 -0
- package/ui/src/flows/flows.tsx +1646 -0
- package/ui/src/flows/node-icon/node-icon.ts +34 -0
- package/ui/src/flows/node-palette/node-palette.tsx +214 -0
- package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
- package/ui/src/folder-contents/folder-contents.tsx +321 -0
- package/ui/src/folder-index/folder-index.ts +55 -0
- package/ui/src/frontmatter/frontmatter.tsx +193 -0
- package/ui/src/graph-pane/graph-pane.tsx +53 -0
- package/ui/src/hooks/use-mobile.ts +19 -0
- package/ui/src/i18n/custom.json +4 -0
- package/ui/src/i18n/de.json +609 -0
- package/ui/src/i18n/en.json +609 -0
- package/ui/src/i18n/es.json +609 -0
- package/ui/src/i18n/i18n-context.tsx +65 -0
- package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
- package/ui/src/i18n/i18n.ts +32 -0
- package/ui/src/i18n/i18n.types.ts +49 -0
- package/ui/src/kind-icon.ts +97 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +68 -0
- package/ui/src/modal/modal.tsx +53 -0
- package/ui/src/node-details/node-details.tsx +545 -0
- package/ui/src/node-editor/node-editor.tsx +304 -0
- package/ui/src/node-graph/graph-notice.tsx +21 -0
- package/ui/src/node-graph/node-graph.ts +214 -0
- package/ui/src/node-graph/node-graph.tsx +295 -0
- package/ui/src/node-import/node-import.tsx +418 -0
- package/ui/src/node-save-error.ts +46 -0
- package/ui/src/node-source/node-source.tsx +128 -0
- package/ui/src/node-table/node-table.tsx +107 -0
- package/ui/src/nodes/nodes.tsx +240 -0
- package/ui/src/rename-resource/rename-resource.ts +80 -0
- package/ui/src/resolved-names/resolved-names.tsx +67 -0
- package/ui/src/resource-error.ts +33 -0
- package/ui/src/resource-menu/resource-menu.tsx +1156 -0
- package/ui/src/router/router-context.ts +6 -0
- package/ui/src/router/router.tsx +67 -0
- package/ui/src/router/router.types.ts +8 -0
- package/ui/src/router/selection-search.ts +88 -0
- package/ui/src/save-button/save-button.tsx +94 -0
- package/ui/src/section-hint/section-hint.tsx +40 -0
- package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
- package/ui/src/styles.css +189 -0
- package/ui/src/table-columns/table-columns.tsx +379 -0
- package/ui/src/theme/custom.css +1 -0
- package/ui/src/theme/theme-context.tsx +89 -0
- package/ui/src/theme/theme.ts +70 -0
- package/ui/src/time/relative-time.tsx +41 -0
- package/ui/src/time/time-context.tsx +78 -0
- package/ui/src/time/time.ts +165 -0
- package/ui/src/title-row/title-row.tsx +185 -0
- package/ui/src/tools/tools.tsx +546 -0
- package/ui/src/user-name/user-name.ts +61 -0
- package/ui/src/verbatim/verbatim.tsx +68 -0
- package/ui/tsconfig.json +17 -0
- package/ui/vite.config.ts +129 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BoardGetInput,
|
|
3
|
+
BoardTaskCreateInput,
|
|
4
|
+
BoardTaskUpdateInput,
|
|
5
|
+
BoardUpdateInput,
|
|
6
|
+
BoardView,
|
|
7
|
+
DirectoryPersonList,
|
|
8
|
+
} from "@contract/board.ts";
|
|
9
|
+
import type { BundleImportResult } from "@contract/bundle.ts";
|
|
10
|
+
import type { SessionUser } from "@contract/contract.ts";
|
|
11
|
+
import type { FeedListResponse } from "@contract/feed.ts";
|
|
12
|
+
import type {
|
|
13
|
+
ArchiveFlowInput,
|
|
14
|
+
CreateFlowInput,
|
|
15
|
+
Flow,
|
|
16
|
+
FlowDocument,
|
|
17
|
+
FlowList,
|
|
18
|
+
FlowPublishPreview,
|
|
19
|
+
FlowRequirements,
|
|
20
|
+
FlowValidation,
|
|
21
|
+
FlowVersionList,
|
|
22
|
+
ListFlowsInput,
|
|
23
|
+
PreviewFlowPublishInput,
|
|
24
|
+
PublishFlowInput,
|
|
25
|
+
PurgeFlowInput,
|
|
26
|
+
PurgeFlowResult,
|
|
27
|
+
RelationGraph,
|
|
28
|
+
RelationGraphScope,
|
|
29
|
+
SaveFlowVersionInput,
|
|
30
|
+
UpdateFlowInput,
|
|
31
|
+
} from "@contract/flow.ts";
|
|
32
|
+
import type {
|
|
33
|
+
CompleteFlowRunStepInput,
|
|
34
|
+
FlowRunHistory,
|
|
35
|
+
FlowRunList,
|
|
36
|
+
FlowRunStep,
|
|
37
|
+
ListFlowRunsInput,
|
|
38
|
+
StartFlowRunInput,
|
|
39
|
+
} from "@contract/flow-run.ts";
|
|
40
|
+
import type {
|
|
41
|
+
ArchiveNodeInput,
|
|
42
|
+
CreateNodeInput,
|
|
43
|
+
ListNodesInput,
|
|
44
|
+
Node,
|
|
45
|
+
NodeDocument,
|
|
46
|
+
NodeGraph,
|
|
47
|
+
NodeKind,
|
|
48
|
+
NodeLinkList,
|
|
49
|
+
NodeList,
|
|
50
|
+
NodeTable,
|
|
51
|
+
NodeVersionList,
|
|
52
|
+
PurgeNodeInput,
|
|
53
|
+
PurgeNodePreviewInput,
|
|
54
|
+
PurgeNodeResult,
|
|
55
|
+
ReindexResult,
|
|
56
|
+
ResolveNodeLinksInput,
|
|
57
|
+
ResolveNodeLinksResult,
|
|
58
|
+
SaveAttachmentInput,
|
|
59
|
+
SaveNodeVersionInput,
|
|
60
|
+
SearchInput,
|
|
61
|
+
SearchResult,
|
|
62
|
+
UpdateNodeInput,
|
|
63
|
+
} from "@contract/node.ts";
|
|
64
|
+
import type {
|
|
65
|
+
ResourceGrantList,
|
|
66
|
+
RevokeFlowGrantInput,
|
|
67
|
+
RevokeGrantInput,
|
|
68
|
+
RevokeGrantResult,
|
|
69
|
+
ShareFlowInput,
|
|
70
|
+
ShareInput,
|
|
71
|
+
ShareResult,
|
|
72
|
+
} from "@contract/share.ts";
|
|
73
|
+
import type {
|
|
74
|
+
AppendTableRowsInput,
|
|
75
|
+
AppendTableRowsResult,
|
|
76
|
+
DefineTableInput,
|
|
77
|
+
RedefineTableInput,
|
|
78
|
+
} from "@contract/table.ts";
|
|
79
|
+
import type { ToolCatalog, ToolServerCatalog } from "@contract/tool.ts";
|
|
80
|
+
|
|
81
|
+
// One row of the shared tree. Nodes and Flows share the folder, not their nature (ADR-0004), so
|
|
82
|
+
// this is a union that keeps each side's record whole — never a merged "node" that is a bit of both.
|
|
83
|
+
// ⚠️ `openable` means "has something to expand that THIS reader may see" (#59) — not "is of a kind
|
|
84
|
+
// that can be expanded". The difference is the whole point: an arrow on a row with nothing beneath
|
|
85
|
+
// it promises content that expanding does not deliver.
|
|
86
|
+
export type TreeEntry =
|
|
87
|
+
| {
|
|
88
|
+
type: "node";
|
|
89
|
+
id: string;
|
|
90
|
+
title: string;
|
|
91
|
+
kind: NodeKind;
|
|
92
|
+
openable: boolean;
|
|
93
|
+
node: Node;
|
|
94
|
+
}
|
|
95
|
+
| { type: "flow"; id: string; title: string; kind: "flow"; openable: boolean; flow: Flow };
|
|
96
|
+
|
|
97
|
+
export interface IntelDataProvider {
|
|
98
|
+
getSession(): Promise<SessionUser>;
|
|
99
|
+
/**
|
|
100
|
+
* Rebuild the whole search index (`intel/admin`).
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ Answers as soon as the work is QUEUED, not when it is done — `queued` counts the nodes that
|
|
103
|
+
* will be read again, and the index catches up behind it. A screen that said "finished" here
|
|
104
|
+
* would be reporting the wrong thing.
|
|
105
|
+
*/
|
|
106
|
+
reindexNodes(): Promise<ReindexResult>;
|
|
107
|
+
/**
|
|
108
|
+
* One board, in one answer: its columns and every card the caller may see, without the card
|
|
109
|
+
* bodies (#648).
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ **One call, not one per card.** A screen that fetched each card separately would look
|
|
112
|
+
* identical and is the reason this endpoint exists — the test that guards it counts requests
|
|
113
|
+
* rather than pixels.
|
|
114
|
+
*/
|
|
115
|
+
getBoard(input: BoardGetInput): Promise<BoardView>;
|
|
116
|
+
updateBoard(input: BoardUpdateInput): Promise<BoardView>;
|
|
117
|
+
createBoardTask(input: BoardTaskCreateInput): Promise<BoardView>;
|
|
118
|
+
// Every write answers with the WHOLE board rather than the row it changed: a drag moves one card
|
|
119
|
+
// and shifts nothing else, but a column's contents are what the screen draws, and re-deriving
|
|
120
|
+
// them from a single row is where a second, quietly different truth would start.
|
|
121
|
+
updateBoardTask(boardId: string, input: BoardTaskUpdateInput): Promise<BoardView>;
|
|
122
|
+
/**
|
|
123
|
+
* Who a card on THIS board may be given to (#700, D70).
|
|
124
|
+
*
|
|
125
|
+
* ⚠️ Board-scoped, and the same query against two boards can give two different answers. Only
|
|
126
|
+
* people who can actually open this one are offered.
|
|
127
|
+
*/
|
|
128
|
+
searchBoardAssignees(boardId: string, query: string): Promise<DirectoryPersonList>;
|
|
129
|
+
/**
|
|
130
|
+
* What the people already on cards are called (#258, #700).
|
|
131
|
+
*
|
|
132
|
+
* ⚠️ NOT board-scoped, unlike the search above: somebody whose access was withdrawn is still who
|
|
133
|
+
* the card belongs to, and a card that read as unassigned would be a worse answer than the truth.
|
|
134
|
+
* Ids that cannot be named are absent from the answer rather than reported.
|
|
135
|
+
*/
|
|
136
|
+
resolveNames(ids: string[]): Promise<DirectoryPersonList>;
|
|
137
|
+
listNodes(input?: Partial<ListNodesInput>): Promise<NodeList>;
|
|
138
|
+
// One level of the shared tree: the documents and the flows filed in the same folder, in one
|
|
139
|
+
// sorted list. Per level rather than recursive, so opening a folder is what costs a request.
|
|
140
|
+
listTreeChildren(parentId: string | null): Promise<TreeEntry[]>;
|
|
141
|
+
getNode(nodeId: string): Promise<NodeDocument>;
|
|
142
|
+
createNodes(input: CreateNodeInput): Promise<Node>;
|
|
143
|
+
getNodeGraph(limit?: number): Promise<NodeGraph>;
|
|
144
|
+
listNodeLinks(nodeId: string): Promise<NodeLinkList>;
|
|
145
|
+
// The titles of the documents a text links to, for the reader. A target they may not see, or one
|
|
146
|
+
// that is gone, is absent from the answer — the two look the same on purpose (#41).
|
|
147
|
+
resolveNodeLinks(input: ResolveNodeLinksInput): Promise<ResolveNodeLinksResult>;
|
|
148
|
+
saveNode(input: SaveNodeVersionInput): Promise<NodeDocument>;
|
|
149
|
+
saveNodeAttachment(input: SaveAttachmentInput): Promise<NodeDocument>;
|
|
150
|
+
/**
|
|
151
|
+
* A file uploaded as itself, not as base64 in a JSON body (#821).
|
|
152
|
+
*
|
|
153
|
+
* ⚠️ The `File` is handed over rather than read: the browser streams it from disk, so a 50 MB
|
|
154
|
+
* upload never stands in memory here. `saveNodeAttachment` beside it does the opposite by
|
|
155
|
+
* construction, which is why it keeps the smaller limit and why this is what the tree uses.
|
|
156
|
+
*/
|
|
157
|
+
saveNodeAttachmentBytes(input: {
|
|
158
|
+
nodeId: string;
|
|
159
|
+
baseVersionId: string | null;
|
|
160
|
+
file: File;
|
|
161
|
+
idempotencyKey: string;
|
|
162
|
+
}): Promise<NodeDocument>;
|
|
163
|
+
getNodeAttachment(nodeId: string): Promise<Blob>;
|
|
164
|
+
// One subtree — or one single node — as the zip bundle the server streams (#136). `null` is the
|
|
165
|
+
// root: the whole installation as this caller may read it.
|
|
166
|
+
exportNodeBundle(nodeId: string | null): Promise<Blob>;
|
|
167
|
+
// The same bundle shape for a single flow, through the flow's own export route.
|
|
168
|
+
exportFlowBundle(flowId: string): Promise<Blob>;
|
|
169
|
+
// A zip bundle — or a folder zipped on the client — as a new subtree under the target folder
|
|
170
|
+
// (#137). `nodeId` null files at the root. Always new nodes; the server is all-or-nothing.
|
|
171
|
+
importNodeBundle(input: {
|
|
172
|
+
nodeId: string | null;
|
|
173
|
+
zip: Blob;
|
|
174
|
+
idempotencyKey: string;
|
|
175
|
+
}): Promise<BundleImportResult>;
|
|
176
|
+
// A table as columns and rows. The CSV stays canonical — `getNode` still answers with it,
|
|
177
|
+
// which is what the download uses — and this is the same bytes read by the server's one reader.
|
|
178
|
+
getNodeTable(nodeId: string): Promise<NodeTable>;
|
|
179
|
+
defineTable(input: DefineTableInput): Promise<NodeTable>;
|
|
180
|
+
appendTableRows(input: AppendTableRowsInput): Promise<AppendTableRowsResult>;
|
|
181
|
+
// Changing the header of a table that already has one (#135). The mapping is explicit — every new
|
|
182
|
+
// column names the current one whose cells fill it, or `null` for an empty one — because a new
|
|
183
|
+
// header without it would reinterpret every stored row under names nobody matched to the old ones.
|
|
184
|
+
//
|
|
185
|
+
// ⚠️ `baseVersionId` is the `versionId` the mapping was written against, and the server refuses
|
|
186
|
+
// with `version_conflict` when the table has moved on. Not optional: a mapping onto a header
|
|
187
|
+
// somebody else has since changed would move the wrong cells, silently.
|
|
188
|
+
redefineTable(input: RedefineTableInput): Promise<NodeTable>;
|
|
189
|
+
|
|
190
|
+
// Walks the browser through the portal's OAuth flow without asking anybody anything: Gate is the
|
|
191
|
+
// identity provider Cloudflare Access consumes, so a signed-in person is already known there
|
|
192
|
+
// (#60). It navigates away — the caller renders no button for it and gets no answer back.
|
|
193
|
+
startPortalSignIn(returnTo?: string): void;
|
|
194
|
+
listNodeVersions(nodeId: string): Promise<NodeVersionList>;
|
|
195
|
+
updateNode(input: UpdateNodeInput): Promise<Node>;
|
|
196
|
+
archiveNode(input: ArchiveNodeInput): Promise<Node>;
|
|
197
|
+
// ⚠️ The one way across this seam after which nothing is really left (#457). The title comes back
|
|
198
|
+
// because nothing can look it up afterwards.
|
|
199
|
+
purgeNode(input: PurgeNodeInput): Promise<PurgeNodeResult>;
|
|
200
|
+
previewNodePurge(
|
|
201
|
+
input: PurgeNodePreviewInput,
|
|
202
|
+
): Promise<import("@contract/node.ts").PurgeNodePreview>;
|
|
203
|
+
purgeFlow(input: PurgeFlowInput): Promise<PurgeFlowResult>;
|
|
204
|
+
searchNodes(input: SearchInput): Promise<SearchResult>;
|
|
205
|
+
listGrants(resourceId: string): Promise<ResourceGrantList>;
|
|
206
|
+
listEffectiveAccess(resourceId: string): Promise<import("@contract/share.ts").ResourceAccessList>;
|
|
207
|
+
// The grant, and what the grant does not cover: the documents the flows in this folder read that
|
|
208
|
+
// the new principal still cannot. A warning, never a refusal (ADR-0004 §4).
|
|
209
|
+
shareNode(input: ShareInput): Promise<ShareResult>;
|
|
210
|
+
revokeGrant(input: RevokeGrantInput): Promise<RevokeGrantResult>;
|
|
211
|
+
// The same four for one flow (#530). Separate calls rather than a widened `resourceId`, because
|
|
212
|
+
// the id names a different table on the other side and the routes are separate there too.
|
|
213
|
+
listFlowGrants(flowId: string): Promise<ResourceGrantList>;
|
|
214
|
+
listEffectiveFlowAccess(flowId: string): Promise<import("@contract/share.ts").ResourceAccessList>;
|
|
215
|
+
// ⚠️ `ShareResult.unrunnable` is only ever filled in here: a grant on one flow does not reach the
|
|
216
|
+
// flows it calls, and this is where that gets said.
|
|
217
|
+
shareFlow(input: ShareFlowInput): Promise<ShareResult>;
|
|
218
|
+
revokeFlowGrant(input: RevokeFlowGrantInput): Promise<RevokeGrantResult>;
|
|
219
|
+
listFlows(input?: ListFlowsInput): Promise<FlowList>;
|
|
220
|
+
getFlow(flowId: string): Promise<FlowDocument>;
|
|
221
|
+
// What a flow calls, read out of its graph. It answers a different question from `listTreeChildren`
|
|
222
|
+
// and deliberately gives a different answer: a shared sub-flow is listed under every caller.
|
|
223
|
+
listFlowCalls(flowId: string): Promise<FlowList>;
|
|
224
|
+
// Would this flow start for me, right now. A question, not an action: nothing is created (#72).
|
|
225
|
+
validateFlow(flowId: string): Promise<FlowValidation>;
|
|
226
|
+
// What accesses what, for one level of the shared tree: a folder for its contents, a flow for
|
|
227
|
+
// itself. ⚠️ Only nodes the signed-in user may see come back, and one they may not is absent
|
|
228
|
+
// altogether — never a placeholder, because the edge into one would already say that it exists.
|
|
229
|
+
getRelationGraph(scope: RelationGraphScope, limit?: number): Promise<RelationGraph>;
|
|
230
|
+
// What a flow touches: the documents and tools its graph names. Documents the signed-in user may
|
|
231
|
+
// not see are counted rather than named, and nothing here claims anybody may reach them.
|
|
232
|
+
getFlowRequirements(flowId: string): Promise<FlowRequirements>;
|
|
233
|
+
createFlow(input: CreateFlowInput): Promise<Flow>;
|
|
234
|
+
updateFlow(input: UpdateFlowInput): Promise<Flow>;
|
|
235
|
+
// Archive a flow or take it back out. An archived flow leaves the tree, stops being startable and
|
|
236
|
+
// stops resolving as another flow's callee; its versions stay untouched.
|
|
237
|
+
archiveFlow(input: ArchiveFlowInput): Promise<Flow>;
|
|
238
|
+
saveFlow(input: SaveFlowVersionInput): Promise<FlowDocument>;
|
|
239
|
+
listFlowVersions(flowId: string): Promise<FlowVersionList>;
|
|
240
|
+
// Which version each sub-flow call will take once published, and which of them publishing
|
|
241
|
+
// freezes. Read before publishing, so the author agrees to the pins rather than discovering them.
|
|
242
|
+
previewFlowPublish(input: PreviewFlowPublishInput): Promise<FlowPublishPreview>;
|
|
243
|
+
publishFlow(input: PublishFlowInput): Promise<Flow>;
|
|
244
|
+
startFlow(input: StartFlowRunInput): Promise<FlowRunStep>;
|
|
245
|
+
// What this flow has done, newest first, one page at a time. ⚠️ It carries what a run did and
|
|
246
|
+
// never what it produced: a run reads nodes with the rights of whoever started it, so its
|
|
247
|
+
// result is not automatically readable for everyone who may read the flow.
|
|
248
|
+
// The activity feed, newest first (#742). `before` is the previous answer's `nextCursor`,
|
|
249
|
+
// handed back unchanged; `actor` narrows it to one person or one agent.
|
|
250
|
+
listFeed(input: {
|
|
251
|
+
limit: number;
|
|
252
|
+
before?: string | undefined;
|
|
253
|
+
actor?: string | undefined;
|
|
254
|
+
}): Promise<FeedListResponse>;
|
|
255
|
+
listFlowRuns(input: ListFlowRunsInput): Promise<FlowRunList>;
|
|
256
|
+
getFlowRun(runId: string): Promise<FlowRunStep>;
|
|
257
|
+
// The drill-down behind one run: every step it took, and the call chain it belongs to.
|
|
258
|
+
getFlowRunSteps(runId: string): Promise<FlowRunHistory>;
|
|
259
|
+
completeFlowStep(input: CompleteFlowRunStepInput): Promise<FlowRunStep>;
|
|
260
|
+
// Reading the catalog is the whole of the tool surface here: calling a tool belongs to a flow
|
|
261
|
+
// or to the Intel MCP surface, not to the screen that shows what the portal offers.
|
|
262
|
+
listTools(): Promise<ToolCatalog>;
|
|
263
|
+
listToolServers(): Promise<ToolServerCatalog>;
|
|
264
|
+
|
|
265
|
+
logout(): Promise<void>;
|
|
266
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { retryRequest } from "@/data/request-refusal/request-refusal.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The one query client the application runs on.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ It exists as a function rather than as the literal `new QueryClient()` that stood in `main.tsx`
|
|
8
|
+
* so the policy in it can be exercised by a test (#430). A default nothing renders under is a
|
|
9
|
+
* default nothing checks, and this one decides how long a screen stays on "Loading…".
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ Flat, and without a `.unit.ts` of its own on purpose: the only thing to assert here is that the
|
|
12
|
+
* returned client carries `retryRequest`, and what that predicate DOES is already covered next door
|
|
13
|
+
* in `request-refusal.unit.ts` — twice over, because two screen tests render against this very
|
|
14
|
+
* client. A test that restated the wiring would be the "pure wiring" the repository rules exclude.
|
|
15
|
+
*/
|
|
16
|
+
export function createIntelQueryClient(): QueryClient {
|
|
17
|
+
return new QueryClient({ defaultOptions: { queries: { retry: retryRequest } } });
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Refusal } from "@/data/request-refusal/request-refusal.ts";
|
|
2
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What a refusal is told with, on every surface that has one.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ A refusal is not a failure, and the difference is the whole of #430. Somebody whose share was
|
|
8
|
+
* revoked was left in front of "Loading…" — the worst answer of all, because it says the program is
|
|
9
|
+
* still working. They get a sentence and no retry button: the answer was final, and a button that
|
|
10
|
+
* changes nothing is an invitation to keep waiting.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ One sentence for `404`, whichever of its two reasons applies. Intel answers the same status
|
|
13
|
+
* for "no such node" and "not for you" on purpose — `requireVisible` in
|
|
14
|
+
* `packages/intel/src/nodes/nodes.ts` — and a screen that told them apart would undo that from the
|
|
15
|
+
* other side.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ One function rather than a block per branch, because two answers to one question on one
|
|
18
|
+
* surface IS #445. A screen's halves — the empty level, the selected item, the view beside it — are
|
|
19
|
+
* asked the same thing, and copies drift.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ **Why this became shared here and not one copy earlier.** `nodes.tsx` (#445) and `flows.tsx`
|
|
22
|
+
* (#557) each carried an identical version, and #557 wrote its reason on the second one: this
|
|
23
|
+
* repository builds an abstraction after the third time it hurts, not the second. #568 is the third
|
|
24
|
+
* time — `?view=graph` and `?view=runs` are the third and fourth surface asking for the same
|
|
25
|
+
* sentence — so what would have been a third copy is this file. The rule did not bend; it was met.
|
|
26
|
+
*/
|
|
27
|
+
export function RefusalNotice({ refusal }: { refusal: Refusal }) {
|
|
28
|
+
const i18n = useI18n();
|
|
29
|
+
return (
|
|
30
|
+
<div role="status" className="grid flex-1 place-items-center p-8 text-center text-sm">
|
|
31
|
+
<p className="max-w-sm text-muted-foreground">
|
|
32
|
+
{i18n.t(refusal === "no-permission" ? "common.noPermission" : "common.noAccess")}
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { IntelRequestError } from "@/data/intel-data-provider/intel-data-provider.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a failed request already settles, as opposed to what it might answer differently next time.
|
|
5
|
+
*
|
|
6
|
+
* - `not-available` is Intel's `404`. It is deliberately the same answer for "there is no such node"
|
|
7
|
+
* and "you may not reach this node" — `requireVisible` in `packages/intel/src/nodes/nodes.ts`, held
|
|
8
|
+
* in place by "answers 404 for a foreign version and for an unreadable node" in `nodes.int.ts`.
|
|
9
|
+
* A screen must therefore not guess between them either: one sentence covers both, and that is why
|
|
10
|
+
* `no-permission` is a separate value rather than prose that could be shown for a `404` by mistake.
|
|
11
|
+
* - `no-permission` is a `403`. The server said the word itself, so repeating it reveals nothing.
|
|
12
|
+
* - `null` is everything else: a timeout, a `502`, a network that came back. Those are worth trying
|
|
13
|
+
* again, and the screen keeps its retry button for them.
|
|
14
|
+
*/
|
|
15
|
+
export type Refusal = "not-available" | "no-permission";
|
|
16
|
+
|
|
17
|
+
/** The HTTP status the data provider recorded, or `null` where the failure carried none. */
|
|
18
|
+
export function statusOf(error: unknown): number | null {
|
|
19
|
+
return error instanceof IntelRequestError ? error.status : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function refusalOf(error: unknown): Refusal | null {
|
|
23
|
+
switch (statusOf(error)) {
|
|
24
|
+
case 403:
|
|
25
|
+
return "no-permission";
|
|
26
|
+
case 404:
|
|
27
|
+
return "not-available";
|
|
28
|
+
default:
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The retry policy every query in Intel runs under.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ A refusal is deterministic, and repeating it is what left the screen on "Loading…" after a
|
|
37
|
+
* share was revoked (#430). React Query's default is three retries with a growing delay, so a `404`
|
|
38
|
+
* kept the reader in front of a moving loading state for seconds — and a retry is **paused while
|
|
39
|
+
* the tab is unfocused** (`packages/intel/ui/CLAUDE.md`, #212), so "seconds" has no upper bound whenever
|
|
40
|
+
* the person looked somewhere else. The answer was there the whole time.
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ `408` and `429` are the two `4xx` that DO change on their own — a timeout and a rate limit both
|
|
43
|
+
* say "not now" rather than "not you" — so they keep the retries. Everything else in the range is
|
|
44
|
+
* about this request as it was made, and making it again cannot alter that.
|
|
45
|
+
*/
|
|
46
|
+
export function retryRequest(failureCount: number, error: unknown): boolean {
|
|
47
|
+
const status = statusOf(error);
|
|
48
|
+
const deterministic =
|
|
49
|
+
status !== null && status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
50
|
+
return !deterministic && failureCount < 3;
|
|
51
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Sending somebody to sign in is the right answer to a 401 — but only ONCE. If the surface still
|
|
2
|
+
// answers 401 after a successful sign-in, redirecting again produces a page that reloads forever:
|
|
3
|
+
// Gate signs the person in, the interface asks, the answer is 401, and round it goes. From the
|
|
4
|
+
// outside it looks like a broken deployment; from the inside every single step succeeded, which is
|
|
5
|
+
// what makes it expensive to find (#118).
|
|
6
|
+
//
|
|
7
|
+
// It happens whenever a token is valid to Gate but not accepted by `/api/v1` — a session that
|
|
8
|
+
// expires between the two calls, a bearer for a different audience, a service key that no longer
|
|
9
|
+
// holds. The same fix landed in anchrd/agents first; this is its counterpart here.
|
|
10
|
+
|
|
11
|
+
import type { SignIn, SignInDeps } from "./sign-in.types.ts";
|
|
12
|
+
|
|
13
|
+
const Marker = "intel:last-sign-in";
|
|
14
|
+
|
|
15
|
+
// Long enough to cover Gate's redirect chain, short enough that somebody who genuinely returns
|
|
16
|
+
// hours later gets a fresh attempt rather than an error page.
|
|
17
|
+
const LoopWindowMs = 30_000;
|
|
18
|
+
|
|
19
|
+
export function createSignIn(deps: SignInDeps): SignIn {
|
|
20
|
+
return {
|
|
21
|
+
attempt() {
|
|
22
|
+
// ⚠️ Every unusable marker counts as "no recent attempt", never as "loop" — missing, damaged,
|
|
23
|
+
// and dated in the future all mean the same thing here. The last one is not hypothetical: a
|
|
24
|
+
// clock that jumps backwards (time zone, NTP) leaves a marker ahead of `now`, and reading a
|
|
25
|
+
// negative age as "just tried" would lock somebody out over the wrong wall clock. Refusing
|
|
26
|
+
// costs an error page; allowing costs one extra reload, so the doubt goes to allowing.
|
|
27
|
+
const last = Number(deps.read(Marker) ?? 0);
|
|
28
|
+
const age = deps.now() - last;
|
|
29
|
+
if (Number.isFinite(last) && last > 0 && age >= 0 && age < LoopWindowMs) return false;
|
|
30
|
+
|
|
31
|
+
deps.write(Marker, String(deps.now()));
|
|
32
|
+
deps.go(deps.loginPath(deps.currentPath()));
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The same thing wired to the browser.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ `sessionStorage`, not `localStorage`: the window is about one sign-in journey, and a marker
|
|
42
|
+
* that outlives the tab would meet the next visit as if it were still mid-loop. Both throw rather
|
|
43
|
+
* than return null when storage is blocked, so both accesses are guarded — see `attempt`.
|
|
44
|
+
*/
|
|
45
|
+
export function createBrowserSignIn(loginPath: (returnTo?: string) => string): SignIn {
|
|
46
|
+
return createSignIn({
|
|
47
|
+
now: () => Date.now(),
|
|
48
|
+
read: (key) => {
|
|
49
|
+
try {
|
|
50
|
+
return window.sessionStorage.getItem(key);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
write: (key, value) => {
|
|
56
|
+
try {
|
|
57
|
+
window.sessionStorage.setItem(key, value);
|
|
58
|
+
} catch {
|
|
59
|
+
// Nothing to do: without a marker the next 401 redirects once more. One extra reload beats
|
|
60
|
+
// an error page for somebody who could have been let in.
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
go: (url) => window.location.assign(url),
|
|
64
|
+
currentPath: () => `${window.location.pathname}${window.location.search}`,
|
|
65
|
+
loginPath,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* What to do with a 401, across all of them at once (#130).
|
|
71
|
+
*
|
|
72
|
+
* ⚠️ The reason this is a guard and not a call to `attempt()` at each 401: a first load without a
|
|
73
|
+
* session fires several requests together — session, tree, flows — and every one of them answers
|
|
74
|
+
* 401. The first starts the redirect; the rest arrive while the browser is still on this page,
|
|
75
|
+
* find the marker the first one just wrote, and read it as "we already tried, this is a loop". It
|
|
76
|
+
* is not — it is this very sign-in, still in flight.
|
|
77
|
+
*
|
|
78
|
+
* Without `leaving`, the interface refuses itself before it can leave, and nobody reaches the
|
|
79
|
+
* login at all. That is worse than the loop this whole mechanism exists to prevent: a loop still
|
|
80
|
+
* eventually shows a login, a self-refusal never does.
|
|
81
|
+
*/
|
|
82
|
+
export function createSignInGuard(signIn: SignIn, onRefused: () => void): () => void {
|
|
83
|
+
let leaving = false;
|
|
84
|
+
// Latches for its own reason: once the surface has turned down a freshly issued credential,
|
|
85
|
+
// later 401s say nothing new, and repainting for each would flicker the screen once per request
|
|
86
|
+
// the page had in flight.
|
|
87
|
+
let refused = false;
|
|
88
|
+
return () => {
|
|
89
|
+
if (leaving || refused) return;
|
|
90
|
+
if (signIn.attempt()) {
|
|
91
|
+
leaving = true;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
refused = true;
|
|
95
|
+
onRefused();
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SignInDeps {
|
|
2
|
+
now(): number;
|
|
3
|
+
read(key: string): string | null;
|
|
4
|
+
write(key: string, value: string): void;
|
|
5
|
+
go(url: string): void;
|
|
6
|
+
currentPath(): string;
|
|
7
|
+
loginPath(returnTo?: string): string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SignIn {
|
|
11
|
+
// True when it sent the person away, false when it refused to because it just did. A caller that
|
|
12
|
+
// gets `false` must show something rather than wait — the redirect that would have explained
|
|
13
|
+
// itself is exactly what is not happening.
|
|
14
|
+
attempt(): boolean;
|
|
15
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createReactInlineContentSpec } from "@blocknote/react";
|
|
2
|
+
import { DocumentLinkInlineType } from "@contract/node.ts";
|
|
3
|
+
import { FileText, Link2Off } from "lucide-react";
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What a document link resolves to for the person reading it.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ `titles` holds only the documents this reader may see. A missing entry is the whole answer for
|
|
10
|
+
* both of the reasons an entry can be missing — the target is gone, or it is not theirs to reach —
|
|
11
|
+
* and nothing here may ever be given a way to tell those apart. `pending` exists so the editor can
|
|
12
|
+
* hold back the difference between "not resolved yet" and "does not resolve", which would otherwise
|
|
13
|
+
* flash a broken link at every reader for one render.
|
|
14
|
+
*/
|
|
15
|
+
export interface DocumentLinkResolution {
|
|
16
|
+
titles: ReadonlyMap<string, string>;
|
|
17
|
+
pending: boolean;
|
|
18
|
+
// The one word an unresolved link says. It travels in the context because a BlockNote inline
|
|
19
|
+
// element is rendered by the editor rather than by a view that could reach the i18n catalog.
|
|
20
|
+
unresolvedLabel: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DocumentLinkContext = createContext<DocumentLinkResolution>({
|
|
24
|
+
titles: new Map(),
|
|
25
|
+
pending: false,
|
|
26
|
+
unresolvedLabel: "Unavailable document",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const DocumentLinkProvider = DocumentLinkContext.Provider;
|
|
30
|
+
|
|
31
|
+
export function useDocumentLinkResolution(): DocumentLinkResolution {
|
|
32
|
+
return useContext(DocumentLinkContext);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* One document link inside the text (#41).
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ The element stores the target's ID and nothing else. A stored title would be wrong the moment
|
|
39
|
+
* the target is renamed and a stored path the moment it is moved — and both would put a name into a
|
|
40
|
+
* document that any reader of it could then read, whether or not they may reach the target.
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ An unresolved link says that it does not resolve and nothing more: no title, no path, no ID on
|
|
43
|
+
* screen, no hint of which of the two reasons applies. That is the whole of the leak this feature
|
|
44
|
+
* could have introduced, and the reason the broken form carries no data at all.
|
|
45
|
+
*/
|
|
46
|
+
export function DocumentLinkText({ nodeId }: { nodeId: string }) {
|
|
47
|
+
const { titles, pending, unresolvedLabel } = useDocumentLinkResolution();
|
|
48
|
+
const title = titles.get(nodeId);
|
|
49
|
+
if (title === undefined) {
|
|
50
|
+
return (
|
|
51
|
+
<span
|
|
52
|
+
data-document-link={pending ? "pending" : "unresolved"}
|
|
53
|
+
className={
|
|
54
|
+
pending
|
|
55
|
+
? "inline-flex items-center gap-1 rounded-sm px-1 text-muted-foreground"
|
|
56
|
+
: "inline-flex items-center gap-1 rounded-sm bg-destructive/10 px-1 text-destructive line-through"
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
{pending ? null : <Link2Off aria-hidden="true" className="size-3.5" />}
|
|
60
|
+
{pending ? "…" : unresolvedLabel}
|
|
61
|
+
</span>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
<span
|
|
66
|
+
data-document-link="resolved"
|
|
67
|
+
data-node-id={nodeId}
|
|
68
|
+
className="inline-flex items-center gap-1 rounded-sm bg-muted px-1 text-foreground"
|
|
69
|
+
>
|
|
70
|
+
<FileText aria-hidden="true" className="size-3.5" />
|
|
71
|
+
{title}
|
|
72
|
+
</span>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const documentLinkSpec = createReactInlineContentSpec(
|
|
77
|
+
{
|
|
78
|
+
type: DocumentLinkInlineType,
|
|
79
|
+
propSchema: { nodeId: { default: "" } },
|
|
80
|
+
// The link has no editable text of its own: what it reads is the target's title, and a title
|
|
81
|
+
// somebody could type over would stop following the target the first time they did (#41).
|
|
82
|
+
content: "none",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
render: (props) => <DocumentLinkText nodeId={String(props.inlineContent.props.nodeId)} />,
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The IDs a stored BlockNote document links to.
|
|
91
|
+
*
|
|
92
|
+
* The server reads the same thing out of the same content when the document is saved; this side
|
|
93
|
+
* needs it too, to ask which of them it may show a title for.
|
|
94
|
+
*/
|
|
95
|
+
export function documentLinkIds(blocks: unknown): string[] {
|
|
96
|
+
const found = new Set<string>();
|
|
97
|
+
const walk = (value: unknown): void => {
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
for (const entry of value) walk(entry);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (typeof value !== "object" || value === null) return;
|
|
103
|
+
const record = value as Record<string, unknown>;
|
|
104
|
+
if (record.type === DocumentLinkInlineType) {
|
|
105
|
+
const props = record.props as Record<string, unknown> | undefined;
|
|
106
|
+
const nodeId = props?.nodeId;
|
|
107
|
+
if (typeof nodeId === "string" && nodeId.length > 0) found.add(nodeId);
|
|
108
|
+
}
|
|
109
|
+
walk(record.content);
|
|
110
|
+
walk(record.children);
|
|
111
|
+
};
|
|
112
|
+
walk(blocks);
|
|
113
|
+
return [...found];
|
|
114
|
+
}
|