@tangle-network/agent-app 0.49.32 → 0.49.34
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/bin/tangle-agent/home-maintenance.py +280 -0
- package/bin/tangle-agent/home.mjs +166 -0
- package/bin/tangle-agent/provision.mjs +168 -0
- package/bin/tangle-agent/runtime.mjs +151 -0
- package/bin/tangle-agent.mjs +26 -0
- package/dist/VaultPane-2SJSP6TS.js +7 -0
- package/dist/app-auth/index.d.ts +14 -2
- package/dist/app-auth/index.js +3 -3
- package/dist/app-auth/index.js.map +1 -1
- package/dist/assistant/index.js +6 -6
- package/dist/chat-routes/index.js +4 -4
- package/dist/chat-store/index.js +2 -2
- package/dist/{chunk-FCUWRMAF.js → chunk-56SVXAIK.js} +14 -9
- package/dist/chunk-56SVXAIK.js.map +1 -0
- package/dist/chunk-6CV66YOB.js +99 -0
- package/dist/chunk-6CV66YOB.js.map +1 -0
- package/dist/{chunk-V4YGTOCS.js → chunk-MIFVOYZ7.js} +5 -5
- package/dist/{chunk-FXWNNHPC.js → chunk-MVIIH2EQ.js} +165 -72
- package/dist/chunk-MVIIH2EQ.js.map +1 -0
- package/dist/{chunk-4PZE7XAM.js → chunk-S3344RBY.js} +4 -4
- package/dist/{chunk-4S36HWE4.js → chunk-WAMJSZYB.js} +1 -1
- package/dist/{chunk-VIL6MHHQ.js → chunk-XTLYE2VO.js} +6 -6
- package/dist/design-canvas-react/index.js +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/hosted-agent/general-skill.d.ts +2 -0
- package/dist/hosted-agent/general.d.ts +53 -0
- package/dist/hosted-agent/index.d.ts +9 -1
- package/dist/hosted-agent/index.js +294 -7
- package/dist/hosted-agent/index.js.map +1 -1
- package/dist/hosted-agent/react/LineBilling.d.ts +3 -0
- package/dist/hosted-agent/react/LineMembers.d.ts +3 -0
- package/dist/hosted-agent/react/LineSetup.d.ts +3 -0
- package/dist/hosted-agent/react/contracts.d.ts +136 -0
- package/dist/hosted-agent/react/index.d.ts +5 -0
- package/dist/hosted-agent/react/index.js +573 -0
- package/dist/hosted-agent/react/index.js.map +1 -0
- package/dist/hosted-agent/react/lines.css +110 -0
- package/dist/hosted-agent/react/styles.d.ts +2 -0
- package/dist/hosted-agent/react/styles.js +1 -0
- package/dist/hosted-agent/react/styles.js.map +1 -0
- package/dist/hosted-agent/workflows.d.ts +17 -0
- package/dist/integrations/index.d.ts +2 -9
- package/dist/integrations/index.js +11 -27
- package/dist/integrations/index.js.map +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/sso.d.ts +74 -7
- package/dist/profile/home.d.ts +23 -0
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +9 -1
- package/dist/profile/index.js.map +1 -1
- package/dist/public-consultation/index.js +4 -4
- package/dist/stories/hosted-agent/fixtures.d.ts +34 -0
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +4 -4
- package/dist/vault/ConfirmDialog.d.ts +1 -1
- package/dist/vault/VaultPane.d.ts +2 -2
- package/dist/vault/contracts.d.ts +8 -0
- package/dist/vault/index.js +1 -1
- package/dist/vault/lazy.d.ts +3 -3
- package/dist/vault/lazy.js +1 -1
- package/dist/vault/lazy.js.map +1 -1
- package/dist/web-react/index.js +22 -22
- package/package.json +50 -17
- package/dist/VaultPane-32HVWZ6A.js +0 -7
- package/dist/chunk-FCUWRMAF.js.map +0 -1
- package/dist/chunk-FXWNNHPC.js.map +0 -1
- /package/dist/{VaultPane-32HVWZ6A.js.map → VaultPane-2SJSP6TS.js.map} +0 -0
- /package/dist/{chunk-V4YGTOCS.js.map → chunk-MIFVOYZ7.js.map} +0 -0
- /package/dist/{chunk-4PZE7XAM.js.map → chunk-S3344RBY.js.map} +0 -0
- /package/dist/{chunk-4S36HWE4.js.map → chunk-WAMJSZYB.js.map} +0 -0
- /package/dist/{chunk-VIL6MHHQ.js.map → chunk-XTLYE2VO.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hosted-agent/react/LineSetup.tsx","../../../src/hosted-agent/react/LineMembers.tsx","../../../src/hosted-agent/react/LineBilling.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { Line } from '@tangle-network/sandbox'\nimport type {\n LineBoxMode, LineConnectionOption, LineIdentityKind, LineIdentityOption,\n LineSetupLine, LineSetupProps, LineSetupSnapshot,\n} from './contracts'\n\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nconst TRANSPORT: Record<Line['transport'], string> = {\n imessage: 'iMessage', sms: 'SMS', whatsapp: 'WhatsApp', email: 'Email',\n}\nconst IDENTITY: Record<LineIdentityKind, string> = {\n handle: 'Inkbox handle', number: 'Dedicated number', email: 'Email line',\n}\n\ntype Choice = { key: string; connection: LineConnectionOption; identity: LineIdentityOption }\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : 'The line request failed. Try again.'\n}\n\nfunction dateTime(value: string): string {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toLocaleString()\n}\n\nfunction choicesFor(snapshot: LineSetupSnapshot): Choice[] {\n return (snapshot.connections ?? []).flatMap(connection => connection.identities.map((identity, index) => ({\n key: `${connection.id}:${index}`,\n connection,\n identity,\n })))\n}\n\nfunction canOfferReconnect(line: LineSetupLine, choice: Choice): boolean {\n if (line.connectionId !== choice.connection.id || line.transport !== choice.identity.transport || line.targetId !== null) return false\n if (line.providerNumberId === null) return line.transport !== 'whatsapp' && !choice.identity.phoneNumberId && !choice.identity.requiresPhoneNumberId\n return choice.identity.phoneNumberId === line.providerNumberId ||\n (!choice.identity.phoneNumberId && choice.identity.requiresPhoneNumberId === true)\n}\n\nfunction matchesLineIdentity(line: LineSetupLine, identity: LineIdentityOption, numberId: string): boolean {\n if (line.transport !== identity.transport) return false\n if (line.providerNumberId === null) return line.transport !== 'whatsapp' && !identity.phoneNumberId && !identity.requiresPhoneNumberId\n return numberId === line.providerNumberId\n}\n\n/** Set up an owned identity through a host route backed by sandbox.lines and Hub. */\nexport function LineSetup({ client, scopeKey, initialTargetId, canManage, onNotice }: LineSetupProps) {\n const clientRef = useRef(client)\n clientRef.current = client\n const sequence = useRef(0)\n const incarnation = useRef(0)\n const confirmButton = useRef<HTMLButtonElement>(null)\n const heading = useRef<HTMLHeadingElement>(null)\n const disconnectTriggers = useRef(new Map<string, HTMLButtonElement>())\n const restoreFocusTo = useRef<string | null>(null)\n useBrowserLayoutEffect(() => {\n incarnation.current++\n return () => { incarnation.current++ }\n }, [scopeKey, initialTargetId])\n const [loadedSnapshot, setLoadedSnapshot] = useState<{ scopeKey: string; initialTargetId: string | undefined; value: LineSetupSnapshot } | null>(null)\n const snapshot = loadedSnapshot?.scopeKey === scopeKey && loadedSnapshot.initialTargetId === initialTargetId ? loadedSnapshot.value : null\n const [loading, setLoading] = useState(true)\n const [refreshFailed, setRefreshFailed] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [kind, setKind] = useState<LineIdentityKind>('handle')\n const [choiceKey, setChoiceKey] = useState('')\n const [targetId, setTargetId] = useState(initialTargetId ?? '')\n const [boxMode, setBoxMode] = useState<LineBoxMode>('per-member')\n const [phoneNumberId, setPhoneNumberId] = useState('')\n const [confirmLineId, setConfirmLineId] = useState<string | null>(null)\n\n function stillCurrent(requestIncarnation: number): boolean {\n return requestIncarnation === incarnation.current\n }\n\n async function reload(requestIncarnation: number): Promise<LineSetupSnapshot | null> {\n const request = ++sequence.current\n try {\n const next = await clientRef.current.load()\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null\n setLoadedSnapshot({ scopeKey, initialTargetId, value: next })\n setError(null)\n setRefreshFailed(false)\n setTargetId(current => next.targets.some(target => target.id === current)\n ? current : next.targets.find(target => target.id === initialTargetId)?.id ?? next.targets[0]?.id ?? '')\n setLoading(false)\n return next\n } catch (cause) {\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null\n setError(message(cause))\n setRefreshFailed(true)\n setLoading(false)\n return null\n }\n }\n\n function retry() {\n if (loading) return\n setLoading(true)\n void reload(incarnation.current)\n }\n\n function closeConfirmation(lineId: string) {\n restoreFocusTo.current = lineId\n setConfirmLineId(null)\n }\n\n useEffect(() => {\n setLoading(true)\n setLoadedSnapshot(null)\n setError(null)\n setRefreshFailed(false)\n setBusy(false)\n setKind('handle')\n setChoiceKey('')\n setTargetId(initialTargetId ?? '')\n setBoxMode('per-member')\n setPhoneNumberId('')\n setConfirmLineId(null)\n restoreFocusTo.current = null\n void reload(incarnation.current)\n return () => { sequence.current++ }\n }, [scopeKey, initialTargetId])\n\n useEffect(() => {\n if (confirmLineId) confirmButton.current?.focus()\n }, [confirmLineId])\n\n useEffect(() => {\n const lineId = restoreFocusTo.current\n if (confirmLineId !== null || lineId === null) return\n restoreFocusTo.current = null\n const trigger = disconnectTriggers.current.get(lineId)\n if (trigger && !trigger.disabled) trigger.focus()\n else heading.current?.focus()\n }, [confirmLineId, snapshot, refreshFailed])\n\n const allChoices = snapshot ? choicesFor(snapshot) : []\n const choices = snapshot ? allChoices.filter(choice => {\n const occupied = snapshot.lines.filter(line => line.status !== 'released' && line.transport === choice.identity.transport)\n const [line] = occupied\n return occupied.length === 0 || (occupied.length === 1 && line !== undefined && canOfferReconnect(line, choice))\n }) : []\n const availableKinds = (['handle', 'number', 'email'] as const).filter(value => choices.some(choice => choice.identity.kind === value))\n const selectedKind = availableKinds.includes(kind) ? kind : availableKinds[0]\n const kindChoices = choices.filter(choice => choice.identity.kind === selectedKind)\n const selected = kindChoices.find(choice => choice.key === choiceKey) ?? kindChoices[0]\n const selectedTarget = snapshot?.targets.find(target => target.id === targetId)\n const selectedMode = selectedTarget?.modes.includes(boxMode) ? boxMode : selectedTarget?.modes[0]\n const enteredNumber = selected?.identity.phoneNumberId ?? phoneNumberId.trim()\n const needsNumber = selected?.identity.transport === 'whatsapp' || selected?.identity.requiresPhoneNumberId\n const occupied = selected && snapshot?.lines.filter(line => line.status !== 'released' && line.transport === selected.identity.transport)\n const [occupiedLine] = occupied || []\n const reconnectChoice = Boolean(selected && occupied?.length === 1 && occupiedLine && canOfferReconnect(occupiedLine, selected))\n const reconnecting = Boolean(reconnectChoice && selected && occupiedLine &&\n matchesLineIdentity(occupiedLine, selected.identity, enteredNumber))\n const blockedByExistingLine = Boolean(occupied?.length && !reconnecting)\n\n async function connect() {\n if (!selected || !selectedTarget || !selectedMode || (needsNumber && !enteredNumber) || blockedByExistingLine || busy || loading || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(true)\n setError(null)\n try {\n await clientRef.current.connect({\n connectionId: selected.connection.id,\n transport: selected.identity.transport,\n ...(enteredNumber ? { phoneNumberId: enteredNumber } : {}),\n targetId: selectedTarget.id,\n boxMode: selectedMode,\n })\n if (!stillCurrent(requestIncarnation)) return\n const updated = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n if (!updated) {\n onNotice?.({ kind: 'error', message: 'Line connected, but its current status could not be loaded.' })\n return\n }\n const line = updated.lines.find(item => item.status !== 'released' && item.connectionId === selected.connection.id &&\n item.transport === selected.identity.transport && item.providerNumberId === (enteredNumber || null))\n onNotice?.({ kind: 'success', message: line?.answering ? 'The line is answering.' : 'Line connected. Add a member to start answers.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = message(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(false)\n }\n }\n\n async function disconnect(lineId: string) {\n if (busy || loading || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(true)\n setError(null)\n try {\n await clientRef.current.disconnect(lineId)\n if (!stillCurrent(requestIncarnation)) return\n const updated = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n closeConfirmation(lineId)\n onNotice?.(updated\n ? { kind: 'success', message: 'The line stopped answering.' }\n : { kind: 'error', message: 'Line disconnected, but its current status could not be loaded.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = message(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(false)\n }\n }\n\n return <section className=\"tangle-lines\" aria-label=\"Agent lines\">\n <header className=\"tangle-lines__heading\">\n <div>\n <h2 ref={heading} tabIndex={-1}>Lines</h2>\n {snapshot && <p>{snapshot.workspaceName} can use one line per transport.</p>}\n </div>\n {snapshot && <span className=\"tangle-lines__count\">{snapshot.lines.filter(line => line.answering).length} answering</span>}\n </header>\n\n {error && <div className=\"tangle-lines__error\" role=\"alert\">\n <span>{error}</span>\n {(!snapshot || refreshFailed) && <button type=\"button\" disabled={loading} onClick={retry}>Retry</button>}\n </div>}\n {loading && <p className=\"tangle-lines__muted\" role=\"status\">Loading lines…</p>}\n\n {snapshot && <>\n <div className=\"tangle-lines__list\">\n {snapshot.lines.filter(line => line.status !== 'released').length === 0 &&\n <p className=\"tangle-lines__empty\">No line answers for this workspace yet.</p>}\n {snapshot.lines.filter(line => line.status !== 'released').map(line =>\n <div className=\"tangle-lines__row\" key={line.id}>\n <div className=\"tangle-lines__row-main\">\n <span className=\"tangle-lines__eyebrow\">{TRANSPORT[line.transport]} · {line.answering ? 'Answering' : line.targetId === null ? 'Disconnected' : line.status === 'active' ? 'Awaiting member' : line.status}</span>\n <strong>{line.routerAddress ?? line.address}</strong>\n {line.connect && line.targetId !== null && <p>To reach this agent, text <code>{line.connect}</code>.</p>}\n <p>{line.targetLabel ?? 'No agent or box assigned'}{line.boxMode && ` · ${line.boxMode === 'per-member' ? 'one box per member' : 'shared box'}`}</p>\n {line.lastTurn.kind === 'latest' ? <p>Last turn: {line.lastTurn.status} · {dateTime(line.lastTurn.at)}</p>\n : <p>{line.lastTurn.kind === 'none' ? 'No turns yet.' : 'Turn history unavailable.'}</p>}\n </div>\n {canManage && line.canDisconnect && line.targetId !== null && line.status === 'active' && <div className=\"tangle-lines__row-actions\">\n {confirmLineId === line.id ? <div className=\"tangle-lines__confirm\" role=\"group\" aria-label={`Disconnect ${line.address}?`}>\n <span>Disconnect this line?</span>\n <button ref={confirmButton} type=\"button\" className=\"tangle-lines__danger\" disabled={busy || loading || refreshFailed} onClick={() => void disconnect(line.id)}>Disconnect</button>\n <button type=\"button\" disabled={busy} onClick={() => closeConfirmation(line.id)}>Keep line</button>\n </div> : <button ref={node => { if (node) disconnectTriggers.current.set(line.id, node); else disconnectTriggers.current.delete(line.id) }} type=\"button\" className=\"tangle-lines__quiet\" disabled={busy || loading || refreshFailed} onClick={() => setConfirmLineId(line.id)}>Disconnect line</button>}\n </div>}\n </div>)}\n </div>\n\n {canManage && (snapshot.connections === null || choices.length > 0 || allChoices.length === 0) && <div className=\"tangle-lines__setup\">\n <h3>Connect an identity you own</h3>\n <p>Choose an identity already connected in Hub. Billing details appear below.</p>\n {snapshot.connections === null ? <div className=\"tangle-lines__error\" role=\"status\">\n <span>Hub connections could not be read. Check Hub access and try again.</span>\n <button type=\"button\" disabled={loading} onClick={retry}>Retry</button>\n </div>\n : availableKinds.length === 0 ? <p className=\"tangle-lines__empty\">\n Connect an owned handle, number, or mailbox in Hub, then <button type=\"button\" disabled={loading} onClick={retry}>reload</button>.\n </p>\n : <>\n <div className=\"tangle-lines__kinds\" role=\"group\" aria-label=\"Identity type\">\n {availableKinds.map(value => <button key={value} type=\"button\" aria-pressed={selectedKind === value} onClick={() => { setKind(value); setChoiceKey(''); setPhoneNumberId('') }}>{IDENTITY[value]}</button>)}\n </div>\n <div className=\"tangle-lines__fields\">\n <label>Owned connection\n <select value={selected?.key ?? ''} onChange={event => { setChoiceKey(event.target.value); setPhoneNumberId('') }}>\n {kindChoices.map(choice => <option key={choice.key} value={choice.key}>{choice.connection.label} · {choice.identity.label}</option>)}\n </select>\n </label>\n <label>Agent or box\n <select value={selectedTarget?.id ?? ''} onChange={event => setTargetId(event.target.value)}>\n {snapshot.targets.map(target => <option key={target.id} value={target.id}>{target.label} · {target.kind}</option>)}\n </select>\n </label>\n {selectedTarget && selectedTarget.modes.length > 1 && <fieldset>\n <legend>Conversation space</legend>\n <label><input type=\"radio\" name={`line-box-mode-${scopeKey}`} value=\"per-member\" checked={selectedMode === 'per-member'} disabled={!selectedTarget?.modes.includes('per-member')} onChange={() => setBoxMode('per-member')} /> One box per member</label>\n <label><input type=\"radio\" name={`line-box-mode-${scopeKey}`} value=\"shared\" checked={selectedMode === 'shared'} disabled={!selectedTarget?.modes.includes('shared')} onChange={() => setBoxMode('shared')} /> Shared box for a team</label>\n </fieldset>}\n {needsNumber && !selected?.identity.phoneNumberId && <label>Existing provider number ID\n <input value={phoneNumberId} onChange={event => setPhoneNumberId(event.target.value)} autoComplete=\"off\" placeholder=\"Number ID from Hub\" />\n </label>}\n </div>\n {reconnectChoice && blockedByExistingLine && <p className=\"tangle-lines__warning\">Enter this line's current provider number ID to reconnect.</p>}\n <button className=\"tangle-lines__primary\" type=\"button\" disabled={busy || loading || refreshFailed || !selected || !selectedTarget || !selectedMode || (needsNumber && !enteredNumber) || blockedByExistingLine} onClick={() => void connect()}>\n {busy ? (reconnectChoice ? 'Reconnecting…' : 'Connecting…') : `${reconnectChoice ? 'Reconnect' : 'Connect'} ${selected ? TRANSPORT[selected.identity.transport] : 'line'}`}\n </button>\n </>}\n </div>}\n </>}\n </section>\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { LineMember } from '@tangle-network/sandbox'\nimport type { LineMembersProps } from './contracts'\n\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nconst STATUS: Record<LineMember['status'], { label: string; detail: string }> = {\n invited: { label: 'Awaiting consent', detail: 'The first message from this address confirms consent.' },\n active: { label: 'Consented', detail: 'This member has sent a message to the line.' },\n stopped: { label: 'Stopped', detail: 'The member texted STOP. Only a message from them with START resumes replies.' },\n removed: { label: 'Removed', detail: 'This address no longer has access to the line.' },\n}\n\nfunction errorMessage(cause: unknown): string {\n return cause instanceof Error ? cause.message : 'The member request failed. Try again.'\n}\n\n/** Manage Hub line members while keeping consent and STOP authority with the sender. */\nexport function LineMembers({ lineId, scopeKey, client, roles, canManage, canAdd = true, maxMembers, allowRoleChange = true, allowRemoveLastOwner = false, onNotice }: LineMembersProps) {\n const clientRef = useRef(client)\n clientRef.current = client\n const sequence = useRef(0)\n const incarnation = useRef(0)\n const confirmButton = useRef<HTMLButtonElement>(null)\n const heading = useRef<HTMLHeadingElement>(null)\n const removeTriggers = useRef(new Map<string, HTMLButtonElement>())\n const restoreFocusTo = useRef<string | null>(null)\n useBrowserLayoutEffect(() => {\n incarnation.current++\n return () => { incarnation.current++ }\n }, [lineId, scopeKey])\n const [memberSnapshot, setMemberSnapshot] = useState<{ lineId: string; scopeKey: string; members: LineMember[] } | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [refreshFailed, setRefreshFailed] = useState(false)\n const [address, setAddress] = useState('')\n const [label, setLabel] = useState('')\n const [role, setRole] = useState('member')\n const [busy, setBusy] = useState<string | null>(null)\n const [confirmId, setConfirmId] = useState<string | null>(null)\n const members = memberSnapshot?.lineId === lineId && memberSnapshot.scopeKey === scopeKey ? memberSnapshot.members : null\n\n function stillCurrent(requestIncarnation: number): boolean {\n return requestIncarnation === incarnation.current\n }\n\n async function reload(requestIncarnation: number): Promise<boolean> {\n const request = ++sequence.current\n try {\n const next = await clientRef.current.list(lineId)\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false\n setMemberSnapshot({ lineId, scopeKey, members: next })\n setError(null)\n setRefreshFailed(false)\n return true\n } catch (cause) {\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false\n setError(errorMessage(cause))\n setRefreshFailed(true)\n return false\n }\n }\n\n async function retry() {\n if (busy) return\n const requestIncarnation = incarnation.current\n setBusy('refresh')\n try { await reload(requestIncarnation) }\n finally { if (stillCurrent(requestIncarnation)) setBusy(null) }\n }\n\n function closeConfirmation(memberId: string) {\n restoreFocusTo.current = memberId\n setConfirmId(null)\n }\n\n useEffect(() => {\n setMemberSnapshot(null)\n setError(null)\n setRefreshFailed(false)\n setAddress('')\n setLabel('')\n setRole('member')\n setConfirmId(null)\n restoreFocusTo.current = null\n setBusy(null)\n void reload(incarnation.current)\n return () => { sequence.current++ }\n }, [lineId, scopeKey])\n\n useEffect(() => {\n if (confirmId) confirmButton.current?.focus()\n }, [confirmId])\n\n useEffect(() => {\n const memberId = restoreFocusTo.current\n if (confirmId !== null || memberId === null) return\n restoreFocusTo.current = null\n const trigger = removeTriggers.current.get(memberId)\n if (trigger && !trigger.disabled) trigger.focus()\n else heading.current?.focus()\n }, [confirmId, members, refreshFailed])\n\n const selectedRole = roles.some(item => item.value === role) ? role : roles.find(item => item.value === 'member')?.value ?? roles[0]?.value ?? ''\n const activeMembers = members?.filter(member => member.status !== 'removed').length ?? 0\n const activeOwners = members?.filter(member => member.role === 'owner' && member.status !== 'removed').length ?? 0\n\n async function mutate(key: string, action: () => Promise<void>, success: string, onSuccess?: () => void) {\n if (busy || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(key)\n setError(null)\n try {\n await action()\n if (!stillCurrent(requestIncarnation)) return\n const refreshed = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n onSuccess?.()\n onNotice?.(refreshed\n ? { kind: 'success', message: success }\n : { kind: 'error', message: 'Member updated, but the current member list could not be loaded.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = errorMessage(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(null)\n }\n }\n\n function add() {\n const trimmed = address.trim()\n if (!trimmed || !selectedRole || members === null || (maxMembers !== undefined && activeMembers >= maxMembers)) return\n void mutate('add', () => clientRef.current.add(lineId, { address: trimmed, role: selectedRole, ...(label.trim() ? { label: label.trim() } : {}) }), 'Member invited. Their first message confirms consent.', () => {\n setAddress('')\n setLabel('')\n })\n }\n\n return <section className=\"tangle-lines\" aria-label=\"Line members\">\n <header className=\"tangle-lines__heading\">\n <div><h2 ref={heading} tabIndex={-1}>Members</h2><p>Each address controls its own consent and STOP state.</p></div>\n {members && <span className=\"tangle-lines__count\">{activeMembers} {activeMembers === 1 ? 'member' : 'members'}</span>}\n </header>\n {error && <div className=\"tangle-lines__error\" role=\"alert\">\n <span>{error}</span>\n {(members === null || refreshFailed) && <button type=\"button\" disabled={busy !== null} onClick={() => void retry()}>Retry</button>}\n </div>}\n {members === null ? !error && <p className=\"tangle-lines__muted\" role=\"status\">Loading members…</p> : <div className=\"tangle-lines__list\">\n {members.length === 0 && <p className=\"tangle-lines__empty\">{canAdd ? 'No members yet. Invite an address that you own or have permission to add.' : 'No members yet.'}</p>}\n {members.map(member => {\n const lastOwner = member.role === 'owner' && activeOwners <= 1\n const editable = canManage && member.status !== 'removed' && (!lastOwner || allowRemoveLastOwner)\n return <div className=\"tangle-lines__row\" key={member.id}>\n <div className=\"tangle-lines__row-main\">\n <span className={`tangle-lines__state tangle-lines__state--${member.status}`}>{STATUS[member.status].label}</span>\n <strong>{member.label || member.address}</strong>\n {member.label && <p>{member.address}</p>}\n <p>{STATUS[member.status].detail}</p>\n </div>\n <div className=\"tangle-lines__row-actions\">\n {editable ? <>\n {allowRoleChange && !lastOwner ? <label className=\"tangle-lines__compact-label\">Role\n <select value={member.role} disabled={busy !== null || refreshFailed} onChange={event => void mutate(member.id, () => clientRef.current.update(lineId, member.id, { role: event.target.value }), 'Role updated.')}>\n {!roles.some(item => item.value === member.role) && <option value={member.role}>{member.role}</option>}\n {roles.map(item => <option key={item.value} value={item.value}>{item.label}{item.tools === 'chat' ? ' · chat only' : ''}</option>)}\n </select>\n </label> : <span className=\"tangle-lines__role\">{roles.find(item => item.value === member.role)?.label ?? member.role}</span>}\n {confirmId === member.id ? <div className=\"tangle-lines__confirm\" role=\"group\" aria-label={`Remove ${member.label || member.address}?`}>\n <span>Remove {member.label || member.address}?</span>\n <button ref={confirmButton} type=\"button\" className=\"tangle-lines__danger\" disabled={busy !== null || refreshFailed} onClick={() => void mutate(member.id, () => clientRef.current.remove(lineId, member.id), 'Member removed.', () => closeConfirmation(member.id))}>Remove</button>\n <button type=\"button\" disabled={busy !== null} onClick={() => closeConfirmation(member.id)}>Keep</button>\n </div> : <button ref={node => { if (node) removeTriggers.current.set(member.id, node); else removeTriggers.current.delete(member.id) }} type=\"button\" className=\"tangle-lines__quiet\" disabled={busy !== null || refreshFailed} onClick={() => setConfirmId(member.id)}>Remove</button>}\n </> : <span className=\"tangle-lines__role\">{roles.find(item => item.value === member.role)?.label ?? member.role}</span>}\n </div>\n </div>\n })}\n </div>}\n\n {canManage && canAdd && members !== null && (maxMembers === undefined || activeMembers < maxMembers) && <div className=\"tangle-lines__setup\">\n <h3>Invite a member</h3>\n <p>Hub marks the address as invited. The member confirms by messaging the line.</p>\n <div className=\"tangle-lines__fields\">\n <label>Phone number or email\n <input value={address} onChange={event => setAddress(event.target.value)} autoComplete=\"off\" placeholder=\"+15551234567 or name@example.com\" />\n </label>\n <label>Name (optional)\n <input value={label} onChange={event => setLabel(event.target.value)} placeholder=\"Member name\" />\n </label>\n <label>Role\n <select value={selectedRole} disabled={roles.length === 0} onChange={event => setRole(event.target.value)}>\n {roles.map(item => <option key={item.value} value={item.value}>{item.label}{item.tools === 'chat' ? ' · chat only' : ''}</option>)}\n </select>\n </label>\n </div>\n <button type=\"button\" className=\"tangle-lines__primary\" disabled={busy !== null || refreshFailed || !address.trim() || !selectedRole} onClick={add}>\n {busy === 'add' ? 'Inviting…' : 'Invite member'}\n </button>\n </div>}\n </section>\n}\n","import type { LineBillingProps } from './contracts'\n\nfunction resetTime(value: string): string {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toLocaleString()\n}\n\n/** Read-only billing facts from the line attachment and Hub allowance status. */\nexport function LineBilling({ view }: LineBillingProps) {\n return <section className=\"tangle-lines\" aria-label=\"Line billing\">\n <header className=\"tangle-lines__heading\">\n <div><h2>Billing</h2><p>{view.lineAddress}</p></div>\n </header>\n <dl className=\"tangle-lines__billing\">\n <div>\n <dt>Line</dt>\n <dd>{view.linePayer.kind === 'workspace' ? view.linePayer.label : 'Payer not verified'}</dd>\n {view.linePayer.kind === 'workspace' && <dd className=\"tangle-lines__billing-detail\">Charged to {view.workspaceName}.</dd>}\n </div>\n <div>\n <dt>Agent turns</dt>\n <dd>{view.turnPayer.kind === 'unverified' ? 'Payer not verified' : view.turnPayer.label}</dd>\n {view.turnPayer.kind === 'member' && <dd className=\"tangle-lines__billing-detail\">Each member pays for their own admitted turns.</dd>}\n </div>\n <div>\n <dt>Allowance</dt>\n {view.allowance ? <>\n <dd>{view.allowance.turnsPerMemberPerDay} turns per member each day</dd>\n {view.allowance.used !== undefined && view.allowance.limit !== undefined &&\n <dd className=\"tangle-lines__billing-detail\">{view.allowance.used} of {view.allowance.limit} turns used{view.allowance.resetsAt ? ` · resets ${resetTime(view.allowance.resetsAt)}` : ''}</dd>}\n </> : <dd>No allowance reported</dd>}\n </div>\n </dl>\n </section>\n}\n"],"mappings":"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","names":["occupied","line","useEffect","useLayoutEffect","useRef","useState","Fragment","jsx","jsxs","useBrowserLayoutEffect","Fragment","jsx","jsxs"]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.tangle-lines {
|
|
2
|
+
--tl-text: var(--line-kit-text, var(--text-primary, var(--fg, #17212b)));
|
|
3
|
+
--tl-muted: var(--line-kit-muted, var(--text-secondary, var(--fg-muted, #64748b)));
|
|
4
|
+
--tl-border: var(--line-kit-border, var(--border-default, var(--line-subtle, #dbe1e7)));
|
|
5
|
+
--tl-surface: var(--line-kit-surface, var(--bg-input, #fff));
|
|
6
|
+
--tl-accent: var(--line-kit-accent, var(--brand-primary, #2856a1));
|
|
7
|
+
--tl-accent-foreground: var(--line-kit-accent-foreground, hsl(var(--primary-foreground, 0 0% 100%)));
|
|
8
|
+
--tl-success: var(--line-kit-success, var(--success-11, hsl(var(--success, 160 84% 26%))));
|
|
9
|
+
--tl-warning: var(--line-kit-warning, var(--warning-11, hsl(var(--warning-strong, 41 96% 27%))));
|
|
10
|
+
--tl-danger: var(--line-kit-danger, var(--text-danger, #a62f3b));
|
|
11
|
+
color: var(--tl-text);
|
|
12
|
+
font: inherit;
|
|
13
|
+
width: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.tangle-lines * { box-sizing: border-box; }
|
|
17
|
+
.tangle-lines h2, .tangle-lines h3, .tangle-lines p { margin: 0; }
|
|
18
|
+
.tangle-lines h2 { font-size: 1.35rem; font-weight: 650; line-height: 1.25; }
|
|
19
|
+
.tangle-lines h3 { font-size: 1.05rem; font-weight: 650; line-height: 1.3; }
|
|
20
|
+
.tangle-lines p { line-height: 1.45; }
|
|
21
|
+
.tangle-lines button, .tangle-lines input, .tangle-lines select { font: inherit; }
|
|
22
|
+
.tangle-lines button { cursor: pointer; }
|
|
23
|
+
.tangle-lines button:disabled { cursor: not-allowed; opacity: .55; }
|
|
24
|
+
.tangle-lines :is(button, input, select):focus-visible { outline: 2px solid var(--tl-accent); outline-offset: 2px; }
|
|
25
|
+
.tangle-lines h2:focus-visible { outline: 2px solid var(--tl-accent); outline-offset: 4px; }
|
|
26
|
+
|
|
27
|
+
.tangle-lines__heading {
|
|
28
|
+
align-items: start;
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 1rem;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
margin-bottom: 1rem;
|
|
33
|
+
}
|
|
34
|
+
.tangle-lines__heading p { color: var(--tl-muted); font-size: .91rem; margin-top: .25rem; }
|
|
35
|
+
.tangle-lines__count { color: var(--tl-muted); font-size: .82rem; padding-top: .25rem; white-space: nowrap; }
|
|
36
|
+
.tangle-lines__list { border-top: 1px solid var(--tl-border); }
|
|
37
|
+
.tangle-lines__row {
|
|
38
|
+
align-items: start;
|
|
39
|
+
border-bottom: 1px solid var(--tl-border);
|
|
40
|
+
display: flex;
|
|
41
|
+
gap: 1rem;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
padding: 1rem 0;
|
|
44
|
+
}
|
|
45
|
+
.tangle-lines__row-main { min-width: 0; }
|
|
46
|
+
.tangle-lines__row-main strong { display: block; font-size: 1rem; font-weight: 600; overflow-wrap: anywhere; }
|
|
47
|
+
.tangle-lines__row-main p { color: var(--tl-muted); font-size: .85rem; margin-top: .3rem; }
|
|
48
|
+
.tangle-lines__row-main code { color: var(--tl-text); font-size: .92em; font-weight: 600; white-space: nowrap; }
|
|
49
|
+
.tangle-lines__eyebrow { color: var(--tl-muted); display: block; font-size: .79rem; margin-bottom: .25rem; }
|
|
50
|
+
.tangle-lines__row-actions { align-items: end; display: flex; flex-direction: column; gap: .5rem; }
|
|
51
|
+
.tangle-lines__row-actions button, .tangle-lines__error button, .tangle-lines__empty button { background: none; border: 0; color: var(--tl-accent); padding: .25rem 0; white-space: nowrap; }
|
|
52
|
+
.tangle-lines__row-actions button:hover, .tangle-lines__error button:hover, .tangle-lines__empty button:hover { text-decoration: underline; }
|
|
53
|
+
.tangle-lines__confirm { align-items: center; display: flex; flex-wrap: wrap; gap: .65rem; justify-content: flex-end; max-width: 20rem; }
|
|
54
|
+
.tangle-lines__confirm span { color: var(--tl-muted); font-size: .84rem; }
|
|
55
|
+
.tangle-lines__confirm .tangle-lines__danger { color: var(--tl-danger); }
|
|
56
|
+
.tangle-lines__state { color: var(--tl-muted); display: inline-block; font-size: .79rem; margin-bottom: .25rem; }
|
|
57
|
+
.tangle-lines__state--active { color: var(--tl-success); }
|
|
58
|
+
.tangle-lines__state--stopped { color: var(--tl-danger); }
|
|
59
|
+
.tangle-lines__state--invited { color: var(--tl-warning); }
|
|
60
|
+
.tangle-lines__role { color: var(--tl-muted); font-size: .86rem; white-space: nowrap; }
|
|
61
|
+
.tangle-lines__compact-label { color: var(--tl-muted); font-size: .8rem; }
|
|
62
|
+
.tangle-lines__compact-label select { margin-top: .2rem; min-width: 8rem; }
|
|
63
|
+
|
|
64
|
+
.tangle-lines__setup { border: 1px solid var(--tl-border); border-radius: .8rem; margin-top: 1.25rem; padding: 1.15rem; }
|
|
65
|
+
.tangle-lines__setup > p { color: var(--tl-muted); font-size: .87rem; margin-top: .35rem; }
|
|
66
|
+
.tangle-lines__kinds { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: 1rem; }
|
|
67
|
+
.tangle-lines__kinds button {
|
|
68
|
+
background: transparent;
|
|
69
|
+
border: 1px solid var(--tl-border);
|
|
70
|
+
border-radius: .5rem;
|
|
71
|
+
color: var(--tl-muted);
|
|
72
|
+
padding: .48rem .72rem;
|
|
73
|
+
}
|
|
74
|
+
.tangle-lines__kinds button[aria-pressed="true"] { background: var(--tl-text); border-color: var(--tl-text); color: var(--tl-surface); }
|
|
75
|
+
.tangle-lines__fields { display: grid; gap: .9rem; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 1rem 0; }
|
|
76
|
+
.tangle-lines__fields > label, .tangle-lines__compact-label { display: flex; flex-direction: column; gap: .28rem; }
|
|
77
|
+
.tangle-lines__fields > label, .tangle-lines__fields legend { color: var(--tl-text); font-size: .85rem; font-weight: 550; }
|
|
78
|
+
.tangle-lines__fields :is(input:not([type="radio"]), select), .tangle-lines__compact-label select {
|
|
79
|
+
background: var(--tl-surface);
|
|
80
|
+
border: 1px solid var(--tl-border);
|
|
81
|
+
border-radius: .45rem;
|
|
82
|
+
color: var(--tl-text);
|
|
83
|
+
min-height: 2.5rem;
|
|
84
|
+
padding: .45rem .65rem;
|
|
85
|
+
width: 100%;
|
|
86
|
+
}
|
|
87
|
+
.tangle-lines__fields fieldset { border: 0; display: flex; flex-direction: column; gap: .45rem; margin: 0; min-width: 0; padding: 0; }
|
|
88
|
+
.tangle-lines__fields fieldset label { align-items: center; color: var(--tl-muted); display: flex; font-size: .85rem; gap: .45rem; }
|
|
89
|
+
.tangle-lines__fields fieldset input { accent-color: var(--tl-accent); margin: 0; }
|
|
90
|
+
.tangle-lines__primary { background: var(--tl-accent); border: 1px solid var(--tl-accent); border-radius: .5rem; color: var(--tl-accent-foreground); font-size: .88rem; font-weight: 600; min-height: 2.55rem; padding: .55rem .85rem; }
|
|
91
|
+
.tangle-lines__primary:hover:not(:disabled) { filter: brightness(.92); }
|
|
92
|
+
.tangle-lines__muted, .tangle-lines__empty { color: var(--tl-muted); font-size: .88rem; padding: .7rem 0; }
|
|
93
|
+
.tangle-lines__error { align-items: center; border: 1px solid var(--tl-danger); border-radius: .5rem; color: var(--tl-danger); display: flex; font-size: .85rem; gap: .7rem; justify-content: space-between; margin-bottom: .8rem; padding: .65rem .8rem; }
|
|
94
|
+
.tangle-lines__warning { color: var(--tl-warning); font-size: .85rem; margin-bottom: .8rem !important; }
|
|
95
|
+
|
|
96
|
+
.tangle-lines__billing { border-top: 1px solid var(--tl-border); margin: 0; }
|
|
97
|
+
.tangle-lines__billing > div { border-bottom: 1px solid var(--tl-border); display: grid; gap: .1rem 1rem; grid-template-columns: 9rem minmax(0, 1fr); padding: .85rem 0; }
|
|
98
|
+
.tangle-lines__billing dt { color: var(--tl-muted); font-size: .85rem; }
|
|
99
|
+
.tangle-lines__billing dd { font-size: .91rem; font-weight: 600; margin: 0; }
|
|
100
|
+
.tangle-lines__billing dd.tangle-lines__billing-detail { color: var(--tl-muted); font-size: .81rem; font-weight: 400; grid-column: 2; }
|
|
101
|
+
|
|
102
|
+
@media (max-width: 600px) {
|
|
103
|
+
.tangle-lines__heading { align-items: start; }
|
|
104
|
+
.tangle-lines__row { flex-direction: column; gap: .7rem; }
|
|
105
|
+
.tangle-lines__row-actions { align-items: start; width: 100%; }
|
|
106
|
+
.tangle-lines__confirm { justify-content: flex-start; }
|
|
107
|
+
.tangle-lines__fields { grid-template-columns: 1fr; }
|
|
108
|
+
.tangle-lines__billing > div { grid-template-columns: 1fr; }
|
|
109
|
+
.tangle-lines__billing dd.tangle-lines__billing-detail { grid-column: 1; }
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface AgentHomeWorkflowOptions {
|
|
2
|
+
/** Stable name for idempotent operator reconciliation. */
|
|
3
|
+
instanceKey: string;
|
|
4
|
+
lineId: string;
|
|
5
|
+
ownerAddress: string;
|
|
6
|
+
timeZone: string;
|
|
7
|
+
/** Additional owner-authorized watch instructions, not a schedule override. */
|
|
8
|
+
heartbeatInstructions?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AgentHomeWorkflow {
|
|
11
|
+
name: string;
|
|
12
|
+
purpose: 'heartbeat' | 'consolidation';
|
|
13
|
+
/** JSON is valid YAML. Install with the published HubClient.workflows API. */
|
|
14
|
+
yaml: string;
|
|
15
|
+
}
|
|
16
|
+
/** Platform owns timing, admission, approvals, persistence and DM delivery. */
|
|
17
|
+
export declare function agentHomeWorkflows(options: AgentHomeWorkflowOptions): AgentHomeWorkflow[];
|
|
@@ -37,22 +37,15 @@ export interface ParsedIntegrationAction {
|
|
|
37
37
|
* elsewhere instead of misrouting them to the hub.
|
|
38
38
|
*/
|
|
39
39
|
export declare function resolveIntegrationAction(toolName: string): ParsedIntegrationAction | undefined;
|
|
40
|
-
/**
|
|
41
|
-
* credentials, resolves the connection from the bearer principal, evaluates
|
|
42
|
-
* per-action policy (read → allow, write/destructive → approval), and runs the
|
|
43
|
-
* action server-side. Never throws on a policy block — a gated write is a
|
|
44
|
-
* normal `succeeded: false` outcome. */
|
|
40
|
+
/** Product outcome adapter over the published Hub transport. Never grants approval. */
|
|
45
41
|
export declare class HubExecClient {
|
|
46
|
-
private readonly
|
|
47
|
-
private readonly bearer;
|
|
48
|
-
private readonly fetchImpl;
|
|
42
|
+
private readonly hub;
|
|
49
43
|
constructor(options: HubExecClientOptions);
|
|
50
44
|
exec(input: {
|
|
51
45
|
path: string;
|
|
52
46
|
actionInput?: unknown;
|
|
53
47
|
connectionId?: string;
|
|
54
48
|
}): Promise<HubExecResult>;
|
|
55
|
-
private readEnvelope;
|
|
56
49
|
}
|
|
57
50
|
/** Define input parameters for invoking a hub tool with user ID, tool name, and optional arguments */
|
|
58
51
|
export interface HubInvokeInput {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/integrations/index.ts
|
|
2
|
+
import { HubClient, HubSdkError } from "@tangle-network/hub-sdk";
|
|
2
3
|
import { parseIntegrationToolName } from "@tangle-network/agent-integrations/catalog";
|
|
3
4
|
function resolveIntegrationAction(toolName) {
|
|
4
5
|
let parsed;
|
|
@@ -11,43 +12,26 @@ function resolveIntegrationAction(toolName) {
|
|
|
11
12
|
return { ...parsed, path: `${parsed.providerId}.${parsed.connectorId}.${parsed.actionId}` };
|
|
12
13
|
}
|
|
13
14
|
var HubExecClient = class {
|
|
14
|
-
|
|
15
|
-
bearer;
|
|
16
|
-
fetchImpl;
|
|
15
|
+
hub;
|
|
17
16
|
constructor(options) {
|
|
18
17
|
if (!options.baseUrl) throw new Error("HubExecClient: baseUrl is required");
|
|
19
18
|
if (!options.bearer) throw new Error("HubExecClient: bearer is required");
|
|
20
|
-
this.
|
|
21
|
-
this.bearer = options.bearer;
|
|
22
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
19
|
+
this.hub = new HubClient({ baseUrl: options.baseUrl, apiKey: options.bearer, fetch: options.fetchImpl });
|
|
23
20
|
}
|
|
24
21
|
async exec(input) {
|
|
25
|
-
const response = await this.fetchImpl(`${this.baseUrl}/v1/hub/exec`, {
|
|
26
|
-
method: "POST",
|
|
27
|
-
headers: { Authorization: `Bearer ${this.bearer}`, "Content-Type": "application/json", Accept: "application/json" },
|
|
28
|
-
body: JSON.stringify({ path: input.path, input: input.actionInput, connectionId: input.connectionId })
|
|
29
|
-
});
|
|
30
|
-
const envelope = await this.readEnvelope(response);
|
|
31
|
-
if (response.ok && envelope.success) return { succeeded: true, result: envelope.data?.result };
|
|
32
|
-
return {
|
|
33
|
-
succeeded: false,
|
|
34
|
-
code: envelope.error?.code ?? `HUB_HTTP_${response.status}`,
|
|
35
|
-
message: envelope.error?.message ?? `Hub /exec returned ${response.status}`,
|
|
36
|
-
approval: envelope.error?.details?.approval
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
async readEnvelope(response) {
|
|
40
|
-
const text = await response.text();
|
|
41
|
-
if (!text) return { success: false, error: { code: `HUB_HTTP_${response.status}`, message: `Hub returned ${response.status} with no body` } };
|
|
42
22
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
23
|
+
const result = await this.hub.tools.invoke(input.path, input.actionInput, { connectionId: input.connectionId });
|
|
24
|
+
return { succeeded: true, result: result.result };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (!(error instanceof HubSdkError)) throw error;
|
|
27
|
+
const details = error.details;
|
|
28
|
+
const approval = details && typeof details === "object" && "approval" in details ? details.approval : void 0;
|
|
29
|
+
return { succeeded: false, code: error.code, message: error.message, ...approval !== void 0 ? { approval } : {} };
|
|
46
30
|
}
|
|
47
31
|
}
|
|
48
32
|
};
|
|
49
33
|
async function invokeIntegrationHub(input, deps) {
|
|
50
|
-
const env = deps.env ?? process
|
|
34
|
+
const env = deps.env ?? globalThis.process?.env ?? {};
|
|
51
35
|
const baseUrl = deps.baseUrl ?? env.TANGLE_PLATFORM_URL?.trim();
|
|
52
36
|
if (!baseUrl) return { status: 500, body: { error: "TANGLE_PLATFORM_URL is not configured" } };
|
|
53
37
|
const action = resolveIntegrationAction(input.toolName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/integrations/index.ts"],"sourcesContent":["/**\n * Integration-hub WIRING — the agent→hub invocation path every Tangle agent app\n * forks. The integration ENGINE (catalog, connectors, OAuth, policy) is\n * `@tangle-network/agent-integrations` (a peer dependency); this module is the\n * thin app-side wiring on top: a typed client over the platform hub's\n * `POST /v1/hub/exec`, MCP-tool-name → hub-action-path resolution, and the\n * per-turn invoke flow the `integration_invoke` tool calls.\n *\n * The product supplies its own catalog (which connectors it uses) and its own\n * per-user api-key resolver (the `apiKeyResolver` seam) — this module owns\n * neither credentials nor the action catalog.\n */\nimport { parseIntegrationToolName } from '@tangle-network/agent-integrations/catalog'\n\n/** `{ success: false }` codes the hub returns on `/exec`. */\nexport type HubExecErrorCode =\n | 'HUB_APPROVAL_REQUIRED'\n | 'HUB_POLICY_DENIED'\n | 'HUB_CONNECTION_MISSING'\n | 'HUB_CONNECTION_REVOKED'\n | 'HUB_CONFIG_MISSING'\n | 'HUB_NOT_FOUND'\n | string\n\n/** Outcome of a hub `/exec` call. Callers MUST inspect `succeeded` before\n * reading `result` — a denied or approval-gated write resolves with\n * `succeeded: false` and a populated `code`, never a thrown silent failure. */\nexport type HubExecResult =\n | { succeeded: true; result: unknown }\n | { succeeded: false; code: HubExecErrorCode; message: string; approval?: unknown }\n\n/** Define configuration options for initializing a Hub execution client */\nexport interface HubExecClientOptions {\n /** Platform base URL (e.g. `TANGLE_PLATFORM_URL`). */\n baseUrl: string\n /** Calling user's Tangle API key — the hub principal bearer. */\n bearer: string\n /** Test seam. Defaults to global `fetch`. */\n fetchImpl?: typeof fetch\n}\n\n/** The provider/connector/action a hub action path addresses, plus the dotted\n * `path` the hub `/exec` endpoint expects. */\nexport interface ParsedIntegrationAction {\n providerId: string\n connectorId: string\n actionId: string\n /** `provider.connector.action`. */\n path: string\n}\n\n/**\n * Resolve an MCP tool name (the opaque `int_…` catalog name the agent calls)\n * into the dotted hub action path. Returns `undefined` when the name is not a\n * catalog integration tool, so the chat loop routes non-integration calls\n * elsewhere instead of misrouting them to the hub.\n */\nexport function resolveIntegrationAction(toolName: string): ParsedIntegrationAction | undefined {\n let parsed: { providerId: string; connectorId: string; actionId: string }\n try {\n parsed = parseIntegrationToolName(toolName)\n } catch {\n return undefined\n }\n if (!parsed.providerId || !parsed.connectorId || !parsed.actionId) return undefined\n return { ...parsed, path: `${parsed.providerId}.${parsed.connectorId}.${parsed.actionId}` }\n}\n\ninterface HubEnvelope {\n success: boolean\n data?: { result?: unknown }\n error?: { code?: string; message?: string; details?: { approval?: unknown } }\n}\n\n/** Typed client over the platform hub `/v1/hub/exec`. The hub holds the user's\n * credentials, resolves the connection from the bearer principal, evaluates\n * per-action policy (read → allow, write/destructive → approval), and runs the\n * action server-side. Never throws on a policy block — a gated write is a\n * normal `succeeded: false` outcome. */\nexport class HubExecClient {\n private readonly baseUrl: string\n private readonly bearer: string\n private readonly fetchImpl: typeof fetch\n\n constructor(options: HubExecClientOptions) {\n if (!options.baseUrl) throw new Error('HubExecClient: baseUrl is required')\n if (!options.bearer) throw new Error('HubExecClient: bearer is required')\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.bearer = options.bearer\n this.fetchImpl = options.fetchImpl ?? fetch\n }\n\n async exec(input: { path: string; actionInput?: unknown; connectionId?: string }): Promise<HubExecResult> {\n const response = await this.fetchImpl(`${this.baseUrl}/v1/hub/exec`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.bearer}`, 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({ path: input.path, input: input.actionInput, connectionId: input.connectionId }),\n })\n const envelope = await this.readEnvelope(response)\n if (response.ok && envelope.success) return { succeeded: true, result: envelope.data?.result }\n return {\n succeeded: false,\n code: envelope.error?.code ?? `HUB_HTTP_${response.status}`,\n message: envelope.error?.message ?? `Hub /exec returned ${response.status}`,\n approval: envelope.error?.details?.approval,\n }\n }\n\n private async readEnvelope(response: Response): Promise<HubEnvelope> {\n const text = await response.text()\n if (!text) return { success: false, error: { code: `HUB_HTTP_${response.status}`, message: `Hub returned ${response.status} with no body` } }\n try {\n return JSON.parse(text) as HubEnvelope\n } catch {\n return { success: false, error: { code: 'HUB_BAD_RESPONSE', message: `Hub returned non-JSON (${response.status}): ${text.slice(0, 200)}` } }\n }\n }\n}\n\n/** Define input parameters for invoking a hub tool with user ID, tool name, and optional arguments */\nexport interface HubInvokeInput {\n userId: string\n /** The MCP tool name the agent called (`int_<provider>_<connector>_<action>`). */\n toolName: string\n args?: Record<string, unknown>\n}\n/** Describe the outcome of a hub invocation including status and response body */\nexport interface HubInvokeOutcome {\n status: number\n body: Record<string, unknown>\n}\n/** Define dependencies for invoking hub operations including API key resolution and optional configuration */\nexport interface HubInvokeDeps {\n /** Resolve the user's Tangle API key (the hub principal bearer). Required —\n * the product binds its own session-key resolver. Null → user not linked. */\n apiKeyResolver: (userId: string) => Promise<string | null>\n /** Platform base URL. Defaults to `env.TANGLE_PLATFORM_URL`. */\n baseUrl?: string\n fetchImpl?: typeof fetch\n env?: Record<string, string | undefined>\n}\n\n/**\n * Resolve + execute one integration tool call through the hub: resolve the\n * per-user bearer, map the MCP tool name to the hub action path, forward to\n * `/v1/hub/exec`, and shape the route response (200 ok / 401 not-linked /\n * 400 unknown-tool / 409 approval-required / 502 hub-error). A write that's\n * approval-gated surfaces verbatim as 409, never silently executed.\n */\nexport async function invokeIntegrationHub(input: HubInvokeInput, deps: HubInvokeDeps): Promise<HubInvokeOutcome> {\n const env = deps.env ?? (process.env as Record<string, string | undefined>)\n const baseUrl = deps.baseUrl ?? env.TANGLE_PLATFORM_URL?.trim()\n if (!baseUrl) return { status: 500, body: { error: 'TANGLE_PLATFORM_URL is not configured' } }\n\n const action = resolveIntegrationAction(input.toolName)\n if (!action) return { status: 400, body: { error: `Unsupported integration tool: ${input.toolName}` } }\n\n const bearer = await deps.apiKeyResolver(input.userId)\n if (!bearer) return { status: 401, body: { error: 'Tangle account not linked — connect integrations from the app first' } }\n\n const client = new HubExecClient({ baseUrl, bearer, fetchImpl: deps.fetchImpl })\n const outcome = await client.exec({ path: action.path, actionInput: input.args ?? {} })\n\n if (outcome.succeeded) {\n return { status: 200, body: { success: true, path: action.path, providerId: action.providerId, action: action.actionId, result: outcome.result } }\n }\n const status = outcome.code === 'HUB_APPROVAL_REQUIRED' ? 409 : 502\n return {\n status,\n body: { success: false, path: action.path, code: outcome.code, error: outcome.message, ...(outcome.approval ? { approval: outcome.approval } : {}) },\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/integrations/index.ts"],"sourcesContent":["/**\n * Integration-hub WIRING — the agent→hub invocation path every Tangle agent app\n * forks. The integration ENGINE (catalog, connectors, OAuth, policy) is\n * `@tangle-network/agent-integrations` (a peer dependency); this module is the\n * thin app-side wiring on top: an outcome adapter over hub-sdk's\n * `POST /v1/hub/exec`, MCP-tool-name → hub-action-path resolution, and the\n * per-turn invoke flow the `integration_invoke` tool calls.\n *\n * The product supplies its own catalog (which connectors it uses) and its own\n * per-user api-key resolver (the `apiKeyResolver` seam) — this module owns\n * neither credentials nor the action catalog.\n */\nimport { HubClient, HubSdkError } from '@tangle-network/hub-sdk'\nimport { parseIntegrationToolName } from '@tangle-network/agent-integrations/catalog'\n\n/** `{ success: false }` codes the hub returns on `/exec`. */\nexport type HubExecErrorCode =\n | 'HUB_APPROVAL_REQUIRED'\n | 'HUB_POLICY_DENIED'\n | 'HUB_CONNECTION_MISSING'\n | 'HUB_CONNECTION_REVOKED'\n | 'HUB_CONFIG_MISSING'\n | 'HUB_NOT_FOUND'\n | string\n\n/** Outcome of a hub `/exec` call. Callers MUST inspect `succeeded` before\n * reading `result` — a denied or approval-gated write resolves with\n * `succeeded: false` and a populated `code`, never a thrown silent failure. */\nexport type HubExecResult =\n | { succeeded: true; result: unknown }\n | { succeeded: false; code: HubExecErrorCode; message: string; approval?: unknown }\n\n/** Define configuration options for initializing a Hub execution client */\nexport interface HubExecClientOptions {\n /** Platform base URL (e.g. `TANGLE_PLATFORM_URL`). */\n baseUrl: string\n /** Calling user's Tangle API key — the hub principal bearer. */\n bearer: string\n /** Test seam. Defaults to global `fetch`. */\n fetchImpl?: typeof fetch\n}\n\n/** The provider/connector/action a hub action path addresses, plus the dotted\n * `path` the hub `/exec` endpoint expects. */\nexport interface ParsedIntegrationAction {\n providerId: string\n connectorId: string\n actionId: string\n /** `provider.connector.action`. */\n path: string\n}\n\n/**\n * Resolve an MCP tool name (the opaque `int_…` catalog name the agent calls)\n * into the dotted hub action path. Returns `undefined` when the name is not a\n * catalog integration tool, so the chat loop routes non-integration calls\n * elsewhere instead of misrouting them to the hub.\n */\nexport function resolveIntegrationAction(toolName: string): ParsedIntegrationAction | undefined {\n let parsed: { providerId: string; connectorId: string; actionId: string }\n try {\n parsed = parseIntegrationToolName(toolName)\n } catch {\n return undefined\n }\n if (!parsed.providerId || !parsed.connectorId || !parsed.actionId) return undefined\n return { ...parsed, path: `${parsed.providerId}.${parsed.connectorId}.${parsed.actionId}` }\n}\n\n/** Product outcome adapter over the published Hub transport. Never grants approval. */\nexport class HubExecClient {\n private readonly hub: HubClient\n\n constructor(options: HubExecClientOptions) {\n if (!options.baseUrl) throw new Error('HubExecClient: baseUrl is required')\n if (!options.bearer) throw new Error('HubExecClient: bearer is required')\n this.hub = new HubClient({ baseUrl: options.baseUrl, apiKey: options.bearer, fetch: options.fetchImpl })\n }\n\n async exec(input: { path: string; actionInput?: unknown; connectionId?: string }): Promise<HubExecResult> {\n try {\n // No approve flag. Hub still decides whether this exact action is permitted.\n const result = await this.hub.tools.invoke(input.path, input.actionInput, { connectionId: input.connectionId })\n return { succeeded: true, result: result.result }\n } catch (error) {\n if (!(error instanceof HubSdkError)) throw error\n const details = error.details\n const approval = details && typeof details === 'object' && 'approval' in details ? details.approval : undefined\n return { succeeded: false, code: error.code, message: error.message, ...(approval !== undefined ? { approval } : {}) }\n }\n }\n}\n\n/** Define input parameters for invoking a hub tool with user ID, tool name, and optional arguments */\nexport interface HubInvokeInput {\n userId: string\n /** The MCP tool name the agent called (`int_<provider>_<connector>_<action>`). */\n toolName: string\n args?: Record<string, unknown>\n}\n/** Describe the outcome of a hub invocation including status and response body */\nexport interface HubInvokeOutcome {\n status: number\n body: Record<string, unknown>\n}\n/** Define dependencies for invoking hub operations including API key resolution and optional configuration */\nexport interface HubInvokeDeps {\n /** Resolve the user's Tangle API key (the hub principal bearer). Required —\n * the product binds its own session-key resolver. Null → user not linked. */\n apiKeyResolver: (userId: string) => Promise<string | null>\n /** Platform base URL. Defaults to `env.TANGLE_PLATFORM_URL`. */\n baseUrl?: string\n fetchImpl?: typeof fetch\n env?: Record<string, string | undefined>\n}\n\n/**\n * Resolve + execute one integration tool call through the hub: resolve the\n * per-user bearer, map the MCP tool name to the hub action path, forward to\n * `/v1/hub/exec`, and shape the route response (200 ok / 401 not-linked /\n * 400 unknown-tool / 409 approval-required / 502 hub-error). A write that's\n * approval-gated surfaces verbatim as 409, never silently executed.\n */\nexport async function invokeIntegrationHub(input: HubInvokeInput, deps: HubInvokeDeps): Promise<HubInvokeOutcome> {\n const env = deps.env ?? (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {}\n const baseUrl = deps.baseUrl ?? env.TANGLE_PLATFORM_URL?.trim()\n if (!baseUrl) return { status: 500, body: { error: 'TANGLE_PLATFORM_URL is not configured' } }\n\n const action = resolveIntegrationAction(input.toolName)\n if (!action) return { status: 400, body: { error: `Unsupported integration tool: ${input.toolName}` } }\n\n const bearer = await deps.apiKeyResolver(input.userId)\n if (!bearer) return { status: 401, body: { error: 'Tangle account not linked — connect integrations from the app first' } }\n\n const client = new HubExecClient({ baseUrl, bearer, fetchImpl: deps.fetchImpl })\n const outcome = await client.exec({ path: action.path, actionInput: input.args ?? {} })\n\n if (outcome.succeeded) {\n return { status: 200, body: { success: true, path: action.path, providerId: action.providerId, action: action.actionId, result: outcome.result } }\n }\n const status = outcome.code === 'HUB_APPROVAL_REQUIRED' ? 409 : 502\n return {\n status,\n body: { success: false, path: action.path, code: outcome.code, error: outcome.message, ...(outcome.approval ? { approval: outcome.approval } : {}) },\n }\n}\n"],"mappings":";AAYA,SAAS,WAAW,mBAAmB;AACvC,SAAS,gCAAgC;AA6ClC,SAAS,yBAAyB,UAAuD;AAC9F,MAAI;AACJ,MAAI;AACF,aAAS,yBAAyB,QAAQ;AAAA,EAC5C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,OAAO,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,SAAU,QAAO;AAC1E,SAAO,EAAE,GAAG,QAAQ,MAAM,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW,IAAI,OAAO,QAAQ,GAAG;AAC5F;AAGO,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EAEjB,YAAY,SAA+B;AACzC,QAAI,CAAC,QAAQ,QAAS,OAAM,IAAI,MAAM,oCAAoC;AAC1E,QAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,MAAM,mCAAmC;AACxE,SAAK,MAAM,IAAI,UAAU,EAAE,SAAS,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,UAAU,CAAC;AAAA,EACzG;AAAA,EAEA,MAAM,KAAK,OAA+F;AACxG,QAAI;AAEF,YAAM,SAAS,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM,MAAM,MAAM,aAAa,EAAE,cAAc,MAAM,aAAa,CAAC;AAC9G,aAAO,EAAE,WAAW,MAAM,QAAQ,OAAO,OAAO;AAAA,IAClD,SAAS,OAAO;AACd,UAAI,EAAE,iBAAiB,aAAc,OAAM;AAC3C,YAAM,UAAU,MAAM;AACtB,YAAM,WAAW,WAAW,OAAO,YAAY,YAAY,cAAc,UAAU,QAAQ,WAAW;AACtG,aAAO,EAAE,WAAW,OAAO,MAAM,MAAM,MAAM,SAAS,MAAM,SAAS,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC,EAAG;AAAA,IACvH;AAAA,EACF;AACF;AAgCA,eAAsB,qBAAqB,OAAuB,MAAgD;AAChH,QAAM,MAAM,KAAK,OAAQ,WAA0E,SAAS,OAAO,CAAC;AACpH,QAAM,UAAU,KAAK,WAAW,IAAI,qBAAqB,KAAK;AAC9D,MAAI,CAAC,QAAS,QAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,OAAO,wCAAwC,EAAE;AAE7F,QAAM,SAAS,yBAAyB,MAAM,QAAQ;AACtD,MAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,OAAO,iCAAiC,MAAM,QAAQ,GAAG,EAAE;AAEtG,QAAM,SAAS,MAAM,KAAK,eAAe,MAAM,MAAM;AACrD,MAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,OAAO,2EAAsE,EAAE;AAE1H,QAAM,SAAS,IAAI,cAAc,EAAE,SAAS,QAAQ,WAAW,KAAK,UAAU,CAAC;AAC/E,QAAM,UAAU,MAAM,OAAO,KAAK,EAAE,MAAM,OAAO,MAAM,aAAa,MAAM,QAAQ,CAAC,EAAE,CAAC;AAEtF,MAAI,QAAQ,WAAW;AACrB,WAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,SAAS,MAAM,MAAM,OAAO,MAAM,YAAY,OAAO,YAAY,QAAQ,OAAO,UAAU,QAAQ,QAAQ,OAAO,EAAE;AAAA,EACnJ;AACA,QAAM,SAAS,QAAQ,SAAS,0BAA0B,MAAM;AAChE,SAAO;AAAA,IACL;AAAA,IACA,MAAM,EAAE,SAAS,OAAO,MAAM,OAAO,MAAM,MAAM,QAAQ,MAAM,OAAO,QAAQ,SAAS,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC,EAAG;AAAA,EACrJ;AACF;","names":[]}
|
package/dist/platform/index.js
CHANGED
package/dist/platform/sso.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* never imports agent-runtime, an auth framework, or a database driver.
|
|
7
|
-
* WebCrypto only — runs in workerd without node compatibility flags.
|
|
2
|
+
* Tangle SSO for agent apps. OIDC callers use the registered runtime client
|
|
3
|
+
* with S256 PKCE. Legacy API-key callers remain explicit during migration.
|
|
4
|
+
* OIDC never falls back to cross-site exchange. Account matching and native
|
|
5
|
+
* session cookie minting remain shared.
|
|
8
6
|
*/
|
|
9
7
|
/** Define configuration options for managing SSO state including secret, lifetime, and clock injection */
|
|
10
8
|
export interface SsoStateConfig {
|
|
@@ -43,6 +41,39 @@ export interface TangleSsoAuthClient {
|
|
|
43
41
|
}): string;
|
|
44
42
|
exchange(code: string): Promise<TangleSsoExchangeResult>;
|
|
45
43
|
}
|
|
44
|
+
/** Token fields the callback persists from the runtime's OIDC exchange. */
|
|
45
|
+
export interface TangleOidcSsoTokens {
|
|
46
|
+
accessToken: string;
|
|
47
|
+
tokenType: string;
|
|
48
|
+
expiresIn?: number;
|
|
49
|
+
refreshToken?: string;
|
|
50
|
+
idToken?: string;
|
|
51
|
+
scope?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Verified identity returned by the runtime's OIDC userinfo lookup. */
|
|
54
|
+
export interface TangleOidcSsoUser {
|
|
55
|
+
id: string;
|
|
56
|
+
email: string;
|
|
57
|
+
emailVerified: true;
|
|
58
|
+
name?: string;
|
|
59
|
+
}
|
|
60
|
+
/** The caller needs only the OIDC operations used by this callback. Keeping
|
|
61
|
+
* this structural prevents legacy consumers from needing the optional
|
|
62
|
+
* Runtime peer just to load the SSO declarations. */
|
|
63
|
+
export interface TangleOidcSsoAuthClient {
|
|
64
|
+
authorizeUrl(options: {
|
|
65
|
+
state: string;
|
|
66
|
+
codeChallenge: string;
|
|
67
|
+
nonce?: string;
|
|
68
|
+
prompt?: 'login' | 'consent' | 'none';
|
|
69
|
+
loginHint?: string;
|
|
70
|
+
}): string;
|
|
71
|
+
exchange(code: string, codeVerifier: string): Promise<{
|
|
72
|
+
tokens: TangleOidcSsoTokens;
|
|
73
|
+
user: TangleOidcSsoUser;
|
|
74
|
+
}>;
|
|
75
|
+
revoke(token: string, tokenTypeHint?: 'access_token' | 'refresh_token'): Promise<void>;
|
|
76
|
+
}
|
|
46
77
|
/** Local account shape required by the verified Tangle SSO account policy.
|
|
47
78
|
* Consumers map their user/link tables into this shape before resolving an
|
|
48
79
|
* exchange. */
|
|
@@ -148,6 +179,30 @@ export interface TangleSsoAccountStore {
|
|
|
148
179
|
planTier: string | null;
|
|
149
180
|
}): Promise<void>;
|
|
150
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* OIDC tokens are server-side credentials, not API keys. Implementations must
|
|
184
|
+
* encrypt them at rest and bind them to the exact local session. Refresh and
|
|
185
|
+
* disconnect must serialize updates to the same grant.
|
|
186
|
+
*/
|
|
187
|
+
export interface TangleOidcSsoAccountStore extends Omit<TangleSsoAccountStore, 'saveTangleLink'> {
|
|
188
|
+
/** Atomically bind the grant to this exact session. If persistence partially
|
|
189
|
+
* fails, `deleteSession` must remove every row and credential for it. */
|
|
190
|
+
saveTangleLink(input: {
|
|
191
|
+
userId: string;
|
|
192
|
+
sessionToken: string;
|
|
193
|
+
tangleUserId: string;
|
|
194
|
+
email: string;
|
|
195
|
+
name: string | null;
|
|
196
|
+
tokens: TangleOidcSsoTokens;
|
|
197
|
+
accessTokenExpiresAt: Date;
|
|
198
|
+
}): Promise<void>;
|
|
199
|
+
/** Remove an unpublished local session if callback persistence fails.
|
|
200
|
+
* This must also remove any grant rows or credentials partially written by
|
|
201
|
+
* a rejected `saveTangleLink`, keyed by this exact session token. */
|
|
202
|
+
deleteSession(input: {
|
|
203
|
+
sessionToken: string;
|
|
204
|
+
}): Promise<void>;
|
|
205
|
+
}
|
|
151
206
|
/** Successful-login context handed to the `setSessionCookie` seam. */
|
|
152
207
|
export interface TangleSsoSessionCookieArgs {
|
|
153
208
|
/** Session token returned by `store.createSession`. */
|
|
@@ -221,6 +276,7 @@ export interface BetterAuthSessionCookieMinterOptions {
|
|
|
221
276
|
export declare function createBetterAuthSessionCookieMinter(auth: BetterAuthSessionCookieSource, options?: BetterAuthSessionCookieMinterOptions): (args: TangleSsoSessionCookieArgs) => Promise<string[]>;
|
|
222
277
|
/** Define configuration options for handling Tangle SSO authentication and session management */
|
|
223
278
|
export interface TangleSsoHandlerOptions {
|
|
279
|
+
protocol?: 'legacy';
|
|
224
280
|
auth: TangleSsoAuthClient;
|
|
225
281
|
store: TangleSsoAccountStore;
|
|
226
282
|
/** HMAC secret for the state cookie. */
|
|
@@ -262,6 +318,17 @@ export interface TangleSsoHandlerOptions {
|
|
|
262
318
|
log?: (message: string, error?: unknown) => void;
|
|
263
319
|
now?: () => number;
|
|
264
320
|
}
|
|
321
|
+
/** Registered OIDC client and token store. The OIDC caller supplies the Runtime PKCE helper. */
|
|
322
|
+
export interface TangleOidcSsoHandlerOptions extends Omit<TangleSsoHandlerOptions, 'protocol' | 'auth' | 'store'> {
|
|
323
|
+
protocol: 'oidc';
|
|
324
|
+
auth: TangleOidcSsoAuthClient;
|
|
325
|
+
store: TangleOidcSsoAccountStore;
|
|
326
|
+
/** Pass the Runtime's `createPkcePair` helper from the OIDC-only caller. */
|
|
327
|
+
createPkcePair: () => Promise<{
|
|
328
|
+
verifier: string;
|
|
329
|
+
challenge: string;
|
|
330
|
+
}>;
|
|
331
|
+
}
|
|
265
332
|
/** Define handlers for SSO start and callback routes managing authentication flow and session cookies */
|
|
266
333
|
export interface TangleSsoHandlers {
|
|
267
334
|
/** GET start route: mint + sign state, set the state cookie, 302 to the
|
|
@@ -275,4 +342,4 @@ export interface TangleSsoHandlers {
|
|
|
275
342
|
callback(request: Request): Promise<Response>;
|
|
276
343
|
}
|
|
277
344
|
/** Create Tangle SSO handlers to manage authentication state, callbacks, and session cookies */
|
|
278
|
-
export declare function createTangleSsoHandlers(opts: TangleSsoHandlerOptions): TangleSsoHandlers;
|
|
345
|
+
export declare function createTangleSsoHandlers(opts: TangleSsoHandlerOptions | TangleOidcSsoHandlerOptions): TangleSsoHandlers;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AgentProfile, AgentProfileFileMount } from '@tangle-network/agent-interface';
|
|
2
|
+
/** Unicode code-point budgets. The installed writer also bounds UTF-8 bytes. */
|
|
3
|
+
export declare const DEFAULT_HOME_LIMITS: {
|
|
4
|
+
readonly 'AGENTS.md': 12000;
|
|
5
|
+
readonly 'SOUL.md': 8000;
|
|
6
|
+
readonly 'IDENTITY.md': 4000;
|
|
7
|
+
readonly 'USER.md': 4000;
|
|
8
|
+
readonly 'MEMORY.md': 16000;
|
|
9
|
+
readonly 'BOOTSTRAP.md': 8000;
|
|
10
|
+
readonly dailyNote: 12000;
|
|
11
|
+
readonly skill: 32000;
|
|
12
|
+
};
|
|
13
|
+
export declare const DEFAULT_AGENT_HOME = "/var/lib/tangle-agent/home";
|
|
14
|
+
/** Canonical seed records. The trusted image installer materializes these once. */
|
|
15
|
+
export declare function defaultHomeFiles(): AgentProfileFileMount[];
|
|
16
|
+
/**
|
|
17
|
+
* Compose home instructions, not per-turn writes to persistent memory.
|
|
18
|
+
* The published install-home command consumes defaultHomeFiles with the same
|
|
19
|
+
* inline resource format as other profile seeds. A privileged, one-time phase
|
|
20
|
+
* is necessary: ordinary resources.files writes run as the agent and cannot
|
|
21
|
+
* make a same-UID shell respect caps or platform file ownership.
|
|
22
|
+
*/
|
|
23
|
+
export declare function withDefaultAgentHome(profile: AgentProfile): AgentProfile;
|
package/dist/profile/index.d.ts
CHANGED
|
@@ -172,3 +172,4 @@ export { assertSkillDeliveryDisjoint, composeShellResources, composeSkills, corp
|
|
|
172
172
|
export type { ComposedSkills, ComposeShellResourcesInput, CorpusEntry, CorpusLoadResult, GlobModules, LoadCorpusOptions, ParsedSkill, SkillDeliveryMode, SkillEntry, SkillFrontmatter, } from '../skills/index';
|
|
173
173
|
export { diffProfileFingerprints, fingerprintAgentProfile, formatProfileDrift, } from './fingerprint';
|
|
174
174
|
export type { ProfileDrift, ProfileDriftEntry, ProfileFingerprint, ProfileFingerprintContext, } from './fingerprint';
|
|
175
|
+
export { DEFAULT_HOME_LIMITS, defaultHomeFiles, withDefaultAgentHome } from './home';
|
package/dist/profile/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_HOME_LIMITS,
|
|
3
|
+
defaultHomeFiles,
|
|
4
|
+
withDefaultAgentHome
|
|
5
|
+
} from "../chunk-6CV66YOB.js";
|
|
1
6
|
import {
|
|
2
7
|
assertSkillDeliveryDisjoint,
|
|
3
8
|
composeShellResources,
|
|
@@ -86,6 +91,7 @@ function makeEvolvableSection(input) {
|
|
|
86
91
|
return { id: input.id, title: input.title, body, evolvable: true };
|
|
87
92
|
}
|
|
88
93
|
export {
|
|
94
|
+
DEFAULT_HOME_LIMITS,
|
|
89
95
|
DEFAULT_MAX_SYSTEM_PROMPT_BYTES,
|
|
90
96
|
assertProfilePromptWithinBudget,
|
|
91
97
|
assertSkillDeliveryDisjoint,
|
|
@@ -94,6 +100,7 @@ export {
|
|
|
94
100
|
composeShellResources,
|
|
95
101
|
composeSkills,
|
|
96
102
|
corpusSkills,
|
|
103
|
+
defaultHomeFiles,
|
|
97
104
|
diffProfileFingerprints,
|
|
98
105
|
fingerprintAgentProfile,
|
|
99
106
|
formatProfileDrift,
|
|
@@ -111,6 +118,7 @@ export {
|
|
|
111
118
|
skillMountPath,
|
|
112
119
|
skillRefs,
|
|
113
120
|
stripComments,
|
|
114
|
-
userSkillMounts
|
|
121
|
+
userSkillMounts,
|
|
122
|
+
withDefaultAgentHome
|
|
115
123
|
};
|
|
116
124
|
//# sourceMappingURL=index.js.map
|