@townco/ui 0.1.43 → 0.1.45
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/dist/core/hooks/use-chat-messages.js +11 -1
- package/dist/core/store/chat-store.d.ts +2 -0
- package/dist/core/store/chat-store.js +3 -0
- package/dist/gui/components/Button.d.ts +1 -1
- package/dist/gui/components/ChatView.js +3 -6
- package/dist/sdk/schemas/message.d.ts +1 -0
- package/dist/sdk/schemas/message.js +1 -0
- package/dist/sdk/transports/http.js +8 -0
- package/dist/sdk/transports/stdio.d.ts +1 -0
- package/dist/sdk/transports/stdio.js +55 -0
- package/package.json +3 -3
- package/dist/core/lib/logger.d.ts +0 -59
- package/dist/core/lib/logger.js +0 -191
- package/dist/tui/components/LogsPanel.d.ts +0 -5
- package/dist/tui/components/LogsPanel.js +0 -29
|
@@ -14,6 +14,7 @@ export function useChatMessages(client, startSession) {
|
|
|
14
14
|
const addMessage = useChatStore((state) => state.addMessage);
|
|
15
15
|
const updateMessage = useChatStore((state) => state.updateMessage);
|
|
16
16
|
const setError = useChatStore((state) => state.setError);
|
|
17
|
+
const setLatestContextInputTokens = useChatStore((state) => state.setLatestContextInputTokens);
|
|
17
18
|
/**
|
|
18
19
|
* Send a message to the agent
|
|
19
20
|
*/
|
|
@@ -78,8 +79,17 @@ export function useChatMessages(client, startSession) {
|
|
|
78
79
|
let accumulatedContent = "";
|
|
79
80
|
let streamCompleted = false;
|
|
80
81
|
for await (const chunk of messageStream) {
|
|
82
|
+
// Update context input tokens if provided
|
|
83
|
+
if ("contextInputTokens" in chunk &&
|
|
84
|
+
chunk.contextInputTokens != null) {
|
|
85
|
+
const tokens = chunk.contextInputTokens;
|
|
86
|
+
logger.debug("Received contextInputTokens from backend", {
|
|
87
|
+
contextInputTokens: tokens,
|
|
88
|
+
});
|
|
89
|
+
setLatestContextInputTokens(tokens);
|
|
90
|
+
}
|
|
81
91
|
if (chunk.tokenUsage) {
|
|
82
|
-
logger.debug("
|
|
92
|
+
logger.debug("Received tokenUsage from backend", {
|
|
83
93
|
tokenUsage: chunk.tokenUsage,
|
|
84
94
|
});
|
|
85
95
|
}
|
|
@@ -22,6 +22,7 @@ export interface ChatStore {
|
|
|
22
22
|
outputTokens: number;
|
|
23
23
|
totalTokens: number;
|
|
24
24
|
};
|
|
25
|
+
latestContextInputTokens: number | null;
|
|
25
26
|
currentModel: string | null;
|
|
26
27
|
tokenDisplayMode: "context" | "input" | "output";
|
|
27
28
|
logs: LogEntry[];
|
|
@@ -49,6 +50,7 @@ export interface ChatStore {
|
|
|
49
50
|
outputTokens?: number;
|
|
50
51
|
totalTokens?: number;
|
|
51
52
|
}) => void;
|
|
53
|
+
setLatestContextInputTokens: (tokens: number | null) => void;
|
|
52
54
|
setCurrentModel: (model: string) => void;
|
|
53
55
|
resetTokens: () => void;
|
|
54
56
|
cycleTokenDisplayMode: () => void;
|
|
@@ -24,6 +24,7 @@ export const useChatStore = create((set) => ({
|
|
|
24
24
|
outputTokens: 0,
|
|
25
25
|
totalTokens: 0,
|
|
26
26
|
},
|
|
27
|
+
latestContextInputTokens: null,
|
|
27
28
|
currentModel: "claude-sonnet-4-5-20250929", // Default model, TODO: get from server
|
|
28
29
|
tokenDisplayMode: "context", // Default to showing context (both billed and current)
|
|
29
30
|
logs: [],
|
|
@@ -278,6 +279,7 @@ export const useChatStore = create((set) => ({
|
|
|
278
279
|
(state.currentContext.outputTokens + (tokenUsage.outputTokens ?? 0)),
|
|
279
280
|
},
|
|
280
281
|
})),
|
|
282
|
+
setLatestContextInputTokens: (tokens) => set({ latestContextInputTokens: tokens }),
|
|
281
283
|
setCurrentModel: (model) => set({ currentModel: model }),
|
|
282
284
|
resetTokens: () => set({
|
|
283
285
|
totalBilled: {
|
|
@@ -290,6 +292,7 @@ export const useChatStore = create((set) => ({
|
|
|
290
292
|
outputTokens: 0,
|
|
291
293
|
totalTokens: 0,
|
|
292
294
|
},
|
|
295
|
+
latestContextInputTokens: null,
|
|
293
296
|
}),
|
|
294
297
|
cycleTokenDisplayMode: () => set((state) => {
|
|
295
298
|
const modes = [
|
|
@@ -2,7 +2,7 @@ import { type VariantProps } from "class-variance-authority";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
4
|
variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
5
|
-
size?: "default" | "
|
|
5
|
+
size?: "default" | "icon" | "sm" | "lg" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
8
8
|
asChild?: boolean;
|
|
@@ -123,11 +123,8 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
123
123
|
favicon: "https://www.google.com/s2/favicons?domain=theverge.com&sz=32",
|
|
124
124
|
},
|
|
125
125
|
];
|
|
126
|
-
// Get the latest
|
|
127
|
-
const
|
|
128
|
-
.slice()
|
|
129
|
-
.reverse()
|
|
130
|
-
.find((msg) => msg.role === "assistant" && msg.tokenUsage)?.tokenUsage;
|
|
126
|
+
// Get the latest context input tokens from the session context
|
|
127
|
+
const latestContextInputTokens = useChatStore((state) => state.latestContextInputTokens);
|
|
131
128
|
// Command menu items for chat input
|
|
132
129
|
const commandMenuItems = [
|
|
133
130
|
{
|
|
@@ -199,5 +196,5 @@ export function ChatView({ client, initialSessionId, error: initError, }) {
|
|
|
199
196
|
previousMessage?.role === "assistant" ? "mt-2" : "mt-6";
|
|
200
197
|
}
|
|
201
198
|
return (_jsx(Message, { message: message, className: spacingClass, isLastMessage: index === messages.length - 1, children: _jsx(MessageContent, { message: message, thinkingDisplayStyle: "collapsible" }) }, message.id));
|
|
202
|
-
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, {}),
|
|
199
|
+
}) })) }), _jsx(ChatLayout.Footer, { children: _jsxs(ChatInputRoot, { client: client, startSession: startSession, children: [_jsx(ChatInputCommandMenu, { commands: commandMenuItems }), _jsx(ChatInputField, { placeholder: placeholder, autoFocus: true }), _jsxs(ChatInputToolbar, { children: [_jsxs("div", { className: "flex items-baseline gap-1", children: [_jsx(ChatInputActions, {}), _jsx(ChatInputAttachment, {}), latestContextInputTokens != null && (_jsx(ContextUsageButton, { percentage: calculateTokenPercentage(latestContextInputTokens, currentModel ?? undefined), tokens: latestContextInputTokens, formattedPercentage: formatTokenPercentage(latestContextInputTokens, currentModel ?? undefined) }))] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(ChatInputVoiceInput, {}), _jsx(ChatInputSubmit, { children: _jsx(ArrowUp, { className: "size-4" }) })] })] })] }) })] })] }), isLargeScreen && (_jsx(ChatLayout.Aside, { breakpoint: "lg", children: _jsx(AsideTabs, {}) }))] }));
|
|
203
200
|
}
|
|
@@ -196,5 +196,6 @@ export declare const MessageChunk: z.ZodObject<{
|
|
|
196
196
|
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
197
197
|
totalTokens: z.ZodOptional<z.ZodNumber>;
|
|
198
198
|
}, z.core.$strip>>;
|
|
199
|
+
contextInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
199
200
|
}, z.core.$strip>;
|
|
200
201
|
export type MessageChunk = z.infer<typeof MessageChunk>;
|
|
@@ -771,6 +771,13 @@ export class HttpTransport {
|
|
|
771
771
|
"tokenUsage" in update._meta
|
|
772
772
|
? update._meta.tokenUsage
|
|
773
773
|
: undefined;
|
|
774
|
+
// Extract context input tokens from _meta if present
|
|
775
|
+
const contextInputTokens = update._meta &&
|
|
776
|
+
typeof update._meta === "object" &&
|
|
777
|
+
"contextInputTokens" in update._meta &&
|
|
778
|
+
typeof update._meta.contextInputTokens === "number"
|
|
779
|
+
? update._meta.contextInputTokens
|
|
780
|
+
: undefined;
|
|
774
781
|
// Queue message chunks if present (but skip during replay)
|
|
775
782
|
// For agent_message_chunk, content is an object, not an array
|
|
776
783
|
const content = update.content;
|
|
@@ -783,6 +790,7 @@ export class HttpTransport {
|
|
|
783
790
|
role: "assistant",
|
|
784
791
|
contentDelta: { type: "text", text: contentObj.text },
|
|
785
792
|
tokenUsage,
|
|
793
|
+
contextInputTokens,
|
|
786
794
|
isComplete: false,
|
|
787
795
|
};
|
|
788
796
|
}
|
|
@@ -15,6 +15,7 @@ export declare class StdioTransport implements Transport {
|
|
|
15
15
|
private currentSessionId;
|
|
16
16
|
private chunkResolvers;
|
|
17
17
|
private streamComplete;
|
|
18
|
+
private originalConsole;
|
|
18
19
|
constructor(options: StdioTransportOptions);
|
|
19
20
|
connect(): Promise<void>;
|
|
20
21
|
disconnect(): Promise<void>;
|
|
@@ -18,6 +18,7 @@ export class StdioTransport {
|
|
|
18
18
|
currentSessionId = null;
|
|
19
19
|
chunkResolvers = [];
|
|
20
20
|
streamComplete = false;
|
|
21
|
+
originalConsole = null;
|
|
21
22
|
constructor(options) {
|
|
22
23
|
this.options = options;
|
|
23
24
|
}
|
|
@@ -41,7 +42,43 @@ export class StdioTransport {
|
|
|
41
42
|
const outputStream = Writable.toWeb(this.agentProcess.stdin);
|
|
42
43
|
const inputStream = Readable.toWeb(this.agentProcess.stdout);
|
|
43
44
|
// Create the bidirectional stream using ndJsonStream
|
|
45
|
+
// Intercept all console methods to redirect agent output to logger
|
|
46
|
+
// Store original console methods for later restoration
|
|
47
|
+
this.originalConsole = {
|
|
48
|
+
log: console.log,
|
|
49
|
+
error: console.error,
|
|
50
|
+
warn: console.warn,
|
|
51
|
+
info: console.info,
|
|
52
|
+
debug: console.debug,
|
|
53
|
+
};
|
|
54
|
+
console.log = (...args) => {
|
|
55
|
+
logger.info("Agent console.log", {
|
|
56
|
+
message: args.map(String).join(" "),
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
console.error = (...args) => {
|
|
60
|
+
logger.error("Agent console.error", {
|
|
61
|
+
message: args.map(String).join(" "),
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
console.warn = (...args) => {
|
|
65
|
+
logger.warn("Agent console.warn", {
|
|
66
|
+
message: args.map(String).join(" "),
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
console.info = (...args) => {
|
|
70
|
+
logger.info("Agent console.info", {
|
|
71
|
+
message: args.map(String).join(" "),
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
console.debug = (...args) => {
|
|
75
|
+
logger.debug("Agent console.debug", {
|
|
76
|
+
message: args.map(String).join(" "),
|
|
77
|
+
});
|
|
78
|
+
};
|
|
44
79
|
const stream = ndJsonStream(outputStream, inputStream);
|
|
80
|
+
// Keep console methods intercepted throughout the connection lifecycle
|
|
81
|
+
// They will be restored in disconnect()
|
|
45
82
|
// Create ACP client implementation factory
|
|
46
83
|
const self = this;
|
|
47
84
|
const clientFactory = (_agent) => {
|
|
@@ -321,6 +358,15 @@ export class StdioTransport {
|
|
|
321
358
|
}
|
|
322
359
|
catch (error) {
|
|
323
360
|
this.connected = false;
|
|
361
|
+
// Restore original console methods on error
|
|
362
|
+
if (this.originalConsole) {
|
|
363
|
+
console.log = this.originalConsole.log;
|
|
364
|
+
console.error = this.originalConsole.error;
|
|
365
|
+
console.warn = this.originalConsole.warn;
|
|
366
|
+
console.info = this.originalConsole.info;
|
|
367
|
+
console.debug = this.originalConsole.debug;
|
|
368
|
+
this.originalConsole = null;
|
|
369
|
+
}
|
|
324
370
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
325
371
|
this.notifyError(err);
|
|
326
372
|
throw err;
|
|
@@ -343,6 +389,15 @@ export class StdioTransport {
|
|
|
343
389
|
this.connection = null;
|
|
344
390
|
this.connected = false;
|
|
345
391
|
this.currentSessionId = null;
|
|
392
|
+
// Restore original console methods
|
|
393
|
+
if (this.originalConsole) {
|
|
394
|
+
console.log = this.originalConsole.log;
|
|
395
|
+
console.error = this.originalConsole.error;
|
|
396
|
+
console.warn = this.originalConsole.warn;
|
|
397
|
+
console.info = this.originalConsole.info;
|
|
398
|
+
console.debug = this.originalConsole.debug;
|
|
399
|
+
this.originalConsole = null;
|
|
400
|
+
}
|
|
346
401
|
}
|
|
347
402
|
catch (error) {
|
|
348
403
|
const err = error instanceof Error ? error : new Error(String(error));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@agentclientprotocol/sdk": "^0.5.1",
|
|
43
|
-
"@townco/core": "0.0.
|
|
43
|
+
"@townco/core": "0.0.23",
|
|
44
44
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
45
45
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
46
46
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@tailwindcss/postcss": "^4.1.17",
|
|
66
|
-
"@townco/tsconfig": "0.1.
|
|
66
|
+
"@townco/tsconfig": "0.1.42",
|
|
67
67
|
"@types/node": "^24.10.0",
|
|
68
68
|
"@types/react": "^19.2.2",
|
|
69
69
|
"ink": "^6.4.0",
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible logger
|
|
3
|
-
* Outputs structured JSON logs to console with color-coding
|
|
4
|
-
* Also captures logs to a global store for in-app viewing
|
|
5
|
-
* In Node.js environment with logsDir option, also writes to .logs/ directory
|
|
6
|
-
*/
|
|
7
|
-
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
8
|
-
export interface LogEntry {
|
|
9
|
-
id: string;
|
|
10
|
-
timestamp: string;
|
|
11
|
-
level: LogLevel;
|
|
12
|
-
service: string;
|
|
13
|
-
message: string;
|
|
14
|
-
metadata?: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Get all captured logs
|
|
18
|
-
*/
|
|
19
|
-
export declare function getCapturedLogs(): LogEntry[];
|
|
20
|
-
/**
|
|
21
|
-
* Clear all captured logs
|
|
22
|
-
*/
|
|
23
|
-
export declare function clearCapturedLogs(): void;
|
|
24
|
-
/**
|
|
25
|
-
* Subscribe to log updates
|
|
26
|
-
*/
|
|
27
|
-
type LogSubscriber = (entry: LogEntry) => void;
|
|
28
|
-
export declare function subscribeToLogs(callback: LogSubscriber): () => void;
|
|
29
|
-
/**
|
|
30
|
-
* Configure global logs directory for file writing
|
|
31
|
-
* Must be called before creating any loggers (typically at TUI startup)
|
|
32
|
-
*/
|
|
33
|
-
export declare function configureLogsDir(logsDir: string): void;
|
|
34
|
-
export declare class Logger {
|
|
35
|
-
private service;
|
|
36
|
-
private minLevel;
|
|
37
|
-
private logFilePath?;
|
|
38
|
-
private logsDir?;
|
|
39
|
-
private writeQueue;
|
|
40
|
-
private isWriting;
|
|
41
|
-
constructor(service: string, minLevel?: LogLevel);
|
|
42
|
-
private setupFileLogging;
|
|
43
|
-
private writeToFile;
|
|
44
|
-
private shouldLog;
|
|
45
|
-
private log;
|
|
46
|
-
trace(message: string, metadata?: Record<string, unknown>): void;
|
|
47
|
-
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
48
|
-
info(message: string, metadata?: Record<string, unknown>): void;
|
|
49
|
-
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
50
|
-
error(message: string, metadata?: Record<string, unknown>): void;
|
|
51
|
-
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Create a logger instance for a service
|
|
55
|
-
* @param service - Service name (e.g., "gui", "http-agent", "tui")
|
|
56
|
-
* @param minLevel - Minimum log level to display (default: "debug")
|
|
57
|
-
*/
|
|
58
|
-
export declare function createLogger(service: string, minLevel?: LogLevel): Logger;
|
|
59
|
-
export {};
|
package/dist/core/lib/logger.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible logger
|
|
3
|
-
* Outputs structured JSON logs to console with color-coding
|
|
4
|
-
* Also captures logs to a global store for in-app viewing
|
|
5
|
-
* In Node.js environment with logsDir option, also writes to .logs/ directory
|
|
6
|
-
*/
|
|
7
|
-
// Check if running in Node.js
|
|
8
|
-
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
9
|
-
// Global logs directory configuration (set once at app startup for TUI)
|
|
10
|
-
let globalLogsDir;
|
|
11
|
-
// Global log store
|
|
12
|
-
const globalLogStore = [];
|
|
13
|
-
let logIdCounter = 0;
|
|
14
|
-
/**
|
|
15
|
-
* Get all captured logs
|
|
16
|
-
*/
|
|
17
|
-
export function getCapturedLogs() {
|
|
18
|
-
return [...globalLogStore];
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Clear all captured logs
|
|
22
|
-
*/
|
|
23
|
-
export function clearCapturedLogs() {
|
|
24
|
-
globalLogStore.length = 0;
|
|
25
|
-
}
|
|
26
|
-
const logSubscribers = new Set();
|
|
27
|
-
export function subscribeToLogs(callback) {
|
|
28
|
-
logSubscribers.add(callback);
|
|
29
|
-
return () => logSubscribers.delete(callback);
|
|
30
|
-
}
|
|
31
|
-
function notifyLogSubscribers(entry) {
|
|
32
|
-
for (const callback of logSubscribers) {
|
|
33
|
-
callback(entry);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Configure global logs directory for file writing
|
|
38
|
-
* Must be called before creating any loggers (typically at TUI startup)
|
|
39
|
-
*/
|
|
40
|
-
export function configureLogsDir(logsDir) {
|
|
41
|
-
globalLogsDir = logsDir;
|
|
42
|
-
}
|
|
43
|
-
const LOG_LEVELS = {
|
|
44
|
-
trace: 0,
|
|
45
|
-
debug: 1,
|
|
46
|
-
info: 2,
|
|
47
|
-
warn: 3,
|
|
48
|
-
error: 4,
|
|
49
|
-
fatal: 5,
|
|
50
|
-
};
|
|
51
|
-
const _LOG_COLORS = {
|
|
52
|
-
trace: "#6B7280", // gray
|
|
53
|
-
debug: "#3B82F6", // blue
|
|
54
|
-
info: "#10B981", // green
|
|
55
|
-
warn: "#F59E0B", // orange
|
|
56
|
-
error: "#EF4444", // red
|
|
57
|
-
fatal: "#DC2626", // dark red
|
|
58
|
-
};
|
|
59
|
-
const _LOG_STYLES = {
|
|
60
|
-
trace: "color: #6B7280",
|
|
61
|
-
debug: "color: #3B82F6; font-weight: bold",
|
|
62
|
-
info: "color: #10B981; font-weight: bold",
|
|
63
|
-
warn: "color: #F59E0B; font-weight: bold",
|
|
64
|
-
error: "color: #EF4444; font-weight: bold",
|
|
65
|
-
fatal: "color: #DC2626; font-weight: bold; background: #FEE2E2",
|
|
66
|
-
};
|
|
67
|
-
export class Logger {
|
|
68
|
-
service;
|
|
69
|
-
minLevel;
|
|
70
|
-
logFilePath;
|
|
71
|
-
logsDir;
|
|
72
|
-
writeQueue = [];
|
|
73
|
-
isWriting = false;
|
|
74
|
-
constructor(service, minLevel = "debug") {
|
|
75
|
-
this.service = service;
|
|
76
|
-
this.minLevel = minLevel;
|
|
77
|
-
// In production, suppress trace and debug logs
|
|
78
|
-
if (typeof process !== "undefined" &&
|
|
79
|
-
process.env?.NODE_ENV === "production") {
|
|
80
|
-
this.minLevel = "info";
|
|
81
|
-
}
|
|
82
|
-
// Note: File logging setup is done lazily in log() method
|
|
83
|
-
// This allows loggers created before configureLogsDir() to still write to files
|
|
84
|
-
}
|
|
85
|
-
setupFileLogging() {
|
|
86
|
-
if (!isNode || !globalLogsDir)
|
|
87
|
-
return;
|
|
88
|
-
try {
|
|
89
|
-
// Dynamic import for Node.js modules
|
|
90
|
-
const path = require("node:path");
|
|
91
|
-
const fs = require("node:fs");
|
|
92
|
-
this.logsDir = globalLogsDir;
|
|
93
|
-
this.logFilePath = path.join(this.logsDir, `${this.service}.log`);
|
|
94
|
-
// Create logs directory if it doesn't exist
|
|
95
|
-
if (!fs.existsSync(this.logsDir)) {
|
|
96
|
-
fs.mkdirSync(this.logsDir, { recursive: true });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (_error) {
|
|
100
|
-
// Silently fail if we can't set up file logging
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async writeToFile(content) {
|
|
104
|
-
if (!this.logFilePath || !isNode)
|
|
105
|
-
return;
|
|
106
|
-
this.writeQueue.push(content);
|
|
107
|
-
if (this.isWriting) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
this.isWriting = true;
|
|
111
|
-
while (this.writeQueue.length > 0) {
|
|
112
|
-
const batch = this.writeQueue.splice(0, this.writeQueue.length);
|
|
113
|
-
const data = `${batch.join("\n")}\n`;
|
|
114
|
-
try {
|
|
115
|
-
// Dynamic import for Node.js modules
|
|
116
|
-
const fs = require("node:fs");
|
|
117
|
-
await fs.promises.appendFile(this.logFilePath, data, "utf-8");
|
|
118
|
-
}
|
|
119
|
-
catch (_error) {
|
|
120
|
-
// Silently fail
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
this.isWriting = false;
|
|
124
|
-
}
|
|
125
|
-
shouldLog(level) {
|
|
126
|
-
return LOG_LEVELS[level] >= LOG_LEVELS[this.minLevel];
|
|
127
|
-
}
|
|
128
|
-
log(level, message, metadata) {
|
|
129
|
-
if (!this.shouldLog(level)) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const entry = {
|
|
133
|
-
id: `log_${++logIdCounter}`,
|
|
134
|
-
timestamp: new Date().toISOString(),
|
|
135
|
-
level,
|
|
136
|
-
service: this.service,
|
|
137
|
-
message,
|
|
138
|
-
...(metadata && { metadata }),
|
|
139
|
-
};
|
|
140
|
-
// Store in global log store
|
|
141
|
-
globalLogStore.push(entry);
|
|
142
|
-
// Notify subscribers
|
|
143
|
-
notifyLogSubscribers(entry);
|
|
144
|
-
// Write to file in Node.js (for logs tab to read)
|
|
145
|
-
// Lazily set up file logging if globalLogsDir was configured after this logger was created
|
|
146
|
-
if (isNode && !this.logFilePath && globalLogsDir) {
|
|
147
|
-
this.setupFileLogging();
|
|
148
|
-
}
|
|
149
|
-
if (isNode && this.logFilePath) {
|
|
150
|
-
// Write as JSON without the id field (to match expected format)
|
|
151
|
-
const fileEntry = {
|
|
152
|
-
timestamp: entry.timestamp,
|
|
153
|
-
level: entry.level,
|
|
154
|
-
service: entry.service,
|
|
155
|
-
message: entry.message,
|
|
156
|
-
...(entry.metadata && { metadata: entry.metadata }),
|
|
157
|
-
};
|
|
158
|
-
this.writeToFile(JSON.stringify(fileEntry)).catch(() => {
|
|
159
|
-
// Silently fail
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
// No console output - logs are only captured and displayed in UI
|
|
163
|
-
// This prevents logs from polluting stdout/stderr in TUI mode
|
|
164
|
-
}
|
|
165
|
-
trace(message, metadata) {
|
|
166
|
-
this.log("trace", message, metadata);
|
|
167
|
-
}
|
|
168
|
-
debug(message, metadata) {
|
|
169
|
-
this.log("debug", message, metadata);
|
|
170
|
-
}
|
|
171
|
-
info(message, metadata) {
|
|
172
|
-
this.log("info", message, metadata);
|
|
173
|
-
}
|
|
174
|
-
warn(message, metadata) {
|
|
175
|
-
this.log("warn", message, metadata);
|
|
176
|
-
}
|
|
177
|
-
error(message, metadata) {
|
|
178
|
-
this.log("error", message, metadata);
|
|
179
|
-
}
|
|
180
|
-
fatal(message, metadata) {
|
|
181
|
-
this.log("fatal", message, metadata);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Create a logger instance for a service
|
|
186
|
-
* @param service - Service name (e.g., "gui", "http-agent", "tui")
|
|
187
|
-
* @param minLevel - Minimum log level to display (default: "debug")
|
|
188
|
-
*/
|
|
189
|
-
export function createLogger(service, minLevel = "debug") {
|
|
190
|
-
return new Logger(service, minLevel);
|
|
191
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { subscribeToLogs } from "../../core/lib/logger.js";
|
|
5
|
-
// Color mapping for log levels
|
|
6
|
-
const LOG_LEVEL_COLORS = {
|
|
7
|
-
trace: "gray",
|
|
8
|
-
debug: "blue",
|
|
9
|
-
info: "green",
|
|
10
|
-
warn: "yellow",
|
|
11
|
-
error: "red",
|
|
12
|
-
fatal: "red",
|
|
13
|
-
};
|
|
14
|
-
export function LogsPanel({ logs: initialLogs }) {
|
|
15
|
-
const [logs, setLogs] = useState(initialLogs);
|
|
16
|
-
// Subscribe to new logs
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const unsubscribe = subscribeToLogs((entry) => {
|
|
19
|
-
setLogs((prev) => [...prev, entry]);
|
|
20
|
-
});
|
|
21
|
-
return unsubscribe;
|
|
22
|
-
}, []);
|
|
23
|
-
if (logs.length === 0) {
|
|
24
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsx(Text, { dimColor: true, children: "No logs yet..." }) }));
|
|
25
|
-
}
|
|
26
|
-
// Show last 100 logs
|
|
27
|
-
const displayLogs = logs.slice(-100);
|
|
28
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: displayLogs.map((log) => (_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { dimColor: true, children: new Date(log.timestamp).toLocaleTimeString() }), _jsxs(Text, { color: LOG_LEVEL_COLORS[log.level], bold: true, children: ["[", log.level.toUpperCase(), "]"] }), _jsxs(Text, { dimColor: true, children: ["[", log.service, "]"] }), _jsx(Text, { children: log.message }), log.metadata && (_jsx(Text, { dimColor: true, children: JSON.stringify(log.metadata) }))] }, log.id))) }));
|
|
29
|
-
}
|