whale-code 6.4.0 → 6.5.1
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 +51 -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 +65 -8
- 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 +7 -6
- 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 +85 -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 +46 -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 +36 -17
- package/dist/server/lib/server-subagent.d.ts +3 -0
- package/dist/server/lib/server-subagent.js +9 -6
- 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 +25 -2
- package/dist/shared/agent-core.js +66 -5
- 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 +15 -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
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Telemetry —
|
|
2
|
+
* CLI Telemetry — spans are buffered and flushed to the Fly.io server,
|
|
3
|
+
* which queues them into ClickHouse ai_spans.
|
|
3
4
|
*
|
|
4
5
|
* Session-scoped conversationId + auto-incrementing turnNumber.
|
|
5
|
-
* Uses same column schema as
|
|
6
|
+
* Uses same column schema as server-side telemetry (trace_id, span_id, etc).
|
|
6
7
|
* Never blocks or crashes the chat.
|
|
7
8
|
*/
|
|
8
|
-
import { createClient } from "@supabase/supabase-js";
|
|
9
9
|
import { createRequire } from "module";
|
|
10
|
+
import os from "node:os";
|
|
10
11
|
import { resolveConfig, loadConfig } from "./config-store.js";
|
|
11
12
|
import { getValidToken, createAuthenticatedClient } from "./auth-service.js";
|
|
12
13
|
import { captureError } from "./error-logger.js";
|
|
@@ -55,39 +56,6 @@ export function generateSpanId() {
|
|
|
55
56
|
return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
56
57
|
}
|
|
57
58
|
// ============================================================================
|
|
58
|
-
// SUPABASE CLIENT (lazy init)
|
|
59
|
-
// ============================================================================
|
|
60
|
-
async function getClient() {
|
|
61
|
-
if (supabaseClient)
|
|
62
|
-
return supabaseClient;
|
|
63
|
-
const config = resolveConfig();
|
|
64
|
-
// Prefer service role key
|
|
65
|
-
if (config.supabaseUrl && config.supabaseKey) {
|
|
66
|
-
supabaseClient = createClient(config.supabaseUrl, config.supabaseKey, {
|
|
67
|
-
auth: { persistSession: false, autoRefreshToken: false },
|
|
68
|
-
});
|
|
69
|
-
if (process.env.DEBUG_TELEMETRY) {
|
|
70
|
-
process.stderr.write(`[telemetry] using service role key\n`);
|
|
71
|
-
}
|
|
72
|
-
return supabaseClient;
|
|
73
|
-
}
|
|
74
|
-
// Fallback: user JWT
|
|
75
|
-
const token = await getValidToken();
|
|
76
|
-
if (token) {
|
|
77
|
-
supabaseClient = createAuthenticatedClient(token);
|
|
78
|
-
if (process.env.DEBUG_TELEMETRY) {
|
|
79
|
-
process.stderr.write(`[telemetry] using user JWT token\n`);
|
|
80
|
-
}
|
|
81
|
-
return supabaseClient;
|
|
82
|
-
}
|
|
83
|
-
if (process.env.DEBUG_TELEMETRY) {
|
|
84
|
-
process.stderr.write(`[telemetry] NO CLIENT - no service key and no valid token\n`);
|
|
85
|
-
process.stderr.write(`[telemetry] config.supabaseUrl: ${config.supabaseUrl}\n`);
|
|
86
|
-
process.stderr.write(`[telemetry] config.supabaseKey: ${config.supabaseKey ? 'set' : 'not set'}\n`);
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
// ============================================================================
|
|
91
59
|
// TURN CONTEXT
|
|
92
60
|
// ============================================================================
|
|
93
61
|
export function nextTurn() {
|
|
@@ -113,6 +81,93 @@ export function createTurnContext(overrides) {
|
|
|
113
81
|
export function getTurnNumber() {
|
|
114
82
|
return turnNumber;
|
|
115
83
|
}
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// SPAN BUFFER — batches spans for bulk POST to the Fly.io server
|
|
86
|
+
// Same pattern as server's clickhouse-buffer.ts but over HTTP.
|
|
87
|
+
// ============================================================================
|
|
88
|
+
const FLUSH_INTERVAL = 2000; // 2s (slower than server — HTTP has more overhead)
|
|
89
|
+
const FLUSH_MAX = 50; // max spans before force flush
|
|
90
|
+
const spanBuffer = [];
|
|
91
|
+
let flushTimer = null;
|
|
92
|
+
let conversationRegistered = false;
|
|
93
|
+
function queueCliSpan(span) {
|
|
94
|
+
spanBuffer.push(span);
|
|
95
|
+
if (spanBuffer.length >= FLUSH_MAX) {
|
|
96
|
+
flushCliSpans();
|
|
97
|
+
}
|
|
98
|
+
else if (!flushTimer) {
|
|
99
|
+
flushTimer = setTimeout(() => {
|
|
100
|
+
flushTimer = null;
|
|
101
|
+
flushCliSpans();
|
|
102
|
+
}, FLUSH_INTERVAL);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Flush all buffered spans to the Fly.io server.
|
|
107
|
+
* Call this on session end or at shutdown.
|
|
108
|
+
*/
|
|
109
|
+
export function flushCliSpans() {
|
|
110
|
+
if (flushTimer) {
|
|
111
|
+
clearTimeout(flushTimer);
|
|
112
|
+
flushTimer = null;
|
|
113
|
+
}
|
|
114
|
+
if (spanBuffer.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
const batch = spanBuffer.splice(0, spanBuffer.length);
|
|
117
|
+
// Fire-and-forget — never block the chat
|
|
118
|
+
_sendSpans(batch).catch((err) => {
|
|
119
|
+
if (process.env.DEBUG_TELEMETRY) {
|
|
120
|
+
process.stderr.write(`[telemetry] flush error: ${err.message}\n`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function _sendSpans(spans) {
|
|
125
|
+
const config = resolveConfig();
|
|
126
|
+
if (!config.serverUrl)
|
|
127
|
+
return;
|
|
128
|
+
let authToken = config.supabaseKey;
|
|
129
|
+
if (!authToken) {
|
|
130
|
+
authToken = await getValidToken() || "";
|
|
131
|
+
}
|
|
132
|
+
if (!authToken)
|
|
133
|
+
return;
|
|
134
|
+
const fileConfig = loadConfig();
|
|
135
|
+
const body = {
|
|
136
|
+
mode: "telemetry_ingest",
|
|
137
|
+
spans,
|
|
138
|
+
conversation_id: conversationId,
|
|
139
|
+
store_id: config.storeId || undefined,
|
|
140
|
+
userId: fileConfig.user_id || undefined,
|
|
141
|
+
userEmail: fileConfig.email || undefined,
|
|
142
|
+
source: "whale_cli",
|
|
143
|
+
};
|
|
144
|
+
// Include conversation metadata on first flush so server creates the row
|
|
145
|
+
if (!conversationRegistered) {
|
|
146
|
+
conversationRegistered = true;
|
|
147
|
+
body.conversation_title = `CLI Session ${new Date().toISOString().split("T")[0]}`;
|
|
148
|
+
body.hostname = os.hostname();
|
|
149
|
+
body.version = PKG_VERSION;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(config.serverUrl, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
"Authorization": `Bearer ${authToken}`,
|
|
157
|
+
},
|
|
158
|
+
body: JSON.stringify(body),
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok && process.env.DEBUG_TELEMETRY) {
|
|
161
|
+
const text = await response.text().catch(() => "");
|
|
162
|
+
process.stderr.write(`[telemetry] ingest failed (${response.status}): ${text.slice(0, 200)}\n`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
if (process.env.DEBUG_TELEMETRY) {
|
|
167
|
+
process.stderr.write(`[telemetry] ingest error: ${err.message}\n`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
116
171
|
export function logSpan(opts) {
|
|
117
172
|
// Fire-and-forget — don't await, log errors in debug mode
|
|
118
173
|
_logSpan(opts).catch((err) => {
|
|
@@ -125,74 +180,43 @@ async function _logSpan(opts) {
|
|
|
125
180
|
if (process.env.DEBUG_TELEMETRY) {
|
|
126
181
|
process.stderr.write(`[telemetry] _logSpan called for ${opts.action}\n`);
|
|
127
182
|
}
|
|
128
|
-
const client = await getClient();
|
|
129
|
-
if (!client) {
|
|
130
|
-
if (process.env.DEBUG_TELEMETRY) {
|
|
131
|
-
process.stderr.write(`[telemetry] no client for ${opts.action}\n`);
|
|
132
|
-
}
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const now = new Date();
|
|
136
|
-
const startTime = new Date(now.getTime() - opts.durationMs);
|
|
137
183
|
const ctx = opts.context;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
const row = {
|
|
184
|
+
const now = new Date();
|
|
185
|
+
const startedAt = new Date(now.getTime() - opts.durationMs).toISOString();
|
|
186
|
+
const endedAt = now.toISOString();
|
|
187
|
+
// Build span in same shape as server's auditRowToSpan input
|
|
188
|
+
const span = {
|
|
145
189
|
action: opts.action,
|
|
146
190
|
severity: opts.severity || (opts.error ? "error" : "info"),
|
|
147
191
|
store_id: opts.storeId || resolveConfig().storeId || null,
|
|
192
|
+
source: ctx.source || "whale_cli",
|
|
193
|
+
service_name: ctx.serviceName || "whale-code",
|
|
194
|
+
span_kind: "INTERNAL",
|
|
195
|
+
status_code: opts.error ? "ERROR" : "OK",
|
|
196
|
+
trace_id: ctx.traceId || null,
|
|
197
|
+
span_id: ctx.spanId || generateSpanId(),
|
|
198
|
+
conversation_id: ctx.conversationId || conversationId,
|
|
148
199
|
user_id: ctx.userId || null,
|
|
149
200
|
user_email: ctx.userEmail || null,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
request_id: ctx.traceId,
|
|
153
|
-
parent_id: ctx.parentId || null,
|
|
201
|
+
start_time: startedAt,
|
|
202
|
+
end_time: endedAt,
|
|
154
203
|
duration_ms: opts.durationMs,
|
|
155
204
|
error_message: opts.error || null,
|
|
156
|
-
// OTEL columns
|
|
157
|
-
trace_id: ctx.traceId,
|
|
158
|
-
span_id: ctx.spanId,
|
|
159
|
-
trace_flags: ctx.traceFlags ?? 1,
|
|
160
|
-
span_kind: "INTERNAL",
|
|
161
|
-
service_name: ctx.serviceName || "whale-code",
|
|
162
|
-
service_version: ctx.serviceVersion || PKG_VERSION,
|
|
163
|
-
status_code: opts.error ? "ERROR" : "OK",
|
|
164
|
-
start_time: startTime.toISOString(),
|
|
165
|
-
end_time: now.toISOString(),
|
|
166
|
-
// AI telemetry — use ?? to handle 0 correctly
|
|
167
|
-
model: ctx.model || null,
|
|
168
|
-
input_tokens: ctx.inputTokens ?? null,
|
|
169
|
-
output_tokens: ctx.outputTokens ?? null,
|
|
170
|
-
total_cost: ctx.totalCost ?? null,
|
|
171
|
-
turn_number: ctx.turnNumber ?? null,
|
|
172
|
-
conversation_id: ctx.conversationId || null,
|
|
173
205
|
details: {
|
|
174
|
-
source: ctx.source || "whale_cli",
|
|
175
|
-
conversation_id: ctx.conversationId || conversationId,
|
|
176
|
-
turn_number: ctx.turnNumber ?? turnNumber,
|
|
177
|
-
parent_span_id: ctx.parentSpanId || null,
|
|
178
206
|
...opts.details,
|
|
207
|
+
input_tokens: ctx.inputTokens,
|
|
208
|
+
output_tokens: ctx.outputTokens,
|
|
209
|
+
total_cost: ctx.totalCost,
|
|
210
|
+
model: ctx.model,
|
|
211
|
+
turn_number: ctx.turnNumber,
|
|
212
|
+
agent_id: ctx.agentId,
|
|
213
|
+
agent_name: ctx.agentName,
|
|
214
|
+
tool_type: ctx.toolType,
|
|
179
215
|
},
|
|
180
216
|
};
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const { error } = await client.from("audit_logs").insert(row);
|
|
185
|
-
if (error) {
|
|
186
|
-
if (process.env.DEBUG_TELEMETRY) {
|
|
187
|
-
process.stderr.write(`[telemetry db error] ${opts.action}: ${error.message}\n`);
|
|
188
|
-
process.stderr.write(`[telemetry db error] code: ${error.code}\n`);
|
|
189
|
-
process.stderr.write(`[telemetry db error] hint: ${error.hint}\n`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else if (opts.details?.is_teammate && process.env.DEBUG_TELEMETRY) {
|
|
193
|
-
process.stderr.write(`[telemetry] teammate span logged: ${opts.action}\n`);
|
|
194
|
-
}
|
|
195
|
-
// Bridge errors to the error logging system
|
|
217
|
+
// Queue for batch send to server → ClickHouse
|
|
218
|
+
queueCliSpan(span);
|
|
219
|
+
// Also bridge errors to the error logging system (Postgres)
|
|
196
220
|
if (opts.error) {
|
|
197
221
|
captureError({
|
|
198
222
|
errorType: opts.action,
|
|
@@ -12,3 +12,4 @@ export declare function taskTool(input: Record<string, unknown>): Promise<ToolRe
|
|
|
12
12
|
export declare function taskOutput(input: Record<string, unknown>): Promise<ToolResult>;
|
|
13
13
|
export declare function taskStop(input: Record<string, unknown>): ToolResult;
|
|
14
14
|
export declare function teamCreateTool(input: Record<string, unknown>): Promise<ToolResult>;
|
|
15
|
+
export declare function teamAutoTool(input: Record<string, unknown>): Promise<ToolResult>;
|
|
@@ -8,7 +8,7 @@ import { dirname, join } from "path";
|
|
|
8
8
|
import { homedir } from "os";
|
|
9
9
|
import { runSubagent, runSubagentBackground, } from "../subagent.js";
|
|
10
10
|
import { createTurnContext, getTurnNumber, } from "../telemetry.js";
|
|
11
|
-
import { runAgentTeam, } from "../team-lead.js";
|
|
11
|
+
import { runAgentTeam, runAutoTeam, } from "../team-lead.js";
|
|
12
12
|
import { readAgentOutput, stopBackgroundAgent } from "../background-processes.js";
|
|
13
13
|
import { readProcessOutput, killProcess } from "../background-processes.js";
|
|
14
14
|
import { getGlobalEmitter } from "../agent-events.js";
|
|
@@ -244,13 +244,13 @@ export async function taskOutput(input) {
|
|
|
244
244
|
await new Promise(r => setTimeout(r, 1000));
|
|
245
245
|
const updated = readAgentOutput(taskId);
|
|
246
246
|
if (updated && updated.status !== "running") {
|
|
247
|
-
return { success: true, output:
|
|
247
|
+
return { success: true, output: updated.output };
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
const final = readAgentOutput(taskId);
|
|
251
|
-
return { success: true, output:
|
|
251
|
+
return { success: true, output: `${final?.output || ""}\n(timed out waiting — agent may still be running)`.trim() };
|
|
252
252
|
}
|
|
253
|
-
return { success: true, output:
|
|
253
|
+
return { success: true, output: agentResult.output };
|
|
254
254
|
}
|
|
255
255
|
// Fall back to shell process output (bash_output behavior)
|
|
256
256
|
const result = readProcessOutput(taskId, {});
|
|
@@ -345,3 +345,49 @@ export async function teamCreateTool(input) {
|
|
|
345
345
|
};
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// TEAM AUTO — auto-decompose + parallel execution + review
|
|
350
|
+
// ============================================================================
|
|
351
|
+
export async function teamAutoTool(input) {
|
|
352
|
+
const task = input.task;
|
|
353
|
+
if (!task)
|
|
354
|
+
return { success: false, output: "task is required" };
|
|
355
|
+
const maxTeammates = input.max_teammates || 4;
|
|
356
|
+
const model = input.model || "sonnet";
|
|
357
|
+
const workingDirectory = input.working_directory || process.cwd();
|
|
358
|
+
const review = input.review !== false;
|
|
359
|
+
try {
|
|
360
|
+
const result = await runAutoTeam(task, {
|
|
361
|
+
maxTeammates,
|
|
362
|
+
model,
|
|
363
|
+
workingDirectory,
|
|
364
|
+
review,
|
|
365
|
+
});
|
|
366
|
+
const lines = [
|
|
367
|
+
`## Auto Team`,
|
|
368
|
+
`Status: ${result.success ? "SUCCESS" : "PARTIAL"}`,
|
|
369
|
+
`Duration: ${(result.durationMs / 1000).toFixed(1)}s`,
|
|
370
|
+
`Tokens: ${result.tokensUsed.input} in, ${result.tokensUsed.output} out`,
|
|
371
|
+
];
|
|
372
|
+
if (result.warnings?.length) {
|
|
373
|
+
lines.push("", "### Warnings");
|
|
374
|
+
for (const w of result.warnings)
|
|
375
|
+
lines.push(`- ${w}`);
|
|
376
|
+
}
|
|
377
|
+
lines.push("", "### Task Results");
|
|
378
|
+
for (const t of result.taskResults) {
|
|
379
|
+
const icon = t.status === "completed" ? "[done]" : "[fail]";
|
|
380
|
+
lines.push(`${icon} ${t.description.slice(0, 120)}`);
|
|
381
|
+
if (t.result) {
|
|
382
|
+
lines.push(` ${t.result.slice(0, 200)}${t.result.length > 200 ? "..." : ""}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (result.review) {
|
|
386
|
+
lines.push("", "### Review", result.review);
|
|
387
|
+
}
|
|
388
|
+
return { success: result.success, output: lines.join("\n") };
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
return { success: false, output: `Auto team failed: ${err.message || err}` };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ToolResult } from "../../../shared/types.js";
|
|
7
7
|
export declare function resolvePath(p: string): string;
|
|
8
|
+
/** Clear the session-level read cache. Call on session reset. */
|
|
9
|
+
export declare function clearReadCache(): void;
|
|
8
10
|
export declare function readFile(input: Record<string, unknown>): Promise<ToolResult>;
|
|
9
11
|
export declare function writeFile(input: Record<string, unknown>): ToolResult;
|
|
10
12
|
export declare function editFile(input: Record<string, unknown>): ToolResult;
|
|
@@ -15,6 +15,36 @@ export function resolvePath(p) {
|
|
|
15
15
|
return join(homedir(), p.slice(2));
|
|
16
16
|
return p;
|
|
17
17
|
}
|
|
18
|
+
const READ_CACHE_MAX = 100;
|
|
19
|
+
const MAX_ENTRY_SIZE = 100_000; // 100KB — skip caching larger files
|
|
20
|
+
const MAX_CACHE_BYTES = 10_000_000; // 10MB total budget
|
|
21
|
+
let totalCacheBytes = 0;
|
|
22
|
+
const readCache = new Map();
|
|
23
|
+
/** Clear the session-level read cache. Call on session reset. */
|
|
24
|
+
export function clearReadCache() {
|
|
25
|
+
readCache.clear();
|
|
26
|
+
totalCacheBytes = 0;
|
|
27
|
+
}
|
|
28
|
+
/** Invalidate a specific path from the cache (call on write/edit). */
|
|
29
|
+
function invalidateCache(path) {
|
|
30
|
+
const existing = readCache.get(path);
|
|
31
|
+
if (existing) {
|
|
32
|
+
totalCacheBytes -= existing.content.length;
|
|
33
|
+
readCache.delete(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** LRU eviction: remove oldest entries when cache exceeds count or byte budget. */
|
|
37
|
+
function evictIfNeeded() {
|
|
38
|
+
while (readCache.size >= READ_CACHE_MAX || totalCacheBytes > MAX_CACHE_BYTES) {
|
|
39
|
+
const oldest = readCache.keys().next().value;
|
|
40
|
+
if (!oldest)
|
|
41
|
+
break;
|
|
42
|
+
const entry = readCache.get(oldest);
|
|
43
|
+
if (entry)
|
|
44
|
+
totalCacheBytes -= entry.content.length;
|
|
45
|
+
readCache.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
18
48
|
// ============================================================================
|
|
19
49
|
// READ FILE
|
|
20
50
|
// ============================================================================
|
|
@@ -30,6 +60,28 @@ const AUDIO_MEDIA_TYPES = {
|
|
|
30
60
|
m4a: "audio/mp4",
|
|
31
61
|
};
|
|
32
62
|
const AUDIO_MAX_SIZE = 25 * 1024 * 1024; // 25MB
|
|
63
|
+
function formatTextFileResult(lines, input, content) {
|
|
64
|
+
const offset = input.offset || 1; // 1-based
|
|
65
|
+
const limit = input.limit;
|
|
66
|
+
if (offset > 1 || limit) {
|
|
67
|
+
const startIdx = Math.max(0, offset - 1);
|
|
68
|
+
const endIdx = limit ? startIdx + limit : lines.length;
|
|
69
|
+
const slice = lines.slice(startIdx, endIdx);
|
|
70
|
+
const numbered = slice.map((line, i) => {
|
|
71
|
+
const lineNum = startIdx + i + 1;
|
|
72
|
+
return `${String(lineNum).padStart(6)} ${line}`;
|
|
73
|
+
});
|
|
74
|
+
let output = numbered.join("\n");
|
|
75
|
+
if (endIdx < lines.length) {
|
|
76
|
+
output += `\n\n... (showing lines ${startIdx + 1}-${Math.min(endIdx, lines.length)} of ${lines.length})`;
|
|
77
|
+
}
|
|
78
|
+
return { success: true, output };
|
|
79
|
+
}
|
|
80
|
+
if (content.length > 500_000) {
|
|
81
|
+
return { success: true, output: content.slice(0, 500_000) + `\n\n... (safety truncated, ${content.length.toLocaleString()} total chars)` };
|
|
82
|
+
}
|
|
83
|
+
return { success: true, output: content };
|
|
84
|
+
}
|
|
33
85
|
export async function readFile(input) {
|
|
34
86
|
const path = resolvePath(input.path);
|
|
35
87
|
if (!existsSync(path))
|
|
@@ -90,29 +142,39 @@ export async function readFile(input) {
|
|
|
90
142
|
return { success: false, output: `Failed to parse PDF: ${err}` };
|
|
91
143
|
}
|
|
92
144
|
}
|
|
93
|
-
// Text files —
|
|
145
|
+
// Text files — check cache first via mtime comparison
|
|
146
|
+
try {
|
|
147
|
+
const stat = statSync(path);
|
|
148
|
+
const cached = readCache.get(path);
|
|
149
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
150
|
+
// Cache hit — use previously read content
|
|
151
|
+
debugLog("file-ops", `readFile cache hit: ${path}`);
|
|
152
|
+
const content = cached.content;
|
|
153
|
+
const lines = content.split("\n");
|
|
154
|
+
// Move to end of map for LRU freshness
|
|
155
|
+
readCache.delete(path);
|
|
156
|
+
readCache.set(path, cached);
|
|
157
|
+
return formatTextFileResult(lines, input, content);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// stat failed — fall through to normal read
|
|
162
|
+
}
|
|
94
163
|
const content = readFileSync(path, "utf-8");
|
|
95
164
|
const lines = content.split("\n");
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let output = numbered.join("\n");
|
|
107
|
-
if (endIdx < lines.length) {
|
|
108
|
-
output += `\n\n... (showing lines ${startIdx + 1}-${Math.min(endIdx, lines.length)} of ${lines.length})`;
|
|
165
|
+
// Cache the read — skip caching files larger than MAX_ENTRY_SIZE
|
|
166
|
+
if (content.length <= MAX_ENTRY_SIZE) {
|
|
167
|
+
try {
|
|
168
|
+
const stat = statSync(path);
|
|
169
|
+
evictIfNeeded();
|
|
170
|
+
readCache.set(path, { content, mtimeMs: stat.mtimeMs, size: stat.size });
|
|
171
|
+
totalCacheBytes += content.length;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// stat failed — skip caching
|
|
109
175
|
}
|
|
110
|
-
return { success: true, output };
|
|
111
|
-
}
|
|
112
|
-
if (content.length > 500_000) {
|
|
113
|
-
return { success: true, output: content.slice(0, 500_000) + `\n\n... (safety truncated, ${content.length.toLocaleString()} total chars)` };
|
|
114
176
|
}
|
|
115
|
-
return
|
|
177
|
+
return formatTextFileResult(lines, input, content);
|
|
116
178
|
}
|
|
117
179
|
function parsePageRange(range, totalPages) {
|
|
118
180
|
const parts = range.split("-");
|
|
@@ -188,6 +250,7 @@ function computeWriteDiff(oldLines, newLines) {
|
|
|
188
250
|
export function writeFile(input) {
|
|
189
251
|
const path = resolvePath(input.path);
|
|
190
252
|
const content = input.content;
|
|
253
|
+
invalidateCache(path); // Invalidate read cache before write
|
|
191
254
|
const existed = existsSync(path);
|
|
192
255
|
const oldContent = existed ? readFileSync(path, "utf-8") : null;
|
|
193
256
|
backupFile(path); // Save backup before modification
|
|
@@ -236,6 +299,7 @@ export function editFile(input) {
|
|
|
236
299
|
const replaceAll = input.replace_all ?? false;
|
|
237
300
|
if (!existsSync(path))
|
|
238
301
|
return { success: false, output: `File not found: ${path}` };
|
|
302
|
+
invalidateCache(path); // Invalidate read cache before edit
|
|
239
303
|
backupFile(path); // Save backup before modification
|
|
240
304
|
let content = readFileSync(path, "utf-8");
|
|
241
305
|
if (!content.includes(oldString))
|
|
@@ -308,6 +372,7 @@ export function multiEdit(input) {
|
|
|
308
372
|
return { success: false, output: `File not found: ${path}` };
|
|
309
373
|
if (!Array.isArray(edits) || edits.length === 0)
|
|
310
374
|
return { success: false, output: "edits array is required and must not be empty" };
|
|
375
|
+
invalidateCache(path); // Invalidate read cache before edit
|
|
311
376
|
backupFile(path); // Save backup before modification
|
|
312
377
|
let content = readFileSync(path, "utf-8");
|
|
313
378
|
const diffParts = [];
|
|
@@ -371,6 +436,7 @@ export function notebookEdit(input) {
|
|
|
371
436
|
const cellId = input.cell_id;
|
|
372
437
|
if (!existsSync(path))
|
|
373
438
|
return { success: false, output: `Notebook not found: ${path}` };
|
|
439
|
+
invalidateCache(path); // Invalidate read cache before notebook edit
|
|
374
440
|
let notebook;
|
|
375
441
|
try {
|
|
376
442
|
notebook = JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -22,24 +22,34 @@ export async function runCommand(input) {
|
|
|
22
22
|
const background = input.run_in_background;
|
|
23
23
|
const description = input.description;
|
|
24
24
|
debugLog("tools", `run_command: ${description || command.slice(0, 80)}`, { cwd, timeout, background });
|
|
25
|
-
//
|
|
26
|
-
//
|
|
25
|
+
// ── Safety guardrail ──
|
|
26
|
+
// Minimal filter for catastrophic commands that bypass the macOS sandbox.
|
|
27
|
+
// The sandbox (sandbox-exec) is the real security boundary — it restricts
|
|
28
|
+
// file writes to cwd + /tmp + ~/.swagmanager only.
|
|
29
|
+
//
|
|
30
|
+
// Philosophy: block ONLY what the sandbox cannot prevent.
|
|
31
|
+
// - Hardware/filesystem damage (mkfs, dd to block devices)
|
|
32
|
+
// - Process-level DoS (fork bomb)
|
|
33
|
+
// - rm on literal root / or home ~ (sanity check)
|
|
34
|
+
//
|
|
35
|
+
// Everything else is allowed — chmod, chown, curl|bash, npm, pip, etc.
|
|
36
|
+
// are either harmless within the sandbox or need sudo to do real damage.
|
|
27
37
|
if (command.length > 10000) {
|
|
28
38
|
return { success: false, output: "Command too long (max 10000 chars)" };
|
|
29
39
|
}
|
|
30
40
|
const DANGEROUS_PATTERNS = [
|
|
31
|
-
|
|
32
|
-
/\brm\
|
|
41
|
+
// rm on literal root (/) or (/*) — NOT subdirectories like /tmp, /Users/...
|
|
42
|
+
/\brm\b[^|;]*\s\/(\*)?(\s*$|\s*[;|&])/i,
|
|
43
|
+
// rm on literal home (~) or (~/) or (~/*) — NOT subdirs like ~/project
|
|
44
|
+
/\brm\b[^|;]*\s~\/?(\*)?(\s*$|\s*[;|&])/i,
|
|
45
|
+
// Format filesystem — irreversible hardware damage
|
|
33
46
|
/\bmkfs\b/i,
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
// dd writing to raw block devices — irreversible hardware damage
|
|
48
|
+
/\bdd\b.*\bof=\s*\/dev\/(sd|hd|nvme|disk|rdisk|vd)/i,
|
|
49
|
+
// Redirect to raw block devices
|
|
50
|
+
/>\s*\/dev\/(sd|hd|nvme|disk|rdisk|vd)/,
|
|
51
|
+
// Fork bomb — process-level DoS, sandbox cannot prevent
|
|
36
52
|
/:\(\)\s*\{.*\|.*&\s*\}\s*;/,
|
|
37
|
-
/\bchmod\s+(-[a-z]*R[a-z]*\s+)?777\s+\//i,
|
|
38
|
-
/\bchown\s+(-[a-z]*R[a-z]*\s+).*\//i,
|
|
39
|
-
/\bcurl\b.*\|\s*(ba)?sh\b/i,
|
|
40
|
-
/\bwget\b.*\|\s*(ba)?sh\b/i,
|
|
41
|
-
/\b(python|perl|ruby|node)\s+-e\s+.*\b(system|exec|spawn)\b/i,
|
|
42
|
-
/base64\s+(-d|--decode)\s*\|\s*(ba)?sh/i,
|
|
43
53
|
];
|
|
44
54
|
const command_lower = command.toLowerCase().replace(/\s+/g, " ");
|
|
45
55
|
if (DANGEROUS_PATTERNS.some((p) => p.test(command_lower))) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Override any color via ~/.swagmanager/theme.json:
|
|
9
9
|
* { "brand": "#FF6600", "text": "#E0E0E0" }
|
|
10
10
|
*/
|
|
11
|
-
declare const DEFAULT_COLORS: {
|
|
11
|
+
export declare const DEFAULT_COLORS: {
|
|
12
12
|
brand: string;
|
|
13
13
|
brandDim: string;
|
|
14
14
|
success: string;
|
|
@@ -71,4 +71,3 @@ export declare const symbols: {
|
|
|
71
71
|
export declare function boxLine(width: number): string;
|
|
72
72
|
export declare function boxTop(width: number): string;
|
|
73
73
|
export declare function boxBottom(width: number): string;
|
|
74
|
-
export {};
|
package/dist/cli/shared/Theme.js
CHANGED
|
@@ -14,7 +14,7 @@ import { homedir } from "os";
|
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// COLORS — macOS system palette (dark appearance)
|
|
16
16
|
// ============================================================================
|
|
17
|
-
const DEFAULT_COLORS = {
|
|
17
|
+
export const DEFAULT_COLORS = {
|
|
18
18
|
// Accents
|
|
19
19
|
brand: "#0A84FF", // systemBlue
|
|
20
20
|
brandDim: "#0071E3", // Apple link blue
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* whale code banner —
|
|
2
|
+
* whale code banner — ASCII art header
|
|
3
|
+
*
|
|
4
|
+
* Auto-switches to compact mode when terminal is too narrow for ASCII art.
|
|
5
|
+
* Each line rendered separately with wrap="truncate" to prevent reflow on resize.
|
|
3
6
|
*/
|
|
4
7
|
interface WhaleBannerProps {
|
|
5
8
|
subtitle?: string;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { colors
|
|
3
|
+
import { colors } from "./Theme.js";
|
|
4
|
+
const ASCII_LINES = [
|
|
5
|
+
" ╦ ╦╦ ╦╔═╗╦ ╔═╗ ╔═╗╔═╗╔╦╗╔═╗",
|
|
6
|
+
" ║║║╠═╣╠═╣║ ║╣ ║ ║ ║ ║║║╣ ",
|
|
7
|
+
" ╚╩╝╩ ╩╩ ╩╩═╝╚═╝ ╚═╝╚═╝═╩╝╚═╝",
|
|
8
|
+
];
|
|
9
|
+
const MIN_WIDTH_FOR_ART = 38;
|
|
4
10
|
export function WhaleBanner({ subtitle, version, compact }) {
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
const termWidth = process.stdout.columns || 80;
|
|
12
|
+
if (compact || termWidth < MIN_WIDTH_FOR_ART) {
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: colors.brand, bold: true, children: " \u25C6 whale code" }), version && _jsxs(Text, { color: colors.dim, children: [" v", version] }), subtitle && _jsxs(Text, { color: colors.muted, children: [" ", subtitle] })] })] }));
|
|
7
14
|
}
|
|
8
|
-
|
|
9
|
-
const titleLine = `${symbols.sparkle} whale code` + (version ? ` v${version}` : "");
|
|
10
|
-
const titlePad = Math.max(0, Math.floor((width - 2 - titleLine.length) / 2));
|
|
11
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.border, children: boxTop(width) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.border, children: symbols.verticalBar }), _jsx(Text, { children: " ".repeat(width - 2) }), _jsx(Text, { color: colors.border, children: symbols.verticalBar })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.border, children: symbols.verticalBar }), _jsx(Text, { children: " ".repeat(titlePad) }), _jsxs(Text, { color: colors.brand, bold: true, children: [symbols.sparkle, " whale code"] }), version && _jsxs(Text, { color: colors.dim, children: [" v", version] }), _jsx(Text, { children: " ".repeat(Math.max(0, width - 2 - titlePad - titleLine.length)) }), _jsx(Text, { color: colors.border, children: symbols.verticalBar })] }), subtitle && (_jsxs(Box, { children: [_jsx(Text, { color: colors.border, children: symbols.verticalBar }), _jsx(Text, { children: " ".repeat(titlePad) }), _jsx(Text, { color: colors.muted, children: subtitle }), _jsx(Text, { children: " ".repeat(Math.max(0, width - 2 - titlePad - subtitle.length)) }), _jsx(Text, { color: colors.border, children: symbols.verticalBar })] })), _jsxs(Box, { children: [_jsx(Text, { color: colors.border, children: symbols.verticalBar }), _jsx(Text, { children: " ".repeat(width - 2) }), _jsx(Text, { color: colors.border, children: symbols.verticalBar })] }), _jsx(Text, { color: colors.border, children: boxBottom(width) })] }));
|
|
15
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), ASCII_LINES.map((line, i) => (_jsx(Text, { color: colors.info, bold: true, wrap: "truncate", children: line }, i)))] }));
|
|
12
16
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Markdown rendering —
|
|
2
|
+
* Markdown rendering — polished terminal output with shiki syntax highlighting
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Uses marked + marked-terminal
|
|
4
|
+
* Uses shiki for VS Code-quality syntax highlighting.
|
|
5
|
+
* All chalk instances are rebuilt on theme switch via rebuildMarkdownRenderer().
|
|
6
|
+
* Uses marked + marked-terminal for markdown structure.
|
|
7
7
|
*/
|
|
8
8
|
/** Width for top-level assistant text (accounts for MessageList marginLeft=2) */
|
|
9
9
|
export declare function contentWidth(): number;
|
|
10
10
|
/** Width for content nested inside tool results (MessageList=2 + ToolIndicator=2 + safety=2) */
|
|
11
11
|
export declare function toolContentWidth(): number;
|
|
12
|
+
export declare function rebuildMarkdownRenderer(): void;
|
|
12
13
|
/**
|
|
13
14
|
* Close incomplete markdown fences for safe streaming rendering.
|
|
14
15
|
* State-tracking approach: handles nested fences, escaped markers, double-backtick spans.
|