@agent-native/core 0.12.33 → 0.12.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/composer/PromptComposer.js +1 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +2 -2
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/scripts/db/check-scoping.d.ts +3 -2
- package/dist/scripts/db/check-scoping.d.ts.map +1 -1
- package/dist/scripts/db/check-scoping.js +7 -5
- package/dist/scripts/db/check-scoping.js.map +1 -1
- package/dist/scripts/db/exec.d.ts.map +1 -1
- package/dist/scripts/db/exec.js +7 -2
- package/dist/scripts/db/exec.js.map +1 -1
- package/dist/scripts/db/scoping.d.ts +2 -0
- package/dist/scripts/db/scoping.d.ts.map +1 -1
- package/dist/scripts/db/scoping.js +27 -23
- package/dist/scripts/db/scoping.js.map +1 -1
- package/dist/server/agent-chat-plugin.js +2 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +53 -0
- package/dist/server/google-oauth.js.map +1 -1
- package/package.json +1 -1
|
@@ -166,7 +166,7 @@ function PromptComposerInner({ onSubmit, placeholder, disabled, autoFocus, class
|
|
|
166
166
|
onSubmit,
|
|
167
167
|
showModelSelector,
|
|
168
168
|
]);
|
|
169
|
-
return (_jsx("div", { className: cn("agent-composer-area flex flex-col rounded-lg border border-input bg-background focus-within:ring-1 focus-within:ring-ring", className), children: _jsxs(ComposerPrimitive.Root, { className: "flex flex-col", children: [_jsx(PromptAttachmentStrip, {}), _jsx(TiptapComposer, { focusRef: handleRef, disabled: disabled, placeholder: placeholder, onSubmit: handleSubmit, clearOnSubmit: !preserveDraftOnSubmit, plusMenuMode: attachmentsEnabled ? "upload-only" : "hidden", voiceEnabled: voiceEnabled, onTextChange: onTextChange, draftScope: draftScope, selectedModel: showModelSelector ? models.selectedModel : undefined, selectedEffort: showModelSelector ? models.selectedEffort : undefined, availableModels: showModelSelector ? models.availableModels : undefined, onModelChange: showModelSelector ? models.onModelChange : undefined, onEffortChange: showModelSelector ? models.onEffortChange : undefined })] }) }));
|
|
169
|
+
return (_jsx("div", { className: cn("agent-composer-area flex flex-col rounded-lg border border-input bg-background text-left focus-within:ring-1 focus-within:ring-ring", className), children: _jsxs(ComposerPrimitive.Root, { className: "flex flex-col", children: [_jsx(PromptAttachmentStrip, {}), _jsx(TiptapComposer, { focusRef: handleRef, disabled: disabled, placeholder: placeholder, onSubmit: handleSubmit, clearOnSubmit: !preserveDraftOnSubmit, plusMenuMode: attachmentsEnabled ? "upload-only" : "hidden", voiceEnabled: voiceEnabled, onTextChange: onTextChange, draftScope: draftScope, selectedModel: showModelSelector ? models.selectedModel : undefined, selectedEffort: showModelSelector ? models.selectedEffort : undefined, availableModels: showModelSelector ? models.availableModels : undefined, onModelChange: showModelSelector ? models.onModelChange : undefined, onEffortChange: showModelSelector ? models.onEffortChange : undefined })] }) }));
|
|
170
170
|
}
|
|
171
171
|
/**
|
|
172
172
|
* Standalone composer that mirrors the agent sidebar's input experience —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptComposer.js","sourceRoot":"","sources":["../../../src/client/composer/PromptComposer.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, type Ref } from \"react\";\nimport {\n AssistantRuntimeProvider,\n ComposerPrimitive,\n ThreadPrimitive,\n useAui,\n useComposer,\n useLocalRuntime,\n} from \"@assistant-ui/react\";\nimport type {\n Attachment,\n AttachmentAdapter,\n ChatModelAdapter,\n CompleteAttachment,\n PendingAttachment,\n} from \"@assistant-ui/react\";\nimport {\n CompositeAttachmentAdapter,\n SimpleImageAttachmentAdapter,\n} from \"@assistant-ui/react\";\nimport { IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport { TiptapComposer, type TiptapComposerHandle } from \"./TiptapComposer.js\";\nimport type { Reference } from \"./types.js\";\nimport { useChatModels } from \"../use-chat-models.js\";\nimport type { ReasoningEffort } from \"../../shared/reasoning-effort.js\";\nimport { isPastedTextAttachmentName } from \"./pasted-text.js\";\nimport { PastedTextChip } from \"./PastedTextChip.js\";\nimport {\n PROMPT_DOCUMENT_ATTACHMENT_ACCEPT,\n TextAttachmentAdapter,\n} from \"./attachment-accept.js\";\n\nconst MAX_INLINE_TEXT_FILE_CHARS = 60_000;\n\n/**\n * Files the user attached via the \"+\" button in PromptComposer. The host owns\n * what to do with them — typically POST to a per-app upload endpoint and pass\n * the resulting URLs/paths into the prompt that gets sent to the agent.\n */\nexport type PromptComposerFile = File;\n\nexport interface PromptComposerSubmitOptions {\n model?: string;\n engine?: string;\n effort?: ReasoningEffort;\n}\n\nexport interface PromptComposerProps {\n /** Called when the user submits the composer. */\n onSubmit: (\n text: string,\n files: PromptComposerFile[],\n references: Reference[],\n options: PromptComposerSubmitOptions,\n ) => void;\n placeholder?: string;\n disabled?: boolean;\n autoFocus?: boolean;\n className?: string;\n /** Forwarded to TiptapComposer for draft persistence. */\n draftScope?: string;\n /** Keep the submitted prompt in the editor. Default: false. */\n preserveDraftOnSubmit?: boolean;\n /** Show the model selector (default: true). */\n showModelSelector?: boolean;\n /** Show the voice dictation button (default: true). */\n voiceEnabled?: boolean;\n /** Show file upload controls and pass submitted files to onSubmit (default: true). */\n attachmentsEnabled?: boolean;\n /** Called whenever the plain editor text changes. */\n onTextChange?: (text: string) => void;\n /** Imperative handle for focusing the composer. */\n composerRef?: Ref<TiptapComposerHandle>;\n}\n\n// Minimal pass-through adapter. PromptComposer always submits through\n// onSubmitOverride, so the runtime never actually calls this — but\n// `useLocalRuntime` needs *something* shaped like a ChatModelAdapter.\nconst NOOP_ADAPTER: ChatModelAdapter = {\n async *run() {\n return;\n },\n};\n\n/**\n * Local binary document adapter so reference PDFs, decks, and docs can be\n * attached without dragging the whole assistant chat module into bundles that\n * just want a prompt popover.\n */\nclass BinaryDocumentAttachmentAdapter implements AttachmentAdapter {\n public accept = PROMPT_DOCUMENT_ATTACHMENT_ACCEPT;\n\n public async add(state: { file: File }): Promise<PendingAttachment> {\n return {\n id: state.file.name,\n type: \"document\",\n name: state.file.name,\n contentType: state.file.type || \"application/octet-stream\",\n file: state.file,\n status: { type: \"requires-action\", reason: \"composer-send\" },\n };\n }\n\n public async send(\n attachment: PendingAttachment,\n ): Promise<CompleteAttachment> {\n return {\n ...attachment,\n status: { type: \"complete\" },\n content: [],\n };\n }\n\n public async remove() {\n /* noop */\n }\n}\n\nfunction isInlineableTextFile(file: File): boolean {\n if (file.type.startsWith(\"text/\")) return true;\n if (file.type === \"application/json\") return true;\n return /\\.(txt|md|markdown|csv|json|yaml|yml|html?|css|xml)$/i.test(\n file.name,\n );\n}\n\nfunction formatInlineTextFile(name: string, text: string): string {\n const truncated = text.length > MAX_INLINE_TEXT_FILE_CHARS;\n const body = truncated ? text.slice(0, MAX_INLINE_TEXT_FILE_CHARS) : text;\n return [\n `<uploaded-text-file name=\"${name}\">`,\n body,\n truncated\n ? `[Truncated after ${MAX_INLINE_TEXT_FILE_CHARS} characters.]`\n : \"\",\n \"</uploaded-text-file>\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction getImageSrc(attachment: Attachment): string | null {\n if (attachment.type !== \"image\") return null;\n if (\"file\" in attachment && attachment.file) {\n return URL.createObjectURL(attachment.file);\n }\n const imagePart = attachment.content?.find((part) => part.type === \"image\");\n return imagePart && \"image\" in imagePart ? imagePart.image : null;\n}\n\nfunction AttachmentChip({\n attachment,\n onRemove,\n}: {\n attachment: Attachment;\n onRemove: (id: string) => void;\n}) {\n const src = useMemo(() => getImageSrc(attachment), [attachment]);\n useEffect(\n () => () => {\n if (src?.startsWith(\"blob:\")) URL.revokeObjectURL(src);\n },\n [src],\n );\n\n if (isPastedTextAttachmentName(attachment.name)) {\n return <PastedTextChip attachment={attachment} onRemove={onRemove} />;\n }\n\n if (src) {\n return (\n <div className=\"group relative flex h-16 min-w-16 max-w-28 items-center justify-center overflow-hidden rounded-lg border border-border/70 bg-muted/50\">\n <img\n src={src}\n alt={attachment.name}\n className=\"max-h-full max-w-full object-contain p-1\"\n />\n <button\n type=\"button\"\n onClick={() => onRemove(attachment.id)}\n aria-label={`Remove ${attachment.name}`}\n className=\"absolute right-1 top-1 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-background/90 text-muted-foreground hover:text-foreground\"\n >\n <IconX className=\"h-3 w-3\" />\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"group relative inline-flex max-w-[200px] items-center gap-2 rounded-md border border-border/70 bg-muted/50 px-2 py-1.5 text-xs\">\n <div className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded bg-background text-[9px] font-semibold uppercase text-muted-foreground\">\n {attachment.name.split(\".\").pop() || \"file\"}\n </div>\n <span className=\"min-w-0 truncate font-medium\">{attachment.name}</span>\n <button\n type=\"button\"\n onClick={() => onRemove(attachment.id)}\n aria-label={`Remove ${attachment.name}`}\n className=\"flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground\"\n >\n <IconX className=\"h-3 w-3\" />\n </button>\n </div>\n );\n}\n\nfunction PromptAttachmentStrip() {\n const attachments = useComposer((state) => state.attachments);\n const aui = useAui();\n\n const handleRemove = useCallback(\n (id: string) => {\n void aui.composer().attachment({ id }).remove();\n },\n [aui],\n );\n\n if (attachments.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-2 px-2 pt-2\">\n {attachments.map((attachment) => (\n <AttachmentChip\n key={attachment.id}\n attachment={attachment}\n onRemove={handleRemove}\n />\n ))}\n </div>\n );\n}\n\nfunction PromptComposerInner({\n onSubmit,\n placeholder,\n disabled,\n autoFocus,\n className,\n draftScope,\n preserveDraftOnSubmit = false,\n showModelSelector = true,\n voiceEnabled = true,\n attachmentsEnabled = true,\n onTextChange,\n composerRef,\n}: PromptComposerProps) {\n const localRef = useRef<TiptapComposerHandle>(null);\n const handleRef = composerRef ?? localRef;\n const models = useChatModels();\n\n useEffect(() => {\n if (!autoFocus) return;\n const id = window.setTimeout(() => {\n const target =\n typeof handleRef === \"object\" && handleRef && \"current\" in handleRef\n ? handleRef.current\n : null;\n target?.focus();\n }, 50);\n return () => window.clearTimeout(id);\n }, [autoFocus, handleRef]);\n\n const handleSubmit = useCallback(\n async (\n text: string,\n references: Reference[],\n attachments?: ReadonlyArray<unknown>,\n ) => {\n // PromptComposer hosts (NewWorkspaceAppFlow, create-extension, create-deck,\n // …) submit a single string prompt — they don't run the assistant-ui\n // attachment send pipeline. TiptapComposer auto-converts large pastes\n // into a \"Pasted text\" chip, which would otherwise disappear into an\n // unprocessed File. Inline the chip body back into the prompt text so\n // newlines and full content survive the round-trip.\n const files: File[] = [];\n const pastedTextBlocks: string[] = [];\n for (const att of attachments ?? []) {\n const a = att as Attachment;\n if (\"file\" in a && a.file instanceof File) {\n const file = a.file;\n if (isPastedTextAttachmentName(file.name)) {\n try {\n pastedTextBlocks.push(await file.text());\n } catch {\n // If we can't read it, fall back to surfacing it as a regular\n // attachment file rather than silently losing it.\n files.push(file);\n }\n } else {\n if (isInlineableTextFile(file)) {\n try {\n pastedTextBlocks.push(\n formatInlineTextFile(file.name, await file.text()),\n );\n } catch {\n // Keep the upload path fallback below.\n }\n }\n files.push(file);\n }\n }\n }\n const finalText = pastedTextBlocks.length\n ? [text.trim(), ...pastedTextBlocks].filter(Boolean).join(\"\\n\\n\")\n : text;\n onSubmit(finalText, files, references, {\n model: showModelSelector ? models.selectedModel : undefined,\n engine: showModelSelector ? models.selectedEngine : undefined,\n effort: showModelSelector ? models.selectedEffort : undefined,\n });\n },\n [\n models.selectedEffort,\n models.selectedEngine,\n models.selectedModel,\n onSubmit,\n showModelSelector,\n ],\n );\n\n return (\n <div\n className={cn(\n \"agent-composer-area flex flex-col rounded-lg border border-input bg-background focus-within:ring-1 focus-within:ring-ring\",\n className,\n )}\n >\n <ComposerPrimitive.Root className=\"flex flex-col\">\n <PromptAttachmentStrip />\n <TiptapComposer\n focusRef={handleRef}\n disabled={disabled}\n placeholder={placeholder}\n onSubmit={handleSubmit}\n clearOnSubmit={!preserveDraftOnSubmit}\n plusMenuMode={attachmentsEnabled ? \"upload-only\" : \"hidden\"}\n voiceEnabled={voiceEnabled}\n onTextChange={onTextChange}\n draftScope={draftScope}\n selectedModel={showModelSelector ? models.selectedModel : undefined}\n selectedEffort={showModelSelector ? models.selectedEffort : undefined}\n availableModels={\n showModelSelector ? models.availableModels : undefined\n }\n onModelChange={showModelSelector ? models.onModelChange : undefined}\n onEffortChange={showModelSelector ? models.onEffortChange : undefined}\n />\n </ComposerPrimitive.Root>\n </div>\n );\n}\n\n/**\n * Standalone composer that mirrors the agent sidebar's input experience —\n * voice dictation, file upload, model selector, submit-on-Enter — for use in\n * popovers and inline prompt forms (create tool, create deck, create dashboard,\n * the Dispatch new-app flow, etc.).\n *\n * The host owns submission: when the user presses Enter or clicks submit,\n * `onSubmit(text, files, references, options)` is called. PromptComposer runs\n * its own minimal assistant-ui runtime so it can be dropped into any subtree\n * without needing the outer chat to be mounted.\n */\nexport function PromptComposer(props: PromptComposerProps) {\n const attachmentAdapter = useMemo(\n () =>\n new CompositeAttachmentAdapter([\n new SimpleImageAttachmentAdapter(),\n new BinaryDocumentAttachmentAdapter(),\n new TextAttachmentAdapter(),\n ]),\n [],\n );\n const runtime = useLocalRuntime(NOOP_ADAPTER, {\n adapters: { attachments: attachmentAdapter },\n });\n\n return (\n <AssistantRuntimeProvider runtime={runtime}>\n <ThreadPrimitive.Root className=\"contents\">\n <PromptComposerInner {...props} />\n </ThreadPrimitive.Root>\n </AssistantRuntimeProvider>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PromptComposer.js","sourceRoot":"","sources":["../../../src/client/composer/PromptComposer.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, type Ref } from \"react\";\nimport {\n AssistantRuntimeProvider,\n ComposerPrimitive,\n ThreadPrimitive,\n useAui,\n useComposer,\n useLocalRuntime,\n} from \"@assistant-ui/react\";\nimport type {\n Attachment,\n AttachmentAdapter,\n ChatModelAdapter,\n CompleteAttachment,\n PendingAttachment,\n} from \"@assistant-ui/react\";\nimport {\n CompositeAttachmentAdapter,\n SimpleImageAttachmentAdapter,\n} from \"@assistant-ui/react\";\nimport { IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport { TiptapComposer, type TiptapComposerHandle } from \"./TiptapComposer.js\";\nimport type { Reference } from \"./types.js\";\nimport { useChatModels } from \"../use-chat-models.js\";\nimport type { ReasoningEffort } from \"../../shared/reasoning-effort.js\";\nimport { isPastedTextAttachmentName } from \"./pasted-text.js\";\nimport { PastedTextChip } from \"./PastedTextChip.js\";\nimport {\n PROMPT_DOCUMENT_ATTACHMENT_ACCEPT,\n TextAttachmentAdapter,\n} from \"./attachment-accept.js\";\n\nconst MAX_INLINE_TEXT_FILE_CHARS = 60_000;\n\n/**\n * Files the user attached via the \"+\" button in PromptComposer. The host owns\n * what to do with them — typically POST to a per-app upload endpoint and pass\n * the resulting URLs/paths into the prompt that gets sent to the agent.\n */\nexport type PromptComposerFile = File;\n\nexport interface PromptComposerSubmitOptions {\n model?: string;\n engine?: string;\n effort?: ReasoningEffort;\n}\n\nexport interface PromptComposerProps {\n /** Called when the user submits the composer. */\n onSubmit: (\n text: string,\n files: PromptComposerFile[],\n references: Reference[],\n options: PromptComposerSubmitOptions,\n ) => void;\n placeholder?: string;\n disabled?: boolean;\n autoFocus?: boolean;\n className?: string;\n /** Forwarded to TiptapComposer for draft persistence. */\n draftScope?: string;\n /** Keep the submitted prompt in the editor. Default: false. */\n preserveDraftOnSubmit?: boolean;\n /** Show the model selector (default: true). */\n showModelSelector?: boolean;\n /** Show the voice dictation button (default: true). */\n voiceEnabled?: boolean;\n /** Show file upload controls and pass submitted files to onSubmit (default: true). */\n attachmentsEnabled?: boolean;\n /** Called whenever the plain editor text changes. */\n onTextChange?: (text: string) => void;\n /** Imperative handle for focusing the composer. */\n composerRef?: Ref<TiptapComposerHandle>;\n}\n\n// Minimal pass-through adapter. PromptComposer always submits through\n// onSubmitOverride, so the runtime never actually calls this — but\n// `useLocalRuntime` needs *something* shaped like a ChatModelAdapter.\nconst NOOP_ADAPTER: ChatModelAdapter = {\n async *run() {\n return;\n },\n};\n\n/**\n * Local binary document adapter so reference PDFs, decks, and docs can be\n * attached without dragging the whole assistant chat module into bundles that\n * just want a prompt popover.\n */\nclass BinaryDocumentAttachmentAdapter implements AttachmentAdapter {\n public accept = PROMPT_DOCUMENT_ATTACHMENT_ACCEPT;\n\n public async add(state: { file: File }): Promise<PendingAttachment> {\n return {\n id: state.file.name,\n type: \"document\",\n name: state.file.name,\n contentType: state.file.type || \"application/octet-stream\",\n file: state.file,\n status: { type: \"requires-action\", reason: \"composer-send\" },\n };\n }\n\n public async send(\n attachment: PendingAttachment,\n ): Promise<CompleteAttachment> {\n return {\n ...attachment,\n status: { type: \"complete\" },\n content: [],\n };\n }\n\n public async remove() {\n /* noop */\n }\n}\n\nfunction isInlineableTextFile(file: File): boolean {\n if (file.type.startsWith(\"text/\")) return true;\n if (file.type === \"application/json\") return true;\n return /\\.(txt|md|markdown|csv|json|yaml|yml|html?|css|xml)$/i.test(\n file.name,\n );\n}\n\nfunction formatInlineTextFile(name: string, text: string): string {\n const truncated = text.length > MAX_INLINE_TEXT_FILE_CHARS;\n const body = truncated ? text.slice(0, MAX_INLINE_TEXT_FILE_CHARS) : text;\n return [\n `<uploaded-text-file name=\"${name}\">`,\n body,\n truncated\n ? `[Truncated after ${MAX_INLINE_TEXT_FILE_CHARS} characters.]`\n : \"\",\n \"</uploaded-text-file>\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction getImageSrc(attachment: Attachment): string | null {\n if (attachment.type !== \"image\") return null;\n if (\"file\" in attachment && attachment.file) {\n return URL.createObjectURL(attachment.file);\n }\n const imagePart = attachment.content?.find((part) => part.type === \"image\");\n return imagePart && \"image\" in imagePart ? imagePart.image : null;\n}\n\nfunction AttachmentChip({\n attachment,\n onRemove,\n}: {\n attachment: Attachment;\n onRemove: (id: string) => void;\n}) {\n const src = useMemo(() => getImageSrc(attachment), [attachment]);\n useEffect(\n () => () => {\n if (src?.startsWith(\"blob:\")) URL.revokeObjectURL(src);\n },\n [src],\n );\n\n if (isPastedTextAttachmentName(attachment.name)) {\n return <PastedTextChip attachment={attachment} onRemove={onRemove} />;\n }\n\n if (src) {\n return (\n <div className=\"group relative flex h-16 min-w-16 max-w-28 items-center justify-center overflow-hidden rounded-lg border border-border/70 bg-muted/50\">\n <img\n src={src}\n alt={attachment.name}\n className=\"max-h-full max-w-full object-contain p-1\"\n />\n <button\n type=\"button\"\n onClick={() => onRemove(attachment.id)}\n aria-label={`Remove ${attachment.name}`}\n className=\"absolute right-1 top-1 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-background/90 text-muted-foreground hover:text-foreground\"\n >\n <IconX className=\"h-3 w-3\" />\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"group relative inline-flex max-w-[200px] items-center gap-2 rounded-md border border-border/70 bg-muted/50 px-2 py-1.5 text-xs\">\n <div className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded bg-background text-[9px] font-semibold uppercase text-muted-foreground\">\n {attachment.name.split(\".\").pop() || \"file\"}\n </div>\n <span className=\"min-w-0 truncate font-medium\">{attachment.name}</span>\n <button\n type=\"button\"\n onClick={() => onRemove(attachment.id)}\n aria-label={`Remove ${attachment.name}`}\n className=\"flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground\"\n >\n <IconX className=\"h-3 w-3\" />\n </button>\n </div>\n );\n}\n\nfunction PromptAttachmentStrip() {\n const attachments = useComposer((state) => state.attachments);\n const aui = useAui();\n\n const handleRemove = useCallback(\n (id: string) => {\n void aui.composer().attachment({ id }).remove();\n },\n [aui],\n );\n\n if (attachments.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-2 px-2 pt-2\">\n {attachments.map((attachment) => (\n <AttachmentChip\n key={attachment.id}\n attachment={attachment}\n onRemove={handleRemove}\n />\n ))}\n </div>\n );\n}\n\nfunction PromptComposerInner({\n onSubmit,\n placeholder,\n disabled,\n autoFocus,\n className,\n draftScope,\n preserveDraftOnSubmit = false,\n showModelSelector = true,\n voiceEnabled = true,\n attachmentsEnabled = true,\n onTextChange,\n composerRef,\n}: PromptComposerProps) {\n const localRef = useRef<TiptapComposerHandle>(null);\n const handleRef = composerRef ?? localRef;\n const models = useChatModels();\n\n useEffect(() => {\n if (!autoFocus) return;\n const id = window.setTimeout(() => {\n const target =\n typeof handleRef === \"object\" && handleRef && \"current\" in handleRef\n ? handleRef.current\n : null;\n target?.focus();\n }, 50);\n return () => window.clearTimeout(id);\n }, [autoFocus, handleRef]);\n\n const handleSubmit = useCallback(\n async (\n text: string,\n references: Reference[],\n attachments?: ReadonlyArray<unknown>,\n ) => {\n // PromptComposer hosts (NewWorkspaceAppFlow, create-extension, create-deck,\n // …) submit a single string prompt — they don't run the assistant-ui\n // attachment send pipeline. TiptapComposer auto-converts large pastes\n // into a \"Pasted text\" chip, which would otherwise disappear into an\n // unprocessed File. Inline the chip body back into the prompt text so\n // newlines and full content survive the round-trip.\n const files: File[] = [];\n const pastedTextBlocks: string[] = [];\n for (const att of attachments ?? []) {\n const a = att as Attachment;\n if (\"file\" in a && a.file instanceof File) {\n const file = a.file;\n if (isPastedTextAttachmentName(file.name)) {\n try {\n pastedTextBlocks.push(await file.text());\n } catch {\n // If we can't read it, fall back to surfacing it as a regular\n // attachment file rather than silently losing it.\n files.push(file);\n }\n } else {\n if (isInlineableTextFile(file)) {\n try {\n pastedTextBlocks.push(\n formatInlineTextFile(file.name, await file.text()),\n );\n } catch {\n // Keep the upload path fallback below.\n }\n }\n files.push(file);\n }\n }\n }\n const finalText = pastedTextBlocks.length\n ? [text.trim(), ...pastedTextBlocks].filter(Boolean).join(\"\\n\\n\")\n : text;\n onSubmit(finalText, files, references, {\n model: showModelSelector ? models.selectedModel : undefined,\n engine: showModelSelector ? models.selectedEngine : undefined,\n effort: showModelSelector ? models.selectedEffort : undefined,\n });\n },\n [\n models.selectedEffort,\n models.selectedEngine,\n models.selectedModel,\n onSubmit,\n showModelSelector,\n ],\n );\n\n return (\n <div\n className={cn(\n \"agent-composer-area flex flex-col rounded-lg border border-input bg-background text-left focus-within:ring-1 focus-within:ring-ring\",\n className,\n )}\n >\n <ComposerPrimitive.Root className=\"flex flex-col\">\n <PromptAttachmentStrip />\n <TiptapComposer\n focusRef={handleRef}\n disabled={disabled}\n placeholder={placeholder}\n onSubmit={handleSubmit}\n clearOnSubmit={!preserveDraftOnSubmit}\n plusMenuMode={attachmentsEnabled ? \"upload-only\" : \"hidden\"}\n voiceEnabled={voiceEnabled}\n onTextChange={onTextChange}\n draftScope={draftScope}\n selectedModel={showModelSelector ? models.selectedModel : undefined}\n selectedEffort={showModelSelector ? models.selectedEffort : undefined}\n availableModels={\n showModelSelector ? models.availableModels : undefined\n }\n onModelChange={showModelSelector ? models.onModelChange : undefined}\n onEffortChange={showModelSelector ? models.onEffortChange : undefined}\n />\n </ComposerPrimitive.Root>\n </div>\n );\n}\n\n/**\n * Standalone composer that mirrors the agent sidebar's input experience —\n * voice dictation, file upload, model selector, submit-on-Enter — for use in\n * popovers and inline prompt forms (create tool, create deck, create dashboard,\n * the Dispatch new-app flow, etc.).\n *\n * The host owns submission: when the user presses Enter or clicks submit,\n * `onSubmit(text, files, references, options)` is called. PromptComposer runs\n * its own minimal assistant-ui runtime so it can be dropped into any subtree\n * without needing the outer chat to be mounted.\n */\nexport function PromptComposer(props: PromptComposerProps) {\n const attachmentAdapter = useMemo(\n () =>\n new CompositeAttachmentAdapter([\n new SimpleImageAttachmentAdapter(),\n new BinaryDocumentAttachmentAdapter(),\n new TextAttachmentAdapter(),\n ]),\n [],\n );\n const runtime = useLocalRuntime(NOOP_ADAPTER, {\n adapters: { attachments: attachmentAdapter },\n });\n\n return (\n <AssistantRuntimeProvider runtime={runtime}>\n <ThreadPrimitive.Root className=\"contents\">\n <PromptComposerInner {...props} />\n </ThreadPrimitive.Root>\n </AssistantRuntimeProvider>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionsListPage.d.ts","sourceRoot":"","sources":["../../../src/client/extensions/ExtensionsListPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExtensionsListPage.d.ts","sourceRoot":"","sources":["../../../src/client/extensions/ExtensionsListPage.tsx"],"names":[],"mappings":"AA8EA,wBAAgB,kBAAkB,4CAwOjC"}
|
|
@@ -32,7 +32,7 @@ function submitCreateTool(prompt) {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
function CreateToolInput({ className }) {
|
|
35
|
-
return (_jsxs("div", { className: cn("flex flex-col gap-
|
|
35
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-2 text-left", className), children: [_jsx("p", { className: "px-1 text-sm font-medium text-foreground", children: "What should it do?" }), _jsx(PromptComposer, { autoFocus: true, className: "text-left", placeholder: "A todo list, API dashboard, calculator...", draftScope: "extensions:create", onSubmit: (text) => submitCreateTool(text) })] }));
|
|
36
36
|
}
|
|
37
37
|
export function ExtensionsListPage() {
|
|
38
38
|
const [showCreate, setShowCreate] = useState(false);
|
|
@@ -92,7 +92,7 @@ export function ExtensionsListPage() {
|
|
|
92
92
|
queryClient.invalidateQueries({ queryKey: ["extensions"] });
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
return (_jsxs("div", { className: "flex h-full w-full flex-col", children: [_jsxs("header", { className: "flex h-12 items-center justify-between border-b px-4 shrink-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Link, { to: "/", className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", "aria-label": "Back to app", children: _jsx(IconArrowLeft, { className: "h-4 w-4" }) }), _jsx("h1", { className: "text-sm font-semibold", children: "Extensions" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Popover, { open: showCreate, onOpenChange: setShowCreate, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconPlus, { className: "h-4 w-4" }), "New Extension"] }) }), _jsxs(PopoverContent, { align: "end", sideOffset: 6, className: "w-[420px] p-3", children: [_jsx("p", { className: "px-1 pb-2 text-sm font-semibold text-foreground", children: "New extension" }), _jsx(PromptComposer, { autoFocus: true, placeholder: "Describe what you'd like to build...", draftScope: "extensions:create-popover", onSubmit: handleCreate })] })] }), _jsx(NotificationsBell, {}), _jsx(AgentToggleButton, {})] })] }), _jsx("div", { className: "flex-1 overflow-auto px-5 py-8 sm:px-8 sm:py-10", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-lg border border-border bg-card p-5", children: [_jsx("div", { className: "mb-3 h-10 w-10 rounded-lg bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 h-4 w-2/3 rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-3 w-4/5 rounded bg-muted animate-pulse" })] }, i))) })) : toolList.length === 0 ? (_jsx("div", { className: "flex min-h-[calc(100vh-9rem)] flex-col items-center justify-
|
|
95
|
+
return (_jsxs("div", { className: "flex h-full w-full flex-col", children: [_jsxs("header", { className: "flex h-12 items-center justify-between border-b px-4 shrink-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Link, { to: "/", className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", "aria-label": "Back to app", children: _jsx(IconArrowLeft, { className: "h-4 w-4" }) }), _jsx("h1", { className: "text-sm font-semibold", children: "Extensions" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Popover, { open: showCreate, onOpenChange: setShowCreate, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconPlus, { className: "h-4 w-4" }), "New Extension"] }) }), _jsxs(PopoverContent, { align: "end", sideOffset: 6, className: "w-[420px] p-3", children: [_jsx("p", { className: "px-1 pb-2 text-sm font-semibold text-foreground", children: "New extension" }), _jsx(PromptComposer, { autoFocus: true, placeholder: "Describe what you'd like to build...", draftScope: "extensions:create-popover", onSubmit: handleCreate })] })] }), _jsx(NotificationsBell, {}), _jsx(AgentToggleButton, {})] })] }), _jsx("div", { className: "flex-1 overflow-auto px-5 py-8 sm:px-8 sm:py-10", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-lg border border-border bg-card p-5", children: [_jsx("div", { className: "mb-3 h-10 w-10 rounded-lg bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 h-4 w-2/3 rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-3 w-4/5 rounded bg-muted animate-pulse" })] }, i))) })) : toolList.length === 0 ? (_jsx("div", { className: "flex min-h-[calc(100vh-9rem)] flex-col items-center justify-start px-2 pb-12 pt-[clamp(5rem,18vh,11rem)] sm:pb-16", children: _jsxs("div", { className: "mx-auto flex w-full max-w-[34rem] flex-col gap-7", children: [_jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [_jsx(IconTool, { className: "h-10 w-10 text-muted-foreground/40" }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-base font-semibold text-foreground", children: "Create your first extension" }), _jsx("p", { className: "mx-auto max-w-sm text-sm text-muted-foreground", children: "Describe the small app you want and the agent will build it." })] })] }), _jsx(CreateToolInput, { className: "w-full" })] }) })) : (_jsx("div", { className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3", children: toolList.map((extension) => (_jsxs("div", { className: cn("group relative rounded-lg border border-border bg-card", "hover:border-primary/30 hover:shadow-sm"), children: [_jsxs(Link, { to: `/extensions/${extension.id}`, className: "block p-5 pr-12", children: [_jsx("div", { className: "mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary", children: _jsx(IconTool, { className: "h-5 w-5" }) }), _jsx("h3", { className: "mb-1 text-sm font-semibold text-foreground", children: extension.name }), extension.description && (_jsx("p", { className: "line-clamp-2 text-xs text-muted-foreground", children: extension.description }))] }), _jsxs(Popover, { open: confirmDeleteId === extension.id, onOpenChange: (open) => setConfirmDeleteId(open ? extension.id : null), children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "absolute right-3 top-3 inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring group-hover:opacity-100", "aria-label": `Options for ${extension.name}`, children: _jsx(IconDotsVertical, { className: "h-4 w-4" }) }) }), _jsx(PopoverContent, { align: "end", sideOffset: 4, className: "w-64 p-0", children: _jsxs("div", { className: "p-3", children: [_jsxs("p", { className: "text-[12px]", children: [extension.canDelete === false ? "Remove " : "Delete ", _jsx("span", { className: "font-medium", children: extension.name }), "?", extension.canDelete === false
|
|
96
96
|
? " This hides it from your Extensions list without deleting it for anyone else."
|
|
97
97
|
: " This removes it everywhere it is shared."] }), _jsxs("div", { className: "mt-3 flex justify-end gap-1", children: [_jsx("button", { type: "button", onClick: () => setConfirmDeleteId(null), className: "rounded-md px-2 py-1 text-[12px] hover:bg-accent", children: "Cancel" }), _jsxs("button", { type: "button", onClick: () => handleDelete(extension), disabled: deletingId === extension.id, className: cn("inline-flex items-center gap-1.5 rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90", deletingId === extension.id && "opacity-60"), children: [_jsx(IconTrash, { className: "h-3.5 w-3.5" }), deletingId === extension.id
|
|
98
98
|
? extension.canDelete === false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionsListPage.js","sourceRoot":"","sources":["../../../src/client/extensions/ExtensionsListPage.tsx"],"names":[],"mappings":"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","sourcesContent":["import { agentNativePath } from \"../api-path.js\";\nimport { useState, useEffect } from \"react\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { Link } from \"react-router\";\nimport {\n IconArrowLeft,\n IconDotsVertical,\n IconPlus,\n IconTool,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport { AgentToggleButton } from \"../AgentPanel.js\";\nimport { NotificationsBell } from \"../notifications/NotificationsBell.js\";\nimport { sendToAgentChat } from \"../agent-chat.js\";\nimport { PromptComposer } from \"../composer/PromptComposer.js\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport {\n TOOLS_ORDER_CHANGE_EVENT,\n applyToolsOrder,\n getToolsOrder,\n} from \"./extension-order.js\";\nimport {\n deleteOrHideExtension,\n invalidateExtensionRemoval,\n} from \"./delete-extension.js\";\n\ninterface Extension {\n id: string;\n name: string;\n description?: string;\n icon?: string;\n canDelete?: boolean;\n}\n\nlet lastCreateSubmission: { prompt: string; at: number } | null = null;\n\nfunction submitCreateTool(prompt: string) {\n const trimmed = prompt.trim();\n if (!trimmed) return;\n const now = Date.now();\n if (\n lastCreateSubmission &&\n lastCreateSubmission.prompt === trimmed &&\n now - lastCreateSubmission.at < 2_000\n ) {\n return;\n }\n lastCreateSubmission = { prompt: trimmed, at: now };\n sendToAgentChat({\n message: `Create an extension: ${trimmed}`,\n submit: true,\n openSidebar: true,\n newTab: true,\n });\n}\n\nfunction CreateToolInput({ className }: { className?: string }) {\n return (\n <div className={cn(\"flex flex-col gap-3\", className)}>\n <p className=\"text-center text-base font-semibold text-foreground\">\n New extension\n </p>\n <PromptComposer\n autoFocus\n placeholder=\"Describe what you'd like to build... e.g. a todo list, API dashboard, calculator\"\n draftScope=\"extensions:create\"\n onSubmit={(text) => submitCreateTool(text)}\n />\n </div>\n );\n}\n\nexport function ExtensionsListPage() {\n const [showCreate, setShowCreate] = useState(false);\n const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);\n const [deletingId, setDeletingId] = useState<string | null>(null);\n const queryClient = useQueryClient();\n const [toolOrderState, setToolOrderState] = useState<string[]>(() =>\n typeof window !== \"undefined\" ? getToolsOrder() : [],\n );\n\n useEffect(() => {\n fetch(agentNativePath(\"/_agent-native/application-state/navigation\"), {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ view: \"extensions\" }),\n }).catch(() => {});\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const syncOrder = () => setToolOrderState(getToolsOrder());\n window.addEventListener(TOOLS_ORDER_CHANGE_EVENT, syncOrder);\n window.addEventListener(\"storage\", syncOrder);\n return () => {\n window.removeEventListener(TOOLS_ORDER_CHANGE_EVENT, syncOrder);\n window.removeEventListener(\"storage\", syncOrder);\n };\n }, []);\n\n const { data: extensions, isLoading } = useQuery<Extension[]>({\n queryKey: [\"extensions\"],\n queryFn: async () => {\n const res = await fetch(agentNativePath(\"/_agent-native/extensions\"));\n if (!res.ok) return [];\n return res.json();\n },\n });\n\n const toolList =\n toolOrderState.length > 0\n ? applyToolsOrder(extensions ?? [], toolOrderState)\n : (extensions ?? []);\n\n const handleCreate = (text: string) => {\n submitCreateTool(text);\n setShowCreate(false);\n };\n\n const handleDelete = async (extension: Extension) => {\n setDeletingId(extension.id);\n const previous = queryClient.getQueryData<Extension[]>([\"extensions\"]);\n queryClient.setQueryData<Extension[]>([\"extensions\"], (old) =>\n (old ?? []).filter((item) => item.id !== extension.id),\n );\n try {\n await deleteOrHideExtension(extension);\n invalidateExtensionRemoval(queryClient, extension.id);\n } catch {\n if (previous) queryClient.setQueryData([\"extensions\"], previous);\n } finally {\n setDeletingId(null);\n setConfirmDeleteId(null);\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n }\n };\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n <header className=\"flex h-12 items-center justify-between border-b px-4 shrink-0\">\n <div className=\"flex items-center gap-2\">\n <Link\n to=\"/\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground\"\n aria-label=\"Back to app\"\n >\n <IconArrowLeft className=\"h-4 w-4\" />\n </Link>\n <h1 className=\"text-sm font-semibold\">Extensions</h1>\n </div>\n <div className=\"flex items-center gap-2\">\n <Popover open={showCreate} onOpenChange={setShowCreate}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <IconPlus className=\"h-4 w-4\" />\n New Extension\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"end\"\n sideOffset={6}\n className=\"w-[420px] p-3\"\n >\n <p className=\"px-1 pb-2 text-sm font-semibold text-foreground\">\n New extension\n </p>\n <PromptComposer\n autoFocus\n placeholder=\"Describe what you'd like to build...\"\n draftScope=\"extensions:create-popover\"\n onSubmit={handleCreate}\n />\n </PopoverContent>\n </Popover>\n <NotificationsBell />\n <AgentToggleButton />\n </div>\n </header>\n\n <div className=\"flex-1 overflow-auto px-5 py-8 sm:px-8 sm:py-10\">\n {isLoading ? (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"rounded-lg border border-border bg-card p-5\"\n >\n <div className=\"mb-3 h-10 w-10 rounded-lg bg-muted animate-pulse\" />\n <div className=\"mb-2 h-4 w-2/3 rounded bg-muted animate-pulse\" />\n <div className=\"h-3 w-4/5 rounded bg-muted animate-pulse\" />\n </div>\n ))}\n </div>\n ) : toolList.length === 0 ? (\n <div className=\"flex min-h-[calc(100vh-9rem)] flex-col items-center justify-center px-2 py-12 text-center sm:py-16\">\n <div className=\"mx-auto flex w-full max-w-2xl flex-col items-center gap-8\">\n <div className=\"flex flex-col items-center gap-4\">\n <IconTool className=\"h-12 w-12 text-muted-foreground/40\" />\n <div className=\"space-y-2\">\n <p className=\"text-base font-semibold text-foreground\">\n No extensions yet\n </p>\n <p className=\"text-sm text-muted-foreground\">\n Describe what you'd like to build\n </p>\n </div>\n </div>\n <CreateToolInput className=\"w-full\" />\n </div>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {toolList.map((extension) => (\n <div\n key={extension.id}\n className={cn(\n \"group relative rounded-lg border border-border bg-card\",\n \"hover:border-primary/30 hover:shadow-sm\",\n )}\n >\n <Link\n to={`/extensions/${extension.id}`}\n className=\"block p-5 pr-12\"\n >\n <div className=\"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary\">\n <IconTool className=\"h-5 w-5\" />\n </div>\n <h3 className=\"mb-1 text-sm font-semibold text-foreground\">\n {extension.name}\n </h3>\n {extension.description && (\n <p className=\"line-clamp-2 text-xs text-muted-foreground\">\n {extension.description}\n </p>\n )}\n </Link>\n <Popover\n open={confirmDeleteId === extension.id}\n onOpenChange={(open) =>\n setConfirmDeleteId(open ? extension.id : null)\n }\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"absolute right-3 top-3 inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring group-hover:opacity-100\"\n aria-label={`Options for ${extension.name}`}\n >\n <IconDotsVertical className=\"h-4 w-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"end\"\n sideOffset={4}\n className=\"w-64 p-0\"\n >\n <div className=\"p-3\">\n <p className=\"text-[12px]\">\n {extension.canDelete === false ? \"Remove \" : \"Delete \"}\n <span className=\"font-medium\">{extension.name}</span>?\n {extension.canDelete === false\n ? \" This hides it from your Extensions list without deleting it for anyone else.\"\n : \" This removes it everywhere it is shared.\"}\n </p>\n <div className=\"mt-3 flex justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => setConfirmDeleteId(null)}\n className=\"rounded-md px-2 py-1 text-[12px] hover:bg-accent\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDelete(extension)}\n disabled={deletingId === extension.id}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90\",\n deletingId === extension.id && \"opacity-60\",\n )}\n >\n <IconTrash className=\"h-3.5 w-3.5\" />\n {deletingId === extension.id\n ? extension.canDelete === false\n ? \"Removing...\"\n : \"Deleting...\"\n : extension.canDelete === false\n ? \"Remove\"\n : \"Delete\"}\n </button>\n </div>\n </div>\n </PopoverContent>\n </Popover>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ExtensionsListPage.js","sourceRoot":"","sources":["../../../src/client/extensions/ExtensionsListPage.tsx"],"names":[],"mappings":"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","sourcesContent":["import { agentNativePath } from \"../api-path.js\";\nimport { useState, useEffect } from \"react\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { Link } from \"react-router\";\nimport {\n IconArrowLeft,\n IconDotsVertical,\n IconPlus,\n IconTool,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport { AgentToggleButton } from \"../AgentPanel.js\";\nimport { NotificationsBell } from \"../notifications/NotificationsBell.js\";\nimport { sendToAgentChat } from \"../agent-chat.js\";\nimport { PromptComposer } from \"../composer/PromptComposer.js\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport {\n TOOLS_ORDER_CHANGE_EVENT,\n applyToolsOrder,\n getToolsOrder,\n} from \"./extension-order.js\";\nimport {\n deleteOrHideExtension,\n invalidateExtensionRemoval,\n} from \"./delete-extension.js\";\n\ninterface Extension {\n id: string;\n name: string;\n description?: string;\n icon?: string;\n canDelete?: boolean;\n}\n\nlet lastCreateSubmission: { prompt: string; at: number } | null = null;\n\nfunction submitCreateTool(prompt: string) {\n const trimmed = prompt.trim();\n if (!trimmed) return;\n const now = Date.now();\n if (\n lastCreateSubmission &&\n lastCreateSubmission.prompt === trimmed &&\n now - lastCreateSubmission.at < 2_000\n ) {\n return;\n }\n lastCreateSubmission = { prompt: trimmed, at: now };\n sendToAgentChat({\n message: `Create an extension: ${trimmed}`,\n submit: true,\n openSidebar: true,\n newTab: true,\n });\n}\n\nfunction CreateToolInput({ className }: { className?: string }) {\n return (\n <div className={cn(\"flex flex-col gap-2 text-left\", className)}>\n <p className=\"px-1 text-sm font-medium text-foreground\">\n What should it do?\n </p>\n <PromptComposer\n autoFocus\n className=\"text-left\"\n placeholder=\"A todo list, API dashboard, calculator...\"\n draftScope=\"extensions:create\"\n onSubmit={(text) => submitCreateTool(text)}\n />\n </div>\n );\n}\n\nexport function ExtensionsListPage() {\n const [showCreate, setShowCreate] = useState(false);\n const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null);\n const [deletingId, setDeletingId] = useState<string | null>(null);\n const queryClient = useQueryClient();\n const [toolOrderState, setToolOrderState] = useState<string[]>(() =>\n typeof window !== \"undefined\" ? getToolsOrder() : [],\n );\n\n useEffect(() => {\n fetch(agentNativePath(\"/_agent-native/application-state/navigation\"), {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ view: \"extensions\" }),\n }).catch(() => {});\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const syncOrder = () => setToolOrderState(getToolsOrder());\n window.addEventListener(TOOLS_ORDER_CHANGE_EVENT, syncOrder);\n window.addEventListener(\"storage\", syncOrder);\n return () => {\n window.removeEventListener(TOOLS_ORDER_CHANGE_EVENT, syncOrder);\n window.removeEventListener(\"storage\", syncOrder);\n };\n }, []);\n\n const { data: extensions, isLoading } = useQuery<Extension[]>({\n queryKey: [\"extensions\"],\n queryFn: async () => {\n const res = await fetch(agentNativePath(\"/_agent-native/extensions\"));\n if (!res.ok) return [];\n return res.json();\n },\n });\n\n const toolList =\n toolOrderState.length > 0\n ? applyToolsOrder(extensions ?? [], toolOrderState)\n : (extensions ?? []);\n\n const handleCreate = (text: string) => {\n submitCreateTool(text);\n setShowCreate(false);\n };\n\n const handleDelete = async (extension: Extension) => {\n setDeletingId(extension.id);\n const previous = queryClient.getQueryData<Extension[]>([\"extensions\"]);\n queryClient.setQueryData<Extension[]>([\"extensions\"], (old) =>\n (old ?? []).filter((item) => item.id !== extension.id),\n );\n try {\n await deleteOrHideExtension(extension);\n invalidateExtensionRemoval(queryClient, extension.id);\n } catch {\n if (previous) queryClient.setQueryData([\"extensions\"], previous);\n } finally {\n setDeletingId(null);\n setConfirmDeleteId(null);\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n }\n };\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n <header className=\"flex h-12 items-center justify-between border-b px-4 shrink-0\">\n <div className=\"flex items-center gap-2\">\n <Link\n to=\"/\"\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground\"\n aria-label=\"Back to app\"\n >\n <IconArrowLeft className=\"h-4 w-4\" />\n </Link>\n <h1 className=\"text-sm font-semibold\">Extensions</h1>\n </div>\n <div className=\"flex items-center gap-2\">\n <Popover open={showCreate} onOpenChange={setShowCreate}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90\"\n >\n <IconPlus className=\"h-4 w-4\" />\n New Extension\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"end\"\n sideOffset={6}\n className=\"w-[420px] p-3\"\n >\n <p className=\"px-1 pb-2 text-sm font-semibold text-foreground\">\n New extension\n </p>\n <PromptComposer\n autoFocus\n placeholder=\"Describe what you'd like to build...\"\n draftScope=\"extensions:create-popover\"\n onSubmit={handleCreate}\n />\n </PopoverContent>\n </Popover>\n <NotificationsBell />\n <AgentToggleButton />\n </div>\n </header>\n\n <div className=\"flex-1 overflow-auto px-5 py-8 sm:px-8 sm:py-10\">\n {isLoading ? (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"rounded-lg border border-border bg-card p-5\"\n >\n <div className=\"mb-3 h-10 w-10 rounded-lg bg-muted animate-pulse\" />\n <div className=\"mb-2 h-4 w-2/3 rounded bg-muted animate-pulse\" />\n <div className=\"h-3 w-4/5 rounded bg-muted animate-pulse\" />\n </div>\n ))}\n </div>\n ) : toolList.length === 0 ? (\n <div className=\"flex min-h-[calc(100vh-9rem)] flex-col items-center justify-start px-2 pb-12 pt-[clamp(5rem,18vh,11rem)] sm:pb-16\">\n <div className=\"mx-auto flex w-full max-w-[34rem] flex-col gap-7\">\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <IconTool className=\"h-10 w-10 text-muted-foreground/40\" />\n <div className=\"space-y-1.5\">\n <p className=\"text-base font-semibold text-foreground\">\n Create your first extension\n </p>\n <p className=\"mx-auto max-w-sm text-sm text-muted-foreground\">\n Describe the small app you want and the agent will build it.\n </p>\n </div>\n </div>\n <CreateToolInput className=\"w-full\" />\n </div>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {toolList.map((extension) => (\n <div\n key={extension.id}\n className={cn(\n \"group relative rounded-lg border border-border bg-card\",\n \"hover:border-primary/30 hover:shadow-sm\",\n )}\n >\n <Link\n to={`/extensions/${extension.id}`}\n className=\"block p-5 pr-12\"\n >\n <div className=\"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary\">\n <IconTool className=\"h-5 w-5\" />\n </div>\n <h3 className=\"mb-1 text-sm font-semibold text-foreground\">\n {extension.name}\n </h3>\n {extension.description && (\n <p className=\"line-clamp-2 text-xs text-muted-foreground\">\n {extension.description}\n </p>\n )}\n </Link>\n <Popover\n open={confirmDeleteId === extension.id}\n onOpenChange={(open) =>\n setConfirmDeleteId(open ? extension.id : null)\n }\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"absolute right-3 top-3 inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring group-hover:opacity-100\"\n aria-label={`Options for ${extension.name}`}\n >\n <IconDotsVertical className=\"h-4 w-4\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"end\"\n sideOffset={4}\n className=\"w-64 p-0\"\n >\n <div className=\"p-3\">\n <p className=\"text-[12px]\">\n {extension.canDelete === false ? \"Remove \" : \"Delete \"}\n <span className=\"font-medium\">{extension.name}</span>?\n {extension.canDelete === false\n ? \" This hides it from your Extensions list without deleting it for anyone else.\"\n : \" This removes it everywhere it is shared.\"}\n </p>\n <div className=\"mt-3 flex justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => setConfirmDeleteId(null)}\n className=\"rounded-md px-2 py-1 text-[12px] hover:bg-accent\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDelete(extension)}\n disabled={deletingId === extension.id}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-md bg-destructive px-2 py-1 text-[12px] text-destructive-foreground hover:bg-destructive/90\",\n deletingId === extension.id && \"opacity-60\",\n )}\n >\n <IconTrash className=\"h-3.5 w-3.5\" />\n {deletingId === extension.id\n ? extension.canDelete === false\n ? \"Removing...\"\n : \"Deleting...\"\n : extension.canDelete === false\n ? \"Remove\"\n : \"Delete\"}\n </button>\n </div>\n </div>\n </PopoverContent>\n </Popover>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Validates that all template tables have the required ownership columns
|
|
5
5
|
* (owner_email, org_id) for per-user and per-org data scoping.
|
|
6
6
|
*
|
|
7
|
-
* Tables without these columns are
|
|
8
|
-
*
|
|
7
|
+
* Tables without these columns are denied to raw db-* tools by default. If a
|
|
8
|
+
* table should be queryable/writable through raw DB tools, add explicit
|
|
9
|
+
* owner_email/org_id scoping columns and an additive migration.
|
|
9
10
|
*
|
|
10
11
|
* Usage:
|
|
11
12
|
* pnpm action db-check-scoping [--db path] [--require-org] [--format json]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-scoping.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/check-scoping.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"check-scoping.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/check-scoping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2GH,wBAA8B,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0F1E"}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Validates that all template tables have the required ownership columns
|
|
5
5
|
* (owner_email, org_id) for per-user and per-org data scoping.
|
|
6
6
|
*
|
|
7
|
-
* Tables without these columns are
|
|
8
|
-
*
|
|
7
|
+
* Tables without these columns are denied to raw db-* tools by default. If a
|
|
8
|
+
* table should be queryable/writable through raw DB tools, add explicit
|
|
9
|
+
* owner_email/org_id scoping columns and an additive migration.
|
|
9
10
|
*
|
|
10
11
|
* Usage:
|
|
11
12
|
* pnpm action db-check-scoping [--db path] [--require-org] [--format json]
|
|
@@ -156,15 +157,16 @@ Options:
|
|
|
156
157
|
console.log();
|
|
157
158
|
}
|
|
158
159
|
if (withIssues.length > 0) {
|
|
159
|
-
console.log("
|
|
160
|
+
console.log("Tables denied to raw DB tools:");
|
|
160
161
|
for (const r of withIssues) {
|
|
161
162
|
for (const issue of r.issues) {
|
|
162
163
|
console.log(` ✗ ${r.table} — ${issue}`);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
console.log();
|
|
166
|
-
console.log(`${withIssues.length} table(s) lack scoping columns. ` +
|
|
167
|
-
`
|
|
167
|
+
console.log(`${withIssues.length} table(s) lack scoping columns. Raw db-* tools ` +
|
|
168
|
+
`will fail closed for these tables; use scoped actions or add ` +
|
|
169
|
+
`owner_email/org_id when raw DB access is intended.`);
|
|
168
170
|
process.exitCode = 1;
|
|
169
171
|
}
|
|
170
172
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-scoping.js","sourceRoot":"","sources":["../../../src/scripts/db/check-scoping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAC1E,CAAC;AAOD,qEAAqE;AACrE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,UAAU;IACV,mBAAmB;IACnB,cAAc;IACd,UAAU;IACV,4BAA4B;IAC5B,WAAW;IACX,cAAc;IACd,eAAe;IACf,YAAY;IACZ,gBAAgB;IAChB,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,iBAAiB;CAClB,CAAC,CAAC;AASH,SAAS,QAAQ,CACf,UAAyB,EACzB,UAAmB;IAEnB,MAAM,cAAc,GAAG,IAAI,GAAG,EAAoB,CAAC;IACnD,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClB,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,OAAO,GAAuB,EAAE,CAAC;IAEvC,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,cAAc,EAAE,CAAC;QAC9C,6BAA6B;QAC7B,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QACrC,gCAAgC;QAChC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAEpC,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,uBAAuB,CAAC,KAAU;IAC/C,MAAM,IAAI,GAAU,MAAM,KAAK,CAAA;;;;;GAK9B,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,MAAW;IAC9C,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,OAAO,CACvC,gFAAgF,CACjF,CAAC;IACF,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IAE7E,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,sBAAsB,OAAO,IAAI,CAAC,CAAC;QAC3E,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK;gBACL,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAW;aACvC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,cAAc,CAAC,IAAc;IACzD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC;;;;;;2CAM2B,CAAC,CAAC;QACzC,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,aAAa,CAAC,KAAK,MAAM,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAE7B,uBAAuB;IACvB,IAAI,GAAW,CAAC;IAChB,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,GAAG,GAAG,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC;SAAM,IAAI,cAAc,EAAE,EAAE,CAAC;QAC5B,GAAG,GAAG,cAAc,EAAE,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,UAAyB,CAAC;IAE9B,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,uBAAuB,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,YAAY,CAAC;YAC1B,GAAG;YACH,SAAS,EAAE,oBAAoB,EAAE;SAClC,CAAC,CAAC;QACH,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAEjD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IAExD,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG;gBACb,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI;gBACtC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;aAC7B;iBACE,MAAM,CAAC,OAAO,CAAC;iBACf,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,MAAM,KAAK,EAAE,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QACD,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CACT,GAAG,UAAU,CAAC,MAAM,kCAAkC;YACpD,gFAAgF,CACnF,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAClE,CAAC;AACH,CAAC","sourcesContent":["/**\n * Core script: db-check-scoping\n *\n * Validates that all template tables have the required ownership columns\n * (owner_email, org_id) for per-user and per-org data scoping.\n *\n * Tables without these columns are invisible to the scoping system and\n * will be accessible to all users in production mode.\n *\n * Usage:\n * pnpm action db-check-scoping [--db path] [--require-org] [--format json]\n */\n\nimport path from \"path\";\nimport { createClient } from \"@libsql/client\";\nimport { getDatabaseUrl, getDatabaseAuthToken } from \"../../db/client.js\";\nimport { parseArgs } from \"../utils.js\";\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\");\n}\n\ninterface TableColumn {\n table: string;\n column: string;\n}\n\n// Core tables that have their own scoping — skip these in validation\nconst CORE_TABLES = new Set([\n \"settings\",\n \"application_state\",\n \"oauth_tokens\",\n \"sessions\",\n // framework internal tables\n \"resources\",\n \"chat_threads\",\n \"chat_messages\",\n \"chat_tasks\",\n \"recurring_jobs\",\n // drizzle/migration tables\n \"__drizzle_migrations\",\n \"_litestream_lock\",\n \"_litestream_seq\",\n]);\n\ninterface ValidationResult {\n table: string;\n hasOwnerEmail: boolean;\n hasOrgId: boolean;\n issues: string[];\n}\n\nfunction validate(\n allColumns: TableColumn[],\n requireOrg: boolean,\n): ValidationResult[] {\n const columnsByTable = new Map<string, string[]>();\n for (const { table, column } of allColumns) {\n const cols = columnsByTable.get(table) || [];\n cols.push(column);\n columnsByTable.set(table, cols);\n }\n\n const results: ValidationResult[] = [];\n\n for (const [table, columns] of columnsByTable) {\n // Skip core/framework tables\n if (CORE_TABLES.has(table)) continue;\n // Skip migration-related tables\n if (table.startsWith(\"_\")) continue;\n\n const hasOwnerEmail = columns.includes(\"owner_email\");\n const hasOrgId = columns.includes(\"org_id\");\n const issues: string[] = [];\n\n if (!hasOwnerEmail) {\n issues.push(\"missing owner_email column — not scoped per-user\");\n }\n if (requireOrg && !hasOrgId) {\n issues.push(\"missing org_id column — not scoped per-org\");\n }\n\n results.push({ table, hasOwnerEmail, hasOrgId, issues });\n }\n\n return results;\n}\n\nasync function discoverColumnsPostgres(pgSql: any): Promise<TableColumn[]> {\n const rows: any[] = await pgSql`\n SELECT table_name, column_name\n FROM information_schema.columns\n WHERE table_schema = 'public'\n ORDER BY table_name, ordinal_position\n `;\n return rows.map((r) => ({ table: r.table_name, column: r.column_name }));\n}\n\nasync function discoverColumnsSqlite(client: any): Promise<TableColumn[]> {\n const tablesResult = await client.execute(\n `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`,\n );\n const tables = tablesResult.rows.map((r: any) => (r.name ?? r[0]) as string);\n\n const result: TableColumn[] = [];\n for (const table of tables) {\n const escaped = table.replace(/\"/g, '\"\"');\n const colsResult = await client.execute(`PRAGMA table_info(\"${escaped}\")`);\n for (const row of colsResult.rows) {\n result.push({\n table,\n column: (row.name ?? row[1]) as string,\n });\n }\n }\n return result;\n}\n\nexport default async function dbCheckScoping(args: string[]): Promise<void> {\n const parsed = parseArgs(args);\n\n if (parsed.help === \"true\") {\n console.log(`Usage: pnpm action db-check-scoping [options]\n\nOptions:\n --db <path> Path to SQLite database (default: data/app.db)\n --require-org Also check for org_id column (for multi-org apps)\n --format json Output as JSON\n --help Show this help message`);\n return;\n }\n\n const requireOrg = parsed[\"require-org\"] === \"true\";\n const format = parsed.format;\n\n // Resolve database URL\n let url: string;\n if (parsed.db) {\n url = \"file:\" + path.resolve(parsed.db);\n } else if (getDatabaseUrl()) {\n url = getDatabaseUrl();\n } else {\n url = \"file:\" + path.resolve(process.cwd(), \"data\", \"app.db\");\n }\n\n let allColumns: TableColumn[];\n\n if (isPostgresUrl(url)) {\n const { default: pg } = await import(\"postgres\");\n const pgSql = pg(url);\n try {\n allColumns = await discoverColumnsPostgres(pgSql);\n } finally {\n await pgSql.end();\n }\n } else {\n const client = createClient({\n url,\n authToken: getDatabaseAuthToken(),\n });\n try {\n allColumns = await discoverColumnsSqlite(client);\n } finally {\n client.close();\n }\n }\n\n const results = validate(allColumns, requireOrg);\n\n if (format === \"json\") {\n console.log(JSON.stringify({ tables: results }, null, 2));\n return;\n }\n\n const withIssues = results.filter((r) => r.issues.length > 0);\n const ok = results.filter((r) => r.issues.length === 0);\n\n if (ok.length > 0) {\n console.log(\"Scoped tables:\");\n for (const r of ok) {\n const scopes = [\n r.hasOwnerEmail ? \"owner_email\" : null,\n r.hasOrgId ? \"org_id\" : null,\n ]\n .filter(Boolean)\n .join(\", \");\n console.log(` ✓ ${r.table} (${scopes})`);\n }\n console.log();\n }\n\n if (withIssues.length > 0) {\n console.log(\"Unscoped tables (WARNING):\");\n for (const r of withIssues) {\n for (const issue of r.issues) {\n console.log(` ✗ ${r.table} — ${issue}`);\n }\n }\n console.log();\n console.log(\n `${withIssues.length} table(s) lack scoping columns. ` +\n `In production, agents can see ALL rows in these tables regardless of user/org.`,\n );\n process.exitCode = 1;\n } else {\n console.log(\"All template tables have proper scoping columns.\");\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"check-scoping.js","sourceRoot":"","sources":["../../../src/scripts/db/check-scoping.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core script: db-check-scoping\n *\n * Validates that all template tables have the required ownership columns\n * (owner_email, org_id) for per-user and per-org data scoping.\n *\n * Tables without these columns are denied to raw db-* tools by default. If a\n * table should be queryable/writable through raw DB tools, add explicit\n * owner_email/org_id scoping columns and an additive migration.\n *\n * Usage:\n * pnpm action db-check-scoping [--db path] [--require-org] [--format json]\n */\n\nimport path from \"path\";\nimport { createClient } from \"@libsql/client\";\nimport { getDatabaseUrl, getDatabaseAuthToken } from \"../../db/client.js\";\nimport { parseArgs } from \"../utils.js\";\n\nfunction isPostgresUrl(url: string): boolean {\n return url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\");\n}\n\ninterface TableColumn {\n table: string;\n column: string;\n}\n\n// Core tables that have their own scoping — skip these in validation\nconst CORE_TABLES = new Set([\n \"settings\",\n \"application_state\",\n \"oauth_tokens\",\n \"sessions\",\n // framework internal tables\n \"resources\",\n \"chat_threads\",\n \"chat_messages\",\n \"chat_tasks\",\n \"recurring_jobs\",\n // drizzle/migration tables\n \"__drizzle_migrations\",\n \"_litestream_lock\",\n \"_litestream_seq\",\n]);\n\ninterface ValidationResult {\n table: string;\n hasOwnerEmail: boolean;\n hasOrgId: boolean;\n issues: string[];\n}\n\nfunction validate(\n allColumns: TableColumn[],\n requireOrg: boolean,\n): ValidationResult[] {\n const columnsByTable = new Map<string, string[]>();\n for (const { table, column } of allColumns) {\n const cols = columnsByTable.get(table) || [];\n cols.push(column);\n columnsByTable.set(table, cols);\n }\n\n const results: ValidationResult[] = [];\n\n for (const [table, columns] of columnsByTable) {\n // Skip core/framework tables\n if (CORE_TABLES.has(table)) continue;\n // Skip migration-related tables\n if (table.startsWith(\"_\")) continue;\n\n const hasOwnerEmail = columns.includes(\"owner_email\");\n const hasOrgId = columns.includes(\"org_id\");\n const issues: string[] = [];\n\n if (!hasOwnerEmail) {\n issues.push(\"missing owner_email column — not scoped per-user\");\n }\n if (requireOrg && !hasOrgId) {\n issues.push(\"missing org_id column — not scoped per-org\");\n }\n\n results.push({ table, hasOwnerEmail, hasOrgId, issues });\n }\n\n return results;\n}\n\nasync function discoverColumnsPostgres(pgSql: any): Promise<TableColumn[]> {\n const rows: any[] = await pgSql`\n SELECT table_name, column_name\n FROM information_schema.columns\n WHERE table_schema = 'public'\n ORDER BY table_name, ordinal_position\n `;\n return rows.map((r) => ({ table: r.table_name, column: r.column_name }));\n}\n\nasync function discoverColumnsSqlite(client: any): Promise<TableColumn[]> {\n const tablesResult = await client.execute(\n `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`,\n );\n const tables = tablesResult.rows.map((r: any) => (r.name ?? r[0]) as string);\n\n const result: TableColumn[] = [];\n for (const table of tables) {\n const escaped = table.replace(/\"/g, '\"\"');\n const colsResult = await client.execute(`PRAGMA table_info(\"${escaped}\")`);\n for (const row of colsResult.rows) {\n result.push({\n table,\n column: (row.name ?? row[1]) as string,\n });\n }\n }\n return result;\n}\n\nexport default async function dbCheckScoping(args: string[]): Promise<void> {\n const parsed = parseArgs(args);\n\n if (parsed.help === \"true\") {\n console.log(`Usage: pnpm action db-check-scoping [options]\n\nOptions:\n --db <path> Path to SQLite database (default: data/app.db)\n --require-org Also check for org_id column (for multi-org apps)\n --format json Output as JSON\n --help Show this help message`);\n return;\n }\n\n const requireOrg = parsed[\"require-org\"] === \"true\";\n const format = parsed.format;\n\n // Resolve database URL\n let url: string;\n if (parsed.db) {\n url = \"file:\" + path.resolve(parsed.db);\n } else if (getDatabaseUrl()) {\n url = getDatabaseUrl();\n } else {\n url = \"file:\" + path.resolve(process.cwd(), \"data\", \"app.db\");\n }\n\n let allColumns: TableColumn[];\n\n if (isPostgresUrl(url)) {\n const { default: pg } = await import(\"postgres\");\n const pgSql = pg(url);\n try {\n allColumns = await discoverColumnsPostgres(pgSql);\n } finally {\n await pgSql.end();\n }\n } else {\n const client = createClient({\n url,\n authToken: getDatabaseAuthToken(),\n });\n try {\n allColumns = await discoverColumnsSqlite(client);\n } finally {\n client.close();\n }\n }\n\n const results = validate(allColumns, requireOrg);\n\n if (format === \"json\") {\n console.log(JSON.stringify({ tables: results }, null, 2));\n return;\n }\n\n const withIssues = results.filter((r) => r.issues.length > 0);\n const ok = results.filter((r) => r.issues.length === 0);\n\n if (ok.length > 0) {\n console.log(\"Scoped tables:\");\n for (const r of ok) {\n const scopes = [\n r.hasOwnerEmail ? \"owner_email\" : null,\n r.hasOrgId ? \"org_id\" : null,\n ]\n .filter(Boolean)\n .join(\", \");\n console.log(` ✓ ${r.table} (${scopes})`);\n }\n console.log();\n }\n\n if (withIssues.length > 0) {\n console.log(\"Tables denied to raw DB tools:\");\n for (const r of withIssues) {\n for (const issue of r.issues) {\n console.log(` ✗ ${r.table} — ${issue}`);\n }\n }\n console.log();\n console.log(\n `${withIssues.length} table(s) lack scoping columns. Raw db-* tools ` +\n `will fail closed for these tables; use scoped actions or add ` +\n `owner_email/org_id when raw DB access is intended.`,\n );\n process.exitCode = 1;\n } else {\n console.log(\"All template tables have proper scoping columns.\");\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../../src/scripts/db/exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAmlBH,wBAA8B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyJlE"}
|
package/dist/scripts/db/exec.js
CHANGED
|
@@ -320,7 +320,9 @@ function sqliteScopePredicate(tableName, scoping) {
|
|
|
320
320
|
else if (scoping.orgId && hasOrg) {
|
|
321
321
|
clauses.push(`org_id = '${escapeSqlString(scoping.orgId)}'`);
|
|
322
322
|
}
|
|
323
|
-
|
|
323
|
+
if (clauses.length > 0)
|
|
324
|
+
return clauses.join(" AND ");
|
|
325
|
+
return scoping.tablePredicates.get(tableName) ?? null;
|
|
324
326
|
}
|
|
325
327
|
function splitReturning(sql) {
|
|
326
328
|
const match = /\bRETURNING\b/i.exec(sql);
|
|
@@ -363,7 +365,10 @@ function qualifySqliteWrite(sql, scoping) {
|
|
|
363
365
|
return sql.replace(/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+(?:"([^"]+)"|'([^']+)'|(\w+))/i, (match, keyword, quotedDouble, quotedSingle, bare) => {
|
|
364
366
|
const tableName = quotedDouble ?? quotedSingle ?? bare;
|
|
365
367
|
if (!scoping.ownerEmailTables.has(tableName) &&
|
|
366
|
-
!scoping.orgIdTables.has(tableName)) {
|
|
368
|
+
!(scoping.orgId && scoping.orgIdTables.has(tableName))) {
|
|
369
|
+
if (scoping.tablePredicates.has(tableName)) {
|
|
370
|
+
throw new Error(`INSERT/REPLACE into "${tableName}" is not allowed through raw DB tools because the table does not have owner_email/org_id columns for automatic write scoping. Use a template action, or add scoped ownership columns and an additive migration.`);
|
|
371
|
+
}
|
|
367
372
|
return match;
|
|
368
373
|
}
|
|
369
374
|
return `${keyword} main."${tableName.replace(/"/g, '""')}"`;
|