leaf-coding-agent 1.0.3 → 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.
Files changed (33) hide show
  1. package/dist/core/agent-session.d.ts +16 -2
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +62 -13
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/export-html/index.d.ts.map +1 -1
  6. package/dist/core/export-html/index.js +3 -1
  7. package/dist/core/export-html/index.js.map +1 -1
  8. package/dist/core/extensions/runner.d.ts +4 -1
  9. package/dist/core/extensions/runner.d.ts.map +1 -1
  10. package/dist/core/extensions/runner.js +4 -1
  11. package/dist/core/extensions/runner.js.map +1 -1
  12. package/dist/core/system-prompt.d.ts +14 -0
  13. package/dist/core/system-prompt.d.ts.map +1 -1
  14. package/dist/core/system-prompt.js +43 -23
  15. package/dist/core/system-prompt.js.map +1 -1
  16. package/dist/core/tools/worker.d.ts +3 -0
  17. package/dist/core/tools/worker.d.ts.map +1 -1
  18. package/dist/core/tools/worker.js +47 -14
  19. package/dist/core/tools/worker.js.map +1 -1
  20. package/dist/core/worker-integration.d.ts +4 -7
  21. package/dist/core/worker-integration.d.ts.map +1 -1
  22. package/dist/core/worker-integration.js +62 -26
  23. package/dist/core/worker-integration.js.map +1 -1
  24. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  25. package/dist/modes/interactive/components/footer.js +4 -0
  26. package/dist/modes/interactive/components/footer.js.map +1 -1
  27. package/dist/modes/interactive/components/worker-selector.d.ts.map +1 -1
  28. package/dist/modes/interactive/components/worker-selector.js +18 -23
  29. package/dist/modes/interactive/components/worker-selector.js.map +1 -1
  30. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  31. package/dist/modes/interactive/interactive-mode.js +11 -14
  32. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  33. package/package.json +1 -1
@@ -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, null, // agent (Worker 内部会创建自己的 agent)
64
- communicationManager, {
65
- model: options?.model,
66
- apiKey: options?.apiKey,
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 执行失败: ${error}` }],
101
+ content: [{ type: "text", text: `Worker 执行失败: ${errorMessage}` }],
91
102
  details: {
92
103
  enabled: true,
93
104
  success: false,
94
- error: String(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 ?? { setText: () => { } };
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 ?? { setText: () => { } };
124
+ const text = context.lastComponent ?? new Text("", 0, 0);
113
125
  const details = result.details;
114
- if (details?.enabled) {
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
  },
@@ -131,15 +144,25 @@ function formatWorkerResult(result) {
131
144
  const lines = [];
132
145
  lines.push(`## Worker 执行结果`);
133
146
  lines.push("");
134
- lines.push(`- 状态: ${result.success ? "成功" : "失败"}`);
135
- lines.push(`- 耗时: ${result.duration}ms`);
136
- lines.push(`- 任务数: ${result.results.size}`);
147
+ lines.push(`- **状态**: ${result.success ? "成功" : "失败"}`);
148
+ lines.push(`- **耗时**: ${formatDuration(result.duration)}`);
149
+ lines.push(`- **任务数**: ${result.results.size}`);
150
+ if (result.errors.size > 0) {
151
+ lines.push(`- **失败数**: ${result.errors.size}`);
152
+ }
137
153
  lines.push("");
138
154
  // 任务结果
139
155
  if (result.results.size > 0) {
140
156
  lines.push(`### 任务结果`);
141
157
  for (const [workerId, workerResult] of result.results) {
142
- lines.push(`- **${workerId}**: ${JSON.stringify(workerResult.result)}`);
158
+ const resultText = typeof workerResult.result === "string"
159
+ ? workerResult.result
160
+ : JSON.stringify(workerResult.result, null, 2);
161
+ // 截断过长的结果
162
+ const truncated = resultText.length > 200
163
+ ? resultText.substring(0, 200) + "..."
164
+ : resultText;
165
+ lines.push(`- **${workerId}**: ${truncated}`);
143
166
  }
144
167
  lines.push("");
145
168
  }
@@ -153,4 +176,14 @@ function formatWorkerResult(result) {
153
176
  }
154
177
  return lines.join("\n");
155
178
  }
