@agent-native/core 0.210.2 → 0.211.0-nightly-20261020183616

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 (68) hide show
  1. package/dist/action.d.ts +8 -0
  2. package/dist/action.js +17 -0
  3. package/dist/agent/production-agent.js +11 -20
  4. package/dist/client/chat/agentkit-protocol.js +48 -5
  5. package/dist/client/chat/runtime.d.ts +2 -0
  6. package/dist/client/chat/runtime.js +6 -1
  7. package/dist/client/hooks/index.d.ts +1 -1
  8. package/dist/client/hooks/index.js +1 -1
  9. package/dist/client/resources/mcp-integration-logos.js +4 -0
  10. package/dist/client/session-identity.d.ts +8 -0
  11. package/dist/client/session-identity.js +13 -0
  12. package/dist/client/use-action.d.ts +14 -0
  13. package/dist/client/use-action.js +117 -28
  14. package/dist/client/use-session.js +2 -0
  15. package/dist/collab/awareness.d.ts +2 -2
  16. package/dist/collab/routes.d.ts +1 -1
  17. package/dist/collab/struct-routes.d.ts +1 -1
  18. package/dist/deploy/build.js +24 -3
  19. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  20. package/dist/mcp/actions/revoke-org-service-token.js +5 -1
  21. package/dist/mcp/execute-action-call.js +8 -17
  22. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  23. package/dist/notifications/routes.d.ts +1 -1
  24. package/dist/observability/routes.d.ts +5 -5
  25. package/dist/org/actions/set-app-member-roles.js +4 -0
  26. package/dist/org/actions/set-app-permission-roles.js +4 -0
  27. package/dist/org/actions/set-workspace-app-access.js +4 -0
  28. package/dist/org/index.d.ts +2 -2
  29. package/dist/progress/routes.d.ts +1 -1
  30. package/dist/provider-api/actions/custom-provider-registration.d.ts +17 -17
  31. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  32. package/dist/resources/handlers.d.ts +1 -1
  33. package/dist/review/suggestions/actions.d.ts +1 -0
  34. package/dist/review/suggestions/actions.js +54 -42
  35. package/dist/secrets/actions/delete-api-key.js +4 -0
  36. package/dist/secrets/routes.d.ts +4 -4
  37. package/dist/server/action-approval.d.ts +15 -0
  38. package/dist/server/action-approval.js +39 -0
  39. package/dist/server/action-routes.js +33 -28
  40. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  41. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  42. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  43. package/dist/server/edge.d.ts +1 -0
  44. package/dist/server/edge.js +1 -0
  45. package/dist/server/realtime-token.d.ts +1 -1
  46. package/dist/server/transcribe-voice.d.ts +1 -1
  47. package/dist/server/workspace-provider-oauth.js +1 -0
  48. package/dist/shared/action-error-codes.d.ts +5 -0
  49. package/dist/shared/action-error-codes.js +5 -0
  50. package/dist/shared/index.d.ts +1 -0
  51. package/dist/shared/index.js +1 -0
  52. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  53. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
  54. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  55. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  56. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  57. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
  58. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
  59. package/dist/vite/client.js +2 -1
  60. package/docs/content/actions-access-control.mdx +3 -3
  61. package/package.json +3 -3
  62. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  63. package/src/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
  64. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  65. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  66. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  67. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
  68. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
