@opengeni/api-router 0.22.2 → 0.26.1

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 (124) hide show
  1. package/dist/app.d.ts +2 -2
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth.d.ts +0 -30
  4. package/dist/browser-controller-authority.d.ts +43 -0
  5. package/dist/browser-state-authority.d.ts +27 -0
  6. package/dist/{chunk-HWXJW5C7.js → chunk-JIKNR5YL.js} +27993 -14546
  7. package/dist/chunk-JIKNR5YL.js.map +1 -0
  8. package/dist/codemode.d.ts +23 -0
  9. package/dist/editable-artifact-live-hints.d.ts +11 -0
  10. package/dist/editable-artifact-native-kernel.d.ts +37 -0
  11. package/dist/editable-artifact-office-import.d.ts +22 -0
  12. package/dist/editable-artifact-production.d.ts +29 -0
  13. package/dist/editable-artifact-websocket.d.ts +49 -0
  14. package/dist/editable-artifact-workspace-files.d.ts +23 -0
  15. package/dist/github-browser-flow.d.ts +6 -0
  16. package/dist/http/cors.d.ts +1 -0
  17. package/dist/http/sse.d.ts +2 -0
  18. package/dist/index.d.ts +3 -2
  19. package/dist/index.js +1824 -30
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/api-integrations.d.ts +24 -0
  22. package/dist/integrations/atlassian.d.ts +176 -0
  23. package/dist/integrations/github-skill-source.d.ts +5 -0
  24. package/dist/integrations/google-drive.d.ts +85 -0
  25. package/dist/integrations/oauth-client.d.ts +30 -1
  26. package/dist/integrations/provider-oauth.d.ts +19 -0
  27. package/dist/integrations/slack-bot.d.ts +4 -0
  28. package/dist/integrations/slack-interactions.d.ts +14 -2
  29. package/dist/integrations/social-api.d.ts +2 -1
  30. package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
  31. package/dist/mcp/editable-artifacts.d.ts +13 -0
  32. package/dist/mcp/receipts.d.ts +28 -0
  33. package/dist/mcp/scheduled-task-view.d.ts +518 -0
  34. package/dist/mcp/server.d.ts +14 -3
  35. package/dist/memory-slack-delivery.d.ts +9 -0
  36. package/dist/routes/api-integrations.d.ts +8 -0
  37. package/dist/routes/browser-identities.d.ts +5 -0
  38. package/dist/routes/browser-sessions.d.ts +6 -0
  39. package/dist/routes/company-profile.d.ts +3 -0
  40. package/dist/routes/computer-sessions.d.ts +6 -0
  41. package/dist/routes/editable-artifacts.d.ts +44 -0
  42. package/dist/routes/integration-features.d.ts +3 -0
  43. package/dist/routes/memory-slack-publications.d.ts +6 -0
  44. package/dist/routes/plugins.d.ts +8 -0
  45. package/dist/routes/sessions.d.ts +17 -2
  46. package/dist/routes/skills.d.ts +6 -0
  47. package/dist/routes/video-generation.d.ts +3 -0
  48. package/dist/sandbox/auth-callout.d.ts +2 -0
  49. package/dist/sandbox/channel-a.d.ts +59 -2
  50. package/dist/sandbox/metrics-ingestion.d.ts +6 -1
  51. package/dist/sandbox/viewer.d.ts +4 -2
  52. package/dist/temporal-schedule-cleanup.d.ts +26 -0
  53. package/package.json +19 -14
  54. package/src/app.ts +277 -41
  55. package/src/auth/managed-auth.ts +0 -16
  56. package/src/browser-controller-authority.ts +137 -0
  57. package/src/browser-state-authority.ts +236 -0
  58. package/src/codemode.ts +186 -0
  59. package/src/editable-artifact-live-hints.ts +64 -0
  60. package/src/editable-artifact-native-kernel.ts +659 -0
  61. package/src/editable-artifact-office-import.ts +230 -0
  62. package/src/editable-artifact-production.ts +419 -0
  63. package/src/editable-artifact-websocket.ts +311 -0
  64. package/src/editable-artifact-workspace-files.ts +186 -0
  65. package/src/github-browser-flow.ts +35 -6
  66. package/src/http/auth.ts +2 -0
  67. package/src/http/cors.ts +3 -0
  68. package/src/http/sse.ts +101 -6
  69. package/src/index.ts +147 -23
  70. package/src/integrations/api-integrations.ts +350 -0
  71. package/src/integrations/atlassian.ts +1621 -0
  72. package/src/integrations/github-skill-source.ts +142 -0
  73. package/src/integrations/google-drive.ts +1000 -64
  74. package/src/integrations/oauth-client.ts +159 -89
  75. package/src/integrations/provider-oauth.ts +777 -0
  76. package/src/integrations/slack-bot.ts +31 -2
  77. package/src/integrations/slack-interactions.ts +610 -42
  78. package/src/integrations/social-api.ts +11 -0
  79. package/src/mcp/documents.ts +74 -26
  80. package/src/mcp/editable-artifact-query-schema.ts +236 -0
  81. package/src/mcp/editable-artifacts.ts +448 -0
  82. package/src/mcp/receipts.ts +95 -0
  83. package/src/mcp/scheduled-task-view.ts +642 -0
  84. package/src/mcp/server.ts +1718 -310
  85. package/src/memory-slack-delivery.ts +209 -0
  86. package/src/observability.ts +3 -3
  87. package/src/routes/api-integrations.ts +407 -0
  88. package/src/routes/api-keys.ts +7 -1
  89. package/src/routes/browser-identities.ts +136 -0
  90. package/src/routes/browser-sessions.ts +2543 -0
  91. package/src/routes/codex.ts +7 -4
  92. package/src/routes/company-profile.ts +255 -0
  93. package/src/routes/computer-sessions.ts +1247 -0
  94. package/src/routes/connections.ts +358 -102
  95. package/src/routes/documents.ts +22 -3
  96. package/src/routes/editable-artifacts.ts +1159 -0
  97. package/src/routes/enrollments.ts +54 -12
  98. package/src/routes/environments.ts +60 -11
  99. package/src/routes/files.ts +277 -65
  100. package/src/routes/github.ts +18 -2
  101. package/src/routes/install.ts +38 -2
  102. package/src/routes/integration-features.ts +258 -0
  103. package/src/routes/machines.ts +1 -1
  104. package/src/routes/memory-slack-publications.ts +216 -0
  105. package/src/routes/packs.ts +437 -7
  106. package/src/routes/plugins.ts +751 -0
  107. package/src/routes/rigs.ts +77 -20
  108. package/src/routes/scheduled-tasks.ts +94 -42
  109. package/src/routes/sessions.ts +475 -234
  110. package/src/routes/skills.ts +174 -0
  111. package/src/routes/transcription-recordings.ts +65 -33
  112. package/src/routes/video-generation.ts +132 -0
  113. package/src/routes/workspaces.ts +46 -24
  114. package/src/sandbox/auth-callout.ts +16 -4
  115. package/src/sandbox/channel-a.ts +809 -85
  116. package/src/sandbox/enrollment.ts +13 -3
  117. package/src/sandbox/machines.ts +1 -1
  118. package/src/sandbox/metrics-ingestion.ts +121 -3
  119. package/src/sandbox/rematerialize.ts +35 -47
  120. package/src/sandbox/viewer.ts +58 -29
  121. package/src/temporal-schedule-cleanup.ts +135 -0
  122. package/dist/chunk-HWXJW5C7.js.map +0 -1
  123. package/dist/mcp/toolspace.d.ts +0 -62
  124. package/src/mcp/toolspace.ts +0 -1186