179
+ /**
180
+ * 格式化持续时间
181
+ */
182
+ function formatDuration(ms) {
183
+ if (ms < 1000)
184
+ return `${ms}ms`;
185
+ if (ms < 60000)
186
+ return `${(ms / 1000).toFixed(1)}s`;
187
+ return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
188
+ }
156
189
  //# sourceMappingURL=worker.js.map
@@ -1 +1 @@
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 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 }\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 null, // agent (Worker 内部会创建自己的 agent)\n communicationManager,\n {\n model: options?.model,\n apiKey: options?.apiKey,\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(`- 耗时: ${result.duration}ms`);\n lines.push(`- 任务数: ${result.results.size}`);\n lines.push(\"\");\n \n // 任务结果\n if (result.results.size > 0) {\n lines.push(`### 任务结果`);\n for (const [workerId, workerResult] of result.results) {\n lines.push(`- **${workerId}**: ${JSON.stringify(workerResult.result)}`);\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"]}
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,14 +34,7 @@ export declare class WorkerIntegration {
34
34
  * 初始化 Worker 集成
35
35
  */
36
36
  initialize(): Promise<void>;
37
- /**
38
- * 注册 Worker Tool
39
- */
40
37
  private registerWorkerTool;
41
- /**
42
- * 注册扩展命令
43
- */
44
- private registerCommands;
45
38
  /**
46
39
  * 获取 Worker 状态
47
40
  */
@@ -66,6 +59,10 @@ export declare class WorkerIntegration {
66
59
  * 检查是否有 Worker 正在运行
67
60
  */
68
61
  hasRunningWorkers(): boolean;
62
+ /**
63
+ * 更新 API Key
64
+ */
65
+ updateApiKey(apiKey: string): void;
69
66
  /**
70
67
  * 清理资源
71
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,CAUhC;IAED;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAwB1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;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;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 this.registerWorkerTool();\n \n // 注册扩展命令\n this.registerCommands();\n \n console.log(\"[WorkerIntegration] Worker 集成初始化完成\");\n }\n \n /**\n * 注册 Worker Tool\n */\n private registerWorkerTool(): void {\n // 从 agentSession 获取当前模型\n const model = this.config.agentSession.model;\n \n // 创建 Worker Tool,传递 model 和 apiKey\n const workerTool = createWorkerToolDefinition(this.config.cwd, {\n model: model,\n apiKey: this.config.apiKey,\n });\n \n // 注册到扩展运行器\n this.config.extensionRunner.getAllRegisteredTools().push({\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 已注册\");\n }\n \n /**\n * 注册扩展命令\n */\n private registerCommands(): void {\n // 注册 /worker 命令\n this.config.extensionRunner.getCommand(\"worker\");\n \n console.log(\"[WorkerIntegration] Worker 命令已注册\");\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 * 清理资源\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
+ {"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、toolsagent\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,41 +24,71 @@ export class WorkerIntegration {
24
24
  async initialize() {
25
25
  console.log("[WorkerIntegration] 初始化 Worker 集成");
26
26
  // 注册 Worker Tool
27
- this.registerWorkerTool();
28
- // 注册扩展命令
29
- this.registerCommands();
27
+ await this.registerWorkerTool();
30
28
  console.log("[WorkerIntegration] Worker 集成初始化完成");
31
29
  }
32
30
  /**
33
31
  * 注册 Worker Tool
34
32
  */
35
- registerWorkerTool() {
36
- // 从 agentSession 获取当前模型
33
+ async registerWorkerTool() {
34
+ // 从 agentSession 获取当前模型和工具
37
35
  const model = this.config.agentSession.model;
38
- // 创建 Worker Tool,传递 model 和 apiKey
36
+ const agent = this.config.agentSession.agent;
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
- apiKey: this.config.apiKey,
42
- });
43
- // 注册到扩展运行器
44
- this.config.extensionRunner.getAllRegisteredTools().push({
45
- definition: workerTool,
46
- sourceInfo: {
47
- path: "builtin:worker",
48
- source: "builtin",
49
- scope: "temporary",
50
- origin: "top-level",
51
- },
51
+ getApiKey: () => this.config.apiKey,
52
+ streamFn: agent?.streamFn,
53
+ tools: tools,
54
+ agent: agent,
52
55
  });
53
- console.log("[WorkerIntegration] Worker Tool 已注册");
54
- }
55
- /**
56
- * 注册扩展命令
57
- */
58
- registerCommands() {
59
- // 注册 /worker 命令
60
- this.config.extensionRunner.getCommand("worker");
61
- console.log("[WorkerIntegration] Worker 命令已注册");
56
+ // 直接访问 extensionRunner 的 extensions 数组
57
+ const extensionRunner = this.config.extensionRunner;
58
+ // 如果 extensions 为空,创建一个内置扩展
59
+ if (!extensionRunner.extensions || extensionRunner.extensions.length === 0) {
60
+ extensionRunner.extensions = [{
61
+ sourceInfo: {
62
+ path: "builtin:worker",
63
+ source: "builtin",
64
+ scope: "temporary",
65
+ origin: "top-level",
66
+ },
67
+ tools: new Map(),
68
+ commands: new Map(),
69
+ handlers: new Map(),
70
+ flagValues: new Map(),
71
+ shortcuts: [],
72
+ }];
73
+ }
74
+ // 在第一个扩展中注册 Worker Tool
75
+ const firstExtension = extensionRunner.extensions[0];
76
+ if (firstExtension.tools) {
77
+ firstExtension.tools.set(workerTool.name, {
78
+ definition: workerTool,
79
+ sourceInfo: {
80
+ path: "builtin:worker",
81
+ source: "builtin",
82
+ scope: "temporary",
83
+ origin: "top-level",
84
+ },
85
+ });
86
+ console.log("[WorkerIntegration] Worker Tool 已注册到扩展:", firstExtension.sourceInfo?.path);
87
+ }
88
+ // 刷新工具注册表
89
+ if (extensionRunner.runtime?.refreshTools) {
90
+ extensionRunner.runtime.refreshTools();
91
+ }
62
92
  }
63
93
  /**
64
94
  * 获取 Worker 状态
@@ -95,6 +125,12 @@ export class WorkerIntegration {
95
125
  }
96
126
  return !this.workerManager.isAllCompleted();
97
127
  }
128
+ /**
129
+ * 更新 API Key
130
+ */
131
+ updateApiKey(apiKey) {
132
+ this.config.apiKey = apiKey;
133
+ }
98
134
  /**
99
135
  * 清理资源
100
136
  */
@@ -1 +1 @@
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 this.registerWorkerTool();\n \n // 注册扩展命令\n this.registerCommands();\n \n console.log(\"[WorkerIntegration] Worker 集成初始化完成\");\n }\n \n /**\n * 注册 Worker Tool\n */\n private registerWorkerTool(): void {\n // 从 agentSession 获取当前模型\n const model = this.config.agentSession.model;\n \n // 创建 Worker Tool,传递 model 和 apiKey\n const workerTool = createWorkerToolDefinition(this.config.cwd, {\n model: model,\n apiKey: this.config.apiKey,\n });\n \n // 注册到扩展运行器\n this.config.extensionRunner.getAllRegisteredTools().push({\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 已注册\");\n }\n \n /**\n * 注册扩展命令\n */\n private registerCommands(): void {\n // 注册 /worker 命令\n this.config.extensionRunner.getCommand(\"worker\");\n \n console.log(\"[WorkerIntegration] Worker 命令已注册\");\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 * 清理资源\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
+ {"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、toolsagent\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":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAkB,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGhF,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;GAGG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAU;IACjD,qFAAqF;IACrF,OAAO,IAAI;SACT,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,IAAI,EAAE,CAAC;AAAA,CACT;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,KAAa,EAAU;IAC5C,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1C,IAAI,KAAK,GAAG,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,IAAI,KAAK,GAAG,OAAO;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;IAC3D,IAAI,KAAK,GAAG,QAAQ;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAChE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC;AAAA,CACzC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,IAAwB,EAAU;IACjF,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,CAAC;IAEtB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,cAAc,GAAG,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,YAAY,GACjB,cAAc,KAAK,EAAE;QACrB,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;IAEpG,IAAI,CAAC,YAAY;QAAE,OAAO,GAAG,CAAC;IAC9B,OAAO,cAAc,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,cAAc,EAAE,CAAC;AAAA,CAChE;AAED;;;GAGG;AACH,MAAM,OAAO,eAAe;IACnB,kBAAkB,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAe;IACtB,UAAU,CAA6B;IAE/C,YAAY,OAAqB,EAAE,UAAsC,EAAE;QAC1E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAAA,CAC7B;IAED,UAAU,CAAC,OAAqB,EAAQ;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAAA,CACvB;IAED,qBAAqB,CAAC,OAAgB,EAAQ;QAC7C,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC;IAAA,CAClC;IAED;;;OAGG;IACH,UAAU,GAAS;QAClB,sDAAsD;IADnC,CAEnB;IAED;;;OAGG;IACH,OAAO,GAAS;QACf,0CAA0C;IAD1B,CAEhB;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEjC,0FAA0F;QAC1F,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,CAAC;YAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACpE,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;gBACxC,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;gBAC1C,cAAc,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;gBAChD,eAAe,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;gBAClD,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC7C,CAAC;QACF,CAAC;QAED,uEAAuE;QACvE,oEAAoE;QACpE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QACpD,MAAM,aAAa,GAAG,YAAY,EAAE,aAAa,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC,CAAC;QACrF,MAAM,mBAAmB,GAAG,YAAY,EAAE,OAAO,IAAI,CAAC,CAAC;QACvD,MAAM,cAAc,GAAG,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAE7F,gCAAgC;QAChC,IAAI,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEhH,8BAA8B;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QAC9C,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,GAAG,GAAG,GAAG,KAAK,MAAM,GAAG,CAAC;QAC5B,CAAC;QAED,0BAA0B;QAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;QACjE,IAAI,WAAW,EAAE,CAAC;YACjB,GAAG,GAAG,GAAG,GAAG,QAAM,WAAW,EAAE,CAAC;QACjC,CAAC;QAED,mBAAmB;QACnB,MAAM,UAAU,GAAG,EAAE,CAAC;QACtB,IAAI,UAAU;YAAE,UAAU,CAAC,IAAI,CAAC,MAAI,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAChE,IAAI,WAAW;YAAE,UAAU,CAAC,IAAI,CAAC,MAAI,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAClE,IAAI,cAAc;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACxE,IAAI,eAAe;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAE1E,+DAA+D;QAC/D,MAAM,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrG,IAAI,SAAS,IAAI,iBAAiB,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/E,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QAED,6CAA6C;QAC7C,IAAI,iBAAyB,CAAC;QAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,qBAAqB,GAC1B,cAAc,KAAK,GAAG;YACrB,CAAC,CAAC,KAAK,YAAY,CAAC,aAAa,CAAC,GAAG,aAAa,EAAE;YACpD,CAAC,CAAC,GAAG,cAAc,KAAK,YAAY,CAAC,aAAa,CAAC,GAAG,aAAa,EAAE,CAAC;QACxE,IAAI,mBAAmB,GAAG,EAAE,EAAE,CAAC;YAC9B,iBAAiB,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;QAC9D,CAAC;aAAM,IAAI,mBAAmB,GAAG,EAAE,EAAE,CAAC;YACrC,iBAAiB,GAAG,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QAChE,CAAC;aAAM,CAAC;YACP,iBAAiB,GAAG,qBAAqB,CAAC;QAC3C,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAEnC,IAAI,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAErC,6EAA6E;QAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,UAAU,CAAC;QAEhD,IAAI,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAE7C,wCAAwC;QACxC,IAAI,cAAc,GAAG,KAAK,EAAE,CAAC;YAC5B,SAAS,GAAG,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACrD,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;QAED,mFAAmF;QACnF,MAAM,UAAU,GAAG,CAAC,CAAC;QAErB,2DAA2D;QAC3D,IAAI,wBAAwB,GAAG,SAAS,CAAC;QACzC,IAAI,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC;YAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC;YACnD,wBAAwB;gBACvB,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,mBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,QAAM,aAAa,EAAE,CAAC;QAC9F,CAAC;QAED,8FAA8F;QAC9F,IAAI,SAAS,GAAG,wBAAwB,CAAC;QACzC,IAAI,IAAI,CAAC,UAAU,CAAC,yBAAyB,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACpE,SAAS,GAAG,IAAI,KAAK,CAAC,KAAM,CAAC,QAAQ,KAAK,wBAAwB,EAAE,CAAC;YACrE,IAAI,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,KAAK,EAAE,CAAC;gBACnE,sBAAsB;gBACtB,SAAS,GAAG,wBAAwB,CAAC;YACtC,CAAC;QACF,CAAC;QAED,MAAM,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,cAAc,CAAC;QAEjE,IAAI,SAAiB,CAAC;QACtB,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;YAC1B,8CAA8C;YAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC;YACpE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;QAC7C,CAAC;aAAM,CAAC;YACP,8BAA8B;YAC9B,MAAM,iBAAiB,GAAG,KAAK,GAAG,cAAc,GAAG,UAAU,CAAC;YAC9D,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,cAAc,GAAG,eAAe,CAAC,SAAS,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;gBACzE,MAAM,mBAAmB,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,cAAc,GAAG,mBAAmB,CAAC,CAAC,CAAC;gBACtF,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACP,yCAAyC;gBACzC,SAAS,GAAG,SAAS,CAAC;YACvB,CAAC;QACF,CAAC;QAED,uFAAuF;QACvF,qFAAqF;QACrF,sDAAsD;QACtD,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,sBAAsB;QAC3E,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAEhD,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACrF,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;QAErD,wEAAwE;QACxE,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC;QACjE,IAAI,iBAAiB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;iBAC5D,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;iBACtC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5C,iFAAiF;YACjF,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;QAED,OAAO,KAAK,CAAC;IAAA,CACb;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.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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"worker-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/worker-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,SAAS,EAET,YAAY,EAGZ,MAAM,iBAAiB,CAAC;AAIzB,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;CACnD;AAED,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,yBAAyB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC;IAC3E,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,uBAAwB,SAAQ,SAAS;IACrD,OAAO,CAAC,YAAY,CAAe;IAEnC,YACC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,uBAAuB,EAwElC;IAED,eAAe,IAAI,YAAY,CAE9B;CACD","sourcesContent":["import {\n\tContainer,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@looze/leaf-tui\";\nimport { getSettingsListTheme, theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nexport interface WorkerConfig {\n\tenabled: boolean;\n\tmaxWorkers: number;\n\tcommunicationMode: \"socket\" | \"message\" | \"shared\";\n}\n\nexport interface WorkerSelectorCallbacks {\n\tonEnabledChange: (enabled: boolean) => void;\n\tonMaxWorkersChange: (maxWorkers: number) => void;\n\tonCommunicationModeChange: (mode: \"socket\" | \"message\" | \"shared\") => void;\n\tonAbortAll: () => void;\n\tonCancel: () => void;\n}\n\nexport class WorkerSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(\n\t\tconfig: WorkerConfig,\n\t\tcallbacks: WorkerSelectorCallbacks,\n\t) {\n\t\tsuper();\n\n\t\t// 标题\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Worker 设置\")), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// 添加边框\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"worker-enabled\",\n\t\t\t\tlabel: \"Worker 模式\",\n\t\t\t\tdescription: \"启用或禁用 Worker 并行执行模式\",\n\t\t\t\tcurrentValue: config.enabled ? \"开启\" : \"关闭\",\n\t\t\t\tvalues: [\"开启\", \"关闭\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"max-workers\",\n\t\t\t\tlabel: \"最大 Worker 数\",\n\t\t\t\tdescription: \"同时运行的最大 Worker 数量\",\n\t\t\t\tcurrentValue: String(config.maxWorkers),\n\t\t\t\tvalues: [\"1\", \"2\", \"4\", \"8\", \"16\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"communication-mode\",\n\t\t\t\tlabel: \"通信模式\",\n\t\t\t\tdescription: \"Worker 之间的通信方式\",\n\t\t\t\tcurrentValue: config.communicationMode,\n\t\t\t\tvalues: [\"message\", \"socket\", \"shared\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"abort-all\",\n\t\t\t\tlabel: \"中止所有 Worker\",\n\t\t\t\tdescription: \"立即停止所有正在运行的 Worker\",\n\t\t\t\tcurrentValue: \"执行\",\n\t\t\t\tvalues: [\"执行\"],\n\t\t\t},\n\t\t];\n\n\t\t// 添加边框\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\t10,\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"worker-enabled\":\n\t\t\t\t\t\tcallbacks.onEnabledChange(newValue === \"开启\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"max-workers\":\n\t\t\t\t\t\tcallbacks.onMaxWorkersChange(parseInt(newValue, 10));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"communication-mode\":\n\t\t\t\t\t\tcallbacks.onCommunicationModeChange(newValue as \"socket\" | \"message\" | \"shared\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"abort-all\":\n\t\t\t\t\t\tcallbacks.onAbortAll();\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcallbacks.onCancel,\n\t\t\t{ enableSearch: false },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"worker-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/worker-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,SAAS,EAET,YAAY,EAGZ,MAAM,iBAAiB,CAAC;AAIzB,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;CACnD;AAED,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,yBAAyB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC;IAC3E,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,uBAAwB,SAAQ,SAAS;IACrD,OAAO,CAAC,YAAY,CAAe;IAEnC,YACC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,uBAAuB,EAkElC;IAED,eAAe,IAAI,YAAY,CAE9B;CACD","sourcesContent":["import {\n\tContainer,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@looze/leaf-tui\";\nimport { getSettingsListTheme, theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nexport interface WorkerConfig {\n\tenabled: boolean;\n\tmaxWorkers: number;\n\tcommunicationMode: \"socket\" | \"message\" | \"shared\";\n}\n\nexport interface WorkerSelectorCallbacks {\n\tonEnabledChange: (enabled: boolean) => void;\n\tonMaxWorkersChange: (maxWorkers: number) => void;\n\tonCommunicationModeChange: (mode: \"socket\" | \"message\" | \"shared\") => void;\n\tonAbortAll: () => void;\n\tonCancel: () => void;\n}\n\nexport class WorkerSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(\n\t\tconfig: WorkerConfig,\n\t\tcallbacks: WorkerSelectorCallbacks,\n\t) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"worker-enabled\",\n\t\t\t\tlabel: \"Worker Mode\",\n\t\t\t\tdescription: \"Enable or disable Worker parallel execution mode\",\n\t\t\t\tcurrentValue: config.enabled ? \"ON\" : \"OFF\",\n\t\t\t\tvalues: [\"ON\", \"OFF\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"max-workers\",\n\t\t\t\tlabel: \"Max Workers\",\n\t\t\t\tdescription: \"Maximum number of concurrent Workers\",\n\t\t\t\tcurrentValue: String(config.maxWorkers),\n\t\t\t\tvalues: [\"1\", \"2\", \"4\", \"8\", \"16\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"communication-mode\",\n\t\t\t\tlabel: \"Communication\",\n\t\t\t\tdescription: \"Communication method between Workers\",\n\t\t\t\tcurrentValue: config.communicationMode,\n\t\t\t\tvalues: [\"message\", \"socket\", \"shared\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"abort-all\",\n\t\t\t\tlabel: \"Abort All\",\n\t\t\t\tdescription: \"Stop all running Workers immediately\",\n\t\t\t\tcurrentValue: \"Execute\",\n\t\t\t\tvalues: [\"Execute\"],\n\t\t\t},\n\t\t];\n\n\t\t// 上边框\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\t10,\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"worker-enabled\":\n\t\t\t\t\t\tcallbacks.onEnabledChange(newValue === \"ON\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"max-workers\":\n\t\t\t\t\t\tcallbacks.onMaxWorkersChange(parseInt(newValue, 10));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"communication-mode\":\n\t\t\t\t\t\tcallbacks.onCommunicationModeChange(newValue as \"socket\" | \"message\" | \"shared\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"abort-all\":\n\t\t\t\t\t\tcallbacks.onAbortAll();\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcallbacks.onCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\t\n\t\t// 下边框\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}