@agent-native/core 0.131.6 → 0.131.9
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/core/CHANGELOG.md +31 -0
- package/corpus/core/docs/content/deployment.mdx +12 -10
- package/corpus/core/docs/content/security.mdx +2 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/invoke.ts +4 -2
- package/corpus/core/src/agent/action-continuation-guidance.ts +2 -1
- package/corpus/core/src/agent/engine/registry.ts +61 -9
- package/corpus/core/src/application-state/store.ts +18 -31
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- package/corpus/core/src/client/frame-protocol.ts +5 -1
- package/corpus/core/src/guards/no-env-credentials.ts +2 -0
- package/corpus/core/src/scripts/call-agent.ts +4 -2
- package/corpus/core/src/secrets/crypto.ts +44 -15
- package/corpus/core/src/secrets/storage.ts +6 -4
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +2 -0
- package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +14 -9
- package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -9
- package/corpus/core/src/workspace-files/tool.ts +18 -11
- package/corpus/templates/analytics/AGENTS.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-30-delegated-analytics-questions-now-return-a-useful-current-me.md +6 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +10 -0
- package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -0
- package/corpus/templates/plan/actions/get-visual-plan.ts +10 -6
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +117 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +56 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
- package/corpus/templates/plan/changelog/2026-07-30-plan-agents-recover-with-targeted-writes.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +35 -0
- package/corpus/templates/slides/actions/create-deck.ts +1 -1
- package/corpus/templates/slides/changelog/2026-07-30-new-decks-now-start-with-an-empty-slide-list-when-no-slides-.md +6 -0
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +4 -2
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/agent/action-continuation-guidance.d.ts.map +1 -1
- package/dist/agent/action-continuation-guidance.js +2 -1
- package/dist/agent/action-continuation-guidance.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +59 -12
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +20 -36
- package/dist/application-state/store.js.map +1 -1
- package/dist/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/guards/no-env-credentials.d.ts.map +1 -1
- package/dist/guards/no-env-credentials.js +2 -0
- package/dist/guards/no-env-credentials.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +4 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/crypto.d.ts +14 -9
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +42 -16
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/storage.d.ts +6 -4
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +6 -4
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +2 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +14 -9
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +14 -9
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -9
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/deployment.mdx +12 -10
- package/docs/content/security.mdx +2 -2
- package/package.json +1 -1
- package/src/a2a/invoke.ts +4 -2
- package/src/agent/action-continuation-guidance.ts +2 -1
- package/src/agent/engine/registry.ts +61 -9
- package/src/application-state/store.ts +18 -31
- package/src/cli/create.ts +115 -16
- package/src/client/AssistantChat.tsx +10 -2
- package/src/client/frame-protocol.ts +5 -1
- package/src/guards/no-env-credentials.ts +2 -0
- package/src/scripts/call-agent.ts +4 -2
- package/src/secrets/crypto.ts +44 -15
- package/src/secrets/storage.ts +6 -4
- package/src/server/agent-chat/action-filters-a2a.ts +2 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +14 -9
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -9
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -9
- package/src/workspace-files/tool.ts +18 -11
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
* The generic encryption key is derived from
|
|
14
14
|
* `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then
|
|
15
15
|
* `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. Workspace-shared vault
|
|
16
|
-
* rows use a different precedence: `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* rows use a different precedence: `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then
|
|
17
|
+
* the legacy shared `SECRETS_ENCRYPTION_KEY`, then material derived from the
|
|
18
|
+
* workspace-wide `A2A_SECRET`, then the app-local auth/key fallbacks retained
|
|
19
|
+
* for backward-compatible reads. A previous workspace key can be supplied
|
|
20
|
+
* during rotation with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS`. This keeps
|
|
21
|
+
* sibling apps on one stable key even when each app correctly has a different
|
|
22
|
+
* auth secret, and prevents A2A trust rotation from stranding vault data.
|
|
20
23
|
*
|
|
21
24
|
* In production we refuse to start without configured key material — a
|
|
22
25
|
* CWD-derived fallback would be effectively static (e.g. `/var/task` on
|
|
@@ -95,6 +98,18 @@ function genericEncryptionKeyMaterial(): string | undefined {
|
|
|
95
98
|
);
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
function workspaceSharedEncryptionKeyMaterial(): string | undefined {
|
|
102
|
+
if (typeof process === "undefined") return undefined;
|
|
103
|
+
return process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY?.trim() || undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function previousWorkspaceSharedEncryptionKeyMaterial(): string | undefined {
|
|
107
|
+
if (typeof process === "undefined") return undefined;
|
|
108
|
+
return (
|
|
109
|
+
process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS?.trim() || undefined
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
98
113
|
/**
|
|
99
114
|
* Vault sharing follows configured A2A trust even on independently deployed
|
|
100
115
|
* sibling apps that are not launched by the workspace wrapper. The wrapper's
|
|
@@ -116,14 +131,17 @@ function a2aSharedEncryptionKeyMaterial(): string | undefined {
|
|
|
116
131
|
* Stable materials that may be used for workspace-shared vault rows, ordered
|
|
117
132
|
* from current preference to legacy compatibility fallbacks.
|
|
118
133
|
*
|
|
119
|
-
* `BETTER_AUTH_SECRET` deliberately comes after the A2A-derived
|
|
120
|
-
* sibling apps normally have different auth secrets but share
|
|
134
|
+
* `BETTER_AUTH_SECRET` deliberately comes after the workspace and A2A-derived
|
|
135
|
+
* materials: sibling apps normally have different auth secrets but share
|
|
136
|
+
* workspace trust.
|
|
121
137
|
*/
|
|
122
138
|
function sharedEncryptionKeyMaterials(): string[] {
|
|
123
139
|
if (typeof process === "undefined") return [];
|
|
124
140
|
const candidates = [
|
|
141
|
+
workspaceSharedEncryptionKeyMaterial(),
|
|
125
142
|
process.env.SECRETS_ENCRYPTION_KEY,
|
|
126
143
|
a2aSharedEncryptionKeyMaterial(),
|
|
144
|
+
previousWorkspaceSharedEncryptionKeyMaterial(),
|
|
127
145
|
process.env.BETTER_AUTH_SECRET,
|
|
128
146
|
appScopedEncryptionKey(),
|
|
129
147
|
].filter((value): value is string => Boolean(value));
|
|
@@ -131,7 +149,14 @@ function sharedEncryptionKeyMaterials(): string[] {
|
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
function preferredSharedEncryptionKeyMaterial(): string | undefined {
|
|
134
|
-
|
|
152
|
+
if (typeof process === "undefined") return undefined;
|
|
153
|
+
return (
|
|
154
|
+
workspaceSharedEncryptionKeyMaterial() ||
|
|
155
|
+
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
156
|
+
a2aSharedEncryptionKeyMaterial() ||
|
|
157
|
+
process.env.BETTER_AUTH_SECRET ||
|
|
158
|
+
appScopedEncryptionKey()
|
|
159
|
+
);
|
|
135
160
|
}
|
|
136
161
|
|
|
137
162
|
function hashSecretEncryptionKey(material: string): Buffer {
|
|
@@ -187,20 +212,23 @@ export function getSecretEncryptionKey(): Buffer {
|
|
|
187
212
|
/**
|
|
188
213
|
* Derive the preferred workspace-shared key used by `app_secrets` rows.
|
|
189
214
|
* Unlike generic column-level encryption, workspace vault data must decrypt
|
|
190
|
-
* in sibling apps. An explicit `
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
215
|
+
* in sibling apps. An explicit `WORKSPACE_SECRETS_ENCRYPTION_KEY` wins without
|
|
216
|
+
* changing the key for app-local OAuth and credential ciphertext. The legacy
|
|
217
|
+
* shared `SECRETS_ENCRYPTION_KEY` remains supported; hosted workspaces
|
|
218
|
+
* otherwise derive material from their shared `A2A_SECRET` before considering
|
|
219
|
+
* app-local `BETTER_AUTH_SECRET` or app-scoped key fallbacks. Reads try every
|
|
220
|
+
* configured legacy/previous candidate and report when the ciphertext should
|
|
221
|
+
* be refreshed under the preferred key.
|
|
195
222
|
*/
|
|
196
223
|
export function getSharedSecretEncryptionKey(): Buffer {
|
|
197
224
|
return deriveSecretEncryptionKey(
|
|
198
225
|
preferredSharedEncryptionKeyMaterial(),
|
|
199
226
|
"[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. " +
|
|
200
|
-
"Set SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET,
|
|
227
|
+
"Set WORKSPACE_SECRETS_ENCRYPTION_KEY (preferred), SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET, " +
|
|
228
|
+
"or set BETTER_AUTH_SECRET / an app-scoped " +
|
|
201
229
|
"*_SECRETS_ENCRYPTION_KEY compatibility fallback in the deploy environment.",
|
|
202
|
-
"[agent-native/secrets]
|
|
203
|
-
"Set
|
|
230
|
+
"[agent-native/secrets] Workspace encryption key not set — using app-scoped or machine-local fallback for workspace secrets. " +
|
|
231
|
+
"Set WORKSPACE_SECRETS_ENCRYPTION_KEY or rely on A2A_SECRET-derived material on hosted workspace deploys so sibling apps share vault rows.",
|
|
204
232
|
);
|
|
205
233
|
}
|
|
206
234
|
|
|
@@ -208,6 +236,7 @@ export function getSharedSecretEncryptionKey(): Buffer {
|
|
|
208
236
|
export function hasSharedSecretEncryptionKeyMaterial(): boolean {
|
|
209
237
|
if (typeof process === "undefined") return false;
|
|
210
238
|
return Boolean(
|
|
239
|
+
workspaceSharedEncryptionKeyMaterial() ||
|
|
211
240
|
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
212
241
|
a2aSharedEncryptionKeyMaterial() ||
|
|
213
242
|
process.env.BETTER_AUTH_SECRET,
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Storage layer for the framework secrets registry.
|
|
3
3
|
*
|
|
4
4
|
* Values are encrypted at rest with AES-256-GCM. The workspace-shared
|
|
5
|
-
* encryption key
|
|
6
|
-
* workspace-wide `A2A_SECRET`.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* encryption key prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy
|
|
6
|
+
* combined `SECRETS_ENCRYPTION_KEY`, then the workspace-wide `A2A_SECRET`.
|
|
7
|
+
* A configured previous workspace key, `BETTER_AUTH_SECRET`, and app-scoped
|
|
8
|
+
* keys remain read fallbacks for rows written before sibling apps converged on
|
|
9
|
+
* the shared key. Successful fallback reads race-safely refresh the shared
|
|
10
|
+
* ciphertext.
|
|
9
11
|
*
|
|
10
12
|
* Secret values are NEVER logged and NEVER returned from any route handler.
|
|
11
13
|
*/
|
|
@@ -360,6 +360,8 @@ export const DELEGATED_AGENT_EXECUTION_CONTRACT = `
|
|
|
360
360
|
<delegated-agent-contract>
|
|
361
361
|
This request was delegated by another app. You are the specialist owner of the work.
|
|
362
362
|
- Interpret the caller's natural-language objective using your own instructions, skills, data dictionary, credentials, and tools. Choose providers, schemas, queries, and joins here; never ask the caller to invent SQL or source-specific implementation details for you.
|
|
363
|
+
- Finish the objective autonomously in this delegated turn whenever a safe, reasonable default exists. Do not bounce the work back with UI-navigation requests, implementation questions, or intermediate status. Ask for input only when authorization, missing credentials, or a consequential user choice truly blocks progress.
|
|
364
|
+
- When evidence is incomplete, return the best grounded partial answer with explicit coverage gaps instead of refusing or asking the caller to choose your source, filter, dashboard, or workflow.
|
|
363
365
|
- Minimize round trips. Filter, join, aggregate, paginate, stage, and reduce large datasets inside your own tools rather than returning raw records or transcripts.
|
|
364
366
|
- Return a concise caller-ready result with the answer, source and coverage details, relevant counts or IDs, caveats/partial gaps, and exact artifact URLs. Do not return tool transcripts or large raw payloads.
|
|
365
367
|
</delegated-agent-contract>`;
|
|
@@ -227,15 +227,20 @@ Core routes plugin mounts these under `/_agent-native/secrets/` automatically:
|
|
|
227
227
|
prefer `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (for example,
|
|
228
228
|
`ANALYTICS_SECRETS_ENCRYPTION_KEY`), then `SECRETS_ENCRYPTION_KEY`, then
|
|
229
229
|
`BETTER_AUTH_SECRET`.
|
|
230
|
-
- Workspace-shared `app_secrets` prefer
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
- Workspace-shared `app_secrets` prefer
|
|
231
|
+
`WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy shared
|
|
232
|
+
`SECRETS_ENCRYPTION_KEY`, then a purpose-derived key from `A2A_SECRET`.
|
|
233
|
+
Better Auth and app-scoped keys remain legacy read candidates; a successful
|
|
234
|
+
legacy decrypt is compare-and-swap migrated to the preferred shared key
|
|
235
|
+
without changing the row timestamp.
|
|
236
|
+
- Set the same stable `WORKSPACE_SECRETS_ENCRYPTION_KEY` in every app that
|
|
237
|
+
reads a shared vault. It is intentionally separate from app-local OAuth
|
|
238
|
+
encryption. If the vault still relies on `A2A_SECRET`, rotating A2A material
|
|
239
|
+
also rotates its encryption key. Add the stable vault key everywhere and
|
|
240
|
+
read/migrate existing rows before rotating A2A.
|
|
241
|
+
- To rotate the dedicated workspace key itself, deploy the new value together
|
|
242
|
+
with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS=<old value>`, let reads
|
|
243
|
+
migrate ciphertext, then remove the previous key after the migration window.
|
|
239
244
|
- If no configured key material exists, development uses a machine-local
|
|
240
245
|
fallback and logs a one-time warning. Production fails closed.
|
|
241
246
|
|
|
@@ -227,15 +227,20 @@ Core routes plugin mounts these under `/_agent-native/secrets/` automatically:
|
|
|
227
227
|
prefer `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (for example,
|
|
228
228
|
`ANALYTICS_SECRETS_ENCRYPTION_KEY`), then `SECRETS_ENCRYPTION_KEY`, then
|
|
229
229
|
`BETTER_AUTH_SECRET`.
|
|
230
|
-
- Workspace-shared `app_secrets` prefer
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
- Workspace-shared `app_secrets` prefer
|
|
231
|
+
`WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy shared
|
|
232
|
+
`SECRETS_ENCRYPTION_KEY`, then a purpose-derived key from `A2A_SECRET`.
|
|
233
|
+
Better Auth and app-scoped keys remain legacy read candidates; a successful
|
|
234
|
+
legacy decrypt is compare-and-swap migrated to the preferred shared key
|
|
235
|
+
without changing the row timestamp.
|
|
236
|
+
- Set the same stable `WORKSPACE_SECRETS_ENCRYPTION_KEY` in every app that
|
|
237
|
+
reads a shared vault. It is intentionally separate from app-local OAuth
|
|
238
|
+
encryption. If the vault still relies on `A2A_SECRET`, rotating A2A material
|
|
239
|
+
also rotates its encryption key. Add the stable vault key everywhere and
|
|
240
|
+
read/migrate existing rows before rotating A2A.
|
|
241
|
+
- To rotate the dedicated workspace key itself, deploy the new value together
|
|
242
|
+
with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS=<old value>`, let reads
|
|
243
|
+
migrate ciphertext, then remove the previous key after the migration window.
|
|
239
244
|
- If no configured key material exists, development uses a machine-local
|
|
240
245
|
fallback and logs a one-time warning. Production fails closed.
|
|
241
246
|
|
|
@@ -227,15 +227,20 @@ Core routes plugin mounts these under `/_agent-native/secrets/` automatically:
|
|
|
227
227
|
prefer `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (for example,
|
|
228
228
|
`ANALYTICS_SECRETS_ENCRYPTION_KEY`), then `SECRETS_ENCRYPTION_KEY`, then
|
|
229
229
|
`BETTER_AUTH_SECRET`.
|
|
230
|
-
- Workspace-shared `app_secrets` prefer
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
- Workspace-shared `app_secrets` prefer
|
|
231
|
+
`WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy shared
|
|
232
|
+
`SECRETS_ENCRYPTION_KEY`, then a purpose-derived key from `A2A_SECRET`.
|
|
233
|
+
Better Auth and app-scoped keys remain legacy read candidates; a successful
|
|
234
|
+
legacy decrypt is compare-and-swap migrated to the preferred shared key
|
|
235
|
+
without changing the row timestamp.
|
|
236
|
+
- Set the same stable `WORKSPACE_SECRETS_ENCRYPTION_KEY` in every app that
|
|
237
|
+
reads a shared vault. It is intentionally separate from app-local OAuth
|
|
238
|
+
encryption. If the vault still relies on `A2A_SECRET`, rotating A2A material
|
|
239
|
+
also rotates its encryption key. Add the stable vault key everywhere and
|
|
240
|
+
read/migrate existing rows before rotating A2A.
|
|
241
|
+
- To rotate the dedicated workspace key itself, deploy the new value together
|
|
242
|
+
with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS=<old value>`, let reads
|
|
243
|
+
migrate ciphertext, then remove the previous key after the migration window.
|
|
239
244
|
- If no configured key material exists, development uses a machine-local
|
|
240
245
|
fallback and logs a one-time warning. Production fails closed.
|
|
241
246
|
|
|
@@ -86,13 +86,16 @@ export function createWorkspaceFilesTool(): Record<string, ActionEntry> {
|
|
|
86
86
|
'MIME type for new files. Default: "text/plain". Use "application/json" for JSON, "text/markdown" for Markdown.',
|
|
87
87
|
},
|
|
88
88
|
offset: {
|
|
89
|
-
type: "
|
|
89
|
+
type: "integer",
|
|
90
|
+
minimum: 0,
|
|
90
91
|
description:
|
|
91
|
-
"
|
|
92
|
+
"Non-negative character offset to start reading from (for paging large files). Default: 0.",
|
|
92
93
|
},
|
|
93
94
|
maxChars: {
|
|
94
|
-
type: "
|
|
95
|
-
|
|
95
|
+
type: "integer",
|
|
96
|
+
minimum: 1,
|
|
97
|
+
maximum: MAX_READ_CHARS,
|
|
98
|
+
description: `Positive maximum characters to return when reading. Default: ${DEFAULT_READ_CHARS}. Max: ${MAX_READ_CHARS}.`,
|
|
96
99
|
},
|
|
97
100
|
pattern: {
|
|
98
101
|
type: "string",
|
|
@@ -168,16 +171,19 @@ export function createWorkspaceFilesTool(): Record<string, ActionEntry> {
|
|
|
168
171
|
if (!path) return "Error: path is required for read.";
|
|
169
172
|
const rawOffset = Number(args.offset);
|
|
170
173
|
const offset =
|
|
171
|
-
Number.isFinite(rawOffset) && rawOffset > 0
|
|
174
|
+
Number.isFinite(rawOffset) && rawOffset > 0
|
|
175
|
+
? Math.floor(rawOffset)
|
|
176
|
+
: 0;
|
|
172
177
|
const rawMax = Number(args.maxChars);
|
|
173
178
|
const maxChars =
|
|
174
179
|
Number.isFinite(rawMax) && rawMax > 0
|
|
175
|
-
? Math.min(rawMax, MAX_READ_CHARS)
|
|
180
|
+
? Math.min(Math.max(1, Math.floor(rawMax)), MAX_READ_CHARS)
|
|
176
181
|
: DEFAULT_READ_CHARS;
|
|
177
182
|
|
|
183
|
+
// The sentinel character distinguishes an exact page from a truncated one.
|
|
178
184
|
const file = await readWorkspaceFile(scope, path, {
|
|
179
185
|
offset,
|
|
180
|
-
maxChars,
|
|
186
|
+
maxChars: maxChars + 1,
|
|
181
187
|
});
|
|
182
188
|
if (!file) {
|
|
183
189
|
return JSON.stringify({
|
|
@@ -186,19 +192,20 @@ export function createWorkspaceFilesTool(): Record<string, ActionEntry> {
|
|
|
186
192
|
});
|
|
187
193
|
}
|
|
188
194
|
|
|
189
|
-
const truncated = file.content.length
|
|
195
|
+
const truncated = file.content.length > maxChars;
|
|
196
|
+
const content = file.content.slice(0, maxChars);
|
|
190
197
|
return JSON.stringify({
|
|
191
198
|
ok: true,
|
|
192
199
|
path: file.path,
|
|
193
200
|
contentType: file.contentType,
|
|
194
201
|
sizeBytes: file.sizeBytes,
|
|
195
202
|
updatedAt: file.updatedAt,
|
|
196
|
-
content
|
|
203
|
+
content,
|
|
197
204
|
...(truncated
|
|
198
205
|
? {
|
|
199
206
|
truncated: true,
|
|
200
|
-
nextOffset: offset +
|
|
201
|
-
hint: `File has more content. Call again with offset: ${offset +
|
|
207
|
+
nextOffset: offset + content.length,
|
|
208
|
+
hint: `File has more content. Call again with offset: ${offset + content.length}`,
|
|
202
209
|
}
|
|
203
210
|
: {}),
|
|
204
211
|
});
|
|
@@ -48,6 +48,7 @@ change the numbers.
|
|
|
48
48
|
sibling-invocable. Prefer natural-language delegation so this app retains its
|
|
49
49
|
instructions, source selection, and tools. Shaped reads are stable contracts,
|
|
50
50
|
not workarounds for delegation; this app still owns the query.
|
|
51
|
+
- For open-ended delegated requests, choose a safe default and label partial.
|
|
51
52
|
- Data integrity first. Never invent numbers, dimensions, filters, or source
|
|
52
53
|
semantics; only present values you actually retrieved, and state uncertainty.
|
|
53
54
|
- Every analytical answer carries audit context: source(s), time window,
|
|
@@ -7,6 +7,7 @@ const INJECTED_CONTEXT_BLOCKS = [
|
|
|
7
7
|
"available-skills",
|
|
8
8
|
"available-agents",
|
|
9
9
|
"available-jobs",
|
|
10
|
+
"a2a-caller-hint",
|
|
10
11
|
"plan-mode-note",
|
|
11
12
|
"non-analytics-retry",
|
|
12
13
|
"response-guard",
|
|
@@ -233,6 +234,15 @@ export function stripInjectedAnalyticsGuardContext(text: string): string {
|
|
|
233
234
|
"",
|
|
234
235
|
);
|
|
235
236
|
}
|
|
237
|
+
// Compatibility with callers deployed before A2A hints were wrapped in the
|
|
238
|
+
// structured block above. The legacy transport note includes words such as
|
|
239
|
+
// "full transcripts" and "create ... dashboard"; allowing those framework
|
|
240
|
+
// words into intent classification falsely triggered corpus and dashboard
|
|
241
|
+
// guards for ordinary delegated metric questions.
|
|
242
|
+
requestText = requestText.replace(
|
|
243
|
+
/\n*\[Note:\s*this request comes from another app via A2A\.[\s\S]*\]\s*$/i,
|
|
244
|
+
"",
|
|
245
|
+
);
|
|
236
246
|
return requestText.trim();
|
|
237
247
|
}
|
|
238
248
|
|
|
@@ -25,6 +25,7 @@ import { z } from "zod";
|
|
|
25
25
|
|
|
26
26
|
import { getDb, schema } from "../server/db/index.js";
|
|
27
27
|
import { withFullVideoAiInstructions } from "../shared/clips-ai-prefs.js";
|
|
28
|
+
import { WorkflowKindSchema } from "../shared/workflow.js";
|
|
28
29
|
import { readIncludeFullVideoInAi } from "./lib/clips-ai-prefs.js";
|
|
29
30
|
|
|
30
31
|
const KIND_PROMPTS = {
|
|
@@ -64,7 +65,7 @@ export default defineAction({
|
|
|
64
65
|
"Ask the agent to generate a structured workflow doc (pr/sop/ticket/email) from this recording's transcript (and the full video when Include full video is enabled). The agent writes the result to clips-workflow-<recordingId> in application_state.",
|
|
65
66
|
schema: z.object({
|
|
66
67
|
recordingId: z.string().describe("Recording ID"),
|
|
67
|
-
kind:
|
|
68
|
+
kind: WorkflowKindSchema.describe("Workflow kind"),
|
|
68
69
|
}),
|
|
69
70
|
run: async (args) => {
|
|
70
71
|
await assertAccess("recording", args.recordingId, "viewer");
|
|
@@ -98,7 +99,7 @@ export default defineAction({
|
|
|
98
99
|
|
|
99
100
|
const includeFullVideoInAi = await readIncludeFullVideoInAi();
|
|
100
101
|
|
|
101
|
-
const existing = await readAppState(stateKey)
|
|
102
|
+
const existing = await readAppState(stateKey);
|
|
102
103
|
if (existing?.status === "generating") {
|
|
103
104
|
const requestedAt = Date.parse(String(existing.requestedAt ?? ""));
|
|
104
105
|
const isRecent =
|
|
@@ -115,13 +116,14 @@ export default defineAction({
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
const requestedAt = new Date().toISOString();
|
|
118
120
|
// Seed the output state with a "generating" placeholder so the UI can show
|
|
119
121
|
// a loading state immediately.
|
|
120
122
|
await writeAppState(stateKey, {
|
|
121
123
|
kind: args.kind,
|
|
122
124
|
status: "generating",
|
|
123
125
|
recordingId: args.recordingId,
|
|
124
|
-
requestedAt
|
|
126
|
+
requestedAt,
|
|
125
127
|
} as any);
|
|
126
128
|
|
|
127
129
|
const baseMessage =
|
|
@@ -136,7 +138,7 @@ export default defineAction({
|
|
|
136
138
|
kind: "generate-workflow" as const,
|
|
137
139
|
workflowKind: args.kind,
|
|
138
140
|
recordingId: args.recordingId,
|
|
139
|
-
requestedAt
|
|
141
|
+
requestedAt,
|
|
140
142
|
recordingTitle: rec.title,
|
|
141
143
|
recordingDescription: rec.description,
|
|
142
144
|
transcriptStatus: transcript?.status ?? "pending",
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
compareAndSetAppState,
|
|
4
|
+
compareAndSetManyAppState,
|
|
5
|
+
readAppState,
|
|
6
|
+
} from "@agent-native/core/application-state";
|
|
7
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import { WorkflowKindSchema } from "../shared/workflow.js";
|
|
11
|
+
|
|
12
|
+
const WorkflowStateSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
kind: WorkflowKindSchema.optional(),
|
|
15
|
+
status: z.string().optional(),
|
|
16
|
+
content: z.string().optional(),
|
|
17
|
+
recordingId: z.string().optional(),
|
|
18
|
+
requestedAt: z.string().optional(),
|
|
19
|
+
tabId: z.string().optional(),
|
|
20
|
+
claimedAt: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
.passthrough();
|
|
23
|
+
|
|
24
|
+
const WorkflowRequestSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
requestedAt: z.string(),
|
|
27
|
+
deliveredAt: z.string().optional(),
|
|
28
|
+
deliveredTabId: z.string().optional(),
|
|
29
|
+
})
|
|
30
|
+
.passthrough();
|
|
31
|
+
|
|
32
|
+
const CLAIM_LEASE_MS = 30_000;
|
|
33
|
+
|
|
34
|
+
export default defineAction({
|
|
35
|
+
description:
|
|
36
|
+
"Track and reconcile the agent run responsible for a generated workflow.",
|
|
37
|
+
agentTool: false,
|
|
38
|
+
schema: z.object({
|
|
39
|
+
operation: z.enum([
|
|
40
|
+
"track",
|
|
41
|
+
"release",
|
|
42
|
+
"mark-delivered",
|
|
43
|
+
"consume",
|
|
44
|
+
"stop",
|
|
45
|
+
]),
|
|
46
|
+
recordingId: z.string().min(1),
|
|
47
|
+
requestedAt: z.string().min(1),
|
|
48
|
+
tabId: z.string().min(1),
|
|
49
|
+
}),
|
|
50
|
+
run: async ({ operation, recordingId, requestedAt, tabId }) => {
|
|
51
|
+
await assertAccess("recording", recordingId, "viewer");
|
|
52
|
+
|
|
53
|
+
const requestKey = `clips-ai-request-${recordingId}`;
|
|
54
|
+
if (operation === "mark-delivered" || operation === "consume") {
|
|
55
|
+
const rawRequest = await readAppState(requestKey);
|
|
56
|
+
if (rawRequest === null) {
|
|
57
|
+
return operation === "consume"
|
|
58
|
+
? { reconciled: false, consumed: true, reason: "missing" as const }
|
|
59
|
+
: { reconciled: false, delivered: true, reason: "missing" as const };
|
|
60
|
+
}
|
|
61
|
+
const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
|
|
62
|
+
if (!parsedRequest.success) {
|
|
63
|
+
throw new Error(`Invalid workflow request state for ${recordingId}`);
|
|
64
|
+
}
|
|
65
|
+
if (parsedRequest.data.requestedAt !== requestedAt) {
|
|
66
|
+
return operation === "consume"
|
|
67
|
+
? {
|
|
68
|
+
reconciled: false,
|
|
69
|
+
consumed: true,
|
|
70
|
+
reason: "newer-request" as const,
|
|
71
|
+
}
|
|
72
|
+
: {
|
|
73
|
+
reconciled: false,
|
|
74
|
+
delivered: true,
|
|
75
|
+
reason: "newer-request" as const,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (operation === "mark-delivered") {
|
|
79
|
+
if (parsedRequest.data.deliveredTabId === tabId) {
|
|
80
|
+
return { reconciled: false, delivered: true };
|
|
81
|
+
}
|
|
82
|
+
if (parsedRequest.data.deliveredTabId) {
|
|
83
|
+
return {
|
|
84
|
+
reconciled: false,
|
|
85
|
+
delivered: false,
|
|
86
|
+
reason: "different-run" as const,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const stateKey = `clips-workflow-${recordingId}`;
|
|
91
|
+
const rawState = await readAppState(stateKey);
|
|
92
|
+
if (rawState === null) {
|
|
93
|
+
return { reconciled: false, delivered: false, reason: "missing" };
|
|
94
|
+
}
|
|
95
|
+
const parsedState = WorkflowStateSchema.safeParse(rawState);
|
|
96
|
+
if (!parsedState.success) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Invalid generated workflow state for ${recordingId}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (parsedState.data.status !== "generating") {
|
|
102
|
+
const consumed = await compareAndSetAppState(
|
|
103
|
+
requestKey,
|
|
104
|
+
rawRequest,
|
|
105
|
+
null,
|
|
106
|
+
);
|
|
107
|
+
return consumed
|
|
108
|
+
? {
|
|
109
|
+
reconciled: false,
|
|
110
|
+
delivered: true,
|
|
111
|
+
consumed: true,
|
|
112
|
+
reason: "terminal",
|
|
113
|
+
}
|
|
114
|
+
: {
|
|
115
|
+
reconciled: false,
|
|
116
|
+
delivered: false,
|
|
117
|
+
consumed: false,
|
|
118
|
+
reason: "stale",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
parsedState.data.requestedAt !== requestedAt ||
|
|
123
|
+
parsedState.data.tabId !== tabId
|
|
124
|
+
) {
|
|
125
|
+
return {
|
|
126
|
+
reconciled: false,
|
|
127
|
+
delivered: false,
|
|
128
|
+
reason: "different-run",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const deliveredAt = new Date().toISOString();
|
|
133
|
+
const delivered = await compareAndSetManyAppState([
|
|
134
|
+
{
|
|
135
|
+
key: stateKey,
|
|
136
|
+
expectedValue: rawState,
|
|
137
|
+
nextValue: { ...rawState, claimedAt: deliveredAt },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: requestKey,
|
|
141
|
+
expectedValue: rawRequest,
|
|
142
|
+
nextValue: { ...rawRequest, deliveredAt, deliveredTabId: tabId },
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
return delivered
|
|
146
|
+
? { reconciled: false, delivered: true }
|
|
147
|
+
: { reconciled: false, delivered: false, reason: "stale" as const };
|
|
148
|
+
}
|
|
149
|
+
if (parsedRequest.data.deliveredTabId !== tabId) {
|
|
150
|
+
return {
|
|
151
|
+
reconciled: false,
|
|
152
|
+
consumed: false,
|
|
153
|
+
reason: "not-delivered" as const,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const consumed = await compareAndSetAppState(
|
|
157
|
+
requestKey,
|
|
158
|
+
rawRequest,
|
|
159
|
+
null,
|
|
160
|
+
);
|
|
161
|
+
return consumed
|
|
162
|
+
? { reconciled: false, consumed: true }
|
|
163
|
+
: { reconciled: false, consumed: false, reason: "stale" as const };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const stateKey = `clips-workflow-${recordingId}`;
|
|
167
|
+
const rawState = await readAppState(stateKey);
|
|
168
|
+
if (rawState === null) {
|
|
169
|
+
return { reconciled: false, reason: "missing" as const };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parsedState = WorkflowStateSchema.safeParse(rawState);
|
|
173
|
+
if (!parsedState.success) {
|
|
174
|
+
throw new Error(`Invalid generated workflow state for ${recordingId}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const state = parsedState.data;
|
|
178
|
+
if (state.status !== "generating") {
|
|
179
|
+
return { reconciled: false, reason: "terminal" as const };
|
|
180
|
+
}
|
|
181
|
+
if (state.requestedAt !== requestedAt) {
|
|
182
|
+
return { reconciled: false, reason: "newer-request" as const };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (operation === "track") {
|
|
186
|
+
if (state.tabId === tabId) {
|
|
187
|
+
return { reconciled: false, tracked: true };
|
|
188
|
+
}
|
|
189
|
+
if (state.tabId) {
|
|
190
|
+
const claimedAt = Date.parse(
|
|
191
|
+
state.claimedAt ?? state.requestedAt ?? "",
|
|
192
|
+
);
|
|
193
|
+
if (
|
|
194
|
+
!Number.isFinite(claimedAt) ||
|
|
195
|
+
Date.now() - claimedAt < CLAIM_LEASE_MS
|
|
196
|
+
) {
|
|
197
|
+
return {
|
|
198
|
+
reconciled: false,
|
|
199
|
+
tracked: false,
|
|
200
|
+
reason: "claimed" as const,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const rawRequest = await readAppState(requestKey);
|
|
205
|
+
if (rawRequest === null) {
|
|
206
|
+
return { reconciled: false, reason: "request-missing" as const };
|
|
207
|
+
}
|
|
208
|
+
const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
|
|
209
|
+
if (!parsedRequest.success) {
|
|
210
|
+
throw new Error(`Invalid workflow request state for ${recordingId}`);
|
|
211
|
+
}
|
|
212
|
+
if (parsedRequest.data.requestedAt !== requestedAt) {
|
|
213
|
+
return { reconciled: false, reason: "newer-request" as const };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const tracked = await compareAndSetAppState(stateKey, rawState, {
|
|
217
|
+
...rawState,
|
|
218
|
+
tabId,
|
|
219
|
+
claimedAt: new Date().toISOString(),
|
|
220
|
+
});
|
|
221
|
+
return tracked
|
|
222
|
+
? { reconciled: false, tracked: true }
|
|
223
|
+
: { reconciled: false, tracked: false, reason: "stale" as const };
|
|
224
|
+
}
|
|
225
|
+
if (state.tabId !== tabId) {
|
|
226
|
+
return operation === "release"
|
|
227
|
+
? {
|
|
228
|
+
reconciled: false,
|
|
229
|
+
released: true,
|
|
230
|
+
reason: "different-run" as const,
|
|
231
|
+
}
|
|
232
|
+
: { reconciled: false, reason: "different-run" as const };
|
|
233
|
+
}
|
|
234
|
+
if (operation === "release") {
|
|
235
|
+
const untrackedState = { ...rawState };
|
|
236
|
+
delete untrackedState.tabId;
|
|
237
|
+
delete untrackedState.claimedAt;
|
|
238
|
+
const released = await compareAndSetAppState(
|
|
239
|
+
stateKey,
|
|
240
|
+
rawState,
|
|
241
|
+
untrackedState,
|
|
242
|
+
);
|
|
243
|
+
return released
|
|
244
|
+
? { reconciled: false, released: true }
|
|
245
|
+
: { reconciled: false, released: false, reason: "stale" as const };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const reconciled = await compareAndSetAppState(stateKey, rawState, {
|
|
249
|
+
...rawState,
|
|
250
|
+
status: "failed",
|
|
251
|
+
failedAt: new Date().toISOString(),
|
|
252
|
+
});
|
|
253
|
+
return reconciled
|
|
254
|
+
? { reconciled: true }
|
|
255
|
+
: { reconciled: false, reason: "stale" as const };
|
|
256
|
+
},
|
|
257
|
+
});
|