@@ -1127,7 +1127,9 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
1127
1127
  actionRegistrations.push(` const ${handlerName} = defineEventHandler(async (event) => {
1128
1128
  setResponseHeader(event, "Cache-Control", "no-" + "store");
1129
1129
  const actionIsUiOnly = ${a.uiOnly ? "true" : `${varName}.uiOnly === true`};
1130
- const uiActionContext = actionIsUiOnly
1130
+ const actionDeclaresApproval =
1131
+ ${varName}.needsApproval !== undefined && ${varName}.needsApproval !== false;
1132
+ const uiActionContext = actionIsUiOnly || actionDeclaresApproval
1131
1133
  ? await getGeneratedUiActionContext(event)
1132
1134
  : undefined;
1133
1135
  if (actionIsUiOnly && !uiActionContext) {
@@ -1175,10 +1177,29 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
1175
1177
  actionName: ${JSON.stringify(a.name)},
1176
1178
  ...(actionContext ?? {}),
1177
1179
  };
1178
- const runAction = () => ${varName}.run(params, actionRunContext);
1180
+ const runAction = async () => {
1181
+ if (
1182
+ actionDeclaresApproval &&
1183
+ !(uiActionContext && caller === "frontend")
1184
+ ) {
1185
+ const refusal = await generatedActionApprovalRefusal(
1186
+ ${varName},
1187
+ params,
1188
+ actionRunContext,
1189
+ );
1190
+ if (refusal) {
1191
+ return new Response(
1192
+ JSON.stringify({ error: refusal.message, errorCode: refusal.errorCode }),
1193
+ { status: refusal.statusCode, headers: { "Content-Type": "application/json" } },
1194
+ );
1195
+ }
1196
+ }
1197
+ return ${varName}.run(params, actionRunContext);
1198
+ };
1179
1199
  const result = actionContext
1180
1200
  ? await runWithGeneratedRequestContext(actionContext, runAction)
1181
1201
  : await runAction();
1202
+ if (result instanceof Response) return result;
1182
1203
  if (typeof result === "string") { try { return JSON.parse(result); } catch { return result; } }
1183
1204
  return result;
1184
1205
  } catch (err) {
@@ -1200,7 +1221,7 @@ ${["post", "put", "delete"]
1200
1221
  getFrameworkRoutePrefix as getAgentNativeFrameworkRoutePrefix,
1201
1222
  getSsrAuthRedirectScript as getAgentNativeSsrAuthRedirectScript,
1202
1223
  resolveAppHomePath as resolveAgentNativeAppHomePath,
1203
- ${hasActions ? " getSession as getGeneratedSession,\n hasUiActionCapability as hasGeneratedUiActionCapability,\n isSameOriginRequest as isGeneratedSameOriginRequest,\n mountUiActionCapabilityRoute as mountGeneratedUiActionCapabilityRoute,\n resolveOrgIdForEmailViaEvent as resolveGeneratedOrgId,\n runWithRequestContext as runWithGeneratedRequestContext,\n" : ""}
1224
+ ${hasActions ? " directHttpActionApprovalRefusal as generatedActionApprovalRefusal,\n getSession as getGeneratedSession,\n hasUiActionCapability as hasGeneratedUiActionCapability,\n isSameOriginRequest as isGeneratedSameOriginRequest,\n mountUiActionCapabilityRoute as mountGeneratedUiActionCapabilityRoute,\n resolveOrgIdForEmailViaEvent as resolveGeneratedOrgId,\n runWithRequestContext as runWithGeneratedRequestContext,\n" : ""}
1204
1225
  } from "${EDGE_SERVER_ENTRYPOINT}";`);
