@agent-native/core 0.136.3 → 0.136.5

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 (49) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +53 -0
  3. package/corpus/templates/clips/actions/import-loom-recording.ts +7 -0
  4. package/corpus/templates/clips/actions/lib/loom-import-job.ts +24 -4
  5. package/corpus/templates/clips/changelog/2026-08-03-restart-during-a-recording-now-immediately-starts-a-fresh-ta.md +6 -0
  6. package/corpus/templates/clips/desktop/src/app.tsx +81 -30
  7. package/corpus/templates/clips/desktop/src/lib/recorder.ts +436 -158
  8. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +14 -1
  9. package/corpus/templates/clips/server/plugins/auth.ts +9 -0
  10. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +13 -0
  11. package/dist/client/agent-page/AgentJobsTab.js +19 -4
  12. package/dist/client/agent-page/use-jobs.d.ts +10 -0
  13. package/dist/client/agent-page/use-jobs.js +21 -0
  14. package/dist/client/settings/SecretsSection.js +32 -8
  15. package/dist/collab/awareness.d.ts +2 -2
  16. package/dist/collab/struct-routes.d.ts +1 -1
  17. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  18. package/dist/jobs/actions/run-automation-now.d.ts +6 -0
  19. package/dist/jobs/actions/run-automation-now.js +22 -0
  20. package/dist/jobs/background-automation-runner.d.ts +2 -0
  21. package/dist/jobs/background-automation-runner.js +22 -16
  22. package/dist/jobs/run-history.d.ts +17 -3
  23. package/dist/jobs/run-history.js +73 -7
  24. package/dist/jobs/run-now.d.ts +20 -0
  25. package/dist/jobs/run-now.js +99 -0
  26. package/dist/jobs/scheduler.d.ts +15 -0
  27. package/dist/jobs/scheduler.js +76 -12
  28. package/dist/localization/default-messages.d.ts +8 -0
  29. package/dist/localization/default-messages.js +8 -0
  30. package/dist/notifications/routes.d.ts +4 -4
  31. package/dist/observability/routes.d.ts +3 -3
  32. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  33. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  34. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  35. package/dist/secrets/routes.d.ts +5 -5
  36. package/dist/secrets/routes.js +41 -17
  37. package/dist/server/action-discovery.js +4 -0
  38. package/dist/server/agent-chat-plugin.js +135 -68
  39. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  40. package/dist/server/email-actions.d.ts +6 -3
  41. package/dist/server/email-actions.js +17 -83
  42. package/dist/server/email-markdown.d.ts +4 -0
  43. package/dist/server/email-markdown.js +174 -0
  44. package/dist/server/transcribe-voice.d.ts +1 -1
  45. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -0
  46. package/dist/triggers/actions.d.ts +1 -1
  47. package/dist/triggers/actions.js +27 -5
  48. package/package.json +1 -1
  49. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -0
