@agent-native/core 0.119.4 → 0.119.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/durable-background.ts +1 -0
  5. package/corpus/core/src/agent/engine/index.ts +2 -0
  6. package/corpus/core/src/agent/engine/registry.ts +95 -2
  7. package/corpus/core/src/deploy/build.ts +144 -2
  8. package/corpus/core/src/deploy/workspace-deploy.ts +123 -1
  9. package/corpus/core/src/integrations/integration-durable-dispatch-config.ts +22 -0
  10. package/corpus/core/src/integrations/integration-durable-dispatch.ts +190 -0
  11. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +175 -87
  12. package/corpus/core/src/integrations/pending-tasks-store.ts +114 -11
  13. package/corpus/core/src/integrations/plugin.ts +113 -60
  14. package/corpus/core/src/integrations/task-queue-stats.ts +68 -15
  15. package/corpus/core/src/integrations/webhook-handler.ts +21 -46
  16. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +8 -0
  17. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -1
  18. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +9 -1
  19. package/corpus/core/src/server/auth.ts +6 -0
  20. package/corpus/core/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
  21. package/corpus/core/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
  22. package/corpus/core/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
  23. package/corpus/templates/dispatch/changelog/2026-07-21-recover-stalled-slack-agent-work.md +6 -0
  24. package/corpus/templates/slides/actions/patch-deck.ts +2 -1
  25. package/corpus/templates/slides/changelog/2026-07-23-opening-or-refining-a-deck-no-longer-crashes-with-a-blank-er.md +6 -0
  26. package/corpus/templates/slides/shared/aspect-ratios.ts +4 -1
  27. package/corpus/templates/tasks/.oxfmtrc.json +8 -0
  28. package/corpus/templates/tasks/AGENTS.md +34 -41
  29. package/corpus/templates/tasks/README.md +13 -6
  30. package/corpus/templates/tasks/actions/bulk-delete-inbox-items.ts +10 -8
  31. package/corpus/templates/tasks/actions/bulk-delete-tasks.ts +10 -8
  32. package/corpus/templates/tasks/actions/bulk-mark-inbox-items-ready.ts +1 -1
  33. package/corpus/templates/tasks/actions/bulk-update-tasks.ts +12 -10
  34. package/corpus/templates/tasks/actions/create-inbox-item.ts +7 -4
  35. package/corpus/templates/tasks/actions/create-task.ts +2 -1
  36. package/corpus/templates/tasks/actions/delete-inbox-item.ts +6 -4
  37. package/corpus/templates/tasks/actions/delete-task.ts +1 -1
  38. package/corpus/templates/tasks/actions/list-inbox-items.ts +4 -2
  39. package/corpus/templates/tasks/actions/list-visible-task-fields.ts +3 -1
  40. package/corpus/templates/tasks/actions/mark-inbox-item-ready.ts +6 -4
  41. package/corpus/templates/tasks/actions/navigate.ts +16 -14
  42. package/corpus/templates/tasks/actions/reorder-inbox-items.ts +9 -7
  43. package/corpus/templates/tasks/actions/update-inbox-item.ts +6 -4
  44. package/corpus/templates/tasks/actions/update-task.ts +12 -9
  45. package/corpus/templates/tasks/actions/update-visible-task-fields.ts +10 -8
  46. package/corpus/templates/tasks/actions/view-screen.ts +1 -1
  47. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +15 -0
  48. package/corpus/templates/tasks/changelog/2026-07-23-task-detail-extensions.md +6 -0
  49. package/corpus/templates/tasks/drizzle.config.ts +3 -0
  50. package/corpus/templates/tasks/package.json +4 -1
  51. package/corpus/templates/tasks/server/db/schema.ts +0 -10
  52. package/corpus/templates/tasks/server/db/test-tasks-table.ts +0 -5
  53. package/corpus/templates/tasks/server/plugins/agent-chat.ts +1 -1
  54. package/corpus/templates/tasks/server/plugins/db.ts +43 -10
  55. package/corpus/templates/tasks/server/user-config/store.ts +42 -81
  56. package/corpus/templates/tasks/tsconfig.json +1 -2
  57. package/corpus/templates/tasks/vitest.config.ts +10 -1
  58. package/dist/agent/durable-background.d.ts +1 -0
  59. package/dist/agent/durable-background.d.ts.map +1 -1
  60. package/dist/agent/durable-background.js +1 -0
  61. package/dist/agent/durable-background.js.map +1 -1
  62. package/dist/agent/engine/index.d.ts +1 -1
  63. package/dist/agent/engine/index.d.ts.map +1 -1
  64. package/dist/agent/engine/index.js +1 -1
  65. package/dist/agent/engine/index.js.map +1 -1
  66. package/dist/agent/engine/registry.d.ts +32 -1
  67. package/dist/agent/engine/registry.d.ts.map +1 -1
  68. package/dist/agent/engine/registry.js +75 -3
  69. package/dist/agent/engine/registry.js.map +1 -1
  70. package/dist/deploy/build.d.ts +3 -0
  71. package/dist/deploy/build.d.ts.map +1 -1
  72. package/dist/deploy/build.js +106 -3
  73. package/dist/deploy/build.js.map +1 -1
  74. package/dist/deploy/workspace-deploy.d.ts.map +1 -1
  75. package/dist/deploy/workspace-deploy.js +103 -2
  76. package/dist/deploy/workspace-deploy.js.map +1 -1
  77. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  78. package/dist/integrations/integration-durable-dispatch-config.d.ts +7 -0
  79. package/dist/integrations/integration-durable-dispatch-config.d.ts.map +1 -0
  80. package/dist/integrations/integration-durable-dispatch-config.js +16 -0
  81. package/dist/integrations/integration-durable-dispatch-config.js.map +1 -0
  82. package/dist/integrations/integration-durable-dispatch.d.ts +23 -0
  83. package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -0
  84. package/dist/integrations/integration-durable-dispatch.js +115 -0
  85. package/dist/integrations/integration-durable-dispatch.js.map +1 -0
  86. package/dist/integrations/pending-tasks-retry-job.d.ts +13 -1
  87. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  88. package/dist/integrations/pending-tasks-retry-job.js +133 -74
  89. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  90. package/dist/integrations/pending-tasks-store.d.ts +14 -2
  91. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  92. package/dist/integrations/pending-tasks-store.js +75 -11
  93. package/dist/integrations/pending-tasks-store.js.map +1 -1
  94. package/dist/integrations/plugin.d.ts.map +1 -1
  95. package/dist/integrations/plugin.js +88 -55
  96. package/dist/integrations/plugin.js.map +1 -1
  97. package/dist/integrations/task-queue-stats.d.ts +15 -3
  98. package/dist/integrations/task-queue-stats.d.ts.map +1 -1
  99. package/dist/integrations/task-queue-stats.js +45 -15
  100. package/dist/integrations/task-queue-stats.js.map +1 -1
  101. package/dist/integrations/webhook-handler.d.ts +1 -1
  102. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  103. package/dist/integrations/webhook-handler.js +18 -42
  104. package/dist/integrations/webhook-handler.js.map +1 -1
  105. package/dist/notifications/routes.d.ts +2 -2
  106. package/dist/observability/routes.d.ts +5 -5
  107. package/dist/progress/routes.d.ts +1 -1
  108. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  109. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  110. package/dist/resources/handlers.d.ts +1 -1
  111. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  112. package/dist/scripts/agent-engines/list-agent-engines.js +8 -2
  113. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  114. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  115. package/dist/scripts/agent-engines/manage-agent-engine.js +5 -2
  116. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  117. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  118. package/dist/scripts/agent-engines/set-agent-engine.js +9 -2
  119. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  120. package/dist/secrets/routes.d.ts +3 -3
  121. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  122. package/dist/server/auth.d.ts.map +1 -1
  123. package/dist/server/auth.js +5 -0
  124. package/dist/server/auth.js.map +1 -1
  125. package/dist/server/realtime-token.d.ts +1 -1
  126. package/dist/server/transcribe-voice.d.ts +1 -1
  127. package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
  128. package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
  129. package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
  130. package/package.json +1 -1
  131. package/src/agent/durable-background.ts +1 -0
  132. package/src/agent/engine/index.ts +2 -0
  133. package/src/agent/engine/registry.ts +95 -2
  134. package/src/deploy/build.ts +144 -2
  135. package/src/deploy/workspace-deploy.ts +123 -1
  136. package/src/integrations/integration-durable-dispatch-config.ts +22 -0
  137. package/src/integrations/integration-durable-dispatch.ts +190 -0
  138. package/src/integrations/pending-tasks-retry-job.ts +175 -87
  139. package/src/integrations/pending-tasks-store.ts +114 -11
  140. package/src/integrations/plugin.ts +113 -60
  141. package/src/integrations/task-queue-stats.ts +68 -15
  142. package/src/integrations/webhook-handler.ts +21 -46
  143. package/src/scripts/agent-engines/list-agent-engines.ts +8 -0
  144. package/src/scripts/agent-engines/manage-agent-engine.ts +5 -1
  145. package/src/scripts/agent-engines/set-agent-engine.ts +9 -1
  146. package/src/server/auth.ts +6 -0
  147. package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +63 -22
  148. package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +63 -22
  149. package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +63 -22
  150. package/corpus/templates/tasks/.prettierrc +0 -5
