kiku-agent 1.3.2 → 1.3.4

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 (152) hide show
  1. package/README.md +69 -12
  2. package/package.json +2 -1
  3. package/server/agent/agentCore.js +319 -46
  4. package/server/agent/appPaths.js +31 -0
  5. package/server/agent/audioPlayer.js +339 -12
  6. package/server/agent/autoContext.js +193 -0
  7. package/server/agent/catalogEntityResolver.js +170 -28
  8. package/server/agent/clarifyingQuestions.js +137 -35
  9. package/server/agent/cli.js +177 -48
  10. package/server/agent/cloudAdapters.js +116 -0
  11. package/server/agent/commandRegistry.js +409 -80
  12. package/server/agent/commands.js +1056 -125
  13. package/server/agent/config.js +1088 -690
  14. package/server/agent/contextCompactor.js +213 -21
  15. package/server/agent/cpuMetrics.js +199 -0
  16. package/server/agent/engineService.js +591 -0
  17. package/server/agent/eval/modules/evaluation/runner.js +4 -14
  18. package/server/agent/eval/modules/evaluation/scoring.js +15 -3
  19. package/server/agent/eval/modules/server/evalApi.js +5 -5
  20. package/server/agent/eval/modules/ui/evalUi.js +7 -4
  21. package/server/agent/eventBus.js +1 -0
  22. package/server/agent/ink/main.jsx +196 -191
  23. package/server/agent/jobManager.js +11 -1
  24. package/server/agent/lanDiscovery.js +721 -23
  25. package/server/agent/llmClient.js +317 -71
  26. package/server/agent/lmStudioWakeup.js +517 -65
  27. package/server/agent/locationService.js +797 -0
  28. package/server/agent/mcpQuizData.js +509 -0
  29. package/server/agent/memory/contextAllocator.js +19 -7
  30. package/server/agent/memory.js +3 -1
  31. package/server/agent/modelCompatibility.js +352 -0
  32. package/server/agent/modelDefaults.js +1 -0
  33. package/server/agent/modelManager.js +46 -2
  34. package/server/agent/newsWatch.js +9 -3
  35. package/server/agent/processManager.js +229 -0
  36. package/server/agent/prompts.js +174 -8
  37. package/server/agent/routingInterceptor.js +9 -1
  38. package/server/agent/sanitizer.js +41 -6
  39. package/server/agent/searchQueryPlanner.js +202 -0
  40. package/server/agent/session.js +59 -4
  41. package/server/agent/tests/agentFollowUps.test.js +128 -2
  42. package/server/agent/tests/agentMusicControl.test.js +245 -0
  43. package/server/agent/tests/apiRouter.test.js +1 -1
  44. package/server/agent/tests/autoContext.test.js +176 -0
  45. package/server/agent/tests/bilibiliEmbed.test.js +45 -0
  46. package/server/agent/tests/chatHardening.test.js +121 -0
  47. package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
  48. package/server/agent/tests/clarifyingQuestions.test.js +33 -0
  49. package/server/agent/tests/cliAutomation.test.js +38 -1
  50. package/server/agent/tests/cloudProviders.test.js +91 -3
  51. package/server/agent/tests/commands.test.js +92 -3
  52. package/server/agent/tests/contextIntelligence.test.js +50 -3
  53. package/server/agent/tests/cpuMetrics.test.js +101 -0
  54. package/server/agent/tests/decisionAndSearchEnhancements.test.js +32 -0
  55. package/server/agent/tests/edgeCasesScenarios.test.js +82 -0
  56. package/server/agent/tests/engineService.test.js +436 -0
  57. package/server/agent/tests/engineUnload.test.js +94 -0
  58. package/server/agent/tests/envCommand.test.js +69 -0
  59. package/server/agent/tests/evalMetrics.test.js +8 -0
  60. package/server/agent/tests/fetchApi.test.js +99 -0
  61. package/server/agent/tests/hybridWebSearch.test.js +2 -0
  62. package/server/agent/tests/lanDiscovery.test.js +5 -0
  63. package/server/agent/tests/lanInterfaces.test.js +510 -0
  64. package/server/agent/tests/lmStudioWakeup.test.js +278 -0
  65. package/server/agent/tests/locationTool.test.js +278 -0
  66. package/server/agent/tests/memoryRoute.test.js +173 -0
  67. package/server/agent/tests/memoryUiNormalization.test.js +75 -0
  68. package/server/agent/tests/modelCompatibility.test.js +148 -0
  69. package/server/agent/tests/modelManager.test.js +32 -0
  70. package/server/agent/tests/musicControlUiBridge.test.js +113 -0
  71. package/server/agent/tests/nextGenArchitecture.test.js +4 -2
  72. package/server/agent/tests/openDomainPrompt.test.js +197 -0
  73. package/server/agent/tests/openDomainSearch.test.js +121 -0
  74. package/server/agent/tests/processManager.test.js +144 -0
  75. package/server/agent/tests/providerPersistence.test.js +10 -5
  76. package/server/agent/tests/quickActionsAndModals.test.js +180 -0
  77. package/server/agent/tests/searchQueryPlanner.test.js +108 -0
  78. package/server/agent/tests/securityAuditRemediation.test.js +16 -9
  79. package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
  80. package/server/agent/tests/securityHardening.test.js +4 -4
  81. package/server/agent/tests/sessionAndCopy.test.js +39 -4
  82. package/server/agent/tests/sidebarControls.test.js +34 -0
  83. package/server/agent/tests/statefulMemory.test.js +8 -1
  84. package/server/agent/tests/textProtocolLoop.test.js +71 -0
  85. package/server/agent/tests/textToolParsing.test.js +48 -0
  86. package/server/agent/tests/toolRoutingRegressions.test.js +41 -0
  87. package/server/agent/tests/unifiedEndpoints.test.js +13 -6
  88. package/server/agent/tests/v110Features.test.js +1 -1
  89. package/server/agent/tests/vramEstimator.test.js +75 -0
  90. package/server/agent/tests/webChatDocs.test.js +199 -0
  91. package/server/agent/tests/webChatInferenceControl.test.js +298 -0
  92. package/server/agent/tests/webChatInspection.test.js +256 -5
  93. package/server/agent/tests/webChatMcp.test.js +94 -0
  94. package/server/agent/tests/webExtract.test.js +23 -0
  95. package/server/agent/tests/webSources.test.js +46 -12
  96. package/server/agent/textToolProtocol.js +173 -46
  97. package/server/agent/toolRouter.js +318 -44
  98. package/server/agent/tools.js +877 -96
  99. package/server/agent/ui.js +39 -34
  100. package/server/agent/vramService.js +35 -54
  101. package/server/agent/webSearch.js +441 -162
  102. package/server/agent/websearch/orchestrator.js +15 -5
  103. package/server/agent/websearch/resilience/cache.js +9 -2
  104. package/server/agent/websearch/security/ssrf.js +41 -10
  105. package/server/agent/youtubeAuth.js +5 -20
  106. package/server/apiRouter.js +5558 -4422
  107. package/server/mcp/data/discography.js +2 -2
  108. package/server/mcp/examples/antigravity.example.json +17 -0
  109. package/server/mcp/handler.js +8 -4
  110. package/server/mcp/security.js +220 -21
  111. package/server/mcp/server.js +2 -1
  112. package/server/mcp/tests/connection.test.js +14 -8
  113. package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
  114. package/server/mcp/tools.js +7 -1
  115. package/server/supabase_migration_prefix.sql +102 -0
  116. package/server/supabase_schema.sql +18 -18
  117. package/server/web-chat/core/connection.js +179 -218
  118. package/server/web-chat/core/conversationContext.js +9 -2
  119. package/server/web-chat/core/directCatalogEngine.js +52 -28
  120. package/server/web-chat/core/dramaKnowledge.js +33 -22
  121. package/server/web-chat/core/followUpEngine.js +735 -81
  122. package/server/web-chat/core/futureEventGuard.js +5 -1
  123. package/server/web-chat/core/musicControlAction.js +70 -0
  124. package/server/web-chat/core/security.js +32 -21
  125. package/server/web-chat/core/toolInspector.js +6 -6
  126. package/server/web-chat/core/urlGuard.js +45 -0
  127. package/server/web-chat/router.js +204 -157
  128. package/server/web-chat/routes/chatRoute.js +885 -488
  129. package/server/web-chat/routes/engineRoute.js +270 -0
  130. package/server/web-chat/routes/exportRoute.js +1 -1
  131. package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
  132. package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
  133. package/server/web-chat/routes/mcpRoute.js +386 -0
  134. package/server/web-chat/routes/memoryRoute.js +253 -0
  135. package/server/web-chat/routes/providerRoute.js +49 -16
  136. package/server/web-chat/routes/staticRoute.js +306 -17
  137. package/server/web-chat/routes/statusRoute.js +194 -73
  138. package/server/web-chat/routes/suggestRoute.js +11 -3
  139. package/server/web-chat/routes/verifyRoute.js +26 -4
  140. package/server/web-chat/server.js +7 -6
  141. package/server/web-chat/ui/docsBuilder.js +3558 -0
  142. package/server/web-chat/ui/htmlBuilder.js +12 -0
  143. package/server/web-chat/ui/scripts/client.js +3401 -1302
  144. package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
  145. package/server/web-chat/ui/settingsBuilder.js +7870 -0
  146. package/server/web-chat/ui/styles/lan-card.css +165 -0
  147. package/server/web-chat/ui/styles/main.css +4289 -447
  148. package/server/web-chat/ui/sw.js +64 -0
  149. package/server/web-chat/ui/templates/body.html +407 -281
  150. package/src/data/awardsData.js +146 -146
  151. package/src/data/musicVideoData.js +6 -4
  152. package/src/lib/vram/estimator.js +195 -19