@@ -0,0 +1,20 @@
1
+ import { type AutomationScope } from "../automations/service.js";
2
+ export interface RunAutomationNowInput {
3
+ userEmail: string;
4
+ orgId?: string | null;
5
+ scope: AutomationScope;
6
+ name: string;
7
+ }
8
+ export interface QueuedAutomationRun {
9
+ queued: true;
10
+ runId: string;
11
+ automationRunId: string;
12
+ }
13
+ export declare function queueAutomationRunNow(input: RunAutomationNowInput): Promise<QueuedAutomationRun>;
14
+ /**
15
+ * Recover manual rows whose first serverless handoff never reached a worker.
16
+ * This is intentionally a redelivery, not a second execution: the worker's
17
+ * claim CAS decides which request owns the run.
18
+ */
19
+ export declare function redispatchUnclaimedAutomationRuns(): Promise<number>;
20
+ //# sourceMappingURL=run-now.d.ts.map
@@ -0,0 +1,99 @@
1
+ import { AGENT_CHAT_BACKGROUND_RUN_FIELD, dispatchPathTargetsNetlifyBackgroundFunction, resolveAgentChatProcessRunDispatchPath, } from "../agent/durable-background.js";
2
+ import { canUpdateAutomationResource, } from "../automations/service.js";
3
+ import { isLocalDatabase } from "../db/client.js";
4
+ import { organizationResourceOwner, resourceGetByPath, } from "../resources/store.js";
5
+ import { fireInternalDispatch } from "../server/self-dispatch.js";
6
+ import { parseJobResource } from "./frontmatter.js";
7
+ import { listUnclaimedAutomationRuns, startAutomationRun, } from "./run-history.js";
8
+ async function dispatchAutomationRun(historyId) {
9
+ const dispatchPath = resolveAgentChatProcessRunDispatchPath();
10
+ await fireInternalDispatch({
11
+ path: dispatchPath,
12
+ taskId: historyId,
13
+ body: {
14
+ [AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
15
+ runId: historyId,
16
+ automationRunId: historyId,
17
+ },
18
+ },
19
+ ...(dispatchPathTargetsNetlifyBackgroundFunction(dispatchPath)
20
+ ? { awaitResponse: true, responseTimeoutMs: 5_000 }
21
+ : !isLocalDatabase()
22
+ ? { awaitResponse: true, responseTimeoutMs: 5_000 }
23
+ : {}),
24
+ });
25
+ }
26
+ function ownerForScope(input) {
27
+ if (input.scope === "personal")
28
+ return input.userEmail.trim().toLowerCase();
29
+ if (!input.orgId) {
30
+ throw Object.assign(new Error("An organization is required for organization automations."), { statusCode: 400 });
31
+ }
32
+ return organizationResourceOwner(input.orgId);
33
+ }
34
+ export async function queueAutomationRunNow(input) {
35
+ const name = input.name.trim();
36
+ if (!name || name.includes("/") || name.endsWith(".md")) {
37
+ throw Object.assign(new Error("A valid automation name is required."), {
38
+ statusCode: 400,
39
+ });
40
+ }
41
+ const owner = ownerForScope(input);
42
+ const resource = await resourceGetByPath(owner, `jobs/${name}.md`);
43
+ if (!resource) {
44
+ throw Object.assign(new Error(`Automation "${name}" not found.`), {
45
+ statusCode: 404,
46
+ });
47
+ }
48
+ if (!(await canUpdateAutomationResource(input, resource))) {
49
+ throw Object.assign(new Error("Only the automation's creator or an organization admin can run it."), { statusCode: 403 });
50
+ }
51
+ const { body } = parseJobResource(resource.content);
52
+ if (!body.trim()) {
53
+ throw Object.assign(new Error(`Automation "${name}" has no instructions.`), {
54
+ statusCode: 400,
55
+ });
56
+ }
57
+ // A manual-run request is a guaranteed app request even on hosts without a
58
+ // durable timer. Use it to recover older rows before adding the new one.
59
+ await redispatchUnclaimedAutomationRuns().catch((error) => {
60
+ console.warn("[automations] Could not sweep queued runs before run-now:", error);
61
+ });
62
+ const historyId = await startAutomationRun({
63
+ owner: resource.owner,
64
+ automation: name,
65
+ path: resource.path,
66
+ scope: input.scope,
67
+ orgId: input.scope === "organization" ? input.orgId : null,
68
+ dispatchPending: true,
69
+ });
70
+ try {
71
+ await dispatchAutomationRun(historyId);
72
+ }
73
+ catch (error) {
74
+ const message = error instanceof Error ? error.message : "Background dispatch failed";
75
+ console.warn(`[automations] Initial run-now dispatch failed; leaving ${historyId} queued for redelivery:`, message);
76
+ throw error;
77
+ }
78
+ return { queued: true, runId: historyId, automationRunId: historyId };
79
+ }
80
+ /**
81
+ * Recover manual rows whose first serverless handoff never reached a worker.
82
+ * This is intentionally a redelivery, not a second execution: the worker's
83
+ * claim CAS decides which request owns the run.
84
+ */
85
+ export async function redispatchUnclaimedAutomationRuns() {
86
+ const runs = await listUnclaimedAutomationRuns();
87
+ let attempted = 0;
88
+ for (const run of runs) {
89
+ try {
90
+ await dispatchAutomationRun(run.id);
91
+ attempted += 1;
92
+ }
93
+ catch (error) {
94
+ console.error(`[automations] Could not redeliver queued run ${run.id}:`, error);
95
+ }
96
+ }
97
+ return attempted;
98
+ }
99
+ //# sourceMappingURL=run-now.js.map
@@ -27,4 +27,19 @@ export interface SchedulerDeps extends BackgroundAutomationDeps {
27
27
  */
28
28
  export declare function processRecurringJobs(deps: SchedulerDeps): Promise<void>;
29
29
  export declare const jobRunCutOffReason: typeof backgroundRunCutOffReason;
30
+ interface JobExecutionResult {
31
+ status: "success" | "error" | "skipped";
32
+ runId?: string;
33
+ error?: string;
34
+ }
35
+ /** Execute one stored automation without changing its scheduled next run. */
36
+ export declare function runJobNow(owner: string, name: string, deps: SchedulerDeps, options?: {
37
+ historyId?: string;
38
+ }): Promise<JobExecutionResult>;
39
+ /** Process a durable run-now history row exactly once in the background worker. */
40
+ export declare function runQueuedAutomation(historyId: string, deps: SchedulerDeps): Promise<{
41
+ skipped: boolean;
42
+ runId?: string;
43
+ error?: string;
44
+ }>;
30
45
  //# sourceMappingURL=scheduler.d.ts.map
@@ -2,6 +2,7 @@ import { resourceGetByPath, resourceListAllOwners, resourcePutIfCurrent, } from
2
2
  import { backgroundRunCutOffReason, isBackgroundAutomationRunActive, resolveBackgroundAutomationIdentity, runBackgroundAutomation, } from "./background-automation-runner.js";
3
3
  import { nextOccurrence, isValidCron, describeCron, effectiveTimezone, } from "./cron.js";
4
4
  import { buildJobResourceContent, parseJobResource, } from "./frontmatter.js";
5
+ import { claimAutomationRun, finishAutomationRun, getAutomationRun, } from "./run-history.js";
5
6
  // ─── Frontmatter parsing ────────────────────────────────────────────────────
6
7
  export { classifyJobFrontmatter, classifyJobResource, normalizeJobMcpTools, parseJobResource, } from "./frontmatter.js";
7
8
  export function parseJobFrontmatter(content) {
@@ -128,7 +129,7 @@ export async function processRecurringJobs(deps) {
128
129
  }
129
130
  }
130
131
  export const jobRunCutOffReason = backgroundRunCutOffReason;
131
- async function executeJob(resource, meta, body, deps, now) {
132
+ async function executeJob(resource, meta, body, deps, now, options = {}) {
132
133
  const jobName = resource.path.replace(/^jobs\//, "").replace(/\.md$/, "");
133
134
  const jobContext = {
134
135
  name: jobName,
@@ -156,17 +157,36 @@ async function executeJob(resource, meta, body, deps, now) {
156
157
  meta.lastError = identity.reason;
157
158
  if (!alreadyRecorded)
158
159
  await updateResource(resource, meta, body);
159
- return;
160
+ if (options.historyId) {
161
+ await finishAutomationRun(options.historyId, "error", `Automation did not run: ${identity.reason}. No delivery was confirmed.`);
162
+ }
163
+ return { status: "skipped", error: identity.reason };
160
164
  }
161
165
  const jobUserEmail = identity.identity.userEmail;
162
166
  const jobOrgId = identity.identity.orgId;
167
+ // Manual runs use the same resource row as scheduled runs for concurrency
168
+ // protection. The check is paired with the conditional write below: two
169
+ // requests that read the same idle snapshot cannot both claim it.
170
+ if (options.manual && isBackgroundAutomationRunActive(meta, now)) {
171
+ const error = "The automation is already running.";
172
+ if (options.historyId) {
173
+ await finishAutomationRun(options.historyId, "error", `${error} No delivery was confirmed.`);
174
+ }
175
+ return { status: "skipped", error };
176
+ }
163
177
  // Mark as running
164
178
  meta.lastRun = now.toISOString();
165
179
  meta.lastStatus = "running";
166
180
  meta.lastError = undefined;
167
181
  if (!(await updateResource(resource, meta, body))) {
168
182
  console.log(`[recurring-jobs] "${resource.path}" changed before it could start; dropping this tick.`);
169
- return;
183
+ if (options.historyId) {
184
+ await finishAutomationRun(options.historyId, "error", "The automation changed before the run could start. No delivery was confirmed.");
185
+ }
186
+ return {
187
+ status: "error",
188
+ error: "The automation changed before the run could start.",
189
+ };
170
190
  }
171
191
  const requestContext = meta.originScopeId && meta.deliveryPlatform && meta.deliveryDestination
172
192
  ? {
@@ -193,33 +213,74 @@ async function executeJob(resource, meta, body, deps, now) {
193
213
  }
194
214
  : undefined;
195
215
  try {
196
- await runBackgroundAutomation({
216
+ const result = await runBackgroundAutomation({
197
217
  automation: jobContext,
198
218
  ownerEmail: jobUserEmail,
199
219
  orgId: jobOrgId,
200
- prompt: `[Recurring Job: ${jobName}]\nSchedule: ${describeCron(meta.schedule, effectiveTimezone(meta.timezone))}\n\nExecute the following job instructions:\n\n${body}`,
201
- threadTitle: `Job: ${jobName} ${now.toLocaleDateString()}`,
202
- runIdPrefix: `job-${jobName}`,
203
- usageLabel: `recurring-job:${jobName}`,
220
+ prompt: options.manual
221
+ ? `[Manual Automation Run: ${jobName}]\nThis run was explicitly started by the automation owner. Execute the following instructions now:\n\n${body}`
222
+ : `[Recurring Job: ${jobName}]\nSchedule: ${describeCron(meta.schedule, effectiveTimezone(meta.timezone))}\n\nExecute the following job instructions:\n\n${body}`,
223
+ threadTitle: `${options.manual ? "Automation" : "Job"}: ${jobName} — ${now.toLocaleDateString()}`,
224
+ runIdPrefix: `${options.manual ? "manual" : "job"}-${jobName}`,
225
+ usageLabel: `${options.manual ? "manual-automation" : "recurring-job"}:${jobName}`,
204
226
  requestContext,
227
+ ...(options.historyId ? { historyId: options.historyId } : {}),
228
+ actionCaller: "automation",
205
229
  }, deps);
206
230
  await recordExecutionOutcome(resource, {
207
231
  lastRun: meta.lastRun,
208
232
  lastStatus: "success",
209
233
  lastError: undefined,
234
+ advanceSchedule: options.advanceSchedule,
210
235
  });
211
236
  console.log(`[recurring-jobs] Job "${jobName}" completed.`);
237
+ return { status: "success", runId: result.runId };
212
238
  }
213
239
  catch (err) {
214
240
  const lastError = err instanceof Error ? err.message.slice(0, 200) : "Unknown error";
241
+ const reportedError = `${lastError}. No delivery was confirmed.`;
215
242
  await recordExecutionOutcome(resource, {
216
243
  lastRun: meta.lastRun,
217
244
  lastStatus: "error",
218
- lastError,
245
+ lastError: reportedError,
246
+ advanceSchedule: options.advanceSchedule,
219
247
  });
220
- console.error(`[recurring-jobs] Job "${jobName}" failed:`, lastError);
248
+ console.error(`[recurring-jobs] Job "${jobName}" failed:`, reportedError);
249
+ return { status: "error", error: reportedError };
221
250
  }
222
251
  }
252
+ /** Execute one stored automation without changing its scheduled next run. */
253
+ export async function runJobNow(owner, name, deps, options = {}) {
254
+ const path = `jobs/${name}.md`;
255
+ const resource = await resourceGetByPath(owner, path);
256
+ if (!resource)
257
+ throw new Error(`Automation "${name}" not found.`);
258
+ const { meta, body } = parseJobFrontmatter(resource.content);
259
+ if (!body.trim())
260
+ throw new Error(`Automation "${name}" has no instructions.`);
261
+ return executeJob(resource, meta, body, deps, new Date(), {
262
+ advanceSchedule: false,
263
+ historyId: options.historyId,
264
+ manual: true,
265
+ });
266
+ }
267
+ /** Process a durable run-now history row exactly once in the background worker. */
268
+ export async function runQueuedAutomation(historyId, deps) {
269
+ const queued = await getAutomationRun(historyId);
270
+ if (!queued)
271
+ throw new Error(`Automation run "${historyId}" not found.`);
272
+ if (!(await claimAutomationRun(historyId))) {
273
+ return { skipped: true };
274
+ }
275
+ const result = await runJobNow(queued.owner, queued.automation, deps, {
276
+ historyId,
277
+ });
278
+ return {
279
+ skipped: false,
280
+ ...(result.runId ? { runId: result.runId } : {}),
281
+ ...(result.error ? { error: result.error } : {}),
282
+ };
283
+ }
223
284
  async function updateResource(resource, meta, body) {
224
285
  const content = buildJobContent(meta, body);
225
286
  const written = await resourcePutIfCurrent({
@@ -256,8 +317,11 @@ async function recordExecutionOutcome(resource, outcome) {
256
317
  return;
257
318
  }
258
319
  const current = parseJobResource(latest.content);
259
- const meta = { ...current.meta, ...outcome };
260
- if (meta.schedule && isValidCron(meta.schedule)) {
320
+ const { advanceSchedule, ...execution } = outcome;
321
+ const meta = { ...current.meta, ...execution };
322
+ if (advanceSchedule !== false &&
323
+ meta.schedule &&
324
+ isValidCron(meta.schedule)) {
261
325
  // Measured from completion so a long run cannot immediately re-fire.
262
326
  meta.nextRun = nextOccurrence(meta.schedule, new Date(), meta.timezone).toISOString();
263
327
  }
@@ -57,6 +57,11 @@ declare const messages: {
57
57
  scopeLabel: string;
58
58
  scopePersonal: string;
59
59
  scopeWorkspace: string;
60
+ testStoredValue: string;
61
+ candidateValueWorking: string;
62
+ storedValueWorking: string;
63
+ invalid: string;
64
+ testFailed: string;
60
65
  scopePersonalDescription: string;
61
66
  scopeWorkspaceDescription: string;
62
67
  };
@@ -562,6 +567,9 @@ declare const messages: {
562
567
  eventTrigger: string;
563
568
  deleteAutomationTitle: string;
564
569
  deleteAutomationDescription: string;
570
+ runNow: string;
571
+ runNowTitle: string;
572
+ runNowDescription: string;
565
573
  automationDetails: string;
566
574
  automationEventDetails: string;
567
575
  condition: string;
@@ -62,6 +62,11 @@ const messages = {
62
62
  scopeLabel: "Scope",
63
63
  scopePersonal: "Personal",
64
64
  scopeWorkspace: "Workspace",
65
+ testStoredValue: "Test",
66
+ candidateValueWorking: "New value works",
67
+ storedValueWorking: "Working",
68
+ invalid: "Invalid",
69
+ testFailed: "Test failed",
65
70
  scopePersonalDescription: "Only your own signed-in sessions use this key. Integration, webhook, scheduled job, automation, and agent-to-agent runs sign in as their owner rather than as you, so they cannot read it.",
66
71
  scopeWorkspaceDescription: "Everyone in this workspace uses this key, including integration, webhook, scheduled job, automation, and agent-to-agent runs.",
67
72
  },
@@ -567,6 +572,9 @@ const messages = {
567
572
  eventTrigger: "Event-triggered",
568
573
  deleteAutomationTitle: "Delete automation?",
569
574
  deleteAutomationDescription: "This permanently removes the automation and cannot be undone.",
575
+ runNow: "Run now",
576
+ runNowTitle: "Run automation now?",
577
+ runNowDescription: "This runs the automation's real actions immediately. It may send messages or change data, and it will not change the next scheduled run.",
570
578
  automationDetails: "Automation details",
571
579
  automationEventDetails: "Runs when {{event}}.",
572
580
  condition: "Condition",
@@ -16,19 +16,19 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
- count?: undefined;
20
19
  updated: number;
21
20
  error?: undefined;
22
21
  ok?: undefined;
23
- } | {
24
22
  count?: undefined;
23
+ } | {
25
24
  updated?: undefined;
26
25
  error: string;
27
26
  ok?: undefined;
28
- } | {
29
27
  count?: undefined;
28
+ } | {
30
29
  updated?: undefined;
31
- error?: undefined;
32
30
  ok: boolean;
31
+ error?: undefined;
32
+ count?: undefined;
33
33
  }>>;
34
34
  //# sourceMappingURL=routes.d.ts.map
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
44
  summary: import("./types.js").TraceSummary;
46
45
  spans: import("./types.js").TraceSpan[];
47
46
  id?: undefined;
47
+ error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
- error?: undefined;
51
50
  summary?: undefined;
52
51
  spans?: undefined;
53
52
  id: string;
53
+ error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,10 +59,10 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
66
65
  ok: boolean;
66
+ error?: undefined;
67
67
  }>>;
68
68
  //# sourceMappingURL=routes.d.ts.map
@@ -75,10 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- message?: undefined;
79
- id?: undefined;
80
- found?: undefined;
81
78
  deleted?: undefined;
79
+ found?: undefined;
82
80
  providers: {
83
81
  id: string;
84
82
  label: string;
@@ -91,42 +89,44 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
91
89
  count: number;
92
90
  provider?: undefined;
93
91
  registered?: undefined;
92
+ id?: undefined;
94
93
  label?: undefined;
95
- } | {
96
94
  message?: undefined;
97
- id?: undefined;
95
+ } | {
98
96
  deleted?: undefined;
99
- providers?: undefined;
100
97
  count?: undefined;
98
+ providers?: undefined;
101
99
  found: boolean;
102
100
  provider: import("../custom-registry.js").CustomProviderConfig;
103
101
  registered?: undefined;
102
+ id?: undefined;
104
103
  label?: undefined;
105
- } | {
106
104
  message?: undefined;
105
+ } | {
107
106
  deleted?: undefined;
108
- providers?: undefined;
109
107
  count?: undefined;
108
+ providers?: undefined;
110
109
  provider?: undefined;
111
110
  found: boolean;
112
111
  id: string;
113
112
  registered?: undefined;
114
113
  label?: undefined;
115
- } | {
116
114
  message?: undefined;
115
+ } | {
116
+ count?: undefined;
117
117
  found?: undefined;
118
118
  providers?: undefined;
119
- count?: undefined;
120
119
  provider?: undefined;
121
120
  deleted: boolean;
122
121
  id: string;
123
122
  registered?: undefined;
124
123
  label?: undefined;
124
+ message?: undefined;
125
125
  } | {
126
- found?: undefined;
127
126
  deleted?: undefined;
128
- providers?: undefined;
129
127
  count?: undefined;
128
+ found?: undefined;
129
+ providers?: undefined;
130
130
  provider?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
@@ -329,9 +329,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
329
329
  label?: undefined;
330
330
  message?: undefined;
331
331
  } | {
332
- count?: undefined;
333
332
  id?: undefined;
334
333
  deleted?: undefined;
334
+ count?: undefined;
335
335
  providers?: undefined;
336
336
  found: boolean;
337
337
  provider: import("../custom-registry.js").CustomProviderConfig;
@@ -339,9 +339,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
339
339
  label?: undefined;
340
340
  message?: undefined;
341
341
  } | {
342
- count?: undefined;
343
342
  provider?: undefined;
344
343
  deleted?: undefined;
344
+ count?: undefined;
345
345
  providers?: undefined;
346
346
  found: boolean;
347
347
  id: string;
@@ -349,9 +349,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
349
349
  label?: undefined;
350
350
  message?: undefined;
351
351
  } | {
352
- count?: undefined;
353
352
  provider?: undefined;
354
353
  found?: undefined;
354
+ count?: undefined;
355
355
  providers?: undefined;
356
356
  deleted: boolean;
357
357
  id: string;
@@ -359,10 +359,10 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
359
359
  label?: undefined;
360
360
  message?: undefined;
361
361
  } | {
362
- count?: undefined;
363
362
  provider?: undefined;
364
363
  deleted?: undefined;
365
364
  found?: undefined;
365
+ count?: undefined;
366
366
  providers?: undefined;
367
367
  registered: boolean;
368
368
  id: string;
@@ -73,6 +73,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
73
73
  offset?: unknown;
74
74
  limit?: unknown;
75
75
  }, {
76
+ deleted?: undefined;
76
77
  jobs: {
77
78
  id: string;
78
79
  name: string;
@@ -83,7 +84,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
83
84
  updatedAt: string;
84
85
  }[];
85
86
  total: number;
86
- deleted?: undefined;
87
87
  jobId?: undefined;
88
88
  } | {
89
89
  jobs?: undefined;
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
91
91
  deleted: boolean;
92
92
  jobId: string;
93
93
  } | {
94
+ deleted?: undefined;
94
95
  jobs?: undefined;
95
96
  total?: undefined;
96
- deleted?: undefined;
97
97
  jobId?: undefined;
98
98
  hits: Record<string, unknown>[];
99
99
  offset: number;
@@ -34,29 +34,29 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- status?: undefined;
38
37
  ok?: undefined;
38
+ status?: undefined;
39
39
  } | {
40
40
  error?: undefined;
41
41
  ok: boolean;
42
42
  status: string;
43
43
  } | {
44
+ ok?: undefined;
44
45
  error: string;
45
46
  removed?: undefined;
46
- ok?: undefined;
47
47
  } | {
48
48
  error?: undefined;
49
49
  ok: boolean;
50
50
  removed: boolean;
51
51
  }>>;
52
52
  /**
53
- * POST /_agent-native/secrets/:key/test — re-run the validator against the
54
- * current stored value without changing anything. Useful for the "Test" button.
53
+ * POST /_agent-native/secrets/:key/test — validate an optional candidate value
54
+ * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
+ ok?: undefined;
57
58
  error: string;
58
59
  note?: undefined;
59
- ok?: undefined;
60
60
  } | {
61
61
  error?: undefined;
62
62
  ok: boolean;
@@ -277,8 +277,8 @@ async function handleDelete(event, secret) {
277
277
  return { ok: true, removed };
278
278
  }
279
279
  /**
280
- * POST /_agent-native/secrets/:key/test — re-run the validator against the
281
- * current stored value without changing anything. Useful for the "Test" button.
280
+ * POST /_agent-native/secrets/:key/test — validate an optional candidate value
281
+ * or the current stored value without changing anything.
282
282
  */
283
283
  export function createTestSecretHandler() {
284
284
  return defineEventHandler(async (event) => {
@@ -301,25 +301,49 @@ export function createTestSecretHandler() {
301
301
  const has = await hasOAuthSecretForEvent(event, secret).catch(() => false);
302
302
  return { ok: has };
303
303
  }
304
- if (!secret.validator) {
305
- return { ok: true, note: "No validator registered" };
304
+ const body = (await readBody(event).catch(() => ({})));
305
+ const hasCandidateValue = Object.hasOwn(body, "value");
306
+ const candidateValue = typeof body.value === "string" ? body.value.trim() : undefined;
307
+ if (hasCandidateValue && !candidateValue) {
308
+ setResponseStatus(event, 400);
309
+ return { error: "value must be a non-empty string" };
306
310
  }
307
- const { scopeId } = await resolveScopeId(event, secret.scope);
311
+ const { scopeId, reason } = await resolveScopeId(event, secret.scope);
308
312
  if (!scopeId) {
309
313
  setResponseStatus(event, 401);
310
- return { error: "Unable to resolve scope" };
314
+ return { error: reason ?? "Unable to resolve scope" };
311
315
  }
312
- const stored = await readAppSecret({
313
- key: secret.key,
314
- scope: secret.scope,
315
- scopeId,
316
- });
317
- if (!stored) {
318
- setResponseStatus(event, 404);
319
- return { error: "No value stored" };
316
+ if (secret.scope === "workspace" &&
317
+ !(await canMutateWorkspaceScope(event, scopeId))) {
318
+ setResponseStatus(event, 403);
319
+ return {
320
+ error: "Only organization owners and admins can set workspace-scoped secrets",
321
+ };
322
+ }
323
+ if (secret.scope === "org" && !(await canMutateOrgScope(event, scopeId))) {
324
+ setResponseStatus(event, 403);
325
+ return {
326
+ error: "Only organization owners and admins can set org-scoped secrets",
327
+ };
328
+ }
329
+ if (!secret.validator) {
330
+ return { ok: true, note: "No validator registered" };
331
+ }
332
+ let value = candidateValue;
333
+ if (!value) {
334
+ const stored = await readAppSecret({
335
+ key: secret.key,
336
+ scope: secret.scope,
337
+ scopeId,
338
+ });
339
+ if (!stored) {
340
+ setResponseStatus(event, 404);
341
+ return { error: "No value stored" };
342
+ }
343
+ value = stored.value;
320
344
  }
321
345
  try {
322
- const result = await secret.validator(stored.value);
346
+ const result = await secret.validator(value);
323
347
  const ok = typeof result === "boolean" ? result : result?.ok === true;
324
348
  if (!ok) {
325
349
  const err = typeof result === "object" && result && result.error
@@ -327,7 +351,7 @@ export function createTestSecretHandler() {
327
351
  : "Validator rejected the value";
328
352
  return {
329
353
  ok: false,
330
- error: redactSecretFromMessage(err, stored.value),
354
+ error: redactSecretFromMessage(err, value),
331
355
  };
332
356
  }
333
357
  return { ok: true };
@@ -338,7 +362,7 @@ export function createTestSecretHandler() {
338
362
  : "Validator threw";
339
363
  return {
340
364
  ok: false,
341
- error: redactSecretFromMessage(message, stored.value),
365
+ error: redactSecretFromMessage(message, value),
342
366
  };
343
367
  }
344
368
  });
@@ -540,6 +540,10 @@ export async function mergeCoreSharingActions(registry) {
540
540
  "manage-recurring-job",
541
541
  () => import("../jobs/actions/manage-recurring-job.js"),
542
542
  ],
543
+ [
544
+ "run-automation-now",
545
+ () => import("../jobs/actions/run-automation-now.js"),
546
+ ],
543
547
  [
544
548
  "list-automation-runs",
545
549
  () => import("../jobs/actions/list-automation-runs.js"),