@@ -16,10 +16,10 @@ metadata:
16
16
 
17
17
  Integration webhooks (Slack, Telegram, WhatsApp, email, Google Docs, etc.) must
18
18
  **enqueue work to SQL and return 200 immediately**, then process the work in a
19
- **separate fresh function execution** kicked off by a self-fired HTTP POST. A
20
- recurring retry job sweeps anything that gets stuck. This pattern works on every
21
- serverless host (Netlify, Vercel, Cloudflare Workers, Fly, Render, Node) without
22
- relying on platform-specific background-execution features.
19
+ **separate fresh function execution** kicked off by a self-fired HTTP POST. SQL
20
+ is always the source of truth. Long-lived hosts use an in-process retry loop as
21
+ a best-effort safety net; serverless deployments must use an external durable
22
+ sweep when recovery cannot depend on a process staying alive.
23
23
 
24
24
  Do not run agent loops inside the webhook handler itself. Do not rely on
25
25
  fire-and-forget `Promise`s after `return`ing from a serverless handler — they get
@@ -38,8 +38,9 @@ Past attempts that don't work cross-host:
38
38
  freeze the execution context the moment the response goes out. The promise
39
39
  is silently killed, the user gets no reply, and there's no error in the
40
40
  logs.
41
- - **Netlify Background Functions** — Netlify-only, requires a `-background`
42
- filename suffix, breaks on every other host.
41
+ - **A platform background function as the queue** — host-specific execution is
42
+ useful as a delivery mechanism, but it cannot replace the SQL row or atomic
43
+ claim shared by every host.
43
44
  - **Cloudflare `event.waitUntil()`** — CF Workers only, not portable.
44
45
  - **Vercel Fluid / `after()`** — Vercel-only, gated behind specific runtimes.
45
46
  - **A long-lived in-process queue** — fine on a single Node box, but on
@@ -62,8 +63,9 @@ and a recurring job is the safety net.
62
63
  2. INSERT INTO integration_pending_tasks
63
64
  (status='pending', payload=...)
64
65
 
65
- 3. fetch(POST /integrations/_process-task)
66
- — fire-and-forget, NO await on body
66
+ 3. dispatch POST /integrations/process-task
67
+ portable self-fire by default
68
+ — acknowledged background handoff when enabled
67
69
 
68
70
  4. return 200 to platform ◄───────────┘
69
71
 
@@ -79,7 +81,7 @@ and a recurring job is the safety net.
79
81
 
80
82
 
81
83
  ┌──────────────────────────────────────────────┐
82
- Recurring job (every 60s) — safety net
84
+ Recovery sweep (every 60s) — safety net
83
85
  │ Re-fires processor for tasks stuck in │
84
86
  │ 'pending' or 'processing' beyond timeout. │
85
87
  │ Caps retries at 3 then marks 'failed'. │
@@ -98,6 +100,7 @@ loop.
98
100
  | `packages/core/src/integrations/webhook-handler.ts` | Verifies signature, parses, enqueues task, fires processor |