1205
1226
  for (let i = 0; i < edgePlugins.length; i++) {
1206
1227
  const varName = `plugin_${i}`;
@@ -33,9 +33,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
33
33
  idempotencyKey: string;
34
34
  deleted: boolean;
35
35
  } | {
36
+ id?: undefined;
36
37
  error: string;
37
38
  url?: undefined;
38
- id?: undefined;
39
39
  provider?: undefined;
40
40
  configured?: undefined;
41
41
  } | {
@@ -45,8 +45,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
45
45
  configured?: undefined;
46
46
  error?: undefined;
47
47
  } | {
48
- url?: undefined;
49
48
  id?: undefined;
49
+ url?: undefined;
50
50
  provider?: undefined;
51
51
  error: string;
52
52
  configured: boolean;
@@ -10,11 +10,15 @@ import { defineAction } from "../../action.js";
10
10
  import { revokeOrgServiceToken } from "../connect-store.js";
11
11
  import { requireServiceTokenCaller } from "./service-token-access.js";
12
12
  export default defineAction({
13
- description: "Revoke one of your organization's service tokens by id (get ids from list-org-service-tokens). The token stops working immediately. Org owner/admin only. Idempotent.",
13
+ description: "Revoke one of your organization's service tokens by id (get ids from list-org-service-tokens). The token stops working immediately. Every call needs the user's approval. Org owner/admin only. Idempotent.",
14
14
  schema: z.object({
15
15
  id: z.string().min(1).describe("Service token id to revoke"),
16
16
  }),
17
17
  toolCallable: false,
18
+ // It creates, changes, or revokes a credential or who may use one: every
19
+ // call needs the user's approval, and "always allow" is not offered.
20
+ needsApproval: true,
21
+ allowPersistentApproval: false,
18
22
  run: async (args, ctx) => {
19
23
  const caller = await requireServiceTokenCaller({
20
24
  userEmail: ctx?.userEmail,
@@ -1,6 +1,6 @@
1
1
  import { isMcpReadOnlyTool } from "../action-call-classification.js";
2
2
  import { vouchedOutcome } from "../action-frame.js";
3
- import { actionCallIntent, isActionContractError, recordInvalidActionCall, preparedCallArguments, runPreparedActionCall, } from "../action.js";
3
+ import { actionCallIntent, isActionContractError, recordInvalidActionCall, preparedCallNeedsApproval, runPreparedActionCall, } from "../action.js";
4
4
  import { AGENT_IMAGES_FIELD } from "../agent/tool-result-images.js";
5
5
  import { MCP_ACTION_RESULT_MARKER, isMcpActionResult, mcpActionResultText, } from "../mcp-client/app-result.js";
6
6
  import { assertServicePrincipalMayCall, assertServicePrincipalMayRun, recordServicePrincipalDenial, ServicePrincipalRefusedError, } from "../org/service-principal-guard.js";
@@ -564,22 +564,13 @@ async function authorizeApproval(context, call, entry, args, prepared) {
564
564
  if (entry.needsApproval === undefined) {
565
565
  return { status: "authorized", clientAccepted: false };
566
566
  }
567
- let mustApprove = false;
568
- try {
569
- mustApprove =
570
- typeof entry.needsApproval === "function"
571
- ? Boolean(await entry.needsApproval(preparedCallArguments(prepared), {
572
- userEmail: getRequestUserEmail(),
573
- orgId: getRequestOrgId() ?? null,
574
- appId: context.appId,
575
- caller: "mcp",
576
- actionName: name,
577
- }))
578
- : entry.needsApproval === true;
579
- }
580
- catch {
581
- mustApprove = true;
582
- }
567
+ const mustApprove = await preparedCallNeedsApproval(entry, prepared, {
568
+ userEmail: getRequestUserEmail(),
569
+ orgId: getRequestOrgId() ?? null,
570
+ appId: context.appId,
571
+ caller: "mcp",
572
+ actionName: name,
573
+ });
583
574
  if (!mustApprove)
584
575
  return { status: "authorized", clientAccepted: false };
585
576
  if (!hasVerifiedUserIdentity) {
@@ -140,13 +140,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
140
140
  clientHint?: undefined;
141
141
  timestamp?: undefined;
142
142
  chapterId?: undefined;
143
- count?: undefined;
144
143
  startAt?: undefined;
145
144
  endAt?: undefined;
146
145
  reason?: undefined;
147
146
  includeMicrophone?: undefined;
148
147
  includeSystemAudio?: undefined;
149
148
  requestId?: undefined;
149
+ count?: undefined;
150
150
  };
151
151
  required?: undefined;
152
152
  };
@@ -171,13 +171,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
171
171
  clientHint?: undefined;
172
172
  timestamp?: undefined;
173
173
  chapterId?: undefined;
174
- count?: undefined;
175
174
  startAt?: undefined;
176
175
  endAt?: undefined;
177
176
  reason?: undefined;
178
177
  includeMicrophone?: undefined;
179
178
  includeSystemAudio?: undefined;
180
179
  requestId?: undefined;
180
+ count?: undefined;
181
181
  };
182
182
  required?: undefined;
183
183
  };
@@ -196,13 +196,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
196
196
  clientHint?: undefined;
197
197
  timestamp?: undefined;
198
198
  chapterId?: undefined;
199
- count?: undefined;
200
199
  startAt?: undefined;
201
200
  endAt?: undefined;
202
201
  reason?: undefined;
203
202
  includeMicrophone?: undefined;
204
203
  includeSystemAudio?: undefined;
205
204
  requestId?: undefined;
205
+ count?: undefined;
206
206
  };
207
207
  required?: undefined;
208
208
  };
@@ -231,13 +231,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
231
231
  };
232
232
  timestamp?: undefined;
233
233
  chapterId?: undefined;
234
- count?: undefined;
235
234
  startAt?: undefined;
236
235
  endAt?: undefined;
237
236
  reason?: undefined;
238
237
  includeMicrophone?: undefined;
239
238
  includeSystemAudio?: undefined;
240
239
  requestId?: undefined;
240
+ count?: undefined;
241
241
  };
