kiku-agent 1.0.7 → 1.0.8

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.
@@ -12,7 +12,8 @@ import { ResearchOrchestrator } from './research.js';
12
12
  import { SuggestionEngine } from './suggestions.js';
13
13
  import { McpClientManager } from './mcpClient.js';
14
14
  import { DynamicToolRouter } from './toolRouter.js';
15
- import { contextCompactor } from './contextCompactor.js';
15
+ import { contextCompactor } from './contextCompactor.js';
16
+ import { AgentJobManager } from './jobManager.js';
16
17
 
17
18
  export function detectDominantLanguage(text) {
18
19
  if (!text || typeof text !== 'string') return 'th';
@@ -63,7 +64,8 @@ export class KikuAgent {
63
64
  this.mcpManager = options.mcpManager || new McpClientManager();
64
65
  this.router = new DynamicToolRouter(this.getAllToolsSchema());
65
66
  this.research = new ResearchOrchestrator({ client: this.client });
66
- this.suggestions = new SuggestionEngine({ client: this.client, memory: this.memory });
67
+ this.suggestions = new SuggestionEngine({ client: this.client, memory: this.memory });
68
+ this.jobs = new AgentJobManager();
67
69
  this.systemPrompt = this.buildSystemPrompt();
68
70
  this.initHistory();
69
71
  this.restoredMessageCount = 0;
@@ -101,33 +103,39 @@ export class KikuAgent {
101
103
  return this._ready;
102
104
  }
103
105
 
104
- async runSubagent(task, options = {}) {
105
- await this._ready;
106
- const subagent = new ResearchSubagent({
107
- client: this.client,
108
- maxIterations: options.maxIterations || 3,
109
- });
110
- return subagent.run(task, {
111
- signal: options.signal,
112
- onProgress: options.onProgress,
113
- skillContext: this.memory.buildUserSkillContext(),
114
- });
115
- }
116
-
117
- async runResearch(question, options = {}) {
118
- await this._ready;
119
- return this.research.run(question, {
120
- ...options,
121
- skillContext: this.memory.buildUserSkillContext(),
122
- });
106
+ async runSubagent(task, options = {}) {
107
+ await this._ready;
108
+ return this.jobs.run('subagent', task, async ({ signal }) => {
109
+ const subagent = new ResearchSubagent({
110
+ client: this.client,
111
+ maxIterations: options.maxIterations || 3,
112
+ });
113
+ return subagent.run(task, {
114
+ signal,
115
+ onProgress: options.onProgress,
116
+ skillContext: this.memory.buildUserSkillContext(),
117
+ });
118
+ }, options);
119
+ }
120
+
121
+ async runResearch(question, options = {}) {
122
+ await this._ready;
123
+ const type = options.mode === 'verify' ? 'verify' : 'research';
124
+ return this.jobs.run(type, question, ({ signal }) => this.research.run(question, {
125
+ ...options,
126
+ signal,
127
+ skillContext: this.memory.buildUserSkillContext(),
128
+ }), options);
123
129
  }
124
130
 
125
131
  getResearchStatus() {
126
132
  return this.research.getStatus();
127
133
  }
128
134
 
129
- cancelResearch() {
130
- return this.research.cancel();
135
+ cancelResearch() {
136
+ const direct = this.research.cancel();
137
+ const tracked = this.jobs.cancelType('research') || this.jobs.cancelType('verify');
138
+ return direct || tracked;
131
139
  }
132
140
 
133
141
  async generateSuggestions(options = {}) {
@@ -135,14 +143,33 @@ export class KikuAgent {
135
143
  return this.suggestions.generateSuggestions(options);
136
144
  }
137
145
 
138
- async runSuggestionBatch(targets, options = {}) {
139
- await this._ready;
140
- return this.suggestions.runBatchSubagents(targets, options);
141
- }
142
-
143
- getLastSuggestions() {
144
- return this.suggestions.getLastSuggestions();
145
- }
146
+ async runSuggestionBatch(targets, options = {}) {
147
+ await this._ready;
148
+ return this.jobs.run('suggestion', String(targets || 'all'), ({ signal }) => this.suggestions.runBatchSubagents(targets, {
149
+ ...options,
150
+ signal,
151
+ }), options);
152
+ }
153
+
154
+ getLastSuggestions() {
155
+ return this.suggestions.getLastSuggestions();
156
+ }
157
+
158
+ getJobs() {
159
+ return this.jobs.list();
160
+ }
161
+
162
+ getJob(id) {
163
+ return this.jobs.get(id);
164
+ }
165
+
166
+ cancelJob(id) {
167
+ return this.jobs.cancel(id);
168
+ }
169
+
170
+ clearJobs() {
171
+ return this.jobs.clear();
172
+ }
146
173
 
147
174
  initHistory() {
148
175
  this.conversationHistory = [
@@ -17,8 +17,9 @@ import { sanitizeQuery } from './sanitizer.js';
17
17
  import { isAbortError } from './errors.js';
18
18
  import { COMMAND_DEFINITIONS, SUB_COMMANDS, findCommandDefinition, getCommandDetails } from './commandRegistry.js';
19
19
  import { ModelManager } from './modelManager.js';
20
- import { PACKAGE_VERSION } from './version.js';
21
- import { getTheme, getThemeName } from './theme.js';
20
+ import { PACKAGE_VERSION } from './version.js';
21
+ import { getTheme, getThemeName } from './theme.js';
22
+ import { createTerminalImageGallery } from './terminalImage.js';
22
23
  import {
23
24
  classifyToolResult,
24
25
  formatDetailDuration,
@@ -38,7 +39,7 @@ import {
38
39
  printWelcomeHero, getWelcomeHeroLines, randomFanName, setupOutputInterception,
39
40
  rawWrite, INPUT_PLACEHOLDER, UI_ICONS,
40
41
  spinnerFrames, thinkingFrames, createProgressRunner,
41
- enterAlternateScreen, exitAlternateScreen, getTuiViewportLayout, VirtualScreenBuffer,
42
+ enterAlternateScreen, exitAlternateScreen, getTuiViewportLayout, getNewOutputScrollOffset, VirtualScreenBuffer,
42
43
  } from './ui.js';
43
44
 
44
45
  let activeAbortController = null;
@@ -490,7 +491,11 @@ function printAd() {
490
491
 
491
492
  class InteractiveTuiPrompt {
492
493
  constructor(options = {}) {
493
- this.promptText = options.prompt || 'Kiku ❯ ';
494
+ this.defaultPromptText = options.prompt || 'Kiku ❯ ';
495
+ this.promptText = this.defaultPromptText;
496
+ this.inputPlaceholder = INPUT_PLACEHOLDER;
497
+ this.recordInputHistory = true;
498
+ this.cancelValue = null;
494
499
  this.agent = options.agent || null;
495
500
  this.modelName = options.modelName || 'gemma-4-e4b';
496
501
  this.provider = options.provider || 'lmstudio';
@@ -521,27 +526,28 @@ class InteractiveTuiPrompt {
521
526
  );
522
527
  }
523
528
 
524
- addViewportLines(lines) {
525
- if (lines === null || lines === undefined) return;
526
- if (!Array.isArray(lines)) lines = [lines];
527
- const wasLive = this.scrollOffset === 0;
528
- for (const l of lines) {
529
- if (typeof l === 'string') {
530
- const sublines = l.split('\n');
531
- for (const sl of sublines) {
532
- this.viewportLines.push(sl);
533
- }
534
- } else if (l != null) {
535
- this.viewportLines.push(String(l));
536
- }
529
+ addViewportLines(lines) {
530
+ if (lines === null || lines === undefined) return;
531
+ if (!Array.isArray(lines)) lines = [lines];
532
+ const wasLive = this.scrollOffset === 0;
533
+ let addedLineCount = 0;
534
+ for (const l of lines) {
535
+ if (typeof l === 'string') {
536
+ const sublines = l.split('\n');
537
+ for (const sl of sublines) {
538
+ this.viewportLines.push(sl);
539
+ addedLineCount += 1;
540
+ }
541
+ } else if (l != null) {
542
+ this.viewportLines.push(String(l));
543
+ addedLineCount += 1;
544
+ }
537
545
  }
538
546
  if (this.viewportLines.length > 5000) {
539
547
  const removedCount = this.viewportLines.length - 5000;
540
548
  this.viewportLines.splice(0, removedCount);
541
549
  }
542
- if (wasLive) {
543
- this.scrollOffset = 0;
544
- }
550
+ this.scrollOffset = getNewOutputScrollOffset(addedLineCount, process.stdout.rows || 24, wasLive ? 0 : this.scrollOffset);
545
551
  this.render();
546
552
  }
547
553
 
@@ -578,10 +584,15 @@ class InteractiveTuiPrompt {
578
584
  try { if (input.isTTY) input.setRawMode(false); } catch { }
579
585
  }
580
586
 
581
- async readLine() {
582
- return new Promise((resolve, reject) => {
583
- this._cleanupInput();
584
- this.line = '';
587
+ async readLine(options = {}) {
588
+ return new Promise((resolve, reject) => {
589
+ this._cleanupInput();
590
+ const inputOptions = typeof options === 'string' ? { prompt: options } : options;
591
+ this.promptText = inputOptions.prompt || this.defaultPromptText;
592
+ this.inputPlaceholder = inputOptions.placeholder || INPUT_PLACEHOLDER;
593
+ this.recordInputHistory = inputOptions.history !== false;
594
+ this.cancelValue = inputOptions.cancelValue ?? null;
595
+ this.line = '';
585
596
  this.cursor = 0;
586
597
  this.historyIndex = -1;
587
598
  this.popoverIndex = 0;
@@ -615,7 +626,7 @@ class InteractiveTuiPrompt {
615
626
  this.cursor = 0;
616
627
  this.popoverActive = false;
617
628
  this.popoverItems = [];
618
- if (result && result.trim()) this.history.push(result);
629
+ if (this.recordInputHistory && result && result.trim()) this.history.push(result);
619
630
  const resolve = this.activeResolve;
620
631
  this.activeResolve = null;
621
632
  this.activeReject = null;
@@ -784,7 +795,7 @@ class InteractiveTuiPrompt {
784
795
  trailingStyled = `${C.dim}${C.slate}${ghostDisplay}${C.reset}`;
785
796
  }
786
797
  } else if (!this.line) {
787
- const placeholder = sliceLineForDisplay(INPUT_PLACEHOLDER, 0, maxLineWidth).displayLine;
798
+ const placeholder = sliceLineForDisplay(this.inputPlaceholder, 0, maxLineWidth).displayLine;
788
799
  trailingStyled = `${C.dim}${C.slate}${placeholder}${C.reset}`;
789
800
  }
790
801
 
@@ -801,8 +812,12 @@ class InteractiveTuiPrompt {
801
812
  handleKeypress(str, key) {
802
813
  if (!key && !str) return;
803
814
 
804
- if ((key?.ctrl && key?.name === 'c') || str === '\x03') {
805
- if (isProcessing) {
815
+ if ((key?.ctrl && key?.name === 'c') || str === '\x03') {
816
+ if (this.cancelValue !== null) {
817
+ this.finish(this.cancelValue);
818
+ return;
819
+ }
820
+ if (isProcessing) {
806
821
  if (this.onCancel) this.onCancel();
807
822
  else if (this.onSigInt) this.onSigInt();
808
823
  this.addViewportLines(` ${C.amber}[✓ Cancelled by user]${C.reset}`);
@@ -827,8 +842,12 @@ class InteractiveTuiPrompt {
827
842
  return;
828
843
  }
829
844
 
830
- if (key?.name === 'escape' || str === '\x1b') {
831
- if (this.popoverActive) {
845
+ if (key?.name === 'escape' || str === '\x1b') {
846
+ if (this.cancelValue !== null) {
847
+ this.finish(this.cancelValue);
848
+ return;
849
+ }
850
+ if (this.popoverActive) {
832
851
  this.popoverActive = false;
833
852
  this.popoverItems = [];
834
853
  this.ghostText = '';
@@ -1601,7 +1620,7 @@ async function main() {
1601
1620
  return false;
1602
1621
  }
1603
1622
 
1604
- const tuiPrompt = new InteractiveTuiPrompt({
1623
+ const tuiPrompt = new InteractiveTuiPrompt({
1605
1624
  prompt: 'Kiku ❯ ',
1606
1625
  agent,
1607
1626
  modelName: opts.model || client.model || 'gemma-4-e4b',
@@ -1621,7 +1640,8 @@ async function main() {
1621
1640
  process.exit(0);
1622
1641
  }
1623
1642
  }
1624
- });
1643
+ });
1644
+ const terminalGallery = createTerminalImageGallery();
1625
1645
 
1626
1646
  globalTuiPrompt = tuiPrompt;
1627
1647
  setupOutputInterception(tuiPrompt, () => isProcessing);
@@ -1743,9 +1763,17 @@ async function main() {
1743
1763
 
1744
1764
  const mcpStatus = agent.mcpManager ? agent.mcpManager.getStatus() : null;
1745
1765
  if (mcpStatus && mcpStatus.activeServersCount > 0 && !opts.mcp) {
1746
- const serverSummaries = mcpStatus.servers
1747
- .filter(s => s.status === 'connected')
1748
- .map(s => `• ${s.name} (${s.type}) — ${s.toolsCount} tools`);
1766
+ const serverSummaries = mcpStatus.servers
1767
+ .filter(s => s.status === 'connected')
1768
+ .map(s => {
1769
+ const sources = (s.sources || [])
1770
+ .map(source => String(source).split(/[\\/]/).pop())
1771
+ .filter(Boolean);
1772
+ const sourceLabel = sources.length > 0
1773
+ ? ` • source: ${[...new Set(sources)].join(', ')}`
1774
+ : '';
1775
+ return `• ${s.name} (${s.type}) — ${s.toolsCount} tools${sourceLabel}`;
1776
+ });
1749
1777
  printRichCard({
1750
1778
  title: 'Persisted MCP Connected',
1751
1779
  status: 'mcp',
@@ -1804,10 +1832,16 @@ async function main() {
1804
1832
  printHelp,
1805
1833
  printQueryDetails,
1806
1834
  copyQueryDetails,
1807
- printWelcome: printWelcomeHero,
1808
- printAd,
1809
- cancelActiveQuery,
1810
- enqueueQuery: async (prompt, isRetry) => {
1835
+ printWelcome: printWelcomeHero,
1836
+ printAd,
1837
+ showTerminalImage: () => {
1838
+ const imageLines = terminalGallery.maybeRender(queryCount, { force: true });
1839
+ if (imageLines.length > 0) tuiPrompt.addViewportLines(imageLines);
1840
+ return imageLines.length > 0;
1841
+ },
1842
+ cancelActiveQuery,
1843
+ readInput: options => tuiPrompt.readLine(options),
1844
+ enqueueQuery: async (prompt, isRetry) => {
1811
1845
  queryQueue.push({ id: 'q_' + Date.now(), number: nextQueryNumber++, prompt, addedAt: Date.now() });
1812
1846
  triggerQueueProcessing();
1813
1847
  },
@@ -1879,10 +1913,12 @@ async function main() {
1879
1913
  });
1880
1914
  }
1881
1915
  } finally {
1882
- activeAbortController = null;
1883
- queryCount++;
1884
- printAd();
1885
- activeQueryNumber = null;
1916
+ activeAbortController = null;
1917
+ queryCount++;
1918
+ printAd();
1919
+ const imageLines = terminalGallery.maybeRender(queryCount);
1920
+ if (imageLines.length > 0) tuiPrompt.addViewportLines(imageLines);
1921
+ activeQueryNumber = null;
1886
1922
  if (globalTuiPrompt) {
1887
1923
  globalTuiPrompt.agentState = '';
1888
1924
  globalTuiPrompt.render();