99
101
  | `packages/core/src/integrations/pending-tasks-store.ts` | SQL queue: `insertPendingTask`, `claimPendingTask`, `markTaskCompleted`, `markTaskFailed` |
100
102
  | `packages/core/src/integrations/pending-tasks-retry-job.ts` | Recurring retry sweep (`startPendingTasksRetryJob`, `retryStuckPendingTasks`) |
103
+ | `packages/core/src/integrations/integration-durable-dispatch.ts` | Default-off acknowledged dispatch, scoped rollout, and outcome recording |
101
104
  | `packages/core/src/integrations/types.ts` | `PlatformAdapter`, `IncomingMessage`, `OutgoingMessage` |
102
105
  | `packages/core/src/integrations/adapters/{slack,telegram,whatsapp,email,google-docs}.ts` | One adapter per platform |
103
106
 
@@ -108,7 +111,8 @@ All under `/_agent-native/integrations/`:
108
111
  | Method | Path | Purpose |
109
112
  | ------ | -------------------------- | ------------------------------------------------------------- |
110
113
  | POST | `/:platform/webhook` | Platform pings this. Verifies, enqueues, returns 200 quickly. |
111
- | POST | `/_process-task` | Self-webhook target. Claims a task and runs the agent loop. |
114
+ | POST | `/process-task` | Processor target. Claims a task and runs the agent loop. |
115
+ | POST | `/retry-stuck-tasks` | Signed, bounded recovery sweep for durable schedulers. |
112
116
  | GET | `/status` | All integrations status (settings UI). |
113
117
  | GET | `/:platform/status` | One platform's status. |
114
118
  | POST | `/:platform/enable` | Enable an integration. |
