leaf-coding-agent 1.0.4 → 1.0.5
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/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +11 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/tools/worker.d.ts +2 -0
- package/dist/core/tools/worker.d.ts.map +1 -1
- package/dist/core/tools/worker.js +23 -10
- package/dist/core/tools/worker.js.map +1 -1
- package/dist/core/worker-integration.d.ts +4 -3
- package/dist/core/worker-integration.d.ts.map +1 -1
- package/dist/core/worker-integration.js +22 -5
- package/dist/core/worker-integration.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -0
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@ export type WorkerToolInput = typeof workerSchema;
|
|
|
22
22
|
export declare function createWorkerToolDefinition(cwd: string, options?: {
|
|
23
23
|
model?: Model<any>;
|
|
24
24
|
apiKey?: string;
|
|
25
|
+
getApiKey?: () => string | undefined;
|
|
26
|
+
streamFn?: any;
|
|
25
27
|
tools?: any[];
|
|
26
28
|
agent?: any;
|
|
27
29
|
}): ToolDefinition<typeof workerSchema>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/core/tools/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/core/tools/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;;;GAIG;AAEH,QAAA,MAAM,YAAY;;;;;;;;;;;EAWhB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,OAAO,YAAY,CAAC;AAElD,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IACR,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,KAAK,CAAC,EAAE,GAAG,CAAC;CACb,GACA,cAAc,CAAC,OAAO,YAAY,CAAC,CAgJrC","sourcesContent":["import { Type } from \"typebox\";\nimport { Text } from \"@looze/leaf-tui\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { WorkerManager, CommunicationManager } from \"@looze/leaf-agent-core\";\nimport type { WorkerTask, CoordinatorConfig } from \"@looze/leaf-agent-core\";\nimport type { Model } from \"@looze/leaf-ai\";\n\n/**\n * Worker Tool 定义\n * \n * 用于创建和管理 Worker,并行执行任务\n */\n\nconst workerSchema = Type.Object({\n enable: Type.Boolean({ description: \"是否启用 Worker 模式\" }),\n tasks: Type.Array(Type.Object({\n id: Type.String({ description: \"任务 ID\" }),\n description: Type.String({ description: \"任务描述\" }),\n dependencies: Type.Optional(Type.Array(Type.String({ description: \"依赖的任务 ID\" }))),\n system_prompt: Type.Optional(Type.String({ description: \"Worker 的系统提示词\" })),\n })),\n system_prompt: Type.Optional(Type.String({ description: \"默认的 Worker 系统提示词\" })),\n max_workers: Type.Optional(Type.Number({ description: \"最大 Worker 数量\" })),\n communication_mode: Type.Optional(Type.String({ description: \"通信模式:socket/message/shared\" })),\n});\n\nexport type WorkerToolInput = typeof workerSchema;\n\nexport function createWorkerToolDefinition(\n cwd: string,\n options?: {\n model?: Model<any>;\n apiKey?: string;\n getApiKey?: () => string | undefined;\n streamFn?: any;\n tools?: any[];\n agent?: any;\n }\n): ToolDefinition<typeof workerSchema> {\n // 保存 options 供 execute 方法使用\n const workerOptions = options;\n \n return {\n name: \"worker\",\n label: \"Worker\",\n description: \"创建和管理 Worker,并行执行任务。启用 Worker 模式时,会创建多个独立的 Agent 并行执行任务。\",\n promptSnippet: \"创建 Worker 并行执行任务\",\n promptGuidelines: [\n \"Use worker when you need to execute multiple independent tasks in parallel.\",\n \"Worker 模式适合需要并行执行的复杂任务。\",\n \"每个 Worker 有独立的系统提示词和执行环境。\",\n ],\n parameters: workerSchema,\n \n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const { enable, tasks, system_prompt, max_workers, communication_mode } = params;\n \n if (!enable) {\n return {\n content: [{ type: \"text\", text: \"Worker 模式未启用,使用普通模式执行。\" }],\n details: { enabled: false } as any,\n };\n }\n \n // 更新进度\n onUpdate?.({ \n content: [{ type: \"text\", text: `启动 ${tasks.length} 个 Worker...` }],\n details: {} as any\n });\n \n try {\n // 转换任务格式\n const workerTasks: WorkerTask[] = tasks.map(task => ({\n id: task.id,\n description: task.description,\n dependencies: task.dependencies || [],\n status: \"pending\" as const,\n }));\n \n // 创建通信管理器\n const communicationManager = new CommunicationManager(\n (communication_mode as any) || \"message\"\n );\n \n // 创建协调器配置\n const coordinatorConfig: CoordinatorConfig = {\n tasks: workerTasks,\n maxWorkers: max_workers || 4,\n communicationMode: (communication_mode as any) || \"message\",\n systemPrompt: system_prompt || \"你是一个 Worker,负责执行分配给你的任务。\",\n };\n \n // 动态获取 apiKey\n const apiKey = typeof workerOptions?.getApiKey === \"function\"\n ? workerOptions.getApiKey()\n : workerOptions?.apiKey;\n \n // 创建 Worker Manager\n const workerManager = new WorkerManager(\n coordinatorConfig,\n workerOptions?.agent || null,\n communicationManager,\n {\n model: workerOptions?.model,\n apiKey: apiKey,\n streamFn: workerOptions?.streamFn,\n tools: workerOptions?.tools || [],\n }\n );\n \n // 执行所有任务\n const result = await workerManager.executeAll();\n \n // 格式化结果\n const resultText = formatWorkerResult({\n success: result.success,\n results: result.results,\n errors: result.errors,\n duration: result.duration,\n });\n \n return {\n content: [{ type: \"text\", text: resultText }],\n details: { \n enabled: true, \n success: result.success,\n results: Object.fromEntries(result.results),\n errors: Object.fromEntries(result.errors),\n duration: result.duration,\n } as any,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : \"\";\n \n console.error(\"[Worker Tool] 执行失败:\", error);\n \n return {\n content: [{ type: \"text\", text: `Worker 执行失败: ${errorMessage}` }],\n details: { \n enabled: true, \n success: false,\n error: errorMessage,\n stack: errorStack,\n },\n isError: true,\n };\n }\n },\n \n renderCall(args, theme, context) {\n const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n \n if (args.enable) {\n const taskCount = args.tasks?.length || 0;\n text.setText(`worker 启用 Worker 模式,${taskCount} 个任务`);\n } else {\n text.setText(\"worker 未启用\");\n }\n \n return text;\n },\n \n renderResult(result, options, theme, context) {\n const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n \n const details = result.details as any;\n \n // 直接显示结果,不再检查 enabled\n if (details) {\n const success = details.success ? \"✓\" : \"✗\";\n const duration = details.duration;\n const taskCount = Object.keys(details.results || {}).length;\n \n text.setText(`${success} Worker 完成 (${taskCount} 个任务, ${duration}ms)`);\n } else {\n text.setText(\"Worker 执行完成\");\n }\n \n return text;\n },\n };\n}\n\n/**\n * 格式化 Worker 结果\n */\nfunction formatWorkerResult(result: any): string {\n const lines: string[] = [];\n \n lines.push(`## Worker 执行结果`);\n lines.push(\"\");\n lines.push(`- **状态**: ${result.success ? \"✓ 成功\" : \"✗ 失败\"}`);\n lines.push(`- **耗时**: ${formatDuration(result.duration)}`);\n lines.push(`- **任务数**: ${result.results.size}`);\n \n if (result.errors.size > 0) {\n lines.push(`- **失败数**: ${result.errors.size}`);\n }\n \n lines.push(\"\");\n \n // 任务结果\n if (result.results.size > 0) {\n lines.push(`### 任务结果`);\n for (const [workerId, workerResult] of result.results) {\n const resultText = typeof workerResult.result === \"string\" \n ? workerResult.result \n : JSON.stringify(workerResult.result, null, 2);\n \n // 截断过长的结果\n const truncated = resultText.length > 200 \n ? resultText.substring(0, 200) + \"...\" \n : resultText;\n \n lines.push(`- **${workerId}**: ${truncated}`);\n }\n lines.push(\"\");\n }\n \n // 错误信息\n if (result.errors.size > 0) {\n lines.push(`### 错误信息`);\n for (const [workerId, error] of result.errors) {\n lines.push(`- **${workerId}**: ${error}`);\n }\n lines.push(\"\");\n }\n \n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化持续时间\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { Text } from "@looze/leaf-tui";
|
|
2
3
|
import { WorkerManager, CommunicationManager } from "@looze/leaf-agent-core";
|
|
3
4
|
/**
|
|
4
5
|
* Worker Tool 定义
|
|
@@ -18,6 +19,8 @@ const workerSchema = Type.Object({
|
|
|
18
19
|
communication_mode: Type.Optional(Type.String({ description: "通信模式:socket/message/shared" })),
|
|
19
20
|
});
|
|
20
21
|
export function createWorkerToolDefinition(cwd, options) {
|
|
22
|
+
// 保存 options 供 execute 方法使用
|
|
23
|
+
const workerOptions = options;
|
|
21
24
|
return {
|
|
22
25
|
name: "worker",
|
|
23
26
|
label: "Worker",
|
|
@@ -59,11 +62,16 @@ export function createWorkerToolDefinition(cwd, options) {
|
|
|
59
62
|
communicationMode: communication_mode || "message",
|
|
60
63
|
systemPrompt: system_prompt || "你是一个 Worker,负责执行分配给你的任务。",
|
|
61
64
|
};
|
|
65
|
+
// 动态获取 apiKey
|
|
66
|
+
const apiKey = typeof workerOptions?.getApiKey === "function"
|
|
67
|
+
? workerOptions.getApiKey()
|
|
68
|
+
: workerOptions?.apiKey;
|
|
62
69
|
// 创建 Worker Manager
|
|
63
|
-
const workerManager = new WorkerManager(coordinatorConfig,
|
|
64
|
-
model:
|
|
65
|
-
apiKey:
|
|
66
|
-
|
|
70
|
+
const workerManager = new WorkerManager(coordinatorConfig, workerOptions?.agent || null, communicationManager, {
|
|
71
|
+
model: workerOptions?.model,
|
|
72
|
+
apiKey: apiKey,
|
|
73
|
+
streamFn: workerOptions?.streamFn,
|
|
74
|
+
tools: workerOptions?.tools || [],
|
|
67
75
|
});
|
|
68
76
|
// 执行所有任务
|
|
69
77
|
const result = await workerManager.executeAll();
|
|
@@ -86,19 +94,23 @@ export function createWorkerToolDefinition(cwd, options) {
|
|
|
86
94
|
};
|
|
87
95
|
}
|
|
88
96
|
catch (error) {
|
|
97
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
|
+
const errorStack = error instanceof Error ? error.stack : "";
|
|
99
|
+
console.error("[Worker Tool] 执行失败:", error);
|
|
89
100
|
return {
|
|
90
|
-
content: [{ type: "text", text: `Worker 执行失败: ${
|
|
101
|
+
content: [{ type: "text", text: `Worker 执行失败: ${errorMessage}` }],
|
|
91
102
|
details: {
|
|
92
103
|
enabled: true,
|
|
93
104
|
success: false,
|
|
94
|
-
error:
|
|
105
|
+
error: errorMessage,
|
|
106
|
+
stack: errorStack,
|
|
95
107
|
},
|
|
96
108
|
isError: true,
|
|
97
109
|
};
|
|
98
110
|
}
|
|
99
111
|
},
|
|
100
112
|
renderCall(args, theme, context) {
|
|
101
|
-
const text = context.lastComponent ??
|
|
113
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
102
114
|
if (args.enable) {
|
|
103
115
|
const taskCount = args.tasks?.length || 0;
|
|
104
116
|
text.setText(`worker 启用 Worker 模式,${taskCount} 个任务`);
|
|
@@ -109,16 +121,17 @@ export function createWorkerToolDefinition(cwd, options) {
|
|
|
109
121
|
return text;
|
|
110
122
|
},
|
|
111
123
|
renderResult(result, options, theme, context) {
|
|
112
|
-
const text = context.lastComponent ??
|
|
124
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
113
125
|
const details = result.details;
|
|
114
|
-
|
|
126
|
+
// 直接显示结果,不再检查 enabled
|
|
127
|
+
if (details) {
|
|
115
128
|
const success = details.success ? "✓" : "✗";
|
|
116
129
|
const duration = details.duration;
|
|
117
130
|
const taskCount = Object.keys(details.results || {}).length;
|
|
118
131
|
text.setText(`${success} Worker 完成 (${taskCount} 个任务, ${duration}ms)`);
|
|
119
132
|
}
|
|
120
133
|
else {
|
|
121
|
-
text.setText("Worker
|
|
134
|
+
text.setText("Worker 执行完成");
|
|
122
135
|
}
|
|
123
136
|
return text;
|
|
124
137
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/core/tools/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAI7E;;;;GAIG;AAEH,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,4BAAgB,EAAE,CAAC;IACvD,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;QAC5B,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,WAAO,EAAE,CAAC;QACzC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,cAAM,EAAE,CAAC;QACjD,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,oBAAU,EAAE,CAAC,CAAC,CAAC;QACjF,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2BAAe,EAAE,CAAC,CAAC;KAC5E,CAAC,CAAC;IACH,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,kCAAkB,EAAE,CAAC,CAAC;IAC9E,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,sBAAc,EAAE,CAAC,CAAC;IACxE,kBAAkB,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,sCAA4B,EAAE,CAAC,CAAC;CAC9F,CAAC,CAAC;AAIH,MAAM,UAAU,0BAA0B,CACxC,GAAW,EACX,OAKC,EACoC;IACrC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,8HAA0D;QACvE,aAAa,EAAE,kCAAkB;QACjC,gBAAgB,EAAE;YAChB,6EAA6E;YAC7E,yDAAyB;YACzB,6DAA2B;SAC5B;QACD,UAAU,EAAE,YAAY;QAExB,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE;YACvD,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC;YAEjF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sDAAwB,EAAE,CAAC;oBAC3D,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAS;iBACnC,CAAC;YACJ,CAAC;YAED,eAAO;YACP,QAAQ,EAAE,CAAC;gBACT,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAM,KAAK,CAAC,MAAM,gBAAc,EAAE,CAAC;gBACnE,OAAO,EAAE,EAAS;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,qBAAS;gBACT,MAAM,WAAW,GAAiB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBACnD,EAAE,EAAE,IAAI,CAAC,EAAE;oBACX,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;oBACrC,MAAM,EAAE,SAAkB;iBAC3B,CAAC,CAAC,CAAC;gBAEJ,wBAAU;gBACV,MAAM,oBAAoB,GAAG,IAAI,oBAAoB,CAClD,kBAA0B,IAAI,SAAS,CACzC,CAAC;gBAEF,wBAAU;gBACV,MAAM,iBAAiB,GAAsB;oBAC3C,KAAK,EAAE,WAAW;oBAClB,UAAU,EAAE,WAAW,IAAI,CAAC;oBAC5B,iBAAiB,EAAG,kBAA0B,IAAI,SAAS;oBAC3D,YAAY,EAAE,aAAa,IAAI,4DAA0B;iBAC1D,CAAC;gBAEF,wBAAoB;gBACpB,MAAM,aAAa,GAAG,IAAI,aAAa,CACrC,iBAAiB,EACjB,OAAO,EAAE,KAAK,IAAI,IAAI,EACtB,oBAAoB,EACpB;oBACE,KAAK,EAAE,OAAO,EAAE,KAAK;oBACrB,MAAM,EAAE,OAAO,EAAE,MAAM;oBACvB,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE;iBAC5B,CACF,CAAC;gBAEF,qBAAS;gBACT,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,CAAC;gBAEhD,kBAAQ;gBACR,MAAM,UAAU,GAAG,kBAAkB,CAAC;oBACpC,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;iBAC1B,CAAC,CAAC;gBAEH,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;oBAC7C,OAAO,EAAE;wBACP,OAAO,EAAE,IAAI;wBACb,OAAO,EAAE,MAAM,CAAC,OAAO;wBACvB,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC;wBAC3C,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC;wBACzC,QAAQ,EAAE,MAAM,CAAC,QAAQ;qBACnB;iBACT,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAgB,KAAK,EAAE,EAAE,CAAC;oBAC1D,OAAO,EAAE;wBACP,OAAO,EAAE,IAAI;wBACb,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;qBACrB;oBACD,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;QAAA,CACF;QAED,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE;YAC/B,MAAM,IAAI,GAAI,OAAO,CAAC,aAAqB,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC,EAAE,CAAC;YAErE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;gBAC1C,IAAI,CAAC,OAAO,CAAC,iCAAuB,SAAS,YAAM,CAAC,CAAC;YACvD,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,kBAAY,CAAC,CAAC;YAC7B,CAAC;YAED,OAAO,IAAI,CAAC;QAAA,CACb;QAED,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;YAC5C,MAAM,IAAI,GAAI,OAAO,CAAC,aAAqB,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC,EAAE,CAAC;YAErE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAc,CAAC;YACtC,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;gBACrB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAAC,CAAC,CAAC,KAAG,CAAC;gBAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;gBAClC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;gBAE5D,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO,mBAAe,SAAS,eAAS,QAAQ,KAAK,CAAC,CAAC;YACzE,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,CAAC,wBAAc,CAAC,CAAC;YAC/B,CAAC;YAED,OAAO,IAAI,CAAC;QAAA,CACb;KACF,CAAC;AAAA,CACH;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,MAAW,EAAU;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,wBAAgB,CAAC,CAAC;IAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,iBAAa,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,YAAM,CAAC,CAAC,CAAC,YAAM,EAAE,CAAC,CAAC;IAC5D,KAAK,CAAC,IAAI,CAAC,iBAAa,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CAAC,oBAAc,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAEhD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,oBAAc,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,eAAO;IACP,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,kBAAU,CAAC,CAAC;QACvB,KAAK,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACtD,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,KAAK,QAAQ;gBACxD,CAAC,CAAC,YAAY,CAAC,MAAM;gBACrB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAEjD,wBAAU;YACV,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,GAAG,GAAG;gBACvC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK;gBACtC,CAAC,CAAC,UAAU,CAAC;YAEf,KAAK,CAAC,IAAI,CAAC,OAAO,QAAQ,OAAO,SAAS,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,eAAO;IACP,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,kBAAU,CAAC,CAAC;QACvB,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC,OAAO,QAAQ,OAAO,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACzB;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,EAAU,EAAU;IAC1C,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,EAAE,IAAI,CAAC;IAChC,IAAI,EAAE,GAAG,KAAK;QAAE,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACpD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;AAAA,CACzE","sourcesContent":["import { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { WorkerManager, CommunicationManager } from \"@looze/leaf-agent-core\";\nimport type { WorkerTask, CoordinatorConfig } from \"@looze/leaf-agent-core\";\nimport type { Model } from \"@looze/leaf-ai\";\n\n/**\n * Worker Tool 定义\n * \n * 用于创建和管理 Worker,并行执行任务\n */\n\nconst workerSchema = Type.Object({\n enable: Type.Boolean({ description: \"是否启用 Worker 模式\" }),\n tasks: Type.Array(Type.Object({\n id: Type.String({ description: \"任务 ID\" }),\n description: Type.String({ description: \"任务描述\" }),\n dependencies: Type.Optional(Type.Array(Type.String({ description: \"依赖的任务 ID\" }))),\n system_prompt: Type.Optional(Type.String({ description: \"Worker 的系统提示词\" })),\n })),\n system_prompt: Type.Optional(Type.String({ description: \"默认的 Worker 系统提示词\" })),\n max_workers: Type.Optional(Type.Number({ description: \"最大 Worker 数量\" })),\n communication_mode: Type.Optional(Type.String({ description: \"通信模式:socket/message/shared\" })),\n});\n\nexport type WorkerToolInput = typeof workerSchema;\n\nexport function createWorkerToolDefinition(\n cwd: string,\n options?: {\n model?: Model<any>;\n apiKey?: string;\n tools?: any[];\n agent?: any;\n }\n): ToolDefinition<typeof workerSchema> {\n return {\n name: \"worker\",\n label: \"Worker\",\n description: \"创建和管理 Worker,并行执行任务。启用 Worker 模式时,会创建多个独立的 Agent 并行执行任务。\",\n promptSnippet: \"创建 Worker 并行执行任务\",\n promptGuidelines: [\n \"Use worker when you need to execute multiple independent tasks in parallel.\",\n \"Worker 模式适合需要并行执行的复杂任务。\",\n \"每个 Worker 有独立的系统提示词和执行环境。\",\n ],\n parameters: workerSchema,\n \n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const { enable, tasks, system_prompt, max_workers, communication_mode } = params;\n \n if (!enable) {\n return {\n content: [{ type: \"text\", text: \"Worker 模式未启用,使用普通模式执行。\" }],\n details: { enabled: false } as any,\n };\n }\n \n // 更新进度\n onUpdate?.({ \n content: [{ type: \"text\", text: `启动 ${tasks.length} 个 Worker...` }],\n details: {} as any\n });\n \n try {\n // 转换任务格式\n const workerTasks: WorkerTask[] = tasks.map(task => ({\n id: task.id,\n description: task.description,\n dependencies: task.dependencies || [],\n status: \"pending\" as const,\n }));\n \n // 创建通信管理器\n const communicationManager = new CommunicationManager(\n (communication_mode as any) || \"message\"\n );\n \n // 创建协调器配置\n const coordinatorConfig: CoordinatorConfig = {\n tasks: workerTasks,\n maxWorkers: max_workers || 4,\n communicationMode: (communication_mode as any) || \"message\",\n systemPrompt: system_prompt || \"你是一个 Worker,负责执行分配给你的任务。\",\n };\n \n // 创建 Worker Manager\n const workerManager = new WorkerManager(\n coordinatorConfig,\n options?.agent || null,\n communicationManager,\n {\n model: options?.model,\n apiKey: options?.apiKey,\n tools: options?.tools || [],\n }\n );\n \n // 执行所有任务\n const result = await workerManager.executeAll();\n \n // 格式化结果\n const resultText = formatWorkerResult({\n success: result.success,\n results: result.results,\n errors: result.errors,\n duration: result.duration,\n });\n \n return {\n content: [{ type: \"text\", text: resultText }],\n details: { \n enabled: true, \n success: result.success,\n results: Object.fromEntries(result.results),\n errors: Object.fromEntries(result.errors),\n duration: result.duration,\n } as any,\n };\n } catch (error) {\n return {\n content: [{ type: \"text\", text: `Worker 执行失败: ${error}` }],\n details: { \n enabled: true, \n success: false,\n error: String(error),\n },\n isError: true,\n };\n }\n },\n \n renderCall(args, theme, context) {\n const text = (context.lastComponent as any) ?? { setText: () => {} };\n \n if (args.enable) {\n const taskCount = args.tasks?.length || 0;\n text.setText(`worker 启用 Worker 模式,${taskCount} 个任务`);\n } else {\n text.setText(\"worker 未启用\");\n }\n \n return text;\n },\n \n renderResult(result, options, theme, context) {\n const text = (context.lastComponent as any) ?? { setText: () => {} };\n \n const details = result.details as any;\n if (details?.enabled) {\n const success = details.success ? \"✓\" : \"✗\";\n const duration = details.duration;\n const taskCount = Object.keys(details.results || {}).length;\n \n text.setText(`${success} Worker 完成 (${taskCount} 个任务, ${duration}ms)`);\n } else {\n text.setText(\"Worker 模式未启用\");\n }\n \n return text;\n },\n };\n}\n\n/**\n * 格式化 Worker 结果\n */\nfunction formatWorkerResult(result: any): string {\n const lines: string[] = [];\n \n lines.push(`## Worker 执行结果`);\n lines.push(\"\");\n lines.push(`- **状态**: ${result.success ? \"✓ 成功\" : \"✗ 失败\"}`);\n lines.push(`- **耗时**: ${formatDuration(result.duration)}`);\n lines.push(`- **任务数**: ${result.results.size}`);\n \n if (result.errors.size > 0) {\n lines.push(`- **失败数**: ${result.errors.size}`);\n }\n \n lines.push(\"\");\n \n // 任务结果\n if (result.results.size > 0) {\n lines.push(`### 任务结果`);\n for (const [workerId, workerResult] of result.results) {\n const resultText = typeof workerResult.result === \"string\" \n ? workerResult.result \n : JSON.stringify(workerResult.result, null, 2);\n \n // 截断过长的结果\n const truncated = resultText.length > 200 \n ? resultText.substring(0, 200) + \"...\" \n : resultText;\n \n lines.push(`- **${workerId}**: ${truncated}`);\n }\n lines.push(\"\");\n }\n \n // 错误信息\n if (result.errors.size > 0) {\n lines.push(`### 错误信息`);\n for (const [workerId, error] of result.errors) {\n lines.push(`- **${workerId}**: ${error}`);\n }\n lines.push(\"\");\n }\n \n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化持续时间\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/core/tools/worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { Type } from \"typebox\";\nimport { Text } from \"@looze/leaf-tui\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { WorkerManager, CommunicationManager } from \"@looze/leaf-agent-core\";\nimport type { WorkerTask, CoordinatorConfig } from \"@looze/leaf-agent-core\";\nimport type { Model } from \"@looze/leaf-ai\";\n\n/**\n * Worker Tool 定义\n * \n * 用于创建和管理 Worker,并行执行任务\n */\n\nconst workerSchema = Type.Object({\n enable: Type.Boolean({ description: \"是否启用 Worker 模式\" }),\n tasks: Type.Array(Type.Object({\n id: Type.String({ description: \"任务 ID\" }),\n description: Type.String({ description: \"任务描述\" }),\n dependencies: Type.Optional(Type.Array(Type.String({ description: \"依赖的任务 ID\" }))),\n system_prompt: Type.Optional(Type.String({ description: \"Worker 的系统提示词\" })),\n })),\n system_prompt: Type.Optional(Type.String({ description: \"默认的 Worker 系统提示词\" })),\n max_workers: Type.Optional(Type.Number({ description: \"最大 Worker 数量\" })),\n communication_mode: Type.Optional(Type.String({ description: \"通信模式:socket/message/shared\" })),\n});\n\nexport type WorkerToolInput = typeof workerSchema;\n\nexport function createWorkerToolDefinition(\n cwd: string,\n options?: {\n model?: Model<any>;\n apiKey?: string;\n getApiKey?: () => string | undefined;\n streamFn?: any;\n tools?: any[];\n agent?: any;\n }\n): ToolDefinition<typeof workerSchema> {\n // 保存 options 供 execute 方法使用\n const workerOptions = options;\n \n return {\n name: \"worker\",\n label: \"Worker\",\n description: \"创建和管理 Worker,并行执行任务。启用 Worker 模式时,会创建多个独立的 Agent 并行执行任务。\",\n promptSnippet: \"创建 Worker 并行执行任务\",\n promptGuidelines: [\n \"Use worker when you need to execute multiple independent tasks in parallel.\",\n \"Worker 模式适合需要并行执行的复杂任务。\",\n \"每个 Worker 有独立的系统提示词和执行环境。\",\n ],\n parameters: workerSchema,\n \n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const { enable, tasks, system_prompt, max_workers, communication_mode } = params;\n \n if (!enable) {\n return {\n content: [{ type: \"text\", text: \"Worker 模式未启用,使用普通模式执行。\" }],\n details: { enabled: false } as any,\n };\n }\n \n // 更新进度\n onUpdate?.({ \n content: [{ type: \"text\", text: `启动 ${tasks.length} 个 Worker...` }],\n details: {} as any\n });\n \n try {\n // 转换任务格式\n const workerTasks: WorkerTask[] = tasks.map(task => ({\n id: task.id,\n description: task.description,\n dependencies: task.dependencies || [],\n status: \"pending\" as const,\n }));\n \n // 创建通信管理器\n const communicationManager = new CommunicationManager(\n (communication_mode as any) || \"message\"\n );\n \n // 创建协调器配置\n const coordinatorConfig: CoordinatorConfig = {\n tasks: workerTasks,\n maxWorkers: max_workers || 4,\n communicationMode: (communication_mode as any) || \"message\",\n systemPrompt: system_prompt || \"你是一个 Worker,负责执行分配给你的任务。\",\n };\n \n // 动态获取 apiKey\n const apiKey = typeof workerOptions?.getApiKey === \"function\"\n ? workerOptions.getApiKey()\n : workerOptions?.apiKey;\n \n // 创建 Worker Manager\n const workerManager = new WorkerManager(\n coordinatorConfig,\n workerOptions?.agent || null,\n communicationManager,\n {\n model: workerOptions?.model,\n apiKey: apiKey,\n streamFn: workerOptions?.streamFn,\n tools: workerOptions?.tools || [],\n }\n );\n \n // 执行所有任务\n const result = await workerManager.executeAll();\n \n // 格式化结果\n const resultText = formatWorkerResult({\n success: result.success,\n results: result.results,\n errors: result.errors,\n duration: result.duration,\n });\n \n return {\n content: [{ type: \"text\", text: resultText }],\n details: { \n enabled: true, \n success: result.success,\n results: Object.fromEntries(result.results),\n errors: Object.fromEntries(result.errors),\n duration: result.duration,\n } as any,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : \"\";\n \n console.error(\"[Worker Tool] 执行失败:\", error);\n \n return {\n content: [{ type: \"text\", text: `Worker 执行失败: ${errorMessage}` }],\n details: { \n enabled: true, \n success: false,\n error: errorMessage,\n stack: errorStack,\n },\n isError: true,\n };\n }\n },\n \n renderCall(args, theme, context) {\n const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n \n if (args.enable) {\n const taskCount = args.tasks?.length || 0;\n text.setText(`worker 启用 Worker 模式,${taskCount} 个任务`);\n } else {\n text.setText(\"worker 未启用\");\n }\n \n return text;\n },\n \n renderResult(result, options, theme, context) {\n const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n \n const details = result.details as any;\n \n // 直接显示结果,不再检查 enabled\n if (details) {\n const success = details.success ? \"✓\" : \"✗\";\n const duration = details.duration;\n const taskCount = Object.keys(details.results || {}).length;\n \n text.setText(`${success} Worker 完成 (${taskCount} 个任务, ${duration}ms)`);\n } else {\n text.setText(\"Worker 执行完成\");\n }\n \n return text;\n },\n };\n}\n\n/**\n * 格式化 Worker 结果\n */\nfunction formatWorkerResult(result: any): string {\n const lines: string[] = [];\n \n lines.push(`## Worker 执行结果`);\n lines.push(\"\");\n lines.push(`- **状态**: ${result.success ? \"✓ 成功\" : \"✗ 失败\"}`);\n lines.push(`- **耗时**: ${formatDuration(result.duration)}`);\n lines.push(`- **任务数**: ${result.results.size}`);\n \n if (result.errors.size > 0) {\n lines.push(`- **失败数**: ${result.errors.size}`);\n }\n \n lines.push(\"\");\n \n // 任务结果\n if (result.results.size > 0) {\n lines.push(`### 任务结果`);\n for (const [workerId, workerResult] of result.results) {\n const resultText = typeof workerResult.result === \"string\" \n ? workerResult.result \n : JSON.stringify(workerResult.result, null, 2);\n \n // 截断过长的结果\n const truncated = resultText.length > 200 \n ? resultText.substring(0, 200) + \"...\" \n : resultText;\n \n lines.push(`- **${workerId}**: ${truncated}`);\n }\n lines.push(\"\");\n }\n \n // 错误信息\n if (result.errors.size > 0) {\n lines.push(`### 错误信息`);\n for (const [workerId, error] of result.errors) {\n lines.push(`- **${workerId}**: ${error}`);\n }\n lines.push(\"\");\n }\n \n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化持续时间\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;\n}\n"]}
|
|
@@ -34,9 +34,6 @@ export declare class WorkerIntegration {
|
|
|
34
34
|
* 初始化 Worker 集成
|
|
35
35
|
*/
|
|
36
36
|
initialize(): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* 注册 Worker Tool
|
|
39
|
-
*/
|
|
40
37
|
private registerWorkerTool;
|
|
41
38
|
/**
|
|
42
39
|
* 获取 Worker 状态
|
|
@@ -62,6 +59,10 @@ export declare class WorkerIntegration {
|
|
|
62
59
|
* 检查是否有 Worker 正在运行
|
|
63
60
|
*/
|
|
64
61
|
hasRunningWorkers(): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* 更新 API Key
|
|
64
|
+
*/
|
|
65
|
+
updateApiKey(apiKey: string): void;
|
|
65
66
|
/**
|
|
66
67
|
* 清理资源
|
|
67
68
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-integration.d.ts","sourceRoot":"","sources":["../../src/core/worker-integration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,mBAAW;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,oBAAoB;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,sBAAY;IACZ,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,aAAa,CAAM;IAE3B,YAAY,MAAM,EAAE,uBAAuB,EAE1C;IAED;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAOhC;
|
|
1
|
+
{"version":3,"file":"worker-integration.d.ts","sourceRoot":"","sources":["../../src/core/worker-integration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,mBAAW;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,oBAAoB;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,sBAAY;IACZ,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,aAAa,CAAM;IAE3B,YAAY,MAAM,EAAE,uBAAuB,EAE1C;IAED;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAOhC;YAKa,kBAAkB;IAmEhC;;OAEG;IACH,eAAe,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAKrE;IAED;;OAEG;IACH,iBAAiB,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAK5E;IAED;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAIrC;IAED;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAK3B;IAED;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjC;IAED;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAK7B;CACF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,uBAAuB,GAAG,iBAAiB,CAE1F","sourcesContent":["/**\n * Worker 集成模块\n * \n * 负责将 Worker 模块集成到 Agent Session 中\n */\n\nimport type { AgentSession } from \"./agent-session.ts\";\nimport { createWorkerToolDefinition } from \"./tools/worker.ts\";\nimport type { ExtensionRunner } from \"./extensions/runner.ts\";\nimport type { Model } from \"@looze/leaf-ai\";\n\n/**\n * Worker 集成配置\n */\nexport interface WorkerIntegrationConfig {\n /** 工作目录 */\n cwd: string;\n /** Agent Session */\n agentSession: AgentSession;\n /** 扩展运行器 */\n extensionRunner: ExtensionRunner;\n /** API Key */\n apiKey?: string;\n}\n\n/**\n * Worker 集成类\n * \n * 负责:\n * 1. 注册 Worker Tool\n * 2. 管理 Worker 生命周期\n * 3. 提供 Worker 状态查询\n */\nexport class WorkerIntegration {\n private config: WorkerIntegrationConfig;\n private workerManager: any; // WorkerManager 实例\n \n constructor(config: WorkerIntegrationConfig) {\n this.config = config;\n }\n \n /**\n * 初始化 Worker 集成\n */\n async initialize(): Promise<void> {\n console.log(\"[WorkerIntegration] 初始化 Worker 集成\");\n \n // 注册 Worker Tool\n await this.registerWorkerTool();\n \n console.log(\"[WorkerIntegration] Worker 集成初始化完成\");\n }\n \n /**\n * 注册 Worker Tool\n */\n private async registerWorkerTool(): Promise<void> {\n // 从 agentSession 获取当前模型和工具\n const model = this.config.agentSession.model;\n const agent = (this.config.agentSession as any).agent;\n let tools = (this.config.agentSession as any).agent?.state?.tools || [];\n \n // 确保有基础工具\n if (tools.length === 0) {\n try {\n const toolsModule = await import(\"./tools/index.ts\");\n tools = toolsModule.createCodingTools(this.config.cwd);\n } catch (error) {\n console.warn(\"[WorkerIntegration] 加载基础工具失败:\", error);\n }\n }\n \n // 创建 Worker Tool,传递 model、getApiKey、streamFn、tools 和 agent\n const workerTool = createWorkerToolDefinition(this.config.cwd, {\n model: model,\n getApiKey: () => this.config.apiKey,\n streamFn: agent?.streamFn,\n tools: tools,\n agent: agent,\n });\n \n // 直接访问 extensionRunner 的 extensions 数组\n const extensionRunner = this.config.extensionRunner as any;\n \n // 如果 extensions 为空,创建一个内置扩展\n if (!extensionRunner.extensions || extensionRunner.extensions.length === 0) {\n extensionRunner.extensions = [{\n sourceInfo: {\n path: \"builtin:worker\",\n source: \"builtin\",\n scope: \"temporary\",\n origin: \"top-level\",\n },\n tools: new Map(),\n commands: new Map(),\n handlers: new Map(),\n flagValues: new Map(),\n shortcuts: [],\n }];\n }\n \n // 在第一个扩展中注册 Worker Tool\n const firstExtension = extensionRunner.extensions[0];\n if (firstExtension.tools) {\n firstExtension.tools.set(workerTool.name, {\n definition: workerTool,\n sourceInfo: {\n path: \"builtin:worker\",\n source: \"builtin\",\n scope: \"temporary\",\n origin: \"top-level\",\n },\n });\n \n console.log(\"[WorkerIntegration] Worker Tool 已注册到扩展:\", firstExtension.sourceInfo?.path);\n }\n \n // 刷新工具注册表\n if (extensionRunner.runtime?.refreshTools) {\n extensionRunner.runtime.refreshTools();\n }\n }\n \n /**\n * 获取 Worker 状态\n */\n getWorkerStatus(): Array<{ id: string; status: string; task: string }> {\n if (!this.workerManager) {\n return [];\n }\n return this.workerManager.getWorkerStatus();\n }\n \n /**\n * 获取 Worker 进度\n */\n getWorkerProgress(): { completed: number; total: number; percentage: number } {\n if (!this.workerManager) {\n return { completed: 0, total: 0, percentage: 0 };\n }\n return this.workerManager.getProgress();\n }\n \n /**\n * 中止所有 Worker\n */\n async abortAllWorkers(): Promise<void> {\n if (this.workerManager) {\n await this.workerManager.abortAll();\n }\n }\n \n /**\n * 检查是否有 Worker 正在运行\n */\n hasRunningWorkers(): boolean {\n if (!this.workerManager) {\n return false;\n }\n return !this.workerManager.isAllCompleted();\n }\n \n /**\n * 更新 API Key\n */\n updateApiKey(apiKey: string): void {\n this.config.apiKey = apiKey;\n }\n \n /**\n * 清理资源\n */\n async cleanup(): Promise<void> {\n if (this.workerManager) {\n await this.workerManager.abortAll();\n this.workerManager = null;\n }\n }\n}\n\n/**\n * 创建 Worker 集成\n */\nexport function createWorkerIntegration(config: WorkerIntegrationConfig): WorkerIntegration {\n return new WorkerIntegration(config);\n}\n"]}
|
|
@@ -24,21 +24,32 @@ export class WorkerIntegration {
|
|
|
24
24
|
async initialize() {
|
|
25
25
|
console.log("[WorkerIntegration] 初始化 Worker 集成");
|
|
26
26
|
// 注册 Worker Tool
|
|
27
|
-
this.registerWorkerTool();
|
|
27
|
+
await this.registerWorkerTool();
|
|
28
28
|
console.log("[WorkerIntegration] Worker 集成初始化完成");
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* 注册 Worker Tool
|
|
32
32
|
*/
|
|
33
|
-
registerWorkerTool() {
|
|
33
|
+
async registerWorkerTool() {
|
|
34
34
|
// 从 agentSession 获取当前模型和工具
|
|
35
35
|
const model = this.config.agentSession.model;
|
|
36
36
|
const agent = this.config.agentSession.agent;
|
|
37
|
-
|
|
38
|
-
//
|
|
37
|
+
let tools = this.config.agentSession.agent?.state?.tools || [];
|
|
38
|
+
// 确保有基础工具
|
|
39
|
+
if (tools.length === 0) {
|
|
40
|
+
try {
|
|
41
|
+
const toolsModule = await import("./tools/index.js");
|
|
42
|
+
tools = toolsModule.createCodingTools(this.config.cwd);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.warn("[WorkerIntegration] 加载基础工具失败:", error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// 创建 Worker Tool,传递 model、getApiKey、streamFn、tools 和 agent
|
|
39
49
|
const workerTool = createWorkerToolDefinition(this.config.cwd, {
|
|
40
50
|
model: model,
|
|
41
|
-
|
|
51
|
+
getApiKey: () => this.config.apiKey,
|
|
52
|
+
streamFn: agent?.streamFn,
|
|
42
53
|
tools: tools,
|
|
43
54
|
agent: agent,
|
|
44
55
|
});
|
|
@@ -114,6 +125,12 @@ export class WorkerIntegration {
|
|
|
114
125
|
}
|
|
115
126
|
return !this.workerManager.isAllCompleted();
|
|
116
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* 更新 API Key
|
|
130
|
+
*/
|
|
131
|
+
updateApiKey(apiKey) {
|
|
132
|
+
this.config.apiKey = apiKey;
|
|
133
|
+
}
|
|
117
134
|
/**
|
|
118
135
|
* 清理资源
|
|
119
136
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-integration.js","sourceRoot":"","sources":["../../src/core/worker-integration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAkB/D;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IACpB,MAAM,CAA0B;IAChC,aAAa,CAAM,CAAC,uBAAmB;IAE/C,YAAY,MAA+B,EAAE;QAC3C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACtB;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,GAAkB;QAChC,OAAO,CAAC,GAAG,CAAC,6CAAmC,CAAC,CAAC;QAEjD,qBAAiB;QACjB,IAAI,CAAC,kBAAkB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-integration.js","sourceRoot":"","sources":["../../src/core/worker-integration.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Worker 集成模块\n * \n * 负责将 Worker 模块集成到 Agent Session 中\n */\n\nimport type { AgentSession } from \"./agent-session.ts\";\nimport { createWorkerToolDefinition } from \"./tools/worker.ts\";\nimport type { ExtensionRunner } from \"./extensions/runner.ts\";\nimport type { Model } from \"@looze/leaf-ai\";\n\n/**\n * Worker 集成配置\n */\nexport interface WorkerIntegrationConfig {\n /** 工作目录 */\n cwd: string;\n /** Agent Session */\n agentSession: AgentSession;\n /** 扩展运行器 */\n extensionRunner: ExtensionRunner;\n /** API Key */\n apiKey?: string;\n}\n\n/**\n * Worker 集成类\n * \n * 负责:\n * 1. 注册 Worker Tool\n * 2. 管理 Worker 生命周期\n * 3. 提供 Worker 状态查询\n */\nexport class WorkerIntegration {\n private config: WorkerIntegrationConfig;\n private workerManager: any; // WorkerManager 实例\n \n constructor(config: WorkerIntegrationConfig) {\n this.config = config;\n }\n \n /**\n * 初始化 Worker 集成\n */\n async initialize(): Promise<void> {\n console.log(\"[WorkerIntegration] 初始化 Worker 集成\");\n \n // 注册 Worker Tool\n await this.registerWorkerTool();\n \n console.log(\"[WorkerIntegration] Worker 集成初始化完成\");\n }\n \n /**\n * 注册 Worker Tool\n */\n private async registerWorkerTool(): Promise<void> {\n // 从 agentSession 获取当前模型和工具\n const model = this.config.agentSession.model;\n const agent = (this.config.agentSession as any).agent;\n let tools = (this.config.agentSession as any).agent?.state?.tools || [];\n \n // 确保有基础工具\n if (tools.length === 0) {\n try {\n const toolsModule = await import(\"./tools/index.ts\");\n tools = toolsModule.createCodingTools(this.config.cwd);\n } catch (error) {\n console.warn(\"[WorkerIntegration] 加载基础工具失败:\", error);\n }\n }\n \n // 创建 Worker Tool,传递 model、getApiKey、streamFn、tools 和 agent\n const workerTool = createWorkerToolDefinition(this.config.cwd, {\n model: model,\n getApiKey: () => this.config.apiKey,\n streamFn: agent?.streamFn,\n tools: tools,\n agent: agent,\n });\n \n // 直接访问 extensionRunner 的 extensions 数组\n const extensionRunner = this.config.extensionRunner as any;\n \n // 如果 extensions 为空,创建一个内置扩展\n if (!extensionRunner.extensions || extensionRunner.extensions.length === 0) {\n extensionRunner.extensions = [{\n sourceInfo: {\n path: \"builtin:worker\",\n source: \"builtin\",\n scope: \"temporary\",\n origin: \"top-level\",\n },\n tools: new Map(),\n commands: new Map(),\n handlers: new Map(),\n flagValues: new Map(),\n shortcuts: [],\n }];\n }\n \n // 在第一个扩展中注册 Worker Tool\n const firstExtension = extensionRunner.extensions[0];\n if (firstExtension.tools) {\n firstExtension.tools.set(workerTool.name, {\n definition: workerTool,\n sourceInfo: {\n path: \"builtin:worker\",\n source: \"builtin\",\n scope: \"temporary\",\n origin: \"top-level\",\n },\n });\n \n console.log(\"[WorkerIntegration] Worker Tool 已注册到扩展:\", firstExtension.sourceInfo?.path);\n }\n \n // 刷新工具注册表\n if (extensionRunner.runtime?.refreshTools) {\n extensionRunner.runtime.refreshTools();\n }\n }\n \n /**\n * 获取 Worker 状态\n */\n getWorkerStatus(): Array<{ id: string; status: string; task: string }> {\n if (!this.workerManager) {\n return [];\n }\n return this.workerManager.getWorkerStatus();\n }\n \n /**\n * 获取 Worker 进度\n */\n getWorkerProgress(): { completed: number; total: number; percentage: number } {\n if (!this.workerManager) {\n return { completed: 0, total: 0, percentage: 0 };\n }\n return this.workerManager.getProgress();\n }\n \n /**\n * 中止所有 Worker\n */\n async abortAllWorkers(): Promise<void> {\n if (this.workerManager) {\n await this.workerManager.abortAll();\n }\n }\n \n /**\n * 检查是否有 Worker 正在运行\n */\n hasRunningWorkers(): boolean {\n if (!this.workerManager) {\n return false;\n }\n return !this.workerManager.isAllCompleted();\n }\n \n /**\n * 更新 API Key\n */\n updateApiKey(apiKey: string): void {\n this.config.apiKey = apiKey;\n }\n \n /**\n * 清理资源\n */\n async cleanup(): Promise<void> {\n if (this.workerManager) {\n await this.workerManager.abortAll();\n this.workerManager = null;\n }\n }\n}\n\n/**\n * 创建 Worker 集成\n */\nexport function createWorkerIntegration(config: WorkerIntegrationConfig): WorkerIntegration {\n return new WorkerIntegration(config);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,iBAAiB,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA0BxF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYhF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,UAAU,CAA6B;IAE/C,YAAY,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,0BAA0B,EAGxE;IAED,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsJ9B;CACD","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"@looze/leaf-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,iBAAiB,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA8BxF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYhF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,UAAU,CAA6B;IAE/C,YAAY,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,0BAA0B,EAGxE;IAED,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsJ9B;CACD","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"@looze/leaf-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// 确保 text 是字符串\n\tif (typeof text !== \"string\") {\n\t\treturn String(text || \"\");\n\t}\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -6,6 +6,10 @@ import { theme } from "../theme/theme.js";
|
|
|
6
6
|
* Removes newlines, tabs, carriage returns, and other control characters.
|
|
7
7
|
*/
|
|
8
8
|
function sanitizeStatusText(text) {
|
|
9
|
+
// 确保 text 是字符串
|
|
10
|
+
if (typeof text !== "string") {
|
|
11
|
+
return String(text || "");
|
|
12
|
+
}
|
|
9
13
|
// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces
|
|
10
14
|
return text
|
|
11
15
|
.replace(/[\r\n\t]/g, " ")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"@looze/leaf-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"@looze/leaf-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// 确保 text 是字符串\n\tif (typeof text !== \"string\") {\n\t\treturn String(text || \"\");\n\t}\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|