@agent-native/core 0.19.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +12 -1
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +5 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +17 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +210 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts +2 -0
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +39 -1
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-runs.d.ts +3 -0
- package/dist/cli/code-agent-runs.d.ts.map +1 -1
- package/dist/cli/code-agent-runs.js +3 -0
- package/dist/cli/code-agent-runs.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +8 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +58 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +122 -50
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +172 -13
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-sidebar-state.d.ts +2 -0
- package/dist/client/agent-sidebar-state.d.ts.map +1 -1
- package/dist/client/agent-sidebar-state.js +32 -0
- package/dist/client/agent-sidebar-state.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.d.ts +81 -0
- package/dist/client/code-agent-chat-adapter.d.ts.map +1 -0
- package/dist/client/code-agent-chat-adapter.js +297 -0
- package/dist/client/code-agent-chat-adapter.js.map +1 -0
- package/dist/client/composer/PromptComposer.d.ts +11 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +7 -3
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +12 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +16 -7
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/index.d.ts +1 -0
- package/dist/client/composer/index.d.ts.map +1 -1
- package/dist/client/composer/index.js +1 -0
- package/dist/client/composer/index.js.map +1 -1
- package/dist/client/composer/prompt-attachments.d.ts +11 -0
- package/dist/client/composer/prompt-attachments.d.ts.map +1 -0
- package/dist/client/composer/prompt-attachments.js +45 -0
- package/dist/client/composer/prompt-attachments.js.map +1 -0
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/client/conversation/AgentConversation.js +124 -1
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.d.ts +25 -0
- package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -0
- package/dist/client/conversation/code-agent-transcript.js +200 -0
- package/dist/client/conversation/code-agent-transcript.js.map +1 -0
- package/dist/client/conversation/index.d.ts +2 -1
- package/dist/client/conversation/index.d.ts.map +1 -1
- package/dist/client/conversation/index.js +1 -0
- package/dist/client/conversation/index.js.map +1 -1
- package/dist/client/conversation/types.d.ts +8 -0
- package/dist/client/conversation/types.d.ts.map +1 -1
- package/dist/client/conversation/types.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +26 -9
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/client/index.d.ts +5 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +2 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +21 -3
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.spec.js +16 -2
- package/dist/client/settings/useBuilderStatus.spec.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +3 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-chat-models.d.ts +6 -1
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +7 -3
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/client/use-chat-models.spec.d.ts +2 -0
- package/dist/client/use-chat-models.spec.d.ts.map +1 -0
- package/dist/client/use-chat-models.spec.js +39 -0
- package/dist/client/use-chat-models.spec.js.map +1 -0
- package/dist/code-agents/background-controller.d.ts.map +1 -1
- package/dist/code-agents/background-controller.js +16 -0
- package/dist/code-agents/background-controller.js.map +1 -1
- package/dist/code-agents/index.d.ts +2 -0
- package/dist/code-agents/index.d.ts.map +1 -1
- package/dist/code-agents/index.js +2 -0
- package/dist/code-agents/index.js.map +1 -1
- package/dist/code-agents/prompt-attachments.d.ts +11 -0
- package/dist/code-agents/prompt-attachments.d.ts.map +1 -0
- package/dist/code-agents/prompt-attachments.js +23 -0
- package/dist/code-agents/prompt-attachments.js.map +1 -0
- package/dist/code-agents/transcript-normalizer.js +14 -5
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/code-agents/transcript-order.d.ts +16 -0
- package/dist/code-agents/transcript-order.d.ts.map +1 -0
- package/dist/code-agents/transcript-order.js +47 -0
- package/dist/code-agents/transcript-order.js.map +1 -0
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +18 -14
- package/dist/extensions/routes.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +6 -2
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/secrets/substitution.d.ts +18 -0
- package/dist/secrets/substitution.d.ts.map +1 -1
- package/dist/secrets/substitution.js +74 -0
- package/dist/secrets/substitution.js.map +1 -1
- package/dist/server/deep-link.d.ts +0 -18
- package/dist/server/deep-link.d.ts.map +1 -1
- package/dist/server/deep-link.js +2 -1
- package/dist/server/deep-link.js.map +1 -1
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +23 -4
- package/dist/server/open-route.js.map +1 -1
- package/dist/shared/agent-sidebar-url.d.ts +6 -0
- package/dist/shared/agent-sidebar-url.d.ts.map +1 -0
- package/dist/shared/agent-sidebar-url.js +37 -0
- package/dist/shared/agent-sidebar-url.js.map +1 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/styles/agent-conversation.css +403 -0
- package/dist/styles/agent-native.css +2 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"substitution.js","sourceRoot":"","sources":["../../src/secrets/substitution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-side key substitution for automation tools.\n *\n * Resolves `${keys.NAME}` references in user-supplied strings (URLs, headers,\n * bodies, etc.) by looking up the named secret at tool-dispatch time. The\n * raw secret value NEVER enters the model's context — substitution happens\n * after the agent emits its tool call and before the request is dispatched.\n *\n * SECURITY — workspace-scope fallback (audit 05 H2):\n *\n * The user→workspace fallback is OPT-IN via the\n * `AGENT_NATIVE_KEYS_WORKSPACE_FALLBACK=1` env flag. Default OFF.\n *\n * When a user (any org member) writes a workspace-scoped `OPENAI_API_KEY`,\n * a default-on fallback would let every other org member's tools that\n * reference `${keys.OPENAI_API_KEY}` start using the malicious key\n * (key-skimming, mirror requests, billing hijack). The previous\n * fix-wave gated workspace-scope WRITES behind an org-admin check; this\n * file is the read-side defense-in-depth.\n *\n * When the env flag is unset, `resolveKeyReferences(\"user\", scopeId)`\n * queries ONLY user-scope rows. Tools/automations that need shared\n * defaults must explicitly look up via `scope: \"workspace\"`. Most\n * installs benefit from the stricter default — opt in only after the\n * org-admin write-gate is verified to be active.\n */\n\nimport { readAppSecret, readAppSecretMeta } from \"./storage.js\";\nimport type { SecretScope } from \"./register.js\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\n\nconst KEY_REFERENCE_REGEX = /\\$\\{keys\\.([A-Za-z0-9_-]+)\\}/g;\n\nfunction isWorkspaceFallbackEnabled(): boolean {\n const v = process.env.AGENT_NATIVE_KEYS_WORKSPACE_FALLBACK;\n if (!v) return false;\n const normalized = v.trim().toLowerCase();\n return (\n normalized === \"1\" ||\n normalized === \"true\" ||\n normalized === \"yes\" ||\n normalized === \"on\"\n );\n}\n\nexport interface ResolveKeyReferencesResult {\n resolved: string;\n usedKeys: string[];\n secretValues: string[];\n}\n\n/**\n * Resolve `${keys.NAME}` references in `text`. For each reference, looks up\n * the named secret at the given scope, falling back to workspace-scope when\n * the user-scope row doesn't exist. Throws when a referenced key is missing\n * so the agent receives a clear error rather than dispatching with the\n * literal placeholder.\n */\nexport async function resolveKeyReferences(\n text: string,\n scope: SecretScope,\n scopeId: string,\n): Promise<ResolveKeyReferencesResult> {\n const usedKeys: string[] = [];\n const matches = Array.from(text.matchAll(KEY_REFERENCE_REGEX));\n if (matches.length === 0) {\n return { resolved: text, usedKeys, secretValues: [] };\n }\n\n const resolutions = new Map<string, string>();\n const secretValues: string[] = [];\n const workspaceFallbackEnabled = isWorkspaceFallbackEnabled();\n for (const match of matches) {\n const name = match[1];\n if (resolutions.has(name)) continue;\n\n let result = await readAppSecret({ key: name, scope, scopeId });\n // SECURITY (audit 05 H2): user→workspace fallback is opt-in. Default\n // off prevents one malicious org member from poisoning every other\n // member's `${keys.NAME}` resolution with a workspace-scoped value.\n if (!result && scope === \"user\" && workspaceFallbackEnabled) {\n result = await readAppSecret({\n key: name,\n scope: \"workspace\",\n scopeId: getWorkspaceScopeId(scopeId),\n });\n }\n if (!result) {\n throw new Error(\n `Referenced key \"${name}\" is not defined for scope \"${scope}\". Create it in Settings or via the secrets API before using this automation.`,\n );\n }\n resolutions.set(name, result.value);\n usedKeys.push(name);\n if (result.value) secretValues.push(result.value);\n }\n\n const resolved = text.replace(KEY_REFERENCE_REGEX, (_, name: string) => {\n const value = resolutions.get(name);\n if (value === undefined) {\n throw new Error(`Referenced key \"${name}\" was not resolved`);\n }\n return value;\n });\n\n return { resolved, usedKeys, secretValues };\n}\n\n/**\n * Check if a URL is allowed by a key's URL allowlist. Returns true when no\n * allowlist is configured (permissive default — the allowlist is opt-in).\n *\n * Matching is exact on the URL's origin (scheme + host + port), so an entry\n * like `https://hooks.slack.com` blocks `https://evil.example.com` even if\n * the agent tries to redirect the request elsewhere.\n */\nexport function validateUrlAllowlist(\n url: string,\n allowlist: string[] | null,\n): boolean {\n if (!allowlist || allowlist.length === 0) return true;\n let origin: string;\n try {\n origin = new URL(url).origin;\n } catch {\n return false;\n }\n return allowlist.some((entry) => {\n try {\n return new URL(entry).origin === origin;\n } catch {\n return false;\n }\n });\n}\n\n/**\n * Convenience helper: look up a key's allowlist by name+scope. Returns null\n * when the key doesn't exist or has no allowlist configured.\n *\n * SECURITY: workspace fallback obeys the same opt-in flag as\n * `resolveKeyReferences` so the allowlist check stays consistent with the\n * resolved secret. If a future caller queries the allowlist for a key the\n * resolver wouldn't return, we'd risk allowing requests that the resolver\n * would refuse — keep them aligned.\n */\nexport async function getKeyAllowlist(\n name: string,\n scope: SecretScope,\n scopeId: string,\n): Promise<string[] | null> {\n let meta = await readAppSecretMeta({ key: name, scope, scopeId });\n if (!meta && scope === \"user\" && isWorkspaceFallbackEnabled()) {\n meta = await readAppSecretMeta({\n key: name,\n scope: \"workspace\",\n scopeId: getWorkspaceScopeId(scopeId),\n });\n }\n return meta?.urlAllowlist ?? null;\n}\n\nfunction getWorkspaceScopeId(userScopeId: string): string {\n const orgId = getRequestOrgId();\n if (orgId) return orgId;\n const email = getRequestUserEmail() || userScopeId;\n return `solo:${email}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"substitution.js","sourceRoot":"","sources":["../../src/secrets/substitution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-side key substitution for automation tools.\n *\n * Resolves `${keys.NAME}` references in user-supplied strings (URLs, headers,\n * bodies, etc.) by looking up the named secret at tool-dispatch time. The\n * raw secret value NEVER enters the model's context — substitution happens\n * after the agent emits its tool call and before the request is dispatched.\n *\n * SECURITY — workspace-scope fallback (audit 05 H2):\n *\n * The user→workspace fallback is OPT-IN via the\n * `AGENT_NATIVE_KEYS_WORKSPACE_FALLBACK=1` env flag. Default OFF.\n *\n * When a user (any org member) writes a workspace-scoped `OPENAI_API_KEY`,\n * a default-on fallback would let every other org member's tools that\n * reference `${keys.OPENAI_API_KEY}` start using the malicious key\n * (key-skimming, mirror requests, billing hijack). The previous\n * fix-wave gated workspace-scope WRITES behind an org-admin check; this\n * file is the read-side defense-in-depth.\n *\n * When the env flag is unset, `resolveKeyReferences(\"user\", scopeId)`\n * queries ONLY user-scope rows. Tools/automations that need shared\n * defaults must explicitly look up via `scope: \"workspace\"`. Most\n * installs benefit from the stricter default — opt in only after the\n * org-admin write-gate is verified to be active.\n */\n\nimport { readAppSecret, readAppSecretMeta } from \"./storage.js\";\nimport type { SecretScope } from \"./register.js\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\n\nconst KEY_REFERENCE_REGEX = /\\$\\{keys\\.([A-Za-z0-9_-]+)\\}/g;\n\nfunction isWorkspaceFallbackEnabled(): boolean {\n const v = process.env.AGENT_NATIVE_KEYS_WORKSPACE_FALLBACK;\n if (!v) return false;\n const normalized = v.trim().toLowerCase();\n return (\n normalized === \"1\" ||\n normalized === \"true\" ||\n normalized === \"yes\" ||\n normalized === \"on\"\n );\n}\n\nexport interface ResolveKeyReferencesResult {\n resolved: string;\n usedKeys: string[];\n secretValues: string[];\n resolvedKeys?: ResolvedKeyReference[];\n}\n\nexport interface ResolvedKeyReference {\n name: string;\n scope: SecretScope;\n scopeId: string;\n}\n\n/**\n * Resolve `${keys.NAME}` references in `text`. For each reference, looks up\n * the named secret at the given scope, falling back to workspace-scope when\n * the user-scope row doesn't exist. Throws when a referenced key is missing\n * so the agent receives a clear error rather than dispatching with the\n * literal placeholder.\n */\nexport async function resolveKeyReferences(\n text: string,\n scope: SecretScope,\n scopeId: string,\n): Promise<ResolveKeyReferencesResult> {\n const usedKeys: string[] = [];\n const matches = Array.from(text.matchAll(KEY_REFERENCE_REGEX));\n if (matches.length === 0) {\n return { resolved: text, usedKeys, secretValues: [] };\n }\n\n const resolutions = new Map<string, string>();\n const secretValues: string[] = [];\n const workspaceFallbackEnabled = isWorkspaceFallbackEnabled();\n for (const match of matches) {\n const name = match[1];\n if (resolutions.has(name)) continue;\n\n let result = await readAppSecret({ key: name, scope, scopeId });\n // SECURITY (audit 05 H2): user→workspace fallback is opt-in. Default\n // off prevents one malicious org member from poisoning every other\n // member's `${keys.NAME}` resolution with a workspace-scoped value.\n if (!result && scope === \"user\" && workspaceFallbackEnabled) {\n result = await readAppSecret({\n key: name,\n scope: \"workspace\",\n scopeId: getWorkspaceScopeId(scopeId),\n });\n }\n if (!result) {\n throw new Error(\n `Referenced key \"${name}\" is not defined for scope \"${scope}\". Create it in Settings or via the secrets API before using this automation.`,\n );\n }\n resolutions.set(name, result.value);\n usedKeys.push(name);\n if (result.value) secretValues.push(result.value);\n }\n\n const resolved = text.replace(KEY_REFERENCE_REGEX, (_, name: string) => {\n const value = resolutions.get(name);\n if (value === undefined) {\n throw new Error(`Referenced key \"${name}\" was not resolved`);\n }\n return value;\n });\n\n return { resolved, usedKeys, secretValues };\n}\n\n/**\n * Resolve `${keys.NAME}` for browser extension fetches and other request-bound\n * code paths that should honor the active workspace's shared credential store.\n *\n * Lookup order:\n * 1. user scope for personal overrides\n * 2. active org scope (Dispatch vault sync writes here for org workspaces)\n * 3. active org workspace scope (legacy shared rows)\n * 4. solo workspace scope when no org is active\n */\nexport async function resolveKeyReferencesWithRequestScopes(\n text: string,\n userScopeId: string,\n): Promise<ResolveKeyReferencesResult> {\n const usedKeys: string[] = [];\n const matches = Array.from(text.matchAll(KEY_REFERENCE_REGEX));\n if (matches.length === 0) {\n return { resolved: text, usedKeys, secretValues: [], resolvedKeys: [] };\n }\n\n const resolutions = new Map<string, string>();\n const resolvedKeys: ResolvedKeyReference[] = [];\n const secretValues: string[] = [];\n for (const match of matches) {\n const name = match[1];\n if (resolutions.has(name)) continue;\n\n const result = await readRequestScopedSecret(name, userScopeId);\n if (!result) {\n throw new Error(\n `Referenced key \"${name}\" is not defined for this user or active workspace. Create it in Settings or the Dispatch vault before using this extension.`,\n );\n }\n resolutions.set(name, result.value);\n usedKeys.push(name);\n resolvedKeys.push(result.ref);\n if (result.value) secretValues.push(result.value);\n }\n\n const resolved = text.replace(KEY_REFERENCE_REGEX, (_, name: string) => {\n const value = resolutions.get(name);\n if (value === undefined) {\n throw new Error(`Referenced key \"${name}\" was not resolved`);\n }\n return value;\n });\n\n return { resolved, usedKeys, secretValues, resolvedKeys };\n}\n\nasync function readRequestScopedSecret(\n name: string,\n userScopeId: string,\n): Promise<{ value: string; ref: ResolvedKeyReference } | null> {\n const candidates = requestSecretCandidates(userScopeId);\n for (const ref of candidates) {\n const result = await readAppSecret({ key: name, ...ref });\n if (result) return { value: result.value, ref: { name, ...ref } };\n }\n return null;\n}\n\nfunction requestSecretCandidates(\n userScopeId: string,\n): Array<{ scope: SecretScope; scopeId: string }> {\n const orgId = getRequestOrgId();\n if (orgId) {\n return [\n { scope: \"user\", scopeId: userScopeId },\n { scope: \"org\", scopeId: orgId },\n { scope: \"workspace\", scopeId: orgId },\n ];\n }\n\n const email = getRequestUserEmail() || userScopeId;\n return [\n { scope: \"user\", scopeId: userScopeId },\n { scope: \"workspace\", scopeId: `solo:${email}` },\n ];\n}\n\n/**\n * Check if a URL is allowed by a key's URL allowlist. Returns true when no\n * allowlist is configured (permissive default — the allowlist is opt-in).\n *\n * Matching is exact on the URL's origin (scheme + host + port), so an entry\n * like `https://hooks.slack.com` blocks `https://evil.example.com` even if\n * the agent tries to redirect the request elsewhere.\n */\nexport function validateUrlAllowlist(\n url: string,\n allowlist: string[] | null,\n): boolean {\n if (!allowlist || allowlist.length === 0) return true;\n let origin: string;\n try {\n origin = new URL(url).origin;\n } catch {\n return false;\n }\n return allowlist.some((entry) => {\n try {\n return new URL(entry).origin === origin;\n } catch {\n return false;\n }\n });\n}\n\n/**\n * Convenience helper: look up a key's allowlist by name+scope. Returns null\n * when the key doesn't exist or has no allowlist configured.\n *\n * SECURITY: workspace fallback obeys the same opt-in flag as\n * `resolveKeyReferences` so the allowlist check stays consistent with the\n * resolved secret. If a future caller queries the allowlist for a key the\n * resolver wouldn't return, we'd risk allowing requests that the resolver\n * would refuse — keep them aligned.\n */\nexport async function getKeyAllowlist(\n name: string,\n scope: SecretScope,\n scopeId: string,\n): Promise<string[] | null> {\n let meta = await readAppSecretMeta({ key: name, scope, scopeId });\n if (!meta && scope === \"user\" && isWorkspaceFallbackEnabled()) {\n meta = await readAppSecretMeta({\n key: name,\n scope: \"workspace\",\n scopeId: getWorkspaceScopeId(scopeId),\n });\n }\n return meta?.urlAllowlist ?? null;\n}\n\nexport async function getResolvedKeyAllowlist(\n ref: ResolvedKeyReference,\n): Promise<string[] | null> {\n const meta = await readAppSecretMeta({\n key: ref.name,\n scope: ref.scope,\n scopeId: ref.scopeId,\n });\n return meta?.urlAllowlist ?? null;\n}\n\nfunction getWorkspaceScopeId(userScopeId: string): string {\n const orgId = getRequestOrgId();\n if (orgId) return orgId;\n const email = getRequestUserEmail() || userScopeId;\n return `solo:${email}`;\n}\n"]}
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deep-link helpers — the single source of truth for the
|
|
3
|
-
* `/_agent-native/open` URL format.
|
|
4
|
-
*
|
|
5
|
-
* Every artifact-producing / list action that wants an external agent (MCP /
|
|
6
|
-
* A2A) to surface an "Open in <app> →" link returns
|
|
7
|
-
* `{ url: buildDeepLink(...), label }` from its `link` builder. The MCP layer
|
|
8
|
-
* turns the relative path into an absolute web URL (and an `agentnative://`
|
|
9
|
-
* desktop URL) using the request origin.
|
|
10
|
-
*
|
|
11
|
-
* The `/_agent-native/open` route (see `open-route.ts`) consumes these: it
|
|
12
|
-
* resolves the *browser session's* identity, writes the existing one-shot
|
|
13
|
-
* `navigate` application-state command, and 302-redirects to the rendered SPA
|
|
14
|
-
* view so any browser / inline webview lands on the right screen with the
|
|
15
|
-
* record focused. We never invent a new navigation mechanism — this just
|
|
16
|
-
* bridges external surfaces to the `navigate`/`application_state` contract the
|
|
17
|
-
* UI already drains every 2s.
|
|
18
|
-
*/
|
|
19
1
|
/** Path of the framework deep-link route, relative to the route prefix. */
|
|
20
2
|
export declare const OPEN_ROUTE_SUBPATH = "/open";
|
|
21
3
|
/** Custom URL scheme the desktop app registers (`agentnative://open?...`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-link.d.ts","sourceRoot":"","sources":["../../src/server/deep-link.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deep-link.d.ts","sourceRoot":"","sources":["../../src/server/deep-link.ts"],"names":[],"mappings":"AAoBA,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb;yEACqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtE;uDACmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAeD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAI1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAI1D"}
|
package/dist/server/deep-link.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* bridges external surfaces to the `navigate`/`application_state` contract the
|
|
17
17
|
* UI already drains every 2s.
|
|
18
18
|
*/
|
|
19
|
+
import { withCollapsedAgentSidebarParam } from "../shared/agent-sidebar-url.js";
|
|
19
20
|
/** Path of the framework deep-link route, relative to the route prefix. */
|
|
20
21
|
export const OPEN_ROUTE_SUBPATH = "/open";
|
|
21
22
|
/** Custom URL scheme the desktop app registers (`agentnative://open?...`). */
|
|
@@ -42,7 +43,7 @@ function buildQuery(input) {
|
|
|
42
43
|
* Per-app `link` builders call this; never hand-format the URL.
|
|
43
44
|
*/
|
|
44
45
|
export function buildDeepLink(input) {
|
|
45
|
-
return `/_agent-native${OPEN_ROUTE_SUBPATH}?${buildQuery(input)}
|
|
46
|
+
return withCollapsedAgentSidebarParam(`/_agent-native${OPEN_ROUTE_SUBPATH}?${buildQuery(input)}`);
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* Resolve a (possibly relative) deep link to an absolute web URL using the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-link.js","sourceRoot":"","sources":["../../src/server/deep-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"deep-link.js","sourceRoot":"","sources":["../../src/server/deep-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAEhF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAiBrD,SAAS,UAAU,CAAC,KAAoB;IACtC,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,IAAI,KAAK,CAAC,GAAG;QAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,KAAK,CAAC,EAAE;QAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,OAAO;QAAE,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACpD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE;YAAE,SAAS;QACxD,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAoB;IAChD,OAAO,8BAA8B,CACnC,iBAAiB,kBAAkB,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAC3D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,MAA0B;IAE1B,IAAI,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACjE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;AAC5F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,gBAAgB,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;AACnE,CAAC","sourcesContent":["/**\n * Deep-link helpers — the single source of truth for the\n * `/_agent-native/open` URL format.\n *\n * Every artifact-producing / list action that wants an external agent (MCP /\n * A2A) to surface an \"Open in <app> →\" link returns\n * `{ url: buildDeepLink(...), label }` from its `link` builder. The MCP layer\n * turns the relative path into an absolute web URL (and an `agentnative://`\n * desktop URL) using the request origin.\n *\n * The `/_agent-native/open` route (see `open-route.ts`) consumes these: it\n * resolves the *browser session's* identity, writes the existing one-shot\n * `navigate` application-state command, and 302-redirects to the rendered SPA\n * view so any browser / inline webview lands on the right screen with the\n * record focused. We never invent a new navigation mechanism — this just\n * bridges external surfaces to the `navigate`/`application_state` contract the\n * UI already drains every 2s.\n */\nimport { withCollapsedAgentSidebarParam } from \"../shared/agent-sidebar-url.js\";\n\n/** Path of the framework deep-link route, relative to the route prefix. */\nexport const OPEN_ROUTE_SUBPATH = \"/open\";\n\n/** Custom URL scheme the desktop app registers (`agentnative://open?...`). */\nexport const DESKTOP_OPEN_URL = \"agentnative://open\";\n\nexport interface DeepLinkInput {\n /** App id (informational + multi-app/desktop routing), e.g. \"mail\". */\n app?: string;\n /** Target view — maps to the `navigate` command `view`. */\n view: string;\n /** Record-focus + filter params, e.g. `{ threadId }`, `{ eventId, date }`,\n * `{ dashboardId }`. `undefined`/`null`/`\"\"` values are dropped. */\n params?: Record<string, string | number | boolean | null | undefined>;\n /** Explicit client-side path override (must be a same-origin, leading-slash\n * relative path — enforced by the open route). */\n to?: string;\n /** Base64url-encoded JSON compose draft (mail's `compose-{id}` contract). */\n compose?: string;\n}\n\nfunction buildQuery(input: DeepLinkInput): string {\n const sp = new URLSearchParams();\n if (input.app) sp.set(\"app\", input.app);\n sp.set(\"view\", input.view);\n if (input.to) sp.set(\"to\", input.to);\n if (input.compose) sp.set(\"compose\", input.compose);\n for (const [k, v] of Object.entries(input.params ?? {})) {\n if (v === undefined || v === null || v === \"\") continue;\n sp.set(k, String(v));\n }\n return sp.toString();\n}\n\n/**\n * Build the app-relative deep-link path:\n * `/_agent-native/open?app=mail&view=inbox&threadId=abc`.\n * Per-app `link` builders call this; never hand-format the URL.\n */\nexport function buildDeepLink(input: DeepLinkInput): string {\n return withCollapsedAgentSidebarParam(\n `/_agent-native${OPEN_ROUTE_SUBPATH}?${buildQuery(input)}`,\n );\n}\n\n/**\n * Resolve a (possibly relative) deep link to an absolute web URL using the\n * inbound request origin. Absolute URLs pass through unchanged.\n */\nexport function toAbsoluteOpenUrl(\n urlOrPath: string,\n origin: string | undefined,\n): string {\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(urlOrPath)) return urlOrPath;\n if (!origin) return urlOrPath;\n return `${origin.replace(/\\/+$/, \"\")}${urlOrPath.startsWith(\"/\") ? \"\" : \"/\"}${urlOrPath}`;\n}\n\n/**\n * Rewrite a deep link to the desktop `agentnative://open?...` scheme so the\n * desktop app's existing `handleDeepLink` opens it inside the app webview.\n * Accepts either an app-relative `/_agent-native/open?...` path or an absolute\n * web URL; preserves the query string.\n */\nexport function toDesktopOpenUrl(urlOrPath: string): string {\n const qIdx = urlOrPath.indexOf(\"?\");\n const query = qIdx >= 0 ? urlOrPath.slice(qIdx + 1) : \"\";\n return query ? `${DESKTOP_OPEN_URL}?${query}` : DESKTOP_OPEN_URL;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"AAmDA,MAAM,WAAW,gBAAgB;IAC/B;;yEAEqE;IACrE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC;AA0CD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,2FAyHpE"}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { defineEventHandler, getMethod } from "h3";
|
|
2
2
|
import { getSession, getConfiguredLoginHtml } from "./auth.js";
|
|
3
3
|
import { appStatePut, appStateGet } from "../application-state/store.js";
|
|
4
|
+
import { AGENT_SIDEBAR_QUERY_PARAM, withCollapsedAgentSidebarParam, } from "../shared/agent-sidebar-url.js";
|
|
4
5
|
/** Query keys that are route control, not navigation payload. */
|
|
5
|
-
const RESERVED = new Set([
|
|
6
|
+
const RESERVED = new Set([
|
|
7
|
+
"app",
|
|
8
|
+
"view",
|
|
9
|
+
"to",
|
|
10
|
+
"compose",
|
|
11
|
+
AGENT_SIDEBAR_QUERY_PARAM,
|
|
12
|
+
]);
|
|
6
13
|
// Control-char guard (NUL..US + DEL). Defined via codepoints so the source
|
|
7
14
|
// file stays plain ASCII.
|
|
8
15
|
const CONTROL_CHARS = new RegExp("[\\u0000-\\u001f\\u007f]");
|
|
@@ -41,6 +48,19 @@ function redirect(location) {
|
|
|
41
48
|
// redirect pattern used elsewhere in auth.ts.
|
|
42
49
|
return new Response("", { status: 302, headers: { Location: location } });
|
|
43
50
|
}
|
|
51
|
+
function appendSearchParams(target, params) {
|
|
52
|
+
if (!params.toString())
|
|
53
|
+
return target;
|
|
54
|
+
try {
|
|
55
|
+
const url = new URL(target, "http://an.invalid");
|
|
56
|
+
for (const [k, v] of params.entries())
|
|
57
|
+
url.searchParams.set(k, v);
|
|
58
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return target;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
44
64
|
export function createOpenRouteHandler(options = {}) {
|
|
45
65
|
return defineEventHandler(async (event) => {
|
|
46
66
|
const method = getMethod(event);
|
|
@@ -150,9 +170,8 @@ export function createOpenRouteHandler(options = {}) {
|
|
|
150
170
|
if (k.startsWith("f_"))
|
|
151
171
|
filters.set(k, v);
|
|
152
172
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
target += (target.includes("?") ? "&" : "?") + fq;
|
|
173
|
+
target = appendSearchParams(target, filters);
|
|
174
|
+
target = withCollapsedAgentSidebarParam(target);
|
|
156
175
|
return redirect(target);
|
|
157
176
|
});
|
|
158
177
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\"app\", \"view\", \"to\", \"compose\"]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n const fq = filters.toString();\n if (fq) target += (target.includes(\"?\") ? \"&\" : \"?\") + fq;\n\n return redirect(target);\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\nimport {\n AGENT_SIDEBAR_QUERY_PARAM,\n withCollapsedAgentSidebarParam,\n} from \"../shared/agent-sidebar-url.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\n \"app\",\n \"view\",\n \"to\",\n \"compose\",\n AGENT_SIDEBAR_QUERY_PARAM,\n]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nfunction appendSearchParams(target: string, params: URLSearchParams): string {\n if (!params.toString()) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n for (const [k, v] of params.entries()) url.searchParams.set(k, v);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n target = appendSearchParams(target, filters);\n target = withCollapsedAgentSidebarParam(target);\n\n return redirect(target);\n });\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const AGENT_NATIVE_OPEN_PATH = "/_agent-native/open";
|
|
2
|
+
export declare const AGENT_SIDEBAR_QUERY_PARAM = "agentSidebar";
|
|
3
|
+
export declare const AGENT_SIDEBAR_QUERY_VALUE_CLOSED = "closed";
|
|
4
|
+
export declare function isAgentNativeOpenDeepLink(urlOrPath: string): boolean;
|
|
5
|
+
export declare function withCollapsedAgentSidebarParam(urlOrPath: string): string;
|
|
6
|
+
//# sourceMappingURL=agent-sidebar-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-sidebar-url.d.ts","sourceRoot":"","sources":["../../src/shared/agent-sidebar-url.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAC5D,eAAO,MAAM,yBAAyB,iBAAiB,CAAC;AACxD,eAAO,MAAM,gCAAgC,WAAW,CAAC;AAQzD,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAWpE;AAED,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAexE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const AGENT_NATIVE_OPEN_PATH = "/_agent-native/open";
|
|
2
|
+
export const AGENT_SIDEBAR_QUERY_PARAM = "agentSidebar";
|
|
3
|
+
export const AGENT_SIDEBAR_QUERY_VALUE_CLOSED = "closed";
|
|
4
|
+
const RELATIVE_URL_BASE = "http://agent-native.invalid";
|
|
5
|
+
function hasUrlScheme(urlOrPath) {
|
|
6
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(urlOrPath);
|
|
7
|
+
}
|
|
8
|
+
export function isAgentNativeOpenDeepLink(urlOrPath) {
|
|
9
|
+
try {
|
|
10
|
+
const absolute = hasUrlScheme(urlOrPath);
|
|
11
|
+
const url = absolute
|
|
12
|
+
? new URL(urlOrPath)
|
|
13
|
+
: new URL(urlOrPath, RELATIVE_URL_BASE);
|
|
14
|
+
if (url.protocol === "agentnative:" && url.host === "open")
|
|
15
|
+
return true;
|
|
16
|
+
return url.pathname === AGENT_NATIVE_OPEN_PATH;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function withCollapsedAgentSidebarParam(urlOrPath) {
|
|
23
|
+
try {
|
|
24
|
+
const absolute = hasUrlScheme(urlOrPath);
|
|
25
|
+
const url = absolute
|
|
26
|
+
? new URL(urlOrPath)
|
|
27
|
+
: new URL(urlOrPath, RELATIVE_URL_BASE);
|
|
28
|
+
url.searchParams.set(AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED);
|
|
29
|
+
if (absolute)
|
|
30
|
+
return url.toString();
|
|
31
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return urlOrPath;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=agent-sidebar-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-sidebar-url.js","sourceRoot":"","sources":["../../src/shared/agent-sidebar-url.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,cAAc,CAAC;AACxD,MAAM,CAAC,MAAM,gCAAgC,GAAG,QAAQ,CAAC;AAEzD,MAAM,iBAAiB,GAAG,6BAA6B,CAAC;AAExD,SAAS,YAAY,CAAC,SAAiB;IACrC,OAAO,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,SAAiB;IACzD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,QAAQ;YAClB,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC;YACpB,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC1C,IAAI,GAAG,CAAC,QAAQ,KAAK,cAAc,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QACxE,OAAO,GAAG,CAAC,QAAQ,KAAK,sBAAsB,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,SAAiB;IAC9D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,QAAQ;YAClB,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC;YACpB,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QAC1C,GAAG,CAAC,YAAY,CAAC,GAAG,CAClB,yBAAyB,EACzB,gCAAgC,CACjC,CAAC;QACF,IAAI,QAAQ;YAAE,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;QACpC,OAAO,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC","sourcesContent":["export const AGENT_NATIVE_OPEN_PATH = \"/_agent-native/open\";\nexport const AGENT_SIDEBAR_QUERY_PARAM = \"agentSidebar\";\nexport const AGENT_SIDEBAR_QUERY_VALUE_CLOSED = \"closed\";\n\nconst RELATIVE_URL_BASE = \"http://agent-native.invalid\";\n\nfunction hasUrlScheme(urlOrPath: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(urlOrPath);\n}\n\nexport function isAgentNativeOpenDeepLink(urlOrPath: string): boolean {\n try {\n const absolute = hasUrlScheme(urlOrPath);\n const url = absolute\n ? new URL(urlOrPath)\n : new URL(urlOrPath, RELATIVE_URL_BASE);\n if (url.protocol === \"agentnative:\" && url.host === \"open\") return true;\n return url.pathname === AGENT_NATIVE_OPEN_PATH;\n } catch {\n return false;\n }\n}\n\nexport function withCollapsedAgentSidebarParam(urlOrPath: string): string {\n try {\n const absolute = hasUrlScheme(urlOrPath);\n const url = absolute\n ? new URL(urlOrPath)\n : new URL(urlOrPath, RELATIVE_URL_BASE);\n url.searchParams.set(\n AGENT_SIDEBAR_QUERY_PARAM,\n AGENT_SIDEBAR_QUERY_VALUE_CLOSED,\n );\n if (absolute) return url.toString();\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return urlOrPath;\n }\n}\n"]}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { truncate } from "./truncate.js";
|
|
|
5
5
|
export { llmConnectionTrackingProperties, normalizeLlmConnection, type LlmConnectionStatus, } from "./llm-connection.js";
|
|
6
6
|
export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertValidWorkspaceAppId, getWorkspaceAppIdValidationError, isValidWorkspaceAppIdFormat, } from "./workspace-app-id.js";
|
|
7
7
|
export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, type WorkspaceAppRouteAccess, type WorkspaceAppRouteAccessFromConfig, type WorkspaceAppAudience, } from "./workspace-app-audience.js";
|
|
8
|
+
export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,+BAA+B,EAC/B,sBAAsB,EACtB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,mCAAmC,EACnC,8BAA8B,EAC9B,sCAAsC,EACtC,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,KAAK,oBAAoB,GAC1B,MAAM,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,+BAA+B,EAC/B,sBAAsB,EACtB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,mCAAmC,EACnC,8BAA8B,EAC9B,sCAAsC,EACtC,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,KAAK,oBAAoB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,wBAAwB,CAAC"}
|
package/dist/shared/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { truncate } from "./truncate.js";
|
|
|
5
5
|
export { llmConnectionTrackingProperties, normalizeLlmConnection, } from "./llm-connection.js";
|
|
6
6
|
export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertValidWorkspaceAppId, getWorkspaceAppIdValidationError, isValidWorkspaceAppIdFormat, } from "./workspace-app-id.js";
|
|
7
7
|
export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, } from "./workspace-app-audience.js";
|
|
8
|
+
export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
package/dist/shared/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAIV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAe,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,+BAA+B,EAC/B,sBAAsB,GAEvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,mCAAmC,EACnC,8BAA8B,EAC9B,sCAAsC,GAIvC,MAAM,6BAA6B,CAAC","sourcesContent":["export {\n agentChat,\n type AgentChatMessage,\n type AgentChatCallOptions,\n type AgentChatResponse,\n} from \"./agent-chat.js\";\nexport { agentEnv, type EnvVar } from \"./agent-env.js\";\nexport { extractOAuthStateAppId } from \"./oauth-state.js\";\nexport { truncate } from \"./truncate.js\";\nexport {\n llmConnectionTrackingProperties,\n normalizeLlmConnection,\n type LlmConnectionStatus,\n} from \"./llm-connection.js\";\nexport {\n DISPATCH_WORKSPACE_ROOT_REDIRECTS,\n RESERVED_WORKSPACE_APP_IDS,\n assertValidWorkspaceAppId,\n getWorkspaceAppIdValidationError,\n isValidWorkspaceAppIdFormat,\n} from \"./workspace-app-id.js\";\nexport {\n DEFAULT_WORKSPACE_APP_AUDIENCE,\n WORKSPACE_APP_AUDIENCES,\n normalizeWorkspaceAppAudience,\n normalizeWorkspaceAppPathList,\n workspaceAppAudienceFromEnv,\n workspaceAppAudienceFromPackageJson,\n workspaceAppRouteAccessFromEnv,\n workspaceAppRouteAccessFromPackageJson,\n type WorkspaceAppRouteAccess,\n type WorkspaceAppRouteAccessFromConfig,\n type WorkspaceAppAudience,\n} from \"./workspace-app-audience.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAIV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAe,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,+BAA+B,EAC/B,sBAAsB,GAEvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,mCAAmC,EACnC,8BAA8B,EAC9B,sCAAsC,GAIvC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,wBAAwB,CAAC","sourcesContent":["export {\n agentChat,\n type AgentChatMessage,\n type AgentChatCallOptions,\n type AgentChatResponse,\n} from \"./agent-chat.js\";\nexport { agentEnv, type EnvVar } from \"./agent-env.js\";\nexport { extractOAuthStateAppId } from \"./oauth-state.js\";\nexport { truncate } from \"./truncate.js\";\nexport {\n llmConnectionTrackingProperties,\n normalizeLlmConnection,\n type LlmConnectionStatus,\n} from \"./llm-connection.js\";\nexport {\n DISPATCH_WORKSPACE_ROOT_REDIRECTS,\n RESERVED_WORKSPACE_APP_IDS,\n assertValidWorkspaceAppId,\n getWorkspaceAppIdValidationError,\n isValidWorkspaceAppIdFormat,\n} from \"./workspace-app-id.js\";\nexport {\n DEFAULT_WORKSPACE_APP_AUDIENCE,\n WORKSPACE_APP_AUDIENCES,\n normalizeWorkspaceAppAudience,\n normalizeWorkspaceAppPathList,\n workspaceAppAudienceFromEnv,\n workspaceAppAudienceFromPackageJson,\n workspaceAppRouteAccessFromEnv,\n workspaceAppRouteAccessFromPackageJson,\n type WorkspaceAppRouteAccess,\n type WorkspaceAppRouteAccessFromConfig,\n type WorkspaceAppAudience,\n} from \"./workspace-app-audience.js\";\nexport {\n AGENT_NATIVE_OPEN_PATH,\n AGENT_SIDEBAR_QUERY_PARAM,\n AGENT_SIDEBAR_QUERY_VALUE_CLOSED,\n isAgentNativeOpenDeepLink,\n withCollapsedAgentSidebarParam,\n} from \"./agent-sidebar-url.js\";\n"]}
|