@@ -1,6 +1,9 @@
1
1
  import { cases, expandedCases, securityCases, allCases, legacyCases } from '../evaluation/cases.js';
2
2
 
3
- export function getEvalUiHtml() {
3
+ export function getEvalUiHtml(gates = {}) {
4
+ const passAt1Threshold = gates.passAt1?.min ?? gates.lexicalCoverageRate?.min ?? 0.75;
5
+ const toolF1Threshold = gates.toolF1?.min ?? gates.toolAccuracyRate?.min ?? 0.8;
6
+ const loopFreeRateThreshold = gates.loopFreeRate?.min ?? 0.95;
4
7
  const catalogCount = cases.length;
5
8
  const expandedCount = expandedCases ? expandedCases.length : 0;
6
9
  const securityCount = securityCases.length;
@@ -4276,9 +4279,9 @@ export function getEvalUiHtml() {
4276
4279
  return;
4277
4280
  }
4278
4281
 
4279
- const passAt1Ok = (s.passAt1 ?? s.lexicalCoverageRate ?? 0) >= 0.75;
4280
- const toolOk = (s.toolF1 ?? s.toolAccuracyRate ?? 0) >= 0.80;
4281
- const loopOk = (s.loopFreeRate ?? 1) >= 0.95;
4282
+ const passAt1Ok = (s.passAt1 ?? s.lexicalCoverageRate ?? 0) >= ${passAt1Threshold};
4283
+ const toolOk = (s.toolF1 ?? s.toolAccuracyRate ?? 0) >= ${toolF1Threshold};
4284
+ const loopOk = (s.loopFreeRate ?? 1) >= ${loopFreeRateThreshold};
4282
4285
  const allPassed = passAt1Ok && toolOk && loopOk;
4283
4286
 
4284
4287
  overallBadge.className = allPassed ? 'badge-gate badge-pass' : 'badge-gate badge-fail';
@@ -83,6 +83,7 @@ export const Events = Object.freeze({
83
83
  AGENT_TOOL_START: 'agent:tool:start',
84
84
  AGENT_TOOL_RESULT: 'agent:tool:result',
85
85
  AGENT_TOOL_ERROR: 'agent:tool:error',
86
+ AGENT_TOOL_ESCALATED:'agent:tool:escalated',
86
87
 
87
88
  AGENT_PLAN_START: 'agent:plan:start',
88
89
  AGENT_PLAN_STEP_UPDATE: 'agent:plan:step:update',
@@ -2,194 +2,199 @@ import React from 'react';
2
2
  import { render } from 'ink';
3
3
  import '../youtubeAuth.js';
4
4
  import { App } from './App.jsx';
5
- import { KikuAgent } from '../agentCore.js';
6
- import { LMStudioClient } from '../llmClient.js';
7
- import { toolsSchema } from '../tools.js';
8
- import { config } from '../config.js';
9
- import { COMMAND_DEFINITIONS } from '../commandRegistry.js';
10
- import { C, UI_ICONS } from '../ui.js';
11
- import { existsSync } from 'node:fs';
12
- import { spawn } from 'node:child_process';
13
- import { PACKAGE_VERSION } from '../version.js';
14
-
15
- function parseCliArgs() {
16
- const args = process.argv.slice(2);
17
- const options = {
18
- query: null,
19
- model: 'google/gemma-4-e4b',
20
- url: null,
21
- showTools: false,
22
- showStats: false,
23
- thinking: 'show',
24
- verbose: false,
25
- help: false,
26
- version: false
27
- };
28
-
29
- for (let i = 0; i < args.length; i++) {
30
- const arg = args[i];
31
- if (arg === '--help' || arg === '-h') options.help = true;
32
- else if (arg === '--version' || arg === '-V') options.version = true;
33
- else if (arg === '--tools' || arg === '-t') options.showTools = true;
34
- else if (arg === '--stats' || arg === '-s') options.showStats = true;
35
- else if (arg === '--verbose' || arg === '-v') { options.verbose = true; options.thinking = 'show'; }
36
- else if (arg === '--thinking') options.thinking = args[++i] || 'show';
37
- else if (arg === '--model' || arg === '-m') options.model = args[++i] || options.model;
38
- else if (arg === '--url' || arg === '-u') options.url = args[++i];
39
- else if (arg === '--query' || arg === '-q') options.query = args[++i];
40
- else if (!arg.startsWith('-') && !options.query) { options.query = args.slice(i).join(' '); break; }
41
- }
42
- return options;
43
- }
44
-
45
- async function waitForLMStudio(baseUrl, timeoutMs = 30000) {
46
- const client = new LMStudioClient({ baseUrl });
47
- const start = Date.now();
48
- while (Date.now() - start < timeoutMs) {
49
- const health = await client.checkConnection();
50
- if (health.connected) return health;
51
- await new Promise(r => setTimeout(r, 1000));
52
- }
53
- return null;
54
- }
55
-
56
- async function attemptAutoStartLMStudio(baseUrl, model) {
57
- const isWin = process.platform === 'win32';
58
- const isMac = process.platform === 'darwin';
59
-
60
- const candidates = isWin
61
- ? [
62
- `${process.env.LOCALAPPDATA}\\LM Studio\\LM Studio.exe`,
63
- `${process.env.LOCALAPPDATA}\\Programs\\LM Studio\\LM Studio.exe`,
64
- 'C:\\Program Files\\LM Studio\\LM Studio.exe'
65
- ]
66
- : isMac
67
- ? ['/Applications/LM Studio.app/Contents/MacOS/LM Studio']
68
- : ['lmstudio', '/usr/local/bin/lmstudio', '/opt/lmstudio/bin/lmstudio'];
69
-
70
- const exe = candidates.find(p => {
71
- if (p.includes('/') || p.includes('\\')) {
72
- try { return existsSync(p); } catch { return false; }
73
- }
74
- return true;
75
- });
76
-
77
- if (!exe) return null;
78
-
79
- try {
80
- let port = '1234';
81
- try { const parsedUrl = new URL(baseUrl); if (parsedUrl.port) port = parsedUrl.port; } catch {}
82
-
83
- const child = spawn(exe, isWin
84
- ? ['--model', model, '--serve', '--port', port]
85
- : ['serve', '--model', model, '--port', port],
86
- { detached: true, stdio: 'ignore', windowsHide: true }
87
- );
88
- child.unref();
89
-
90
- return await waitForLMStudio(baseUrl, 45000);
91
- } catch { return null; }
92
- }
93
-
94
- function printHelpText() {
95
- const commands = COMMAND_DEFINITIONS
96
- .map(command => ` ${C.emerald}${command.usage.padEnd(30)}${C.reset} ${command.desc}`)
97
- .join('\n');
98
- console.log(`\n ${C.bold}${C.gold}${UI_ICONS.help} Kiku AI Agent — ${COMMAND_DEFINITIONS.length} Commands${C.reset}\n${commands}\n ${C.dim}Tip: Press [Tab] for auto-completion.${C.reset}\n`);
99
- }
100
-
101
- async function printStatsText() {
102
- const { executeTool } = await import('../tools.js');
103
- const result = await executeTool('get_system_stats', {});
104
- const s = result.stats;
105
- console.log(JSON.stringify(s, null, 2));
106
- }
107
-
108
- async function main() {
109
- const opts = parseCliArgs();
110
-
111
- if (opts.version) { console.log(PACKAGE_VERSION); process.exit(0); }
112
- if (opts.help) { printHelpText(); process.exit(0); }
113
- if (opts.showStats) { await printStatsText(); process.exit(0); }
114
-
115
- if (opts.showTools) {
116
- console.log(`\n ${C.bold}${C.sakura}Tools (${toolsSchema.length})${C.reset}\n`);
117
- for (const [i, t] of toolsSchema.entries()) {
118
- console.log(` ${C.gold}${String(i + 1).padStart(2)}${C.reset} ${C.cyberCyan}${t.function.name}${C.reset} — ${C.dim}${t.function.description.slice(0, 60)}${C.reset}`);
119
- }
120
- process.exit(0);
121
- }
122
-
123
- const baseUrl = opts.url || config.lmStudio.baseUrl;
124
- const client = new LMStudioClient({ baseUrl, model: opts.model });
125
- const health = await client.checkConnection();
126
-
127
- if (!health.connected) {
128
- console.log(` ${C.amber}!! LM Studio offline${C.reset}`);
129
- console.log(` ${C.dim}• ${health.error}${C.reset}\n`);
130
-
131
- const autoHealth = await attemptAutoStartLMStudio(baseUrl, opts.model || config.lmStudio.model);
132
- if (autoHealth) {
133
- console.log(` ${C.emerald}[OK] LM Studio started.${C.reset}\n`);
134
- } else {
135
- console.log(` ${C.sakura}Setup: Open LM Studio → load google/gemma-4-e4b → Start Server :1234${C.reset}\n`);
136
- process.exit(1);
137
- }
138
- }
139
-
140
- const agent = new KikuAgent({
141
- baseUrl,
142
- model: health.activeModel || opts.model,
143
- thinkingMode: opts.thinking,
144
- });
145
-
146
- await agent._ready;
147
-
148
- if (opts.query) {
149
- const { executeTool: execTool } = await import('../tools.js');
150
- const { sanitizeQuery } = await import('../sanitizer.js');
151
- const { sanitized } = sanitizeQuery(opts.query);
152
- const prompt = sanitized || opts.query;
153
-
154
- console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${prompt}\n`);
155
-
156
- await agent.ask(prompt, {
157
- onReasoningStart: () => process.stdout.write(' THINKING '),
158
- onReasoningDelta: (chunk) => process.stdout.write(chunk),
159
- onReasoningEnd: () => process.stdout.write('\n'),
160
- onToolStart: (name) => process.stdout.write(` [RUNNING] ${name}... `),
161
- onToolResult: () => process.stdout.write(`[OK]\n`),
162
- onTokenDelta: (chunk) => process.stdout.write(chunk),
163
- onFinalAnswer: () => {},
164
- onError: (err) => console.error(`\n [ERROR] ${err.message}`),
165
- });
166
- process.exit(0);
167
- }
168
-
169
- const helpFn = (sub) => { printHelpText(); };
170
- const adFn = () => { console.log(` ${UI_ICONS.sponsor} SPONSOR DashSecurity — https://dashsecurity.kpltgroup.com/`); };
171
-
172
- const { waitUntilExit } = render(
173
- <App
174
- agent={agent}
175
- client={client}
176
- baseUrl={baseUrl}
177
- printHelp={helpFn}
178
- printAd={adFn}
179
- />,
180
- {
181
- exitOnCtrlC: true,
182
- patchConsole: false,
183
- }
184
- );
185
-
186
- await waitUntilExit;
187
-
188
- try { await agent.endSession(); } catch {}
189
- console.log('\n Session archived. Goodbye. (鞠躬 ~ 谢谢!)\n');
190
- }
191
-
192
- main().catch(err => {
193
- console.error(`\n ${C.red}Fatal: ${err.message}${C.reset}\n`);
194
- process.exit(1);
195
- });
5
+ import { KikuAgent } from '../agentCore.js';
6
+ import { LMStudioClient } from '../llmClient.js';
7
+ import { toolsSchema } from '../tools.js';
8
+ import { config } from '../config.js';
9
+ import { COMMAND_DEFINITIONS } from '../commandRegistry.js';
10
+ import { C, UI_ICONS } from '../ui.js';
11
+ import { existsSync } from 'node:fs';
12
+ import { spawn } from 'node:child_process';
13
+ import { PACKAGE_VERSION } from '../version.js';
14
+ import { resolveDynamicWindowsAppPaths } from '../appPaths.js';
15
+ import { registerBackgroundProcess } from '../processManager.js';
16
+
17
+ function parseCliArgs() {
18
+ const args = process.argv.slice(2);
19
+ const options = {
20
+ query: null,
21
+ model: 'google/gemma-4-e4b',
22
+ url: null,
23
+ showTools: false,
24
+ showStats: false,
25
+ thinking: 'show',
26
+ verbose: false,
27
+ help: false,
28
+ version: false
29
+ };
30
+
31
+ for (let i = 0; i < args.length; i++) {
32
+ const arg = args[i];
33
+ if (arg === '--help' || arg === '-h') options.help = true;
34
+ else if (arg === '--version' || arg === '-V') options.version = true;
35
+ else if (arg === '--tools' || arg === '-t') options.showTools = true;
36
+ else if (arg === '--stats' || arg === '-s') options.showStats = true;
37
+ else if (arg === '--verbose' || arg === '-v') { options.verbose = true; options.thinking = 'show'; }
38
+ else if (arg === '--thinking') options.thinking = args[++i] || 'show';
39
+ else if (arg === '--model' || arg === '-m') options.model = args[++i] || options.model;
40
+ else if (arg === '--url' || arg === '-u') options.url = args[++i];
41
+ else if (arg === '--query' || arg === '-q') options.query = args[++i];
42
+ else if (!arg.startsWith('-') && !options.query) { options.query = args.slice(i).join(' '); break; }
43
+ }
44
+ return options;
45
+ }
46
+
47
+ async function waitForLMStudio(baseUrl, timeoutMs = 30000) {
48
+ const client = new LMStudioClient({ baseUrl });
49
+ const start = Date.now();
50
+ while (Date.now() - start < timeoutMs) {
51
+ const health = await client.checkConnection();
52
+ if (health.connected) return health;
53
+ await new Promise(r => setTimeout(r, 1000));
54
+ }
55
+ return null;
56
+ }
57
+
58
+ async function attemptAutoStartLMStudio(baseUrl, model) {
59
+ const isWin = process.platform === 'win32';
60
+ const isMac = process.platform === 'darwin';
61
+
62
+ const candidates = isWin
63
+ ? resolveDynamicWindowsAppPaths('LM Studio', 'LM Studio.exe')
64
+ : isMac
65
+ ? ['/Applications/LM Studio.app/Contents/MacOS/LM Studio']
66
+ : ['lmstudio', '/usr/local/bin/lmstudio', '/opt/lmstudio/bin/lmstudio'];
67
+
68
+ const exe = candidates.find(p => {
69
+ if (p.includes('/') || p.includes('\\')) {
70
+ try { return existsSync(p); } catch { return false; }
71
+ }
72
+ return true;
73
+ });
74
+
75
+ if (!exe) return null;
76
+
77
+ try {
78
+ let port = '1234';
79
+ try { const parsedUrl = new URL(baseUrl); if (parsedUrl.port) port = parsedUrl.port; } catch {}
80
+
81
+ const child = spawn(exe, isWin
82
+ ? ['--model', model, '--serve', '--port', port]
83
+ : ['serve', '--model', model, '--port', port],
84
+ { detached: true, stdio: 'ignore', windowsHide: true }
85
+ );
86
+ if (child.pid) {
87
+ registerBackgroundProcess('lmstudio', child, {
88
+ command: exe,
89
+ args: isWin ? ['--model', model, '--serve', '--port', port] : ['serve', '--model', model, '--port', port],
90
+ metadata: { model, port },
91
+ });
92
+ }
93
+ child.unref();
94
+
95
+ return await waitForLMStudio(baseUrl, 45000);
96
+ } catch { return null; }
97
+ }
98
+
99
+ function printHelpText() {
100
+ const commands = COMMAND_DEFINITIONS
101
+ .map(command => ` ${C.emerald}${command.usage.padEnd(30)}${C.reset} ${command.desc}`)
102
+ .join('\n');
103
+ console.log(`\n ${C.bold}${C.gold}${UI_ICONS.help} Kiku AI Agent — ${COMMAND_DEFINITIONS.length} Commands${C.reset}\n${commands}\n ${C.dim}Tip: Press [Tab] for auto-completion.${C.reset}\n`);
104
+ }
105
+
106
+ async function printStatsText() {
107
+ const { executeTool } = await import('../tools.js');
108
+ const result = await executeTool('get_system_stats', {});
109
+ const s = result.stats;
110
+ console.log(JSON.stringify(s, null, 2));
111
+ }
112
+
113
+ async function main() {
114
+ const opts = parseCliArgs();
115
+
116
+ if (opts.version) { console.log(PACKAGE_VERSION); process.exit(0); }
117
+ if (opts.help) { printHelpText(); process.exit(0); }
118
+ if (opts.showStats) { await printStatsText(); process.exit(0); }
119
+
120
+ if (opts.showTools) {
121
+ console.log(`\n ${C.bold}${C.sakura}Tools (${toolsSchema.length})${C.reset}\n`);
122
+ for (const [i, t] of toolsSchema.entries()) {
123
+ console.log(` ${C.gold}${String(i + 1).padStart(2)}${C.reset} ${C.cyberCyan}${t.function.name}${C.reset} — ${C.dim}${t.function.description.slice(0, 60)}${C.reset}`);
124
+ }
125
+ process.exit(0);
126
+ }
127
+
128
+ const baseUrl = opts.url || config.lmStudio.baseUrl;
129
+ const client = new LMStudioClient({ baseUrl, model: opts.model });
130
+ const health = await client.checkConnection();
131
+
132
+ if (!health.connected) {
133
+ console.log(` ${C.amber}!! LM Studio offline${C.reset}`);
134
+ console.log(` ${C.dim}• ${health.error}${C.reset}\n`);
135
+
136
+ const autoHealth = await attemptAutoStartLMStudio(baseUrl, opts.model || config.lmStudio.model);
137
+ if (autoHealth) {
138
+ console.log(` ${C.emerald}[OK] LM Studio started.${C.reset}\n`);
139
+ } else {
140
+ console.log(` ${C.sakura}Setup: Open LM Studio → load google/gemma-4-e4b → Start Server :1234${C.reset}\n`);
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ const agent = new KikuAgent({
146
+ baseUrl,
147
+ model: health.activeModel || opts.model,
148
+ thinkingMode: opts.thinking,
149
+ });
150
+
151
+ await agent._ready;
152
+
153
+ if (opts.query) {
154
+ const { executeTool: execTool } = await import('../tools.js');
155
+ const { sanitizeQuery } = await import('../sanitizer.js');
156
+ const { sanitized } = sanitizeQuery(opts.query);
157
+ const prompt = sanitized || opts.query;
158
+
159
+ console.log(` ${C.bold}${C.gold}QUERY${C.reset} ${prompt}\n`);
160
+
161
+ await agent.ask(prompt, {
162
+ onReasoningStart: () => process.stdout.write(' THINKING '),
163
+ onReasoningDelta: (chunk) => process.stdout.write(chunk),
164
+ onReasoningEnd: () => process.stdout.write('\n'),
165
+ onToolStart: (name) => process.stdout.write(` [RUNNING] ${name}... `),
166
+ onToolResult: () => process.stdout.write(`[OK]\n`),
167
+ onTokenDelta: (chunk) => process.stdout.write(chunk),
168
+ onFinalAnswer: () => {},
169
+ onError: (err) => console.error(`\n [ERROR] ${err.message}`),
170
+ });
171
+ process.exit(0);
172
+ }
173
+
174
+ const helpFn = (sub) => { printHelpText(); };
175
+ const adFn = () => { console.log(` ${UI_ICONS.sponsor} SPONSOR DashSecurity — https://dashsecurity.kpltgroup.com/`); };
176
+
177
+ const { waitUntilExit } = render(
178
+ <App
179
+ agent={agent}
180
+ client={client}
181
+ baseUrl={baseUrl}
182
+ printHelp={helpFn}
183
+ printAd={adFn}
184
+ />,
185
+ {
186
+ exitOnCtrlC: true,
187
+ patchConsole: false,
188
+ }
189
+ );
190
+
191
+ await waitUntilExit;
192
+
193
+ try { await agent.endSession(); } catch {}
194
+ console.log('\n Session archived. Goodbye. (鞠躬 ~ 谢谢!)\n');
195
+ }
196
+
197
+ main().catch(err => {
198
+ console.error(`\n ${C.red}Fatal: ${err.message}${C.reset}\n`);
199
+ process.exit(1);
200
+ });
@@ -3,8 +3,9 @@ function errorMessage(error) {
3
3
  }
4
4
 
5
5
  export class AgentJobManager {
6
- constructor(limit = 50) {
6
+ constructor(limit = 50, maxConcurrentHeavy = 2) {
7
7
  this.limit = limit;
8
+ this.maxConcurrentHeavy = maxConcurrentHeavy;
8
9
  this.jobs = [];
9
10
  }
10
11
 
@@ -28,6 +29,15 @@ export class AgentJobManager {
28
29
  }
29
30
 
30
31
  async run(type, label, runner, options = {}) {
32
+ const isHeavy = type === 'research' || type === 'verify';
33
+ const limit = options.maxConcurrent ?? (isHeavy ? this.maxConcurrentHeavy : Infinity);
34
+ if (Number.isFinite(limit)) {
35
+ const activeCount = this.jobs.filter(j => (j.type === type || (isHeavy && (j.type === 'research' || j.type === 'verify'))) && j.state === 'running').length;
36
+ if (activeCount >= limit) {
37
+ throw new Error(`Too many concurrent ${type} tasks running (${activeCount}/${limit}). Please wait for the active task to complete.`);
38
+ }
39
+ }
40
+
31
41
  const job = this.create(type, label);
32
42
  const abortParent = () => job.controller.abort(options.signal?.reason);
33
43
  options.signal?.addEventListener('abort', abortParent, { once: true });