@agent-native/core 0.161.17 → 0.161.22
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/analytics/AGENTS.md +1 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +4 -0
- package/corpus/templates/assets/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/assets/AGENTS.md +1 -0
- package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/brain/AGENTS.md +1 -0
- package/corpus/templates/calendar/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/calendar/AGENTS.md +1 -0
- package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/chat/AGENTS.md +4 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/actions/finalize-meeting.ts +57 -21
- package/corpus/templates/clips/actions/get-meeting.ts +11 -3
- package/corpus/templates/clips/actions/request-transcript.ts +183 -8
- package/corpus/templates/clips/actions/update-meeting.ts +66 -14
- package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +80 -135
- package/corpus/templates/clips/app/components/player/video-player.tsx +4 -0
- package/corpus/templates/clips/app/hooks/use-playback-position.ts +10 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +470 -35
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +26 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -0
- package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-opens-welcome-window-on-first-launch.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-18-meeting-notes-support-rich-editing-for-personal-notes-summar.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-18-pending-recording-files-now-use-a-clips-prefixed-name.md +6 -0
- package/corpus/templates/clips/desktop/src/main.tsx +0 -3
- package/corpus/templates/clips/desktop/src/shared/config.ts +0 -1
- package/corpus/templates/clips/desktop/src/styles.css +0 -118
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +0 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +0 -45
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +1 -4
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +0 -10
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +57 -5
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +13 -3
- package/corpus/templates/content/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/crm/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/crm/AGENTS.md +1 -0
- package/corpus/templates/design/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/design/AGENTS.md +1 -0
- package/corpus/templates/design/app/i18n-data.ts +11 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +9 -8
- package/corpus/templates/dispatch/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/dispatch/AGENTS.md +5 -5
- package/corpus/templates/factory/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/factory/AGENTS.md +1 -0
- package/corpus/templates/forms/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/forms/AGENTS.md +1 -0
- package/corpus/templates/macros/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/macros/AGENTS.md +1 -0
- package/corpus/templates/mail/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/mail/AGENTS.md +1 -0
- package/corpus/templates/mail/app/components/email/EmailList.tsx +74 -22
- package/corpus/templates/mail/app/components/email/infinite-pagination.ts +82 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +1 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +15 -10
- package/corpus/templates/mail/app/pages/inbox-zero.ts +35 -0
- package/corpus/templates/mail/changelog/2026-08-18-pinned-labels-load-matches.md +6 -0
- package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/plan/AGENTS.md +1 -0
- package/corpus/templates/slides/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/slides/AGENTS.md +2 -1
- package/corpus/templates/slides/actions/get-deck.ts +5 -1
- package/corpus/templates/slides/actions/list-decks.ts +29 -8
- package/corpus/templates/slides/actions/view-screen.ts +7 -3
- package/corpus/templates/slides/changelog/2026-08-18-decks-appear-under-mine.md +6 -0
- package/corpus/templates/slides/shared/ownership.ts +6 -0
- package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/tasks/AGENTS.md +1 -0
- package/dist/action.js +43 -1
- package/dist/app-config/schema.d.ts +2 -0
- package/dist/app-config/workspace.d.ts +2 -0
- package/dist/app-config/workspace.js +18 -0
- package/dist/catalog.json +23 -23
- package/dist/client/analytics.d.ts +1 -0
- package/dist/client/api-path.js +7 -3
- package/dist/client/chat/run-recovery.js +1 -1
- package/dist/client/frame.js +20 -6
- package/dist/client/org/workspace-app-links.js +10 -1
- package/dist/client/route-state.js +17 -13
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +26 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/app-origin-config.d.ts +1 -0
- package/dist/server/app-origin-config.js +3 -0
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.js +25 -7
- package/dist/server/cookie-namespace.js +17 -2
- package/dist/server/credential-provider.js +5 -2
- package/dist/server/derived-secret.js +3 -2
- package/dist/server/onboarding-html.js +34 -2
- package/dist/server/workspace-oauth.js +11 -6
- package/dist/shared/runtime-config.d.ts +2 -0
- package/dist/shared/runtime-config.js +12 -3
- package/dist/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/chat/AGENTS.md +4 -0
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/workspace-core/AGENTS.md +4 -0
- package/dist/templates/workspace-root/AGENTS.md +9 -0
- package/dist/vite/client.js +5 -2
- package/docs/content/deployment.mdx +70 -0
- package/docs/content/faq.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/chat/AGENTS.md +4 -0
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/workspace-core/AGENTS.md +4 -0
- package/src/templates/workspace-root/AGENTS.md +9 -0
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +0 -314
|
@@ -21,6 +21,7 @@ Read the relevant skill before deeper work:
|
|
|
21
21
|
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
22
22
|
or `resources`; persist URLs, ids, or handles instead.
|
|
23
23
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
24
|
+
- For external integrations, inspect the workspace/provider connection catalog first; reuse its scoped resolver.
|
|
24
25
|
- Use actions for form lifecycle, fields, publishing, responses, navigation,
|
|
25
26
|
sharing, and database work. Do not bypass ownable access checks. The action
|
|
26
27
|
schema is authoritative when a parameter is unclear.
|
|
@@ -76,6 +76,16 @@ Builder/internal data, or customer data in the action, UI, seed data, fixtures,
|
|
|
76
76
|
docs, prompts, or generated extension/app content. Register required secrets,
|
|
77
77
|
use OAuth helpers, or read scoped values from the vault/credential store.
|
|
78
78
|
|
|
79
|
+
Before writing custom setup or credential UI, perform the shared-primitive
|
|
80
|
+
preflight from `agent-native-toolkit`: inspect the workspace/provider connection
|
|
81
|
+
catalog first, then settings, secrets/vault, OAuth, onboarding, and provider API
|
|
82
|
+
support. Reuse an existing connection's app grant and scoped credential
|
|
83
|
+
resolver; only use app-local vault or OAuth primitives when no reusable
|
|
84
|
+
connection exists. Keep any wizard thin and provider-specific - it may explain
|
|
85
|
+
prerequisites, ordering, or readiness, but must not become a second vault or
|
|
86
|
+
custom credential transport. Do not mark each field `required` by reflex;
|
|
87
|
+
model onboarding around the logical connection outcome.
|
|
88
|
+
|
|
79
89
|
If the feature involves attachments, images, recordings, screenshots, exports,
|
|
80
90
|
or other file-like payloads, design the upload path in the same change:
|
|
81
91
|
provider upload first, then URL/id/blob handle in SQL. Do not add base64/binary
|
|
@@ -152,6 +152,30 @@ preference, notification preference, or usage/billing surface, register it as a
|
|
|
152
152
|
settings tab or app settings panel first. Only add sidebar UI when it is needed
|
|
153
153
|
in the moment of agent use.
|
|
154
154
|
|
|
155
|
+
## Integration Setup Preflight
|
|
156
|
+
|
|
157
|
+
Before building any setup, settings, credential, OAuth, or connection surface,
|
|
158
|
+
search the workspace/provider connection catalog first. If the provider already
|
|
159
|
+
has a reusable connection, use its catalog, app grant, and scoped credential
|
|
160
|
+
resolver rather than registering a parallel secret. Only then classify fields
|
|
161
|
+
that still need app-local setup by lifecycle and scope:
|
|
162
|
+
|
|
163
|
+
| Need | Default primitive |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Deploy- or app-level configuration | Runtime configuration or deployment env vars |
|
|
166
|
+
| Existing workspace/provider connection | Workspace-connection catalog/grant plus `resolveWorkspaceConnectionCredential(s)ForApp` |
|
|
167
|
+
| App-local API/service key with no reusable connection | `registerRequiredSecret({ kind: "api-key" })` and the vault |
|
|
168
|
+
| Authorization-code or refresh-token flow | `kind: "oauth"` with `@agent-native/core/oauth-tokens` |
|
|
169
|
+
| Account, customer, or other non-secret identifiers | Scoped connection metadata or app data |
|
|
170
|
+
| Provider-specific prerequisites, sequencing, or health | A thin app-local guide over the shared primitives |
|
|
171
|
+
|
|
172
|
+
Do not register every provider field as a generic secret, mark every field as
|
|
173
|
+
required, or create a second credential-management surface. One logical
|
|
174
|
+
connection should normally produce one onboarding outcome. A custom setup page
|
|
175
|
+
is appropriate only when it adds domain-specific guidance or readiness checks;
|
|
176
|
+
it should link to or call the shared settings, OAuth, and action surfaces rather
|
|
177
|
+
than duplicating their storage or transport.
|
|
178
|
+
|
|
155
179
|
## Reusable Kits
|
|
156
180
|
|
|
157
181
|
- **Settings kit**: a searchable settings page with account, workspace, AI
|
|
@@ -3,8 +3,8 @@ name: secrets
|
|
|
3
3
|
description: >-
|
|
4
4
|
Declaratively register API keys and service credentials a template needs so
|
|
5
5
|
they appear in the agent sidebar settings UI and the onboarding checklist.
|
|
6
|
-
Use
|
|
7
|
-
|
|
6
|
+
Use before adding any third-party credential or setup UI so API keys, OAuth
|
|
7
|
+
connections, and scoped configuration use the correct shared primitive.
|
|
8
8
|
scope: dev
|
|
9
9
|
metadata:
|
|
10
10
|
internal: true
|
|
@@ -24,6 +24,33 @@ encrypted `app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
|
|
|
24
24
|
`${keys.NAME}` substitution. Examples must use obvious placeholders such as
|
|
25
25
|
`<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not real-looking copied values.
|
|
26
26
|
|
|
27
|
+
## Credential Modeling Preflight
|
|
28
|
+
|
|
29
|
+
Before registering a provider's fields, inspect the workspace/provider connection
|
|
30
|
+
catalog first. If a reusable connection exists, use its app grant and scoped
|
|
31
|
+
`resolveWorkspaceConnectionCredential(s)ForApp` path instead of registering a
|
|
32
|
+
parallel secret. Only classify fields for app-local setup when no reusable
|
|
33
|
+
connection exists:
|
|
34
|
+
|
|
35
|
+
- **API or service key** - register it as `kind: "api-key"` with the narrowest
|
|
36
|
+
correct `scope`, a human label, a description, a docs link, and a validator.
|
|
37
|
+
- **OAuth authorization or refresh token** - use the OAuth token store and
|
|
38
|
+
register a `kind: "oauth"` entry so the shared UI renders Connect and the
|
|
39
|
+
runtime owns status, refresh, and reauthorization.
|
|
40
|
+
- **Deploy- or app-level configuration** - use deployment/runtime
|
|
41
|
+
configuration, not a per-user secret row.
|
|
42
|
+
- **Account, customer, manager, or other non-secret identifier** - store it as
|
|
43
|
+
scoped connection metadata or app data, not as a masked secret field.
|
|
44
|
+
|
|
45
|
+
`required: true` is for a logical setup requirement. If a provider needs
|
|
46
|
+
several values, do not automatically create one required checklist item per
|
|
47
|
+
field; use one composite onboarding step or a registered connection readiness
|
|
48
|
+
check.
|
|
49
|
+
|
|
50
|
+
Custom setup UI is allowed for provider-specific prerequisites, ordering, or
|
|
51
|
+
health checks, but it must delegate credential storage and connection state to
|
|
52
|
+
the shared vault/OAuth/settings surfaces.
|
|
53
|
+
|
|
27
54
|
## When to use
|
|
28
55
|
|
|
29
56
|
Use this for any external credential your template needs: API keys, service
|
|
@@ -17,6 +17,7 @@ actions and SQL state.
|
|
|
17
17
|
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
18
18
|
or `resources`; persist URLs, ids, or handles instead.
|
|
19
19
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
20
|
+
- For external integrations, inspect the workspace/provider connection catalog first; reuse its scoped resolver.
|
|
20
21
|
- Use actions for meals, foods, calorie/macro updates, voice command handling,
|
|
21
22
|
stats, and navigation. Do not mutate app tables directly.
|
|
22
23
|
- Do not invent nutrition values when the source is unknown. Ask, use defaults
|
|
@@ -76,6 +76,16 @@ Builder/internal data, or customer data in the action, UI, seed data, fixtures,
|
|
|
76
76
|
docs, prompts, or generated extension/app content. Register required secrets,
|
|
77
77
|
use OAuth helpers, or read scoped values from the vault/credential store.
|
|
78
78
|
|
|
79
|
+
Before writing custom setup or credential UI, perform the shared-primitive
|
|
80
|
+
preflight from `agent-native-toolkit`: inspect the workspace/provider connection
|
|
81
|
+
catalog first, then settings, secrets/vault, OAuth, onboarding, and provider API
|
|
82
|
+
support. Reuse an existing connection's app grant and scoped credential
|
|
83
|
+
resolver; only use app-local vault or OAuth primitives when no reusable
|
|
84
|
+
connection exists. Keep any wizard thin and provider-specific - it may explain
|
|
85
|
+
prerequisites, ordering, or readiness, but must not become a second vault or
|
|
86
|
+
custom credential transport. Do not mark each field `required` by reflex;
|
|
87
|
+
model onboarding around the logical connection outcome.
|
|
88
|
+
|
|
79
89
|
If the feature involves attachments, images, recordings, screenshots, exports,
|
|
80
90
|
or other file-like payloads, design the upload path in the same change:
|
|
81
91
|
provider upload first, then URL/id/blob handle in SQL. Do not add base64/binary
|
|
@@ -152,6 +152,30 @@ preference, notification preference, or usage/billing surface, register it as a
|
|
|
152
152
|
settings tab or app settings panel first. Only add sidebar UI when it is needed
|
|
153
153
|
in the moment of agent use.
|
|
154
154
|
|
|
155
|
+
## Integration Setup Preflight
|
|
156
|
+
|
|
157
|
+
Before building any setup, settings, credential, OAuth, or connection surface,
|
|
158
|
+
search the workspace/provider connection catalog first. If the provider already
|
|
159
|
+
has a reusable connection, use its catalog, app grant, and scoped credential
|
|
160
|
+
resolver rather than registering a parallel secret. Only then classify fields
|
|
161
|
+
that still need app-local setup by lifecycle and scope:
|
|
162
|
+
|
|
163
|
+
| Need | Default primitive |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Deploy- or app-level configuration | Runtime configuration or deployment env vars |
|
|
166
|
+
| Existing workspace/provider connection | Workspace-connection catalog/grant plus `resolveWorkspaceConnectionCredential(s)ForApp` |
|
|
167
|
+
| App-local API/service key with no reusable connection | `registerRequiredSecret({ kind: "api-key" })` and the vault |
|
|
168
|
+
| Authorization-code or refresh-token flow | `kind: "oauth"` with `@agent-native/core/oauth-tokens` |
|
|
169
|
+
| Account, customer, or other non-secret identifiers | Scoped connection metadata or app data |
|
|
170
|
+
| Provider-specific prerequisites, sequencing, or health | A thin app-local guide over the shared primitives |
|
|
171
|
+
|
|
172
|
+
Do not register every provider field as a generic secret, mark every field as
|
|
173
|
+
required, or create a second credential-management surface. One logical
|
|
174
|
+
connection should normally produce one onboarding outcome. A custom setup page
|
|
175
|
+
is appropriate only when it adds domain-specific guidance or readiness checks;
|
|
176
|
+
it should link to or call the shared settings, OAuth, and action surfaces rather
|
|
177
|
+
than duplicating their storage or transport.
|
|
178
|
+
|
|
155
179
|
## Reusable Kits
|
|
156
180
|
|
|
157
181
|
- **Settings kit**: a searchable settings page with account, workspace, AI
|
|
@@ -3,8 +3,8 @@ name: secrets
|
|
|
3
3
|
description: >-
|
|
4
4
|
Declaratively register API keys and service credentials a template needs so
|
|
5
5
|
they appear in the agent sidebar settings UI and the onboarding checklist.
|
|
6
|
-
Use
|
|
7
|
-
|
|
6
|
+
Use before adding any third-party credential or setup UI so API keys, OAuth
|
|
7
|
+
connections, and scoped configuration use the correct shared primitive.
|
|
8
8
|
scope: dev
|
|
9
9
|
metadata:
|
|
10
10
|
internal: true
|
|
@@ -24,6 +24,33 @@ encrypted `app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
|
|
|
24
24
|
`${keys.NAME}` substitution. Examples must use obvious placeholders such as
|
|
25
25
|
`<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not real-looking copied values.
|
|
26
26
|
|
|
27
|
+
## Credential Modeling Preflight
|
|
28
|
+
|
|
29
|
+
Before registering a provider's fields, inspect the workspace/provider connection
|
|
30
|
+
catalog first. If a reusable connection exists, use its app grant and scoped
|
|
31
|
+
`resolveWorkspaceConnectionCredential(s)ForApp` path instead of registering a
|
|
32
|
+
parallel secret. Only classify fields for app-local setup when no reusable
|
|
33
|
+
connection exists:
|
|
34
|
+
|
|
35
|
+
- **API or service key** - register it as `kind: "api-key"` with the narrowest
|
|
36
|
+
correct `scope`, a human label, a description, a docs link, and a validator.
|
|
37
|
+
- **OAuth authorization or refresh token** - use the OAuth token store and
|
|
38
|
+
register a `kind: "oauth"` entry so the shared UI renders Connect and the
|
|
39
|
+
runtime owns status, refresh, and reauthorization.
|
|
40
|
+
- **Deploy- or app-level configuration** - use deployment/runtime
|
|
41
|
+
configuration, not a per-user secret row.
|
|
42
|
+
- **Account, customer, manager, or other non-secret identifier** - store it as
|
|
43
|
+
scoped connection metadata or app data, not as a masked secret field.
|
|
44
|
+
|
|
45
|
+
`required: true` is for a logical setup requirement. If a provider needs
|
|
46
|
+
several values, do not automatically create one required checklist item per
|
|
47
|
+
field; use one composite onboarding step or a registered connection readiness
|
|
48
|
+
check.
|
|
49
|
+
|
|
50
|
+
Custom setup UI is allowed for provider-specific prerequisites, ordering, or
|
|
51
|
+
health checks, but it must delegate credential storage and connection state to
|
|
52
|
+
the shared vault/OAuth/settings surfaces.
|
|
53
|
+
|
|
27
54
|
## When to use
|
|
28
55
|
|
|
29
56
|
Use this for any external credential your template needs: API keys, service
|
|
@@ -56,6 +56,7 @@ Read the relevant skill before deeper work:
|
|
|
56
56
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private
|
|
57
57
|
Builder/internal data, customer data, or credential-looking literals. Use
|
|
58
58
|
secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
59
|
+
- For external integrations, inspect the workspace/provider connection catalog first; reuse its scoped resolver.
|
|
59
60
|
|
|
60
61
|
## Action Map
|
|
61
62
|
|
|
@@ -62,6 +62,11 @@ import { ensureThread, warmThreads } from "@/lib/thread-cache";
|
|
|
62
62
|
import { cn } from "@/lib/utils";
|
|
63
63
|
|
|
64
64
|
import { EmailListItem } from "./EmailListItem";
|
|
65
|
+
import {
|
|
66
|
+
observeNextPage,
|
|
67
|
+
retryNextPage as runPaginationRetry,
|
|
68
|
+
shouldShowPaginationRetry,
|
|
69
|
+
} from "./infinite-pagination";
|
|
65
70
|
|
|
66
71
|
type EmailsPage = { emails: EmailMessage[]; nextPageToken?: string };
|
|
67
72
|
type InfiniteEmails = InfiniteData<EmailsPage, string | undefined>;
|
|
@@ -83,6 +88,7 @@ interface EmailListProps {
|
|
|
83
88
|
hasNextPage?: boolean;
|
|
84
89
|
fetchNextPage?: () => Promise<unknown>;
|
|
85
90
|
isFetchingNextPage?: boolean;
|
|
91
|
+
isFetchNextPageError?: boolean;
|
|
86
92
|
focusedId: string | null;
|
|
87
93
|
setFocusedId: (id: string | null) => void;
|
|
88
94
|
selectedIds: Set<string>;
|
|
@@ -366,6 +372,7 @@ export function EmailList({
|
|
|
366
372
|
hasNextPage: hasNextPageProp,
|
|
367
373
|
fetchNextPage: fetchNextPageProp,
|
|
368
374
|
isFetchingNextPage: isFetchingNextPageProp,
|
|
375
|
+
isFetchNextPageError: isFetchNextPageErrorProp,
|
|
369
376
|
focusedId,
|
|
370
377
|
setFocusedId,
|
|
371
378
|
selectedIds,
|
|
@@ -397,6 +404,7 @@ export function EmailList({
|
|
|
397
404
|
hasNextPage: fetchedEmailsHasNextPage,
|
|
398
405
|
fetchNextPage: fetchFetchedNextPage,
|
|
399
406
|
isFetchingNextPage: fetchedEmailsFetchingNextPage,
|
|
407
|
+
isFetchNextPageError: fetchedEmailsFetchNextPageError,
|
|
400
408
|
} = useEmails(view, searchQuery, labelParam ?? undefined, {
|
|
401
409
|
enabled: emailsProp === undefined,
|
|
402
410
|
});
|
|
@@ -410,6 +418,13 @@ export function EmailList({
|
|
|
410
418
|
const fetchNextPage = fetchNextPageProp ?? fetchFetchedNextPage;
|
|
411
419
|
const isFetchingNextPage =
|
|
412
420
|
isFetchingNextPageProp ?? fetchedEmailsFetchingNextPage;
|
|
421
|
+
const isFetchNextPageError =
|
|
422
|
+
isFetchNextPageErrorProp ?? fetchedEmailsFetchNextPageError;
|
|
423
|
+
const paginationRetryInFlightRef = useRef(false);
|
|
424
|
+
const retryNextPage = useCallback(
|
|
425
|
+
() => runPaginationRetry(fetchNextPage, paginationRetryInFlightRef),
|
|
426
|
+
[fetchNextPage],
|
|
427
|
+
);
|
|
413
428
|
const markRead = useMarkRead();
|
|
414
429
|
const markThreadRead = useMarkThreadRead();
|
|
415
430
|
const toggleStar = useToggleStar();
|
|
@@ -1127,31 +1142,20 @@ export function EmailList({
|
|
|
1127
1142
|
}, [focusedIndex, rowVirtualizer]);
|
|
1128
1143
|
|
|
1129
1144
|
// Infinite scroll — fetch next page when the sentinel enters the viewport.
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
// sentinel is still visible after a fetch, a reconnecting observer would
|
|
1133
|
-
// immediately fire again and we'd loop (visible to the user as "Loading
|
|
1134
|
-
// more..." flashing every ~second while tab is idle).
|
|
1145
|
+
// Re-arm the observer when a fetch completes so a still-visible sentinel can
|
|
1146
|
+
// walk through consecutive pages with no client-side filtered matches.
|
|
1135
1147
|
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
1136
|
-
const isFetchingNextPageRef = useRef(isFetchingNextPage);
|
|
1137
|
-
isFetchingNextPageRef.current = isFetchingNextPage;
|
|
1138
1148
|
useEffect(() => {
|
|
1139
1149
|
const el = sentinelRef.current;
|
|
1140
|
-
if (!el
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
},
|
|
1150
|
-
{ rootMargin: "200px" },
|
|
1151
|
-
);
|
|
1152
|
-
observer.observe(el);
|
|
1153
|
-
return () => observer.disconnect();
|
|
1154
|
-
}, [hasNextPage, fetchNextPage]);
|
|
1150
|
+
if (!el) return;
|
|
1151
|
+
return observeNextPage({
|
|
1152
|
+
element: el,
|
|
1153
|
+
hasNextPage: Boolean(hasNextPage),
|
|
1154
|
+
isFetchingNextPage: Boolean(isFetchingNextPage),
|
|
1155
|
+
isFetchNextPageError: Boolean(isFetchNextPageError),
|
|
1156
|
+
fetchNextPage,
|
|
1157
|
+
});
|
|
1158
|
+
}, [hasNextPage, isFetchingNextPage, isFetchNextPageError, fetchNextPage]);
|
|
1155
1159
|
|
|
1156
1160
|
// Advance selection when an email is snoozed (same logic as archiveFocused)
|
|
1157
1161
|
useEffect(() => {
|
|
@@ -1597,6 +1601,41 @@ export function EmailList({
|
|
|
1597
1601
|
return <MailLoadingState containerRef={containerRef} />;
|
|
1598
1602
|
}
|
|
1599
1603
|
|
|
1604
|
+
// Client-sliced inbox tabs can have no matches on the first page even when
|
|
1605
|
+
// later inbox pages contain matching threads. Keep the sentinel mounted so
|
|
1606
|
+
// the infinite query can continue before showing an empty state.
|
|
1607
|
+
if (threads.length === 0 && hasNextPage) {
|
|
1608
|
+
return (
|
|
1609
|
+
<div className="flex h-full flex-col" ref={containerRef}>
|
|
1610
|
+
<div className="flex flex-1 items-center justify-center" />
|
|
1611
|
+
<div
|
|
1612
|
+
ref={sentinelRef}
|
|
1613
|
+
className="flex items-center justify-center py-3"
|
|
1614
|
+
>
|
|
1615
|
+
{isFetchingNextPage && (
|
|
1616
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
1617
|
+
<Spinner className="size-3 text-muted-foreground" />
|
|
1618
|
+
{t("mail.empty.loadingMore")}
|
|
1619
|
+
</div>
|
|
1620
|
+
)}
|
|
1621
|
+
{shouldShowPaginationRetry({
|
|
1622
|
+
hasNextPage: Boolean(hasNextPage),
|
|
1623
|
+
isFetchingNextPage: Boolean(isFetchingNextPage),
|
|
1624
|
+
isFetchNextPageError: Boolean(isFetchNextPageError),
|
|
1625
|
+
}) && (
|
|
1626
|
+
<button
|
|
1627
|
+
type="button"
|
|
1628
|
+
onClick={() => void retryNextPage()}
|
|
1629
|
+
className="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground shadow-sm hover:bg-primary/90"
|
|
1630
|
+
>
|
|
1631
|
+
{t("mail.error.tryAgain")}
|
|
1632
|
+
</button>
|
|
1633
|
+
)}
|
|
1634
|
+
</div>
|
|
1635
|
+
</div>
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1600
1639
|
// Empty state
|
|
1601
1640
|
if (threads.length === 0) {
|
|
1602
1641
|
if (searchQuery) {
|
|
@@ -1737,6 +1776,19 @@ export function EmailList({
|
|
|
1737
1776
|
{t("mail.empty.loadingMore")}
|
|
1738
1777
|
</div>
|
|
1739
1778
|
)}
|
|
1779
|
+
{shouldShowPaginationRetry({
|
|
1780
|
+
hasNextPage: Boolean(hasNextPage),
|
|
1781
|
+
isFetchingNextPage: Boolean(isFetchingNextPage),
|
|
1782
|
+
isFetchNextPageError: Boolean(isFetchNextPageError),
|
|
1783
|
+
}) && (
|
|
1784
|
+
<button
|
|
1785
|
+
type="button"
|
|
1786
|
+
onClick={() => void retryNextPage()}
|
|
1787
|
+
className="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground shadow-sm hover:bg-primary/90"
|
|
1788
|
+
>
|
|
1789
|
+
{t("mail.error.tryAgain")}
|
|
1790
|
+
</button>
|
|
1791
|
+
)}
|
|
1740
1792
|
</div>
|
|
1741
1793
|
)}
|
|
1742
1794
|
</div>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
interface ObserveNextPageOptions {
|
|
2
|
+
element: Element;
|
|
3
|
+
hasNextPage: boolean;
|
|
4
|
+
isFetchingNextPage: boolean;
|
|
5
|
+
isFetchNextPageError: boolean;
|
|
6
|
+
fetchNextPage: () => Promise<unknown>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PaginationRetryState {
|
|
10
|
+
hasNextPage: boolean;
|
|
11
|
+
isFetchingNextPage: boolean;
|
|
12
|
+
isFetchNextPageError: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function shouldShowPaginationRetry({
|
|
16
|
+
hasNextPage,
|
|
17
|
+
isFetchingNextPage,
|
|
18
|
+
isFetchNextPageError,
|
|
19
|
+
}: PaginationRetryState): boolean {
|
|
20
|
+
return hasNextPage && isFetchNextPageError && !isFetchingNextPage;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run an explicit pagination retry without leaking a rejected provider promise
|
|
25
|
+
* to the browser. The shared state closes the synchronous double-click window
|
|
26
|
+
* before React Query has reflected isFetchingNextPage.
|
|
27
|
+
*/
|
|
28
|
+
export async function retryNextPage(
|
|
29
|
+
fetchNextPage: () => Promise<unknown>,
|
|
30
|
+
inFlightState: { current: boolean },
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
if (inFlightState.current) return;
|
|
33
|
+
inFlightState.current = true;
|
|
34
|
+
try {
|
|
35
|
+
await fetchNextPage();
|
|
36
|
+
} catch {
|
|
37
|
+
// coercion-ok: React Query owns the error; catch prevents an unhandled retry rejection.
|
|
38
|
+
// React Query owns the visible error state for the failed page.
|
|
39
|
+
} finally {
|
|
40
|
+
inFlightState.current = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Observe a visible infinite-scroll sentinel for one page at a time.
|
|
46
|
+
*
|
|
47
|
+
* The owning effect re-runs when `isFetchingNextPage` changes. That lets the
|
|
48
|
+
* next observer receive the sentinel's already-visible state after a fetch,
|
|
49
|
+
* including when several consecutive pages contain no filtered matches. A
|
|
50
|
+
* failed page is deliberately left unobserved until the user explicitly
|
|
51
|
+
* retries it, so a persistent provider error cannot create a request loop.
|
|
52
|
+
*/
|
|
53
|
+
export function observeNextPage({
|
|
54
|
+
element,
|
|
55
|
+
hasNextPage,
|
|
56
|
+
isFetchingNextPage,
|
|
57
|
+
isFetchNextPageError,
|
|
58
|
+
fetchNextPage,
|
|
59
|
+
}: ObserveNextPageOptions): () => void {
|
|
60
|
+
if (!hasNextPage || isFetchingNextPage || isFetchNextPageError)
|
|
61
|
+
return () => {};
|
|
62
|
+
|
|
63
|
+
let fetchInFlight = false;
|
|
64
|
+
const observer = new IntersectionObserver(
|
|
65
|
+
(entries) => {
|
|
66
|
+
if (!entries[0]?.isIntersecting || fetchInFlight) return;
|
|
67
|
+
|
|
68
|
+
fetchInFlight = true;
|
|
69
|
+
void fetchNextPage()
|
|
70
|
+
.catch(() => {
|
|
71
|
+
// React Query owns the visible error state; avoid a global
|
|
72
|
+
// unhandledrejection for transient list-page fetch failures.
|
|
73
|
+
})
|
|
74
|
+
.finally(() => {
|
|
75
|
+
fetchInFlight = false;
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
{ rootMargin: "200px" },
|
|
79
|
+
);
|
|
80
|
+
observer.observe(element);
|
|
81
|
+
return () => observer.disconnect();
|
|
82
|
+
}
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
import { groupIntoThreads, type ThreadSummary } from "@/lib/threads";
|
|
32
32
|
import { cn } from "@/lib/utils";
|
|
33
33
|
|
|
34
|
+
import { shouldShowInboxZero } from "./inbox-zero";
|
|
35
|
+
|
|
34
36
|
function ContactPanel({
|
|
35
37
|
emailId,
|
|
36
38
|
contactEmail,
|
|
@@ -315,6 +317,7 @@ export function InboxPage() {
|
|
|
315
317
|
hasNextPage,
|
|
316
318
|
fetchNextPage,
|
|
317
319
|
isFetchingNextPage,
|
|
320
|
+
isFetchNextPageError,
|
|
318
321
|
} = useEmails(view, searchQuery, effectiveLabel);
|
|
319
322
|
const hasEmailData = rawEmails !== undefined;
|
|
320
323
|
const emailListLoading =
|
|
@@ -590,16 +593,17 @@ export function InboxPage() {
|
|
|
590
593
|
|
|
591
594
|
const isMobile = useIsMobile();
|
|
592
595
|
const hasThread = !!threadId;
|
|
593
|
-
const
|
|
594
|
-
view
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
596
|
+
const isInboxZero = shouldShowInboxZero({
|
|
597
|
+
view,
|
|
598
|
+
activeLabel,
|
|
599
|
+
hasEmailData,
|
|
600
|
+
isLoading: emailListLoading,
|
|
601
|
+
isError,
|
|
602
|
+
hasThread,
|
|
603
|
+
searchQuery,
|
|
604
|
+
threadCount: threads.length,
|
|
605
|
+
hasNextPage: Boolean(hasNextPage),
|
|
606
|
+
});
|
|
603
607
|
const [sidebarContactEmail, setSidebarContactEmail] = useState<
|
|
604
608
|
string | undefined
|
|
605
609
|
>();
|
|
@@ -680,6 +684,7 @@ export function InboxPage() {
|
|
|
680
684
|
hasNextPage={hasNextPage}
|
|
681
685
|
fetchNextPage={fetchNextPage}
|
|
682
686
|
isFetchingNextPage={isFetchingNextPage}
|
|
687
|
+
isFetchNextPageError={isFetchNextPageError}
|
|
683
688
|
/>
|
|
684
689
|
)}
|
|
685
690
|
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface InboxZeroState {
|
|
2
|
+
view: string;
|
|
3
|
+
activeLabel: string | null | undefined;
|
|
4
|
+
hasEmailData: boolean;
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
isError: boolean;
|
|
7
|
+
hasThread: boolean;
|
|
8
|
+
searchQuery: string | undefined;
|
|
9
|
+
threadCount: number;
|
|
10
|
+
hasNextPage: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function shouldShowInboxZero({
|
|
14
|
+
view,
|
|
15
|
+
activeLabel,
|
|
16
|
+
hasEmailData,
|
|
17
|
+
isLoading,
|
|
18
|
+
isError,
|
|
19
|
+
hasThread,
|
|
20
|
+
searchQuery,
|
|
21
|
+
threadCount,
|
|
22
|
+
hasNextPage,
|
|
23
|
+
}: InboxZeroState): boolean {
|
|
24
|
+
return (
|
|
25
|
+
view === "inbox" &&
|
|
26
|
+
(!activeLabel || activeLabel === "important") &&
|
|
27
|
+
hasEmailData &&
|
|
28
|
+
!isLoading &&
|
|
29
|
+
!isError &&
|
|
30
|
+
!hasThread &&
|
|
31
|
+
!searchQuery &&
|
|
32
|
+
threadCount === 0 &&
|
|
33
|
+
!hasNextPage
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -76,6 +76,16 @@ Builder/internal data, or customer data in the action, UI, seed data, fixtures,
|
|
|
76
76
|
docs, prompts, or generated extension/app content. Register required secrets,
|
|
77
77
|
use OAuth helpers, or read scoped values from the vault/credential store.
|
|
78
78
|
|
|
79
|
+
Before writing custom setup or credential UI, perform the shared-primitive
|
|
80
|
+
preflight from `agent-native-toolkit`: inspect the workspace/provider connection
|
|
81
|
+
catalog first, then settings, secrets/vault, OAuth, onboarding, and provider API
|
|
82
|
+
support. Reuse an existing connection's app grant and scoped credential
|
|
83
|
+
resolver; only use app-local vault or OAuth primitives when no reusable
|
|
84
|
+
connection exists. Keep any wizard thin and provider-specific - it may explain
|
|
85
|
+
prerequisites, ordering, or readiness, but must not become a second vault or
|
|
86
|
+
custom credential transport. Do not mark each field `required` by reflex;
|
|
87
|
+
model onboarding around the logical connection outcome.
|
|
88
|
+
|
|
79
89
|
If the feature involves attachments, images, recordings, screenshots, exports,
|
|
80
90
|
or other file-like payloads, design the upload path in the same change:
|
|
81
91
|
provider upload first, then URL/id/blob handle in SQL. Do not add base64/binary
|
|
@@ -152,6 +152,30 @@ preference, notification preference, or usage/billing surface, register it as a
|
|
|
152
152
|
settings tab or app settings panel first. Only add sidebar UI when it is needed
|
|
153
153
|
in the moment of agent use.
|
|
154
154
|
|
|
155
|
+
## Integration Setup Preflight
|
|
156
|
+
|
|
157
|
+
Before building any setup, settings, credential, OAuth, or connection surface,
|
|
158
|
+
search the workspace/provider connection catalog first. If the provider already
|
|
159
|
+
has a reusable connection, use its catalog, app grant, and scoped credential
|
|
160
|
+
resolver rather than registering a parallel secret. Only then classify fields
|
|
161
|
+
that still need app-local setup by lifecycle and scope:
|
|
162
|
+
|
|
163
|
+
| Need | Default primitive |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Deploy- or app-level configuration | Runtime configuration or deployment env vars |
|
|
166
|
+
| Existing workspace/provider connection | Workspace-connection catalog/grant plus `resolveWorkspaceConnectionCredential(s)ForApp` |
|
|
167
|
+
| App-local API/service key with no reusable connection | `registerRequiredSecret({ kind: "api-key" })` and the vault |
|
|
168
|
+
| Authorization-code or refresh-token flow | `kind: "oauth"` with `@agent-native/core/oauth-tokens` |
|
|
169
|
+
| Account, customer, or other non-secret identifiers | Scoped connection metadata or app data |
|
|
170
|
+
| Provider-specific prerequisites, sequencing, or health | A thin app-local guide over the shared primitives |
|
|
171
|
+
|
|
172
|
+
Do not register every provider field as a generic secret, mark every field as
|
|
173
|
+
required, or create a second credential-management surface. One logical
|
|
174
|
+
connection should normally produce one onboarding outcome. A custom setup page
|
|
175
|
+
is appropriate only when it adds domain-specific guidance or readiness checks;
|
|
176
|
+
it should link to or call the shared settings, OAuth, and action surfaces rather
|
|
177
|
+
than duplicating their storage or transport.
|
|
178
|
+
|
|
155
179
|
## Reusable Kits
|
|
156
180
|
|
|
157
181
|
- **Settings kit**: a searchable settings page with account, workspace, AI
|
|
@@ -3,8 +3,8 @@ name: secrets
|
|
|
3
3
|
description: >-
|
|
4
4
|
Declaratively register API keys and service credentials a template needs so
|
|
5
5
|
they appear in the agent sidebar settings UI and the onboarding checklist.
|
|
6
|
-
Use
|
|
7
|
-
|
|
6
|
+
Use before adding any third-party credential or setup UI so API keys, OAuth
|
|
7
|
+
connections, and scoped configuration use the correct shared primitive.
|
|
8
8
|
scope: dev
|
|
9
9
|
metadata:
|
|
10
10
|
internal: true
|
|
@@ -24,6 +24,33 @@ encrypted `app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
|
|
|
24
24
|
`${keys.NAME}` substitution. Examples must use obvious placeholders such as
|
|
25
25
|
`<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not real-looking copied values.
|
|
26
26
|
|
|
27
|
+
## Credential Modeling Preflight
|
|
28
|
+
|
|
29
|
+
Before registering a provider's fields, inspect the workspace/provider connection
|
|
30
|
+
catalog first. If a reusable connection exists, use its app grant and scoped
|
|
31
|
+
`resolveWorkspaceConnectionCredential(s)ForApp` path instead of registering a
|
|
32
|
+
parallel secret. Only classify fields for app-local setup when no reusable
|
|
33
|
+
connection exists:
|
|
34
|
+
|
|
35
|
+
- **API or service key** - register it as `kind: "api-key"` with the narrowest
|
|
36
|
+
correct `scope`, a human label, a description, a docs link, and a validator.
|
|
37
|
+
- **OAuth authorization or refresh token** - use the OAuth token store and
|
|
38
|
+
register a `kind: "oauth"` entry so the shared UI renders Connect and the
|
|
39
|
+
runtime owns status, refresh, and reauthorization.
|
|
40
|
+
- **Deploy- or app-level configuration** - use deployment/runtime
|
|
41
|
+
configuration, not a per-user secret row.
|
|
42
|
+
- **Account, customer, manager, or other non-secret identifier** - store it as
|
|
43
|
+
scoped connection metadata or app data, not as a masked secret field.
|
|
44
|
+
|
|
45
|
+
`required: true` is for a logical setup requirement. If a provider needs
|
|
46
|
+
several values, do not automatically create one required checklist item per
|
|
47
|
+
field; use one composite onboarding step or a registered connection readiness
|
|
48
|
+
check.
|
|
49
|
+
|
|
50
|
+
Custom setup UI is allowed for provider-specific prerequisites, ordering, or
|
|
51
|
+
health checks, but it must delegate credential storage and connection state to
|
|
52
|
+
the shared vault/OAuth/settings surfaces.
|
|
53
|
+
|
|
27
54
|
## When to use
|
|
28
55
|
|
|
29
56
|
Use this for any external credential your template needs: API keys, service
|