whale-code 6.4.0 → 6.5.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/bin/swagmanager-mcp.js +7 -0
- package/dist/cli/app.js +30 -2
- package/dist/cli/chat/ChatApp.d.ts +4 -4
- package/dist/cli/chat/ChatApp.js +114 -44
- package/dist/cli/chat/ChatInput.d.ts +13 -6
- package/dist/cli/chat/ChatInput.js +433 -89
- package/dist/cli/chat/MemoryManager.d.ts +15 -0
- package/dist/cli/chat/MemoryManager.js +61 -0
- package/dist/cli/chat/MessageList.d.ts +8 -0
- package/dist/cli/chat/MessageList.js +1 -1
- package/dist/cli/chat/NodeManager.d.ts +30 -0
- package/dist/cli/chat/NodeManager.js +89 -0
- package/dist/cli/chat/NodeSelector.d.ts +19 -0
- package/dist/cli/chat/NodeSelector.js +37 -0
- package/dist/cli/chat/PlanApproval.d.ts +17 -0
- package/dist/cli/chat/PlanApproval.js +82 -0
- package/dist/cli/chat/SessionManager.d.ts +16 -0
- package/dist/cli/chat/SessionManager.js +43 -0
- package/dist/cli/chat/SlashMenu.d.ts +38 -0
- package/dist/cli/chat/SlashMenu.js +208 -0
- package/dist/cli/chat/StatusBar.d.ts +16 -0
- package/dist/cli/chat/StatusBar.js +22 -0
- package/dist/cli/chat/ThemeSelector.d.ts +14 -0
- package/dist/cli/chat/ThemeSelector.js +29 -0
- package/dist/cli/chat/ToolIndicator.d.ts +8 -0
- package/dist/cli/chat/ToolIndicator.js +33 -9
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -1
- package/dist/cli/chat/hooks/useAgentLoop.js +22 -17
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +19 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +254 -15
- package/dist/cli/commands/config-cmd.js +4 -25
- package/dist/cli/commands/db.d.ts +13 -0
- package/dist/cli/commands/db.js +243 -0
- package/dist/cli/commands/doctor.js +6 -9
- package/dist/cli/commands/mcp.js +1 -20
- package/dist/cli/services/agent-events.d.ts +22 -1
- package/dist/cli/services/agent-events.js +9 -0
- package/dist/cli/services/agent-loop.js +66 -2
- package/dist/cli/services/agent-worker-base.js +21 -6
- package/dist/cli/services/api-retry.d.ts +25 -0
- package/dist/cli/services/api-retry.js +91 -0
- package/dist/cli/services/auth-service.d.ts +1 -1
- package/dist/cli/services/auth-service.js +40 -19
- package/dist/cli/services/background-processes.js +26 -2
- package/dist/cli/services/config-store.d.ts +13 -1
- package/dist/cli/services/config-store.js +116 -13
- package/dist/cli/services/format-server-response.js +12 -6
- package/dist/cli/services/ink-resize-fix.d.ts +18 -0
- package/dist/cli/services/ink-resize-fix.js +66 -0
- package/dist/cli/services/interactive-tools.d.ts +14 -0
- package/dist/cli/services/interactive-tools.js +47 -2
- package/dist/cli/services/keybinding-manager.js +1 -1
- package/dist/cli/services/local-tools.js +35 -2
- package/dist/cli/services/server-tools.js +175 -3
- package/dist/cli/services/subagent.js +15 -3
- package/dist/cli/services/system-prompt.js +5 -3
- package/dist/cli/services/task-decomposer.d.ts +35 -0
- package/dist/cli/services/task-decomposer.js +199 -0
- package/dist/cli/services/team-lead.d.ts +18 -0
- package/dist/cli/services/team-lead.js +80 -0
- package/dist/cli/services/teammate.js +5 -5
- package/dist/cli/services/telemetry.d.ts +8 -2
- package/dist/cli/services/telemetry.js +116 -92
- package/dist/cli/services/tools/agent-tools.d.ts +1 -0
- package/dist/cli/services/tools/agent-tools.js +50 -4
- package/dist/cli/services/tools/file-ops.d.ts +2 -0
- package/dist/cli/services/tools/file-ops.js +71 -19
- package/dist/cli/services/tools/shell-exec.js +22 -12
- package/dist/cli/shared/Theme.d.ts +1 -2
- package/dist/cli/shared/Theme.js +1 -1
- package/dist/cli/shared/WhaleBanner.d.ts +4 -1
- package/dist/cli/shared/WhaleBanner.js +12 -8
- package/dist/cli/shared/markdown.d.ts +5 -4
- package/dist/cli/shared/markdown.js +376 -334
- package/dist/cli/shared/theme-manager.d.ts +27 -0
- package/dist/cli/shared/theme-manager.js +178 -0
- package/dist/cli/shared/theme-presets.d.ts +16 -0
- package/dist/cli/shared/theme-presets.js +265 -0
- package/dist/index.js +0 -51
- package/dist/node/adapters/imessage.d.ts +10 -0
- package/dist/node/adapters/imessage.js +45 -6
- package/dist/node/cli.js +459 -8
- package/dist/node/config.d.ts +17 -0
- package/dist/node/gateway-client.d.ts +55 -0
- package/dist/node/gateway-client.js +201 -0
- package/dist/node/portal/clipboard.d.ts +28 -0
- package/dist/node/portal/clipboard.js +183 -0
- package/dist/node/portal/discovery.d.ts +29 -0
- package/dist/node/portal/discovery.js +61 -0
- package/dist/node/portal/forward.d.ts +30 -0
- package/dist/node/portal/forward.js +90 -0
- package/dist/node/portal/index.d.ts +47 -0
- package/dist/node/portal/index.js +250 -0
- package/dist/node/portal/multiplexer.d.ts +48 -0
- package/dist/node/portal/multiplexer.js +207 -0
- package/dist/node/portal/permissions.d.ts +36 -0
- package/dist/node/portal/permissions.js +131 -0
- package/dist/node/portal/protocol.d.ts +140 -0
- package/dist/node/portal/protocol.js +193 -0
- package/dist/node/portal/screen.d.ts +18 -0
- package/dist/node/portal/screen.js +93 -0
- package/dist/node/portal/session.d.ts +68 -0
- package/dist/node/portal/session.js +127 -0
- package/dist/node/portal/shell.d.ts +26 -0
- package/dist/node/portal/shell.js +142 -0
- package/dist/node/portal/stream.d.ts +43 -0
- package/dist/node/portal/stream.js +90 -0
- package/dist/node/portal/transfer.d.ts +33 -0
- package/dist/node/portal/transfer.js +231 -0
- package/dist/node/portal/ui.d.ts +16 -0
- package/dist/node/portal/ui.js +148 -0
- package/dist/node/remote-desktop/compile-helper.d.ts +13 -0
- package/dist/node/remote-desktop/compile-helper.js +73 -0
- package/dist/node/remote-desktop/index.d.ts +67 -0
- package/dist/node/remote-desktop/index.js +220 -0
- package/dist/node/remote-desktop/protocol.d.ts +96 -0
- package/dist/node/remote-desktop/protocol.js +67 -0
- package/dist/node/runtime.d.ts +8 -1
- package/dist/node/runtime.js +117 -9
- package/dist/server/handlers/__test-utils__/test-db.d.ts +25 -0
- package/dist/server/handlers/__test-utils__/test-db.js +128 -0
- package/dist/server/handlers/api-keys.js +26 -2
- package/dist/server/handlers/browser.d.ts +0 -4
- package/dist/server/handlers/browser.js +0 -46
- package/dist/server/handlers/catalog.js +37 -14
- package/dist/server/handlers/clickhouse.d.ts +10 -0
- package/dist/server/handlers/clickhouse.js +215 -0
- package/dist/server/handlers/comms.d.ts +308 -4
- package/dist/server/handlers/comms.js +444 -11
- package/dist/server/handlers/creations.js +1 -1
- package/dist/server/handlers/crm.d.ts +54 -8
- package/dist/server/handlers/crm.js +353 -68
- package/dist/server/handlers/embeddings.js +3 -3
- package/dist/server/handlers/enrichment.js +39 -55
- package/dist/server/handlers/inventory.js +1 -1
- package/dist/server/handlers/kali.d.ts +9 -1
- package/dist/server/handlers/kali.js +50 -1
- package/dist/server/handlers/media.d.ts +8 -0
- package/dist/server/handlers/media.js +902 -0
- package/dist/server/handlers/meta-ads.js +6 -3
- package/dist/server/handlers/nodes.d.ts +2 -0
- package/dist/server/handlers/nodes.js +331 -40
- package/dist/server/handlers/operations.d.ts +4 -6
- package/dist/server/handlers/operations.js +99 -38
- package/dist/server/handlers/platform.js +224 -107
- package/dist/server/handlers/remove-bg.d.ts +6 -0
- package/dist/server/handlers/remove-bg.js +96 -0
- package/dist/server/handlers/storefront.d.ts +6 -0
- package/dist/server/handlers/storefront.js +477 -0
- package/dist/server/handlers/supply-chain.js +21 -3
- package/dist/server/handlers/workflow-steps.js +87 -31
- package/dist/server/handlers/workflows.js +4 -1
- package/dist/server/index.js +334 -88
- package/dist/server/lib/clickhouse-buffer.d.ts +48 -0
- package/dist/server/lib/clickhouse-buffer.js +175 -0
- package/dist/server/lib/clickhouse-client.d.ts +112 -0
- package/dist/server/lib/clickhouse-client.js +141 -0
- package/dist/server/lib/coa-renderer.d.ts +91 -0
- package/dist/server/lib/coa-renderer.js +411 -0
- package/dist/server/lib/compaction-service.js +45 -1
- package/dist/server/lib/pdf-renderer.d.ts +143 -0
- package/dist/server/lib/pdf-renderer.js +867 -0
- package/dist/server/lib/react-pdf-layout.d.ts +40 -0
- package/dist/server/lib/react-pdf-layout.js +437 -0
- package/dist/server/lib/server-agent-loop.d.ts +2 -0
- package/dist/server/lib/server-agent-loop.js +61 -15
- package/dist/server/lib/server-subagent.d.ts +3 -0
- package/dist/server/lib/server-subagent.js +7 -4
- package/dist/server/lib/supabase-client.js +51 -3
- package/dist/server/lib/template-resolver.js +14 -4
- package/dist/server/lib/utils.js +15 -0
- package/dist/server/local-agent-gateway.d.ts +44 -0
- package/dist/server/local-agent-gateway.js +389 -49
- package/dist/server/providers/anthropic.js +12 -2
- package/dist/server/providers/gemini.js +17 -2
- package/dist/server/proxy-handlers.js +151 -0
- package/dist/server/tool-router.d.ts +2 -2
- package/dist/server/tool-router.js +25 -35
- package/dist/shared/agent-core.d.ts +5 -2
- package/dist/shared/agent-core.js +30 -4
- package/dist/shared/api-client.js +54 -3
- package/dist/shared/sse-parser.d.ts +1 -1
- package/dist/shared/sse-parser.js +5 -2
- package/dist/shared/tool-dispatch.js +1 -1
- package/package.json +16 -10
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +0 -11
- package/dist/server/handlers/__test-utils__/mock-supabase.js +0 -393
|
@@ -20,6 +20,151 @@ const GEMINI_MAX_TOKENS = 65536;
|
|
|
20
20
|
const OPENAI_MAX_TOKENS = 128000;
|
|
21
21
|
const OPENAI_REASONING_MAX_TOKENS = 100000;
|
|
22
22
|
// ============================================================================
|
|
23
|
+
// IMAGE MIME TYPE CORRECTION
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Detects actual image format from base64 magic bytes and corrects media_type
|
|
26
|
+
// mismatches. Prevents Anthropic API 400 errors that permanently poison the
|
|
27
|
+
// conversation when a file extension doesn't match the actual image content
|
|
28
|
+
// (e.g. a .webp file that's actually PNG).
|
|
29
|
+
const IMAGE_SIGNATURES = [
|
|
30
|
+
{ prefix: "iVBORw0KGgo", type: "image/png" },
|
|
31
|
+
{ prefix: "/9j/", type: "image/jpeg" },
|
|
32
|
+
{ prefix: "R0lGOD", type: "image/gif" },
|
|
33
|
+
{ prefix: "UklGR", type: "image/webp" },
|
|
34
|
+
];
|
|
35
|
+
function detectImageType(base64Data) {
|
|
36
|
+
for (const sig of IMAGE_SIGNATURES) {
|
|
37
|
+
if (base64Data.startsWith(sig.prefix)) {
|
|
38
|
+
// RIFF header could be WebP or other formats — verify WEBP marker at offset 8
|
|
39
|
+
if (sig.prefix === "UklGR") {
|
|
40
|
+
try {
|
|
41
|
+
const buf = Buffer.from(base64Data.slice(0, 24), "base64");
|
|
42
|
+
if (buf.length >= 12 && buf.toString("ascii", 8, 12) === "WEBP") {
|
|
43
|
+
return "image/webp";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return sig.type;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function fixImageBlock(block) {
|
|
57
|
+
if (block.type === "image" && block.source?.type === "base64" && block.source?.data) {
|
|
58
|
+
const actual = detectImageType(block.source.data);
|
|
59
|
+
if (actual && actual !== block.source.media_type) {
|
|
60
|
+
console.log(`[proxy] Fixed image MIME: declared=${block.source.media_type} actual=${actual}`);
|
|
61
|
+
block.source.media_type = actual;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function fixImageMimeTypes(messages) {
|
|
68
|
+
let fixes = 0;
|
|
69
|
+
for (const msg of messages) {
|
|
70
|
+
if (!Array.isArray(msg.content))
|
|
71
|
+
continue;
|
|
72
|
+
for (const block of msg.content) {
|
|
73
|
+
if (fixImageBlock(block))
|
|
74
|
+
fixes++;
|
|
75
|
+
// Also check tool_result blocks — images from Read tool end up here
|
|
76
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
77
|
+
for (const sub of block.content) {
|
|
78
|
+
if (fixImageBlock(sub))
|
|
79
|
+
fixes++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (fixes > 0) {
|
|
85
|
+
console.log(`[proxy] Corrected ${fixes} image MIME type(s)`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// IMAGE PRUNING — strip base64 from old turns to keep conversation size bounded
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Conversation history with many images grows unboundedly. We keep full image
|
|
92
|
+
// data only in the most recent user turn; older turns get their base64 replaced
|
|
93
|
+
// with a lightweight "[image]" text placeholder. The model already processed
|
|
94
|
+
// those images — it doesn't need the raw bytes again.
|
|
95
|
+
function replaceImageBlock(block) {
|
|
96
|
+
// Replace a base64 image block with a text placeholder.
|
|
97
|
+
// Matches both full-data blocks AND blocks whose data was zeroed by the raw pruner.
|
|
98
|
+
if (block.type === "image" && block.source?.type === "base64") {
|
|
99
|
+
block.type = "text";
|
|
100
|
+
block.text = "[image]";
|
|
101
|
+
delete block.source;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
function pruneOldImages(messages) {
|
|
107
|
+
// Find index of last user message — that turn keeps full image data
|
|
108
|
+
let lastUserIdx = -1;
|
|
109
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
110
|
+
if (messages[i].role === "user") {
|
|
111
|
+
lastUserIdx = i;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
let pruned = 0;
|
|
116
|
+
for (let i = 0; i < messages.length; i++) {
|
|
117
|
+
if (i === lastUserIdx)
|
|
118
|
+
continue; // keep latest user turn intact
|
|
119
|
+
const msg = messages[i];
|
|
120
|
+
if (!Array.isArray(msg.content))
|
|
121
|
+
continue;
|
|
122
|
+
for (const block of msg.content) {
|
|
123
|
+
if (replaceImageBlock(block)) {
|
|
124
|
+
pruned++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
128
|
+
for (const sub of block.content) {
|
|
129
|
+
if (replaceImageBlock(sub))
|
|
130
|
+
pruned++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (pruned > 0)
|
|
136
|
+
console.log(`[proxy] Pruned ${pruned} base64 image(s) from old conversation turns`);
|
|
137
|
+
}
|
|
138
|
+
/** Remove any image blocks with empty/missing data — left by the raw body pruner.
|
|
139
|
+
* These would cause a 400 from Anthropic regardless of which turn they're in. */
|
|
140
|
+
function removeEmptyImageBlocks(messages) {
|
|
141
|
+
let removed = 0;
|
|
142
|
+
for (const msg of messages) {
|
|
143
|
+
if (!Array.isArray(msg.content))
|
|
144
|
+
continue;
|
|
145
|
+
for (const block of msg.content) {
|
|
146
|
+
if (block.type === "image" && block.source?.type === "base64" && !block.source?.data) {
|
|
147
|
+
block.type = "text";
|
|
148
|
+
block.text = "[image]";
|
|
149
|
+
delete block.source;
|
|
150
|
+
removed++;
|
|
151
|
+
}
|
|
152
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
153
|
+
for (const sub of block.content) {
|
|
154
|
+
if (sub.type === "image" && sub.source?.type === "base64" && !sub.source?.data) {
|
|
155
|
+
sub.type = "text";
|
|
156
|
+
sub.text = "[image]";
|
|
157
|
+
delete sub.source;
|
|
158
|
+
removed++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (removed > 0)
|
|
165
|
+
console.log(`[proxy] Removed ${removed} empty image block(s) from messages`);
|
|
166
|
+
}
|
|
167
|
+
// ============================================================================
|
|
23
168
|
// MAIN PROXY DISPATCHER
|
|
24
169
|
// ============================================================================
|
|
25
170
|
export async function handleProxy(res, body, corsHeaders) {
|
|
@@ -28,6 +173,12 @@ export async function handleProxy(res, body, corsHeaders) {
|
|
|
28
173
|
jsonResponse(res, 400, { error: "messages array required" }, corsHeaders);
|
|
29
174
|
return;
|
|
30
175
|
}
|
|
176
|
+
// Fix mismatched image MIME types before they hit the API
|
|
177
|
+
fixImageMimeTypes(messages);
|
|
178
|
+
// Strip base64 data from old turns — keeps request size bounded across long conversations
|
|
179
|
+
pruneOldImages(messages);
|
|
180
|
+
// Remove empty image blocks left by the raw body pruner (data:"") — would cause Anthropic 400
|
|
181
|
+
removeEmptyImageBlocks(messages);
|
|
31
182
|
// Resolve aliases (e.g. "whale/agent" → "claude-opus-4-6", "opus" → "claude-opus-4-6")
|
|
32
183
|
const resolved = MODEL_MAP[requestedModel] || requestedModel;
|
|
33
184
|
const model = ALLOWED_MODELS.includes(resolved) ? resolved : MODELS.SONNET;
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* That's it — MCP, WhaleChat, workflows, CLI all pick it up automatically.
|
|
18
18
|
*/
|
|
19
19
|
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
20
|
-
|
|
20
|
+
import { flushSpans } from "./lib/clickhouse-buffer.js";
|
|
21
|
+
export { flushSpans };
|
|
21
22
|
export declare function getToolMetrics(): Record<string, {
|
|
22
23
|
invocations: number;
|
|
23
24
|
errors: number;
|
|
@@ -146,4 +147,3 @@ skipAudit?: boolean): Promise<{
|
|
|
146
147
|
error?: string;
|
|
147
148
|
}>;
|
|
148
149
|
export declare function loadAgentConfig(supabase: SupabaseClient, agentId: string, storeId?: string): Promise<AgentConfig | null>;
|
|
149
|
-
export {};
|
|
@@ -35,6 +35,8 @@ import { handleWorkflows } from "./handlers/workflows.js";
|
|
|
35
35
|
import { handleEmbeddings } from "./handlers/embeddings.js";
|
|
36
36
|
import { handleLLM } from "./handlers/llm-providers.js";
|
|
37
37
|
import { handleImageGen } from "./handlers/image-gen.js";
|
|
38
|
+
import { handleRemoveBg } from "./handlers/remove-bg.js";
|
|
39
|
+
import { handleMedia } from "./handlers/media.js";
|
|
38
40
|
import { handleVideoGen } from "./handlers/video-gen.js";
|
|
39
41
|
import { handleAPIKeys } from "./handlers/api-keys.js";
|
|
40
42
|
import { handleCreations } from "./handlers/creations.js";
|
|
@@ -42,39 +44,15 @@ import { handleMetaAds } from "./handlers/meta-ads.js";
|
|
|
42
44
|
import { handleKali } from "./handlers/kali.js";
|
|
43
45
|
import { handleLocalAgent } from "./handlers/local-agent.js";
|
|
44
46
|
import { handleEnrichment } from "./handlers/enrichment.js";
|
|
47
|
+
import { handleStorefront } from "./handlers/storefront.js";
|
|
48
|
+
import { handleClickHouse } from "./handlers/clickhouse.js";
|
|
45
49
|
import { summarizeResult, withTimeout } from "./lib/utils.js";
|
|
46
50
|
// ============================================================================
|
|
47
|
-
//
|
|
51
|
+
// TELEMETRY — ClickHouse span buffer (replaces Postgres audit_logs buffer)
|
|
48
52
|
// ============================================================================
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let auditFlushTimer = null;
|
|
53
|
-
export async function flushAuditLogs(supabase) {
|
|
54
|
-
if (auditLogBuffer.length === 0)
|
|
55
|
-
return;
|
|
56
|
-
const batch = auditLogBuffer.splice(0, auditLogBuffer.length);
|
|
57
|
-
try {
|
|
58
|
-
const { error } = await supabase.from("audit_logs").insert(batch);
|
|
59
|
-
if (error)
|
|
60
|
-
console.error("[audit-batch] flush error:", error.message, "lost", batch.length, "records");
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
console.error("[audit-batch] flush exception:", err.message, "lost", batch.length, "records");
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function queueAuditLog(supabase, row) {
|
|
67
|
-
auditLogBuffer.push(row);
|
|
68
|
-
if (auditLogBuffer.length >= AUDIT_FLUSH_MAX) {
|
|
69
|
-
flushAuditLogs(supabase);
|
|
70
|
-
}
|
|
71
|
-
else if (!auditFlushTimer) {
|
|
72
|
-
auditFlushTimer = setTimeout(() => {
|
|
73
|
-
auditFlushTimer = null;
|
|
74
|
-
flushAuditLogs(supabase);
|
|
75
|
-
}, AUDIT_FLUSH_INTERVAL);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
53
|
+
import { queueSpan, flushSpans, auditRowToSpan, classifyErrorType } from "./lib/clickhouse-buffer.js";
|
|
54
|
+
// Re-export for callers that used flushAuditLogs (e.g. index.ts shutdown)
|
|
55
|
+
export { flushSpans };
|
|
78
56
|
// ============================================================================
|
|
79
57
|
// IN-MEMORY EXECUTION METRICS
|
|
80
58
|
// ============================================================================
|
|
@@ -299,9 +277,10 @@ export const TOOL_HANDLERS = {
|
|
|
299
277
|
locations: { handler: handleLocations, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
300
278
|
suppliers: { handler: handleSuppliers, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
301
279
|
store: { handler: handleStore, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
280
|
+
storefront: { handler: handleStorefront, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
302
281
|
// --- Communication ---
|
|
303
282
|
email: { handler: handleEmail, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
304
|
-
documents: { handler: handleDocuments, timeout:
|
|
283
|
+
documents: { handler: handleDocuments, timeout: 120_000, requiresStore: true },
|
|
305
284
|
// --- Operations ---
|
|
306
285
|
alerts: { handler: handleAlerts, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
307
286
|
audit_trail: { handler: handleAuditTrail, timeout: DEFAULT_TIMEOUT, requiresStore: true },
|
|
@@ -309,6 +288,8 @@ export const TOOL_HANDLERS = {
|
|
|
309
288
|
// --- AI & Generation ---
|
|
310
289
|
voice: { handler: handleVoice, timeout: 120_000, requiresStore: true },
|
|
311
290
|
image_gen: { handler: handleImageGen, timeout: 60_000, requiresStore: true },
|
|
291
|
+
remove_bg: { handler: handleRemoveBg, timeout: 60_000, requiresStore: true },
|
|
292
|
+
media: { handler: handleMedia, timeout: 60_000, requiresStore: true },
|
|
312
293
|
video_gen: { handler: handleVideoGen, timeout: 600_000, requiresStore: true },
|
|
313
294
|
llm: { handler: handleLLM, timeout: 120_000, requiresStore: true },
|
|
314
295
|
embeddings: { handler: handleEmbeddings, timeout: 60_000, requiresStore: true },
|
|
@@ -326,6 +307,8 @@ export const TOOL_HANDLERS = {
|
|
|
326
307
|
local_agent: { handler: handleLocalAgent, timeout: 600_000, requiresStore: false },
|
|
327
308
|
// --- Customer Data Protection ---
|
|
328
309
|
enrichment: { handler: handleEnrichment, timeout: 60_000, requiresStore: true },
|
|
310
|
+
// --- Observability (ClickHouse) ---
|
|
311
|
+
clickhouse: { handler: handleClickHouse, timeout: 60_000, requiresStore: false },
|
|
329
312
|
// --- Meta: Tool Discovery (lazy loading) ---
|
|
330
313
|
discover_tools: { handler: handleDiscoverTools, timeout: 5000, requiresStore: false },
|
|
331
314
|
};
|
|
@@ -377,14 +360,14 @@ const TOOL_CATEGORIES = {
|
|
|
377
360
|
inventory: "business", purchase_orders: "business", transfers: "business",
|
|
378
361
|
products: "business", collections: "business", customers: "business",
|
|
379
362
|
orders: "business", analytics: "business", locations: "business",
|
|
380
|
-
suppliers: "business", store: "business",
|
|
363
|
+
suppliers: "business", store: "business", storefront: "business",
|
|
381
364
|
// Communication
|
|
382
365
|
email: "communication", documents: "communication",
|
|
383
366
|
// Operations
|
|
384
367
|
alerts: "operations", audit_trail: "operations", workflows: "operations",
|
|
385
368
|
telemetry: "operations",
|
|
386
369
|
// Media & AI
|
|
387
|
-
voice: "media", image_gen: "media", video_gen: "media",
|
|
370
|
+
voice: "media", image_gen: "media", video_gen: "media", remove_bg: "media", media: "media",
|
|
388
371
|
llm: "ai", embeddings: "ai", creations: "media",
|
|
389
372
|
// Platform
|
|
390
373
|
web_search: "platform", browser: "platform", discovery: "platform",
|
|
@@ -750,6 +733,9 @@ skipAudit) {
|
|
|
750
733
|
}
|
|
751
734
|
if (result.error) {
|
|
752
735
|
details.tool_error = result.error;
|
|
736
|
+
const errorType = classifyErrorType(result.error);
|
|
737
|
+
if (errorType)
|
|
738
|
+
details.error_type = errorType;
|
|
753
739
|
}
|
|
754
740
|
const bytes = new Uint8Array(8);
|
|
755
741
|
crypto.getRandomValues(bytes);
|
|
@@ -765,9 +751,12 @@ skipAudit) {
|
|
|
765
751
|
source: source || "fly_container",
|
|
766
752
|
details,
|
|
767
753
|
error_message: result.error || null,
|
|
754
|
+
error_type: classifyErrorType(result.error) || undefined,
|
|
768
755
|
duration_ms: endTime - startTime,
|
|
769
756
|
user_id: userId || null,
|
|
770
757
|
user_email: userEmail || null,
|
|
758
|
+
input_bytes: inputBytes,
|
|
759
|
+
output_bytes: outputBytes,
|
|
771
760
|
// OTEL fields
|
|
772
761
|
trace_id: traceId || null,
|
|
773
762
|
span_id: spanId,
|
|
@@ -777,7 +766,7 @@ skipAudit) {
|
|
|
777
766
|
start_time: new Date(startTime).toISOString(),
|
|
778
767
|
end_time: new Date(endTime).toISOString(),
|
|
779
768
|
};
|
|
780
|
-
|
|
769
|
+
queueSpan(auditRowToSpan(auditRow));
|
|
781
770
|
}
|
|
782
771
|
catch (err) {
|
|
783
772
|
console.error("[audit] exception:", err);
|
|
@@ -788,11 +777,12 @@ skipAudit) {
|
|
|
788
777
|
// AGENT LOADER
|
|
789
778
|
// ============================================================================
|
|
790
779
|
export async function loadAgentConfig(supabase, agentId, storeId) {
|
|
780
|
+
// storeId is required for tenant isolation — only omit for internal/migration callers
|
|
791
781
|
let query = supabase
|
|
792
782
|
.from("ai_agent_config")
|
|
793
783
|
.select("*")
|
|
794
784
|
.eq("id", agentId);
|
|
795
|
-
//
|
|
785
|
+
// ALWAYS filter by store_id when provided (which should be always for user requests)
|
|
796
786
|
if (storeId) {
|
|
797
787
|
query = query.eq("store_id", storeId);
|
|
798
788
|
}
|
|
@@ -56,8 +56,8 @@ export interface ContextManagementConfig {
|
|
|
56
56
|
export declare const COMPACTION_TRIGGER_TOKENS = 120000;
|
|
57
57
|
/** Max cumulative tokens before forcing wrap-up (prevents runaway compaction cost) */
|
|
58
58
|
export declare const COMPACTION_TOTAL_BUDGET = 2000000;
|
|
59
|
-
/** Default session cost budget in USD —
|
|
60
|
-
export declare const DEFAULT_SESSION_COST_BUDGET_USD
|
|
59
|
+
/** Default session cost budget in USD — hard cap to prevent runaway spending */
|
|
60
|
+
export declare const DEFAULT_SESSION_COST_BUDGET_USD = 5;
|
|
61
61
|
/**
|
|
62
62
|
* Provider-aware compaction configuration.
|
|
63
63
|
* - Anthropic/Bedrock: native server-side compaction via compact_20260112
|
|
@@ -97,12 +97,15 @@ export declare class LoopDetector {
|
|
|
97
97
|
private failedStrategies;
|
|
98
98
|
private consecutiveFailedTurns;
|
|
99
99
|
private totalSessionErrors;
|
|
100
|
+
/** Tracks how many times the same file path has been read this session */
|
|
101
|
+
private fileReadCounts;
|
|
100
102
|
static IDENTICAL_CALL_LIMIT: number;
|
|
101
103
|
static CONSECUTIVE_ERROR_LIMIT: number;
|
|
102
104
|
static TURN_ERROR_LIMIT: number;
|
|
103
105
|
static WINDOW: number;
|
|
104
106
|
static SESSION_TOOL_ERROR_LIMIT: number;
|
|
105
107
|
static CONSECUTIVE_FAILED_TURN_LIMIT: number;
|
|
108
|
+
static FILE_READ_LIMIT: number;
|
|
106
109
|
/** Get the error-tracking key for a tool call. Tools with an `action` param
|
|
107
110
|
* are tracked per-action so e.g. voice/speak failing won't block voice/music_compose. */
|
|
108
111
|
private errorKey;
|
|
@@ -25,6 +25,14 @@ export function resolveToolChoice(opts) {
|
|
|
25
25
|
return "none";
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
// 2b. Alternating pattern detection: catch A→B→A→B→A→B death spirals (e.g. read→edit→read→edit)
|
|
29
|
+
if (opts.recentToolUses.length >= 6) {
|
|
30
|
+
const last6 = opts.recentToolUses.slice(-6);
|
|
31
|
+
const isRepeatingPair = last6[2] === last6[0] && last6[3] === last6[1] &&
|
|
32
|
+
last6[4] === last6[0] && last6[5] === last6[1];
|
|
33
|
+
if (isRepeatingPair)
|
|
34
|
+
return "none";
|
|
35
|
+
}
|
|
28
36
|
// 3. Keyword matching: check if the user message mentions a specific tool name
|
|
29
37
|
// Only on the first turn (avoids false positives on multi-turn conversations)
|
|
30
38
|
if (opts.turnCount === 1 && opts.userMessage && opts.availableToolNames.length > 0) {
|
|
@@ -47,8 +55,8 @@ export function resolveToolChoice(opts) {
|
|
|
47
55
|
export const COMPACTION_TRIGGER_TOKENS = 120_000;
|
|
48
56
|
/** Max cumulative tokens before forcing wrap-up (prevents runaway compaction cost) */
|
|
49
57
|
export const COMPACTION_TOTAL_BUDGET = 2_000_000;
|
|
50
|
-
/** Default session cost budget in USD —
|
|
51
|
-
export const DEFAULT_SESSION_COST_BUDGET_USD =
|
|
58
|
+
/** Default session cost budget in USD — hard cap to prevent runaway spending */
|
|
59
|
+
export const DEFAULT_SESSION_COST_BUDGET_USD = 5.00;
|
|
52
60
|
export function getCompactionConfig(model) {
|
|
53
61
|
const provider = getProvider(model);
|
|
54
62
|
switch (provider) {
|
|
@@ -203,12 +211,15 @@ export class LoopDetector {
|
|
|
203
211
|
failedStrategies = new Set();
|
|
204
212
|
consecutiveFailedTurns = 0;
|
|
205
213
|
totalSessionErrors = 0;
|
|
214
|
+
/** Tracks how many times the same file path has been read this session */
|
|
215
|
+
fileReadCounts = new Map();
|
|
206
216
|
static IDENTICAL_CALL_LIMIT = 4;
|
|
207
217
|
static CONSECUTIVE_ERROR_LIMIT = 3;
|
|
208
218
|
static TURN_ERROR_LIMIT = 5;
|
|
209
219
|
static WINDOW = 20;
|
|
210
220
|
static SESSION_TOOL_ERROR_LIMIT = 10;
|
|
211
221
|
static CONSECUTIVE_FAILED_TURN_LIMIT = 3;
|
|
222
|
+
static FILE_READ_LIMIT = 3;
|
|
212
223
|
/** Get the error-tracking key for a tool call. Tools with an `action` param
|
|
213
224
|
* are tracked per-action so e.g. voice/speak failing won't block voice/music_compose. */
|
|
214
225
|
errorKey(name, input) {
|
|
@@ -219,6 +230,18 @@ export class LoopDetector {
|
|
|
219
230
|
recordCall(name, input) {
|
|
220
231
|
const inputHash = djb2Hash(JSON.stringify({ name, ...input }));
|
|
221
232
|
const eKey = this.errorKey(name, input);
|
|
233
|
+
// File read frequency tracking — block re-reading the same file >3 times per session
|
|
234
|
+
if (name === "read_file" && typeof input.path === "string") {
|
|
235
|
+
const filePath = input.path;
|
|
236
|
+
const readCount = (this.fileReadCounts.get(filePath) || 0) + 1;
|
|
237
|
+
this.fileReadCounts.set(filePath, readCount);
|
|
238
|
+
if (readCount > LoopDetector.FILE_READ_LIMIT) {
|
|
239
|
+
return {
|
|
240
|
+
blocked: true,
|
|
241
|
+
reason: `File "${filePath}" already read ${readCount - 1} times this session. Use the content from a previous read instead of re-reading.`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
222
245
|
if (this.failedStrategies.has(inputHash)) {
|
|
223
246
|
return {
|
|
224
247
|
blocked: true,
|
|
@@ -319,15 +342,18 @@ export class LoopDetector {
|
|
|
319
342
|
return { shouldBail: false };
|
|
320
343
|
}
|
|
321
344
|
resetTurn() {
|
|
322
|
-
|
|
323
|
-
|
|
345
|
+
// Only reset per-turn counters — history and consecutiveErrors persist
|
|
346
|
+
// for cross-turn detection. The full reset() clears everything for new sessions.
|
|
324
347
|
this.turnErrors = 0;
|
|
325
348
|
this.turnHadErrors = false;
|
|
326
349
|
}
|
|
327
350
|
reset() {
|
|
328
351
|
this.resetTurn();
|
|
352
|
+
this.history = [];
|
|
353
|
+
this.consecutiveErrors.clear();
|
|
329
354
|
this.sessionErrors.clear();
|
|
330
355
|
this.failedStrategies.clear();
|
|
356
|
+
this.fileReadCounts.clear();
|
|
331
357
|
this.consecutiveFailedTurns = 0;
|
|
332
358
|
this.totalSessionErrors = 0;
|
|
333
359
|
}
|
|
@@ -50,6 +50,18 @@ export function buildAPIRequest(opts) {
|
|
|
50
50
|
keep: { type: "tool_uses", value: 2 },
|
|
51
51
|
},
|
|
52
52
|
];
|
|
53
|
+
// Compaction for sub-agents on models that support it (lower threshold than main agent).
|
|
54
|
+
// Trigger at 80K — above the 60K clear_tool_uses threshold so clearing fires first.
|
|
55
|
+
const supportsCompaction = model.includes("opus-4-6") || model.includes("sonnet-4-6");
|
|
56
|
+
if (supportsCompaction) {
|
|
57
|
+
edits.push({
|
|
58
|
+
type: "compact_20260112",
|
|
59
|
+
trigger: { type: "input_tokens", value: 80_000 },
|
|
60
|
+
pause_after_compaction: true,
|
|
61
|
+
instructions: "Summarize preserving: task goal, files found/modified, key findings, next steps.",
|
|
62
|
+
});
|
|
63
|
+
betas.push("compact-2026-01-12");
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
break;
|
|
55
67
|
case "teammate": {
|
|
@@ -113,16 +125,46 @@ export async function callServerProxy(config) {
|
|
|
113
125
|
if (config.storeId) {
|
|
114
126
|
body.store_id = config.storeId;
|
|
115
127
|
}
|
|
128
|
+
// Prune old base64 images from conversation history before serializing.
|
|
129
|
+
// Keep images only in the last 2 messages to avoid multi-MB request bodies
|
|
130
|
+
// that can cause "Invalid JSON" errors from body truncation on the proxy.
|
|
131
|
+
if (Array.isArray(body.messages) && body.messages.length > 4) {
|
|
132
|
+
const msgs = body.messages;
|
|
133
|
+
const keepFrom = Math.max(0, msgs.length - 2);
|
|
134
|
+
for (let i = 0; i < keepFrom; i++) {
|
|
135
|
+
const msg = msgs[i];
|
|
136
|
+
if (!Array.isArray(msg?.content))
|
|
137
|
+
continue;
|
|
138
|
+
for (const block of msg.content) {
|
|
139
|
+
if (block.type === "image" && block.source?.type === "base64") {
|
|
140
|
+
block.type = "text";
|
|
141
|
+
block.text = "[image]";
|
|
142
|
+
delete block.source;
|
|
143
|
+
}
|
|
144
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
145
|
+
for (const sub of block.content) {
|
|
146
|
+
if (sub.type === "image" && sub.source?.type === "base64") {
|
|
147
|
+
sub.type = "text";
|
|
148
|
+
sub.text = "[image]";
|
|
149
|
+
delete sub.source;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const serialized = JSON.stringify(body);
|
|
116
157
|
const fetchOpts = {
|
|
117
158
|
method: "POST",
|
|
118
159
|
headers: {
|
|
119
160
|
"Content-Type": "application/json",
|
|
120
161
|
"Authorization": `Bearer ${config.token}`,
|
|
162
|
+
"Content-Length": String(Buffer.byteLength(serialized)),
|
|
121
163
|
},
|
|
122
|
-
body:
|
|
164
|
+
body: serialized,
|
|
123
165
|
signal,
|
|
124
166
|
};
|
|
125
|
-
// Apply timeout
|
|
167
|
+
// Apply timeout only when the caller did NOT already supply a signal
|
|
126
168
|
let controller;
|
|
127
169
|
let timeout;
|
|
128
170
|
if (timeoutMs && !signal) {
|
|
@@ -162,7 +204,16 @@ export async function callServerProxy(config) {
|
|
|
162
204
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
163
205
|
config.onRetry?.(attempt + 1, MAX_RETRIES, errMsg);
|
|
164
206
|
const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
165
|
-
|
|
207
|
+
// Abort-aware backoff — ESC can interrupt the wait immediately
|
|
208
|
+
await new Promise((resolve, reject) => {
|
|
209
|
+
if (signal?.aborted) {
|
|
210
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const timer = setTimeout(resolve, delay);
|
|
214
|
+
const onAbort = () => { clearTimeout(timer); reject(signal.reason ?? new Error("Aborted")); };
|
|
215
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
216
|
+
});
|
|
166
217
|
// Fallback model on last retry
|
|
167
218
|
if (attempt === MAX_RETRIES - 1 && config.fallbackModel) {
|
|
168
219
|
const fromModel = config.model;
|
|
@@ -13,7 +13,7 @@ import type { StreamResult, StreamCallbacks } from "./types.js";
|
|
|
13
13
|
* Parse SSE stream from proxy HTTP response into typed events.
|
|
14
14
|
* Handles `data: {...}\n\n` format with [DONE] sentinel.
|
|
15
15
|
*/
|
|
16
|
-
export declare function parseSSEStream(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<BetaStreamEvent>;
|
|
16
|
+
export declare function parseSSEStream(body: ReadableStream<Uint8Array>, signal?: AbortSignal, readTimeoutMs?: number): AsyncGenerator<BetaStreamEvent>;
|
|
17
17
|
/**
|
|
18
18
|
* Collect all events into a StreamResult. No callbacks — used by
|
|
19
19
|
* subagent and teammate where real-time text isn't needed.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Parse SSE stream from proxy HTTP response into typed events.
|
|
15
15
|
* Handles `data: {...}\n\n` format with [DONE] sentinel.
|
|
16
16
|
*/
|
|
17
|
-
export async function* parseSSEStream(body, signal) {
|
|
17
|
+
export async function* parseSSEStream(body, signal, readTimeoutMs = 90_000) {
|
|
18
18
|
const reader = body.getReader();
|
|
19
19
|
const decoder = new TextDecoder();
|
|
20
20
|
let buffer = "";
|
|
@@ -22,7 +22,10 @@ export async function* parseSSEStream(body, signal) {
|
|
|
22
22
|
while (true) {
|
|
23
23
|
if (signal?.aborted)
|
|
24
24
|
break;
|
|
25
|
-
|
|
25
|
+
// Read with timeout — abort if no data arrives within readTimeoutMs
|
|
26
|
+
const readPromise = reader.read();
|
|
27
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`SSE stream stalled — no data for ${readTimeoutMs / 1000}s`)), readTimeoutMs));
|
|
28
|
+
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
26
29
|
if (done)
|
|
27
30
|
break;
|
|
28
31
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
const SAFETY_MAX_CHARS = 30_000;
|
|
15
15
|
/** Tools that spawn workers/subprocesses and need much longer than 2 min */
|
|
16
16
|
const LONG_RUNNING_TOOL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
17
|
-
const LONG_RUNNING_TOOLS = new Set(["team_create", "task", "kali", "delegate_task"]);
|
|
17
|
+
const LONG_RUNNING_TOOLS = new Set(["team_create", "task", "kali", "delegate_task", "exit_plan_mode", "ask_user_question"]);
|
|
18
18
|
// ============================================================================
|
|
19
19
|
// DISPATCH
|
|
20
20
|
// ============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whale-code",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"description": "whale code — local-first AI agent CLI for inventory, orders, and analytics powered by MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,20 +15,21 @@
|
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"test": "vitest run",
|
|
17
17
|
"test:watch": "vitest",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"postinstall": "chmod +x node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper 2>/dev/null || true; node scripts/patch-ink-resize.js 2>/dev/null || true"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@anthropic-ai/sdk": "^0.74.0",
|
|
22
|
-
"@aws-sdk/client-bedrock-runtime": "^3.
|
|
23
|
+
"@aws-sdk/client-bedrock-runtime": "^3.1000.0",
|
|
23
24
|
"@google/genai": "^1.41.0",
|
|
24
25
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
26
|
+
"@react-pdf/renderer": "^4.3.2",
|
|
25
27
|
"@supabase/supabase-js": "2.95.3",
|
|
26
28
|
"bonjour-service": "^1.3.0",
|
|
27
29
|
"chalk": "^5.6.2",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"ink": "^
|
|
31
|
-
"ink-select-input": "^6.0.0",
|
|
30
|
+
"diff": "^8.0.3",
|
|
31
|
+
"ink": "^6.8.0",
|
|
32
|
+
"ink-select-input": "^6.2.0",
|
|
32
33
|
"ink-spinner": "^5.0.0",
|
|
33
34
|
"ink-text-input": "^6.0.0",
|
|
34
35
|
"marked": "^15.0.12",
|
|
@@ -39,7 +40,10 @@
|
|
|
39
40
|
"pg": "^8.18.0",
|
|
40
41
|
"pino": "^10.3.1",
|
|
41
42
|
"playwright-core": "^1.58.2",
|
|
42
|
-
"
|
|
43
|
+
"qrcode": "^1.5.4",
|
|
44
|
+
"react": "^19.2.4",
|
|
45
|
+
"react-pdf-html": "^2.1.5",
|
|
46
|
+
"shiki": "^4.0.0",
|
|
43
47
|
"ws": "^8.19.0",
|
|
44
48
|
"zod": "^4.3.6"
|
|
45
49
|
},
|
|
@@ -50,7 +54,8 @@
|
|
|
50
54
|
"@types/node": "^20.10.0",
|
|
51
55
|
"@types/pg": "^8.16.0",
|
|
52
56
|
"@types/pino": "^7.0.4",
|
|
53
|
-
"@types/
|
|
57
|
+
"@types/qrcode": "^1.5.6",
|
|
58
|
+
"@types/react": "^19.2.14",
|
|
54
59
|
"@types/ws": "^8.18.1",
|
|
55
60
|
"@vitest/coverage-v8": "^4.0.18",
|
|
56
61
|
"dotenv": "^17.3.1",
|
|
@@ -90,6 +95,7 @@
|
|
|
90
95
|
],
|
|
91
96
|
"license": "MIT",
|
|
92
97
|
"overrides": {
|
|
93
|
-
"minimatch": ">=10.2.1"
|
|
98
|
+
"minimatch": ">=10.2.1",
|
|
99
|
+
"fast-xml-parser": ">=5.3.8"
|
|
94
100
|
}
|
|
95
101
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** Pre-populate a table with rows for the current test */
|
|
2
|
-
export declare function setTableData(table: string, rows: unknown[]): void;
|
|
3
|
-
/** Pre-configure the return value for an RPC call */
|
|
4
|
-
export declare function setRpcResponse(name: string, data: unknown, error?: unknown): void;
|
|
5
|
-
/** Force a table to return an error on any operation */
|
|
6
|
-
export declare function setTableError(table: string, message: string, code?: string): void;
|
|
7
|
-
/** Reset all in-memory state between tests */
|
|
8
|
-
export declare function resetMock(): void;
|
|
9
|
-
/** Get current in-memory rows for a table (for post-operation assertions) */
|
|
10
|
-
export declare function getTableData(table: string): unknown[];
|
|
11
|
-
export declare function createMockSupabase(): unknown;
|