@opengeni/api-router 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/{chunk-3HIA43CC.js → chunk-DO2G3JSB.js} +5184 -2195
- package/dist/chunk-DO2G3JSB.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +297 -54
- package/dist/index.js.map +1 -1
- package/package.json +20 -20
- package/src/app.ts +415 -147
- package/src/auth/managed-auth.ts +32 -16
- package/src/http/auth.ts +8 -1
- package/src/http/common.ts +6 -2
- package/src/http/sse.ts +27 -6
- package/src/index.ts +196 -74
- package/src/integrations/oauth-client.ts +403 -120
- package/src/integrations/provider-domain.ts +4 -1
- package/src/mcp/documents.ts +173 -94
- package/src/mcp/server.ts +1517 -692
- package/src/mcp/session-view.ts +8 -2
- package/src/mcp/toolspace.ts +175 -84
- package/src/observability.ts +7 -1
- package/src/routes/api-keys.ts +39 -23
- package/src/routes/billing.ts +180 -65
- package/src/routes/capabilities.ts +17 -8
- package/src/routes/catalog-assets.ts +5 -2
- package/src/routes/codex.ts +244 -63
- package/src/routes/connections.ts +71 -33
- package/src/routes/documents.ts +242 -92
- package/src/routes/enrollments.ts +100 -70
- package/src/routes/environments.ts +205 -136
- package/src/routes/files.ts +164 -39
- package/src/routes/github.ts +123 -50
- package/src/routes/install.ts +9 -2
- package/src/routes/machines.ts +9 -8
- package/src/routes/packs.ts +141 -89
- package/src/routes/rigs.ts +189 -0
- package/src/routes/scheduled-tasks.ts +51 -9
- package/src/routes/sessions.ts +839 -329
- package/src/routes/social.ts +50 -38
- package/src/routes/workspace-capture.ts +238 -0
- package/src/routes/workspaces.ts +159 -13
- package/src/sandbox/access.ts +11 -3
- package/src/sandbox/auth-callout.ts +5 -1
- package/src/sandbox/channel-a.ts +104 -27
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +68 -59
- package/src/sandbox/metrics-ingestion.ts +238 -17
- package/src/sandbox/viewer.ts +172 -46
- package/dist/chunk-3HIA43CC.js.map +0 -1
package/src/auth/managed-auth.ts
CHANGED
|
@@ -27,12 +27,14 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
27
27
|
trustedOrigins: betterAuthTrustedOrigins(settings),
|
|
28
28
|
advanced: {
|
|
29
29
|
useSecureCookies: settings.publicBaseUrl?.startsWith("https://") ?? false,
|
|
30
|
-
...(settings.betterAuthCookieDomain
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
...(settings.betterAuthCookieDomain
|
|
31
|
+
? {
|
|
32
|
+
crossSubDomainCookies: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
domain: settings.betterAuthCookieDomain,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
: {}),
|
|
36
38
|
database: {
|
|
37
39
|
generateId: () => crypto.randomUUID(),
|
|
38
40
|
},
|
|
@@ -149,7 +151,11 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
149
151
|
}) as ManagedAuth;
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
export async function managedSessionAccessContext(
|
|
154
|
+
export async function managedSessionAccessContext(
|
|
155
|
+
auth: ManagedAuth,
|
|
156
|
+
db: Database,
|
|
157
|
+
headers: Headers,
|
|
158
|
+
) {
|
|
153
159
|
const session = await auth.api.getSession({ headers });
|
|
154
160
|
if (!session?.user) {
|
|
155
161
|
return null;
|
|
@@ -184,15 +190,20 @@ function betterAuthTrustedOrigins(settings: Settings): string[] {
|
|
|
184
190
|
return [...origins];
|
|
185
191
|
}
|
|
186
192
|
|
|
187
|
-
async function sendEmail(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
async function sendEmail(
|
|
194
|
+
settings: Settings,
|
|
195
|
+
input: {
|
|
196
|
+
to: string;
|
|
197
|
+
subject: string;
|
|
198
|
+
text: string;
|
|
199
|
+
html: string;
|
|
200
|
+
},
|
|
201
|
+
): Promise<void> {
|
|
193
202
|
if (!settings.resendApiKey) {
|
|
194
203
|
if (settings.environment === "local" || settings.environment === "test") {
|
|
195
|
-
console.warn(
|
|
204
|
+
console.warn(
|
|
205
|
+
`[opengeni] Skipping email to ${input.to}: OPENGENI_RESEND_API_KEY is not configured`,
|
|
206
|
+
);
|
|
196
207
|
return;
|
|
197
208
|
}
|
|
198
209
|
throw new Error("OPENGENI_RESEND_API_KEY is required to send managed auth email");
|
|
@@ -212,7 +223,9 @@ async function sendEmail(settings: Settings, input: {
|
|
|
212
223
|
|
|
213
224
|
async function verificationUrl(settings: Settings, email: string): Promise<string> {
|
|
214
225
|
if (!settings.betterAuthSecret) {
|
|
215
|
-
throw new Error(
|
|
226
|
+
throw new Error(
|
|
227
|
+
"OPENGENI_BETTER_AUTH_SECRET is required to send managed auth verification email",
|
|
228
|
+
);
|
|
216
229
|
}
|
|
217
230
|
if (!settings.publicBaseUrl) {
|
|
218
231
|
throw new Error("OPENGENI_PUBLIC_BASE_URL is required to send managed auth verification email");
|
|
@@ -225,7 +238,10 @@ async function verificationUrl(settings: Settings, email: string): Promise<strin
|
|
|
225
238
|
}
|
|
226
239
|
|
|
227
240
|
function splitCsv(raw: string): string[] {
|
|
228
|
-
return raw
|
|
241
|
+
return raw
|
|
242
|
+
.split(",")
|
|
243
|
+
.map((value) => value.trim())
|
|
244
|
+
.filter(Boolean);
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
function escapeHtml(value: string): string {
|
package/src/http/auth.ts
CHANGED
|
@@ -84,7 +84,14 @@ function isAuthorized(c: Context, expected: string | undefined): boolean {
|
|
|
84
84
|
return false;
|
|
85
85
|
}
|
|
86
86
|
const explicit = c.req.header("x-opengeni-access-key");
|
|
87
|
-
|
|
87
|
+
if (constantTimeEqual(explicit, expected)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const authorization = c.req.header("authorization");
|
|
91
|
+
const bearer = authorization?.startsWith("Bearer ")
|
|
92
|
+
? authorization.slice("Bearer ".length)
|
|
93
|
+
: undefined;
|
|
94
|
+
return constantTimeEqual(bearer, expected);
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
function constantTimeEqual(actual: string | undefined, expected: string): boolean {
|
package/src/http/common.ts
CHANGED
|
@@ -9,8 +9,12 @@ export function boundedLimit(raw: string | undefined): number {
|
|
|
9
9
|
return Math.min(500, Math.max(1, Math.floor(limit)));
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export async function assertSessionExists(
|
|
13
|
-
|
|
12
|
+
export async function assertSessionExists(
|
|
13
|
+
db: Database,
|
|
14
|
+
workspaceId: string,
|
|
15
|
+
sessionId: string,
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
if (!(await getSession(db, workspaceId, sessionId))) {
|
|
14
18
|
throw new HTTPException(404, { message: "session not found" });
|
|
15
19
|
}
|
|
16
20
|
}
|
package/src/http/sse.ts
CHANGED
|
@@ -2,7 +2,14 @@ import type { SessionEvent } from "@opengeni/contracts";
|
|
|
2
2
|
import { listSessionEvents, type Database } from "@opengeni/db";
|
|
3
3
|
import { formatSse, type EventBus } from "@opengeni/events";
|
|
4
4
|
|
|
5
|
-
export async function sseSessionStream(
|
|
5
|
+
export async function sseSessionStream(
|
|
6
|
+
db: Database,
|
|
7
|
+
bus: EventBus,
|
|
8
|
+
workspaceId: string,
|
|
9
|
+
sessionId: string,
|
|
10
|
+
after: number,
|
|
11
|
+
signal: AbortSignal,
|
|
12
|
+
): Promise<Response> {
|
|
6
13
|
const encoder = new TextEncoder();
|
|
7
14
|
let controller: ReadableStreamDefaultController<Uint8Array>;
|
|
8
15
|
let lastSent = after;
|
|
@@ -18,7 +25,13 @@ export async function sseSessionStream(db: Database, bus: EventBus, workspaceId:
|
|
|
18
25
|
return;
|
|
19
26
|
}
|
|
20
27
|
if (event.sequence > lastSent + 1) {
|
|
21
|
-
const missing = await listSessionEvents(
|
|
28
|
+
const missing = await listSessionEvents(
|
|
29
|
+
db,
|
|
30
|
+
workspaceId,
|
|
31
|
+
sessionId,
|
|
32
|
+
lastSent,
|
|
33
|
+
event.sequence - lastSent - 1,
|
|
34
|
+
);
|
|
22
35
|
for (const missed of missing) {
|
|
23
36
|
if (missed.sequence > lastSent) {
|
|
24
37
|
controller.enqueue(encoder.encode(formatSse(missed)));
|
|
@@ -40,7 +53,11 @@ export async function sseSessionStream(db: Database, bus: EventBus, workspaceId:
|
|
|
40
53
|
}
|
|
41
54
|
});
|
|
42
55
|
|
|
43
|
-
await replaySessionEvents(
|
|
56
|
+
await replaySessionEvents(
|
|
57
|
+
(cursor, limit) => listSessionEvents(db, workspaceId, sessionId, cursor, limit),
|
|
58
|
+
send,
|
|
59
|
+
after,
|
|
60
|
+
);
|
|
44
61
|
replaying = false;
|
|
45
62
|
for (const event of buffered.sort((a, b) => a.sequence - b.sequence)) {
|
|
46
63
|
await send(event);
|
|
@@ -53,9 +70,13 @@ export async function sseSessionStream(db: Database, bus: EventBus, workspaceId:
|
|
|
53
70
|
},
|
|
54
71
|
});
|
|
55
72
|
|
|
56
|
-
signal.addEventListener(
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
signal.addEventListener(
|
|
74
|
+
"abort",
|
|
75
|
+
() => {
|
|
76
|
+
unsubscribe?.();
|
|
77
|
+
},
|
|
78
|
+
{ once: true },
|
|
79
|
+
);
|
|
59
80
|
|
|
60
81
|
return new Response(stream, {
|
|
61
82
|
headers: {
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
dbSearchPath,
|
|
3
|
+
getSettings,
|
|
4
|
+
resolveNatsCalloutConfig,
|
|
5
|
+
resolveNatsControlPlaneAuth,
|
|
6
|
+
retryStartupDependency,
|
|
7
|
+
startupRetryOptions,
|
|
8
|
+
} from "@opengeni/config";
|
|
9
|
+
import type {
|
|
10
|
+
ScheduledTask,
|
|
11
|
+
ScheduledTaskOverlapPolicy,
|
|
12
|
+
ScheduledTaskScheduleSpec,
|
|
13
|
+
} from "@opengeni/contracts";
|
|
14
|
+
import { createDb, markSessionWorkflowWakeDelivered, type Database } from "@opengeni/db";
|
|
4
15
|
import { createNatsEventBus, type ResponderConnection } from "@opengeni/events";
|
|
5
16
|
import { createObservability, logStartupDependencyRetry } from "@opengeni/observability";
|
|
6
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Connection,
|
|
19
|
+
Client as TemporalClient,
|
|
20
|
+
ScheduleNotFoundError,
|
|
21
|
+
ScheduleOverlapPolicy,
|
|
22
|
+
WorkflowExecutionAlreadyStartedError,
|
|
23
|
+
} from "@temporalio/client";
|
|
7
24
|
import type { ScheduleOptions, ScheduleSpec, ScheduleUpdateOptions } from "@temporalio/client";
|
|
8
25
|
import { createApp, type DocumentIndexClient, type SessionWorkflowClient } from "./app";
|
|
9
26
|
import { observabilityEventLogger } from "./observability";
|
|
@@ -35,7 +52,10 @@ const TEMPORAL_MONTHS = [
|
|
|
35
52
|
"DECEMBER",
|
|
36
53
|
] as const;
|
|
37
54
|
|
|
38
|
-
export async function createTemporalWorkflowClient(
|
|
55
|
+
export async function createTemporalWorkflowClient(
|
|
56
|
+
settings: ReturnType<typeof getSettings>,
|
|
57
|
+
db: Database,
|
|
58
|
+
): Promise<{
|
|
39
59
|
client: SessionWorkflowClient;
|
|
40
60
|
documentIndexer: DocumentIndexClient;
|
|
41
61
|
close: () => Promise<void>;
|
|
@@ -49,7 +69,13 @@ export async function createTemporalWorkflowClient(settings: ReturnType<typeof g
|
|
|
49
69
|
signalUserMessage: async ({ eventId, workflowId }) => {
|
|
50
70
|
await temporal.workflow.getHandle(workflowId).signal("userMessage", eventId);
|
|
51
71
|
},
|
|
52
|
-
wakeSessionWorkflow: async ({
|
|
72
|
+
wakeSessionWorkflow: async ({
|
|
73
|
+
accountId,
|
|
74
|
+
workspaceId,
|
|
75
|
+
sessionId,
|
|
76
|
+
workflowId,
|
|
77
|
+
wakeRevision,
|
|
78
|
+
}) => {
|
|
53
79
|
await temporal.workflow.signalWithStart("sessionWorkflow", {
|
|
54
80
|
taskQueue: settings.temporalTaskQueue,
|
|
55
81
|
workflowId,
|
|
@@ -57,26 +83,87 @@ export async function createTemporalWorkflowClient(settings: ReturnType<typeof g
|
|
|
57
83
|
args: [{ accountId, workspaceId, sessionId }],
|
|
58
84
|
signal: "queueChanged",
|
|
59
85
|
});
|
|
86
|
+
await markSessionWorkflowWakeDelivered(db, {
|
|
87
|
+
accountId,
|
|
88
|
+
workspaceId,
|
|
89
|
+
sessionId,
|
|
90
|
+
temporalWorkflowId: workflowId,
|
|
91
|
+
wakeRevision,
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
signalCodexCapacity: async ({
|
|
95
|
+
accountId,
|
|
96
|
+
workspaceId,
|
|
97
|
+
sessionId,
|
|
98
|
+
workflowId,
|
|
99
|
+
wakeRevision,
|
|
100
|
+
workflowWakeRevision,
|
|
101
|
+
}) => {
|
|
102
|
+
await temporal.workflow.signalWithStart("sessionWorkflow", {
|
|
103
|
+
taskQueue: settings.temporalTaskQueue,
|
|
104
|
+
workflowId,
|
|
105
|
+
workflowIdReusePolicy: "ALLOW_DUPLICATE",
|
|
106
|
+
args: [{ accountId, workspaceId, sessionId }],
|
|
107
|
+
signal: "codexCapacityChanged",
|
|
108
|
+
signalArgs: [wakeRevision],
|
|
109
|
+
});
|
|
110
|
+
await markSessionWorkflowWakeDelivered(db, {
|
|
111
|
+
accountId,
|
|
112
|
+
workspaceId,
|
|
113
|
+
sessionId,
|
|
114
|
+
temporalWorkflowId: workflowId,
|
|
115
|
+
wakeRevision: workflowWakeRevision,
|
|
116
|
+
});
|
|
60
117
|
},
|
|
61
|
-
signalApprovalDecision: async ({
|
|
62
|
-
|
|
118
|
+
signalApprovalDecision: async ({
|
|
119
|
+
accountId,
|
|
120
|
+
workspaceId,
|
|
121
|
+
sessionId,
|
|
122
|
+
eventId,
|
|
123
|
+
workflowId,
|
|
124
|
+
workflowWakeRevision,
|
|
125
|
+
}) => {
|
|
126
|
+
await temporal.workflow.signalWithStart("sessionWorkflow", {
|
|
127
|
+
taskQueue: settings.temporalTaskQueue,
|
|
128
|
+
workflowId,
|
|
129
|
+
workflowIdReusePolicy: "ALLOW_DUPLICATE",
|
|
130
|
+
args: [{ accountId, workspaceId, sessionId }],
|
|
131
|
+
signal: "approvalDecision",
|
|
132
|
+
signalArgs: [eventId],
|
|
133
|
+
});
|
|
134
|
+
await markSessionWorkflowWakeDelivered(db, {
|
|
135
|
+
accountId,
|
|
136
|
+
workspaceId,
|
|
137
|
+
sessionId,
|
|
138
|
+
temporalWorkflowId: workflowId,
|
|
139
|
+
wakeRevision: workflowWakeRevision,
|
|
140
|
+
});
|
|
63
141
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
142
|
+
signalSessionControl: async ({
|
|
143
|
+
accountId,
|
|
144
|
+
workspaceId,
|
|
145
|
+
sessionId,
|
|
146
|
+
eventId,
|
|
147
|
+
workflowId,
|
|
148
|
+
workflowWakeRevision,
|
|
149
|
+
}) => {
|
|
150
|
+
// Start-or-signal: a control sent after the prior workflow returned idle
|
|
151
|
+
// starts a fresh run with the durable control already buffered.
|
|
72
152
|
await temporal.workflow.signalWithStart("sessionWorkflow", {
|
|
73
153
|
taskQueue: settings.temporalTaskQueue,
|
|
74
154
|
workflowId,
|
|
75
155
|
workflowIdReusePolicy: "ALLOW_DUPLICATE",
|
|
76
156
|
args: [{ accountId, workspaceId, sessionId }],
|
|
77
|
-
signal: "
|
|
157
|
+
signal: "sessionControl",
|
|
78
158
|
signalArgs: [eventId],
|
|
79
159
|
});
|
|
160
|
+
await markSessionWorkflowWakeDelivered(db, {
|
|
161
|
+
accountId,
|
|
162
|
+
workspaceId,
|
|
163
|
+
sessionId,
|
|
164
|
+
temporalWorkflowId: workflowId,
|
|
165
|
+
wakeRevision: workflowWakeRevision,
|
|
166
|
+
});
|
|
80
167
|
},
|
|
81
168
|
syncScheduledTask: async ({ task }) => {
|
|
82
169
|
const schedule = temporal.schedule.getHandle(task.temporalScheduleId);
|
|
@@ -91,36 +178,60 @@ export async function createTemporalWorkflowClient(settings: ReturnType<typeof g
|
|
|
91
178
|
}
|
|
92
179
|
},
|
|
93
180
|
deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {
|
|
94
|
-
await temporal.schedule
|
|
181
|
+
await temporal.schedule
|
|
182
|
+
.getHandle(temporalScheduleId)
|
|
183
|
+
.delete()
|
|
184
|
+
.catch(() => undefined);
|
|
95
185
|
},
|
|
96
186
|
triggerScheduledTask: async ({ task, agentRunUsageIdempotencyKey, triggerWorkflowId }) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
187
|
+
// Deterministic workflowId (derived from the trigger token by the
|
|
188
|
+
// caller) + REJECT_DUPLICATE makes a retried manual trigger idempotent:
|
|
189
|
+
// the second start collides on the id and is rejected instead of
|
|
190
|
+
// spawning a second run. The shared idempotency key dedupes the charge.
|
|
191
|
+
const workflowId =
|
|
192
|
+
triggerWorkflowId ?? `scheduled-task-${task.id}-manual-${crypto.randomUUID()}`;
|
|
193
|
+
try {
|
|
194
|
+
await temporal.workflow.start("scheduledTaskFireWorkflow", {
|
|
195
|
+
taskQueue: settings.temporalTaskQueue,
|
|
196
|
+
workflowId,
|
|
197
|
+
workflowIdReusePolicy: "REJECT_DUPLICATE",
|
|
198
|
+
args: [
|
|
199
|
+
{
|
|
200
|
+
accountId: task.accountId,
|
|
201
|
+
workspaceId: task.workspaceId,
|
|
202
|
+
taskId: task.id,
|
|
203
|
+
triggerType: "manual",
|
|
204
|
+
agentRunUsageIdempotencyKey,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// A duplicate trigger token started this run already; treat the retry
|
|
210
|
+
// as a no-op so the (idempotent) usage charge stays the only effect.
|
|
211
|
+
if (isWorkflowAlreadyStarted(error)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
121
214
|
throw error;
|
|
122
215
|
}
|
|
123
216
|
},
|
|
217
|
+
startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {
|
|
218
|
+
const targetId = changeId ?? versionId;
|
|
219
|
+
if (!targetId) {
|
|
220
|
+
throw new Error("rig verification requires changeId or versionId");
|
|
221
|
+
}
|
|
222
|
+
await temporal.workflow.start("rigVerificationWorkflow", {
|
|
223
|
+
taskQueue: settings.temporalTaskQueue,
|
|
224
|
+
workflowId: workflowId ?? `rig-verification-${targetId}-${crypto.randomUUID()}`,
|
|
225
|
+
workflowIdReusePolicy: "ALLOW_DUPLICATE",
|
|
226
|
+
args: [
|
|
227
|
+
{
|
|
228
|
+
workspaceId,
|
|
229
|
+
...(changeId ? { changeId } : {}),
|
|
230
|
+
...(versionId ? { versionId } : {}),
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
});
|
|
234
|
+
},
|
|
124
235
|
check: async () => {
|
|
125
236
|
await connection.workflowService.getSystemInfo({});
|
|
126
237
|
},
|
|
@@ -158,7 +269,8 @@ export async function startApi() {
|
|
|
158
269
|
let bus: Awaited<ReturnType<typeof createNatsEventBus>> | undefined;
|
|
159
270
|
let workflowClient: Awaited<ReturnType<typeof createTemporalWorkflowClient>> | undefined;
|
|
160
271
|
const retryOptions = startupRetryOptions(settings);
|
|
161
|
-
const onRetry = (event: Parameters<typeof logStartupDependencyRetry>[1]) =>
|
|
272
|
+
const onRetry = (event: Parameters<typeof logStartupDependencyRetry>[1]) =>
|
|
273
|
+
logStartupDependencyRetry(observability, event);
|
|
162
274
|
// The PRIVILEGED control-plane NATS login (M-AUTH): when the server runs with
|
|
163
275
|
// auth_callout, api/worker authenticate as a static account user permitted to
|
|
164
276
|
// request `agent.*.rpc`. Null in local dev (anonymous connect — the bus default).
|
|
@@ -169,7 +281,9 @@ export async function startApi() {
|
|
|
169
281
|
() =>
|
|
170
282
|
createNatsEventBus(
|
|
171
283
|
settings.natsUrl,
|
|
172
|
-
controlPlaneAuth
|
|
284
|
+
controlPlaneAuth
|
|
285
|
+
? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password }
|
|
286
|
+
: undefined,
|
|
173
287
|
{ logger: observabilityEventLogger(observability) },
|
|
174
288
|
),
|
|
175
289
|
{
|
|
@@ -177,16 +291,16 @@ export async function startApi() {
|
|
|
177
291
|
onRetry,
|
|
178
292
|
},
|
|
179
293
|
);
|
|
180
|
-
workflowClient = await retryStartupDependency(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
294
|
+
workflowClient = await retryStartupDependency(
|
|
295
|
+
"Temporal",
|
|
296
|
+
() => createTemporalWorkflowClient(settings, dbClient.db),
|
|
297
|
+
{
|
|
298
|
+
...retryOptions,
|
|
299
|
+
onRetry,
|
|
300
|
+
},
|
|
301
|
+
);
|
|
184
302
|
} catch (error) {
|
|
185
|
-
await Promise.allSettled([
|
|
186
|
-
bus?.close(),
|
|
187
|
-
workflowClient?.close(),
|
|
188
|
-
dbClient.close(),
|
|
189
|
-
]);
|
|
303
|
+
await Promise.allSettled([bus?.close(), workflowClient?.close(), dbClient.close()]);
|
|
190
304
|
throw error;
|
|
191
305
|
}
|
|
192
306
|
if (!bus || !workflowClient) {
|
|
@@ -295,25 +409,29 @@ export function temporalScheduleSpec(schedule: ScheduledTaskScheduleSpec): Sched
|
|
|
295
409
|
}
|
|
296
410
|
if (schedule.type === "calendar") {
|
|
297
411
|
return {
|
|
298
|
-
calendars: [
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
412
|
+
calendars: [
|
|
413
|
+
{
|
|
414
|
+
hour: schedule.hour,
|
|
415
|
+
minute: schedule.minute,
|
|
416
|
+
second: 0,
|
|
417
|
+
...(schedule.daysOfWeek ? { dayOfWeek: schedule.daysOfWeek } : {}),
|
|
418
|
+
},
|
|
419
|
+
],
|
|
304
420
|
timezone: schedule.timeZone,
|
|
305
421
|
};
|
|
306
422
|
}
|
|
307
423
|
const runAt = new Date(schedule.runAt);
|
|
308
424
|
return {
|
|
309
|
-
calendars: [
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
425
|
+
calendars: [
|
|
426
|
+
{
|
|
427
|
+
year: runAt.getUTCFullYear(),
|
|
428
|
+
month: temporalMonth(runAt.getUTCMonth()),
|
|
429
|
+
dayOfMonth: runAt.getUTCDate(),
|
|
430
|
+
hour: runAt.getUTCHours(),
|
|
431
|
+
minute: runAt.getUTCMinutes(),
|
|
432
|
+
second: runAt.getUTCSeconds(),
|
|
433
|
+
},
|
|
434
|
+
],
|
|
317
435
|
timezone: "UTC",
|
|
318
436
|
};
|
|
319
437
|
}
|
|
@@ -330,12 +448,14 @@ function temporalScheduleOptions(task: ScheduledTask, taskQueue: string): Schedu
|
|
|
330
448
|
type: "startWorkflow",
|
|
331
449
|
workflowType: "scheduledTaskFireWorkflow",
|
|
332
450
|
taskQueue,
|
|
333
|
-
args: [
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
451
|
+
args: [
|
|
452
|
+
{
|
|
453
|
+
accountId: task.accountId,
|
|
454
|
+
workspaceId: task.workspaceId,
|
|
455
|
+
taskId: task.id,
|
|
456
|
+
triggerType: "scheduled",
|
|
457
|
+
},
|
|
458
|
+
],
|
|
339
459
|
},
|
|
340
460
|
policies: {
|
|
341
461
|
overlap: temporalOverlapPolicy(task.overlapPolicy),
|
|
@@ -362,6 +482,8 @@ function temporalScheduleUpdateOptions(options: ScheduleOptions): ScheduleUpdate
|
|
|
362
482
|
...(options.policies ? { policies: options.policies } : {}),
|
|
363
483
|
state: options.state ?? {},
|
|
364
484
|
...(options.searchAttributes ? { searchAttributes: options.searchAttributes } : {}),
|
|
365
|
-
...(options.typedSearchAttributes
|
|
485
|
+
...(options.typedSearchAttributes
|
|
486
|
+
? { typedSearchAttributes: options.typedSearchAttributes }
|
|
487
|
+
: {}),
|
|
366
488
|
};
|
|
367
489
|
}
|