242
242
  };
243
243
  } | {
@@ -260,12 +260,12 @@ export declare function screenMemoryMcpToolDefinitions(): ({
260
260
  description: string;
261
261
  };
262
262
  chapterId?: undefined;
263
- count?: undefined;
264
263
  startAt?: undefined;
265
264
  endAt?: undefined;
266
265
  includeMicrophone?: undefined;
267
266
  includeSystemAudio?: undefined;
268
267
  requestId?: undefined;
268
+ count?: undefined;
269
269
  };
270
270
  };
271
271
  } | {
@@ -317,7 +317,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
317
317
  clientHint?: undefined;
318
318
  timestamp?: undefined;
319
319
  chapterId?: undefined;
320
- count?: undefined;
321
320
  startAt: {
322
321
  type: string;
323
322
  description: string;
@@ -337,6 +336,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
337
336
  type: string;
338
337
  };
339
338
  requestId?: undefined;
339
+ count?: undefined;
340
340
  };
341
341
  };
342
342
  } | {
@@ -352,7 +352,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
352
352
  clientHint?: undefined;
353
353
  timestamp?: undefined;
354
354
  chapterId?: undefined;
355
- count?: undefined;
356
355
  startAt?: undefined;
357
356
  endAt?: undefined;
358
357
  reason?: undefined;
@@ -361,6 +360,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
361
360
  requestId: {
362
361
  type: string;
363
362
  };
363
+ count?: undefined;
364
364
  };
365
365
  };
366
366
  })[];
@@ -16,6 +16,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  } | {
17
17
  count?: undefined;
18
18
  updated?: undefined;
19
- ok: boolean;
20
19
  error?: undefined;
20
+ ok: boolean;
21
21
  }>>;
@@ -27,39 +27,39 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
27
27
  thumbsUpRate: number;
28
28
  avgEvalScore: number;
29
29
  } | {
30
+ ok?: undefined;
30
31
  error: string;
31
32
  message: string;
32
33
  summary?: undefined;
33
34
  spans?: undefined;
34
- ok?: undefined;
35
35
  } | {
36
36
  error?: undefined;
37
+ ok?: undefined;
37
38
  message?: undefined;
38
39
  summary: import("./types.js").TraceSummary;
39
40
  spans: import("./types.js").TraceSpan[];
40
- ok?: undefined;
41
41
  } | {
42
42
  error?: undefined;
43
+ ok?: undefined;
43
44
  message?: undefined;
44
45
  summary?: undefined;
45
46
  spans?: undefined;
46
47
  traceMissing: true;
47
48
  id: string;
48
- ok?: undefined;
49
49
  } | {
50
50
  error?: undefined;
51
+ ok?: undefined;
51
52
  message?: undefined;
52
53
  summary?: undefined;
53
54
  spans?: undefined;
54
55
  traceMissing?: undefined;
55
56
  id: string;
56
- ok?: undefined;
57
57
  } | {
58
+ ok?: undefined;
58
59
  message?: undefined;
59
60
  summary?: undefined;
60
61
  spans?: undefined;
61
62
  error: any;
62
- ok?: undefined;
63
63
  } | {
64
64
  error?: undefined;
65
65
  message?: undefined;
@@ -19,6 +19,10 @@ export default defineAction({
19
19
  return `Updated ${args.appId} roles for ${args.email}: [${(change.previousRoles ?? []).join(", ")}] -> [${(change.roles ?? []).join(", ")}]`;
20
20
  },
21
21
  }),
