whale-code 6.4.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/README.md +95 -0
- package/bin/swag-agent.js +9 -0
- package/bin/swagmanager-mcp.js +321 -0
- package/dist/cli/app.d.ts +26 -0
- package/dist/cli/app.js +64 -0
- package/dist/cli/chat/AgentSelector.d.ts +14 -0
- package/dist/cli/chat/AgentSelector.js +14 -0
- package/dist/cli/chat/ChatApp.d.ts +9 -0
- package/dist/cli/chat/ChatApp.js +267 -0
- package/dist/cli/chat/ChatInput.d.ts +39 -0
- package/dist/cli/chat/ChatInput.js +509 -0
- package/dist/cli/chat/MarkdownText.d.ts +10 -0
- package/dist/cli/chat/MarkdownText.js +20 -0
- package/dist/cli/chat/MessageList.d.ts +37 -0
- package/dist/cli/chat/MessageList.js +80 -0
- package/dist/cli/chat/ModelSelector.d.ts +20 -0
- package/dist/cli/chat/ModelSelector.js +73 -0
- package/dist/cli/chat/RewindViewer.d.ts +26 -0
- package/dist/cli/chat/RewindViewer.js +185 -0
- package/dist/cli/chat/StoreSelector.d.ts +14 -0
- package/dist/cli/chat/StoreSelector.js +24 -0
- package/dist/cli/chat/StreamingText.d.ts +12 -0
- package/dist/cli/chat/StreamingText.js +12 -0
- package/dist/cli/chat/SubagentPanel.d.ts +45 -0
- package/dist/cli/chat/SubagentPanel.js +110 -0
- package/dist/cli/chat/TeamPanel.d.ts +21 -0
- package/dist/cli/chat/TeamPanel.js +42 -0
- package/dist/cli/chat/ToolIndicator.d.ts +25 -0
- package/dist/cli/chat/ToolIndicator.js +436 -0
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
- package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
- package/dist/cli/commands/config-cmd.d.ts +10 -0
- package/dist/cli/commands/config-cmd.js +99 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +172 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +278 -0
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +162 -0
- package/dist/cli/login/LoginApp.d.ts +7 -0
- package/dist/cli/login/LoginApp.js +157 -0
- package/dist/cli/print-mode.d.ts +31 -0
- package/dist/cli/print-mode.js +202 -0
- package/dist/cli/serve-mode.d.ts +37 -0
- package/dist/cli/serve-mode.js +636 -0
- package/dist/cli/services/agent-definitions.d.ts +25 -0
- package/dist/cli/services/agent-definitions.js +91 -0
- package/dist/cli/services/agent-events.d.ts +178 -0
- package/dist/cli/services/agent-events.js +175 -0
- package/dist/cli/services/agent-loop.d.ts +90 -0
- package/dist/cli/services/agent-loop.js +762 -0
- package/dist/cli/services/agent-worker-base.d.ts +97 -0
- package/dist/cli/services/agent-worker-base.js +220 -0
- package/dist/cli/services/auth-service.d.ts +30 -0
- package/dist/cli/services/auth-service.js +160 -0
- package/dist/cli/services/background-processes.d.ts +126 -0
- package/dist/cli/services/background-processes.js +318 -0
- package/dist/cli/services/browser-auth.d.ts +24 -0
- package/dist/cli/services/browser-auth.js +180 -0
- package/dist/cli/services/claude-md-loader.d.ts +16 -0
- package/dist/cli/services/claude-md-loader.js +58 -0
- package/dist/cli/services/config-store.d.ts +47 -0
- package/dist/cli/services/config-store.js +79 -0
- package/dist/cli/services/debug-log.d.ts +10 -0
- package/dist/cli/services/debug-log.js +52 -0
- package/dist/cli/services/error-logger.d.ts +58 -0
- package/dist/cli/services/error-logger.js +269 -0
- package/dist/cli/services/file-history.d.ts +21 -0
- package/dist/cli/services/file-history.js +83 -0
- package/dist/cli/services/format-server-response.d.ts +16 -0
- package/dist/cli/services/format-server-response.js +440 -0
- package/dist/cli/services/git-context.d.ts +11 -0
- package/dist/cli/services/git-context.js +66 -0
- package/dist/cli/services/hooks.d.ts +85 -0
- package/dist/cli/services/hooks.js +258 -0
- package/dist/cli/services/interactive-tools.d.ts +125 -0
- package/dist/cli/services/interactive-tools.js +260 -0
- package/dist/cli/services/keybinding-manager.d.ts +52 -0
- package/dist/cli/services/keybinding-manager.js +115 -0
- package/dist/cli/services/local-tools.d.ts +22 -0
- package/dist/cli/services/local-tools.js +697 -0
- package/dist/cli/services/lsp-manager.d.ts +18 -0
- package/dist/cli/services/lsp-manager.js +717 -0
- package/dist/cli/services/mcp-client.d.ts +48 -0
- package/dist/cli/services/mcp-client.js +157 -0
- package/dist/cli/services/memory-manager.d.ts +16 -0
- package/dist/cli/services/memory-manager.js +57 -0
- package/dist/cli/services/model-manager.d.ts +18 -0
- package/dist/cli/services/model-manager.js +71 -0
- package/dist/cli/services/model-router.d.ts +26 -0
- package/dist/cli/services/model-router.js +149 -0
- package/dist/cli/services/permission-modes.d.ts +13 -0
- package/dist/cli/services/permission-modes.js +43 -0
- package/dist/cli/services/rewind.d.ts +84 -0
- package/dist/cli/services/rewind.js +194 -0
- package/dist/cli/services/ripgrep.d.ts +28 -0
- package/dist/cli/services/ripgrep.js +138 -0
- package/dist/cli/services/sandbox.d.ts +29 -0
- package/dist/cli/services/sandbox.js +97 -0
- package/dist/cli/services/server-tools.d.ts +61 -0
- package/dist/cli/services/server-tools.js +543 -0
- package/dist/cli/services/session-persistence.d.ts +23 -0
- package/dist/cli/services/session-persistence.js +99 -0
- package/dist/cli/services/subagent-worker.d.ts +19 -0
- package/dist/cli/services/subagent-worker.js +41 -0
- package/dist/cli/services/subagent.d.ts +47 -0
- package/dist/cli/services/subagent.js +647 -0
- package/dist/cli/services/system-prompt.d.ts +7 -0
- package/dist/cli/services/system-prompt.js +198 -0
- package/dist/cli/services/team-lead.d.ts +73 -0
- package/dist/cli/services/team-lead.js +512 -0
- package/dist/cli/services/team-state.d.ts +77 -0
- package/dist/cli/services/team-state.js +398 -0
- package/dist/cli/services/teammate.d.ts +31 -0
- package/dist/cli/services/teammate.js +689 -0
- package/dist/cli/services/telemetry.d.ts +61 -0
- package/dist/cli/services/telemetry.js +209 -0
- package/dist/cli/services/tools/agent-tools.d.ts +14 -0
- package/dist/cli/services/tools/agent-tools.js +347 -0
- package/dist/cli/services/tools/file-ops.d.ts +15 -0
- package/dist/cli/services/tools/file-ops.js +487 -0
- package/dist/cli/services/tools/search-tools.d.ts +8 -0
- package/dist/cli/services/tools/search-tools.js +186 -0
- package/dist/cli/services/tools/shell-exec.d.ts +10 -0
- package/dist/cli/services/tools/shell-exec.js +168 -0
- package/dist/cli/services/tools/task-manager.d.ts +28 -0
- package/dist/cli/services/tools/task-manager.js +209 -0
- package/dist/cli/services/tools/web-tools.d.ts +11 -0
- package/dist/cli/services/tools/web-tools.js +395 -0
- package/dist/cli/setup/SetupApp.d.ts +9 -0
- package/dist/cli/setup/SetupApp.js +191 -0
- package/dist/cli/shared/MatrixIntro.d.ts +4 -0
- package/dist/cli/shared/MatrixIntro.js +83 -0
- package/dist/cli/shared/Theme.d.ts +74 -0
- package/dist/cli/shared/Theme.js +127 -0
- package/dist/cli/shared/WhaleBanner.d.ts +10 -0
- package/dist/cli/shared/WhaleBanner.js +12 -0
- package/dist/cli/shared/markdown.d.ts +21 -0
- package/dist/cli/shared/markdown.js +756 -0
- package/dist/cli/status/StatusApp.d.ts +4 -0
- package/dist/cli/status/StatusApp.js +105 -0
- package/dist/cli/stores/StoreApp.d.ts +7 -0
- package/dist/cli/stores/StoreApp.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +538 -0
- package/dist/local-agent/connection.d.ts +48 -0
- package/dist/local-agent/connection.js +332 -0
- package/dist/local-agent/discovery.d.ts +18 -0
- package/dist/local-agent/discovery.js +146 -0
- package/dist/local-agent/executor.d.ts +34 -0
- package/dist/local-agent/executor.js +241 -0
- package/dist/local-agent/index.d.ts +14 -0
- package/dist/local-agent/index.js +198 -0
- package/dist/node/adapters/base.d.ts +35 -0
- package/dist/node/adapters/base.js +10 -0
- package/dist/node/adapters/discord.d.ts +29 -0
- package/dist/node/adapters/discord.js +299 -0
- package/dist/node/adapters/email.d.ts +23 -0
- package/dist/node/adapters/email.js +218 -0
- package/dist/node/adapters/imessage.d.ts +17 -0
- package/dist/node/adapters/imessage.js +118 -0
- package/dist/node/adapters/slack.d.ts +26 -0
- package/dist/node/adapters/slack.js +259 -0
- package/dist/node/adapters/sms.d.ts +23 -0
- package/dist/node/adapters/sms.js +161 -0
- package/dist/node/adapters/telegram.d.ts +17 -0
- package/dist/node/adapters/telegram.js +101 -0
- package/dist/node/adapters/webchat.d.ts +27 -0
- package/dist/node/adapters/webchat.js +160 -0
- package/dist/node/adapters/whatsapp.d.ts +28 -0
- package/dist/node/adapters/whatsapp.js +230 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +325 -0
- package/dist/node/config.d.ts +17 -0
- package/dist/node/config.js +31 -0
- package/dist/node/runtime.d.ts +50 -0
- package/dist/node/runtime.js +351 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
- package/dist/server/handlers/analytics.d.ts +17 -0
- package/dist/server/handlers/analytics.js +266 -0
- package/dist/server/handlers/api-keys.d.ts +6 -0
- package/dist/server/handlers/api-keys.js +221 -0
- package/dist/server/handlers/billing.d.ts +33 -0
- package/dist/server/handlers/billing.js +272 -0
- package/dist/server/handlers/browser.d.ts +10 -0
- package/dist/server/handlers/browser.js +517 -0
- package/dist/server/handlers/catalog.d.ts +99 -0
- package/dist/server/handlers/catalog.js +976 -0
- package/dist/server/handlers/comms.d.ts +254 -0
- package/dist/server/handlers/comms.js +588 -0
- package/dist/server/handlers/creations.d.ts +6 -0
- package/dist/server/handlers/creations.js +479 -0
- package/dist/server/handlers/crm.d.ts +89 -0
- package/dist/server/handlers/crm.js +538 -0
- package/dist/server/handlers/discovery.d.ts +6 -0
- package/dist/server/handlers/discovery.js +288 -0
- package/dist/server/handlers/embeddings.d.ts +92 -0
- package/dist/server/handlers/embeddings.js +197 -0
- package/dist/server/handlers/enrichment.d.ts +8 -0
- package/dist/server/handlers/enrichment.js +768 -0
- package/dist/server/handlers/image-gen.d.ts +6 -0
- package/dist/server/handlers/image-gen.js +409 -0
- package/dist/server/handlers/inventory.d.ts +319 -0
- package/dist/server/handlers/inventory.js +447 -0
- package/dist/server/handlers/kali.d.ts +10 -0
- package/dist/server/handlers/kali.js +210 -0
- package/dist/server/handlers/llm-providers.d.ts +6 -0
- package/dist/server/handlers/llm-providers.js +673 -0
- package/dist/server/handlers/local-agent.d.ts +6 -0
- package/dist/server/handlers/local-agent.js +118 -0
- package/dist/server/handlers/meta-ads.d.ts +111 -0
- package/dist/server/handlers/meta-ads.js +2279 -0
- package/dist/server/handlers/nodes.d.ts +33 -0
- package/dist/server/handlers/nodes.js +699 -0
- package/dist/server/handlers/operations.d.ts +138 -0
- package/dist/server/handlers/operations.js +131 -0
- package/dist/server/handlers/platform.d.ts +23 -0
- package/dist/server/handlers/platform.js +227 -0
- package/dist/server/handlers/supply-chain.d.ts +19 -0
- package/dist/server/handlers/supply-chain.js +327 -0
- package/dist/server/handlers/transcription.d.ts +17 -0
- package/dist/server/handlers/transcription.js +121 -0
- package/dist/server/handlers/video-gen.d.ts +6 -0
- package/dist/server/handlers/video-gen.js +466 -0
- package/dist/server/handlers/voice.d.ts +8 -0
- package/dist/server/handlers/voice.js +1146 -0
- package/dist/server/handlers/workflow-steps.d.ts +86 -0
- package/dist/server/handlers/workflow-steps.js +2349 -0
- package/dist/server/handlers/workflows.d.ts +7 -0
- package/dist/server/handlers/workflows.js +989 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2427 -0
- package/dist/server/lib/batch-client.d.ts +80 -0
- package/dist/server/lib/batch-client.js +467 -0
- package/dist/server/lib/code-worker-pool.d.ts +31 -0
- package/dist/server/lib/code-worker-pool.js +224 -0
- package/dist/server/lib/code-worker.d.ts +1 -0
- package/dist/server/lib/code-worker.js +188 -0
- package/dist/server/lib/compaction-service.d.ts +32 -0
- package/dist/server/lib/compaction-service.js +162 -0
- package/dist/server/lib/logger.d.ts +19 -0
- package/dist/server/lib/logger.js +46 -0
- package/dist/server/lib/otel.d.ts +38 -0
- package/dist/server/lib/otel.js +126 -0
- package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
- package/dist/server/lib/pg-rate-limiter.js +86 -0
- package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
- package/dist/server/lib/prompt-sanitizer.js +177 -0
- package/dist/server/lib/provider-capabilities.d.ts +85 -0
- package/dist/server/lib/provider-capabilities.js +190 -0
- package/dist/server/lib/provider-failover.d.ts +74 -0
- package/dist/server/lib/provider-failover.js +210 -0
- package/dist/server/lib/rate-limiter.d.ts +39 -0
- package/dist/server/lib/rate-limiter.js +147 -0
- package/dist/server/lib/server-agent-loop.d.ts +107 -0
- package/dist/server/lib/server-agent-loop.js +667 -0
- package/dist/server/lib/server-subagent.d.ts +78 -0
- package/dist/server/lib/server-subagent.js +203 -0
- package/dist/server/lib/session-checkpoint.d.ts +51 -0
- package/dist/server/lib/session-checkpoint.js +145 -0
- package/dist/server/lib/ssrf-guard.d.ts +13 -0
- package/dist/server/lib/ssrf-guard.js +240 -0
- package/dist/server/lib/supabase-client.d.ts +7 -0
- package/dist/server/lib/supabase-client.js +78 -0
- package/dist/server/lib/template-resolver.d.ts +31 -0
- package/dist/server/lib/template-resolver.js +215 -0
- package/dist/server/lib/utils.d.ts +16 -0
- package/dist/server/lib/utils.js +147 -0
- package/dist/server/local-agent-gateway.d.ts +82 -0
- package/dist/server/local-agent-gateway.js +426 -0
- package/dist/server/providers/anthropic.d.ts +20 -0
- package/dist/server/providers/anthropic.js +199 -0
- package/dist/server/providers/bedrock.d.ts +20 -0
- package/dist/server/providers/bedrock.js +194 -0
- package/dist/server/providers/gemini.d.ts +24 -0
- package/dist/server/providers/gemini.js +486 -0
- package/dist/server/providers/openai.d.ts +24 -0
- package/dist/server/providers/openai.js +522 -0
- package/dist/server/providers/registry.d.ts +32 -0
- package/dist/server/providers/registry.js +58 -0
- package/dist/server/providers/shared.d.ts +32 -0
- package/dist/server/providers/shared.js +124 -0
- package/dist/server/providers/types.d.ts +92 -0
- package/dist/server/providers/types.js +12 -0
- package/dist/server/proxy-handlers.d.ts +6 -0
- package/dist/server/proxy-handlers.js +89 -0
- package/dist/server/tool-router.d.ts +149 -0
- package/dist/server/tool-router.js +803 -0
- package/dist/server/validation.d.ts +24 -0
- package/dist/server/validation.js +301 -0
- package/dist/server/worker.d.ts +19 -0
- package/dist/server/worker.js +201 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/shared/agent-core.d.ts +157 -0
- package/dist/shared/agent-core.js +534 -0
- package/dist/shared/anthropic-types.d.ts +105 -0
- package/dist/shared/anthropic-types.js +7 -0
- package/dist/shared/api-client.d.ts +90 -0
- package/dist/shared/api-client.js +379 -0
- package/dist/shared/constants.d.ts +33 -0
- package/dist/shared/constants.js +80 -0
- package/dist/shared/sse-parser.d.ts +26 -0
- package/dist/shared/sse-parser.js +259 -0
- package/dist/shared/tool-dispatch.d.ts +52 -0
- package/dist/shared/tool-dispatch.js +191 -0
- package/dist/shared/types.d.ts +72 -0
- package/dist/shared/types.js +7 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/dist/webchat/widget.d.ts +0 -0
- package/dist/webchat/widget.js +397 -0
- package/package.json +95 -0
- package/src/cli/services/builtin-skills/commit.md +19 -0
- package/src/cli/services/builtin-skills/review-pr.md +21 -0
- package/src/cli/services/builtin-skills/review.md +18 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod input validation for server tool dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Only mutation actions are validated — reads (find, get, list, summary, etc.)
|
|
5
|
+
* pass through without schema checks. This prevents malformed AI tool calls
|
|
6
|
+
* from crashing handlers with TypeErrors on unchecked `as` casts.
|
|
7
|
+
*/
|
|
8
|
+
export type ValidationResult = {
|
|
9
|
+
valid: true;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
} | {
|
|
12
|
+
valid: false;
|
|
13
|
+
error: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Validate tool args before dispatch to handler.
|
|
17
|
+
*
|
|
18
|
+
* Returns `{ valid: true, data }` with parsed (coerced) data on success,
|
|
19
|
+
* or `{ valid: false, error }` with a human-readable message on failure.
|
|
20
|
+
*
|
|
21
|
+
* If no schema is registered for the (tool, action) pair, returns passthrough
|
|
22
|
+
* with `{ valid: true, data: args }` — no validation, no blocking.
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateToolArgs(toolName: string, args: Record<string, unknown>): ValidationResult;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod input validation for server tool dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Only mutation actions are validated — reads (find, get, list, summary, etc.)
|
|
5
|
+
* pass through without schema checks. This prevents malformed AI tool calls
|
|
6
|
+
* from crashing handlers with TypeErrors on unchecked `as` casts.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// SHARED PRIMITIVES
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const uuid = z.string().uuid();
|
|
13
|
+
const positiveNumber = z.number().positive();
|
|
14
|
+
const nonNegativeNumber = z.number().min(0);
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// INVENTORY MUTATIONS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const inventoryAdjustSchema = z.object({
|
|
19
|
+
action: z.literal("adjust"),
|
|
20
|
+
product_id: uuid,
|
|
21
|
+
location_id: uuid,
|
|
22
|
+
adjustment: z.number(),
|
|
23
|
+
reason: z.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
const inventorySetSchema = z.object({
|
|
26
|
+
action: z.literal("set"),
|
|
27
|
+
product_id: uuid,
|
|
28
|
+
location_id: uuid,
|
|
29
|
+
quantity: nonNegativeNumber,
|
|
30
|
+
});
|
|
31
|
+
const inventoryTransferSchema = z.object({
|
|
32
|
+
action: z.literal("transfer"),
|
|
33
|
+
product_id: uuid,
|
|
34
|
+
from_location_id: uuid,
|
|
35
|
+
to_location_id: uuid,
|
|
36
|
+
quantity: positiveNumber,
|
|
37
|
+
});
|
|
38
|
+
const inventoryBulkSetSchema = z.object({
|
|
39
|
+
action: z.literal("bulk_set"),
|
|
40
|
+
items: z.array(z.object({
|
|
41
|
+
product_id: uuid,
|
|
42
|
+
location_id: uuid,
|
|
43
|
+
quantity: nonNegativeNumber,
|
|
44
|
+
})).min(1),
|
|
45
|
+
});
|
|
46
|
+
const inventoryBulkAdjustSchema = z.object({
|
|
47
|
+
action: z.literal("bulk_adjust"),
|
|
48
|
+
items: z.array(z.object({
|
|
49
|
+
product_id: uuid,
|
|
50
|
+
location_id: uuid,
|
|
51
|
+
adjustment: z.number(),
|
|
52
|
+
})).min(1),
|
|
53
|
+
});
|
|
54
|
+
const inventoryBulkClearSchema = z.object({
|
|
55
|
+
action: z.literal("bulk_clear"),
|
|
56
|
+
location_id: uuid,
|
|
57
|
+
});
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// SUPPLY CHAIN — PURCHASE ORDERS
|
|
60
|
+
// ============================================================================
|
|
61
|
+
const poCreateSchema = z.object({
|
|
62
|
+
action: z.literal("create"),
|
|
63
|
+
supplier_id: uuid,
|
|
64
|
+
location_id: uuid,
|
|
65
|
+
items: z.array(z.object({
|
|
66
|
+
product_id: uuid,
|
|
67
|
+
quantity: positiveNumber,
|
|
68
|
+
unit_price: nonNegativeNumber.optional(),
|
|
69
|
+
unit_cost: nonNegativeNumber.optional(),
|
|
70
|
+
})).min(1).optional(),
|
|
71
|
+
notes: z.string().optional(),
|
|
72
|
+
expected_delivery_date: z.string().optional(),
|
|
73
|
+
po_type: z.string().optional(),
|
|
74
|
+
});
|
|
75
|
+
const poAddItemsSchema = z.object({
|
|
76
|
+
action: z.literal("add_items"),
|
|
77
|
+
purchase_order_id: uuid,
|
|
78
|
+
items: z.array(z.object({
|
|
79
|
+
product_id: uuid,
|
|
80
|
+
quantity: positiveNumber,
|
|
81
|
+
unit_price: nonNegativeNumber.optional(),
|
|
82
|
+
unit_cost: nonNegativeNumber.optional(),
|
|
83
|
+
})).min(1),
|
|
84
|
+
});
|
|
85
|
+
const poApproveSchema = z.object({
|
|
86
|
+
action: z.literal("approve"),
|
|
87
|
+
purchase_order_id: uuid,
|
|
88
|
+
});
|
|
89
|
+
const poReceiveSchema = z.object({
|
|
90
|
+
action: z.literal("receive"),
|
|
91
|
+
purchase_order_id: uuid,
|
|
92
|
+
});
|
|
93
|
+
const poCancelSchema = z.object({
|
|
94
|
+
action: z.literal("cancel"),
|
|
95
|
+
purchase_order_id: uuid,
|
|
96
|
+
});
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// SUPPLY CHAIN — TRANSFERS
|
|
99
|
+
// ============================================================================
|
|
100
|
+
const transferCreateSchema = z.object({
|
|
101
|
+
action: z.literal("create"),
|
|
102
|
+
from_location_id: uuid.optional(),
|
|
103
|
+
source_location_id: uuid.optional(),
|
|
104
|
+
to_location_id: uuid.optional(),
|
|
105
|
+
destination_location_id: uuid.optional(),
|
|
106
|
+
items: z.array(z.object({
|
|
107
|
+
product_id: uuid,
|
|
108
|
+
quantity: positiveNumber,
|
|
109
|
+
})).min(1).optional(),
|
|
110
|
+
notes: z.string().optional(),
|
|
111
|
+
}).refine((d) => d.from_location_id || d.source_location_id, { message: "from_location_id or source_location_id is required", path: ["from_location_id"] }).refine((d) => d.to_location_id || d.destination_location_id, { message: "to_location_id or destination_location_id is required", path: ["to_location_id"] });
|
|
112
|
+
const transferApproveSchema = z.object({
|
|
113
|
+
action: z.literal("approve"),
|
|
114
|
+
transfer_id: uuid,
|
|
115
|
+
});
|
|
116
|
+
const transferShipSchema = z.object({
|
|
117
|
+
action: z.union([z.literal("ship"), z.literal("mark_in_transit")]),
|
|
118
|
+
transfer_id: uuid,
|
|
119
|
+
});
|
|
120
|
+
const transferReceiveSchema = z.object({
|
|
121
|
+
action: z.union([z.literal("receive"), z.literal("complete")]),
|
|
122
|
+
transfer_id: uuid,
|
|
123
|
+
});
|
|
124
|
+
const transferCancelSchema = z.object({
|
|
125
|
+
action: z.literal("cancel"),
|
|
126
|
+
transfer_id: uuid,
|
|
127
|
+
});
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// CUSTOMERS
|
|
130
|
+
// ============================================================================
|
|
131
|
+
const customerCreateSchema = z.object({
|
|
132
|
+
action: z.literal("create"),
|
|
133
|
+
first_name: z.string().min(1),
|
|
134
|
+
last_name: z.string().min(1),
|
|
135
|
+
email: z.string().email().optional(),
|
|
136
|
+
phone: z.string().optional(),
|
|
137
|
+
date_of_birth: z.string().optional(),
|
|
138
|
+
email_consent: z.boolean().optional(),
|
|
139
|
+
sms_consent: z.boolean().optional(),
|
|
140
|
+
street_address: z.string().optional(),
|
|
141
|
+
city: z.string().optional(),
|
|
142
|
+
state: z.string().optional(),
|
|
143
|
+
postal_code: z.string().optional(),
|
|
144
|
+
drivers_license_number: z.string().optional(),
|
|
145
|
+
medical_card_number: z.string().optional(),
|
|
146
|
+
medical_card_expiry: z.string().optional(),
|
|
147
|
+
});
|
|
148
|
+
const customerUpdateSchema = z.object({
|
|
149
|
+
action: z.literal("update"),
|
|
150
|
+
customer_id: uuid,
|
|
151
|
+
first_name: z.string().min(1).optional(),
|
|
152
|
+
last_name: z.string().min(1).optional(),
|
|
153
|
+
email: z.string().email().optional(),
|
|
154
|
+
phone: z.string().optional(),
|
|
155
|
+
date_of_birth: z.string().optional(),
|
|
156
|
+
status: z.string().optional(),
|
|
157
|
+
email_consent: z.boolean().optional(),
|
|
158
|
+
sms_consent: z.boolean().optional(),
|
|
159
|
+
push_consent: z.boolean().optional(),
|
|
160
|
+
loyalty_points: z.number().optional(),
|
|
161
|
+
loyalty_tier: z.string().optional(),
|
|
162
|
+
street_address: z.string().optional(),
|
|
163
|
+
city: z.string().optional(),
|
|
164
|
+
state: z.string().optional(),
|
|
165
|
+
postal_code: z.string().optional(),
|
|
166
|
+
drivers_license_number: z.string().optional(),
|
|
167
|
+
id_verified: z.boolean().optional(),
|
|
168
|
+
medical_card_number: z.string().optional(),
|
|
169
|
+
medical_card_expiry: z.string().optional(),
|
|
170
|
+
is_wholesale_approved: z.boolean().optional(),
|
|
171
|
+
wholesale_tier: z.string().optional(),
|
|
172
|
+
wholesale_business_name: z.string().optional(),
|
|
173
|
+
wholesale_license_number: z.string().optional(),
|
|
174
|
+
wholesale_tax_id: z.string().optional(),
|
|
175
|
+
});
|
|
176
|
+
const customerMergeSchema = z.object({
|
|
177
|
+
action: z.literal("merge"),
|
|
178
|
+
primary_customer_id: uuid,
|
|
179
|
+
secondary_customer_id: uuid,
|
|
180
|
+
});
|
|
181
|
+
const productMergeSchema = z.object({
|
|
182
|
+
action: z.literal("merge"),
|
|
183
|
+
primary_product_id: uuid,
|
|
184
|
+
secondary_product_id: uuid,
|
|
185
|
+
});
|
|
186
|
+
const productSchemas = {
|
|
187
|
+
merge: productMergeSchema,
|
|
188
|
+
};
|
|
189
|
+
const inventorySchemas = {
|
|
190
|
+
adjust: inventoryAdjustSchema,
|
|
191
|
+
set: inventorySetSchema,
|
|
192
|
+
transfer: inventoryTransferSchema,
|
|
193
|
+
bulk_set: inventoryBulkSetSchema,
|
|
194
|
+
bulk_adjust: inventoryBulkAdjustSchema,
|
|
195
|
+
bulk_clear: inventoryBulkClearSchema,
|
|
196
|
+
};
|
|
197
|
+
const purchaseOrderSchemas = {
|
|
198
|
+
create: poCreateSchema,
|
|
199
|
+
add_items: poAddItemsSchema,
|
|
200
|
+
approve: poApproveSchema,
|
|
201
|
+
mark_ordered: poApproveSchema,
|
|
202
|
+
receive: poReceiveSchema,
|
|
203
|
+
cancel: poCancelSchema,
|
|
204
|
+
};
|
|
205
|
+
const transferSchemas = {
|
|
206
|
+
create: transferCreateSchema,
|
|
207
|
+
approve: transferApproveSchema,
|
|
208
|
+
ship: transferShipSchema,
|
|
209
|
+
mark_in_transit: transferShipSchema,
|
|
210
|
+
receive: transferReceiveSchema,
|
|
211
|
+
complete: transferReceiveSchema,
|
|
212
|
+
cancel: transferCancelSchema,
|
|
213
|
+
};
|
|
214
|
+
const customerSchemas = {
|
|
215
|
+
create: customerCreateSchema,
|
|
216
|
+
update: customerUpdateSchema,
|
|
217
|
+
merge: customerMergeSchema,
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Top-level registry keyed by tool name.
|
|
221
|
+
* Only tools with mutation actions are listed.
|
|
222
|
+
* Read-only tools (analytics, orders.find, etc.) are intentionally absent — passthrough.
|
|
223
|
+
*/
|
|
224
|
+
const toolSchemaRegistry = {
|
|
225
|
+
products: productSchemas,
|
|
226
|
+
inventory: inventorySchemas,
|
|
227
|
+
purchase_orders: purchaseOrderSchemas,
|
|
228
|
+
transfers: transferSchemas,
|
|
229
|
+
customers: customerSchemas,
|
|
230
|
+
};
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// SUPPLY_CHAIN META-TOOL RESOLUTION
|
|
233
|
+
// ============================================================================
|
|
234
|
+
/**
|
|
235
|
+
* The "supply_chain" tool dispatches by action prefix:
|
|
236
|
+
* po_create → purchase_orders / create
|
|
237
|
+
* transfer_create → transfers / create
|
|
238
|
+
*
|
|
239
|
+
* This helper resolves supply_chain actions to the underlying tool + action.
|
|
240
|
+
*/
|
|
241
|
+
function resolveSupplyChainAction(action) {
|
|
242
|
+
if (action.startsWith("po_"))
|
|
243
|
+
return { tool: "purchase_orders", action: action.slice(3) };
|
|
244
|
+
if (action.startsWith("transfer_"))
|
|
245
|
+
return { tool: "transfers", action: action.slice(9) };
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Validate tool args before dispatch to handler.
|
|
250
|
+
*
|
|
251
|
+
* Returns `{ valid: true, data }` with parsed (coerced) data on success,
|
|
252
|
+
* or `{ valid: false, error }` with a human-readable message on failure.
|
|
253
|
+
*
|
|
254
|
+
* If no schema is registered for the (tool, action) pair, returns passthrough
|
|
255
|
+
* with `{ valid: true, data: args }` — no validation, no blocking.
|
|
256
|
+
*/
|
|
257
|
+
export function validateToolArgs(toolName, args) {
|
|
258
|
+
const action = args.action;
|
|
259
|
+
if (!action) {
|
|
260
|
+
// No action param = passthrough (some tools don't use action)
|
|
261
|
+
return { valid: true, data: args };
|
|
262
|
+
}
|
|
263
|
+
let schemaMap;
|
|
264
|
+
if (toolName === "supply_chain") {
|
|
265
|
+
const resolved = resolveSupplyChainAction(action);
|
|
266
|
+
if (resolved) {
|
|
267
|
+
schemaMap = toolSchemaRegistry[resolved.tool];
|
|
268
|
+
// Look up by the resolved action (e.g. "create" not "po_create")
|
|
269
|
+
const schema = schemaMap?.[resolved.action];
|
|
270
|
+
if (!schema)
|
|
271
|
+
return { valid: true, data: args };
|
|
272
|
+
// For supply_chain, we validate using the inner action name
|
|
273
|
+
// Rebuild args with the inner action for schema parsing
|
|
274
|
+
const innerArgs = { ...args, action: resolved.action };
|
|
275
|
+
return runSchema(schema, innerArgs, args);
|
|
276
|
+
}
|
|
277
|
+
// Unknown supply_chain action (e.g. find_suppliers) — passthrough
|
|
278
|
+
return { valid: true, data: args };
|
|
279
|
+
}
|
|
280
|
+
schemaMap = toolSchemaRegistry[toolName];
|
|
281
|
+
if (!schemaMap)
|
|
282
|
+
return { valid: true, data: args };
|
|
283
|
+
const schema = schemaMap[action];
|
|
284
|
+
if (!schema)
|
|
285
|
+
return { valid: true, data: args };
|
|
286
|
+
return runSchema(schema, args, args);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Parse args against schema. On success, merge parsed data back onto original
|
|
290
|
+
* args so extra fields (not in schema) are preserved for the handler.
|
|
291
|
+
*/
|
|
292
|
+
function runSchema(schema, parseTarget, originalArgs) {
|
|
293
|
+
const result = schema.safeParse(parseTarget);
|
|
294
|
+
if (!result.success) {
|
|
295
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
296
|
+
return { valid: false, error: `Validation failed: ${issues.join(", ")}` };
|
|
297
|
+
}
|
|
298
|
+
// Merge parsed data back with original args so extra passthrough fields survive
|
|
299
|
+
const parsed = result.data;
|
|
300
|
+
return { valid: true, data: { ...originalArgs, ...parsed } };
|
|
301
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worker.ts — Standalone workflow worker process
|
|
3
|
+
*
|
|
4
|
+
* Phase 3.2: Separates workflow step execution from the HTTP server.
|
|
5
|
+
* Deploy as a separate Fly.io process group so long-running agent conversations
|
|
6
|
+
* on the web process don't compete with step execution.
|
|
7
|
+
*
|
|
8
|
+
* Both processes use claim_step_for_run RPC with FOR UPDATE SKIP LOCKED —
|
|
9
|
+
* no double-claiming even if both web + worker run simultaneously.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node dist/server/worker.js
|
|
13
|
+
*
|
|
14
|
+
* Fly.io config:
|
|
15
|
+
* [processes]
|
|
16
|
+
* web = "node dist/server/index.js"
|
|
17
|
+
* worker = "node dist/server/worker.js"
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worker.ts — Standalone workflow worker process
|
|
3
|
+
*
|
|
4
|
+
* Phase 3.2: Separates workflow step execution from the HTTP server.
|
|
5
|
+
* Deploy as a separate Fly.io process group so long-running agent conversations
|
|
6
|
+
* on the web process don't compete with step execution.
|
|
7
|
+
*
|
|
8
|
+
* Both processes use claim_step_for_run RPC with FOR UPDATE SKIP LOCKED —
|
|
9
|
+
* no double-claiming even if both web + worker run simultaneously.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node dist/server/worker.js
|
|
13
|
+
*
|
|
14
|
+
* Fly.io config:
|
|
15
|
+
* [processes]
|
|
16
|
+
* web = "node dist/server/index.js"
|
|
17
|
+
* worker = "node dist/server/worker.js"
|
|
18
|
+
*/
|
|
19
|
+
import pg from "pg";
|
|
20
|
+
import { createClient } from "@supabase/supabase-js";
|
|
21
|
+
import { processWorkflowSteps, processWaitingSteps, initWorkerPool, shutdownPool, processScheduleTriggers, enforceWorkflowTimeouts, processEventTriggers, cleanupOrphanedSteps, processDlqRetries, setToolExecutor, } from "./handlers/workflows.js";
|
|
22
|
+
import { executeTool, loadTools } from "./tool-router.js";
|
|
23
|
+
import { createLogger } from "./lib/logger.js";
|
|
24
|
+
const log = createLogger("worker");
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// CONFIG
|
|
27
|
+
// ============================================================================
|
|
28
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || "";
|
|
29
|
+
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
30
|
+
const DATABASE_URL = process.env.DATABASE_URL || "";
|
|
31
|
+
const BASE_INTERVAL_MS = 15_000; // Safety net interval (NOTIFY is the fast path)
|
|
32
|
+
const MAX_INTERVAL_MS = 60_000;
|
|
33
|
+
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
|
|
34
|
+
log.fatal("SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// SUPABASE CLIENT
|
|
39
|
+
// ============================================================================
|
|
40
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
|
|
41
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
42
|
+
});
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// WORKER LOOP
|
|
45
|
+
// ============================================================================
|
|
46
|
+
let running = false;
|
|
47
|
+
let notifyProcessing = false;
|
|
48
|
+
let consecutiveErrors = 0;
|
|
49
|
+
let currentInterval = BASE_INTERVAL_MS;
|
|
50
|
+
let intervalHandle;
|
|
51
|
+
async function workerTick() {
|
|
52
|
+
if (running || notifyProcessing)
|
|
53
|
+
return;
|
|
54
|
+
running = true;
|
|
55
|
+
try {
|
|
56
|
+
const [stepResult, waitingResolved] = await Promise.all([
|
|
57
|
+
processWorkflowSteps(supabase, 10),
|
|
58
|
+
processWaitingSteps(supabase),
|
|
59
|
+
Promise.resolve(supabase.rpc("expire_pending_waitpoints")).then(() => { }).catch((e) => log.warn({ err: e.message }, "expire_pending_waitpoints failed")),
|
|
60
|
+
]);
|
|
61
|
+
const [scheduled, timedOut, eventsProcessed, orphansCleaned, dlqRetried] = await Promise.all([
|
|
62
|
+
processScheduleTriggers(supabase).catch((e) => { log.warn({ err: e.message }, "schedule trigger failed"); return 0; }),
|
|
63
|
+
enforceWorkflowTimeouts(supabase).catch((e) => { log.warn({ err: e.message }, "timeout enforcement failed"); return 0; }),
|
|
64
|
+
processEventTriggers(supabase).catch((e) => { log.warn({ err: e.message }, "event trigger failed"); return 0; }),
|
|
65
|
+
cleanupOrphanedSteps(supabase).catch((e) => { log.warn({ err: e.message }, "orphan cleanup failed"); return 0; }),
|
|
66
|
+
processDlqRetries(supabase).catch((e) => { log.warn({ err: e.message }, "DLQ retry failed"); return 0; }),
|
|
67
|
+
]);
|
|
68
|
+
if (stepResult.processed > 0 || waitingResolved > 0 || scheduled > 0 || timedOut > 0 || eventsProcessed > 0 || orphansCleaned > 0 || dlqRetried > 0) {
|
|
69
|
+
log.info({
|
|
70
|
+
processed: stepResult.processed, errors: stepResult.errors,
|
|
71
|
+
reclaimed: stepResult.reclaimed || 0, waiting: waitingResolved,
|
|
72
|
+
scheduled, timedOut, events: eventsProcessed, orphans: orphansCleaned, dlqRetries: dlqRetried,
|
|
73
|
+
}, "worker tick");
|
|
74
|
+
}
|
|
75
|
+
// Reset backoff on success
|
|
76
|
+
if (consecutiveErrors > 0) {
|
|
77
|
+
consecutiveErrors = 0;
|
|
78
|
+
if (currentInterval !== BASE_INTERVAL_MS) {
|
|
79
|
+
currentInterval = BASE_INTERVAL_MS;
|
|
80
|
+
clearInterval(intervalHandle);
|
|
81
|
+
intervalHandle = setInterval(workerTick, currentInterval);
|
|
82
|
+
log.info({ intervalMs: currentInterval }, "interval reset after recovery");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
consecutiveErrors++;
|
|
88
|
+
log.error({ err: err.message, consecutiveErrors }, "worker error");
|
|
89
|
+
if (consecutiveErrors >= 3) {
|
|
90
|
+
const newInterval = Math.min(BASE_INTERVAL_MS * Math.pow(2, consecutiveErrors - 2), MAX_INTERVAL_MS);
|
|
91
|
+
if (newInterval !== currentInterval) {
|
|
92
|
+
currentInterval = newInterval;
|
|
93
|
+
clearInterval(intervalHandle);
|
|
94
|
+
intervalHandle = setInterval(workerTick, currentInterval);
|
|
95
|
+
log.warn({ intervalMs: currentInterval, consecutiveErrors }, "interval increased due to errors");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
running = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// NOTIFY LISTENER (fast path)
|
|
105
|
+
// ============================================================================
|
|
106
|
+
let pgClient = null;
|
|
107
|
+
async function setupPgListen() {
|
|
108
|
+
if (!DATABASE_URL) {
|
|
109
|
+
log.warn("no DATABASE_URL — NOTIFY-driven execution disabled, polling only");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const cleanUrl = DATABASE_URL.replace(/[?&]sslmode=[^&]*/g, "").replace(/\?$/, "");
|
|
114
|
+
pgClient = new pg.Client({ connectionString: cleanUrl, ssl: { rejectUnauthorized: false } });
|
|
115
|
+
await pgClient.connect();
|
|
116
|
+
await pgClient.query("LISTEN workflow_step_pending");
|
|
117
|
+
// P1 FIX: Debounce NOTIFY to prevent thundering herd when many steps
|
|
118
|
+
// become pending simultaneously (e.g., for_each expansion).
|
|
119
|
+
let notifyTimer = null;
|
|
120
|
+
let notifyPending = 0;
|
|
121
|
+
pgClient.on("notification", (msg) => {
|
|
122
|
+
if (msg.channel === "workflow_step_pending") {
|
|
123
|
+
notifyPending++;
|
|
124
|
+
if (!notifyTimer) {
|
|
125
|
+
notifyTimer = setTimeout(() => {
|
|
126
|
+
const batch = Math.min(notifyPending, 10);
|
|
127
|
+
notifyPending = 0;
|
|
128
|
+
notifyTimer = null;
|
|
129
|
+
if (notifyProcessing || running)
|
|
130
|
+
return; // Skip if already processing
|
|
131
|
+
notifyProcessing = true;
|
|
132
|
+
processWorkflowSteps(supabase, batch)
|
|
133
|
+
.catch((err) => {
|
|
134
|
+
log.error({ err: err.message }, "NOTIFY step processing failed");
|
|
135
|
+
})
|
|
136
|
+
.finally(() => { notifyProcessing = false; });
|
|
137
|
+
}, 50); // 50ms debounce — still fast, but batches concurrent notifications
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
pgClient.on("error", (err) => {
|
|
142
|
+
log.error({ err: err.message }, "pg-listen connection error");
|
|
143
|
+
pgClient = null;
|
|
144
|
+
setTimeout(() => setupPgListen(), 5000);
|
|
145
|
+
});
|
|
146
|
+
log.info("pg LISTEN active on workflow_step_pending");
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
log.error({ err: err.message }, "pg-listen failed");
|
|
150
|
+
pgClient = null;
|
|
151
|
+
setTimeout(() => setupPgListen(), 5000);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// STARTUP + SHUTDOWN
|
|
156
|
+
// ============================================================================
|
|
157
|
+
async function start() {
|
|
158
|
+
log.info("starting workflow worker process");
|
|
159
|
+
// Wire tool executor for workflow steps that need tool calls
|
|
160
|
+
setToolExecutor((sb, toolName, args, storeId, traceId) => executeTool(sb, toolName, args, storeId, traceId));
|
|
161
|
+
// Load tools registry
|
|
162
|
+
await loadTools(supabase);
|
|
163
|
+
// Initialize code worker pool
|
|
164
|
+
try {
|
|
165
|
+
initWorkerPool();
|
|
166
|
+
log.info("worker pool initialized");
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
log.error({ err: err.message }, "worker pool init failed");
|
|
170
|
+
}
|
|
171
|
+
// Start NOTIFY listener
|
|
172
|
+
await setupPgListen();
|
|
173
|
+
// Start polling loop (safety net)
|
|
174
|
+
intervalHandle = setInterval(workerTick, currentInterval);
|
|
175
|
+
log.info({ intervalMs: currentInterval }, "worker loop started");
|
|
176
|
+
// Run first tick immediately
|
|
177
|
+
workerTick();
|
|
178
|
+
}
|
|
179
|
+
function shutdown(signal) {
|
|
180
|
+
log.info({ signal }, "worker shutting down");
|
|
181
|
+
clearInterval(intervalHandle);
|
|
182
|
+
try {
|
|
183
|
+
shutdownPool();
|
|
184
|
+
}
|
|
185
|
+
catch { /* ignore */ }
|
|
186
|
+
if (pgClient) {
|
|
187
|
+
pgClient.end().catch(() => { });
|
|
188
|
+
pgClient = null;
|
|
189
|
+
}
|
|
190
|
+
// Give in-flight work 10s to finish
|
|
191
|
+
setTimeout(() => process.exit(0), 10_000).unref();
|
|
192
|
+
}
|
|
193
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
194
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
195
|
+
process.on("unhandledRejection", (reason) => {
|
|
196
|
+
log.error({ err: reason }, "unhandled rejection in worker");
|
|
197
|
+
});
|
|
198
|
+
start().catch((err) => {
|
|
199
|
+
log.fatal({ err: err.message }, "worker failed to start");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|