@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,71 @@
|
|
|
1
|
+
import type { NodeDocument } from "../contract/node.js";
|
|
2
|
+
import type { Actor } from "../nodes/nodes.types.js";
|
|
3
|
+
/**
|
|
4
|
+
* One entry of the slash-command catalogue (#775): a document offered as an instruction, or a
|
|
5
|
+
* published flow offered as a way to start one.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ `kind` is two values and not `NodeKind`. A flow is not a node, and the five node kinds that
|
|
8
|
+
* cannot be offered have no business appearing in this union — a widened type here would be a
|
|
9
|
+
* standing invitation to hand `prompts/list` a folder.
|
|
10
|
+
*/
|
|
11
|
+
export interface PromptEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
kind: "document" | "flow";
|
|
14
|
+
targetId: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Who holds a name, as much of it as the asking actor is entitled to learn.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ `title` is `null` when the holder exists but this actor may not see it, and that is not the
|
|
22
|
+
* same as "no holder". ADR-0004 §3 is explicit that a refusal must not become a way of reading the
|
|
23
|
+
* tree: naming the document that holds `payroll` would tell somebody without access that a document
|
|
24
|
+
* by that title exists. The caller is told the name is taken — which they must be, or they cannot
|
|
25
|
+
* proceed — and nothing else.
|
|
26
|
+
*/
|
|
27
|
+
export interface PromptNameHolder {
|
|
28
|
+
kind: "document" | "flow";
|
|
29
|
+
title: string | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Who is asking for the catalogue, and which halves of it they are entitled to at all.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ **The capability follows the KIND, exactly as it does for `audit_list`** (#774). A prompt
|
|
35
|
+
* entry says the same thing its resource says, so listing it asks what reading that resource asks:
|
|
36
|
+
* `nodes/read` for a document, `flows/read` for a flow. Somebody holding only one of the two gets
|
|
37
|
+
* the half they hold — not an empty list, and not the other half by accident.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ These are the GATE capabilities and they are the coarse half of the answer. The resource ACL
|
|
40
|
+
* still runs underneath, per row, in the query itself; a caller with `nodes/read` sees the offered
|
|
41
|
+
* documents THEY may read and no others.
|
|
42
|
+
*/
|
|
43
|
+
export interface PromptActor extends Actor {
|
|
44
|
+
canReadNodes: boolean;
|
|
45
|
+
canReadFlows: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface PromptRepository {
|
|
48
|
+
listOffered(actor: PromptActor): Promise<PromptEntry[]>;
|
|
49
|
+
findOffered(actor: PromptActor, name: string): Promise<PromptEntry | null>;
|
|
50
|
+
}
|
|
51
|
+
export interface PromptsDeps {
|
|
52
|
+
repository: PromptRepository;
|
|
53
|
+
/**
|
|
54
|
+
* One document, as the node service reads it: the same authorization walk, the same content
|
|
55
|
+
* store. `prompts/get` does not reach into R2 on its own — a second reader would be a second
|
|
56
|
+
* place for the ACL to be applied, and the one that gets it wrong is the one nobody looks at.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ **The whole document, not a string.** What is stored under a document node is the editor's
|
|
59
|
+
* payload, and turning that into readable text needs the `mediaType` beside the content. Handing
|
|
60
|
+
* a string in would put that decision in whatever wired this up — in the Cloudflare shell, which
|
|
61
|
+
* is the one place in this package that may not hold business behaviour.
|
|
62
|
+
*/
|
|
63
|
+
readDocument(actor: Actor, nodeId: string): Promise<NodeDocument>;
|
|
64
|
+
}
|
|
65
|
+
export interface Prompts {
|
|
66
|
+
list(actor: PromptActor): Promise<PromptEntry[]>;
|
|
67
|
+
get(actor: PromptActor, name: string): Promise<{
|
|
68
|
+
entry: PromptEntry;
|
|
69
|
+
text: string;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { NamedOrCounted } from "../../contract/contract.js";
|
|
2
|
+
type Callers = {
|
|
3
|
+
visible: string[];
|
|
4
|
+
hidden: number;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* ⚠️ Named where the reader may see it, counted where they may not (ADR-0004 §3). A refusal has to
|
|
8
|
+
* be actionable without becoming a way of reading the tree: whoever holds one grant on one folder
|
|
9
|
+
* must not learn the titles of flows they are refused directly.
|
|
10
|
+
*
|
|
11
|
+
* This is the middle of both refusals that ask the question — narrowing a folder (`nodes.ts`) and
|
|
12
|
+
* deleting a flow for good (`flows.ts`). ⚠️ **The two SENTENCES around it stay two**, because they
|
|
13
|
+
* say different things to different people: one is about a folder that is about to lose a grant,
|
|
14
|
+
* the other about a flow that is about to be gone. Folding them into one parameterised sentence
|
|
15
|
+
* would buy a shared string and cost the reason each one gives.
|
|
16
|
+
*/
|
|
17
|
+
export declare function callersPhrase(callers: Callers): string;
|
|
18
|
+
/**
|
|
19
|
+
* The same two facts the sentence is built from, in the shape a grant's own warning already uses
|
|
20
|
+
* (`UnreadableNodes`, #448). One question — what may you see, and of how much — deserves one answer
|
|
21
|
+
* shape on every surface, and a screen that has to write the sentence itself needs the facts and
|
|
22
|
+
* not the sentence.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ It reads the SAME argument the phrase above reads. A surface that recomputed the split would
|
|
25
|
+
* be deciding what it may not, and a second decision here would be the one that drifts.
|
|
26
|
+
*/
|
|
27
|
+
export declare function namedOrCounted(callers: Callers): NamedOrCounted;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⚠️ Named where the reader may see it, counted where they may not (ADR-0004 §3). A refusal has to
|
|
3
|
+
* be actionable without becoming a way of reading the tree: whoever holds one grant on one folder
|
|
4
|
+
* must not learn the titles of flows they are refused directly.
|
|
5
|
+
*
|
|
6
|
+
* This is the middle of both refusals that ask the question — narrowing a folder (`nodes.ts`) and
|
|
7
|
+
* deleting a flow for good (`flows.ts`). ⚠️ **The two SENTENCES around it stay two**, because they
|
|
8
|
+
* say different things to different people: one is about a folder that is about to lose a grant,
|
|
9
|
+
* the other about a flow that is about to be gone. Folding them into one parameterised sentence
|
|
10
|
+
* would buy a shared string and cost the reason each one gives.
|
|
11
|
+
*/
|
|
12
|
+
export function callersPhrase(callers) {
|
|
13
|
+
const named = callers.visible.map((title) => `“${title}”`).join(", ");
|
|
14
|
+
const rest = callers.hidden === 0
|
|
15
|
+
? ""
|
|
16
|
+
: `${named ? " and " : ""}${callers.hidden} more flow${callers.hidden === 1 ? "" : "s"} you cannot see`;
|
|
17
|
+
return `${named}${rest}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The same two facts the sentence is built from, in the shape a grant's own warning already uses
|
|
21
|
+
* (`UnreadableNodes`, #448). One question — what may you see, and of how much — deserves one answer
|
|
22
|
+
* shape on every surface, and a screen that has to write the sentence itself needs the facts and
|
|
23
|
+
* not the sentence.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ It reads the SAME argument the phrase above reads. A surface that recomputed the split would
|
|
26
|
+
* be deciding what it may not, and a second decision here would be the one that drifts.
|
|
27
|
+
*/
|
|
28
|
+
export function namedOrCounted(callers) {
|
|
29
|
+
return { titles: callers.visible, hidden: callers.hidden };
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function encodeCsvRow(cells: readonly string[]): string;
|
|
2
|
+
export declare function encodeCsv(rows: readonly (readonly string[])[]): string;
|
|
3
|
+
/**
|
|
4
|
+
* Every row of a CSV body, quoted fields included.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ A quoted field may contain a line break, so this cannot split on `\n` first. A row is closed
|
|
7
|
+
* by a line break that falls outside quotes and by nothing else — the reason a naive `split` reads
|
|
8
|
+
* a table with one multi-line cell as two broken rows.
|
|
9
|
+
*
|
|
10
|
+
* A trailing newline does not produce an empty final row; an empty line in the middle does produce
|
|
11
|
+
* a row with one empty cell, because that is what the file says.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseCsv(text: string): string[][];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The one CSV reader and writer in Intel. A table's canonical body is CSV (#40), and every surface
|
|
2
|
+
// that has to look inside one — the grid, the column check, the search index — goes through here so
|
|
3
|
+
// none of them can grow a private opinion about quoting.
|
|
4
|
+
//
|
|
5
|
+
// RFC 4180 with two deliberate narrowings: rows are separated by `\n` when written (a reader
|
|
6
|
+
// accepts `\r\n` too), and a field is quoted only when it has to be. Nothing else is escaped,
|
|
7
|
+
// because CSV has no escapes — a quote inside a quoted field is written twice.
|
|
8
|
+
function needsQuoting(cell) {
|
|
9
|
+
return (cell.includes(",") ||
|
|
10
|
+
cell.includes('"') ||
|
|
11
|
+
cell.includes("\n") ||
|
|
12
|
+
cell.includes("\r") ||
|
|
13
|
+
cell !== cell.trim());
|
|
14
|
+
}
|
|
15
|
+
export function encodeCsvRow(cells) {
|
|
16
|
+
return cells
|
|
17
|
+
.map((cell) => (needsQuoting(cell) ? `"${cell.replaceAll('"', '""')}"` : cell))
|
|
18
|
+
.join(",");
|
|
19
|
+
}
|
|
20
|
+
export function encodeCsv(rows) {
|
|
21
|
+
return rows.length === 0 ? "" : `${rows.map(encodeCsvRow).join("\n")}\n`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Every row of a CSV body, quoted fields included.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ A quoted field may contain a line break, so this cannot split on `\n` first. A row is closed
|
|
27
|
+
* by a line break that falls outside quotes and by nothing else — the reason a naive `split` reads
|
|
28
|
+
* a table with one multi-line cell as two broken rows.
|
|
29
|
+
*
|
|
30
|
+
* A trailing newline does not produce an empty final row; an empty line in the middle does produce
|
|
31
|
+
* a row with one empty cell, because that is what the file says.
|
|
32
|
+
*/
|
|
33
|
+
export function parseCsv(text) {
|
|
34
|
+
const rows = [];
|
|
35
|
+
let row = [];
|
|
36
|
+
let cell = "";
|
|
37
|
+
let quoted = false;
|
|
38
|
+
let started = false;
|
|
39
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
40
|
+
const character = text[index];
|
|
41
|
+
if (quoted) {
|
|
42
|
+
if (character === '"') {
|
|
43
|
+
if (text[index + 1] === '"') {
|
|
44
|
+
cell += '"';
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
quoted = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
cell += character;
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (character === '"' && cell === "") {
|
|
57
|
+
quoted = true;
|
|
58
|
+
started = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (character === ",") {
|
|
62
|
+
row.push(cell);
|
|
63
|
+
cell = "";
|
|
64
|
+
started = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (character === "\n" || character === "\r") {
|
|
68
|
+
if (character === "\r" && text[index + 1] === "\n")
|
|
69
|
+
index += 1;
|
|
70
|
+
row.push(cell);
|
|
71
|
+
rows.push(row);
|
|
72
|
+
row = [];
|
|
73
|
+
cell = "";
|
|
74
|
+
started = false;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
cell += character;
|
|
78
|
+
started = true;
|
|
79
|
+
}
|
|
80
|
+
if (started || cell !== "" || row.length > 0) {
|
|
81
|
+
row.push(cell);
|
|
82
|
+
rows.push(row);
|
|
83
|
+
}
|
|
84
|
+
return rows;
|
|
85
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The READABLE text of one stored version, for the two places that need a document as prose rather
|
|
3
|
+
* than as the editor's own payload.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ **What a document node stores is NOT its text.** The editor writes a BlockNote payload —
|
|
6
|
+
* `{ blocks, markdown, … }` — and `markdown` beside the blocks is the whole of what anybody outside
|
|
7
|
+
* the editor can read. `packages/intel/ui/CLAUDE.md` states it for the search: `indexing.ts` pulls that
|
|
8
|
+
* field and nothing else into full text and vectors, so it is what anyone searching Intel matches
|
|
9
|
+
* against. A caller who hands the raw content on instead is handing on a JSON blob.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ **Every other media type travels unchanged**, and that is not an oversight: a `text/markdown`
|
|
12
|
+
* version IS its text, and a table renders its own. Only the editor's payload has a wrapper around
|
|
13
|
+
* the words.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ **`null` means "this content is not readable as text", not "empty".** Immutable content never
|
|
16
|
+
* becomes parsable on a retry, so the two callers answer it differently and each is right for its
|
|
17
|
+
* own surface: the indexer refuses the version permanently rather than requeueing it forever, and
|
|
18
|
+
* `prompts/get` refuses the call rather than handing a model half a JSON document. Returning `""`
|
|
19
|
+
* here would collapse both into "there is nothing to say".
|
|
20
|
+
*/
|
|
21
|
+
export declare function documentText(mediaType: string, content: string): string | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { BlockNoteDocument, BlockNoteMediaType } from "../../contract/node.js";
|
|
2
|
+
/**
|
|
3
|
+
* The READABLE text of one stored version, for the two places that need a document as prose rather
|
|
4
|
+
* than as the editor's own payload.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ **What a document node stores is NOT its text.** The editor writes a BlockNote payload —
|
|
7
|
+
* `{ blocks, markdown, … }` — and `markdown` beside the blocks is the whole of what anybody outside
|
|
8
|
+
* the editor can read. `packages/intel/ui/CLAUDE.md` states it for the search: `indexing.ts` pulls that
|
|
9
|
+
* field and nothing else into full text and vectors, so it is what anyone searching Intel matches
|
|
10
|
+
* against. A caller who hands the raw content on instead is handing on a JSON blob.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ **Every other media type travels unchanged**, and that is not an oversight: a `text/markdown`
|
|
13
|
+
* version IS its text, and a table renders its own. Only the editor's payload has a wrapper around
|
|
14
|
+
* the words.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ **`null` means "this content is not readable as text", not "empty".** Immutable content never
|
|
17
|
+
* becomes parsable on a retry, so the two callers answer it differently and each is right for its
|
|
18
|
+
* own surface: the indexer refuses the version permanently rather than requeueing it forever, and
|
|
19
|
+
* `prompts/get` refuses the call rather than handing a model half a JSON document. Returning `""`
|
|
20
|
+
* here would collapse both into "there is nothing to say".
|
|
21
|
+
*/
|
|
22
|
+
export function documentText(mediaType, content) {
|
|
23
|
+
if (mediaType !== BlockNoteMediaType)
|
|
24
|
+
return content;
|
|
25
|
+
try {
|
|
26
|
+
return BlockNoteDocument.parse(JSON.parse(content)).markdown;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Authorized, GateClient } from "@anchrd/gate-sdk";
|
|
2
|
+
export declare function bearer(headers: Headers): string | null;
|
|
3
|
+
export declare function authorize(gate: Pick<GateClient, "authorize">, headers: Headers, resource: string): Promise<Authorized | null>;
|
|
4
|
+
export declare function permits(authorization: Pick<Authorized, "can">, handle: string, fn: string): boolean;
|
|
5
|
+
export declare function authorizeBearer(gate: Pick<GateClient, "authorize">, token: string, resource: string): Promise<Authorized | null>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function bearer(headers) {
|
|
2
|
+
const value = headers.get("authorization");
|
|
3
|
+
// RFC 9110 defines the auth scheme as case-insensitive, so a client sending "bearer" must not be
|
|
4
|
+
// turned away as unauthenticated.
|
|
5
|
+
if (!value || !/^bearer /i.test(value))
|
|
6
|
+
return null;
|
|
7
|
+
const token = value.slice(7);
|
|
8
|
+
return token.length > 0 ? token : null;
|
|
9
|
+
}
|
|
10
|
+
export async function authorize(gate, headers, resource) {
|
|
11
|
+
const token = bearer(headers);
|
|
12
|
+
if (!token)
|
|
13
|
+
return null;
|
|
14
|
+
return await authorizeBearer(gate, token, resource);
|
|
15
|
+
}
|
|
16
|
+
// `intel/admin` opens every door, and it has to: `share` inherits downwards only, so without a way
|
|
17
|
+
// in that needs no grant, a fresh instance has nobody who can set the first one (ADR-0004 §6). Gate
|
|
18
|
+
// learned the same lesson at three separate doors — a guard written per route is a list that goes
|
|
19
|
+
// quietly incomplete at the next one, so both surfaces ask this one function instead.
|
|
20
|
+
export function permits(authorization, handle, fn) {
|
|
21
|
+
return authorization.can(handle, fn) || authorization.can("intel", "admin");
|
|
22
|
+
}
|
|
23
|
+
export async function authorizeBearer(gate, token, resource) {
|
|
24
|
+
const result = await gate.authorize(token);
|
|
25
|
+
return result.ok && result.resource === resource ? result : null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⚠️ A grant whose expiry has already passed is refused, never written (#442).
|
|
3
|
+
*
|
|
4
|
+
* It would otherwise be created, answered with a `201`, and listed — while reaching nobody. The
|
|
5
|
+
* sharer reads the row as done; the grantee gets no notification and finds out days later, if at
|
|
6
|
+
* all. That is the failure mode this repository keeps meeting: something that looks like success,
|
|
7
|
+
* and the only feedback is the wrong one.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ The check cannot live in the Zod schema. That boundary knows nothing about `deps.now()`, and
|
|
10
|
+
* the rule is decided by the very clock that evaluates the grant afterwards — so it belongs in the
|
|
11
|
+
* application layer, where every surface passes through it. The `.describe()` on `expiresAt`
|
|
12
|
+
* documents the rule; this refuses it.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ Equal to `now` is refused too: a grant that expires this instant is spent before the answer
|
|
15
|
+
* reaches the caller.
|
|
16
|
+
*
|
|
17
|
+
* `null` is untouched and stays what it always meant — a grant that does not expire on its own.
|
|
18
|
+
*/
|
|
19
|
+
export declare function requireFutureExpiry(expiresAt: string | null, now: Date): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IntelError } from "../intel-error/intel-error.js";
|
|
2
|
+
/**
|
|
3
|
+
* ⚠️ A grant whose expiry has already passed is refused, never written (#442).
|
|
4
|
+
*
|
|
5
|
+
* It would otherwise be created, answered with a `201`, and listed — while reaching nobody. The
|
|
6
|
+
* sharer reads the row as done; the grantee gets no notification and finds out days later, if at
|
|
7
|
+
* all. That is the failure mode this repository keeps meeting: something that looks like success,
|
|
8
|
+
* and the only feedback is the wrong one.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ The check cannot live in the Zod schema. That boundary knows nothing about `deps.now()`, and
|
|
11
|
+
* the rule is decided by the very clock that evaluates the grant afterwards — so it belongs in the
|
|
12
|
+
* application layer, where every surface passes through it. The `.describe()` on `expiresAt`
|
|
13
|
+
* documents the rule; this refuses it.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Equal to `now` is refused too: a grant that expires this instant is spent before the answer
|
|
16
|
+
* reaches the caller.
|
|
17
|
+
*
|
|
18
|
+
* `null` is untouched and stays what it always meant — a grant that does not expire on its own.
|
|
19
|
+
*/
|
|
20
|
+
export function requireFutureExpiry(expiresAt, now) {
|
|
21
|
+
if (expiresAt === null)
|
|
22
|
+
return;
|
|
23
|
+
if (Date.parse(expiresAt) > now.getTime())
|
|
24
|
+
return;
|
|
25
|
+
throw new IntelError(400, "grant_already_expired", "A grant's expiry has to lie in the future; pass null for one that does not expire on its own");
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NamedOrCounted } from "../../contract/contract.js";
|
|
2
|
+
export declare class IntelError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly callers?: NamedOrCounted | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* ⚠️ `callers` is what a refusal that NAMES flows carries beside its sentence — today
|
|
8
|
+
* `folder_execute_in_use` (#448) and `flow_in_use_by_flow` (#593). It rides on the error rather
|
|
9
|
+
* than on the route because the split it belongs to is an application-layer one: who may be named
|
|
10
|
+
* and who may only be counted is an authorization answer, and it must be the same over HTTP, over
|
|
11
|
+
* MCP and in the browser. A route that assembled it would be a second place for that rule to live.
|
|
12
|
+
*
|
|
13
|
+
* `message` still carries the finished English sentence — a reader that only takes text must lose
|
|
14
|
+
* nothing — and the field is what lets a screen say it in its own language instead.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ **Setting this field is only half of reaching MCP.** The SDK formats a thrown error through
|
|
17
|
+
* `createToolError`, which keeps `message` and drops everything else, so a tool whose handler is
|
|
18
|
+
* not wrapped in `refusing()` (`mcp/mcp.ts`) answers prose and nothing more (#597). Whoever
|
|
19
|
+
* attaches `callers` to a new refusal wraps that tool's handler in the same change.
|
|
20
|
+
*/
|
|
21
|
+
constructor(status: number, code: string, message: string, callers?: NamedOrCounted | undefined);
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class IntelError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
code;
|
|
4
|
+
callers;
|
|
5
|
+
/**
|
|
6
|
+
* ⚠️ `callers` is what a refusal that NAMES flows carries beside its sentence — today
|
|
7
|
+
* `folder_execute_in_use` (#448) and `flow_in_use_by_flow` (#593). It rides on the error rather
|
|
8
|
+
* than on the route because the split it belongs to is an application-layer one: who may be named
|
|
9
|
+
* and who may only be counted is an authorization answer, and it must be the same over HTTP, over
|
|
10
|
+
* MCP and in the browser. A route that assembled it would be a second place for that rule to live.
|
|
11
|
+
*
|
|
12
|
+
* `message` still carries the finished English sentence — a reader that only takes text must lose
|
|
13
|
+
* nothing — and the field is what lets a screen say it in its own language instead.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ **Setting this field is only half of reaching MCP.** The SDK formats a thrown error through
|
|
16
|
+
* `createToolError`, which keeps `message` and drops everything else, so a tool whose handler is
|
|
17
|
+
* not wrapped in `refusing()` (`mcp/mcp.ts`) answers prose and nothing more (#597). Whoever
|
|
18
|
+
* attaches `callers` to a new refusal wraps that tool's handler in the same change.
|
|
19
|
+
*/
|
|
20
|
+
constructor(status, code, message, callers) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.callers = callers;
|
|
25
|
+
this.name = "IntelError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⚠️ A title is plain text, and an HTML entity in one is never what somebody meant.
|
|
3
|
+
*
|
|
4
|
+
* Intel escapes nothing, anywhere — the entity arrives already in the value (#202). The chain that
|
|
5
|
+
* carries a title to a write can HTML-escape what it hands to a model: the GitHub MCP server, for
|
|
6
|
+
* one, returns every string with `&` as `&` and `"` as `"`, so a model that copies a name
|
|
7
|
+
* out of one tool result into the next call writes the entity, not the character. Refusing the
|
|
8
|
+
* write would be the louder answer and the wrong one — the model reads the same escaped value
|
|
9
|
+
* again on its retry and cannot get past it. So the write boundary accepts it and stores what was
|
|
10
|
+
* meant.
|
|
11
|
+
*
|
|
12
|
+
* Repeated until it stops changing rather than once, because that is the failure the ticket names:
|
|
13
|
+
* a title read back escaped and written again is escaped twice, and a single round would leave
|
|
14
|
+
* `&amp;` as `&` — still an entity, still spreading.
|
|
15
|
+
*/
|
|
16
|
+
export declare function plainTitle(title: string): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Exactly what an HTML escaper emits, and nothing else. `"` and `'` are Go's
|
|
2
|
+
// `html.EscapeString` spelling of the quotes — the fingerprint of the tool that produced the
|
|
3
|
+
// titles this exists for. A general entity table is deliberately not here: ` ` or `ä` in
|
|
4
|
+
// a title is a person's choice, an escaped `&` never is.
|
|
5
|
+
const Escapes = [
|
|
6
|
+
[/</g, "<"],
|
|
7
|
+
[/>/g, ">"],
|
|
8
|
+
[/"/g, '"'],
|
|
9
|
+
[/�*34;/g, '"'],
|
|
10
|
+
[/'/g, "'"],
|
|
11
|
+
[/�*39;/g, "'"],
|
|
12
|
+
// Last in the pass, so one round of unescaping is the exact inverse of one round of escaping:
|
|
13
|
+
// `&lt;` becomes `<` here and only the NEXT round turns it into `<`.
|
|
14
|
+
[/&/g, "&"],
|
|
15
|
+
];
|
|
16
|
+
// A title that survived one more escaping than any writer intended. Eight is far past anything a
|
|
17
|
+
// real chain produces and stops a crafted title from spinning here.
|
|
18
|
+
const MaxRounds = 8;
|
|
19
|
+
/**
|
|
20
|
+
* ⚠️ A title is plain text, and an HTML entity in one is never what somebody meant.
|
|
21
|
+
*
|
|
22
|
+
* Intel escapes nothing, anywhere — the entity arrives already in the value (#202). The chain that
|
|
23
|
+
* carries a title to a write can HTML-escape what it hands to a model: the GitHub MCP server, for
|
|
24
|
+
* one, returns every string with `&` as `&` and `"` as `"`, so a model that copies a name
|
|
25
|
+
* out of one tool result into the next call writes the entity, not the character. Refusing the
|
|
26
|
+
* write would be the louder answer and the wrong one — the model reads the same escaped value
|
|
27
|
+
* again on its retry and cannot get past it. So the write boundary accepts it and stores what was
|
|
28
|
+
* meant.
|
|
29
|
+
*
|
|
30
|
+
* Repeated until it stops changing rather than once, because that is the failure the ticket names:
|
|
31
|
+
* a title read back escaped and written again is escaped twice, and a single round would leave
|
|
32
|
+
* `&amp;` as `&` — still an entity, still spreading.
|
|
33
|
+
*/
|
|
34
|
+
export function plainTitle(title) {
|
|
35
|
+
let current = title;
|
|
36
|
+
for (let round = 0; round < MaxRounds; round += 1) {
|
|
37
|
+
let next = current;
|
|
38
|
+
for (const [pattern, character] of Escapes)
|
|
39
|
+
next = next.replace(pattern, character);
|
|
40
|
+
if (next === current)
|
|
41
|
+
break;
|
|
42
|
+
current = next;
|
|
43
|
+
}
|
|
44
|
+
return current;
|
|
45
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IntelError } from "../intel-error/intel-error.js";
|
|
2
|
+
export declare function problemDetails(status: number, code: string, title: string, detail?: string): {
|
|
3
|
+
type: string;
|
|
4
|
+
status: number;
|
|
5
|
+
title: string;
|
|
6
|
+
detail?: string | undefined;
|
|
7
|
+
instance?: string | undefined;
|
|
8
|
+
code?: string | undefined;
|
|
9
|
+
callers?: {
|
|
10
|
+
titles: string[];
|
|
11
|
+
hidden: number;
|
|
12
|
+
} | undefined;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* One refusal, one document — written here rather than at each `onError`, because two apps turn an
|
|
16
|
+
* `IntelError` into a body and an extension member added at one of them would silently be missing
|
|
17
|
+
* from the other (#448). Whatever the application layer decided to say travels with it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function intelProblem(error: IntelError): {
|
|
20
|
+
type: string;
|
|
21
|
+
status: number;
|
|
22
|
+
title: string;
|
|
23
|
+
detail?: string | undefined;
|
|
24
|
+
instance?: string | undefined;
|
|
25
|
+
code?: string | undefined;
|
|
26
|
+
callers?: {
|
|
27
|
+
titles: string[];
|
|
28
|
+
hidden: number;
|
|
29
|
+
} | undefined;
|
|
30
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ProblemDetails } from "../../contract/contract.js";
|
|
2
|
+
export function problemDetails(status, code, title, detail) {
|
|
3
|
+
return ProblemDetails.parse({ type: "about:blank", status, code, title, detail });
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* One refusal, one document — written here rather than at each `onError`, because two apps turn an
|
|
7
|
+
* `IntelError` into a body and an extension member added at one of them would silently be missing
|
|
8
|
+
* from the other (#448). Whatever the application layer decided to say travels with it.
|
|
9
|
+
*/
|
|
10
|
+
export function intelProblem(error) {
|
|
11
|
+
return ProblemDetails.parse({
|
|
12
|
+
type: "about:blank",
|
|
13
|
+
status: error.status,
|
|
14
|
+
code: error.code,
|
|
15
|
+
title: error.message,
|
|
16
|
+
callers: error.callers,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function reportUnexpectedError(error: unknown): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// A library error can quote whatever the peer sent — an echoed Authorization header, a token in a
|
|
2
|
+
// URL, a response body. The log must show the failure and never the credential, so everything
|
|
3
|
+
// shaped like one is cut before the line is written. The patterns match shapes, not sources: a JWT
|
|
4
|
+
// is recognizable on its own, and other tokens only ever appear behind their label.
|
|
5
|
+
function redact(text) {
|
|
6
|
+
// ⚠️ The order is load-bearing: "Authorization: Bearer x" must hit the bearer rule first — a
|
|
7
|
+
// combined alternation would let the label rule win the leftmost match, swallow the word
|
|
8
|
+
// "Bearer" as the value, and leave the token itself standing.
|
|
9
|
+
return text
|
|
10
|
+
.replace(/\beyJ[\w-]{4,}\.[\w-]+\.[\w-]*/g, "[redacted]")
|
|
11
|
+
.replace(/\b(bearer\s+)[\w.~+/-]+=*/gi, "$1[redacted]")
|
|
12
|
+
.replace(/\b((?:access_token|refresh_token|id_token|client_secret|api_key|authorization)"?\s*[:=]\s*"?)[\w.~+/-]+=*/gi, "$1[redacted]");
|
|
13
|
+
}
|
|
14
|
+
function describe(error) {
|
|
15
|
+
if (error instanceof Error)
|
|
16
|
+
return error.stack ?? `${error.name}: ${error.message}`;
|
|
17
|
+
if (typeof error === "string")
|
|
18
|
+
return error;
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(error);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return String(error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// openid-client wraps the informative part — the HTTP response, the OAuth error body — into
|
|
27
|
+
// `cause`, so a log line without the cause chain would name the wrapper and hide the reason (#93).
|
|
28
|
+
const MaxCauseDepth = 5;
|
|
29
|
+
export function reportUnexpectedError(error) {
|
|
30
|
+
const parts = [];
|
|
31
|
+
let current = error;
|
|
32
|
+
for (let depth = 0; depth < MaxCauseDepth && current !== undefined; depth += 1) {
|
|
33
|
+
parts.push(describe(current));
|
|
34
|
+
current = current instanceof Error ? current.cause : undefined;
|
|
35
|
+
}
|
|
36
|
+
console.error(redact(parts.join("\ncaused by: ")));
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const SafeReturnPath = z
|
|
3
|
+
.string()
|
|
4
|
+
.startsWith("/")
|
|
5
|
+
.refine((value) => !value.startsWith("//") &&
|
|
6
|
+
!value.startsWith("/\\") &&
|
|
7
|
+
!value.includes("\\") &&
|
|
8
|
+
![...value].some((character) => {
|
|
9
|
+
const code = character.charCodeAt(0);
|
|
10
|
+
return code < 32 || code === 127;
|
|
11
|
+
}), "Return path must be a local absolute path");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The one place a digest becomes text. Both doors for an attachment write a `contentHash`: the
|
|
2
|
+
// base64 one hashes a buffer it already holds, the streaming one takes its digest as the bytes pass
|
|
3
|
+
// (#821). Two spellings of the same digest would make the two versions of one file look different.
|
|
4
|
+
export function hexDigest(digest) {
|
|
5
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
6
|
+
}
|
|
7
|
+
export async function sha256Hex(crypto, content) {
|
|
8
|
+
const source = typeof content === "string"
|
|
9
|
+
? new TextEncoder().encode(content).buffer
|
|
10
|
+
: content instanceof Uint8Array
|
|
11
|
+
? Uint8Array.from(content).buffer
|
|
12
|
+
: content;
|
|
13
|
+
return hexDigest(await crypto.subtle.digest("SHA-256", source));
|
|
14
|
+
}
|