@@ -129,6 +133,10 @@ CREATE TABLE IF NOT EXISTS integration_pending_tasks (
129
133
  org_id TEXT,
130
134
  status TEXT NOT NULL, -- pending | processing | completed | failed
131
135
  attempts INTEGER NOT NULL DEFAULT 0,
136
+ dispatch_attempts INTEGER NOT NULL DEFAULT 0,
137
+ last_dispatch_at INTEGER,
138
+ last_dispatch_outcome TEXT,
139
+ dispatch_scope TEXT, -- persisted channel scope for recovery
132
140
  error_message TEXT,
133
141
  created_at INTEGER NOT NULL,
134
142
  updated_at INTEGER NOT NULL,
@@ -136,6 +144,8 @@ CREATE TABLE IF NOT EXISTS integration_pending_tasks (
136
144
  );
137
145
  CREATE INDEX IF NOT EXISTS idx_pending_tasks_status_created
138
146
  ON integration_pending_tasks(status, created_at);
147
+ CREATE INDEX IF NOT EXISTS idx_pending_tasks_dispatch_scope
148
+ ON integration_pending_tasks(platform, dispatch_scope);
139
149
  ```
140
150
 
141
151
  The store layer creates this lazily on first use via `ensureTable()` and uses
@@ -143,10 +153,38 @@ The store layer creates this lazily on first use via `ensureTable()` and uses
143
153
 
144
154
  `claimPendingTask` is the critical concurrency primitive: it atomically flips
145
155
  `pending` → `processing` and increments `attempts`, returning `null` if another
146
- worker beat us to it. Both the initial fire-and-forget call and the retry job
156
+ worker beat us to it. Both the initial dispatch and every recovery sweep
147
157
  funnel through the same processor endpoint, and `claimPendingTask` is what
148
158
  prevents the same task from being processed twice.
149
159
 
160
+ ### Netlify durable recovery
161
+
162
+ The portable self-dispatch remains the default. To emit and use Netlify's
163
+ acknowledged background worker plus a one-minute scheduled recovery function,
164
+ set `AGENT_INTEGRATION_DURABLE_DISPATCH=true` at build and runtime. A production
165
+ rollout can be narrowed with the comma-separated
166
+ `AGENT_INTEGRATION_DURABLE_DISPATCH_SCOPES` allowlist. Supported values are
167
+ `<platform>:*`, `<platform>:<external-thread-id>`, and, for adapters that expose
168
+ one, `<platform>:<channel-id>` (for example `slack:C123`).
169
+ Channel-scoped handoffs persist that scope separately from the provider thread
170
+ identity so a later bounded recovery sweep applies the same allowlist decision.
171
+
172
+ Workspace deploys emit this pair only for the Dispatch control-plane app, which
173
+ owns workspace messaging integrations. Standalone templates emit their own
174
+ pair when the same flag is enabled.
175
+
176
+ The background handoff waits only for Netlify's enqueue acknowledgement, not
177
+ for the agent loop. A failed or non-2xx acknowledgement falls back to the
178
+ portable processor. The scheduled function calls the HMAC-authenticated,
179
+ bounded `/retry-stuck-tasks` route; its atomic compare-and-set update prevents
180
+ overlapping sweeps from dispatching the same stale row twice.
181
+ Rows acknowledged by the 15-minute background worker use a 16-minute stale
182
+ cutoff, while portable synchronous dispatches retain the shorter serverless
183
+ cutoff. Apply that lease predicate before `LIMIT` so healthy long runs cannot
184
+ hide recoverable work. The background processor writes its lease marker in the
185
+ same atomic update that claims the row; best-effort dispatch telemetry is not
186
+ trusted for replay safety.
187
+
150
188
  ## Adding a New Platform Adapter
151
189
 
152
190
  1. **Implement `PlatformAdapter`** in `packages/core/src/integrations/adapters/<platform>.ts`:
@@ -245,9 +283,10 @@ so a normal long-running reply is safe.
245
283
 
246
284
  ## Cross-Platform Considerations
247
285
 
248
- - **No platform-specific background APIs.** No `waitUntil`, no
249
- `-background.ts` filenames, no Vercel `after()`. The pattern works
250
- identically on every host because it only uses `fetch()` and SQL.
286
+ - **Portable correctness, optional host acceleration.** Every host uses SQL,
287
+ HMAC-authenticated processor routes, and atomic claims. A host-specific
288
+ background function or scheduler may provide a more reliable wake-up, but
289
+ disabling it must preserve the portable path.
251
290
  - **No assumed runtime.** The processor endpoint is a normal H3 handler under
252
291
  `/_agent-native/`. It runs wherever the rest of the framework runs.
253
292
  - **No persistent in-memory state.** The dedup map in the webhook handler is
@@ -262,11 +301,12 @@ so a normal long-running reply is safe.
262
301
 
263
302
  ## Why Fire-and-Forget on Serverless Is Unreliable
264
303
 
265
- Even though the webhook handler does `fetch(processorUrl, ...)` without
266
- awaiting the response body, that initial dispatch is **not** guaranteed to
267
- complete before the function freezes. In practice it usually does — the TCP
268
- connect + write happens quickly but the recurring retry job is the safety
269
- net for the cases where:
304
+ The portable webhook path gives the outbound processor request a short head
305
+ start but does not wait for the response body, so that initial dispatch is
306
+ **not** guaranteed to complete before the function freezes. On a long-lived
307
+ host, the in-process retry loop is a best-effort safety net. On Netlify with
308
+ durable dispatch enabled, the initial handoff is acknowledged and the external
309
+ scheduled function supplies the recovery wake-up for cases where:
270
310
 
271
311
  - The serverless platform froze the handler before the outbound `fetch`
272
312
  flushed its bytes.
@@ -278,8 +318,9 @@ Tasks stuck in `pending` for >90s or `processing` for >5min get re-fired up to
278
318
  3 times. After 3 attempts they're marked `failed` permanently so we stop
279
319
  spamming the processor.
280
320
 
281
- **Never assume the initial fire-and-forget succeeded.** Always rely on the
282
- queue + retry job for at-least-once delivery.
321
+ **Never assume the initial dispatch succeeded.** Rely on the SQL queue, atomic
322
+ claim, and a recovery mechanism whose lifetime is independent of the original
323
+ request. An in-process timer alone is not that mechanism on serverless.
283
324
 
284
325
  ## Debugging Checklist
285
326
 
@@ -292,7 +333,7 @@ queue + retry job for at-least-once delivery.
292
333
  4. **Status?** `pending` means the processor never picked it up — check that
293
334
  `_process-task` is reachable from the box itself (the self-fetch must work
294
335
  over the public URL). `processing` for over 5 minutes means the processor
295
- died mid-run — the retry job will pick it up.
336
+ died mid-run — a live recovery sweep will pick it up.
296
337
  5. **Failed?** Check `error_message` and `attempts`. After 3 attempts the row
297
338
  is parked at `failed` and won't be retried.
298
339
  6. **Reply not delivered?** The processor likely succeeded but
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-21
4
+ ---
5
+
6
+ Slack agent requests recover automatically when the first background handoff is interrupted.
@@ -26,6 +26,7 @@ import { z } from "zod";
26
26
  import { normalizeSlidePadding } from "../app/lib/normalize-slide-padding.js";
27
27
  import { getDb, schema } from "../server/db/index.js";
28
28
  import { notifyClients } from "../server/handlers/decks.js";
29
+ import { ASPECT_RATIO_VALUES } from "../shared/aspect-ratios.js";
29
30
 
30
31
  // ---------------------------------------------------------------------------
31
32
  // Per-deck write lock — same pattern as add-slide.ts so all client and agent
@@ -122,7 +123,7 @@ const PatchDeckFieldsOp = z.object({
122
123
  tweaks: z
123
124
  .record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
124
125
  .optional(),
125
- aspectRatio: z.string().optional(),
126
+ aspectRatio: z.enum(ASPECT_RATIO_VALUES).optional(),
126
127
  shareToken: z.string().optional(),
127
128
  visibility: z.enum(["private", "org", "public"]).optional(),
128
129
  })
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ Opening or refining a deck no longer crashes with a blank error screen when the deck has an unrecognized aspect ratio
@@ -30,5 +30,8 @@ export const ASPECT_RATIO_VALUES = ["16:9", "1:1", "9:16", "4:5"] as const;
30
30
  export const DEFAULT_ASPECT_RATIO: AspectRatio = "16:9";
31
31
 
32
32
  export function getAspectRatioDims(ratio: AspectRatio | undefined | null) {
33
- return ASPECT_RATIOS[ratio ?? DEFAULT_ASPECT_RATIO];
33
+ if (ratio && Object.prototype.hasOwnProperty.call(ASPECT_RATIOS, ratio)) {
34
+ return ASPECT_RATIOS[ratio];
35
+ }
36
+ return ASPECT_RATIOS[DEFAULT_ASPECT_RATIO];
34
37
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "printWidth": 80,
5
+ "trailingComma": "all",
6
+ "proseWrap": "preserve",
7
+ "sortImports": true
8
+ }
@@ -7,6 +7,11 @@ inventory existing public kits and installed package seams. Use
7
7
  `customizing-agent-native` for the configure → compose → eject → propose seam
8
8
  ladder.
9
9
 
10
+ ## Authoritative References
11
+
12
+ - [Feature docs](./docs/features/README.md) define product behavior and feature status.
13
+ - [Action definitions](./actions/) contain the canonical tool descriptions, input schemas, and implementations.
14
+
10
15
  ## Core Rules
11
16
 
12
17
  - 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.
@@ -16,34 +21,38 @@ ladder.
16
21
  - Keep the action surface small: task CRUD actions plus `reorder-tasks`, `view-screen`, and `navigate`.
17
22
  - Do not use `db-query` for normal task operations.
18
23
  - Call `view-screen` first when the user's visible task context matters (especially on `/tasks`).
24
+ - Tasks are private to each user. Preserve `ownerEmail` scoping unless intentionally implementing sharing.
25
+ - The task detail panel exposes `tasks.task-detail.bottom` as an `ExtensionSlot` with `slotContext` containing `taskId`, `title`, `done`, and `fieldValues`.
19
26
 
20
27
  ## Actions
21
28
 
22
- | Action | Method | Purpose |
23
- | ---------------------------- | ------ | -------------------------------------------------------------------------------------------- |
24
- | `list-tasks` | GET | List current user's tasks; `includeDone` and `includeFields` default to false |
25
- | `create-task` | POST | Create a task with `title` |
26
- | `update-task` | POST | Patch `title`, `done`, and/or `fieldValues` by `taskId` |
27
- | `delete-task` | POST | Delete a task by `taskId` (confirm with user first) |
28
- | `bulk-update-tasks` | POST | Patch `title` and/or `done` on multiple tasks by id |
29
- | `bulk-delete-tasks` | POST | Delete multiple tasks by id (confirm with user first) |
30
- | `reorder-tasks` | POST | Reorder visible tasks by id list top-to-bottom |
31
- | `list-inbox-items` | GET | List current user's inbox items |
32
- | `create-inbox-item` | POST | Create a not-ready inbox item with `title` (default chat capture) |
33
- | `update-inbox-item` | POST | Patch inbox item `title` by `inboxItemId` |
34
- | `delete-inbox-item` | POST | Delete an inbox item (confirm with user first) |
35
- | `mark-inbox-item-ready` | POST | Promote inbox item to an incomplete task |
36
- | `reorder-inbox-items` | POST | Reorder inbox items by id list top-to-bottom |
37
- | `list-custom-fields` | GET | List custom field definitions |
38
- | `create-custom-field` | POST | Create a custom field definition with `title`, `type`, and optional `config` |
39
- | `update-custom-field` | POST | Patch a field definition `title` and/or type-compatible `config`; type is immutable |
40
- | `delete-custom-field` | POST | Delete a field definition and its values on every task (confirm with user first) |
41
- | `reorder-custom-fields` | POST | Reorder custom field definitions by id list top-to-bottom |
42
- | `list-visible-task-fields` | GET | List custom field ids shown on task cards for the current user |
43
- | `update-visible-task-fields` | POST | Replace which custom fields appear on task cards (max 3) |
44
- | `view-screen` | | Read navigation, UI bulk selection, visible tasks, and inbox snapshot |
45
- | `navigate` | | Move UI to a view: `tasks`, `inbox`, `fields`, `extensions`, `team` (`home`/`ask` → `tasks`) |
46
- | `render-task-list-inline` | — | Render an interactive task-list widget inline in chat without leaving the current view |
29
+ | Action | Method | Purpose |
30
+ | ----------------------------- | ------ | -------------------------------------------------------------------------------------------- |
31
+ | `list-tasks` | GET | List current user's tasks; `includeDone` and `includeFields` default to false |
32
+ | `create-task` | POST | Create a task with `title` |
33
+ | `update-task` | POST | Patch `title`, `done`, and/or `fieldValues` by `taskId` |
34
+ | `delete-task` | POST | Delete a task by `taskId` (confirm with user first) |
35
+ | `bulk-update-tasks` | POST | Patch `title` and/or `done` on multiple tasks by id |
36
+ | `bulk-delete-tasks` | POST | Delete multiple tasks by id (confirm with user first) |
37
+ | `reorder-tasks` | POST | Reorder visible tasks by id list top-to-bottom |
38
+ | `list-inbox-items` | GET | List current user's inbox items |
39
+ | `create-inbox-item` | POST | Create a not-ready inbox item with `title` (default chat capture) |
40
+ | `update-inbox-item` | POST | Patch inbox item `title` by `inboxItemId` |
41
+ | `delete-inbox-item` | POST | Delete an inbox item (confirm with user first) |
42
+ | `bulk-delete-inbox-items` | POST | Delete multiple inbox items by id (confirm with user first) |
43
+ | `mark-inbox-item-ready` | POST | Promote inbox item to an incomplete task |
44
+ | `bulk-mark-inbox-items-ready` | POST | Promote multiple inbox items to incomplete tasks by id |
45
+ | `reorder-inbox-items` | POST | Reorder inbox items by id list top-to-bottom |
46
+ | `list-custom-fields` | GET | List custom field definitions |
47
+ | `create-custom-field` | POST | Create a custom field definition with `title`, `type`, and optional `config` |
48
+ | `update-custom-field` | POST | Patch a field definition `title` and/or type-compatible `config`; type is immutable |
49
+ | `delete-custom-field` | POST | Delete a field definition and its values on every task (confirm with user first) |
50
+ | `reorder-custom-fields` | POST | Reorder custom field definitions by id list top-to-bottom |
51
+ | `list-visible-task-fields` | GET | List custom field ids shown on task cards for the current user |
52
+ | `update-visible-task-fields` | POST | Replace which custom fields appear on task cards (max 3) |
53
+ | `view-screen` | — | Read navigation, UI bulk selection, visible tasks, and inbox snapshot |
54
+ | `navigate` | — | Move UI to a view: `tasks`, `inbox`, `fields`, `extensions`, `team` (`home`/`ask` → `tasks`) |
55
+ | `render-task-list-inline` | — | Render an interactive task-list widget inline in chat without leaving the current view |
47
56
 
48
57
  ## Store Functions And Transactions
49
58
 
@@ -80,22 +89,6 @@ export type DbHandle = Pick<
80
89
 
81
90
  ## Commit Message Conventions
82
91
 
83
- - Never include `Made-with: Cursor` in commit messages. Remove it if it appears
84
- in a generated message.
85
- - Use one of these prefixes:
86
- - `feature: ...` or `feature(PROJECT): ...`
87
- - `fix: ...` or `fix(PROJECT): ...`
88
- - `refactor: ...` or `refactor(PROJECT): ...`
89
- - `technical: ...` or `technical(PROJECT): ...`
90
- - `chore: ...` or `chore(PROJECT): ...`
91
- - `PROJECT` is optional. If provided, it must be one of `generator` or `web`.
92
- - Before creating any commit, always:
93
- - ask for confirmation,
94
- - show the proposed commit message first,
95
- - commit only after explicit user approval.
96
-
97
- ## Commit Message Conventions
98
-
99
92
  - Never include `Made-with: Cursor` in commit messages. Remove it if it appears
100
93
  in a generated message.
101
94
  - Use one of these prefixes:
@@ -4,13 +4,20 @@ Task-list-first agent-native app. Default home is `/tasks`; chat handles capture
4
4
 
5
5
  ## TODO
6
6
 
7
- - [ ] **i18n** — Add `app/i18n/` catalogs (`en-US` + locale mirrors) and migrate UI copy from raw literals to `useT()`. Remove tasks strings from `scripts/i18n-raw-literal-baseline.txt` once migrated.
8
- - [ ] **Delete dialog wiring** — `TaskList` and `InboxList` duplicate single/bulk delete dialog state (`pendingDeleteId`, `bulkDeleteOpen`, confirm handlers, toast on bulk success). Extract a shared hook or composer helper (e.g. `useDeleteDialogs`) that owns pending id, open state, and confirm wiring while composers pass entity labels and delete mutations.
9
- - [ ] Extract shared action schema helpers (e.g. `taskId` field, title patch) once the action surface grows beyond MVP.
10
- - [ ] Add route-level `ErrorBoundary` and `Suspense` around task list loading (align with other agent-native templates).
7
+ - [ ] **Runtime action discovery filtering** — Update `@agent-native/core` development action discovery to exclude colocated `*.test.*`, `*.spec.*`, and helper-only modules so they are never exposed as agent tools or HTTP action routes.
8
+ - [ ] **Optional event-triggered automations** — If Tasks needs workflows such as “when a task is completed, notify another system,” register stable domain events in `server/plugins/agent-chat.ts` and emit owner-scoped payloads from the successful write paths. Add event discovery, payload-validation, transition, and failure-isolation tests before exposing the events as a public automation contract.
11
9
  - [ ] **`view-screen` agent snapshot** — Revisit `AGENT_TASKS_LIST_CAP` in `actions/view-screen.ts`: it caps the agent tool payload (token budget), not the UI list. Confirm whether `list` / `selectedItem` / `inListSnapshot` correctly represent what the user sees, tune or document the cap, and align with UI limits if needed. Also review Zod parsing of `navigation` / app-state selection keys for strictness vs backward compatibility.
12
- - [ ] **Selection context** — Revisit prop drilling through `TaskList` → header/bar/rows once the composer stabilizes; a narrow context for `{ state, actions }` would trim wiring but must not blur the `dnd/` ↔ `selection/` boundary.
13
10
  - [ ] **`TaskList` optimistic state** — Refactor manual `allTasks` buffer in `app/components/tasks/TaskList.tsx` to TanStack Query `onMutate` once exit animations and reorder rollback can share one cache strategy.
14
11
  - [ ] **Exit-animation list reconcile** — Move exit-animation reconcile / `exitingIdsRef` out of `app/components/tasks/TaskList.tsx` into a neutral list helper (e.g. `use-ordered-tasks-with-exit-animation.ts`). That logic keeps completed rows visible during CSS exit when Show all is off; it is not DnD. `TaskList` should pass an ordered list into generic `SortableList`.
15
- - Shared `ListRowPreview` (`app/components/shared/dnd/ListRowPreview.tsx`) still duplicates row chrome; render the live row in the overlay when `SortableList` can mount previews safely (see TODO in that file).
16
12
  - [ ] **`move-tasks` / relative reorder (try on a separate branch)** — Today DnD persistence is FE-heavy: `SortableList` runs `reorderMovingItems` (`app/components/dnd/reorder-moving-items.ts`) to compute the **full visible id list**, then `TaskList` calls `reorder-tasks` with every id top-to-bottom. The BE already persists order via `server/tasks/store.ts` `reorderTasks` — no separate `bulkReorder` needed, but the **drop → order** translation is brittle (multi-select before/after/top/bottom edge cases). **Proposal:** add a relative move action, e.g. `move-tasks` with `{ taskIds, beforeId? | afterId?, includeDone? }` (or extend `reorder-tasks` with a relative mode). BE applies the block move, validates visible list / ownership, rewrites `sortOrder`, returns updated tasks. Keep **absolute** full-list `taskIds[]` as an escape hatch for agent/import flows. **FE after refactor:** dnd-kit still handles overlay/collision/optimistic UI, but on drop only derives `{ selected ids, anchor id, before | after }` from `over` + drag direction — delete most of `computeBlockInsertIndex`. **Prefer `beforeId`/`afterId` over `toIndex`** — matches user/agent language (“put these after task Z”) and avoids index ambiguity with `includeDone` / hidden rows. **Touch:** new `actions/move-tasks.ts`, `server/tasks/store.ts`, `use-tasks.ts`, `TaskList.tsx` / `InboxList.tsx`, agent prompt in `server/plugins/agent-chat.ts`, unit tests on store + thin FE mapping tests; keep `reorder-tasks` until callers migrate.
13
+ - [ ] **List pagination + virtualization (when volume warrants)** — `listStoredItems` is unbounded and rows render unvirtualized. Fine at personal-list volumes; if lists grow, adopt mail's pattern (`useInfiniteQuery` with page size 25 + `@tanstack/react-virtual` in `EmailList.tsx`). Parked by the 2026-07-17 sibling-parity review as a latent scaling concern, not an active bug.
14
+
15
+ ## Optional improvements
16
+
17
+ Reviewed against sibling templates (starter, mail, content, calendar, plan): none of these has an established cross-template convention to adopt, so they are optional polish rather than alignment work.
18
+
19
+ - [ ] **Delete dialog wiring** — `TaskList` and `InboxList` duplicate single/bulk delete dialog state (`pendingDeleteId`, `bulkDeleteOpen`, confirm handlers, toast on bulk success); a shared `useDeleteDialogs` hook could own that wiring. Siblings: no template has a shared delete-confirm hook — each inlines shadcn `AlertDialog` per call site (calendar's `DeleteEventDialog` is feature-specific). tasks' shared `DeleteItemDialog`/`BulkDeleteDialog` components already lead here; this would be a net-new pattern, not adoption.
20
+ - [ ] **Shared action schema helpers** — Extract shared helpers (e.g. `taskId` field, title patch) if duplication grows. Siblings: no template has generic schema helpers — every action inlines `z.object({...})` in `defineAction`; the only shared primitives are feature-specific (clips' `booleanParam`, tasks' own `boolean-query-param`). Wait for a concrete third duplication before abstracting.
21
+ - [ ] **Custom `ErrorBoundary` copy** — tasks already exports the root-level `ErrorBoundary` (re-export from `@agent-native/core/client`), which is full boundary parity: no template has per-route boundaries, and `Suspense` only wraps `lazy()` components — data loads through react-query, not loaders. The only optional delta: mail and content (2 of 4) replace the re-export with custom copy plus `ErrorReportActions` (send feedback / open issue).
22
+ - [ ] **Task attachments** — no `@agent-native/core/file-upload` usage anywhere, though `.gitignore` already carries the standard `data/uploads/` entry. mail/content/plan use the framework file-upload module. A product decision, not an alignment gap.
23
+ - [ ] **Selection context** — Revisit prop drilling through `TaskList` → header/bar/rows; a narrow `{ state, actions }` context would trim wiring but must not blur the `dnd/` ↔ `selection/` boundary. Siblings: no template uses a React context for list multi-select — the universal pattern is a `useState<Set<string>>` hook plus prop drilling (mail's `EmailList` passes `selectedIds` down as props), and tasks' `use-list-selection.ts` is already the most generalized version of it.
@@ -4,16 +4,18 @@ import { z } from "zod";
4
4
  import { deleteInboxItems, requireUserEmail } from "../server/inbox/store.js";
5
5
  import { BULK_ID_LIMIT } from "../shared/bulk-limits.js";
6
6
 
7
+ export const bulkDeleteInboxItemsSchema = z.object({
8
+ inboxItemIds: z
9
+ .array(z.string())
10
+ .min(1)
11
+ .max(BULK_ID_LIMIT)
12
+ .describe("Inbox item ids to delete"),
13
+ });
14
+
7
15
  export default defineAction({
8
16
  description:
9
- "Delete multiple inbox items permanently. Ask the user to confirm before calling.",
10
- schema: z.object({
11
- inboxItemIds: z
12
- .array(z.string())
13
- .min(1)
14
- .max(BULK_ID_LIMIT)
15
- .describe("Inbox item ids to delete"),
16
- }),
17
+ "Delete multiple inbox items permanently. Use delete-inbox-item for one item. Ask the user to confirm before calling.",
18
+ schema: bulkDeleteInboxItemsSchema,
17
19
  run: async (args, ctx) => {
18
20
  const ownerEmail = requireUserEmail(ctx?.userEmail);
19
21
  return deleteInboxItems({
@@ -4,16 +4,18 @@ import { z } from "zod";
4
4
  import { deleteTasks, requireUserEmail } from "../server/tasks/store.js";
5
5
  import { BULK_ID_LIMIT } from "../shared/bulk-limits.js";
6
6
 
7
+ export const bulkDeleteTasksSchema = z.object({
8
+ taskIds: z
9
+ .array(z.string())
10
+ .min(1)
11
+ .max(BULK_ID_LIMIT)
12
+ .describe("Task ids to delete"),
13
+ });
14
+
7
15
  export default defineAction({
8
16
  description:
9
- "Delete multiple tasks permanently. Ask the user to confirm before calling.",
10
- schema: z.object({
11
- taskIds: z
12
- .array(z.string())
13
- .min(1)
14
- .max(BULK_ID_LIMIT)
15
- .describe("Task ids to delete"),
16
- }),
17
+ "Delete multiple tasks permanently. Use delete-task for one task. Ask the user to confirm before calling.",
18
+ schema: bulkDeleteTasksSchema,
17
19
  run: async (args, ctx) => {
18
20
  const ownerEmail = requireUserEmail(ctx?.userEmail);
19
21
  return deleteTasks({ ownerEmail, ids: args.taskIds });
@@ -9,7 +9,7 @@ import { BULK_ID_LIMIT } from "../shared/bulk-limits.js";
9
9
 
10
10
  export default defineAction({
11
11
  description:
12
- "Promote multiple inbox items to incomplete tasks in one atomic batch.",
12
+ "Promote multiple inbox items to incomplete tasks in one atomic batch, preserving their ids. Use mark-inbox-item-ready for one item.",
13
13
  schema: z.object({
14
14
  inboxItemIds: z
15
15
  .array(z.string())
@@ -5,18 +5,20 @@ import { UserInputError } from "../server/errors.js";
5
5
  import { requireUserEmail, updateTasks } from "../server/tasks/store.js";
6
6
  import { BULK_ID_LIMIT } from "../shared/bulk-limits.js";
7
7
 
8
+ export const bulkUpdateTasksSchema = z.object({
9
+ taskIds: z
10
+ .array(z.string())
11
+ .min(1)
12
+ .max(BULK_ID_LIMIT)
13
+ .describe("Task ids to update"),
14
+ title: z.string().min(1).optional().describe("New title for every task"),
15
+ done: z.boolean().optional().describe("Completion state for every task"),
16
+ });
17
+
8
18
  export default defineAction({
9
19
  description:
10
- "Update multiple tasks with the same title and/or completion patch.",
11
- schema: z.object({
12
- taskIds: z
13
- .array(z.string())
14
- .min(1)
15
- .max(BULK_ID_LIMIT)
16
- .describe("Task ids to update"),
17
- title: z.string().min(1).optional().describe("New title for every task"),
18
- done: z.boolean().optional().describe("Completion state for every task"),
19
- }),
20
+ "Apply the same title and/or completion state to multiple tasks. Use update-task for one task or custom field values.",
21
+ schema: bulkUpdateTasksSchema,
20
22
  run: async (args, ctx) => {
21
23
  const ownerEmail = requireUserEmail(ctx?.userEmail);
22
24
  if (args.title === undefined && args.done === undefined) {
@@ -5,11 +5,14 @@ import { z } from "zod";
5
5
  import { createInboxItem, requireUserEmail } from "../server/inbox/store.js";
6
6
  import type { InboxItem } from "../server/inbox/store.js";
7
7
 
8
+ export const createInboxItemSchema = z.object({
9
+ title: z.string().min(1).describe("Inbox item title"),
10
+ });
11
+
8
12
  export default defineAction({
9
- description: "Create a not-ready inbox item with a title.",
10
- schema: z.object({
11
- title: z.string().min(1).describe("Inbox item title"),
12
- }),
13
+ description:
14
+ "Create a not-ready inbox item for later triage. Use create-task when the user explicitly wants it added directly to the task list.",
15
+ schema: createInboxItemSchema,
13
16
  run: async (args, ctx) => {
14
17
  const ownerEmail = requireUserEmail(ctx?.userEmail);
15
18
  const item = await createInboxItem({ ownerEmail, title: args.title });
@@ -9,7 +9,8 @@ import {
9
9
  } from "../server/tasks/store.js";
10
10
 
11
11
  export default defineAction({
12
- description: "Create a new task with a title.",
12
+ description:
13
+ "Create an incomplete task directly on the task list. Use create-inbox-item for a quick capture to triage later.",
13
14
  schema: z.object({
14
15
  title: z.string().min(1).describe("Task title"),
15
16
  }),
@@ -3,12 +3,14 @@ import { z } from "zod";
3
3
 
4
4
  import { deleteInboxItem, requireUserEmail } from "../server/inbox/store.js";
5
5
 
6
+ export const deleteInboxItemSchema = z.object({
7
+ inboxItemId: z.string().describe("Inbox item id"),
8
+ });
9
+
6
10
  export default defineAction({
7
11
  description:
8
- "Delete an inbox item permanently. Ask the user to confirm before calling.",
9
- schema: z.object({
10
- inboxItemId: z.string().describe("Inbox item id"),
11
- }),
12
+ "Delete one inbox item permanently. Use bulk-delete-inbox-items for multiple items. Ask the user to confirm before calling.",
13
+ schema: deleteInboxItemSchema,
12
14
  run: async (args, ctx) => {
13
15
  const ownerEmail = requireUserEmail(ctx?.userEmail);
14
16
  await deleteInboxItem({ ownerEmail, id: args.inboxItemId });
@@ -5,7 +5,7 @@ import { deleteTask, requireUserEmail } from "../server/tasks/store.js";
5
5
 
6
6
  export default defineAction({
7
7
  description:
8
- "Delete a task permanently. Ask the user to confirm before calling.",
8
+ "Delete one task permanently. Use bulk-delete-tasks for multiple tasks. Ask the user to confirm before calling.",
9
9
  schema: z.object({
10
10
  taskId: z.string().describe("Task id"),
11
11
  }),
@@ -3,9 +3,11 @@ import { z } from "zod";
3
3
 
4
4
  import { listInboxItems, requireUserEmail } from "../server/inbox/store.js";
5
5
 
6
+ export const listInboxItemsSchema = z.object({});
7
+
6
8
  export default defineAction({
7
- description: "List inbox items for the current user (not-ready capture).",
8
- schema: z.object({}),
9
+ description: "List not-ready inbox items awaiting triage.",
10
+ schema: listInboxItemsSchema,
9
11
  http: { method: "GET" },
10
12
  readOnly: true,
11
13
  run: async (_args, ctx) => {
@@ -4,10 +4,12 @@ import { z } from "zod";
4
4
  import { requireUserEmail } from "../server/custom-fields/store.js";
5
5
  import { getTaskCardFieldIds } from "../server/user-config/store.js";
6
6
 
7
+ export const listVisibleTaskFieldsSchema = z.object({});
8
+
7
9
  export default defineAction({
8
10
  description:
9
11
  "List custom field ids currently shown on task cards for the current user.",
10
- schema: z.object({}),
12
+ schema: listVisibleTaskFieldsSchema,
11
13
  http: { method: "GET" },
12
14
  readOnly: true,
13
15
  run: async (_args, ctx) => {
@@ -5,12 +5,14 @@ import { z } from "zod";
5
5
  import { markInboxItemReady, requireUserEmail } from "../server/inbox/store.js";
6
6
  import type { Task } from "../server/tasks/store.js";
7
7
 
8
+ export const markInboxItemReadySchema = z.object({
9
+ inboxItemId: z.string().describe("Inbox item id"),
10
+ });
11
+
8
12
  export default defineAction({
9
13
  description:
10
- "Mark an inbox item ready: promotes the inbox item to an incomplete task (same id).",
11
- schema: z.object({
12
- inboxItemId: z.string().describe("Inbox item id"),
13
- }),
14
+ "Promote one inbox item to an incomplete task, preserving its id. Use bulk-mark-inbox-items-ready for multiple items.",
15
+ schema: markInboxItemReadySchema,
14
16
  run: async (args, ctx) => {
15
17
  const ownerEmail = requireUserEmail(ctx?.userEmail);
16
18
  return markInboxItemReady({ ownerEmail, id: args.inboxItemId });
@@ -21,22 +21,24 @@ import { optionalBooleanQueryParam } from "./lib/boolean-query-param.js";
21
21
 
22
22
  const viewSchema = z.enum(NAV_VIEW_INPUTS);
23
23
 
24
+ export const navigateSchema = z.object({
25
+ view: viewSchema.describe(
26
+ "View name to navigate to; home and ask are aliases for tasks",
27
+ ),
28
+ taskId: z.string().optional().describe("Selected task id on /tasks"),
29
+ fieldId: z
30
+ .string()
31
+ .optional()
32
+ .describe("Selected custom field id on /fields"),
33
+ includeDone: optionalBooleanQueryParam().describe(
34
+ "When true, show completed tasks on /tasks",
35
+ ),
36
+ });
37
+
24
38
  export default defineAction({
25
39
  description:
26
- "Navigate the UI to a specific view. Writes a navigate command to application state which the UI reads and auto-deletes.",
27
- schema: z.object({
28
- view: viewSchema.describe(
29
- "View name to navigate to; home and ask are aliases for tasks",
30
- ),
31
- taskId: z.string().optional().describe("Selected task id on /tasks"),
32
- fieldId: z
33
- .string()
34
- .optional()
35
- .describe("Selected custom field id on /fields"),
36
- includeDone: optionalBooleanQueryParam().describe(
37
- "When true, show completed tasks on /tasks",
38
- ),
39
- }),
40
+ "Navigate the Tasks UI to a view, optionally selecting a task or custom field.",
41
+ schema: navigateSchema,
40
42
  http: false,
41
43
  run: async (args) => {
42
44
  const view = resolveNavView(args.view);