@k2b/cloud 0.43.0 → 0.45.0
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/package.json +11 -2
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/GroupCoverage.tsx +20 -5
- package/src/access/PermissionEditor.tsx +20 -5
- package/src/access/PrincipalAvatar.tsx +30 -21
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/access/messages.ts +4 -4
- package/src/account/EntitySearch.tsx +12 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +10 -6
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +40 -25
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +12 -5
- package/src/ai/inference-calls.ts +5 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api.ts +26 -9
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/project-tool.ts +4 -4
- package/src/ai/projects-authorization.ts +53 -0
- package/src/ai/projects-routes.ts +58 -47
- package/src/ai/projects.ts +110 -88
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +9 -3
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +47 -48
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/skill-tool.ts +4 -4
- package/src/ai/skills-routes.ts +27 -26
- package/src/ai/skills.ts +120 -81
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +21 -44
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +27 -22
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +22 -3
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-skills.ts +4 -1
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +35 -5
- package/src/api/capabilities.ts +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/session.ts +39 -0
- package/src/browser/shared-text-history-validation.ts +151 -0
- package/src/browser/shared-text-history.ts +464 -0
- package/src/browser/shared-text-offline-authorized.ts +29 -0
- package/src/browser/shared-text-offline.ts +351 -0
- package/src/browser/shared-text-provider.ts +714 -0
- package/src/browser/shared-text-table-contract.ts +150 -0
- package/src/browser/shared-text.ts +19 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +6 -1
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +1 -1
- package/src/server/api/respond.ts +17 -4
- package/src/server/index.ts +2 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +31 -10
- package/src/server/services/index.ts +1 -0
- package/src/services/accounts/app.ts +36 -13
- package/src/services/accounts/entities.ts +43 -19
- package/src/services/accounts/group-sql.ts +23 -0
- package/src/services/accounts/groups.ts +19 -14
- package/src/services/accounts/identities.ts +70 -11
- package/src/services/accounts/identity-reconciliation.ts +3 -1
- package/src/services/accounts/local-groups.ts +69 -4
- package/src/services/accounts/users.ts +45 -14
- package/src/services/app-access.ts +25 -7
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/index.ts +2 -2
- package/src/services/mandates/policy.ts +20 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/session/index.ts +26 -3
- package/src/services/settings/core-settings.ts +71 -43
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/email-html.ts +1 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/markdown/extensions/katex.ts +8 -1
- package/src/shared/markdown/extensions/tables.ts +14 -1
- package/src/shared/markdown/index.ts +22 -11
- package/src/shared-text/anchors.ts +29 -0
- package/src/shared-text/awareness.ts +87 -0
- package/src/shared-text/changes.ts +27 -0
- package/src/shared-text/documents.ts +433 -65
- package/src/shared-text/events.ts +12 -2
- package/src/shared-text/index.ts +16 -1
- package/src/shared-text/log.ts +24 -5
- package/src/shared-text/snapshots.ts +69 -23
- package/src/shared-text/structural-history.ts +377 -0
- package/src/shared-text/table-actions.ts +432 -0
- package/src/shared-text/text-diff.ts +75 -0
- package/src/shared-text/types.ts +24 -5
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +44 -7
- package/src/ssr/Layout.tsx +9 -7
- package/src/ssr/MobileProfileActions.tsx +2 -2
- package/src/ssr/ProfilePreferences.tsx +2 -1
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -31
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-launchpad.tsx +6 -1
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +11 -1
- package/src/ssr/profile-actions.ts +3 -6
- package/src/ssr/pwa-messages.ts +2 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +3 -1
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/language/expressions.ts +3 -2
- package/src/workflows/runtime/executor.ts +134 -34
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { StructuredOutputError } from "@k2b/nessi";
|
|
2
|
-
import type
|
|
2
|
+
import { type Job, StoreFullError, type Worker } from "@k2b/sync";
|
|
3
3
|
import { lazySync } from "../../_internal/process-sync";
|
|
4
|
-
import { isAiSettingsError, runAiStructured } from "../../ai";
|
|
5
4
|
import { AiBackgroundAdmissionError, AiBackgroundCostError } from "../../ai/inference-calls";
|
|
5
|
+
import { AiQuotaError } from "../../ai/quotas";
|
|
6
|
+
import { resolveAiWorkflowModel, runAiStructured } from "../../ai/structured";
|
|
6
7
|
import { executeAiTask } from "../../ai/task-execution";
|
|
8
|
+
import { resolveApplicationAiTaskModel } from "../../ai/task-model";
|
|
9
|
+
import { isAiSettingsError } from "../../ai/validate";
|
|
10
|
+
import { env } from "../../config/env";
|
|
11
|
+
import { WorkflowAiAdmissionError, workflowAiModelHash } from "./policy";
|
|
7
12
|
|
|
8
13
|
import {
|
|
9
14
|
claimWorkflowAiTask,
|
|
10
15
|
completeWorkflowAiTask,
|
|
16
|
+
expireWorkflowAiTasks,
|
|
11
17
|
failWorkflowAiTask,
|
|
12
18
|
getWorkflowAiTask,
|
|
19
|
+
heartbeatWorkflowAiTask,
|
|
13
20
|
listRecoverableWorkflowAiTaskIds,
|
|
14
21
|
markWorkflowAiTaskCanceledIfRequested,
|
|
15
22
|
requeueWorkflowAiTask,
|
|
@@ -18,7 +25,7 @@ import {
|
|
|
18
25
|
} from "./store";
|
|
19
26
|
import type { WorkflowAiTask } from "./types";
|
|
20
27
|
|
|
21
|
-
const
|
|
28
|
+
export const WORKFLOW_AI_JOB_ID = "cloud.workflow-ai.v2";
|
|
22
29
|
const MAX_ATTEMPTS = 3;
|
|
23
30
|
const DEFAULT_CANCEL_POLL_MS = 500;
|
|
24
31
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
@@ -28,16 +35,26 @@ type StructuredRunner = typeof runAiStructured;
|
|
|
28
35
|
export type WorkflowAiRuntimeOptions = {
|
|
29
36
|
runStructured?: StructuredRunner;
|
|
30
37
|
cancelPollMs?: number;
|
|
38
|
+
attemptTimeoutMs?: number;
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
/** Attempt budget shared by the job declaration and the Postgres-side settle logic. */
|
|
34
|
-
export type WorkflowAiProcessOptions = Required<WorkflowAiRuntimeOptions
|
|
42
|
+
export type WorkflowAiProcessOptions = Required<Omit<WorkflowAiRuntimeOptions, "attemptTimeoutMs">> & {
|
|
43
|
+
maxAttempts: number;
|
|
44
|
+
attemptTimeoutMs?: number;
|
|
45
|
+
};
|
|
35
46
|
|
|
36
47
|
const workflowAiJob = lazySync((sync) =>
|
|
37
48
|
sync.job<{ taskId: string }>({
|
|
38
|
-
id:
|
|
49
|
+
id: WORKFLOW_AI_JOB_ID,
|
|
50
|
+
whenFull: "reject",
|
|
39
51
|
owner: "cloud",
|
|
40
|
-
delivery: {
|
|
52
|
+
delivery: {
|
|
53
|
+
maxInFlight: env.WORKFLOW_AI_BUDGET.globalConcurrent,
|
|
54
|
+
ackWaitMs: 30_000,
|
|
55
|
+
maxAttempts: MAX_ATTEMPTS,
|
|
56
|
+
backoffMs: [1_000, 2_000],
|
|
57
|
+
},
|
|
41
58
|
}),
|
|
42
59
|
);
|
|
43
60
|
|
|
@@ -76,24 +93,40 @@ export const executeWorkflowAiRequest = async (task: WorkflowAiTask, runStructur
|
|
|
76
93
|
task.request,
|
|
77
94
|
{
|
|
78
95
|
appId: task.appId,
|
|
79
|
-
attribution: {
|
|
80
|
-
|
|
96
|
+
attribution: {
|
|
97
|
+
workflowRunId: task.runId ?? undefined,
|
|
98
|
+
stepKey: task.stepKey,
|
|
99
|
+
userId: task.usageSubject?.type === "user" ? task.usageSubject.userId : task.usageUserId,
|
|
100
|
+
},
|
|
101
|
+
usageSubject: task.usageSubject,
|
|
102
|
+
model: { id: task.modelId },
|
|
103
|
+
resolveModel: async () => {
|
|
104
|
+
const resolved = task.runId
|
|
105
|
+
? await resolveAiWorkflowModel({ id: task.modelId })
|
|
106
|
+
: await resolveApplicationAiTaskModel(task.usageSubject, { id: task.modelId });
|
|
107
|
+
if (task.modelConfigHash && workflowAiModelHash(resolved.profile) !== task.modelConfigHash)
|
|
108
|
+
throw new WorkflowAiAdmissionError("WORKFLOW_AI_MODEL_CHANGED");
|
|
109
|
+
return { ...resolved, model: task.modelRef ?? resolved.model };
|
|
110
|
+
},
|
|
81
111
|
signal,
|
|
82
|
-
taskPrefix: "workflow",
|
|
112
|
+
taskPrefix: task.runId ? "workflow" : "application",
|
|
83
113
|
},
|
|
84
114
|
runStructured,
|
|
85
115
|
);
|
|
86
116
|
|
|
87
117
|
const retryableError = (error: unknown): boolean => {
|
|
118
|
+
if (error instanceof WorkflowAiAdmissionError) return error.retryable;
|
|
88
119
|
if (error instanceof AiBackgroundAdmissionError) return error.retryable;
|
|
89
|
-
if (error instanceof AiBackgroundCostError) return false;
|
|
120
|
+
if (error instanceof AiBackgroundCostError || error instanceof AiQuotaError) return false;
|
|
90
121
|
if (isAiSettingsError(error)) return false;
|
|
91
122
|
if (error instanceof StructuredOutputError) return error.code === "loop_failed";
|
|
92
123
|
return true;
|
|
93
124
|
};
|
|
94
125
|
|
|
95
126
|
const errorCode = (error: unknown): string => {
|
|
96
|
-
if (error instanceof
|
|
127
|
+
if (error instanceof WorkflowAiAdmissionError) return error.code;
|
|
128
|
+
if (error instanceof AiBackgroundCostError || error instanceof AiBackgroundAdmissionError || error instanceof AiQuotaError)
|
|
129
|
+
return error.code;
|
|
97
130
|
if (isAiSettingsError(error)) return error.aiError.code.toUpperCase();
|
|
98
131
|
if (error instanceof StructuredOutputError) return `WORKFLOW_AI_${error.code.toUpperCase()}`;
|
|
99
132
|
return "WORKFLOW_AI_PROVIDER_ERROR";
|
|
@@ -113,12 +146,18 @@ export const processWorkflowAiTask = async (
|
|
|
113
146
|
if (task.status === "canceled") return;
|
|
114
147
|
|
|
115
148
|
const controller = new AbortController();
|
|
149
|
+
let timedOut = false;
|
|
150
|
+
const deadline = setTimeout(() => {
|
|
151
|
+
timedOut = true;
|
|
152
|
+
controller.abort(new Error("Workflow AI attempt deadline exceeded. Retry the workflow or inspect the provider."));
|
|
153
|
+
}, options.attemptTimeoutMs ?? env.WORKFLOW_AI_ATTEMPT_TIMEOUT_MS);
|
|
116
154
|
let canceled = false;
|
|
117
155
|
let monitorError: unknown;
|
|
118
156
|
let checking = false;
|
|
119
157
|
let lastHeartbeat = Date.now();
|
|
120
158
|
const abortForShutdown = () => controller.abort(ctx.signal.reason ?? new Error("Workflow AI runtime stopped"));
|
|
121
159
|
ctx.signal.addEventListener("abort", abortForShutdown, { once: true });
|
|
160
|
+
if (ctx.signal.aborted) abortForShutdown();
|
|
122
161
|
|
|
123
162
|
const monitor = setInterval(() => {
|
|
124
163
|
if (checking || controller.signal.aborted) return;
|
|
@@ -132,6 +171,7 @@ export const processWorkflowAiTask = async (
|
|
|
132
171
|
}
|
|
133
172
|
if (Date.now() - lastHeartbeat >= HEARTBEAT_INTERVAL_MS) {
|
|
134
173
|
await ctx.heartbeat();
|
|
174
|
+
await heartbeatWorkflowAiTask(task.id);
|
|
135
175
|
lastHeartbeat = Date.now();
|
|
136
176
|
}
|
|
137
177
|
} catch (error) {
|
|
@@ -144,7 +184,13 @@ export const processWorkflowAiTask = async (
|
|
|
144
184
|
}, options.cancelPollMs);
|
|
145
185
|
|
|
146
186
|
try {
|
|
147
|
-
|
|
187
|
+
controller.signal.throwIfAborted();
|
|
188
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
189
|
+
const onAbort = () => reject(controller.signal.reason);
|
|
190
|
+
if (controller.signal.aborted) onAbort();
|
|
191
|
+
else controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
192
|
+
});
|
|
193
|
+
const result = await Promise.race([executeWorkflowAiRequest(task, options.runStructured, controller.signal), aborted]);
|
|
148
194
|
if (canceled || (await workflowAiTaskCancellationRequested(task.id))) {
|
|
149
195
|
await markWorkflowAiTaskCanceledIfRequested(task.id);
|
|
150
196
|
return;
|
|
@@ -155,6 +201,13 @@ export const processWorkflowAiTask = async (
|
|
|
155
201
|
await markWorkflowAiTaskCanceledIfRequested(task.id);
|
|
156
202
|
return;
|
|
157
203
|
}
|
|
204
|
+
if (timedOut) {
|
|
205
|
+
throw new WorkflowAiAttemptError(
|
|
206
|
+
"WORKFLOW_AI_TIMEOUT",
|
|
207
|
+
"Workflow AI attempt deadline exceeded. Retry the workflow or inspect the provider.",
|
|
208
|
+
true,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
158
211
|
if (ctx.signal.aborted || monitorError) {
|
|
159
212
|
throw new WorkflowAiAttemptError("WORKFLOW_AI_INTERRUPTED", "Workflow AI task was interrupted.", true);
|
|
160
213
|
}
|
|
@@ -165,12 +218,23 @@ export const processWorkflowAiTask = async (
|
|
|
165
218
|
);
|
|
166
219
|
} finally {
|
|
167
220
|
clearInterval(monitor);
|
|
221
|
+
clearTimeout(deadline);
|
|
168
222
|
ctx.signal.removeEventListener("abort", abortForShutdown);
|
|
169
223
|
}
|
|
170
224
|
};
|
|
171
225
|
|
|
172
226
|
let activeJob: Job<{ taskId: string }> | null = null;
|
|
173
227
|
let activeWorker: Worker | null = null;
|
|
228
|
+
let recoveryTimer: ReturnType<typeof setInterval> | null = null;
|
|
229
|
+
let recovery: Promise<void> | null = null;
|
|
230
|
+
let shutdown: AbortController | null = null;
|
|
231
|
+
|
|
232
|
+
const recover = (): Promise<void> => {
|
|
233
|
+
recovery ??= expireWorkflowAiTasks(env.WORKFLOW_AI_QUEUE_TIMEOUT_MS).finally(() => {
|
|
234
|
+
recovery = null;
|
|
235
|
+
});
|
|
236
|
+
return recovery;
|
|
237
|
+
};
|
|
174
238
|
|
|
175
239
|
export const startWorkflowAiRuntime = async (input: WorkflowAiRuntimeOptions = {}): Promise<void> => {
|
|
176
240
|
if (activeJob) return;
|
|
@@ -178,14 +242,26 @@ export const startWorkflowAiRuntime = async (input: WorkflowAiRuntimeOptions = {
|
|
|
178
242
|
runStructured: input.runStructured ?? runAiStructured,
|
|
179
243
|
maxAttempts: MAX_ATTEMPTS,
|
|
180
244
|
cancelPollMs: input.cancelPollMs ?? DEFAULT_CANCEL_POLL_MS,
|
|
245
|
+
attemptTimeoutMs: input.attemptTimeoutMs ?? env.WORKFLOW_AI_ATTEMPT_TIMEOUT_MS,
|
|
181
246
|
};
|
|
182
247
|
const next = workflowAiJob();
|
|
183
248
|
activeJob = next;
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
shutdown = controller;
|
|
184
251
|
try {
|
|
185
|
-
activeWorker = await next.process({ concurrency:
|
|
252
|
+
activeWorker = await next.process({ concurrency: env.WORKFLOW_AI_BUDGET.globalConcurrent }, async (context) => {
|
|
186
253
|
try {
|
|
187
|
-
await processWorkflowAiTask(
|
|
254
|
+
await processWorkflowAiTask(
|
|
255
|
+
context.input.taskId,
|
|
256
|
+
{ signal: AbortSignal.any([context.signal, controller.signal]), heartbeat: () => context.heartbeat() },
|
|
257
|
+
options,
|
|
258
|
+
);
|
|
188
259
|
} catch (caught) {
|
|
260
|
+
if (controller.signal.aborted) {
|
|
261
|
+
// Shutdown is not a provider failure. Persist before drain lets the process exit.
|
|
262
|
+
if (!(await markWorkflowAiTaskCanceledIfRequested(context.input.taskId))) await requeueWorkflowAiTask(context.input.taskId);
|
|
263
|
+
throw caught;
|
|
264
|
+
}
|
|
189
265
|
const error =
|
|
190
266
|
caught instanceof WorkflowAiAttemptError
|
|
191
267
|
? caught
|
|
@@ -195,21 +271,55 @@ export const startWorkflowAiRuntime = async (input: WorkflowAiRuntimeOptions = {
|
|
|
195
271
|
// Postgres owns the terminal result, including intentional cancellation.
|
|
196
272
|
}
|
|
197
273
|
});
|
|
198
|
-
|
|
274
|
+
await recover();
|
|
275
|
+
for (const taskId of await listRecoverableWorkflowAiTaskIds()) {
|
|
276
|
+
try {
|
|
277
|
+
await submitWorkflowAiTask(taskId);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
// Submission already persists and wakes the failed task; recover the others.
|
|
280
|
+
console.error("Workflow AI task resubmission failed", { taskId, error });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
recoveryTimer = setInterval(() => {
|
|
284
|
+
void recover().catch((error) => console.error("Workflow AI recovery failed", error));
|
|
285
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
199
286
|
} catch (error) {
|
|
200
|
-
stopWorkflowAiRuntime();
|
|
287
|
+
await stopWorkflowAiRuntime();
|
|
201
288
|
throw error;
|
|
202
289
|
}
|
|
203
290
|
};
|
|
204
291
|
|
|
205
292
|
export const submitWorkflowAiTask = async (taskId: string): Promise<void> => {
|
|
206
293
|
if (!activeJob) throw new Error("Workflow AI runtime is not started.");
|
|
207
|
-
|
|
294
|
+
try {
|
|
295
|
+
await activeJob.submit({ key: `task:${taskId}`, coalesce: true, input: { taskId } });
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const failure = new WorkflowAiAttemptError(
|
|
298
|
+
error instanceof StoreFullError ? "WORKFLOW_AI_QUEUE_FULL" : "WORKFLOW_AI_SUBMISSION_FAILED",
|
|
299
|
+
"Workflow AI queue could not accept the task. Retry the workflow after capacity or connectivity recovers.",
|
|
300
|
+
true,
|
|
301
|
+
);
|
|
302
|
+
await failWorkflowAiTask(taskId, failure);
|
|
303
|
+
throw failure;
|
|
304
|
+
}
|
|
208
305
|
};
|
|
209
306
|
|
|
210
|
-
export const stopWorkflowAiRuntime = (): void => {
|
|
211
|
-
activeWorker
|
|
307
|
+
export const stopWorkflowAiRuntime = async (): Promise<void> => {
|
|
308
|
+
const worker = activeWorker;
|
|
309
|
+
worker?.stop();
|
|
310
|
+
shutdown?.abort(new Error("Workflow AI runtime stopped"));
|
|
311
|
+
if (recoveryTimer) clearInterval(recoveryTimer);
|
|
312
|
+
recoveryTimer = null;
|
|
212
313
|
|
|
213
|
-
|
|
214
|
-
|
|
314
|
+
// Sync's existing drain budget bounds settlement; do not exit before DB requeue.
|
|
315
|
+
try {
|
|
316
|
+
await worker?.drain();
|
|
317
|
+
} finally {
|
|
318
|
+
// The recovery timer already reports its failure. Always allow a later start,
|
|
319
|
+
// including when drain rejects, while preserving the drain error for callers.
|
|
320
|
+
await recovery?.catch(() => {});
|
|
321
|
+
activeWorker = null;
|
|
322
|
+
activeJob = null;
|
|
323
|
+
shutdown = null;
|
|
324
|
+
}
|
|
215
325
|
};
|