22
+ // It changes who may use an app or what they may do in it: every call
23
+ // needs the user's approval, and "always allow" is not offered.
24
+ needsApproval: true,
25
+ allowPersistentApproval: false,
22
26
  run: async ({ appId, email, roles }, ctx) => {
23
27
  const caller = await requireOrgMember(ctx, true);
24
28
  const descriptor = getRegisteredAppRoles(appId);
@@ -19,6 +19,10 @@ export default defineAction({
19
19
  return `${args.reset ? "Reset" : "Updated"} ${args.appId} permission ${args.permission}: [${(change.previousRoles ?? []).join(", ")}] -> [${(change.roles ?? []).join(", ")}]`;
20
20
  },
21
21
  }),
22
+ // It changes who may use an app or what they may do in it: every call
23
+ // needs the user's approval, and "always allow" is not offered.
24
+ needsApproval: true,
25
+ allowPersistentApproval: false,
22
26
  run: async ({ appId, permission, roles, reset }, ctx) => {
23
27
  const caller = await requireOrgMember(ctx, true);
24
28
  const descriptor = getRegisteredAppRoles(appId);
@@ -16,6 +16,10 @@ export default defineAction({
16
16
  targetId: (args) => args.appId,
17
17
  summary: (args) => `Set ${args.appId} workspace access to ${args.mode}`,
18
18
  }),
19
+ // It changes who may use an app or what they may do in it: every call
20
+ // needs the user's approval, and "always allow" is not offered.
21
+ needsApproval: true,
22
+ allowPersistentApproval: false,
19
23
  run: async ({ appId, mode }, ctx) => {
20
24
  const caller = await requireOrgMember(ctx, true);
21
25
  const db = getDbExec();
@@ -162,10 +162,10 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
162
162
  soloDeploymentAdmin: boolean;
163
163
  }>>;
164
164
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
165
- name?: undefined;
166
165
  success: boolean;
167
- role?: undefined;
168
166
  id?: undefined;
167
+ name?: undefined;
168
+ role?: undefined;
169
169
  } | {
170
170
  success?: undefined;
171
171
  id: string;
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- error?: undefined;
6
5
  ok: boolean;
6
+ error?: undefined;
7
7
  }>>;
@@ -75,10 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- provider?: undefined;
78
+ message?: undefined;
79
79
  id?: undefined;
80
- deleted?: undefined;
81
- found?: undefined;
82
80
  providers: {
83
81
  id: string;
84
82
  label: string;
@@ -89,45 +87,47 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
89
87
  updatedAt: number;
90
88
  }[];
91
89
  count: number;
90
+ provider?: undefined;
91
+ found?: undefined;
92
+ deleted?: undefined;
92
93
  registered?: undefined;
93
94
  label?: undefined;