@@ -7,7 +7,6 @@ import {
7
7
  import {
8
8
  beginRigChangeVerificationAttempt,
9
9
  listRigs,
10
- RigChangeAlreadyVerifyingError,
11
10
  RigChangeTransitionError,
12
11
  } from "@opengeni/db";
13
12
  import type { Hono } from "hono";
@@ -32,38 +31,87 @@ import { boundedLimit } from "../http/common";
32
31
  export function registerRigRoutes(app: Hono, deps: ApiRouteDeps): void {
33
32
  const { db, workflowClient } = deps;
34
33
 
35
- async function startChangeVerification(workspaceId: string, changeId: string): Promise<unknown> {
34
+ async function startChangeVerification(
35
+ workspaceId: string,
36
+ changeId: string,
37
+ ): Promise<{
38
+ change: Awaited<ReturnType<typeof beginRigChangeVerificationAttempt>>;
39
+ started: boolean;
40
+ }> {
36
41
  const startedAt = new Date().toISOString();
37
42
  let change;
38
43
  try {
39
- change = await beginRigChangeVerificationAttempt(db, workspaceId, changeId, { startedAt });
44
+ change = await beginRigChangeVerificationAttempt(db, workspaceId, changeId, {
45
+ startedAt,
46
+ // Reuse the existing attempt after an ambiguous Temporal start. Its
47
+ // deterministic workflow id turns this into an idempotent recovery.
48
+ allowAlreadyVerifying: true,
49
+ });
40
50
  } catch (error) {
41
- if (
42
- error instanceof RigChangeAlreadyVerifyingError ||
43
- error instanceof RigChangeTransitionError
44
- ) {
51
+ if (error instanceof RigChangeTransitionError) {
45
52
  throw new HTTPException(409, { message: error.message });
46
53
  }
47
54
  throw error;
48
55
  }
49
56
  const attempt =
50
57
  typeof change.verification?.attempt === "number" ? change.verification.attempt : Date.now();
51
- await workflowClient.startRigVerification({
52
- workspaceId,
53
- changeId,
54
- workflowId: `rig-verification-change-${changeId}-attempt-${attempt}`,
55
- });
56
- return change;
58
+ try {
59
+ await workflowClient.startRigVerification({
60
+ workspaceId,
61
+ changeId,
62
+ workflowId: `rig-verification-change-${changeId}-attempt-${attempt}`,
63
+ });
64
+ return { change, started: true };
65
+ } catch (error) {
66
+ // The verifying transition already committed. Keep its deterministic
67
+ // attempt identity retryable instead of reporting an opaque 5xx after a
68
+ // durable mutation or inventing a second attempt after an ambiguous start.
69
+ deps.observability?.warn("rig change verification start failed", {
70
+ workspaceId,
71
+ changeId,
72
+ attempt,
73
+ error: error instanceof Error ? error.message : String(error),
74
+ });
75
+ return { change, started: false };
76
+ }
57
77
  }
58
78
 
59
- async function startVersionVerification(workspaceId: string, versionId: string): Promise<void> {
79
+ async function startVersionVerification(
80
+ workspaceId: string,
81
+ versionId: string,
82
+ workflowId = `rig-verification-version-${versionId}-${crypto.randomUUID()}`,
83
+ ): Promise<void> {
60
84
  await workflowClient.startRigVerification({
61
85
  workspaceId,
62
86
  versionId,
63
- workflowId: `rig-verification-version-${versionId}-${crypto.randomUUID()}`,
87
+ workflowId,
64
88
  });
65
89
  }
66
90
 
91
+ async function tryStartInitialVersionVerification(
92
+ workspaceId: string,
93
+ versionId: string,
94
+ ): Promise<boolean> {
95
+ try {
96
+ await startVersionVerification(
97
+ workspaceId,
98
+ versionId,
99
+ `rig-verification-version-${versionId}-initial`,
100
+ );
101
+ return true;
102
+ } catch (error) {
103
+ // Creation already committed. The rig remains fully usable through its
104
+ // runtime setup fallback and can be re-verified explicitly; never turn a
105
+ // successful create into an unretryable 5xx because Temporal was down.
106
+ deps.observability?.warn("initial rig provider-image verification start failed", {
107
+ workspaceId,
108
+ versionId,
109
+ error: error instanceof Error ? error.message : String(error),
110
+ });
111
+ return false;
112
+ }
113
+ }
114
+
67
115
  app.get("/v1/workspaces/:workspaceId/rigs", async (c) => {
68
116
  const workspaceId = c.req.param("workspaceId");
69
117
  await requireAccessGrant(c, deps, workspaceId, "rigs:use");
@@ -75,6 +123,10 @@ export function registerRigRoutes(app: Hono, deps: ApiRouteDeps): void {
75
123
  const grant = await requireAccessGrant(c, deps, workspaceId, "rigs:manage");
76
124
  const payload = CreateRigRequest.parse(await c.req.json());
77
125
  const rig = await createRigForApi({ db }, grant, payload);
126
+ if (rig.activeVersion) {
127
+ const started = await tryStartInitialVersionVerification(workspaceId, rig.activeVersion.id);
128
+ if (!started) c.header("OpenGeni-Rig-Verification", "deferred");
129
+ }
78
130
  return c.json(rig, 201);
79
131
  });
80
132
 
@@ -112,7 +164,10 @@ export function registerRigRoutes(app: Hono, deps: ApiRouteDeps): void {
112
164
  const grant = await requireAccessGrant(c, deps, workspaceId, "rigs:manage");
113
165
  const rig = await requireRigForApi(db, workspaceId, c.req.param("rigId"));
114
166
  const payload = RigDefinitionEditPayload.parse(await c.req.json());
115
- return c.json(await createRigVersionForApi({ db }, grant, rig, payload), 201);
167
+ const version = await createRigVersionForApi({ db }, grant, rig, payload);
168
+ const started = await tryStartInitialVersionVerification(workspaceId, version.id);
169
+ if (!started) c.header("OpenGeni-Rig-Verification", "deferred");
170
+ return c.json(version, 201);
116
171
  });
117
172
 
118
173
  // Rollback / promote-activate: flips which existing version is active.
@@ -142,8 +197,9 @@ export function registerRigRoutes(app: Hono, deps: ApiRouteDeps): void {
142
197
  const rig = await requireRigForApi(db, workspaceId, c.req.param("rigId"));
143
198
  const request = ProposeRigChangeRequest.parse(await c.req.json());
144
199
  const change = await proposeRigChangeForApi({ db }, grant, rig, request);
145
- const verifying = await startChangeVerification(workspaceId, change.id);
146
- return c.json(verifying, 201);
200
+ const verification = await startChangeVerification(workspaceId, change.id);
201
+ if (!verification.started) c.header("OpenGeni-Rig-Verification", "deferred");
202
+ return c.json(verification.change, 201);
147
203
  });
148
204
 
149
205
  app.get("/v1/workspaces/:workspaceId/rigs/:rigId/changes/:changeId", async (c) => {
@@ -163,8 +219,9 @@ export function registerRigRoutes(app: Hono, deps: ApiRouteDeps): void {
163
219
  c.req.param("rigId"),
164
220
  c.req.param("changeId"),
165
221
  );
166
- const verifying = await startChangeVerification(workspaceId, change.id);
167
- return c.json(verifying, 202);
222
+ const verification = await startChangeVerification(workspaceId, change.id);
223
+ if (!verification.started) c.header("OpenGeni-Rig-Verification", "deferred");
224
+ return c.json(verification.change, 202);
168
225
  });
169
226
 
170
227
  app.post("/v1/workspaces/:workspaceId/rigs/:rigId/changes/:changeId/promote", async (c) => {
@@ -10,6 +10,7 @@ import {
10
10
  updateScheduledTask,
11
11
  } from "@opengeni/db";
12
12
  import type { Hono } from "hono";
13
+ import { HTTPException } from "hono/http-exception";
13
14
  import { requireAccessGrant } from "@opengeni/core";
14
15
  import { recordWorkspaceUsage, requireLimit } from "@opengeni/core";
15
16
  import type { ApiRouteDeps } from "@opengeni/core";
@@ -19,13 +20,18 @@ import {
19
20
  manualScheduledTaskTriggerUsageKey,
20
21
  manualScheduledTaskTriggerWorkflowId,
21
22
  scheduledTaskToolsProvided,
23
+ scheduledTaskForGrant,
24
+ scheduledTaskRunForGrant,
22
25
  scheduledTaskTriggerToken,
23
26
  requireScheduledTaskForApi,
24
27
  syncCreatedScheduledTask,
25
28
  syncUpdatedScheduledTask,
29
+ validateScheduledTaskTarget,
26
30
  validatedScheduledTaskUpdate,
27
31
  } from "@opengeni/core";
28
32
  import { boundedLimit } from "../http/common";
33
+ import { revokeKnowledgeSourceScheduleAuthorization } from "../integrations/google-drive";
34
+ import { revokeAtlassianScheduleAuthorization } from "../integrations/atlassian";
29
35
 
30
36
  export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void {
31
37
  const { settings, db, workflowClient, objectStorage } = deps;
@@ -34,7 +40,13 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
34
40
  const workspaceId = c.req.param("workspaceId");
35
41
  const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
36
42
  const rawPayload = await c.req.json();
37
- const payload = CreateScheduledTaskRequest.parse(rawPayload);
43
+ const parsedPayload = CreateScheduledTaskRequest.safeParse(rawPayload);
44
+ if (!parsedPayload.success) {
45
+ throw new HTTPException(400, {
46
+ message: "invalid scheduled task create request",
47
+ });
48
+ }
49
+ const payload = parsedPayload.data;
38
50
  await requireLimit(deps, {
39
51
  accountId: grant.accountId,
40
52
  workspaceId,
@@ -48,21 +60,25 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
48
60
  grant,
49
61
  payload,
50
62
  toolsProvided: scheduledTaskToolsProvided(rawPayload),
63
+ sessionAuthorization: deps.sessionAuthorization,
64
+ authorizationSurface: "http",
51
65
  });
52
66
  await syncCreatedScheduledTask({ db, workflowClient, task });
53
- return c.json(task, 201);
67
+ return c.json(scheduledTaskForGrant(task, grant), 201);
54
68
  });
55
69
 
56
70
  app.get("/v1/workspaces/:workspaceId/scheduled-tasks", async (c) => {
57
71
  const workspaceId = c.req.param("workspaceId");
58
- await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:run");
59
- return c.json(await listScheduledTasks(db, workspaceId, boundedLimit(c.req.query("limit"))));
72
+ const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:run");
73
+ const tasks = await listScheduledTasks(db, workspaceId, boundedLimit(c.req.query("limit")));
74
+ return c.json(tasks.map((task) => scheduledTaskForGrant(task, grant)));
60
75
  });
61
76
 
62
77
  app.get("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId", async (c) => {
63
78
  const workspaceId = c.req.param("workspaceId");
64
- await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:run");
65
- return c.json(await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId")));
79
+ const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:run");
80
+ const task = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
81
+ return c.json(scheduledTaskForGrant(task, grant));
66
82
  });
67
83
 
68
84
  app.patch("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId", async (c) => {
@@ -72,7 +88,13 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
72
88
  const existing = await requireScheduledTaskForApi(db, workspaceId, taskId);
73
89
  const previous = await captureScheduledTaskRestoreState(db, existing);
74
90
  const rawPayload = await c.req.json();
75
- const payload = UpdateScheduledTaskRequest.parse(rawPayload);
91
+ const parsedPayload = UpdateScheduledTaskRequest.safeParse(rawPayload);
92
+ if (!parsedPayload.success) {
93
+ throw new HTTPException(400, {
94
+ message: "invalid scheduled task update request",
95
+ });
96
+ }
97
+ const payload = parsedPayload.data;
76
98
  const update = await validatedScheduledTaskUpdate({
77
99
  settings,
78
100
  db,
@@ -81,30 +103,36 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
81
103
  existing,
82
104
  payload,
83
105
  toolsProvided: scheduledTaskToolsProvided(rawPayload),
106
+ sessionAuthorization: deps.sessionAuthorization,
107
+ authorizationSurface: "http",
84
108
  });
85
109
  const task = await updateScheduledTask(db, workspaceId, taskId, update);
86
110
  await syncUpdatedScheduledTask({ db, workflowClient, previous, task });
87
- return c.json(task);
111
+ return c.json(scheduledTaskForGrant(task, grant));
88
112
  });
89
113
 
90
114
  app.post("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/pause", async (c) => {
91
115
  const workspaceId = c.req.param("workspaceId");
92
- await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
116
+ const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
93
117
  const existing = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
94
118
  const previous = await captureScheduledTaskRestoreState(db, existing);
95
- const task = await updateScheduledTask(db, workspaceId, existing.id, { status: "paused" });
119
+ const task = await updateScheduledTask(db, workspaceId, existing.id, {
120
+ status: "paused",
121
+ });
96
122
  await syncUpdatedScheduledTask({ db, workflowClient, previous, task });
97
- return c.json(task);
123
+ return c.json(scheduledTaskForGrant(task, grant));
98
124
  });
99
125
 
100
126
  app.post("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/resume", async (c) => {
101
127
  const workspaceId = c.req.param("workspaceId");
102
- await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
128
+ const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
103
129
  const existing = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
104
130
  const previous = await captureScheduledTaskRestoreState(db, existing);
105
- const task = await updateScheduledTask(db, workspaceId, existing.id, { status: "active" });
131
+ const task = await updateScheduledTask(db, workspaceId, existing.id, {
132
+ status: "active",
133
+ });
106
134
  await syncUpdatedScheduledTask({ db, workflowClient, previous, task });
107
- return c.json(task);
135
+ return c.json(scheduledTaskForGrant(task, grant));
108
136
  });
109
137
 
110
138
  app.post("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/trigger", async (c) => {
@@ -113,23 +141,36 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
113
141
  // Load the task before the gate so a codex-model scheduled task can be
114
142
  // recognised as codex-billed and skip the credit/cost gates at the edge.
115
143
  const task = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
116
- await requireLimit(deps, {
117
- accountId: grant.accountId,
118
- workspaceId,
119
- action: "agent_run:create",
120
- quantity: 1,
121
- model: task.agentConfig.model ?? deps.settings.openaiModel,
122
- });
144
+ if (task.action.kind === "agent_turn") {
145
+ await validateScheduledTaskTarget({
146
+ db,
147
+ sessionAuthorization: deps.sessionAuthorization,
148
+ authorizationSurface: "http",
149
+ grant,
150
+ targetSessionId: task.targetSessionId,
151
+ runMode: task.runMode,
152
+ variableSetId: task.variableSetId,
153
+ rigId: task.rigId,
154
+ agentConfig: task.agentConfig,
155
+ missingTargetStatus: 404,
156
+ });
157
+ await requireLimit(deps, {
158
+ accountId: grant.accountId,
159
+ workspaceId,
160
+ action: "agent_run:create",
161
+ quantity: 1,
162
+ model: task.agentConfig.model ?? deps.settings.openaiModel,
163
+ });
164
+ }
123
165
  // Body is optional (a bare POST is still a valid trigger); only a present,
124
166
  // non-empty body must parse against the contract.
125
167
  const body = await c.req.json().catch(() => ({}));
126
168
  const { triggerId } = TriggerScheduledTaskRequest.parse(body ?? {});
127
169
  const triggerToken = scheduledTaskTriggerToken(triggerId);
128
- const agentRunUsageIdempotencyKey = manualScheduledTaskTriggerUsageKey(
129
- workspaceId,
130
- task.id,
131
- triggerToken,
132
- );
170
+ const agentRunUsageIdempotencyKey =
171
+ task.action.kind === "agent_turn"
172
+ ? manualScheduledTaskTriggerUsageKey(workspaceId, task.id, triggerToken)
173
+ : `knowledge-source-sync:manual:${workspaceId}:${task.id}:${triggerToken}`;
133
174
  const triggerWorkflowId = manualScheduledTaskTriggerWorkflowId(task.id, triggerToken);
134
175
  await workflowClient.triggerScheduledTask({
135
176
  task,
@@ -137,24 +178,31 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
137
178
  triggerWorkflowId,
138
179
  initiator: { kind: "subject", subjectId: grant.subjectId },
139
180
  });
140
- await recordWorkspaceUsage(deps, {
141
- accountId: grant.accountId,
142
- workspaceId,
143
- subjectId: grant.subjectId,
144
- eventType: "agent_run.created",
145
- quantity: 1,
146
- unit: "run",
147
- sourceResourceType: "scheduled_task",
148
- sourceResourceId: task.id,
149
- idempotencyKey: agentRunUsageIdempotencyKey,
150
- });
151
- return c.json(task, 202);
181
+ if (task.action.kind === "agent_turn") {
182
+ await recordWorkspaceUsage(deps, {
183
+ accountId: grant.accountId,
184
+ workspaceId,
185
+ subjectId: grant.subjectId,
186
+ eventType: "agent_run.created",
187
+ quantity: 1,
188
+ unit: "run",
189
+ sourceResourceType: "scheduled_task",
190
+ sourceResourceId: task.id,
191
+ idempotencyKey: agentRunUsageIdempotencyKey,
192
+ });
193
+ }
194
+ return c.json(scheduledTaskForGrant(task, grant), 202);
152
195
  });
153
196
 
154
197
  app.delete("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId", async (c) => {
155
198
  const workspaceId = c.req.param("workspaceId");
156
- await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
199
+ const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
157
200
  const task = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
201
+ if (task.metadata.connectorKind === "atlassian") {
202
+ await revokeAtlassianScheduleAuthorization(deps, { task, subjectId: grant.subjectId });
203
+ } else {
204
+ await revokeKnowledgeSourceScheduleAuthorization(deps, { task, subjectId: grant.subjectId });
205
+ }
158
206
  await workflowClient.deleteScheduledTaskSchedule({
159
207
  temporalScheduleId: task.temporalScheduleId,
160
208
  });
@@ -164,10 +212,14 @@ export function registerScheduledTaskRoutes(app: Hono, deps: ApiRouteDeps): void
164
212
 
165
213
  app.get("/v1/workspaces/:workspaceId/scheduled-tasks/:taskId/runs", async (c) => {
166
214
  const workspaceId = c.req.param("workspaceId");
167
- await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:run");
215
+ const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:run");
168
216
  const task = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
169
- return c.json(
170
- await listScheduledTaskRuns(db, workspaceId, task.id, boundedLimit(c.req.query("limit"))),
217
+ const taskRuns = await listScheduledTaskRuns(
218
+ db,
219
+ workspaceId,
220
+ task.id,
221
+ boundedLimit(c.req.query("limit")),
171
222
  );
223
+ return c.json(taskRuns.map((run) => scheduledTaskRunForGrant(run, grant)));
172
224
  });
173
225
  }