@tangle-network/agent-app 0.49.33 → 0.49.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.
Files changed (76) hide show
  1. package/bin/tangle-agent/home-maintenance.py +280 -0
  2. package/bin/tangle-agent/home.mjs +166 -0
  3. package/bin/tangle-agent/provision.mjs +168 -0
  4. package/bin/tangle-agent/runtime.mjs +151 -0
  5. package/bin/tangle-agent.mjs +26 -0
  6. package/dist/VaultPane-2SJSP6TS.js +7 -0
  7. package/dist/app-auth/index.d.ts +14 -2
  8. package/dist/app-auth/index.js +3 -3
  9. package/dist/app-auth/index.js.map +1 -1
  10. package/dist/assistant/index.js +7 -7
  11. package/dist/chat-react/index.js +1 -1
  12. package/dist/chat-routes/index.js +4 -4
  13. package/dist/chat-store/index.js +2 -2
  14. package/dist/{chunk-FCUWRMAF.js → chunk-56SVXAIK.js} +14 -9
  15. package/dist/chunk-56SVXAIK.js.map +1 -0
  16. package/dist/chunk-6CV66YOB.js +99 -0
  17. package/dist/chunk-6CV66YOB.js.map +1 -0
  18. package/dist/{chunk-V4YGTOCS.js → chunk-MIFVOYZ7.js} +5 -5
  19. package/dist/{chunk-FXWNNHPC.js → chunk-MVIIH2EQ.js} +165 -72
  20. package/dist/chunk-MVIIH2EQ.js.map +1 -0
  21. package/dist/{chunk-TNWQA5XJ.js → chunk-NLNM5PP6.js} +5 -4
  22. package/dist/chunk-NLNM5PP6.js.map +1 -0
  23. package/dist/{chunk-4PZE7XAM.js → chunk-S3344RBY.js} +4 -4
  24. package/dist/{chunk-4S36HWE4.js → chunk-WAMJSZYB.js} +1 -1
  25. package/dist/{chunk-VIL6MHHQ.js → chunk-XTLYE2VO.js} +6 -6
  26. package/dist/design-canvas-react/index.js +1 -1
  27. package/dist/design-canvas-react/lazy.js +1 -1
  28. package/dist/hosted-agent/general-skill.d.ts +2 -0
  29. package/dist/hosted-agent/general.d.ts +53 -0
  30. package/dist/hosted-agent/index.d.ts +9 -1
  31. package/dist/hosted-agent/index.js +294 -7
  32. package/dist/hosted-agent/index.js.map +1 -1
  33. package/dist/hosted-agent/react/LineBilling.d.ts +3 -0
  34. package/dist/hosted-agent/react/LineMembers.d.ts +3 -0
  35. package/dist/hosted-agent/react/LineSetup.d.ts +3 -0
  36. package/dist/hosted-agent/react/contracts.d.ts +136 -0
  37. package/dist/hosted-agent/react/index.d.ts +5 -0
  38. package/dist/hosted-agent/react/index.js +573 -0
  39. package/dist/hosted-agent/react/index.js.map +1 -0
  40. package/dist/hosted-agent/react/lines.css +110 -0
  41. package/dist/hosted-agent/react/styles.d.ts +2 -0
  42. package/dist/hosted-agent/react/styles.js +1 -0
  43. package/dist/hosted-agent/react/styles.js.map +1 -0
  44. package/dist/hosted-agent/workflows.d.ts +17 -0
  45. package/dist/{mention-editor-4PYZJM5A.js → mention-editor-ELAINF6H.js} +3 -2
  46. package/dist/mention-editor-ELAINF6H.js.map +1 -0
  47. package/dist/platform/index.js +1 -1
  48. package/dist/platform/sso.d.ts +74 -7
  49. package/dist/profile/home.d.ts +23 -0
  50. package/dist/profile/index.d.ts +1 -0
  51. package/dist/profile/index.js +9 -1
  52. package/dist/profile/index.js.map +1 -1
  53. package/dist/public-consultation/index.js +4 -4
  54. package/dist/stories/hosted-agent/fixtures.d.ts +34 -0
  55. package/dist/teams/index.js +5 -5
  56. package/dist/teams/invitations-api.js +4 -4
  57. package/dist/theme/tokens.css +20 -3
  58. package/dist/vault/ConfirmDialog.d.ts +1 -1
  59. package/dist/vault/VaultPane.d.ts +2 -2
  60. package/dist/vault/contracts.d.ts +8 -0
  61. package/dist/vault/index.js +1 -1
  62. package/dist/vault/lazy.d.ts +3 -3
  63. package/dist/vault/lazy.js +1 -1
  64. package/dist/vault/lazy.js.map +1 -1
  65. package/dist/web-react/index.js +23 -23
  66. package/package.json +47 -14
  67. package/dist/VaultPane-32HVWZ6A.js +0 -7
  68. package/dist/chunk-FCUWRMAF.js.map +0 -1
  69. package/dist/chunk-FXWNNHPC.js.map +0 -1
  70. package/dist/chunk-TNWQA5XJ.js.map +0 -1
  71. package/dist/mention-editor-4PYZJM5A.js.map +0 -1
  72. /package/dist/{VaultPane-32HVWZ6A.js.map → VaultPane-2SJSP6TS.js.map} +0 -0
  73. /package/dist/{chunk-V4YGTOCS.js.map → chunk-MIFVOYZ7.js.map} +0 -0
  74. /package/dist/{chunk-4PZE7XAM.js.map → chunk-S3344RBY.js.map} +0 -0
  75. /package/dist/{chunk-4S36HWE4.js.map → chunk-WAMJSZYB.js.map} +0 -0
  76. /package/dist/{chunk-VIL6MHHQ.js.map → chunk-XTLYE2VO.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vault/VaultPane.tsx","../src/vault/ConfirmDialog.tsx"],"sourcesContent":["/**\n * The shared 3-pane vault: tree | artifact viewer | optional agent dock. This is\n * pure shell MECHANISM — selection, the dirty-guard + pending-nav state machine,\n * rich/source editor modes, create/delete/refresh, skeletons, and an error\n * boundary. It renders NO file tree and NO artifact viewer of its own:\n * those arrive through the `renderTree` / `renderArtifact` / `renderDock` seams,\n * so a product wires sandbox-ui's RichFileTree + FileArtifactPane in ~10 lines.\n *\n * Data flows exclusively through `port` (a `VaultDataPort`). The pane never\n * imports a fetch client, a router, a toast system, or a markdown library — the\n * optional `codec` seam supplies rich/source parsing (identity passthrough by\n * default). Chrome uses the shared theme tokens (bg-card, border-border, …).\n */\n\nimport {\n Component,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ErrorInfo,\n type MouseEvent,\n type ReactNode,\n} from 'react'\nimport { Download, Folder, Trash2 } from 'lucide-react'\nimport { ConfirmDialog } from './ConfirmDialog'\nimport type {\n VaultEditorMode,\n VaultFile,\n VaultMarkdownCodec,\n VaultOperation,\n VaultOperationFailure,\n VaultOperationPhase,\n VaultPaneHandle,\n VaultPaneProps,\n VaultRichParts,\n VaultTreeNode,\n} from './contracts'\n\nconst IDENTITY_CODEC: VaultMarkdownCodec = {\n parse: (raw) => raw,\n serialize: (parts) => (typeof parts === 'string' ? parts : String(parts ?? '')),\n}\n\ntype PendingNav = { type: 'open'; path: string } | { type: 'close' } | null\n\ninterface TreeRefreshContext {\n operation: Extract<VaultOperation, 'list' | 'create' | 'delete'>\n phase: VaultOperationPhase\n path?: string\n selectAfterRecovery?: string\n}\n\ninterface TreeFailureState {\n failure: VaultOperationFailure\n context: TreeRefreshContext\n}\n\nconst LIST_CONTEXT: TreeRefreshContext = { operation: 'list', phase: 'operation' }\n\nfunction operationMessage(error: unknown, fallback: string): string {\n return error instanceof Error && error.message ? error.message : fallback\n}\n\nfunction treeFailureMessage(failure: VaultOperationFailure): string {\n if (failure.phase !== 'post-mutation-refresh') return failure.message\n const completed = failure.operation === 'create' ? 'created' : 'deleted'\n return `The file was ${completed}, but the Vault couldn't refresh. ${failure.message}`\n}\n\ninterface TreePaths {\n files: Set<string>\n directories: Set<string>\n}\n\n// Directories are collected alongside files because a click on one has to be\n// RECOGNIZED to be answered — dropping them here is what made a folder row a\n// dead target: the path resolved to nothing and every downstream branch was\n// keyed on a file.\nfunction collectTreePaths(nodes: VaultTreeNode[], into: TreePaths): TreePaths {\n for (const node of nodes) {\n if (node.type === 'file') into.files.add(node.path)\n else into.directories.add(node.path)\n if (node.children) collectTreePaths(node.children, into)\n }\n return into\n}\n\nfunction resolveFilePath(rawPath: string, filePaths: Set<string>): string | null {\n if (filePaths.has(rawPath)) return rawPath\n const path = rawPath.replace(/^\\/+|\\/+$/g, '')\n return filePaths.has(path) ? path : null\n}\n\nfunction resolveTreePath(rawPath: string, paths: TreePaths): { path: string; type: 'file' | 'directory' } | null {\n const file = resolveFilePath(rawPath, paths.files)\n if (file) return { path: file, type: 'file' }\n if (paths.directories.has(rawPath)) return { path: rawPath, type: 'directory' }\n const trimmed = rawPath.replace(/^\\/+|\\/+$/g, '')\n return paths.directories.has(trimmed) ? { path: trimmed, type: 'directory' } : null\n}\n\nfunction findDirectory(nodes: VaultTreeNode[], path: string): VaultTreeNode | null {\n for (const node of nodes) {\n if (node.type === 'directory' && node.path === path) return node\n const found = node.children ? findDirectory(node.children, path) : null\n if (found) return found\n }\n return null\n}\n\n/** The clicked tree row, files and directories alike. The type travels with the\n * path so the caller routes on it instead of discarding everything that is not\n * a file. */\nfunction treeClickTarget(event: MouseEvent<HTMLElement>): { path: string; type: string } | null {\n const read = (el: HTMLElement) => {\n const path = el.dataset.itemPath\n return path ? { path, type: el.dataset.itemType ?? '' } : null\n }\n\n const path = event.nativeEvent.composedPath?.() ?? []\n for (const item of path) {\n if (!(item instanceof HTMLElement)) continue\n if (item.dataset.type !== 'item') continue\n return read(item)\n }\n\n const target = event.target instanceof HTMLElement\n ? event.target.closest('[data-type=\"item\"]')\n : null\n return target instanceof HTMLElement ? read(target) : null\n}\n\n// Case-insensitive name filter over the tree: files survive when their name\n// matches; a directory survives whole (with all its children) when its own name\n// matches, otherwise only when some descendant survives.\nfunction filterNodes(nodes: VaultTreeNode[], q: string): VaultTreeNode[] {\n const out: VaultTreeNode[] = []\n for (const node of nodes) {\n if (node.type === 'file') {\n if (node.name.toLowerCase().includes(q)) out.push(node)\n } else if (node.name.toLowerCase().includes(q)) {\n out.push(node)\n } else {\n const children = filterNodes(node.children ?? [], q)\n if (children.length > 0) out.push({ ...node, children })\n }\n }\n return out\n}\n\nclass EditorErrorBoundary extends Component<{ children: ReactNode; onReset?: () => void }, { error: unknown }> {\n state: { error: unknown } = { error: null }\n static getDerivedStateFromError(error: unknown) {\n return { error }\n }\n componentDidCatch(error: unknown, info: ErrorInfo) {\n console.error('Vault crashed:', error, info)\n }\n render() {\n if (this.state.error) {\n const msg = this.state.error instanceof Error\n ? this.state.error.message\n : typeof this.state.error === 'string'\n ? this.state.error\n : 'Something went wrong loading the vault'\n return (\n <div className=\"flex h-full flex-1 flex-col items-center justify-center p-8 text-center\">\n <h3 className=\"mb-1 text-sm font-medium text-foreground\">Vault failed to load</h3>\n <p className=\"mb-4 max-w-xs text-xs text-muted-foreground\">{String(msg)}</p>\n <button\n type=\"button\"\n onClick={() => { this.setState({ error: null }); this.props.onReset?.() }}\n className=\"inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Try again\n </button>\n </div>\n )\n }\n return this.props.children\n }\n}\n\n/**\n * A skeleton is a picture of a wait, and a picture is exactly what a screen\n * reader cannot see. `aria-hidden` on the shimmer bars is right — they carry no\n * information — but hiding them without saying anything else is what left the\n * whole load silent. The container announces the wait instead: `aria-busy` for\n * the state, a `status` live region so arrival is reported, and a real text\n * label, since a live region with only decorative children announces nothing.\n */\nfunction SkeletonRegion({ label, className, children }: { label: string; className: string; children: ReactNode }) {\n // The live region is a SIBLING of the shimmer, not a wrapper around it: the\n // shimmer container keeps its exact classes and its exact position in the\n // parent's layout, so adding the announcement cannot move a pixel. Wrapping\n // it would put a new box between `space-y-*` and the bars it spaces.\n return (\n <>\n <span role=\"status\" aria-live=\"polite\" aria-busy={true} className=\"sr-only\">\n {label}\n </span>\n <div className={className} aria-hidden=\"true\">\n {children}\n </div>\n </>\n )\n}\n\nfunction TreeSkeleton() {\n return (\n <SkeletonRegion label=\"Loading files…\" className=\"space-y-2 p-4\">\n {[32, 48, 40, 52].map((w, i) => (\n <div key={i} className=\"h-4 animate-pulse rounded bg-muted\" style={{ width: `${w * 4}px` }} />\n ))}\n </SkeletonRegion>\n )\n}\n\nfunction EditorSkeleton() {\n return (\n <SkeletonRegion label=\"Loading file…\" className=\"space-y-3 p-8\">\n <div className=\"h-5 w-1/2 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-full animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-3/4 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-2/3 animate-pulse rounded bg-muted\" />\n </SkeletonRegion>\n )\n}\n\nfunction ReadErrorState({ message, onRetry }: { message: string; onRetry: () => void }) {\n return (\n <div className=\"flex h-full flex-col items-center justify-center gap-3 p-8 text-center\">\n <div>\n <h3 className=\"text-sm font-medium text-foreground\">Couldn't open this file</h3>\n <p className=\"mt-1 max-w-md text-xs text-muted-foreground\">{message}</p>\n </div>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Retry\n </button>\n </div>\n )\n}\n\nfunction TreeErrorState({ message, onRetry }: { message: string; onRetry: () => void }) {\n return (\n <div role=\"alert\" className=\"flex h-full flex-col items-center justify-center gap-3 p-6 text-center\">\n <div>\n <h3 className=\"text-sm font-medium text-foreground\">Couldn't load the Vault</h3>\n <p className=\"mt-1 max-w-xs text-xs text-muted-foreground\">{message}</p>\n </div>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Retry\n </button>\n </div>\n )\n}\n\nfunction OperationErrorAlert({\n message,\n retryLabel,\n onRetry,\n onDismiss,\n}: {\n message: string\n retryLabel: string\n onRetry: () => void\n onDismiss: () => void\n}) {\n return (\n <div\n role=\"alert\"\n className=\"flex shrink-0 items-center justify-between gap-3 border-b border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive\"\n >\n <span className=\"min-w-0 flex-1\">{message}</span>\n <div className=\"flex shrink-0 items-center gap-3\">\n <button type=\"button\" aria-label={retryLabel} onClick={onRetry} className=\"font-medium underline-offset-2 hover:underline\">\n Retry\n </button>\n <button type=\"button\" onClick={onDismiss} className=\"underline-offset-2 hover:underline\">\n Dismiss\n </button>\n </div>\n </div>\n )\n}\n\n/**\n * Browse and edit files in the available pane width.\n * Below 45rem, Files and the selected document share one pane.\n * Switching panes keeps the editor mounted and preserves unsaved changes.\n */\nexport const VaultPane = forwardRef<VaultPaneHandle, VaultPaneProps>(function VaultPane(props, ref) {\n const {\n port,\n renderTree,\n renderArtifact,\n renderDock,\n canWrite = true,\n selectedPath: controlledPath,\n onSelectedPathChange,\n onOperationError,\n codec,\n className,\n dockToggle,\n refreshKey,\n headerActions,\n onDownloadFile,\n pathBarClassName,\n } = props\n\n const activeCodec = codec ?? IDENTITY_CODEC\n const controlled = controlledPath !== undefined\n const isMarkdownCapable = codec !== undefined\n // `false` → a persistent dock (no toggle, always open with the selected file).\n const persistentDock = dockToggle === false\n const dockToggleCfg = dockToggle ? dockToggle : { label: 'Discuss', disabledWhenDirty: true }\n\n const [tree, setTree] = useState<VaultTreeNode[]>([])\n const [treeLoading, setTreeLoading] = useState(true)\n const [treeLoaded, setTreeLoaded] = useState(false)\n const [treeError, setTreeError] = useState<TreeFailureState | null>(null)\n const [internalPath, setInternalPath] = useState<string | null>(null)\n const selectedPath = controlled ? (controlledPath ?? null) : internalPath\n const [filesOpen, setFilesOpen] = useState(false)\n const showFiles = filesOpen || !selectedPath\n const searchRef = useRef<HTMLInputElement>(null)\n const documentRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n setFilesOpen(false)\n }, [selectedPath])\n\n const showDocument = useCallback(() => {\n const focused = document.activeElement\n setFilesOpen(false)\n requestAnimationFrame(() => {\n if (document.activeElement === focused || document.activeElement === document.body) {\n documentRef.current?.focus({ preventScroll: true })\n }\n })\n }, [])\n\n const [selectedFile, setSelectedFile] = useState<VaultFile | null>(null)\n const [fileLoading, setFileLoading] = useState(false)\n const [readError, setReadError] = useState<string | null>(null)\n const [reloadNonce, setReloadNonce] = useState(0)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<VaultOperationFailure | null>(null)\n\n const [editorMode, setEditorMode] = useState<VaultEditorMode>('rich')\n const [richDraft, setRichDraft] = useState<VaultRichParts>('')\n const [sourceDraft, setSourceDraft] = useState('')\n const [isDirty, setIsDirty] = useState(false)\n\n const [createOpen, setCreateOpen] = useState(false)\n const [newPath, setNewPath] = useState('')\n const [creating, setCreating] = useState(false)\n const [createError, setCreateError] = useState<VaultOperationFailure | null>(null)\n const [deleteOpen, setDeleteOpen] = useState(false)\n const [deleting, setDeleting] = useState(false)\n const [deleteError, setDeleteError] = useState<VaultOperationFailure | null>(null)\n const [dockOpen, setDockOpen] = useState(false)\n const [pendingNav, setPendingNav] = useState<PendingNav>(null)\n const [query, setQuery] = useState('')\n const [folderPath, setFolderPath] = useState<string | null>(null)\n\n const dirtyRef = useRef(isDirty)\n dirtyRef.current = isDirty\n const treeRequestRef = useRef(0)\n const savedContentRef = useRef('')\n const loadedPathRef = useRef<string | null>(null)\n const onOperationErrorRef = useRef(onOperationError)\n onOperationErrorRef.current = onOperationError\n\n const treePaths = useMemo(\n () => collectTreePaths(tree, { files: new Set<string>(), directories: new Set<string>() }),\n [tree],\n )\n const resolvedSelectedPath = useMemo(() => {\n if (!selectedPath) return null\n const listed = resolveTreePath(selectedPath, treePaths)\n if (listed?.type === 'directory') return null\n // A committed file can be readable before an older tree snapshot lists it.\n // The data port owns validation and missing-file errors for explicit paths.\n return listed?.path ?? selectedPath\n }, [selectedPath, treePaths])\n // The clicked folder, resolved against the CURRENT tree — a folder that a\n // refresh removed stops being the active one on its own, so neither the\n // search scope nor the create target can point at a directory that is gone.\n const activeFolderNode = useMemo(\n () => (folderPath ? findDirectory(tree, folderPath) : null),\n [tree, folderPath],\n )\n const activeFolder = activeFolderNode?.path ?? null\n const treeRoot = useMemo<VaultTreeNode>(\n () => ({ name: 'Vault', path: '', type: 'directory', children: tree }),\n [tree],\n )\n // With no query the whole vault stays on screen: the tree renderer owns\n // expansion (both sandbox-ui trees keep it internal), so re-rooting on a\n // plain folder click would fight the expand the click already performs. The\n // folder scopes the SEARCH, which is where a narrowed list is what the reader\n // asked for.\n const visibleRoot = useMemo<VaultTreeNode>(() => {\n const q = query.trim().toLowerCase()\n if (!q) return treeRoot\n const base = activeFolderNode ?? treeRoot\n return { ...base, children: filterNodes(base.children ?? [], q) }\n }, [treeRoot, activeFolderNode, query])\n\n const commitPath = useCallback(\n (next: string | null) => {\n if (!controlled) setInternalPath(next)\n onSelectedPathChange?.(next)\n },\n [controlled, onSelectedPathChange],\n )\n\n const reportFailure = useCallback((\n operation: VaultOperation,\n phase: VaultOperationPhase,\n error: unknown,\n fallback: string,\n path?: string,\n ): VaultOperationFailure => {\n const failure: VaultOperationFailure = {\n operation,\n phase,\n path,\n message: operationMessage(error, fallback),\n cause: error,\n }\n try {\n onOperationErrorRef.current?.(failure)\n } catch (callbackError) {\n console.error('Vault onOperationError callback failed:', callbackError)\n }\n return failure\n }, [])\n\n const refresh = useCallback(async (context: TreeRefreshContext = LIST_CONTEXT): Promise<boolean> => {\n const request = ++treeRequestRef.current\n setTreeLoading(true)\n setTreeError(null)\n try {\n const nextTree = await port.listTree()\n if (request !== treeRequestRef.current) return false\n setTree(nextTree)\n setTreeLoaded(true)\n return true\n } catch (error) {\n if (request !== treeRequestRef.current) return false\n const failure = reportFailure(\n context.operation,\n context.phase,\n error,\n 'Failed to load the Vault',\n context.path,\n )\n setTreeError({ failure, context })\n return false\n } finally {\n if (request === treeRequestRef.current) setTreeLoading(false)\n }\n }, [port, reportFailure])\n\n useEffect(() => {\n setTree([])\n setTreeLoaded(false)\n setTreeError(null)\n setSelectedFile(null)\n }, [port])\n\n useEffect(() => {\n void refresh()\n return () => { treeRequestRef.current += 1 }\n }, [refresh, refreshKey])\n\n const retryTree = useCallback(async () => {\n if (!treeError) return\n const { context } = treeError\n const recovered = await refresh(context)\n if (recovered && context.selectAfterRecovery) commitPath(context.selectAfterRecovery)\n }, [treeError, refresh, commitPath])\n\n useEffect(() => {\n if (!selectedPath) {\n setSelectedFile(null)\n setFileLoading(false)\n setReadError(null)\n loadedPathRef.current = null\n return\n }\n if (treeLoading || !treeLoaded || isDirty || saving) return\n if (!resolvedSelectedPath) {\n commitPath(null)\n setSelectedFile(null)\n setFileLoading(false)\n setReadError(null)\n loadedPathRef.current = null\n return\n }\n let cancelled = false\n const path = resolvedSelectedPath\n if (path !== selectedPath) commitPath(path)\n setFileLoading(true)\n setReadError(null)\n void (async () => {\n try {\n const file = await port.readFile(path)\n if (!cancelled && !dirtyRef.current) setSelectedFile(file)\n } catch (err) {\n // Surface read failures instead of making them indistinguishable from\n // the intentionally empty \"no file selected\" state.\n if (!cancelled) {\n const failure = reportFailure('read', 'operation', err, 'Failed to read file', path)\n setSelectedFile((current) => current?.path === path ? current : null)\n setReadError(failure.message)\n }\n } finally {\n if (!cancelled) setFileLoading(false)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [port, selectedPath, resolvedSelectedPath, treeLoading, treeLoaded, isDirty, saving, reloadNonce, commitPath, reportFailure])\n\n useEffect(() => {\n if (!selectedFile) {\n loadedPathRef.current = null\n savedContentRef.current = ''\n setRichDraft('')\n setSourceDraft('')\n setEditorMode('rich')\n setIsDirty(false)\n setSaveError(null)\n return\n }\n const pathChanged = loadedPathRef.current !== selectedFile.path\n loadedPathRef.current = selectedFile.path\n savedContentRef.current = selectedFile.content\n setRichDraft(activeCodec.parse(selectedFile.content))\n setSourceDraft(selectedFile.content)\n if (pathChanged) setEditorMode('rich')\n setIsDirty(false)\n setSaveError(null)\n setDockOpen(false)\n }, [selectedFile?.path, selectedFile?.content, activeCodec])\n\n const guardedOpen = useCallback(\n (path: string) => {\n if (path === selectedPath) {\n showDocument()\n return\n }\n if (isDirty) {\n setPendingNav({ type: 'open', path })\n return\n }\n showDocument()\n commitPath(path)\n },\n [isDirty, selectedPath, commitPath, showDocument],\n )\n\n useImperativeHandle(ref, () => ({ openFile: guardedOpen }), [guardedOpen])\n\n // Clicking a folder makes it the vault's active folder: the search narrows to\n // it and a new file lands inside it. Clicking it again clears that — the same\n // row is the way back out, so the gesture is reversible where it was made.\n const toggleFolder = useCallback((path: string) => {\n setFolderPath((current) => (current === path ? null : path))\n }, [])\n\n // Some tree models keep their original selection callback while resetting\n // paths internally. Keep the callable stable, but have it execute the latest\n // path validation and dirty-guard logic.\n const selectFileRef = useRef<(path: string) => void>(() => {})\n selectFileRef.current = (rawPath: string) => {\n const target = resolveTreePath(rawPath, treePaths)\n if (!target) return\n if (target.type === 'file') {\n guardedOpen(target.path)\n return\n }\n toggleFolder(target.path)\n }\n const handleTreeSelect = useCallback((path: string) => selectFileRef.current(path), [])\n\n const guardedClose = useCallback(() => {\n if (isDirty) {\n setPendingNav({ type: 'close' })\n return\n }\n commitPath(null)\n setSelectedFile(null)\n }, [isDirty, commitPath])\n\n const confirmDiscard = useCallback(() => {\n const nav = pendingNav\n setPendingNav(null)\n setIsDirty(false)\n if (!nav) return\n if (nav.type === 'open') {\n showDocument()\n commitPath(nav.path)\n } else {\n commitPath(null)\n setSelectedFile(null)\n }\n }, [pendingNav, commitPath, showDocument])\n\n const showRichMode = useCallback(() => {\n setEditorMode((mode) => {\n if (mode === 'rich') return mode\n setRichDraft(activeCodec.parse(sourceDraft))\n setIsDirty(sourceDraft !== savedContentRef.current)\n return 'rich'\n })\n }, [activeCodec, sourceDraft])\n\n const showSourceMode = useCallback(() => {\n setEditorMode((mode) => {\n if (mode === 'source') return mode\n const content = isDirty ? activeCodec.serialize(richDraft) : savedContentRef.current\n setSourceDraft(content)\n setIsDirty(content !== savedContentRef.current)\n return 'source'\n })\n }, [activeCodec, isDirty, richDraft])\n\n const onSourceChange = useCallback((next: string) => {\n dirtyRef.current = next !== savedContentRef.current\n setSourceDraft(next)\n setIsDirty(dirtyRef.current)\n }, [])\n\n const onRichChange = useCallback((next: VaultRichParts) => {\n dirtyRef.current = activeCodec.serialize(next) !== savedContentRef.current\n setRichDraft(next)\n setIsDirty(dirtyRef.current)\n }, [activeCodec])\n\n const saveCurrent = useCallback(async () => {\n if (!selectedFile) return\n const content = editorMode === 'source' ? sourceDraft : activeCodec.serialize(richDraft)\n setSaving(true)\n setSaveError(null)\n try {\n await port.writeFile(selectedFile.path, content)\n savedContentRef.current = content\n setSelectedFile({ ...selectedFile, content })\n setSourceDraft(content)\n setRichDraft(activeCodec.parse(content))\n setIsDirty(false)\n } catch (error) {\n setSaveError(reportFailure('save', 'operation', error, 'Failed to save file', selectedFile.path))\n } finally {\n setSaving(false)\n }\n }, [selectedFile, editorMode, sourceDraft, richDraft, activeCodec, port, reportFailure])\n\n const handleCreate = useCallback(async () => {\n const trimmed = newPath.trim()\n // Same rule as the confirm button, enforced here too: the dialog can also be\n // confirmed by keyboard, and a directory path is not a file to create.\n if (!trimmed || !(trimmed.split('/').pop()?.trim())) return\n setCreating(true)\n setCreateError(null)\n try {\n const created = await port.createFile(trimmed)\n setCreateOpen(false)\n setNewPath('')\n const refreshed = await refresh({\n operation: 'create',\n phase: 'post-mutation-refresh',\n path: created,\n selectAfterRecovery: created,\n })\n if (refreshed) commitPath(created)\n } catch (error) {\n setCreateError(reportFailure('create', 'operation', error, 'Failed to create file', trimmed))\n } finally {\n setCreating(false)\n }\n }, [newPath, port, refresh, commitPath, reportFailure])\n\n const handleDelete = useCallback(async () => {\n if (!selectedFile) return\n const path = selectedFile.path\n setDeleting(true)\n setDeleteError(null)\n try {\n await port.deleteFile(path)\n setDeleteOpen(false)\n setIsDirty(false)\n commitPath(null)\n setSelectedFile(null)\n await refresh({ operation: 'delete', phase: 'post-mutation-refresh', path })\n } catch (error) {\n setDeleteError(reportFailure('delete', 'operation', error, 'Failed to delete file', path))\n } finally {\n setDeleting(false)\n }\n }, [selectedFile, port, refresh, commitPath, reportFailure])\n\n const createFileName = newPath.trim().split('/').pop()?.trim() ?? ''\n\n let treeContent: ReactNode\n if (!treeLoaded && (treeLoading || !treeError)) {\n treeContent = <TreeSkeleton />\n } else if (!treeLoaded && treeError) {\n treeContent = <TreeErrorState message={treeFailureMessage(treeError.failure)} onRetry={() => void retryTree()} />\n } else {\n treeContent = (\n <>\n {treeError && (\n <OperationErrorAlert\n message={treeFailureMessage(treeError.failure)}\n retryLabel=\"Retry vault refresh\"\n onRetry={() => void retryTree()}\n onDismiss={() => setTreeError(null)}\n />\n )}\n {renderTree({\n root: visibleRoot,\n selectedPath: resolvedSelectedPath ?? undefined,\n onSelect: handleTreeSelect,\n })}\n </>\n )\n }\n\n return (\n <EditorErrorBoundary onReset={() => { commitPath(null); setSelectedFile(null) }}>\n <div className={`flex min-h-0 min-w-0 flex-1 overflow-hidden ${className ?? ''}`}>\n <div className=\"@container/vault flex min-w-0 flex-1 flex-col\">\n <nav aria-label=\"Vault navigation\" className=\"flex shrink-0 items-center gap-1 border-b border-border p-2 @[45rem]/vault:hidden\">\n <button\n type=\"button\"\n aria-pressed={showFiles}\n onClick={() => {\n const focused = document.activeElement\n setFilesOpen(true)\n requestAnimationFrame(() => {\n if (document.activeElement === focused || document.activeElement === document.body) {\n searchRef.current?.focus()\n }\n })\n }}\n className={`shrink-0 rounded-md px-3 py-2 text-sm ${showFiles ? 'bg-muted font-medium text-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n Files\n </button>\n {selectedPath && (\n <button\n type=\"button\"\n aria-pressed={!showFiles}\n onClick={showDocument}\n title={selectedPath}\n className={`min-w-0 truncate rounded-md px-3 py-2 text-sm ${!showFiles ? 'bg-muted font-medium text-foreground' : 'text-muted-foreground hover:bg-muted'}`}\n >\n {selectedPath.split('/').pop()}\n </button>\n )}\n </nav>\n <div className=\"flex min-h-0 min-w-0 flex-1\">\n <div data-vault-tree className={`${showFiles ? 'flex' : 'hidden'} min-w-0 flex-1 flex-col border-r border-border bg-background @[45rem]/vault:flex @[45rem]/vault:w-[23rem] @[45rem]/vault:min-w-[23rem] @[45rem]/vault:flex-none`}>\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <input\n ref={searchRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={activeFolder ? `Search ${activeFolder}…` : 'Search…'}\n aria-label=\"Search vault\"\n className=\"h-8 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground\"\n />\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {headerActions}\n <button\n type=\"button\"\n aria-label=\"Refresh vault\"\n onClick={() => void refresh()}\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n ↻\n </button>\n {canWrite && (\n <button\n type=\"button\"\n aria-label={activeFolder ? `New vault file in ${activeFolder}` : 'New vault file'}\n onClick={() => { setCreateError(null); setNewPath(activeFolder ? `${activeFolder}/` : ''); setCreateOpen(true) }}\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary/90\"\n >\n +\n </button>\n )}\n </div>\n </div>\n {activeFolder && (\n <div className=\"flex items-center gap-1.5 border-b border-border bg-muted/40 px-4 py-1.5 text-xs\">\n <Folder className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span data-vault-folder className=\"min-w-0 flex-1 truncate font-medium text-foreground\" title={activeFolder}>\n {activeFolder}\n </span>\n <button\n type=\"button\"\n aria-label=\"Clear the active folder\"\n onClick={() => setFolderPath(null)}\n className=\"shrink-0 rounded px-1 text-muted-foreground underline-offset-2 transition-colors hover:text-foreground hover:underline\"\n >\n Clear\n </button>\n </div>\n )}\n <div\n className=\"flex-1 overflow-y-auto\"\n onClickCapture={(event) => {\n const target = treeClickTarget(event)\n if (target) handleTreeSelect(target.path)\n }}\n >\n {treeContent}\n </div>\n </div>\n\n <div\n ref={documentRef}\n role=\"region\"\n aria-label=\"Vault document\"\n tabIndex={-1}\n className={`${showFiles ? 'hidden' : 'flex'} min-w-0 flex-1 flex-col overflow-hidden @[45rem]/vault:flex`}\n >\n {selectedFile && (\n <div className={`flex shrink-0 items-center justify-between gap-2 border-b border-border px-4 py-1.5 ${pathBarClassName ?? 'bg-card'}`}>\n <span data-vault-path className=\"min-w-0 flex-1 truncate text-xs font-medium text-foreground\">{selectedFile.path}</span>\n <div className=\"flex shrink-0 items-center gap-1\">\n {canWrite && isMarkdownCapable && (\n <div className=\"mr-1 flex items-center gap-1\">\n <button\n type=\"button\"\n aria-label=\"Edit as rich text\"\n aria-pressed={editorMode === 'rich'}\n onClick={showRichMode}\n className={`inline-flex h-7 items-center rounded px-2 text-xs transition-colors ${\n editorMode === 'rich'\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n Rich\n </button>\n <button\n type=\"button\"\n aria-label=\"Edit as source\"\n aria-pressed={editorMode === 'source'}\n onClick={showSourceMode}\n className={`inline-flex h-7 items-center rounded px-2 text-xs transition-colors ${\n editorMode === 'source'\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n Source\n </button>\n </div>\n )}\n {renderDock && !persistentDock && (\n <button\n type=\"button\"\n aria-label={dockToggleCfg.label}\n aria-pressed={dockOpen}\n disabled={(dockToggleCfg.disabledWhenDirty ?? true) && isDirty}\n title={(dockToggleCfg.disabledWhenDirty ?? true) && isDirty ? 'Save your changes first' : (dockToggleCfg.title ?? dockToggleCfg.label)}\n onClick={() => setDockOpen((v) => !v)}\n className={`inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs transition-colors disabled:pointer-events-none disabled:opacity-40 ${\n dockOpen\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n {dockToggleCfg.label}\n </button>\n )}\n {onDownloadFile && (\n <button\n type=\"button\"\n aria-label=\"Download this file\"\n title=\"Download file\"\n onClick={() => onDownloadFile(selectedFile)}\n className=\"inline-flex h-7 w-7 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Download className=\"h-4 w-4\" />\n </button>\n )}\n {canWrite && (\n <button\n type=\"button\"\n aria-label=\"Delete this file\"\n title=\"Delete file\"\n onClick={() => { setDeleteError(null); setDeleteOpen(true) }}\n className=\"inline-flex h-7 w-7 items-center justify-center rounded text-destructive/70 transition-colors hover:bg-destructive/10 hover:text-destructive\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n </div>\n )}\n {selectedFile && saveError && (\n <OperationErrorAlert\n message={saveError.message}\n retryLabel=\"Retry save\"\n onRetry={() => void saveCurrent()}\n onDismiss={() => setSaveError(null)}\n />\n )}\n {readError && selectedFile?.path === resolvedSelectedPath && (\n <OperationErrorAlert\n message={readError}\n retryLabel=\"Retry file refresh\"\n onRetry={() => setReloadNonce((n) => n + 1)}\n onDismiss={() => setReadError(null)}\n />\n )}\n <div className=\"flex-1 overflow-hidden\">\n {fileLoading && selectedFile?.path !== resolvedSelectedPath ? (\n <EditorSkeleton />\n ) : readError && selectedFile?.path !== resolvedSelectedPath ? (\n <ReadErrorState message={readError} onRetry={() => setReloadNonce((n) => n + 1)} />\n ) : selectedFile && canWrite && isMarkdownCapable && editorMode === 'source' ? (\n <SourceEditor\n path={selectedFile.path}\n content={sourceDraft}\n saving={saving}\n dirty={isDirty}\n onChange={onSourceChange}\n onSave={() => void saveCurrent()}\n />\n ) : selectedFile ? (\n renderArtifact({\n file: selectedFile,\n loading: false,\n mode: editorMode,\n canWrite,\n richDraft,\n dirty: isDirty,\n onRichChange,\n onSave: () => void saveCurrent(),\n })\n ) : null}\n </div>\n </div>\n </div>\n </div>\n\n {renderDock && selectedFile && renderDock({\n file: selectedFile,\n open: persistentDock ? true : dockOpen,\n onClose: persistentDock ? () => {} : () => setDockOpen(false),\n })}\n\n <ConfirmDialog\n open={createOpen}\n title=\"Create vault file\"\n description={activeFolder ? `Add a new document to ${activeFolder}.` : 'Add a new document to this vault.'}\n confirmLabel={creating ? 'Creating…' : 'Create'}\n // A prefilled folder is a path with no file name yet, so emptiness is\n // not the test — `folder/` would otherwise be sent to the port as a\n // file to create.\n confirmDisabled={creating || !createFileName}\n onConfirm={() => void handleCreate()}\n onCancel={() => { setCreateOpen(false); setNewPath(''); setCreateError(null) }}\n >\n <div className=\"space-y-2\">\n <input\n value={newPath}\n autoFocus\n onChange={(e) => setNewPath(e.target.value)}\n placeholder=\"e.g. playbooks/new-strategy.md\"\n aria-label=\"New file path\"\n className=\"h-9 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground\"\n />\n {createError && <p role=\"alert\" className=\"text-xs text-destructive\">{createError.message}</p>}\n </div>\n </ConfirmDialog>\n\n <ConfirmDialog\n open={deleteOpen}\n title=\"Delete file?\"\n description={`This permanently removes ${selectedFile?.path ?? 'this file'} from the vault.`}\n confirmLabel={deleting ? 'Deleting…' : 'Delete file'}\n confirmDisabled={deleting}\n destructive\n onConfirm={() => void handleDelete()}\n onCancel={() => { setDeleteOpen(false); setDeleteError(null) }}\n >\n {deleteError && <p role=\"alert\" className=\"text-xs text-destructive\">{deleteError.message}</p>}\n </ConfirmDialog>\n\n <ConfirmDialog\n open={pendingNav !== null}\n title=\"Discard unsaved changes?\"\n description=\"Your edits to this document haven't been saved. Continue and lose them?\"\n confirmLabel=\"Discard changes\"\n destructive\n onConfirm={confirmDiscard}\n onCancel={() => setPendingNav(null)}\n />\n </div>\n </EditorErrorBoundary>\n )\n})\n\nfunction SourceEditor({\n path,\n content,\n saving,\n dirty,\n onChange,\n onSave,\n}: {\n path: string\n content: string\n saving: boolean\n dirty: boolean\n onChange: (content: string) => void\n onSave: () => void\n}) {\n return (\n <div className=\"flex h-full min-h-0 flex-col bg-background\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-4 py-2\">\n <p className=\"truncate font-mono text-xs text-muted-foreground\">{path}</p>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground\">\n {dirty ? 'Unsaved changes' : 'Saved'}\n </span>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving || !dirty}\n className=\"inline-flex h-7 items-center rounded-md bg-primary px-2 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\"\n >\n {saving ? 'Saving…' : 'Save'}\n </button>\n </div>\n </div>\n <textarea\n value={content}\n onChange={(event) => onChange(event.target.value)}\n spellCheck={false}\n aria-label=\"Source editor\"\n // Full-bleed editor: keep the floor's ring but draw it inside the border\n // box, since an outward ring on a `flex-1` pane child is clipped.\n className=\"min-h-0 flex-1 resize-none border-0 bg-background p-4 font-mono text-sm leading-6 text-foreground focus-visible:[outline-offset:-2px]\"\n />\n </div>\n )\n}\n","/**\n * Self-contained confirm dialog — no dialog library. A focus-trapped modal with\n * Escape-to-cancel and Enter from text inputs, used for create / delete / discard-unsaved\n * flows so the pane carries zero UI-kit dependency.\n */\n\nimport { useEffect, useRef, type ReactNode } from 'react'\n\nexport interface ConfirmDialogProps {\n open: boolean\n title: string\n description?: ReactNode\n confirmLabel?: string\n cancelLabel?: string\n /** Styles the confirm button as a destructive action. */\n destructive?: boolean\n /** Disables the confirm button (e.g. while the action is in flight). */\n confirmDisabled?: boolean\n onConfirm: () => void\n onCancel: () => void\n /** Optional body (e.g. an input field for the create flow). */\n children?: ReactNode\n}\n\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n destructive = false,\n confirmDisabled = false,\n onConfirm,\n onCancel,\n children,\n}: ConfirmDialogProps) {\n const panelRef = useRef<HTMLDivElement>(null)\n const confirmRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!open) return\n confirmRef.current?.focus()\n }, [open])\n\n if (!open) return null\n\n function onKeyDown(event: React.KeyboardEvent) {\n if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n return\n }\n const target = event.target\n if (\n event.key === 'Enter'\n && !event.defaultPrevented\n && !event.nativeEvent.isComposing\n && !confirmDisabled\n && target instanceof HTMLInputElement\n && ['text', 'search', 'email', 'url', 'tel', 'password', 'number'].includes(target.type)\n ) {\n event.preventDefault()\n onConfirm()\n return\n }\n if (event.key !== 'Tab') return\n const focusable = panelRef.current?.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n )\n if (!focusable || focusable.length === 0) return\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n const active = document.activeElement as HTMLElement | null\n if (event.shiftKey && active === first) {\n event.preventDefault()\n last?.focus()\n } else if (!event.shiftKey && active === last) {\n event.preventDefault()\n first?.focus()\n }\n }\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\"\n onMouseDown={(e) => { if (e.target === e.currentTarget) onCancel() }}\n >\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n onKeyDown={onKeyDown}\n className=\"w-full max-w-md rounded-lg border border-border bg-card p-5 shadow-lg\"\n >\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n {description && <p className=\"mt-1.5 text-xs text-muted-foreground\">{description}</p>}\n {children && <div className=\"mt-3\">{children}</div>}\n <div className=\"mt-5 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"inline-flex h-8 items-center rounded-md px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n {cancelLabel}\n </button>\n <button\n ref={confirmRef}\n type=\"button\"\n onClick={onConfirm}\n disabled={confirmDisabled}\n className={`inline-flex h-8 items-center rounded-md px-3 text-xs font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 ${\n destructive\n ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'\n : 'bg-primary text-primary-foreground hover:bg-primary/90'\n }`}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useRef","jsx","jsxs","path","VaultPane","useRef","useEffect"]}
@@ -0,0 +1,99 @@
1
+ // src/profile/home.ts
2
+ import { defineInlineResource, mergeAgentProfiles } from "@tangle-network/agent-interface";
3
+ var DEFAULT_HOME_LIMITS = {
4
+ "AGENTS.md": 12e3,
5
+ "SOUL.md": 8e3,
6
+ "IDENTITY.md": 4e3,
7
+ "USER.md": 4e3,
8
+ "MEMORY.md": 16e3,
9
+ "BOOTSTRAP.md": 8e3,
10
+ dailyNote: 12e3,
11
+ skill: 32e3
12
+ };
13
+ var DEFAULT_AGENT_HOME = "/var/lib/tangle-agent/home";
14
+ var AGENTS = `# Home
15
+
16
+ This is your durable home in your private Tangle computer. Inspect and use the computer.
17
+ The platform owns this file and the home writer. Your normal shell is not root.
18
+
19
+ ## Behavior
20
+ - The person's current request comes first.
21
+ - Reply in the person's language. Switch when they switch.
22
+ - In a group, reply only when mentioned or directly addressed.
23
+ - Be genuinely helpful, not performatively helpful. Be resourceful before asking.
24
+ - Have a point of view when useful, and distinguish evidence from uncertainty.
25
+ - Remember you are a guest in the person's or group's space.
26
+
27
+ ## Memory
28
+ - IDENTITY.md starts empty. Fill it during first run.
29
+ - USER.md holds dated preferences, at most 4,000 Unicode characters.
30
+ - MEMORY.md holds curated durable facts. Shared facts must be safe for the whole group.
31
+ - memory/YYYY-MM-DD.md holds daily notes; consolidate durable facts nightly.
32
+ - skills/<name>/SKILL.md holds reusable skills, not new authority.
33
+ - Never write credentials or private third-party records into memory.
34
+ - Use home_read, home_write, home_append, home_status and home_consolidate.
35
+ - The protected writer enforces file, character and byte budgets before writes and commits to git.
36
+ - Shell, code and ordinary project files remain available elsewhere in this sandbox.
37
+ - Do not work around a rejected home write by chmod, symlinks, another interpreter or another credential.
38
+
39
+ ## Self edits
40
+ SOUL.md is editable through home_write. Tell the owner what changed and why when ownerNoticeRequired is returned.
41
+ AGENTS.md is platform-owned and cannot be edited. Business rules belong in skills and presets.
42
+ `;
43
+ var SOUL = `# Soul
44
+
45
+ Be genuinely helpful, not performatively helpful.
46
+ Have opinions when judgment is useful.
47
+ Be resourceful before asking.
48
+ Remember you are a guest.
49
+ Prefer clear, direct answers over ceremony.
50
+ `;
51
+ var BOOTSTRAP = `# First run
52
+
53
+ The user's request always comes first. Do not delay the answer for setup.
54
+
55
+ After answering enough to be useful:
56
+ 1. Read AGENTS.md and SOUL.md.
57
+ 2. Fill empty IDENTITY.md through home_write using the configured identity, not an invented biography.
58
+ 3. Record any stated durable preference as a dated USER.md entry through home_write or home_append.
59
+ 4. Add a daily note only when there is something worth remembering.
60
+ 5. Call home_bootstrap. It removes this file and returns the git commit.
61
+ `;
62
+ function inline(path, content) {
63
+ return { path, resource: defineInlineResource(`default-home:${path}`, content) };
64
+ }
65
+ function defaultHomeFiles() {
66
+ return [
67
+ inline("AGENTS.md", AGENTS),
68
+ inline("SOUL.md", SOUL),
69
+ inline("IDENTITY.md", ""),
70
+ inline("USER.md", ""),
71
+ inline("MEMORY.md", ""),
72
+ inline("BOOTSTRAP.md", BOOTSTRAP)
73
+ ];
74
+ }
75
+ function withDefaultAgentHome(profile) {
76
+ const reserved = /* @__PURE__ */ new Set(["AGENTS.md", "SOUL.md", "IDENTITY.md", "USER.md", "MEMORY.md", "BOOTSTRAP.md", ".tangle/home.py"]);
77
+ for (const file of profile.resources?.files ?? []) {
78
+ const parts = file.path.replace(/\\/g, "/").split("/").filter((part) => part && part !== ".");
79
+ if (parts.includes("..")) throw new Error("Home-aware profiles require canonical file mount paths");
80
+ const path = parts.join("/");
81
+ if (reserved.has(path) || path === "var/lib/tangle-agent" || path.startsWith("var/lib/tangle-agent/") || path.startsWith("etc/tangle-agent/") || path === "usr/local/libexec/tangle-agent-home.py") {
82
+ throw new Error("Do not remount the protected home; use the home tools and business skills");
83
+ }
84
+ }
85
+ const merged = mergeAgentProfiles(profile, { prompt: { instructions: [
86
+ `Your git-tracked home is ${DEFAULT_AGENT_HOME}. Read AGENTS.md and SOUL.md there.`,
87
+ "Use the protected home tools for memory and bootstrap. Native shell remains available for ordinary code and project files. A missing protected-home installation is an infrastructure error, not permission to create an unprotected replacement."
88
+ ] } });
89
+ if (!merged) throw new Error("Home profile composition returned no profile");
90
+ return merged;
91
+ }
92
+
93
+ export {
94
+ DEFAULT_HOME_LIMITS,
95
+ DEFAULT_AGENT_HOME,
96
+ defaultHomeFiles,
97
+ withDefaultAgentHome
98
+ };
99
+ //# sourceMappingURL=chunk-6CV66YOB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/profile/home.ts"],"sourcesContent":["import type { AgentProfile, AgentProfileFileMount } from '@tangle-network/agent-interface'\nimport { defineInlineResource, mergeAgentProfiles } from '@tangle-network/agent-interface'\n\n/** Unicode code-point budgets. The installed writer also bounds UTF-8 bytes. */\nexport const DEFAULT_HOME_LIMITS = {\n 'AGENTS.md': 12_000,\n 'SOUL.md': 8_000,\n 'IDENTITY.md': 4_000,\n 'USER.md': 4_000,\n 'MEMORY.md': 16_000,\n 'BOOTSTRAP.md': 8_000,\n dailyNote: 12_000,\n skill: 32_000,\n} as const\n\nexport const DEFAULT_AGENT_HOME = '/var/lib/tangle-agent/home'\n\nconst AGENTS = `# Home\n\nThis is your durable home in your private Tangle computer. Inspect and use the computer.\nThe platform owns this file and the home writer. Your normal shell is not root.\n\n## Behavior\n- The person's current request comes first.\n- Reply in the person's language. Switch when they switch.\n- In a group, reply only when mentioned or directly addressed.\n- Be genuinely helpful, not performatively helpful. Be resourceful before asking.\n- Have a point of view when useful, and distinguish evidence from uncertainty.\n- Remember you are a guest in the person's or group's space.\n\n## Memory\n- IDENTITY.md starts empty. Fill it during first run.\n- USER.md holds dated preferences, at most 4,000 Unicode characters.\n- MEMORY.md holds curated durable facts. Shared facts must be safe for the whole group.\n- memory/YYYY-MM-DD.md holds daily notes; consolidate durable facts nightly.\n- skills/<name>/SKILL.md holds reusable skills, not new authority.\n- Never write credentials or private third-party records into memory.\n- Use home_read, home_write, home_append, home_status and home_consolidate.\n- The protected writer enforces file, character and byte budgets before writes and commits to git.\n- Shell, code and ordinary project files remain available elsewhere in this sandbox.\n- Do not work around a rejected home write by chmod, symlinks, another interpreter or another credential.\n\n## Self edits\nSOUL.md is editable through home_write. Tell the owner what changed and why when ownerNoticeRequired is returned.\nAGENTS.md is platform-owned and cannot be edited. Business rules belong in skills and presets.\n`\n\nconst SOUL = `# Soul\n\nBe genuinely helpful, not performatively helpful.\nHave opinions when judgment is useful.\nBe resourceful before asking.\nRemember you are a guest.\nPrefer clear, direct answers over ceremony.\n`\n\nconst BOOTSTRAP = `# First run\n\nThe user's request always comes first. Do not delay the answer for setup.\n\nAfter answering enough to be useful:\n1. Read AGENTS.md and SOUL.md.\n2. Fill empty IDENTITY.md through home_write using the configured identity, not an invented biography.\n3. Record any stated durable preference as a dated USER.md entry through home_write or home_append.\n4. Add a daily note only when there is something worth remembering.\n5. Call home_bootstrap. It removes this file and returns the git commit.\n`\n\nfunction inline(path: string, content: string): AgentProfileFileMount {\n return { path, resource: defineInlineResource(`default-home:${path}`, content) }\n}\n\n/** Canonical seed records. The trusted image installer materializes these once. */\nexport function defaultHomeFiles(): AgentProfileFileMount[] {\n return [\n inline('AGENTS.md', AGENTS), inline('SOUL.md', SOUL),\n inline('IDENTITY.md', ''), inline('USER.md', ''), inline('MEMORY.md', ''),\n inline('BOOTSTRAP.md', BOOTSTRAP),\n ]\n}\n\n/**\n * Compose home instructions, not per-turn writes to persistent memory.\n * The published install-home command consumes defaultHomeFiles with the same\n * inline resource format as other profile seeds. A privileged, one-time phase\n * is necessary: ordinary resources.files writes run as the agent and cannot\n * make a same-UID shell respect caps or platform file ownership.\n */\nexport function withDefaultAgentHome(profile: AgentProfile): AgentProfile {\n const reserved = new Set(['AGENTS.md', 'SOUL.md', 'IDENTITY.md', 'USER.md', 'MEMORY.md', 'BOOTSTRAP.md', '.tangle/home.py'])\n for (const file of profile.resources?.files ?? []) {\n const parts = file.path.replace(/\\\\/g, '/').split('/').filter(part => part && part !== '.')\n if (parts.includes('..')) throw new Error('Home-aware profiles require canonical file mount paths')\n const path = parts.join('/')\n if (reserved.has(path) || path === 'var/lib/tangle-agent' || path.startsWith('var/lib/tangle-agent/') ||\n path.startsWith('etc/tangle-agent/') || path === 'usr/local/libexec/tangle-agent-home.py') {\n throw new Error('Do not remount the protected home; use the home tools and business skills')\n }\n }\n const merged = mergeAgentProfiles(profile, { prompt: { instructions: [\n `Your git-tracked home is ${DEFAULT_AGENT_HOME}. Read AGENTS.md and SOUL.md there.`,\n 'Use the protected home tools for memory and bootstrap. Native shell remains available for ordinary code and project files. A missing protected-home installation is an infrastructure error, not permission to create an unprotected replacement.',\n ] } })\n if (!merged) throw new Error('Home profile composition returned no profile')\n return merged\n}\n"],"mappings":";AACA,SAAS,sBAAsB,0BAA0B;AAGlD,IAAM,sBAAsB;AAAA,EACjC,aAAa;AAAA,EACb,WAAW;AAAA,EACX,eAAe;AAAA,EACf,WAAW;AAAA,EACX,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,OAAO;AACT;AAEO,IAAM,qBAAqB;AAElC,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8Bf,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASb,IAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYlB,SAAS,OAAO,MAAc,SAAwC;AACpE,SAAO,EAAE,MAAM,UAAU,qBAAqB,gBAAgB,IAAI,IAAI,OAAO,EAAE;AACjF;AAGO,SAAS,mBAA4C;AAC1D,SAAO;AAAA,IACL,OAAO,aAAa,MAAM;AAAA,IAAG,OAAO,WAAW,IAAI;AAAA,IACnD,OAAO,eAAe,EAAE;AAAA,IAAG,OAAO,WAAW,EAAE;AAAA,IAAG,OAAO,aAAa,EAAE;AAAA,IACxE,OAAO,gBAAgB,SAAS;AAAA,EAClC;AACF;AASO,SAAS,qBAAqB,SAAqC;AACxE,QAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,WAAW,eAAe,WAAW,aAAa,gBAAgB,iBAAiB,CAAC;AAC3H,aAAW,QAAQ,QAAQ,WAAW,SAAS,CAAC,GAAG;AACjD,UAAM,QAAQ,KAAK,KAAK,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,OAAO,UAAQ,QAAQ,SAAS,GAAG;AAC1F,QAAI,MAAM,SAAS,IAAI,EAAG,OAAM,IAAI,MAAM,wDAAwD;AAClG,UAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,QAAI,SAAS,IAAI,IAAI,KAAK,SAAS,0BAA0B,KAAK,WAAW,uBAAuB,KAChG,KAAK,WAAW,mBAAmB,KAAK,SAAS,0CAA0C;AAC7F,YAAM,IAAI,MAAM,2EAA2E;AAAA,IAC7F;AAAA,EACF;AACA,QAAM,SAAS,mBAAmB,SAAS,EAAE,QAAQ,EAAE,cAAc;AAAA,IACnE,4BAA4B,kBAAkB;AAAA,IAC9C;AAAA,EACF,EAAE,EAAE,CAAC;AACL,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,8CAA8C;AAC3E,SAAO;AACT;","names":[]}
@@ -1,13 +1,13 @@
1
- import {
2
- createInteractionAnswerRoute
3
- } from "./chunk-ZSSCVT6H.js";
4
1
  import {
5
2
  parseJsonObjectBody
6
3
  } from "./chunk-EA4UVS4T.js";
4
+ import {
5
+ createInteractionAnswerRoute
6
+ } from "./chunk-ZSSCVT6H.js";
7
7
  import {
8
8
  mentionInputToPart,
9
9
  toChatMessageParts
10
- } from "./chunk-4PZE7XAM.js";
10
+ } from "./chunk-S3344RBY.js";
11
11
  import {
12
12
  ChatTurnInputError,
13
13
  assertPromptPartsWithinCap,
@@ -1078,4 +1078,4 @@ export {
1078
1078
  createChatTurnRoutes,
1079
1079
  streamChatRouteAsSandboxEvents
1080
1080
  };
1081
- //# sourceMappingURL=chunk-V4YGTOCS.js.map
1081
+ //# sourceMappingURL=chunk-MIFVOYZ7.js.map
@@ -48,14 +48,15 @@ async function verifySignedSsoState(state, config) {
48
48
  const parts = state.split(".");
49
49
  if (parts.length !== 3) return false;
50
50
  const [random, timestamp, mac] = parts;
51
- if (!random || !timestamp || !mac) return false;
51
+ if (!random || !timestamp || !mac || !/^[0-9a-z]+$/.test(timestamp)) return false;
52
52
  const expected = await hmacHex(config.secret, `${random}.${timestamp}`);
53
53
  if (!constantTimeEqual(mac, expected)) return false;
54
54
  const mintedAt = parseInt(timestamp, 36);
55
55
  if (!Number.isFinite(mintedAt)) return false;
56
56
  const now = config.now ?? Date.now;
57
57
  const ttlMs = config.ttlMs ?? DEFAULT_STATE_TTL_SECONDS * 1e3;
58
- return now() - mintedAt <= ttlMs;
58
+ const age = now() - mintedAt;
59
+ return age >= 0 && age <= ttlMs;
59
60
  }
60
61
  function normalizeTangleSsoEmail(email) {
61
62
  return email.trim().toLowerCase();
@@ -176,27 +177,46 @@ function resolveOnReferenceOrigin(value) {
176
177
  }
177
178
  function redirectResponse(location, headers = new Headers()) {
178
179
  headers.set("Location", location);
180
+ headers.set("Cache-Control", "no-store");
181
+ headers.set("Referrer-Policy", "no-referrer");
179
182
  return new Response(null, { status: 302, headers });
180
183
  }
181
184
  function clientIp(request) {
182
185
  return request.headers.get("CF-Connecting-IP") ?? request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? null;
183
186
  }
184
187
  function parseStateCookiePayload(raw) {
185
- if (!raw) return null;
188
+ if (!raw || raw.length > 4096) return null;
186
189
  try {
187
190
  const parsed = JSON.parse(raw);
188
191
  if (parsed === null || typeof parsed !== "object") return null;
189
- const { s, r } = parsed;
192
+ const { s, r, v, m } = parsed;
190
193
  if (typeof s !== "string" || typeof r !== "string") return null;
191
- return { s, r };
194
+ return { s, r, ...typeof v === "string" ? { v } : {}, ...typeof m === "string" ? { m } : {} };
192
195
  } catch {
193
196
  return null;
194
197
  }
195
198
  }
199
+ function stateBinding(payload, callbackUrl) {
200
+ return JSON.stringify([payload.s, payload.r, payload.v ?? null, callbackUrl]);
201
+ }
196
202
  function createTangleSsoHandlers(opts) {
197
203
  if (!opts.stateSecret) throw new Error("TangleSsoHandlerOptions.stateSecret is required");
198
204
  if (!opts.callbackUrl) throw new Error("TangleSsoHandlerOptions.callbackUrl is required");
199
205
  if (!opts.stateCookieName) throw new Error("TangleSsoHandlerOptions.stateCookieName is required");
206
+ const callbackUrl = new URL(opts.callbackUrl);
207
+ if (opts.protocol === "oidc") {
208
+ if (opts.stateSecret.length < 32) throw new Error("OIDC stateSecret must contain at least 32 characters");
209
+ if (callbackUrl.username || callbackUrl.password || callbackUrl.hash || callbackUrl.search) {
210
+ throw new Error("OIDC callbackUrl must be fixed and have no credentials, query, or fragment");
211
+ }
212
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(callbackUrl.hostname);
213
+ if (callbackUrl.protocol !== "https:" && !(callbackUrl.protocol === "http:" && loopback)) {
214
+ throw new Error("OIDC requires HTTPS except on loopback");
215
+ }
216
+ if (callbackUrl.protocol === "https:" && !opts.secureCookies) {
217
+ throw new Error("OIDC HTTPS callbacks require Secure cookies");
218
+ }
219
+ }
200
220
  const sessionCookieName = opts.sessionCookieName ?? DEFAULT_SESSION_COOKIE;
201
221
  let mintSessionCookies;
202
222
  if (opts.setSessionCookie) {
@@ -212,14 +232,15 @@ function createTangleSsoHandlers(opts) {
212
232
  })
213
233
  ];
214
234
  } else {
215
- throw new Error(
216
- "TangleSsoHandlerOptions requires setSessionCookie or sessionCookieSecret: better-auth only accepts HMAC-signed (and, on https, __Secure--prefixed) session cookies, so an unsigned default would mint sessions that read back null"
217
- );
235
+ throw new Error("TangleSsoHandlerOptions requires setSessionCookie or sessionCookieSecret");
218
236
  }
219
237
  const sessionTtlSeconds = opts.sessionTtlSeconds ?? DEFAULT_SESSION_TTL_SECONDS;
220
238
  const stateTtlSeconds = opts.stateTtlSeconds ?? DEFAULT_STATE_TTL_SECONDS;
221
- const defaultRedirectPath = opts.defaultRedirectPath ?? DEFAULT_REDIRECT_PATH;
222
- const loginPath = opts.loginPath ?? DEFAULT_LOGIN_PATH;
239
+ if (!Number.isFinite(stateTtlSeconds) || stateTtlSeconds <= 0 || !Number.isFinite(sessionTtlSeconds) || sessionTtlSeconds <= 0) {
240
+ throw new Error("SSO lifetimes must be positive and finite");
241
+ }
242
+ const defaultRedirectPath = sanitizeRedirectPath(opts.defaultRedirectPath ?? DEFAULT_REDIRECT_PATH, DEFAULT_REDIRECT_PATH);
243
+ const loginPath = sanitizeRedirectPath(opts.loginPath ?? DEFAULT_LOGIN_PATH, DEFAULT_LOGIN_PATH);
223
244
  const log = opts.log ?? (() => {
224
245
  });
225
246
  const now = opts.now ?? Date.now;
@@ -228,91 +249,163 @@ function createTangleSsoHandlers(opts) {
228
249
  function loginErrorRedirect(code) {
229
250
  const headers = new Headers();
230
251
  headers.append("Set-Cookie", clearCookieHeader(stateCookieOpts));
231
- return redirectResponse(`${loginPath}?error=${code}`, headers);
252
+ const target = new URL(loginPath, REDIRECT_REFERENCE_ORIGIN);
253
+ target.searchParams.set("error", code);
254
+ return redirectResponse(`${target.pathname}${target.search}${target.hash}`, headers);
232
255
  }
233
256
  return {
234
257
  async start(request) {
258
+ if (request.method !== "GET") return new Response(null, { status: 405, headers: { Allow: "GET" } });
235
259
  const url = new URL(request.url);
260
+ if (opts.protocol === "oidc" && url.origin !== callbackUrl.origin) {
261
+ return loginErrorRedirect("tangle_origin_mismatch");
262
+ }
236
263
  const redirectPath = sanitizeRedirectPath(url.searchParams.get("redirect"), defaultRedirectPath);
237
264
  const state = await createSignedSsoState(stateConfig);
238
- const cookie = serializeCookie(JSON.stringify({ s: state, r: redirectPath }), {
265
+ const payload = { s: state, r: redirectPath };
266
+ let authorizationUrl;
267
+ if (opts.protocol === "oidc") {
268
+ const pkce = await opts.createPkcePair();
269
+ payload.v = pkce.verifier;
270
+ payload.m = await hmacHex(opts.stateSecret, stateBinding(payload, opts.callbackUrl));
271
+ authorizationUrl = opts.auth.authorizeUrl({ state, codeChallenge: pkce.challenge });
272
+ if (new URL(authorizationUrl).searchParams.get("redirect_uri") !== opts.callbackUrl) {
273
+ throw new Error("PlatformOidcClient.redirectUri must equal callbackUrl");
274
+ }
275
+ } else {
276
+ authorizationUrl = opts.auth.authorizeUrl({ state, redirectUri: opts.callbackUrl });
277
+ }
278
+ const cookie = serializeCookie(JSON.stringify(payload), {
239
279
  ...stateCookieOpts,
240
280
  maxAgeSeconds: stateTtlSeconds
241
281
  });
242
282
  const headers = new Headers();
243
283
  headers.append("Set-Cookie", cookie);
244
- return redirectResponse(opts.auth.authorizeUrl({ state, redirectUri: opts.callbackUrl }), headers);
284
+ return redirectResponse(authorizationUrl, headers);
245
285
  },
246
286
  async callback(request) {
287
+ if (request.method !== "GET") return new Response(null, { status: 405, headers: { Allow: "GET" } });
247
288
  const url = new URL(request.url);
289
+ if (opts.protocol === "oidc" && (url.origin !== callbackUrl.origin || url.pathname !== callbackUrl.pathname)) {
290
+ return loginErrorRedirect("tangle_origin_mismatch");
291
+ }
248
292
  const code = url.searchParams.get("code");
249
293
  const stateFromPlatform = url.searchParams.get("state");
250
- if (!code || !stateFromPlatform) return loginErrorRedirect("tangle_callback_missing");
294
+ if (!code || !stateFromPlatform || url.searchParams.has("error")) return loginErrorRedirect("tangle_callback_missing");
251
295
  const payload = parseStateCookiePayload(readCookieValue(request.headers.get("cookie"), opts.stateCookieName));
252
- if (!payload || payload.s !== stateFromPlatform) return loginErrorRedirect("tangle_state_mismatch");
296
+ if (!payload || !constantTimeEqual(payload.s, stateFromPlatform)) return loginErrorRedirect("tangle_state_mismatch");
253
297
  if (!await verifySignedSsoState(payload.s, stateConfig)) return loginErrorRedirect("tangle_state_mismatch");
254
- let exchanged;
255
- try {
256
- exchanged = await opts.auth.exchange(code);
257
- } catch (err) {
258
- log("[tangle-sso] exchange failed", err);
259
- return loginErrorRedirect("tangle_exchange_failed");
298
+ if (opts.protocol === "oidc" && (!payload.v || !/^[A-Za-z0-9_-]{43,128}$/.test(payload.v) || !payload.m || !constantTimeEqual(payload.m, await hmacHex(opts.stateSecret, stateBinding(payload, opts.callbackUrl))))) {
299
+ return loginErrorRedirect("tangle_state_mismatch");
260
300
  }
261
- if (exchanged.emailVerified !== true) {
262
- log("[tangle-sso] exchange did not include a verified-email proof");
263
- return loginErrorRedirect("tangle_exchange_failed");
264
- }
265
- let userId;
301
+ const tokenRequestedAt = now();
302
+ let oidcTokens;
303
+ let legacyExchange;
304
+ let createdSession;
305
+ let committed = false;
266
306
  try {
267
- const email = normalizeTangleSsoEmail(exchanged.user.email);
268
- const resolution = await opts.store.resolveAccount({
269
- email,
270
- platformUserId: exchanged.user.id
271
- });
272
- if (resolution.kind === "reject") {
273
- throw new TangleSsoAccountConflictError(resolution.reason);
307
+ let exchanged;
308
+ try {
309
+ if (opts.protocol === "oidc") {
310
+ const result = await opts.auth.exchange(code, payload.v);
311
+ oidcTokens = result.tokens;
312
+ if (!oidcTokens.refreshToken || !Number.isFinite(oidcTokens.expiresIn) || oidcTokens.expiresIn <= 0) {
313
+ throw new Error("OIDC requires offline_access and a finite access-token lifetime");
314
+ }
315
+ exchanged = { user: result.user, emailVerified: result.user.emailVerified };
316
+ } else {
317
+ legacyExchange = await opts.auth.exchange(code);
318
+ exchanged = legacyExchange;
319
+ }
320
+ } catch {
321
+ log("[tangle-sso] exchange failed");
322
+ return loginErrorRedirect("tangle_exchange_failed");
323
+ }
324
+ if (exchanged.emailVerified !== true) {
325
+ log("[tangle-sso] exchange did not include a verified-email proof");
326
+ return loginErrorRedirect("tangle_exchange_failed");
274
327
  }
275
- ;
276
- ({ userId } = await opts.store.upsertUserByEmail({
277
- email,
278
- name: exchanged.user.name ?? null,
328
+ let userId;
329
+ try {
330
+ const email = normalizeTangleSsoEmail(exchanged.user.email);
331
+ const resolution = await opts.store.resolveAccount({ email, platformUserId: exchanged.user.id });
332
+ if (resolution.kind === "reject") throw new TangleSsoAccountConflictError(resolution.reason);
333
+ ({ userId } = await opts.store.upsertUserByEmail({
334
+ email,
335
+ name: exchanged.user.name ?? null,
336
+ tangleUserId: exchanged.user.id,
337
+ resolution
338
+ }));
339
+ } catch (err) {
340
+ if (err instanceof TangleSsoAccountConflictError) {
341
+ log("[tangle-sso] account linking conflict", err.reason);
342
+ return loginErrorRedirect("tangle_account_conflict");
343
+ }
344
+ if (err instanceof TangleSsoUserCreateError) return loginErrorRedirect("tangle_user_create_failed");
345
+ throw err;
346
+ }
347
+ const expiresAt = new Date(now() + sessionTtlSeconds * 1e3);
348
+ const { token } = await opts.store.createSession({
349
+ userId,
350
+ expiresAt,
351
+ ipAddress: clientIp(request),
352
+ userAgent: request.headers.get("user-agent")
353
+ });
354
+ createdSession = token;
355
+ const link = {
356
+ userId,
357
+ sessionToken: token,
279
358
  tangleUserId: exchanged.user.id,
280
- resolution
281
- }));
282
- } catch (err) {
283
- if (err instanceof TangleSsoAccountConflictError) {
284
- log("[tangle-sso] account linking conflict", err);
285
- return loginErrorRedirect("tangle_account_conflict");
359
+ email: normalizeTangleSsoEmail(exchanged.user.email),
360
+ name: exchanged.user.name ?? null
361
+ };
362
+ if (opts.protocol === "oidc") {
363
+ const headers2 = new Headers();
364
+ headers2.append("Set-Cookie", clearCookieHeader(stateCookieOpts));
365
+ const sessionCookies2 = await mintSessionCookies({ token, expiresAt, ttlSeconds: sessionTtlSeconds, secure: opts.secureCookies });
366
+ for (const cookie of sessionCookies2) headers2.append("Set-Cookie", cookie);
367
+ const response = redirectResponse(sanitizeRedirectPath(payload.r, defaultRedirectPath), headers2);
368
+ await opts.store.saveTangleLink({
369
+ ...link,
370
+ tokens: oidcTokens,
371
+ accessTokenExpiresAt: new Date(tokenRequestedAt + oidcTokens.expiresIn * 1e3)
372
+ });
373
+ committed = true;
374
+ return response;
375
+ }
376
+ await opts.store.saveTangleLink({ ...link, apiKey: legacyExchange.apiKey, planTier: legacyExchange.plan?.tier ?? null });
377
+ const headers = new Headers();
378
+ headers.append("Set-Cookie", clearCookieHeader(stateCookieOpts));
379
+ const sessionCookies = await mintSessionCookies({ token, expiresAt, ttlSeconds: sessionTtlSeconds, secure: opts.secureCookies });
380
+ for (const cookie of sessionCookies) headers.append("Set-Cookie", cookie);
381
+ return redirectResponse(sanitizeRedirectPath(payload.r, defaultRedirectPath), headers);
382
+ } catch (error) {
383
+ if (opts.protocol !== "oidc") throw error;
384
+ log("[tangle-sso] local session persistence failed");
385
+ return loginErrorRedirect("tangle_session_failed");
386
+ } finally {
387
+ if (opts.protocol === "oidc" && !committed) {
388
+ if (createdSession) {
389
+ try {
390
+ await opts.store.deleteSession({ sessionToken: createdSession });
391
+ } catch {
392
+ log("[tangle-sso] unpublished session cleanup failed");
393
+ }
394
+ }
395
+ if (oidcTokens) {
396
+ try {
397
+ if (oidcTokens.refreshToken) await opts.auth.revoke(oidcTokens.refreshToken, "refresh_token");
398
+ } catch {
399
+ log("[tangle-sso] rejected grant refresh revocation failed");
400
+ }
401
+ try {
402
+ await opts.auth.revoke(oidcTokens.accessToken, "access_token");
403
+ } catch {
404
+ log("[tangle-sso] rejected grant access revocation failed");
405
+ }
406
+ }
286
407
  }
287
- if (err instanceof TangleSsoUserCreateError) return loginErrorRedirect("tangle_user_create_failed");
288
- throw err;
289
408
  }
290
- const expiresAt = new Date(now() + sessionTtlSeconds * 1e3);
291
- const { token } = await opts.store.createSession({
292
- userId,
293
- expiresAt,
294
- ipAddress: clientIp(request),
295
- userAgent: request.headers.get("user-agent")
296
- });
297
- await opts.store.saveTangleLink({
298
- userId,
299
- sessionToken: token,
300
- tangleUserId: exchanged.user.id,
301
- email: normalizeTangleSsoEmail(exchanged.user.email),
302
- name: exchanged.user.name ?? null,
303
- apiKey: exchanged.apiKey,
304
- planTier: exchanged.plan?.tier ?? null
305
- });
306
- const headers = new Headers();
307
- headers.append("Set-Cookie", clearCookieHeader(stateCookieOpts));
308
- const sessionCookies = await mintSessionCookies({
309
- token,
310
- expiresAt,
311
- ttlSeconds: sessionTtlSeconds,
312
- secure: opts.secureCookies
313
- });
314
- for (const cookie of sessionCookies) headers.append("Set-Cookie", cookie);
315
- return redirectResponse(sanitizeRedirectPath(payload.r, defaultRedirectPath), headers);
316
409
  }
317
410
  };
318
411
  }
@@ -387,4 +480,4 @@ export {
387
480
  createAdminGuard,
388
481
  assertBillableBalance
389
482
  };
390
- //# sourceMappingURL=chunk-FXWNNHPC.js.map
483
+ //# sourceMappingURL=chunk-MVIIH2EQ.js.map