94
- message?: undefined;
95
95
  } | {
96
+ message?: undefined;
96
97
  id?: undefined;
97
- count?: undefined;
98
- deleted?: undefined;
99
98
  providers?: undefined;
99
+ count?: undefined;
100
100
  found: boolean;
101
101
  provider: import("../custom-registry.js").CustomProviderConfig;
102
+ deleted?: undefined;
102
103
  registered?: undefined;
103
104
  label?: undefined;
104
- message?: undefined;
105
105
  } | {
106
- provider?: undefined;
107
- count?: undefined;
108
- deleted?: undefined;
106
+ message?: undefined;
109
107
  providers?: undefined;
108
+ count?: undefined;
109
+ provider?: undefined;
110
110
  found: boolean;
111
111
  id: string;
112
+ deleted?: undefined;
112
113
  registered?: undefined;
113
114
  label?: undefined;
114
- message?: undefined;
115
115
  } | {
116
- provider?: undefined;
116
+ message?: undefined;
117
+ providers?: undefined;
117
118
  count?: undefined;
119
+ provider?: undefined;
118
120
  found?: undefined;
119
- providers?: undefined;
120
121
  deleted: boolean;
121
122
  id: string;
122
123
  registered?: undefined;
123
124
  label?: undefined;
124
- message?: undefined;
125
125
  } | {
126
- provider?: undefined;
126
+ providers?: undefined;
127
127
  count?: undefined;
128
- deleted?: undefined;
128
+ provider?: undefined;
129
129
  found?: undefined;
130
- providers?: undefined;
130
+ deleted?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
133
133
  label: string;
@@ -316,11 +316,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
316
316
  notes?: string | undefined;
317
317
  scope?: "org" | "user" | undefined;
318
318
  }, {
319
- id?: undefined;
320
319
  provider?: undefined;
321
- message?: undefined;
320
+ id?: undefined;
322
321
  deleted?: undefined;
323
- found?: undefined;
324
322
  providers: {
325
323
  id: string;
326
324
  label: string;
@@ -331,44 +329,46 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
331
329
  updatedAt: number;
332
330
  }[];
333
331
  count: number;
332
+ found?: undefined;
334
333
  registered?: undefined;
335
334
  label?: undefined;
335
+ message?: undefined;
336
336
  } | {
337
337
  id?: undefined;
338
- count?: undefined;
339
- message?: undefined;
340
338
  deleted?: undefined;
341
339
  providers?: undefined;
340
+ count?: undefined;
342
341
  found: boolean;
343
342
  provider: import("../custom-registry.js").CustomProviderConfig;
344
343
  registered?: undefined;
345
344
  label?: undefined;
345
+ message?: undefined;
346
346
  } | {
347
347
  provider?: undefined;
348
- count?: undefined;
349
- message?: undefined;
350
348
  deleted?: undefined;
351
349
  providers?: undefined;
350
+ count?: undefined;
352
351
  found: boolean;
353
352
  id: string;
354
353
  registered?: undefined;
355
354
  label?: undefined;
355
+ message?: undefined;
356
356
  } | {
357
357
  provider?: undefined;
358
+ providers?: undefined;
358
359
  count?: undefined;
359
- message?: undefined;
360
360
  found?: undefined;
361
- providers?: undefined;
362
361
  deleted: boolean;
363
362
  id: string;
364
363
  registered?: undefined;
365
364
  label?: undefined;
365
+ message?: undefined;
366
366
  } | {
367
367
  provider?: undefined;
368
- count?: undefined;
369
368
  deleted?: undefined;
370
- found?: undefined;
371
369
  providers?: undefined;
370
+ count?: undefined;
371
+ found?: undefined;
372
372
  registered: boolean;
373
373
  id: string;
374
374
  label: string;
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
39
39
  error: string;
40
40
  }>;
41
41
  export declare function handleDeleteResource(event: any): Promise<{
42
- ok?: undefined;
43
42
  error: string;
43
+ ok?: undefined;
44
44
  } | {
45
45
  error?: undefined;
46
46
  ok: boolean;
@@ -83,6 +83,7 @@ export declare const createResourceSuggestionProposal: import("../../action.js")
83
83
  }[];
84
84
  metadata?: Record<string, unknown>;
85
85
  }[];
86
+ supersedes?: string[];
86
87
  }, {
87
88
  proposal: import("./types.js").ResourceSuggestionProposal;
88
89
  suggestions: ResourceSuggestion[];
@@ -58,6 +58,50 @@ function assertCreationReplay(receipt, requestHash, authorEmail, actorKind) {
58
58
  }
59
59
  return receipt.suggestion;
60
60
  }
