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,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Response Formatter — converts raw JSON to readable markdown
|
|
3
|
+
*
|
|
4
|
+
* Transforms server tool JSON responses into:
|
|
5
|
+
* - Markdown tables for arrays of flat objects
|
|
6
|
+
* - Bold key-value pairs for single objects
|
|
7
|
+
* - JSON fences for complex nested data
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// COLUMN PRIORITIES — per tool category
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const PRIORITY_COLUMNS = {
|
|
13
|
+
analytics: ["name", "revenue", "quantity", "total", "count", "average", "period"],
|
|
14
|
+
products: ["name", "sku", "status", "category_name", "cost_price", "stock_quantity"],
|
|
15
|
+
inventory: ["product_name", "quantity", "location_name", "product_sku", "product_id"],
|
|
16
|
+
customers: ["first_name", "last_name", "email", "phone", "status", "loyalty_tier"],
|
|
17
|
+
orders: ["order_number", "status", "total", "customer_name", "created_at"],
|
|
18
|
+
supply_chain: ["name", "status", "quantity", "supplier", "location", "expected_delivery_date"],
|
|
19
|
+
workflows: ["name", "status", "trigger_type", "created_at", "last_run"],
|
|
20
|
+
email: ["subject", "to", "status", "sent_at", "category"],
|
|
21
|
+
};
|
|
22
|
+
// Keys that are always deprioritized (shown last or hidden)
|
|
23
|
+
const LOW_PRIORITY_KEYS = new Set([
|
|
24
|
+
"id", "store_id", "created_at", "updated_at", "deleted_at",
|
|
25
|
+
"created_by", "updated_by", "metadata", "raw_data",
|
|
26
|
+
]);
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// FORMATTERS
|
|
29
|
+
// ============================================================================
|
|
30
|
+
function isNarrow() {
|
|
31
|
+
return (process.stdout.columns || 80) < 90;
|
|
32
|
+
}
|
|
33
|
+
function truncateUuid(val) {
|
|
34
|
+
// UUID pattern: 8-4-4-4-12 hex chars
|
|
35
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(val)) {
|
|
36
|
+
return val.slice(0, 8);
|
|
37
|
+
}
|
|
38
|
+
return val;
|
|
39
|
+
}
|
|
40
|
+
function formatValue(val, key) {
|
|
41
|
+
if (val === null || val === undefined)
|
|
42
|
+
return "—";
|
|
43
|
+
if (typeof val === "boolean")
|
|
44
|
+
return val ? "\u2713" : "\u2715";
|
|
45
|
+
if (typeof val === "number") {
|
|
46
|
+
if (key && isPercentageKey(key))
|
|
47
|
+
return `${val}%`;
|
|
48
|
+
return val.toLocaleString();
|
|
49
|
+
}
|
|
50
|
+
if (typeof val === "string") {
|
|
51
|
+
// Money detection
|
|
52
|
+
if (/^\d+\.\d{2}$/.test(val) && parseFloat(val) > 0) {
|
|
53
|
+
return `$${parseFloat(val).toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
|
|
54
|
+
}
|
|
55
|
+
// Keys the LLM needs verbatim (IDs, file paths, URLs) — skip all truncation
|
|
56
|
+
if (key && NEVER_TRUNCATE_KEYS.has(key))
|
|
57
|
+
return val;
|
|
58
|
+
// UUID truncation for display-only columns
|
|
59
|
+
const truncated = truncateUuid(val);
|
|
60
|
+
// Date formatting
|
|
61
|
+
if (/^\d{4}-\d{2}-\d{2}T/.test(val)) {
|
|
62
|
+
return val.slice(0, 10);
|
|
63
|
+
}
|
|
64
|
+
// Truncate long strings
|
|
65
|
+
if (truncated.length > 200)
|
|
66
|
+
return truncated.slice(0, 197) + "...";
|
|
67
|
+
return truncated;
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(val)) {
|
|
70
|
+
if (val.length === 0)
|
|
71
|
+
return "—";
|
|
72
|
+
// Extract names from array of objects for useful display
|
|
73
|
+
if (typeof val[0] === "object" && val[0] !== null) {
|
|
74
|
+
const names = val.slice(0, 3)
|
|
75
|
+
.map(v => {
|
|
76
|
+
const o = v;
|
|
77
|
+
return o.name || o.location_name || o.title || o.label;
|
|
78
|
+
})
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.map(String);
|
|
81
|
+
if (names.length > 0) {
|
|
82
|
+
return names.join(", ") + (val.length > 3 ? ` +${val.length - 3} more` : "");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return `[${val.length} items]`;
|
|
86
|
+
}
|
|
87
|
+
if (typeof val === "object") {
|
|
88
|
+
const obj = val;
|
|
89
|
+
// Extract name/title from nested objects instead of showing {...}
|
|
90
|
+
if (typeof obj.name === "string")
|
|
91
|
+
return obj.name;
|
|
92
|
+
if (typeof obj.title === "string")
|
|
93
|
+
return obj.title;
|
|
94
|
+
if (typeof obj.label === "string")
|
|
95
|
+
return obj.label;
|
|
96
|
+
if (typeof obj.id === "string")
|
|
97
|
+
return obj.id;
|
|
98
|
+
return "{...}";
|
|
99
|
+
}
|
|
100
|
+
return String(val);
|
|
101
|
+
}
|
|
102
|
+
/** Keys whose values must never be truncated (file paths, URLs, IDs the LLM needs verbatim) */
|
|
103
|
+
const NEVER_TRUNCATE_KEYS = new Set([
|
|
104
|
+
"id", "product_id", "category_id", "location_id", "supplier_id", "customer_id",
|
|
105
|
+
"store_id", "order_id", "workflow_id", "schema_id", "field_schema_id", "pricing_schema_id",
|
|
106
|
+
"transfer_id", "run_id", "step_id", "audit_id", "media_id", "creation_id", "job_id",
|
|
107
|
+
"collection_id", "source_id", "thread_id", "form_id", "template_id", "webhook_id",
|
|
108
|
+
"rule_id", "subscription_id", "span_id", "trace_id", "ad_id", "ad_set_id", "campaign_id",
|
|
109
|
+
"audience_id", "pixel_id", "key_id", "catalog_id", "purchase_order_id",
|
|
110
|
+
"primary_category_id", "parent_id", "from_location_id", "to_location_id",
|
|
111
|
+
"local_file", "file_url", "file_path", "download", "url",
|
|
112
|
+
"preview_url", "thumbnail_url", "audio_file",
|
|
113
|
+
"stdout", "stderr", "command", "output",
|
|
114
|
+
]);
|
|
115
|
+
function formatMoneyValue(val) {
|
|
116
|
+
if (typeof val === "number") {
|
|
117
|
+
return `$${val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
118
|
+
}
|
|
119
|
+
return formatValue(val);
|
|
120
|
+
}
|
|
121
|
+
function isPercentageKey(key) {
|
|
122
|
+
return /percentage|_pct$|percent/i.test(key);
|
|
123
|
+
}
|
|
124
|
+
function isMoneyKey(key) {
|
|
125
|
+
// Bare "total" is almost always a count, not money. Only "total_revenue", "total_amount" etc. are money.
|
|
126
|
+
if (key === "total")
|
|
127
|
+
return false;
|
|
128
|
+
// Exclude "total_" when followed by non-money qualifiers
|
|
129
|
+
if (/^total[_](stock|products|items|orders|units|count|quantity|customers|runs|pages|results|employees)/i.test(key))
|
|
130
|
+
return false;
|
|
131
|
+
return /price|cost|revenue|total_revenue|total_amount|amount|subtotal|tax|discount|budget/i.test(key);
|
|
132
|
+
}
|
|
133
|
+
function prettifyKey(key) {
|
|
134
|
+
return key
|
|
135
|
+
.replace(/_/g, " ")
|
|
136
|
+
.replace(/\bid\b/gi, "ID")
|
|
137
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
138
|
+
}
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// COLUMN SELECTION
|
|
141
|
+
// ============================================================================
|
|
142
|
+
function selectColumns(rows, toolName) {
|
|
143
|
+
if (rows.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
// Collect all keys from first few rows
|
|
146
|
+
const allKeys = new Set();
|
|
147
|
+
for (const row of rows.slice(0, 5)) {
|
|
148
|
+
for (const key of Object.keys(row))
|
|
149
|
+
allKeys.add(key);
|
|
150
|
+
}
|
|
151
|
+
// Find matching priority list
|
|
152
|
+
const maxCols = isNarrow() ? 4 : 6;
|
|
153
|
+
let priority = [];
|
|
154
|
+
if (toolName) {
|
|
155
|
+
// Match tool name to category
|
|
156
|
+
for (const [category, cols] of Object.entries(PRIORITY_COLUMNS)) {
|
|
157
|
+
if (toolName.toLowerCase().includes(category)) {
|
|
158
|
+
priority = cols;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Build ordered column list: priority first, then remaining non-low-priority
|
|
164
|
+
const selected = [];
|
|
165
|
+
for (const col of priority) {
|
|
166
|
+
if (allKeys.has(col) && selected.length < maxCols) {
|
|
167
|
+
selected.push(col);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Fill remaining slots with non-priority, non-low-priority keys
|
|
171
|
+
for (const key of allKeys) {
|
|
172
|
+
if (selected.length >= maxCols)
|
|
173
|
+
break;
|
|
174
|
+
if (selected.includes(key))
|
|
175
|
+
continue;
|
|
176
|
+
if (LOW_PRIORITY_KEYS.has(key))
|
|
177
|
+
continue;
|
|
178
|
+
// Skip arrays and nested objects from table columns — they don't render well
|
|
179
|
+
// in fixed-width tables. Nested objects show as "{...}" which is not useful.
|
|
180
|
+
const sample = rows[0][key];
|
|
181
|
+
if (Array.isArray(sample))
|
|
182
|
+
continue;
|
|
183
|
+
if (typeof sample === "object" && sample !== null)
|
|
184
|
+
continue;
|
|
185
|
+
selected.push(key);
|
|
186
|
+
}
|
|
187
|
+
return selected;
|
|
188
|
+
}
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// TABLE BUILDER
|
|
191
|
+
// ============================================================================
|
|
192
|
+
function buildTable(rows, columns) {
|
|
193
|
+
if (columns.length === 0 || rows.length === 0)
|
|
194
|
+
return "";
|
|
195
|
+
const headers = columns.map(prettifyKey);
|
|
196
|
+
// Calculate column widths
|
|
197
|
+
const widths = columns.map((col, i) => {
|
|
198
|
+
let max = headers[i].length;
|
|
199
|
+
for (const row of rows) {
|
|
200
|
+
const val = isMoneyKey(col) ? formatMoneyValue(row[col]) : formatValue(row[col], col);
|
|
201
|
+
max = Math.max(max, val.length);
|
|
202
|
+
}
|
|
203
|
+
return Math.min(max, 30); // Cap individual column width
|
|
204
|
+
});
|
|
205
|
+
// Header row
|
|
206
|
+
const headerRow = columns.map((_, i) => headers[i].padEnd(widths[i])).join(" | ");
|
|
207
|
+
const separator = widths.map(w => "-".repeat(w)).join(" | ");
|
|
208
|
+
// Data rows
|
|
209
|
+
const dataRows = rows.map(row => columns.map((col, i) => {
|
|
210
|
+
const val = isMoneyKey(col) ? formatMoneyValue(row[col]) : formatValue(row[col], col);
|
|
211
|
+
// Right-align numbers and money
|
|
212
|
+
const isNum = typeof row[col] === "number" || isMoneyKey(col);
|
|
213
|
+
return isNum ? val.padStart(widths[i]) : val.padEnd(widths[i]);
|
|
214
|
+
}).join(" | "));
|
|
215
|
+
return [
|
|
216
|
+
`| ${headerRow} |`,
|
|
217
|
+
`| ${separator} |`,
|
|
218
|
+
...dataRows.map(r => `| ${r} |`),
|
|
219
|
+
].join("\n");
|
|
220
|
+
}
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// ID REFERENCE — gives the LLM full UUIDs for follow-up calls
|
|
223
|
+
// ============================================================================
|
|
224
|
+
/** After a table, append a compact name→UUID mapping so the LLM can reference IDs */
|
|
225
|
+
function buildIdReference(rows) {
|
|
226
|
+
if (rows.length === 0)
|
|
227
|
+
return "";
|
|
228
|
+
// Find the primary ID column — prefer 'id', fall back to 'product_id', 'customer_id', etc.
|
|
229
|
+
const idKey = ["id", "product_id", "customer_id", "order_id", "workflow_id"]
|
|
230
|
+
.find(k => rows.some(r => r[k] && typeof r[k] === "string"));
|
|
231
|
+
if (!idKey)
|
|
232
|
+
return "";
|
|
233
|
+
// Find a human-readable label key
|
|
234
|
+
const nameKey = ["name", "product_name", "first_name", "title", "subject", "label", "slug", "order_number"]
|
|
235
|
+
.find(k => rows[0][k] !== undefined) || null;
|
|
236
|
+
const entries = [];
|
|
237
|
+
for (const row of rows) {
|
|
238
|
+
const idVal = row[idKey];
|
|
239
|
+
if (!idVal || typeof idVal !== "string")
|
|
240
|
+
continue;
|
|
241
|
+
const label = nameKey && row[nameKey] ? String(row[nameKey]) : String(idVal).slice(0, 8);
|
|
242
|
+
entries.push(`${label}=${idVal}`);
|
|
243
|
+
}
|
|
244
|
+
if (entries.length === 0)
|
|
245
|
+
return "";
|
|
246
|
+
return `\nIDs: ${entries.join(", ")}`;
|
|
247
|
+
}
|
|
248
|
+
// ============================================================================
|
|
249
|
+
// MAIN FORMATTER
|
|
250
|
+
// ============================================================================
|
|
251
|
+
function detectToolCategory(toolName) {
|
|
252
|
+
if (!toolName)
|
|
253
|
+
return undefined;
|
|
254
|
+
const name = toolName.toLowerCase();
|
|
255
|
+
for (const category of Object.keys(PRIORITY_COLUMNS)) {
|
|
256
|
+
if (name.includes(category))
|
|
257
|
+
return category;
|
|
258
|
+
}
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Format a server tool response as readable markdown.
|
|
263
|
+
*
|
|
264
|
+
* @param data - The raw response data (parsed JSON)
|
|
265
|
+
* @param toolName - Optional tool name for smart column selection
|
|
266
|
+
* @returns Formatted markdown string
|
|
267
|
+
*/
|
|
268
|
+
export function formatServerResponse(data, toolName) {
|
|
269
|
+
// String passthrough
|
|
270
|
+
if (typeof data === "string")
|
|
271
|
+
return data;
|
|
272
|
+
// Shell execution tools (local_agent, kali) — stdout is the main content
|
|
273
|
+
if (toolName && (toolName === "local_agent" || toolName === "kali") && typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
274
|
+
const obj = data;
|
|
275
|
+
if ("stdout" in obj || "stderr" in obj || "exit_code" in obj) {
|
|
276
|
+
const stdout = String(obj.stdout || "").trim();
|
|
277
|
+
const stderr = String(obj.stderr || "").trim();
|
|
278
|
+
const exitCode = obj.exit_code;
|
|
279
|
+
const durationMs = obj.duration_ms;
|
|
280
|
+
// For successful commands, show stdout as primary output (like bash tool)
|
|
281
|
+
if (exitCode === 0 && stdout) {
|
|
282
|
+
const parts = [stdout];
|
|
283
|
+
if (stderr)
|
|
284
|
+
parts.push(`\n**Stderr**: ${stderr}`);
|
|
285
|
+
// Compact metadata footer
|
|
286
|
+
const meta = [];
|
|
287
|
+
if (durationMs !== undefined)
|
|
288
|
+
meta.push(`${durationMs}ms`);
|
|
289
|
+
if (obj.cwd)
|
|
290
|
+
meta.push(`cwd: ${obj.cwd}`);
|
|
291
|
+
if (meta.length > 0)
|
|
292
|
+
parts.push(`\n*${meta.join(" | ")}*`);
|
|
293
|
+
return parts.join("\n");
|
|
294
|
+
}
|
|
295
|
+
// For errors or no-stdout results, show structured output
|
|
296
|
+
const lines = [];
|
|
297
|
+
if (obj.error)
|
|
298
|
+
lines.push(`**Error**: ${String(obj.error)}`);
|
|
299
|
+
if (exitCode !== undefined && exitCode !== 0)
|
|
300
|
+
lines.push(`**Exit Code**: ${exitCode}`);
|
|
301
|
+
if (stdout)
|
|
302
|
+
lines.push(stdout);
|
|
303
|
+
if (stderr)
|
|
304
|
+
lines.push(`**Stderr**: ${stderr}`);
|
|
305
|
+
if (obj.killed)
|
|
306
|
+
lines.push(`**Killed**: process was terminated`);
|
|
307
|
+
if (durationMs !== undefined)
|
|
308
|
+
lines.push(`*${durationMs}ms${obj.cwd ? ` | cwd: ${obj.cwd}` : ""}*`);
|
|
309
|
+
return lines.join("\n") || "Command completed with no output.";
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Null/undefined
|
|
313
|
+
if (data === null || data === undefined)
|
|
314
|
+
return "No data returned.";
|
|
315
|
+
// Empty array
|
|
316
|
+
if (Array.isArray(data) && data.length === 0)
|
|
317
|
+
return "No results found.";
|
|
318
|
+
// Array of flat objects → markdown table
|
|
319
|
+
if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null) {
|
|
320
|
+
const rows = data;
|
|
321
|
+
const columns = selectColumns(rows, toolName);
|
|
322
|
+
if (columns.length > 0) {
|
|
323
|
+
const category = detectToolCategory(toolName);
|
|
324
|
+
const label = category ? prettifyKey(category) : "Results";
|
|
325
|
+
const header = `**${label}** (${rows.length} result${rows.length !== 1 ? "s" : ""})`;
|
|
326
|
+
const table = buildTable(rows, columns);
|
|
327
|
+
const idRef = buildIdReference(rows);
|
|
328
|
+
return `${header}\n\n${table}${idRef}`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Single object → bold key-value pairs + expand nested arrays as sub-tables
|
|
332
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
333
|
+
const obj = data;
|
|
334
|
+
const scalarEntries = Object.entries(obj)
|
|
335
|
+
.filter(([key, val]) => {
|
|
336
|
+
if (LOW_PRIORITY_KEYS.has(key) && key !== "id")
|
|
337
|
+
return false;
|
|
338
|
+
return typeof val !== "object" || val === null;
|
|
339
|
+
});
|
|
340
|
+
// Nested objects (e.g. supplier, customer, profile joins) — extract their scalar fields
|
|
341
|
+
const nestedObjectEntries = Object.entries(obj)
|
|
342
|
+
.filter(([key, val]) => {
|
|
343
|
+
if (LOW_PRIORITY_KEYS.has(key))
|
|
344
|
+
return false;
|
|
345
|
+
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
346
|
+
});
|
|
347
|
+
const arrayEntries = Object.entries(obj)
|
|
348
|
+
.filter(([, val]) => Array.isArray(val) && val.length > 0 &&
|
|
349
|
+
typeof val[0] === "object");
|
|
350
|
+
if (scalarEntries.length > 0 || nestedObjectEntries.length > 0 || arrayEntries.length > 0) {
|
|
351
|
+
const parts = [];
|
|
352
|
+
// Scalar key-value pairs (with pagination awareness)
|
|
353
|
+
if (scalarEntries.length > 0 && scalarEntries.length <= 20) {
|
|
354
|
+
const hasPagination = typeof obj.total === "number" && typeof obj.count === "number";
|
|
355
|
+
const filteredScalars = scalarEntries.filter(([key]) => {
|
|
356
|
+
if (!hasPagination)
|
|
357
|
+
return true;
|
|
358
|
+
// Skip raw pagination fields — shown as "Showing X of Y" instead
|
|
359
|
+
if (key === "count" || key === "offset" || key === "limit")
|
|
360
|
+
return false;
|
|
361
|
+
return true;
|
|
362
|
+
});
|
|
363
|
+
const scalarLines = filteredScalars.map(([key, val]) => {
|
|
364
|
+
// Replace "total" with "Showing X of Y" when pagination data exists
|
|
365
|
+
if (hasPagination && key === "total" && typeof val === "number" && val > obj.count) {
|
|
366
|
+
return `**Showing**: ${obj.count} of ${val}`;
|
|
367
|
+
}
|
|
368
|
+
const fmtVal = isMoneyKey(key) ? formatMoneyValue(val) : formatValue(val, key);
|
|
369
|
+
return `**${prettifyKey(key)}**: ${fmtVal}`;
|
|
370
|
+
});
|
|
371
|
+
if (scalarLines.length > 0)
|
|
372
|
+
parts.push(scalarLines.join("\n"));
|
|
373
|
+
}
|
|
374
|
+
// Nested objects — expand their scalar fields with parent key prefix
|
|
375
|
+
for (const [key, val] of nestedObjectEntries) {
|
|
376
|
+
const nested = val;
|
|
377
|
+
const nestedScalars = Object.entries(nested)
|
|
378
|
+
.filter(([k, v]) => !LOW_PRIORITY_KEYS.has(k) && (typeof v !== "object" || v === null));
|
|
379
|
+
if (nestedScalars.length > 0 && nestedScalars.length <= 10) {
|
|
380
|
+
const prefix = prettifyKey(key);
|
|
381
|
+
const lines = nestedScalars.map(([k, v]) => {
|
|
382
|
+
const fmtVal = isMoneyKey(k) ? formatMoneyValue(v) : formatValue(v, k);
|
|
383
|
+
return `**${prefix} ${prettifyKey(k)}**: ${fmtVal}`;
|
|
384
|
+
});
|
|
385
|
+
parts.push(lines.join("\n"));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Nested arrays as sub-tables (e.g. by_location, items, steps)
|
|
389
|
+
for (const [key, val] of arrayEntries) {
|
|
390
|
+
const rows = val;
|
|
391
|
+
// Use toolName for priority matching when the key is generic ("data", "results", etc.)
|
|
392
|
+
const colHint = ["data", "results", "items", "rows", "records"].includes(key) ? toolName : key;
|
|
393
|
+
const columns = selectColumns(rows, colHint);
|
|
394
|
+
if (columns.length > 0) {
|
|
395
|
+
const table = buildTable(rows, columns);
|
|
396
|
+
const idRef = buildIdReference(rows);
|
|
397
|
+
parts.push(`\n**${prettifyKey(key)}** (${rows.length}):\n${table}${idRef}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (parts.length > 0)
|
|
401
|
+
return parts.join("\n");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Object with a data/results/items array (common API wrapper pattern)
|
|
405
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
406
|
+
const obj = data;
|
|
407
|
+
for (const key of ["data", "results", "items", "rows", "records"]) {
|
|
408
|
+
if (Array.isArray(obj[key]) && obj[key].length > 0) {
|
|
409
|
+
// Format the nested array and prepend any summary fields
|
|
410
|
+
const summaryFields = [];
|
|
411
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
412
|
+
if (k === key)
|
|
413
|
+
continue;
|
|
414
|
+
if (typeof v !== "object" || v === null) {
|
|
415
|
+
// Show total/offset/limit for pagination context
|
|
416
|
+
if (k === "total" && typeof v === "number" && typeof obj.count === "number" && v > obj.count) {
|
|
417
|
+
summaryFields.push(`**Showing**: ${obj.count} of ${v}`);
|
|
418
|
+
}
|
|
419
|
+
else if (k === "count" && obj.total !== undefined) {
|
|
420
|
+
continue; // skip count when total is present (shown in "Showing X of Y")
|
|
421
|
+
}
|
|
422
|
+
else if (k === "offset" || k === "limit") {
|
|
423
|
+
continue; // skip raw pagination params
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
summaryFields.push(`**${prettifyKey(k)}**: ${formatValue(v, k)}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const arrayResult = formatServerResponse(obj[key], toolName);
|
|
431
|
+
return summaryFields.length > 0
|
|
432
|
+
? `${summaryFields.join(" | ")}\n\n${arrayResult}`
|
|
433
|
+
: arrayResult;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Fallback: JSON fence
|
|
438
|
+
const json = JSON.stringify(data, null, 2);
|
|
439
|
+
return "```json\n" + json + "\n```";
|
|
440
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Context — gather branch, status, recent commits for system prompt
|
|
3
|
+
*
|
|
4
|
+
* Extracted from agent-loop.ts for single-responsibility.
|
|
5
|
+
* All consumers should import from agent-loop.ts (re-export facade).
|
|
6
|
+
*/
|
|
7
|
+
export declare function gatherGitContext(): Promise<string>;
|
|
8
|
+
/** Force refresh of git context (e.g. after a commit) */
|
|
9
|
+
export declare function refreshGitContext(): void;
|
|
10
|
+
/** Reset git context cache (called by resetSessionState) */
|
|
11
|
+
export declare function resetGitContext(): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Context — gather branch, status, recent commits for system prompt
|
|
3
|
+
*
|
|
4
|
+
* Extracted from agent-loop.ts for single-responsibility.
|
|
5
|
+
* All consumers should import from agent-loop.ts (re-export facade).
|
|
6
|
+
*/
|
|
7
|
+
import { exec } from "child_process";
|
|
8
|
+
import { promisify } from "util";
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
/** CLI-only: cached git context (cleared by refreshGitContext/resetGitContext) */
|
|
11
|
+
let cachedGitContext = null;
|
|
12
|
+
let gitContextCwd = null;
|
|
13
|
+
export async function gatherGitContext() {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
// Return cached if same cwd
|
|
16
|
+
if (cachedGitContext !== null && gitContextCwd === cwd)
|
|
17
|
+
return cachedGitContext;
|
|
18
|
+
gitContextCwd = cwd;
|
|
19
|
+
try {
|
|
20
|
+
await execAsync("git rev-parse --is-inside-work-tree", { cwd, timeout: 3000 });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
cachedGitContext = "";
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
const parts = [];
|
|
27
|
+
try {
|
|
28
|
+
const { stdout } = await execAsync("git branch --show-current", { cwd, timeout: 3000 });
|
|
29
|
+
const branch = stdout.trim();
|
|
30
|
+
if (branch)
|
|
31
|
+
parts.push(`Branch: ${branch}`);
|
|
32
|
+
}
|
|
33
|
+
catch { /* skip */ }
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execAsync("git status --short 2>/dev/null | head -20", { cwd, timeout: 3000 });
|
|
36
|
+
const status = stdout.trim();
|
|
37
|
+
if (status) {
|
|
38
|
+
const lines = status.split("\n");
|
|
39
|
+
parts.push(`Status: ${lines.length} changed file${lines.length !== 1 ? "s" : ""}`);
|
|
40
|
+
parts.push(status);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
parts.push("Status: clean");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { /* skip */ }
|
|
47
|
+
try {
|
|
48
|
+
const { stdout } = await execAsync("git log --oneline -5 2>/dev/null", { cwd, timeout: 3000 });
|
|
49
|
+
const log = stdout.trim();
|
|
50
|
+
if (log)
|
|
51
|
+
parts.push(`Recent commits:\n${log}`);
|
|
52
|
+
}
|
|
53
|
+
catch { /* skip */ }
|
|
54
|
+
cachedGitContext = parts.length > 0 ? parts.join("\n") : "";
|
|
55
|
+
return cachedGitContext;
|
|
56
|
+
}
|
|
57
|
+
/** Force refresh of git context (e.g. after a commit) */
|
|
58
|
+
export function refreshGitContext() {
|
|
59
|
+
cachedGitContext = null;
|
|
60
|
+
gitContextCwd = null;
|
|
61
|
+
}
|
|
62
|
+
/** Reset git context cache (called by resetSessionState) */
|
|
63
|
+
export function resetGitContext() {
|
|
64
|
+
cachedGitContext = null;
|
|
65
|
+
gitContextCwd = null;
|
|
66
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks System — shell commands that run before/after tool calls and at session lifecycle events.
|
|
3
|
+
*
|
|
4
|
+
* Hooks are configured in:
|
|
5
|
+
* - Project: .whale/hooks.json (array of HookConfig)
|
|
6
|
+
* - User: ~/.swagmanager/hooks.json (array of HookConfig)
|
|
7
|
+
*
|
|
8
|
+
* Both are loaded and merged (project hooks run first).
|
|
9
|
+
*
|
|
10
|
+
* Hook process receives JSON on stdin:
|
|
11
|
+
* { event, tool_name?, tool_input?, tool_output?, tool_success?, session_id? }
|
|
12
|
+
*
|
|
13
|
+
* Hook process may output JSON on stdout:
|
|
14
|
+
* { allow?: boolean, message?: string, modified_input?: object, modified_output?: string }
|
|
15
|
+
*
|
|
16
|
+
* If allow: false, the tool call is blocked and message is returned as the tool result.
|
|
17
|
+
* If hook exits non-zero, it is logged as a warning but does not crash the session.
|
|
18
|
+
*/
|
|
19
|
+
export type HookEvent = "BeforeTool" | "AfterTool" | "SessionStart" | "SessionEnd" | "Notification";
|
|
20
|
+
export interface HookConfig {
|
|
21
|
+
/** Which event triggers this hook */
|
|
22
|
+
event: HookEvent;
|
|
23
|
+
/** Shell command to run */
|
|
24
|
+
command: string;
|
|
25
|
+
/** Optional: only trigger for matching tool names (glob pattern) */
|
|
26
|
+
pattern?: string;
|
|
27
|
+
/** Max ms to wait for hook process (default 10000) */
|
|
28
|
+
timeout?: number;
|
|
29
|
+
/** Can hook modify the tool input/output? (default false) */
|
|
30
|
+
allowModify?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** JSON sent to hook process on stdin */
|
|
33
|
+
interface HookPayload {
|
|
34
|
+
event: HookEvent;
|
|
35
|
+
tool_name?: string;
|
|
36
|
+
tool_input?: Record<string, unknown>;
|
|
37
|
+
tool_output?: string;
|
|
38
|
+
tool_success?: boolean;
|
|
39
|
+
session_id?: string;
|
|
40
|
+
}
|
|
41
|
+
/** JSON output from hook process on stdout */
|
|
42
|
+
interface HookResponse {
|
|
43
|
+
allow?: boolean;
|
|
44
|
+
message?: string;
|
|
45
|
+
modified_input?: Record<string, unknown>;
|
|
46
|
+
modified_output?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Match a tool name against a simple glob pattern.
|
|
50
|
+
* Supports * (any chars) and ? (single char).
|
|
51
|
+
*/
|
|
52
|
+
export declare function matchGlob(pattern: string, name: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Load hooks from project (.whale/hooks.json) and user (~/.swagmanager/hooks.json).
|
|
55
|
+
* Project hooks come first in the array, then user hooks.
|
|
56
|
+
* Invalid files are skipped with a warning.
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadHooks(cwd: string): HookConfig[];
|
|
59
|
+
/**
|
|
60
|
+
* Execute a hook command with JSON payload on stdin.
|
|
61
|
+
* Returns parsed JSON response from stdout, or null on error/timeout.
|
|
62
|
+
*/
|
|
63
|
+
export declare function executeHook(hook: HookConfig, payload: HookPayload): Promise<HookResponse | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Run BeforeTool hooks for a given tool call.
|
|
66
|
+
* Returns whether the tool call should proceed and optionally modified input.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runBeforeToolHook(hooks: HookConfig[], toolName: string, input: Record<string, unknown>): Promise<{
|
|
69
|
+
allow: boolean;
|
|
70
|
+
message?: string;
|
|
71
|
+
modifiedInput?: Record<string, unknown>;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Run AfterTool hooks for a given tool result.
|
|
75
|
+
* Returns optionally modified output.
|
|
76
|
+
*/
|
|
77
|
+
export declare function runAfterToolHook(hooks: HookConfig[], toolName: string, output: string, success: boolean): Promise<{
|
|
78
|
+
modifiedOutput?: string;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Run session lifecycle hooks (SessionStart, SessionEnd, Notification).
|
|
82
|
+
* Fire-and-forget — errors are logged but don't affect session.
|
|
83
|
+
*/
|
|
84
|
+
export declare function runSessionHook(hooks: HookConfig[], event: "SessionStart" | "SessionEnd" | "Notification", data?: Record<string, unknown>): Promise<void>;
|
|
85
|
+
export {};
|