kiku-agent 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +7 -6
- package/server/agent/agentCore.js +111 -58
- package/server/agent/commandRegistry.js +6 -4
- package/server/agent/commands.js +73 -5
- package/server/agent/session.js +14 -0
- package/server/agent/ui.js +45 -41
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ Automatic installation is available for global npm installs. Source checkouts, l
|
|
|
87
87
|
|
|
88
88
|
After an update or rollback, restart `kiku-agent` and any application hosting `kiku-mcp`. Update settings and rollback history are stored in the normal Kiku user data directory as `update-state.json`; sessions, memory, `.env`, and YouTube cookies are not modified.
|
|
89
89
|
|
|
90
|
-
For a local checkout before the package is published, use `npm pack` followed by `npm install -g ./kiku-agent-1.0.
|
|
90
|
+
For a local checkout before the package is published, use `npm pack` followed by `npm install -g ./kiku-agent-1.0.10.tgz`.
|
|
91
91
|
|
|
92
92
|
### ✨ Features
|
|
93
93
|
- **LM Studio Integration**: Connects seamlessly to LM Studio's OpenAI-compatible server at `http://127.0.0.1:1234/v1` (default model `google/gemma-4-e4b`, with auto-detection of loaded models).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiku-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Kiku AI terminal agent and MCP server for Ju Jingyi knowledge, media, and career data.",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"kikuUpdate": {
|
|
30
30
|
"channel": "stable",
|
|
31
31
|
"releaseSummary": [
|
|
32
|
-
"Added
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
32
|
+
"Added session restore and resume support",
|
|
33
|
+
"Fixed CJK fullwidth table wrapping in TUI",
|
|
34
|
+
"Enhanced React 19 rendering safety",
|
|
35
|
+
"Added comprehensive version bump tool"
|
|
36
36
|
],
|
|
37
37
|
"releaseUrl": "https://www.npmjs.com/package/kiku-agent"
|
|
38
38
|
},
|
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
"kiku-agent": "node server/agent/cli.js",
|
|
64
64
|
"kiku-mcp": "node server/mcp/stdio.js",
|
|
65
65
|
"kiku-ink": "npx tsx server/agent/ink/main.jsx",
|
|
66
|
-
"test": "node --test server/agent/tests/*.test.js server/mcp/tests/*.test.js"
|
|
66
|
+
"test": "node --test server/agent/tests/*.test.js server/mcp/tests/*.test.js",
|
|
67
|
+
"bump": "node bump_version.js"
|
|
67
68
|
},
|
|
68
69
|
"dependencies": {
|
|
69
70
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -12,8 +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';
|
|
16
|
-
import { AgentJobManager } from './jobManager.js';
|
|
15
|
+
import { contextCompactor } from './contextCompactor.js';
|
|
16
|
+
import { AgentJobManager } from './jobManager.js';
|
|
17
17
|
|
|
18
18
|
export function detectDominantLanguage(text) {
|
|
19
19
|
if (!text || typeof text !== 'string') return 'th';
|
|
@@ -64,8 +64,8 @@ export class KikuAgent {
|
|
|
64
64
|
this.mcpManager = options.mcpManager || new McpClientManager();
|
|
65
65
|
this.router = new DynamicToolRouter(this.getAllToolsSchema());
|
|
66
66
|
this.research = new ResearchOrchestrator({ client: this.client });
|
|
67
|
-
this.suggestions = new SuggestionEngine({ client: this.client, memory: this.memory });
|
|
68
|
-
this.jobs = new AgentJobManager();
|
|
67
|
+
this.suggestions = new SuggestionEngine({ client: this.client, memory: this.memory });
|
|
68
|
+
this.jobs = new AgentJobManager();
|
|
69
69
|
this.systemPrompt = this.buildSystemPrompt();
|
|
70
70
|
this.initHistory();
|
|
71
71
|
this.restoredMessageCount = 0;
|
|
@@ -103,39 +103,39 @@ export class KikuAgent {
|
|
|
103
103
|
return this._ready;
|
|
104
104
|
}
|
|
105
105
|
|
|
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);
|
|
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);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
getResearchStatus() {
|
|
132
132
|
return this.research.getStatus();
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
cancelResearch() {
|
|
136
|
-
const direct = this.research.cancel();
|
|
137
|
-
const tracked = this.jobs.cancelType('research') || this.jobs.cancelType('verify');
|
|
138
|
-
return direct || tracked;
|
|
135
|
+
cancelResearch() {
|
|
136
|
+
const direct = this.research.cancel();
|
|
137
|
+
const tracked = this.jobs.cancelType('research') || this.jobs.cancelType('verify');
|
|
138
|
+
return direct || tracked;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
async generateSuggestions(options = {}) {
|
|
@@ -143,33 +143,33 @@ export class KikuAgent {
|
|
|
143
143
|
return this.suggestions.generateSuggestions(options);
|
|
144
144
|
}
|
|
145
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
|
+
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
|
+
}
|
|
173
173
|
|
|
174
174
|
initHistory() {
|
|
175
175
|
this.conversationHistory = [
|
|
@@ -223,6 +223,59 @@ export class KikuAgent {
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
async restoreSession(sessionId) {
|
|
227
|
+
await this._ready;
|
|
228
|
+
if (!sessionId) return null;
|
|
229
|
+
|
|
230
|
+
let targetSessionId = String(sessionId).trim();
|
|
231
|
+
const archives = this.memory.getArchivedSessions(50);
|
|
232
|
+
if (/^\d+$/.test(targetSessionId)) {
|
|
233
|
+
const idx = parseInt(targetSessionId, 10) - 1;
|
|
234
|
+
if (archives[idx]) {
|
|
235
|
+
targetSessionId = archives[idx].id;
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
const match = archives.find(s => s.id === targetSessionId || s.id.toLowerCase().includes(targetSessionId.toLowerCase()));
|
|
239
|
+
if (match) {
|
|
240
|
+
targetSessionId = match.id;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const archived = await this.memory.loadArchivedSession(targetSessionId);
|
|
245
|
+
if (!archived) return null;
|
|
246
|
+
|
|
247
|
+
if (this.session.messages.length > 0 && this.session.id !== targetSessionId) {
|
|
248
|
+
await this.memory.archiveSession(this.session.toSnapshot()).catch(() => {});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.session.restoreFromSnapshot(archived);
|
|
252
|
+
await this.session.saveToDisk().catch(() => {});
|
|
253
|
+
|
|
254
|
+
this.initHistory();
|
|
255
|
+
for (const msg of this.session.messages) {
|
|
256
|
+
if (msg.role === 'user') {
|
|
257
|
+
const textPart = msg.parts?.find(p => p.type === 'text');
|
|
258
|
+
if (textPart?.content) {
|
|
259
|
+
this.conversationHistory.push({
|
|
260
|
+
role: 'user',
|
|
261
|
+
content: textPart.content
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
} else if (msg.role === 'assistant') {
|
|
265
|
+
const textPart = msg.parts?.find(p => p.type === 'text');
|
|
266
|
+
if (textPart?.content) {
|
|
267
|
+
this.conversationHistory.push({
|
|
268
|
+
role: 'assistant',
|
|
269
|
+
content: textPart.content
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this.restoredMessageCount = this.session.messages.length;
|
|
275
|
+
eventBus.emit(Events.SESSION_SAVED, { sessionId: this.session.id });
|
|
276
|
+
return archived;
|
|
277
|
+
}
|
|
278
|
+
|
|
226
279
|
getEnrichedSystemPrompt() {
|
|
227
280
|
const memoryContext = this.memory.buildMemoryContext();
|
|
228
281
|
return this.systemPrompt + memoryContext;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const COMMAND_DEFINITIONS = Object.freeze([
|
|
2
2
|
{ name: '/help', usage: '/help [command]', desc: 'Show all commands or details for one command', insert: '/help ' },
|
|
3
3
|
{ name: '/history', usage: '/history [n|search|summary|clear]', desc: 'View, search, or summarize chat history', insert: '/history ' },
|
|
4
|
-
{ name: '/session', usage: '/session [info|summary|list|export|clear]', desc: 'Manage or summarize
|
|
4
|
+
{ name: '/session', usage: '/session [info|summary|list|load <id>|export|clear]', desc: 'Manage, restore, or summarize conversation sessions', insert: '/session ' },
|
|
5
5
|
{ name: '/thinking', usage: '/thinking [show|hide|toggle]', desc: 'Control reasoning visibility', insert: '/thinking ' },
|
|
6
6
|
{ name: '/details', usage: '/details [Qn] [overview|answer|thinking|tools|tool n]', desc: 'Inspect query diagnostics (F2 opens latest overview)', insert: '/details ' },
|
|
7
7
|
{ name: '/copy', usage: '/copy [Qn] [answer|thinking|tools|tool n|all|markdown]', desc: 'Copy full untruncated model answer, thinking, or diagnostics to clipboard', insert: '/copy ' },
|
|
@@ -87,7 +87,7 @@ export const SUB_COMMANDS = Object.freeze({
|
|
|
87
87
|
'/thinking': ['show', 'hide', 'toggle'],
|
|
88
88
|
'/details': ['latest', 'overview', 'answer', 'thinking', 'tools', 'tool <n>', 'all', 'markdown', 'Q1 overview', 'Q1 tool 1'],
|
|
89
89
|
'/copy': ['answer', 'thinking', 'tools', 'tool 1', 'overview', 'all', 'markdown', 'Q1 answer', 'Q1 thinking', 'Q1 all'],
|
|
90
|
-
'/session': ['info', 'summary', 'list', 'export', 'clear'],
|
|
90
|
+
'/session': ['info', 'summary', 'list', 'load <id>', 'export', 'clear'],
|
|
91
91
|
'/history': ['10', '20', '50', 'search <query>', 'summary', 'clear'],
|
|
92
92
|
'/queue': ['clear'],
|
|
93
93
|
'/update': ['check', 'install', 'install next', 'install canary', 'channel', 'channel latest', 'channel next', 'channel canary', 'rollback', 'help'],
|
|
@@ -408,12 +408,13 @@ export const COMMAND_DETAILS = Object.freeze({
|
|
|
408
408
|
],
|
|
409
409
|
},
|
|
410
410
|
'/session': {
|
|
411
|
-
summary: 'Manage
|
|
412
|
-
usage: '/session [info | list | export | clear]',
|
|
411
|
+
summary: 'Manage, restore, or summarize conversation sessions and history snapshots',
|
|
412
|
+
usage: '/session [info | summary | list | load <id> | export | clear]',
|
|
413
413
|
subcommands: [
|
|
414
414
|
{ cmd: 'info', desc: 'Display session ID, active duration, message count, and turn stats', example: '/session info' },
|
|
415
415
|
{ cmd: 'summary', desc: 'Synthesize an AI analytical summary of the active session', example: '/session summary' },
|
|
416
416
|
{ cmd: 'list', desc: 'List all saved conversation sessions on disk', example: '/session list' },
|
|
417
|
+
{ cmd: 'load <id>', desc: 'Restore and resume an archived session by ID or list number', example: '/session load 1' },
|
|
417
418
|
{ cmd: 'export', desc: 'Export full conversation session history to JSON', example: '/session export' },
|
|
418
419
|
{ cmd: 'clear', desc: 'Archive current conversation session and reset history', example: '/session clear' },
|
|
419
420
|
],
|
|
@@ -421,6 +422,7 @@ export const COMMAND_DETAILS = Object.freeze({
|
|
|
421
422
|
'/session info',
|
|
422
423
|
'/session summary',
|
|
423
424
|
'/session list',
|
|
425
|
+
'/session load 1',
|
|
424
426
|
'/session clear',
|
|
425
427
|
],
|
|
426
428
|
},
|
package/server/agent/commands.js
CHANGED
|
@@ -771,10 +771,14 @@ export const handlers = {
|
|
|
771
771
|
},
|
|
772
772
|
|
|
773
773
|
'/session': async (ctx) => {
|
|
774
|
-
const { agent, sub } = ctx;
|
|
774
|
+
const { agent, sub, extra } = ctx;
|
|
775
775
|
const sess = agent.session;
|
|
776
776
|
const action = (sub || 'info').toLowerCase();
|
|
777
777
|
|
|
778
|
+
if (/^\d+$/.test(action) || action.startsWith('sess_')) {
|
|
779
|
+
return handlers['/session']({ ...ctx, sub: 'load', extra: sub });
|
|
780
|
+
}
|
|
781
|
+
|
|
778
782
|
if (action === 'info' || !action) {
|
|
779
783
|
const msgCount = sess.messages.length;
|
|
780
784
|
const userMsgs = sess.messages.filter(m => m.role === 'user').length;
|
|
@@ -839,10 +843,10 @@ export const handlers = {
|
|
|
839
843
|
title: `Archived Sessions (Total: ${sessions.length})`,
|
|
840
844
|
lines: sessions.length === 0
|
|
841
845
|
? [`${C.dim}No archived sessions found.${C.reset}`]
|
|
842
|
-
: sessions.map(s => {
|
|
846
|
+
: sessions.map((s, i) => {
|
|
843
847
|
const date = new Date(s.createdAt).toLocaleString();
|
|
844
848
|
const dur = ((s.stats?.totalDurationMs || 0) / 1000).toFixed(1);
|
|
845
|
-
return `${C.gold}${s.id}${C.reset} ${C.dim}(${date})${C.reset} — ${s.messageCount} msgs, ${dur}s`;
|
|
849
|
+
return `${C.gold}[${i + 1}] ${s.id}${C.reset} ${C.dim}(${date})${C.reset} — ${s.messageCount} msgs, ${dur}s`;
|
|
846
850
|
}),
|
|
847
851
|
headerBg: '\x1b[48;2;48;54;70m',
|
|
848
852
|
bg: '\x1b[48;2;36;40;52m'
|
|
@@ -850,6 +854,60 @@ export const handlers = {
|
|
|
850
854
|
return 'HANDLED';
|
|
851
855
|
}
|
|
852
856
|
|
|
857
|
+
if (action === 'load' || action === 'resume' || action === 'restore') {
|
|
858
|
+
const target = (extra || '').trim();
|
|
859
|
+
if (!target) {
|
|
860
|
+
const sessions = agent.memory.getArchivedSessions(10);
|
|
861
|
+
if (sessions.length === 0) {
|
|
862
|
+
console.log(`\n ${C.amber}No archived sessions available to load.${C.reset}\n`);
|
|
863
|
+
return 'HANDLED';
|
|
864
|
+
}
|
|
865
|
+
printSolidPanel({
|
|
866
|
+
title: 'Load Session Usage',
|
|
867
|
+
lines: [
|
|
868
|
+
`${C.dim}Specify a session ID or list number to resume:${C.reset}`,
|
|
869
|
+
'',
|
|
870
|
+
...sessions.map((s, i) => {
|
|
871
|
+
const date = new Date(s.createdAt).toLocaleString();
|
|
872
|
+
return ` ${C.gold}[${i + 1}]${C.reset} ${C.bold}${C.white}${s.id}${C.reset} ${C.dim}(${date} — ${s.messageCount} msgs)${C.reset}`;
|
|
873
|
+
}),
|
|
874
|
+
'',
|
|
875
|
+
`${C.dim}Example: /session load 1 OR /session load ${sessions[0].id}${C.reset}`,
|
|
876
|
+
],
|
|
877
|
+
headerBg: '\x1b[48;2;48;54;70m',
|
|
878
|
+
bg: '\x1b[48;2;36;40;52m',
|
|
879
|
+
});
|
|
880
|
+
return 'HANDLED';
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const restored = await agent.restoreSession(target);
|
|
884
|
+
if (!restored) {
|
|
885
|
+
console.log(`\n ${C.amber}Session "${target}" not found. Use /session list to view available archives.${C.reset}\n`);
|
|
886
|
+
return 'HANDLED';
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const date = restored.createdAt ? new Date(restored.createdAt).toLocaleString() : 'unknown';
|
|
890
|
+
const msgCount = restored.messages?.length || 0;
|
|
891
|
+
const totalDur = (((restored.stats?.totalDurationMs) || 0) / 1000).toFixed(1);
|
|
892
|
+
|
|
893
|
+
printSolidPanel({
|
|
894
|
+
title: 'Session Restored Successfully',
|
|
895
|
+
lines: [
|
|
896
|
+
`${C.gold}Session ID:${C.reset} ${C.bold}${C.white}${restored.id}${C.reset}`,
|
|
897
|
+
`${C.gold}Created At:${C.reset} ${C.white}${date}${C.reset}`,
|
|
898
|
+
`${C.gold}Restored Messages:${C.reset} ${C.cyberCyan}${msgCount}${C.reset} exchanges loaded into memory`,
|
|
899
|
+
`${C.gold}Total Queries:${C.reset} ${C.cyberCyan}${restored.stats?.totalQueries || 0}${C.reset}`,
|
|
900
|
+
`${C.gold}Tool Invocations:${C.reset} ${C.cyberCyan}${restored.stats?.totalToolCalls || 0}${C.reset}`,
|
|
901
|
+
`${C.gold}Total Duration:${C.reset} ${C.cyberCyan}${totalDur}s${C.reset}`,
|
|
902
|
+
'',
|
|
903
|
+
`${C.emerald}✓ Session conversation context reloaded. You can now continue chatting!${C.reset}`,
|
|
904
|
+
],
|
|
905
|
+
headerBg: '\x1b[48;2;48;54;70m',
|
|
906
|
+
bg: '\x1b[48;2;36;40;52m',
|
|
907
|
+
});
|
|
908
|
+
return 'HANDLED';
|
|
909
|
+
}
|
|
910
|
+
|
|
853
911
|
if (action === 'export') {
|
|
854
912
|
const exported = await agent.session.exportToMarkdown();
|
|
855
913
|
console.log(`\n ${C.emerald}[OK] Session transcript exported.${C.reset}`);
|
|
@@ -863,7 +921,7 @@ export const handlers = {
|
|
|
863
921
|
return 'HANDLED';
|
|
864
922
|
}
|
|
865
923
|
|
|
866
|
-
console.log(`\n ${C.amber}Unknown session action: "${sub}". Use /session [info|list|export|clear]${C.reset}\n`);
|
|
924
|
+
console.log(`\n ${C.amber}Unknown session action: "${sub}". Use /session [info|summary|list|load|export|clear]${C.reset}\n`);
|
|
867
925
|
return 'HANDLED';
|
|
868
926
|
},
|
|
869
927
|
|
|
@@ -1168,7 +1226,16 @@ export const handlers = {
|
|
|
1168
1226
|
|
|
1169
1227
|
'/stop': async () => { await audioPlayer.stop(); console.log(`\n ${C.emerald}[OK] Stopped${C.reset}\n`); return 'HANDLED'; },
|
|
1170
1228
|
'/pause': async () => { const r = await audioPlayer.pause(); console.log(`\n ${C.emerald}${r}${C.reset}\n`); return 'HANDLED'; },
|
|
1171
|
-
'/resume': async () => {
|
|
1229
|
+
'/resume': async (ctx) => {
|
|
1230
|
+
const { sub, extra } = ctx || {};
|
|
1231
|
+
if (sub) {
|
|
1232
|
+
const target = extra ? `${sub} ${extra}`.trim() : sub;
|
|
1233
|
+
return handlers['/session']({ ...ctx, sub: 'load', extra: target });
|
|
1234
|
+
}
|
|
1235
|
+
const r = await audioPlayer.resume();
|
|
1236
|
+
console.log(`\n ${C.emerald}${r}${C.reset}\n`);
|
|
1237
|
+
return 'HANDLED';
|
|
1238
|
+
},
|
|
1172
1239
|
'/next': async () => { const r = await audioPlayer.next(); console.log(`\n ${C.emerald}${r}${C.reset}\n`); return 'HANDLED'; },
|
|
1173
1240
|
'/prev': async () => { const r = await audioPlayer.prev(); console.log(`\n ${C.emerald}${r}${C.reset}\n`); return 'HANDLED'; },
|
|
1174
1241
|
'/mute': async () => { const r = await audioPlayer.mute(); console.log(`\n ${C.emerald}${r}${C.reset}\n`); return 'HANDLED'; },
|
|
@@ -2580,6 +2647,7 @@ const ALIASES = {
|
|
|
2580
2647
|
'/?': '/help', '/commands': '/help', '/menu': '/help',
|
|
2581
2648
|
'/hist': '/history', '/h': '/history',
|
|
2582
2649
|
'/sess': '/session', '/sessions': '/session',
|
|
2650
|
+
'/load-session': '/session', '/load': '/session', '/restore': '/session',
|
|
2583
2651
|
'/think': '/thinking', '/th': '/thinking',
|
|
2584
2652
|
'/detail': '/details', '/d': '/details',
|
|
2585
2653
|
'/language': '/lang',
|
package/server/agent/session.js
CHANGED
|
@@ -160,6 +160,20 @@ export class AgentSession {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
restoreFromSnapshot(sessionData) {
|
|
164
|
+
if (!sessionData) return false;
|
|
165
|
+
this.id = sessionData.id || `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
166
|
+
this.createdAt = sessionData.createdAt ? new Date(sessionData.createdAt) : new Date();
|
|
167
|
+
this.thinkingMode = sessionData.thinkingMode || 'hide';
|
|
168
|
+
this.messages = Array.isArray(sessionData.messages) ? sessionData.messages : [];
|
|
169
|
+
this.stats = sessionData.stats || {
|
|
170
|
+
totalQueries: 0,
|
|
171
|
+
totalToolCalls: 0,
|
|
172
|
+
totalDurationMs: 0
|
|
173
|
+
};
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
163
177
|
reset() {
|
|
164
178
|
this.id = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
165
179
|
this.createdAt = new Date();
|
package/server/agent/ui.js
CHANGED
|
@@ -130,9 +130,13 @@ function getGraphemeDisplayWidth(segment) {
|
|
|
130
130
|
if (
|
|
131
131
|
(code >= 0x1100 && code <= 0x11FF) ||
|
|
132
132
|
(code >= 0x2E80 && code <= 0x9FFF) ||
|
|
133
|
+
(code >= 0x3000 && code <= 0x303F) ||
|
|
133
134
|
(code >= 0xAC00 && code <= 0xD7A3) ||
|
|
134
135
|
(code >= 0xF900 && code <= 0xFAFF) ||
|
|
135
|
-
(code >= 0xFE30 && code <= 0xFE4F)
|
|
136
|
+
(code >= 0xFE30 && code <= 0xFE4F) ||
|
|
137
|
+
(code >= 0xFF01 && code <= 0xFF60) ||
|
|
138
|
+
(code >= 0xFFE0 && code <= 0xFFE6) ||
|
|
139
|
+
(code >= 0x20000 && code <= 0x3FFFF)
|
|
136
140
|
) {
|
|
137
141
|
width += 2;
|
|
138
142
|
} else {
|
|
@@ -297,7 +301,7 @@ export function wrapText(text, maxWidth = 72) {
|
|
|
297
301
|
|
|
298
302
|
|
|
299
303
|
export function stripAnsi(str) {
|
|
300
|
-
return str.replace(/\x1b
|
|
304
|
+
return String(str ?? '').replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/g, '');
|
|
301
305
|
}
|
|
302
306
|
|
|
303
307
|
export function sanitizeTerminalText(value, maxLength = 2000) {
|
|
@@ -751,8 +755,8 @@ export function formatTerminalMarkdown(text) {
|
|
|
751
755
|
if (!text) return '';
|
|
752
756
|
return text
|
|
753
757
|
.replace(/^###\s*(.+)$/gm, `${C.bold}${C.gold}✦ $1${C.reset}`)
|
|
754
|
-
.replace(/^##\s*(.+)$/gm,
|
|
755
|
-
.replace(/^#\s*(.+)$/gm,
|
|
758
|
+
.replace(/^##\s*(.+)$/gm, `${C.bold}${C.roseGlow}■ $1${C.reset}`)
|
|
759
|
+
.replace(/^#\s*(.+)$/gm, `${C.bold}${C.sakura}━━━ $1 ━━━${C.reset}`)
|
|
756
760
|
.replace(/^>\s*(.+)$/gm, ` ${C.slate}│${C.reset} ${C.italic}${C.white}$1${C.reset}`)
|
|
757
761
|
.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, `${C.bold}${C.cyberCyan}$1${C.reset} ➔ ${C.underline}${C.roseGlow}$2${C.reset}`)
|
|
758
762
|
.replace(/\*{3}([^*]+)\*{3}/g, `${C.bold}${C.italic}${C.white}$1${C.reset}`)
|
|
@@ -854,7 +858,7 @@ export function formatMarkdownLines(text, maxWidth = getFullWidth() - 8) {
|
|
|
854
858
|
return result;
|
|
855
859
|
}
|
|
856
860
|
|
|
857
|
-
export function getWelcomeHeroLines() {
|
|
861
|
+
export function getWelcomeHeroLines() {
|
|
858
862
|
const p = C.sakura;
|
|
859
863
|
const r = C.roseGlow;
|
|
860
864
|
const g = C.gold;
|
|
@@ -866,8 +870,8 @@ export function getWelcomeHeroLines() {
|
|
|
866
870
|
const b = C.bold;
|
|
867
871
|
const d = C.dim;
|
|
868
872
|
const rst = C.reset;
|
|
869
|
-
const boxWidth = Math.min(84, Math.max(50, getFullWidth() - 4));
|
|
870
|
-
const compact = getFullWidth() < 120;
|
|
873
|
+
const boxWidth = Math.min(84, Math.max(50, getFullWidth() - 4));
|
|
874
|
+
const compact = getFullWidth() < 120;
|
|
871
875
|
const headerTitle = 'Suggested Topics & Queries';
|
|
872
876
|
const topDashCount = Math.max(2, boxWidth - getStringDisplayWidth(headerTitle) - 6);
|
|
873
877
|
const innerWidth = boxWidth - 2;
|
|
@@ -880,28 +884,28 @@ export function getWelcomeHeroLines() {
|
|
|
880
884
|
return ` ${s}│${rst} ${icon} ${tag}${rst}${' '.repeat(Math.max(0, tagWidth - getStringDisplayWidth(tag)))} ${w}${text}${rst}${' '.repeat(pad)}${s}│${rst}`;
|
|
881
885
|
};
|
|
882
886
|
|
|
883
|
-
const heroLines = compact ? [
|
|
884
|
-
` ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
|
|
885
|
-
` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst}`,
|
|
886
|
-
` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst}`,
|
|
887
|
-
] : [
|
|
888
|
-
` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst} ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
|
|
889
|
-
` ${p}██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║${rst} ${d}${s}Interactive Fan Knowledge Base & Autonomous Agent${rst}`,
|
|
890
|
-
` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst} ${d}${s}Filmography • Discography • Fashion & Awards${rst}`,
|
|
891
|
-
` ${r}██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██╗██║${rst} ${c}● Live Ready${rst} ${s}• Multilingual (CN / EN / TH)${rst}`,
|
|
892
|
-
` ${p}██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║${rst}`,
|
|
893
|
-
` ${p}╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝${rst}`,
|
|
894
|
-
];
|
|
895
|
-
const shortcutLines = compact ? [
|
|
896
|
-
` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Commands • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[F2]${rst} ${s}Details${rst}`,
|
|
897
|
-
` ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
|
|
898
|
-
] : [
|
|
899
|
-
` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Autocomplete • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[F2]${rst} ${s}Details • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
|
|
900
|
-
];
|
|
901
|
-
|
|
902
|
-
return [
|
|
903
|
-
...(compact ? [] : ['']),
|
|
904
|
-
...heroLines,
|
|
887
|
+
const heroLines = compact ? [
|
|
888
|
+
` ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
|
|
889
|
+
` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst}`,
|
|
890
|
+
` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst}`,
|
|
891
|
+
] : [
|
|
892
|
+
` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst} ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
|
|
893
|
+
` ${p}██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║${rst} ${d}${s}Interactive Fan Knowledge Base & Autonomous Agent${rst}`,
|
|
894
|
+
` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst} ${d}${s}Filmography • Discography • Fashion & Awards${rst}`,
|
|
895
|
+
` ${r}██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██╗██║${rst} ${c}● Live Ready${rst} ${s}• Multilingual (CN / EN / TH)${rst}`,
|
|
896
|
+
` ${p}██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║${rst}`,
|
|
897
|
+
` ${p}╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝${rst}`,
|
|
898
|
+
];
|
|
899
|
+
const shortcutLines = compact ? [
|
|
900
|
+
` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Commands • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[F2]${rst} ${s}Details${rst}`,
|
|
901
|
+
` ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
|
|
902
|
+
] : [
|
|
903
|
+
` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Autocomplete • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[F2]${rst} ${s}Details • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
|
|
904
|
+
];
|
|
905
|
+
|
|
906
|
+
return [
|
|
907
|
+
...(compact ? [] : ['']),
|
|
908
|
+
...heroLines,
|
|
905
909
|
'',
|
|
906
910
|
` ${s}╭──${rst} ${b}${g}${headerTitle}${rst} ${s}${'─'.repeat(topDashCount)}╮${rst}`,
|
|
907
911
|
padItem(p + '◆', '2026 Works', '"What are Ju Jingyi\'s 2026 upcoming dramas?"'),
|
|
@@ -910,7 +914,7 @@ export function getWelcomeHeroLines() {
|
|
|
910
914
|
padItem(l + '◆', 'Tools / MCP', '"@search_works 2026, /mcp list, /stats, /help"'),
|
|
911
915
|
` ${s}╰${'─'.repeat(boxWidth)}╯${rst}`,
|
|
912
916
|
'',
|
|
913
|
-
...shortcutLines,
|
|
917
|
+
...shortcutLines,
|
|
914
918
|
'',
|
|
915
919
|
];
|
|
916
920
|
}
|
|
@@ -1080,7 +1084,7 @@ export function exitAlternateScreen() {
|
|
|
1080
1084
|
}
|
|
1081
1085
|
}
|
|
1082
1086
|
|
|
1083
|
-
export function getTuiViewportLayout(termRows = 24) {
|
|
1087
|
+
export function getTuiViewportLayout(termRows = 24) {
|
|
1084
1088
|
const parsedRows = Number(termRows);
|
|
1085
1089
|
const rows = Math.max(10, Number.isFinite(parsedRows) ? Math.floor(parsedRows) : 24);
|
|
1086
1090
|
const viewportStartRow = 1;
|
|
@@ -1095,16 +1099,16 @@ export function getTuiViewportLayout(termRows = 24) {
|
|
|
1095
1099
|
statusBarRow,
|
|
1096
1100
|
promptRow,
|
|
1097
1101
|
separatorRow,
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
export function getNewOutputScrollOffset(addedLineCount, termRows = 24, currentOffset = 0) {
|
|
1102
|
-
const added = Math.max(0, Number.isFinite(Number(addedLineCount)) ? Math.floor(Number(addedLineCount)) : 0);
|
|
1103
|
-
if (currentOffset > 0) return currentOffset + added;
|
|
1104
|
-
return Math.max(0, added - getTuiViewportLayout(termRows).viewportHeight);
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
export class VirtualScreenBuffer {
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
export function getNewOutputScrollOffset(addedLineCount, termRows = 24, currentOffset = 0) {
|
|
1106
|
+
const added = Math.max(0, Number.isFinite(Number(addedLineCount)) ? Math.floor(Number(addedLineCount)) : 0);
|
|
1107
|
+
if (currentOffset > 0) return currentOffset + added;
|
|
1108
|
+
return Math.max(0, added - getTuiViewportLayout(termRows).viewportHeight);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export class VirtualScreenBuffer {
|
|
1108
1112
|
constructor(width = 80, height = 24, writer = null) {
|
|
1109
1113
|
this.width = Math.max(10, width);
|
|
1110
1114
|
this.height = Math.max(5, height);
|