61
+ // One revised proposal can replace every edit of the one before it.
62
+ const MAX_SUPERSEDED = 100;
63
+ async function supersedeSuggestions(tx, args, authorEmail, replacementId) {
64
+ for (const supersededId of new Set(args.supersedes ?? [])) {
65
+ const prior = await getSuggestion(supersededId, tx);
66
+ if (!prior ||
67
+ prior.resourceType !== args.resourceType ||
68
+ prior.resourceId !== args.resourceId) {
69
+ fail("A superseded suggestion was not found on this resource", {
70
+ statusCode: 404,
71
+ errorCode: "not_found",
72
+ });
73
+ }
74
+ if (!authorEmail || prior.authorEmail !== authorEmail) {
75
+ fail("Only the author can supersede this suggestion", {
76
+ statusCode: 403,
77
+ errorCode: "forbidden",
78
+ });
79
+ }
80
+ if (prior.status !== "pending")
81
+ continue;
82
+ const claimed = await updateSuggestionStatus(tx, prior.id, "superseded", prior.revision);
83
+ if (!claimed) {
84
+ const latest = await getSuggestion(prior.id, tx);
85
+ // A reviewer decided it first; only pending proposals are replaced.
86
+ if (latest && latest.status !== "pending")
87
+ continue;
88
+ fail("The superseded suggestion changed; refresh and try again", {
89
+ statusCode: 409,
90
+ errorCode: "suggestion_conflict",
91
+ });
92
+ }
93
+ await recordDecision(tx, {
94
+ suggestionId: prior.id,
95
+ idempotencyKey: `${args.idempotencyKey}:supersedes:${prior.id}`,
96
+ reviewer: authorEmail,
97
+ decision: "superseded",
98
+ observedBase: prior.baseRevision,
99
+ outcome: "superseded",
100
+ detail: replacementId,
101
+ });
102
+ await resolveReviewThreadWithClient(tx, prior.threadId, authorEmail, { resourceType: prior.resourceType, resourceId: prior.resourceId }, "superseded");
103
+ }
104
+ }
61
105
  export const createResourceSuggestion = defineAction({
62
106
  description: "Create a typed pending suggestion without changing the canonical resource. Pass supersedes to replace your own earlier pending suggestions on the same resource in the same step.",
63
107
  schema: z.object({
@@ -70,7 +114,7 @@ export const createResourceSuggestion = defineAction({
70
114
  metadata: z.record(z.string(), z.unknown()).optional(),
71
115
  supersedes: z
72
116
  .array(z.string().min(1))
73
- .max(20)
117
+ .max(MAX_SUPERSEDED)
74
118
  .optional()
75
119
  .describe("IDs of your own earlier suggestions on this resource that this one replaces. Pending ones become superseded atomically with this creation; already decided ones are left as they are."),
76
120
  }),
@@ -133,46 +177,7 @@ export const createResourceSuggestion = defineAction({
133
177
  threadComment: null,
134
178
  };
135
179
  }
136
- for (const supersededId of new Set(args.supersedes ?? [])) {
137
- const prior = await getSuggestion(supersededId, tx);
138
- if (!prior ||
139
- prior.resourceType !== args.resourceType ||
140
- prior.resourceId !== args.resourceId) {
141
- fail("A superseded suggestion was not found on this resource", {
142
- statusCode: 404,
143
- errorCode: "not_found",
144
- });
145
- }
146
- if (!authorEmail || prior.authorEmail !== authorEmail) {
147
- fail("Only the author can supersede this suggestion", {
148
- statusCode: 403,
149
- errorCode: "forbidden",
150
- });
151
- }
152
- if (prior.status !== "pending")
153
- continue;
154
- const claimed = await updateSuggestionStatus(tx, prior.id, "superseded", prior.revision);
155
- if (!claimed) {
156
- const latest = await getSuggestion(prior.id, tx);
157
- // A reviewer decided it first; only pending proposals are replaced.
158
- if (latest && latest.status !== "pending")
159
- continue;
160
- fail("The superseded suggestion changed; refresh and try again", {
161
- statusCode: 409,
162
- errorCode: "suggestion_conflict",
163
- });
164
- }
165
- await recordDecision(tx, {
166
- suggestionId: prior.id,
167
- idempotencyKey: `${args.idempotencyKey}:supersedes:${prior.id}`,
168
- reviewer: authorEmail,
169
- decision: "superseded",
170
- observedBase: prior.baseRevision,
171
- outcome: "superseded",
172
- detail: created.id,
173
- });
174
- await resolveReviewThreadWithClient(tx, prior.threadId, authorEmail, { resourceType: prior.resourceType, resourceId: prior.resourceId }, "superseded");
175
- }
180
+ await supersedeSuggestions(tx, args, authorEmail, created.id);
176
181
  const threadComment = await insertReviewCommentWithClient({
177
182
  resourceType: created.resourceType,
178
183
  resourceId: created.resourceId,
@@ -533,6 +538,11 @@ const proposalCreationSchema = z.object({
533
538
  metadata: z.record(z.string(), z.unknown()).optional(),
534
539
  }))
535
540
  .min(1),
541
+ supersedes: z
542
+ .array(z.string().min(1))
543
+ .max(MAX_SUPERSEDED)
544
+ .optional()
545
+ .describe("IDs of your own earlier suggestions on this resource that this proposal replaces. Pending ones become superseded atomically with this creation; already decided ones are left as they are."),
536
546
  });
537
547
  export const getResourceSuggestionProposalByCreationKey = defineAction({
538
548
  description: "Find an earlier proposal creation by its idempotency key before recomputing an edit.",
@@ -565,7 +575,7 @@ export const getResourceSuggestionProposalByCreationKey = defineAction({
565
575
  },
566
576
  });
567
577
  export const createResourceSuggestionProposal = defineAction({
568
- description: "Create or append independently reviewable suggestions under one durable proposal.",
578
+ description: "Create or append independently reviewable suggestions under one durable proposal. Pass supersedes to replace your own earlier pending suggestions on the same resource in the same step.",
569
579
  schema: proposalCreationSchema,
570
580
  run: async (args, ctx) => {
571
581
  await assertReviewableResourceAccess(args.resourceType, args.resourceId, ctx, "commenter");
@@ -582,6 +592,7 @@ export const createResourceSuggestionProposal = defineAction({
582
592
  summary: args.summary,
583
593
  operations: args.suggestions,
584
594
  metadata: { proposalId: args.proposalId ?? null },
595
+ supersedes: args.supersedes,
585
596
  });
586
597
  const db = getDbExec();
587
598
  await ensureSuggestionTables();
@@ -701,6 +712,7 @@ export const createResourceSuggestionProposal = defineAction({
701
712
  const recorded = await recordProposalCreation(tx, args.idempotencyKey, proposal.id, authorEmail, actorKind, requestHash, suggestions.map((suggestion) => suggestion.id));
702
713
  if (!recorded)
703
714
  throw concurrentCreation;
715
+ await supersedeSuggestions(tx, args, authorEmail, suggestions[0].id);
704
716
  return { proposal, suggestions, comments };
705
717
  })
706
718
  .catch(async (error) => {
@@ -29,6 +29,10 @@ export default defineAction({
29
29
  ? `Deleted organization key ${args.name}`
30
30
  : `Deleted personal key ${args.name}`,
31
31
  },
32
+ // It creates, changes, or revokes a credential or who may use one: every
33
+ // call needs the user's approval, and "always allow" is not offered.
34
+ needsApproval: true,
35
+ allowPersistentApproval: false,
32
36
  run: async (args, ctx) => {
33
37
  const email = ctx?.userEmail?.trim().toLowerCase();
34
38
  if (!email)