@steerable/agent-cli 0.6.62 → 0.6.63
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 +3 -1
- package/dist/cli.js +2 -4
- package/dist/tui/commands.js +2 -0
- package/dist/tui/run.d.ts +6 -2
- package/dist/tui/run.js +18 -7
- package/dist/tui/screen.d.ts +11 -0
- package/dist/tui/screen.js +20 -0
- package/dist/tui/session.d.ts +11 -0
- package/dist/tui/session.js +198 -16
- package/dist/tui/smoke-entry.js +118 -3
- package/dist/tui/transcript.js +6 -1
- package/dist/tui/view.d.ts +0 -1
- package/dist/tui/view.js +39 -21
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# @steerable/agent-cli
|
|
2
2
|
|
|
3
|
-
Product-neutral command line for a Steerable host. Products wrap `createCli` and supply the bin name.
|
|
3
|
+
Product-neutral command line for a Steerable host. Products wrap `createCli` and supply the bin name. A bare invocation opens the TUI when stdin is a terminal; `tui` opens it explicitly. Other commands are `run`, `chat`, `skills`, `mcp`, `config`, and `doctor`.
|
|
4
|
+
|
|
5
|
+
`pnpm test:tui` runs the deterministic renderer and interaction cases on every supported operating system. `pnpm test:tui:pty` runs the built TUI in a 100×32 tmux pane on Linux. Failed state waits write the visible frame, input trace, and environment metadata under `test-results/tui/`.
|
package/dist/cli.js
CHANGED
|
@@ -64,10 +64,8 @@ async function runCli(options) {
|
|
|
64
64
|
stdout.write(`${rootHelp(packCommands)}\n`);
|
|
65
65
|
return 0;
|
|
66
66
|
}
|
|
67
|
-
if (!command)
|
|
68
|
-
stderr
|
|
69
|
-
return 2;
|
|
70
|
-
}
|
|
67
|
+
if (!command)
|
|
68
|
+
return tuiCommand(parsed, options, stdout, stderr);
|
|
71
69
|
if (command === 'run')
|
|
72
70
|
return runCommand(parsed, options, stdout, stderr);
|
|
73
71
|
if (command === 'chat')
|
package/dist/tui/commands.js
CHANGED
|
@@ -10,6 +10,8 @@ const COMMANDS = [
|
|
|
10
10
|
{ name: 'export', hint: '导出会话' },
|
|
11
11
|
{ name: 'compact', hint: '压缩上下文' },
|
|
12
12
|
{ name: 'plan', hint: '计划模式' },
|
|
13
|
+
{ name: 'goal', hint: '持续目标' },
|
|
14
|
+
{ name: 'loop', hint: '循环执行' },
|
|
13
15
|
{ name: 'tasks', hint: '后台任务' },
|
|
14
16
|
{ name: 'fork', hint: '分叉' },
|
|
15
17
|
{ name: 'rewind', hint: '回退' },
|
package/dist/tui/run.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type Terminal } from '@earendil-works/pi-tui';
|
|
2
|
+
import type { CliRenderer } from '@opentui/core';
|
|
2
3
|
import type { AgentClient } from '@steerable/agent-client';
|
|
3
|
-
export
|
|
4
|
+
export interface RunTuiOptions {
|
|
4
5
|
client: AgentClient;
|
|
5
6
|
product: string;
|
|
6
7
|
dataDir?: string;
|
|
7
8
|
terminal?: Terminal;
|
|
8
|
-
|
|
9
|
+
fallbackTerminal?: Terminal;
|
|
10
|
+
openRenderer?: CliRenderer;
|
|
11
|
+
}
|
|
12
|
+
export declare function runTui(options: RunTuiOptions): Promise<number>;
|
|
9
13
|
/** Terminal reports (capability replies, focus) must reach the renderer so Shift+Enter stays distinct from Enter. */
|
|
10
14
|
export declare function yieldsToRenderer(sequence: string): boolean;
|
package/dist/tui/run.js
CHANGED
|
@@ -12,13 +12,18 @@ export async function runTui(options) {
|
|
|
12
12
|
catch (error) {
|
|
13
13
|
if (!openTuiUnavailable(error))
|
|
14
14
|
throw error;
|
|
15
|
-
return runPlainTui(
|
|
15
|
+
return runPlainTui({
|
|
16
|
+
...options,
|
|
17
|
+
...(options.fallbackTerminal ? { terminal: options.fallbackTerminal } : {}),
|
|
18
|
+
});
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
async function runOpenTui(options) {
|
|
19
|
-
const
|
|
22
|
+
const createCliRenderer = options.openRenderer
|
|
23
|
+
? null
|
|
24
|
+
: (await import('@opentui/core')).createCliRenderer;
|
|
20
25
|
const { OpenTuiView } = await import('./view.js');
|
|
21
|
-
const renderer = await createCliRenderer({
|
|
26
|
+
const renderer = options.openRenderer ?? await createCliRenderer({
|
|
22
27
|
exitOnCtrlC: false,
|
|
23
28
|
useMouse: false,
|
|
24
29
|
consoleMode: 'disabled',
|
|
@@ -27,7 +32,7 @@ async function runOpenTui(options) {
|
|
|
27
32
|
backgroundColor: '#16161e',
|
|
28
33
|
targetFps: 30,
|
|
29
34
|
});
|
|
30
|
-
return new Promise((resolve) => {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
31
36
|
const session = new AgentTui(options.client, {
|
|
32
37
|
product: options.product,
|
|
33
38
|
...(options.dataDir ? { dataDir: options.dataDir } : {}),
|
|
@@ -51,13 +56,16 @@ async function runOpenTui(options) {
|
|
|
51
56
|
session.handleInput(sequence);
|
|
52
57
|
return true;
|
|
53
58
|
});
|
|
54
|
-
void session.open().then(() => view.apply(session.snapshot()))
|
|
59
|
+
void session.open().then(() => view.apply(session.snapshot()), (error) => {
|
|
60
|
+
renderer.destroy();
|
|
61
|
+
reject(error);
|
|
62
|
+
});
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
65
|
function runPlainTui(options) {
|
|
58
66
|
const terminal = options.terminal ?? new ProcessTerminal();
|
|
59
67
|
const tui = new TuiAltScreen(terminal);
|
|
60
|
-
return new Promise((resolve) => {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
61
69
|
const session = new AgentTui(options.client, {
|
|
62
70
|
product: options.product,
|
|
63
71
|
...(options.dataDir ? { dataDir: options.dataDir } : {}),
|
|
@@ -71,7 +79,10 @@ function runPlainTui(options) {
|
|
|
71
79
|
});
|
|
72
80
|
tui.addChild(session);
|
|
73
81
|
tui.setFocus(session);
|
|
74
|
-
void session.open().then(() => tui.requestRender())
|
|
82
|
+
void session.open().then(() => tui.requestRender(), (error) => {
|
|
83
|
+
tui.stop();
|
|
84
|
+
reject(error);
|
|
85
|
+
});
|
|
75
86
|
tui.start();
|
|
76
87
|
});
|
|
77
88
|
}
|
package/dist/tui/screen.d.ts
CHANGED
|
@@ -53,6 +53,17 @@ export interface TuiScreen {
|
|
|
53
53
|
content: string;
|
|
54
54
|
status: 'pending' | 'in_progress' | 'completed';
|
|
55
55
|
}>;
|
|
56
|
+
goal?: {
|
|
57
|
+
objective: string;
|
|
58
|
+
phase: 'active' | 'paused' | 'blocked' | 'complete';
|
|
59
|
+
turns: number;
|
|
60
|
+
blockedReason?: string;
|
|
61
|
+
} | null;
|
|
62
|
+
loops?: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
prompt: string;
|
|
65
|
+
intervalSeconds: number;
|
|
66
|
+
}>;
|
|
56
67
|
}
|
|
57
68
|
export declare function draftHint(): string;
|
|
58
69
|
export declare function renderScreen(screen: TuiScreen, width: number): string[];
|
package/dist/tui/screen.js
CHANGED
|
@@ -59,6 +59,8 @@ export function helpLines() {
|
|
|
59
59
|
'/export 导出会话',
|
|
60
60
|
'/compact 压缩上下文',
|
|
61
61
|
'/plan 计划模式',
|
|
62
|
+
'/goal <目标> 持续执行',
|
|
63
|
+
'/loop <间隔> <任务> 循环执行',
|
|
62
64
|
'/tasks 后台任务',
|
|
63
65
|
'/fork 分叉',
|
|
64
66
|
'/rewind 回退',
|
|
@@ -151,6 +153,11 @@ function screenLines(screen) {
|
|
|
151
153
|
export function bannerLines(screen) {
|
|
152
154
|
const lines = [];
|
|
153
155
|
if (!screen.help && !screen.chats) {
|
|
156
|
+
if (screen.goal)
|
|
157
|
+
lines.push(goalText(screen.goal));
|
|
158
|
+
if ((screen.loops?.length ?? 0) > 0) {
|
|
159
|
+
lines.push(`Loop · ${screen.loops.length} 个运行中`);
|
|
160
|
+
}
|
|
154
161
|
for (const todo of screen.todos ?? [])
|
|
155
162
|
lines.push(todoText(todo));
|
|
156
163
|
}
|
|
@@ -164,6 +171,19 @@ export function bannerLines(screen) {
|
|
|
164
171
|
lines.push(row);
|
|
165
172
|
return lines;
|
|
166
173
|
}
|
|
174
|
+
function goalText(goal) {
|
|
175
|
+
const objective = goal.objective.length > 48
|
|
176
|
+
? `${goal.objective.slice(0, 47)}…`
|
|
177
|
+
: goal.objective;
|
|
178
|
+
if (goal.phase === 'blocked') {
|
|
179
|
+
return `目标 · 阻塞 · ${objective}${goal.blockedReason ? ` · ${goal.blockedReason}` : ''}`;
|
|
180
|
+
}
|
|
181
|
+
if (goal.phase === 'paused')
|
|
182
|
+
return `目标 · 暂停 · ${objective}`;
|
|
183
|
+
if (goal.phase === 'complete')
|
|
184
|
+
return `目标 · 完成 · ${objective}`;
|
|
185
|
+
return `目标 · ${objective} · 第 ${goal.turns} 轮`;
|
|
186
|
+
}
|
|
167
187
|
function todoText(todo) {
|
|
168
188
|
if (todo.status === 'completed')
|
|
169
189
|
return `✓ ${todo.content}`;
|
package/dist/tui/session.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare class AgentTui implements Component {
|
|
|
31
31
|
private chats;
|
|
32
32
|
private selected;
|
|
33
33
|
private approval;
|
|
34
|
+
private approvalSubmitting;
|
|
34
35
|
private ask;
|
|
35
36
|
private readOnly;
|
|
36
37
|
private help;
|
|
@@ -49,6 +50,10 @@ export declare class AgentTui implements Component {
|
|
|
49
50
|
private mode;
|
|
50
51
|
private permission;
|
|
51
52
|
private todos;
|
|
53
|
+
private goal;
|
|
54
|
+
private loops;
|
|
55
|
+
private backendTurnActive;
|
|
56
|
+
private loopExitArmed;
|
|
52
57
|
private liveGeneration;
|
|
53
58
|
private children;
|
|
54
59
|
private readonly cwd;
|
|
@@ -81,9 +86,12 @@ export declare class AgentTui implements Component {
|
|
|
81
86
|
private markSelection;
|
|
82
87
|
private openChat;
|
|
83
88
|
private submit;
|
|
89
|
+
private drainQueue;
|
|
84
90
|
private sendPrepared;
|
|
85
91
|
private prepareOwned;
|
|
86
92
|
private slash;
|
|
93
|
+
private manageGoal;
|
|
94
|
+
private manageLoop;
|
|
87
95
|
private attach;
|
|
88
96
|
private prepareMessage;
|
|
89
97
|
private ensureChat;
|
|
@@ -94,6 +102,9 @@ export declare class AgentTui implements Component {
|
|
|
94
102
|
private toggleFold;
|
|
95
103
|
private armLive;
|
|
96
104
|
private pollLive;
|
|
105
|
+
private refreshGoal;
|
|
106
|
+
private refreshLoops;
|
|
107
|
+
private reloadMessages;
|
|
97
108
|
private showList;
|
|
98
109
|
private showTasks;
|
|
99
110
|
private exportChat;
|
package/dist/tui/session.js
CHANGED
|
@@ -31,6 +31,7 @@ export class AgentTui {
|
|
|
31
31
|
chats = null;
|
|
32
32
|
selected = 0;
|
|
33
33
|
approval = null;
|
|
34
|
+
approvalSubmitting = false;
|
|
34
35
|
ask = null;
|
|
35
36
|
readOnly = false;
|
|
36
37
|
help = false;
|
|
@@ -49,6 +50,10 @@ export class AgentTui {
|
|
|
49
50
|
mode = 'agent';
|
|
50
51
|
permission = 'ask';
|
|
51
52
|
todos = [];
|
|
53
|
+
goal = null;
|
|
54
|
+
loops = [];
|
|
55
|
+
backendTurnActive = false;
|
|
56
|
+
loopExitArmed = false;
|
|
52
57
|
liveGeneration = 0;
|
|
53
58
|
children = [];
|
|
54
59
|
cwd;
|
|
@@ -128,6 +133,8 @@ export class AgentTui {
|
|
|
128
133
|
return;
|
|
129
134
|
}
|
|
130
135
|
if (this.approval) {
|
|
136
|
+
if (this.approvalSubmitting)
|
|
137
|
+
return;
|
|
131
138
|
const kind = approvalKind(data);
|
|
132
139
|
if (kind)
|
|
133
140
|
void this.decide(kind);
|
|
@@ -349,6 +356,8 @@ export class AgentTui {
|
|
|
349
356
|
mode: this.mode,
|
|
350
357
|
permission: this.permission,
|
|
351
358
|
todos: this.todos,
|
|
359
|
+
goal: this.goal,
|
|
360
|
+
loops: this.loops,
|
|
352
361
|
};
|
|
353
362
|
}
|
|
354
363
|
searchBar() {
|
|
@@ -519,6 +528,8 @@ export class AgentTui {
|
|
|
519
528
|
this.children = [];
|
|
520
529
|
this.picks = null;
|
|
521
530
|
this.attachments = [];
|
|
531
|
+
await this.refreshGoal(id);
|
|
532
|
+
await this.refreshLoops(id);
|
|
522
533
|
this.touch();
|
|
523
534
|
this.armLive(id);
|
|
524
535
|
}
|
|
@@ -530,8 +541,14 @@ export class AgentTui {
|
|
|
530
541
|
rememberPrompt(this.prompts, text);
|
|
531
542
|
if (!text && this.attachments.length === 0)
|
|
532
543
|
return;
|
|
533
|
-
if (text.startsWith('/')) {
|
|
534
|
-
|
|
544
|
+
if (text.startsWith('/') && !isSkillFacadeCommand(text)) {
|
|
545
|
+
try {
|
|
546
|
+
await this.slash(text);
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
this.status = `命令失败:${error instanceof Error ? error.message : String(error)}`;
|
|
550
|
+
this.touch();
|
|
551
|
+
}
|
|
535
552
|
return;
|
|
536
553
|
}
|
|
537
554
|
if (this.readOnly) {
|
|
@@ -544,14 +561,18 @@ export class AgentTui {
|
|
|
544
561
|
return;
|
|
545
562
|
}
|
|
546
563
|
const files = this.attachments.splice(0);
|
|
547
|
-
|
|
548
|
-
|
|
564
|
+
this.queue.push({ text, files });
|
|
565
|
+
if (this.sending || this.backendTurnActive) {
|
|
549
566
|
this.touch();
|
|
550
567
|
return;
|
|
551
568
|
}
|
|
569
|
+
await this.drainQueue();
|
|
570
|
+
}
|
|
571
|
+
async drainQueue() {
|
|
572
|
+
if (this.sending || this.backendTurnActive)
|
|
573
|
+
return;
|
|
552
574
|
this.sending = true;
|
|
553
575
|
try {
|
|
554
|
-
await this.sendPrepared(text, files);
|
|
555
576
|
while (!this.stopped && this.queue.length > 0) {
|
|
556
577
|
const next = this.queue.shift();
|
|
557
578
|
if (!next)
|
|
@@ -565,6 +586,7 @@ export class AgentTui {
|
|
|
565
586
|
}
|
|
566
587
|
}
|
|
567
588
|
async sendPrepared(text, files) {
|
|
589
|
+
this.status = '';
|
|
568
590
|
const prepared = await this.prepareOwned(text, files);
|
|
569
591
|
this.lines.push({ kind: 'user', text: prepared.message });
|
|
570
592
|
this.touch();
|
|
@@ -634,11 +656,11 @@ export class AgentTui {
|
|
|
634
656
|
return;
|
|
635
657
|
}
|
|
636
658
|
if (command === 'skills') {
|
|
637
|
-
await this.showList('GET', '/api/v2/chat-agents/skills', 'skills', (row) => `技能 ${textField(row, 'name')}${textField(row, 'description') ? ` ${textField(row, 'description')}` : ''}`, '没有技能');
|
|
659
|
+
await this.showList('GET', '/api/v2/chat-agents/skills', 'skills', (row) => `技能 ${textField(row, 'name')}${textField(row, 'description') ? ` ${textField(row, 'description')}` : ''}`, '没有技能', '技能不可用');
|
|
638
660
|
return;
|
|
639
661
|
}
|
|
640
662
|
if (command === 'mcp') {
|
|
641
|
-
await this.showList('GET', '/api/v2/mcp/servers', 'servers', (row) => `MCP ${textField(row, 'name') || textField(row, 'id')} ${numberField(row, 'toolCount')} 个工具`, '没有 MCP');
|
|
663
|
+
await this.showList('GET', '/api/v2/mcp/servers', 'servers', (row) => `MCP ${textField(row, 'name') || textField(row, 'id')} ${numberField(row, 'toolCount')} 个工具`, '没有 MCP', 'MCP 不可用');
|
|
642
664
|
return;
|
|
643
665
|
}
|
|
644
666
|
if (command === 'export') {
|
|
@@ -655,6 +677,14 @@ export class AgentTui {
|
|
|
655
677
|
this.touch();
|
|
656
678
|
return;
|
|
657
679
|
}
|
|
680
|
+
if (command === 'goal') {
|
|
681
|
+
await this.manageGoal(rest);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (command === 'loop') {
|
|
685
|
+
await this.manageLoop(rest);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
658
688
|
if (command === 'tasks') {
|
|
659
689
|
await this.showTasks();
|
|
660
690
|
return;
|
|
@@ -684,6 +714,54 @@ export class AgentTui {
|
|
|
684
714
|
this.status = `unknown command /${command}`;
|
|
685
715
|
this.touch();
|
|
686
716
|
}
|
|
717
|
+
async manageGoal(parts) {
|
|
718
|
+
if (!this.chatId) {
|
|
719
|
+
this.status = '还没有会话';
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (parts.length === 0) {
|
|
723
|
+
await this.refreshGoal(this.chatId);
|
|
724
|
+
this.status = this.goal ? '' : '没有持续目标';
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const action = parts[0];
|
|
728
|
+
if (!['edit', 'pause', 'resume', 'complete', 'clear'].includes(action)) {
|
|
729
|
+
this.status = '用法 /goal [edit <目标>|pause|resume|complete|clear]';
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const objective = parts.slice(1).join(' ').trim();
|
|
733
|
+
if (action === 'edit' && !objective) {
|
|
734
|
+
this.status = '用法 /goal edit <目标>';
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const response = await this.client.request('POST', `/api/v2/chats/${encodeURIComponent(this.chatId)}/goal`, { action, ...(objective ? { objective } : {}) });
|
|
738
|
+
if (response.status !== 200) {
|
|
739
|
+
this.status = `目标操作失败 (${response.status})`;
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
this.goal = response.data.goal ?? null;
|
|
743
|
+
this.status = '';
|
|
744
|
+
}
|
|
745
|
+
async manageLoop(parts) {
|
|
746
|
+
if (!this.chatId) {
|
|
747
|
+
this.status = '还没有会话';
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (parts[0] === 'list') {
|
|
751
|
+
await this.refreshLoops(this.chatId);
|
|
752
|
+
this.panel = this.loops.length > 0
|
|
753
|
+
? this.loops.map((loop) => `Loop ${loop.id} 每 ${loop.intervalSeconds} 秒 ${loop.prompt}`)
|
|
754
|
+
: ['没有运行中的 Loop'];
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (parts[0] === 'stop' && parts[1]) {
|
|
758
|
+
const response = await this.client.request('DELETE', `/api/v2/chats/${encodeURIComponent(this.chatId)}/loops/${encodeURIComponent(parts[1])}`, undefined);
|
|
759
|
+
this.status = response.status === 200 ? 'Loop 已停止' : `停止 Loop 失败 (${response.status})`;
|
|
760
|
+
await this.refreshLoops(this.chatId);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
this.status = '用法 /loop list | /loop stop <id>';
|
|
764
|
+
}
|
|
687
765
|
async attach(target) {
|
|
688
766
|
if (this.readOnly) {
|
|
689
767
|
this.status = '只读 · 另一个进程正在运行';
|
|
@@ -758,7 +836,6 @@ export class AgentTui {
|
|
|
758
836
|
const controller = new AbortController();
|
|
759
837
|
this.turnAbort = controller;
|
|
760
838
|
this.children = [];
|
|
761
|
-
this.status = '';
|
|
762
839
|
try {
|
|
763
840
|
const payload = { message: text };
|
|
764
841
|
if (images.length > 0)
|
|
@@ -770,6 +847,12 @@ export class AgentTui {
|
|
|
770
847
|
this.touch();
|
|
771
848
|
}
|
|
772
849
|
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
if (!controller.signal.aborted) {
|
|
852
|
+
this.status = error instanceof Error ? error.message : String(error);
|
|
853
|
+
this.touch();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
773
856
|
finally {
|
|
774
857
|
for (const line of this.lines)
|
|
775
858
|
line.streaming = false;
|
|
@@ -863,6 +946,32 @@ export class AgentTui {
|
|
|
863
946
|
if (payload.requestId)
|
|
864
947
|
this.ask = { requestId: payload.requestId, prompt: payload.prompt ?? '' };
|
|
865
948
|
}
|
|
949
|
+
else if (event.channel === 'goal-changed') {
|
|
950
|
+
const payload = event.payload;
|
|
951
|
+
if (payload.chatId === this.chatId)
|
|
952
|
+
this.goal = payload.goal ?? null;
|
|
953
|
+
}
|
|
954
|
+
else if (event.channel === 'loop-changed') {
|
|
955
|
+
const payload = event.payload;
|
|
956
|
+
if (payload.chatId === this.chatId)
|
|
957
|
+
this.loops = payload.loops ?? [];
|
|
958
|
+
}
|
|
959
|
+
else if (event.channel === 'chat-turn-started') {
|
|
960
|
+
const payload = event.payload;
|
|
961
|
+
if (payload.chatId === this.chatId) {
|
|
962
|
+
this.backendTurnActive = true;
|
|
963
|
+
this.armLive(payload.chatId, true);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
else if (event.channel === 'chat-turn-finished') {
|
|
967
|
+
const payload = event.payload;
|
|
968
|
+
if (payload.chatId === this.chatId) {
|
|
969
|
+
this.backendTurnActive = false;
|
|
970
|
+
await this.reloadMessages(payload.chatId);
|
|
971
|
+
await this.refreshGoal(payload.chatId);
|
|
972
|
+
void this.drainQueue();
|
|
973
|
+
}
|
|
974
|
+
}
|
|
866
975
|
this.touch();
|
|
867
976
|
}
|
|
868
977
|
}
|
|
@@ -904,11 +1013,11 @@ export class AgentTui {
|
|
|
904
1013
|
}
|
|
905
1014
|
}
|
|
906
1015
|
}
|
|
907
|
-
armLive(chatId) {
|
|
1016
|
+
armLive(chatId, waitForStart = false) {
|
|
908
1017
|
const generation = ++this.liveGeneration;
|
|
909
|
-
void this.pollLive(chatId, generation);
|
|
1018
|
+
void this.pollLive(chatId, generation, waitForStart ? 10 : 0);
|
|
910
1019
|
}
|
|
911
|
-
async pollLive(chatId, generation) {
|
|
1020
|
+
async pollLive(chatId, generation, startRetries) {
|
|
912
1021
|
if (this.stopped || this.chatId !== chatId || generation !== this.liveGeneration)
|
|
913
1022
|
return;
|
|
914
1023
|
const live = await this.client.request('GET', `/api/v2/chats/${encodeURIComponent(chatId)}/live-stream`, undefined);
|
|
@@ -918,6 +1027,13 @@ export class AgentTui {
|
|
|
918
1027
|
return;
|
|
919
1028
|
const data = live.data;
|
|
920
1029
|
if (data.active !== true) {
|
|
1030
|
+
if (startRetries > 0) {
|
|
1031
|
+
await new Promise((resolve) => {
|
|
1032
|
+
setTimeout(resolve, this.options.liveIntervalMs ?? 1000);
|
|
1033
|
+
});
|
|
1034
|
+
await this.pollLive(chatId, generation, startRetries - 1);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
921
1037
|
if (this.status === '运行中')
|
|
922
1038
|
this.status = '';
|
|
923
1039
|
this.touch();
|
|
@@ -939,10 +1055,43 @@ export class AgentTui {
|
|
|
939
1055
|
await new Promise((resolve) => {
|
|
940
1056
|
setTimeout(resolve, wait);
|
|
941
1057
|
});
|
|
942
|
-
await this.pollLive(chatId, generation);
|
|
1058
|
+
await this.pollLive(chatId, generation, 0);
|
|
1059
|
+
}
|
|
1060
|
+
async refreshGoal(chatId) {
|
|
1061
|
+
const response = await this.client.request('GET', `/api/v2/chats/${encodeURIComponent(chatId)}/goal`, undefined);
|
|
1062
|
+
this.goal = response.status === 200 && response.data && typeof response.data === 'object'
|
|
1063
|
+
? (response.data.goal ?? null)
|
|
1064
|
+
: null;
|
|
1065
|
+
}
|
|
1066
|
+
async refreshLoops(chatId) {
|
|
1067
|
+
const response = await this.client.request('GET', `/api/v2/chats/${encodeURIComponent(chatId)}/loops`, undefined);
|
|
1068
|
+
this.loops = response.status === 200 && response.data && typeof response.data === 'object'
|
|
1069
|
+
? (response.data.loops ?? [])
|
|
1070
|
+
: [];
|
|
1071
|
+
}
|
|
1072
|
+
async reloadMessages(chatId) {
|
|
1073
|
+
const response = await this.client.request('GET', `/api/v2/chats/${encodeURIComponent(chatId)}/messages`, undefined);
|
|
1074
|
+
if (response.status !== 200)
|
|
1075
|
+
return;
|
|
1076
|
+
const records = response.data.messages ?? [];
|
|
1077
|
+
this.todos = [];
|
|
1078
|
+
this.lines = historyRows(records).map((row) => {
|
|
1079
|
+
if (row.kind !== 'tool')
|
|
1080
|
+
return { kind: row.kind, text: row.text };
|
|
1081
|
+
const next = todosFromAction(row.action.tool, row.action.arguments, row.action.result);
|
|
1082
|
+
if (next)
|
|
1083
|
+
this.todos = next;
|
|
1084
|
+
return toolLine(row.action);
|
|
1085
|
+
});
|
|
1086
|
+
this.touch();
|
|
943
1087
|
}
|
|
944
|
-
async showList(method, requestPath, key, format, empty) {
|
|
1088
|
+
async showList(method, requestPath, key, format, empty, unavailable) {
|
|
945
1089
|
const listed = await this.client.request(method, requestPath, undefined);
|
|
1090
|
+
if (listed.status !== 200) {
|
|
1091
|
+
this.panel = [`${unavailable} (${listed.status})`];
|
|
1092
|
+
this.touch();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
946
1095
|
const rows = listed.status === 200 && listed.data && typeof listed.data === 'object'
|
|
947
1096
|
? listed.data[key]
|
|
948
1097
|
: [];
|
|
@@ -959,6 +1108,11 @@ export class AgentTui {
|
|
|
959
1108
|
return;
|
|
960
1109
|
}
|
|
961
1110
|
const listed = await this.client.request('GET', `/api/v2/chats/${encodeURIComponent(this.chatId)}/tasks`, undefined);
|
|
1111
|
+
if (listed.status !== 200) {
|
|
1112
|
+
this.panel = [`后台任务不可用 (${listed.status})`];
|
|
1113
|
+
this.touch();
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
962
1116
|
const rows = listed.status === 200 && listed.data && typeof listed.data === 'object'
|
|
963
1117
|
? listed.data.tasks ?? []
|
|
964
1118
|
: [];
|
|
@@ -1058,11 +1212,24 @@ export class AgentTui {
|
|
|
1058
1212
|
}
|
|
1059
1213
|
async decide(kind) {
|
|
1060
1214
|
const request = this.approval;
|
|
1061
|
-
if (!request)
|
|
1215
|
+
if (!request || this.approvalSubmitting)
|
|
1062
1216
|
return;
|
|
1063
|
-
this.
|
|
1217
|
+
this.approvalSubmitting = true;
|
|
1064
1218
|
this.touch();
|
|
1065
|
-
|
|
1219
|
+
try {
|
|
1220
|
+
const accepted = await this.client.decideApproval(request.requestId, kind);
|
|
1221
|
+
if (!accepted)
|
|
1222
|
+
throw new Error('approval unavailable');
|
|
1223
|
+
if (this.approval?.requestId === request.requestId)
|
|
1224
|
+
this.approval = null;
|
|
1225
|
+
}
|
|
1226
|
+
catch (error) {
|
|
1227
|
+
this.status = `审批失败:${error instanceof Error ? error.message : String(error)}`;
|
|
1228
|
+
}
|
|
1229
|
+
finally {
|
|
1230
|
+
this.approvalSubmitting = false;
|
|
1231
|
+
this.touch();
|
|
1232
|
+
}
|
|
1066
1233
|
}
|
|
1067
1234
|
touch() {
|
|
1068
1235
|
if (this.stopped)
|
|
@@ -1070,6 +1237,12 @@ export class AgentTui {
|
|
|
1070
1237
|
this.options.onChange?.();
|
|
1071
1238
|
}
|
|
1072
1239
|
exit() {
|
|
1240
|
+
if (this.loops.length > 0 && !this.loopExitArmed) {
|
|
1241
|
+
this.loopExitArmed = true;
|
|
1242
|
+
this.status = '活动 Loop 会随本进程停止;再按一次退出';
|
|
1243
|
+
this.touch();
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1073
1246
|
this.stopped = true;
|
|
1074
1247
|
this.turnAbort?.abort();
|
|
1075
1248
|
this.options.onExit();
|
|
@@ -1144,6 +1317,15 @@ function taskStatus(status) {
|
|
|
1144
1317
|
return '等待';
|
|
1145
1318
|
return status ?? '';
|
|
1146
1319
|
}
|
|
1320
|
+
function isSkillFacadeCommand(text) {
|
|
1321
|
+
if (/^\/goal(?:\s|$)/.test(text)) {
|
|
1322
|
+
return !/^\/goal(?:\s+(?:edit|pause|resume|complete|clear)(?:\s|$)|\s*$)/.test(text);
|
|
1323
|
+
}
|
|
1324
|
+
if (/^\/loop(?:\s|$)/.test(text)) {
|
|
1325
|
+
return !/^\/loop\s+(?:list|stop)(?:\s|$)/.test(text);
|
|
1326
|
+
}
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1147
1329
|
async function editWithEditor(text) {
|
|
1148
1330
|
const editor = process.env.VISUAL || process.env.EDITOR;
|
|
1149
1331
|
if (!editor)
|
package/dist/tui/smoke-entry.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { createCli } from '../cli.js';
|
|
1
2
|
import { runTui } from './run.js';
|
|
2
3
|
const queue = [];
|
|
3
4
|
const waiters = [];
|
|
4
5
|
let closed = false;
|
|
5
6
|
let decided = () => { };
|
|
7
|
+
let goal = null;
|
|
8
|
+
let loops = [];
|
|
9
|
+
const proactiveMessages = [];
|
|
10
|
+
let liveActive = false;
|
|
6
11
|
function push(item) {
|
|
7
12
|
const waiter = waiters.shift();
|
|
8
13
|
if (waiter)
|
|
@@ -19,10 +24,78 @@ const client = {
|
|
|
19
24
|
return { status: 200, data: { model: 'demo-model' } };
|
|
20
25
|
if (method === 'POST' && requestPath === '/api/v2/chats/new')
|
|
21
26
|
return { status: 200, data: { chatId: 'chat-1' } };
|
|
27
|
+
if (method === 'GET' && requestPath.endsWith('/goal'))
|
|
28
|
+
return { status: 200, data: { goal } };
|
|
29
|
+
if (method === 'GET' && requestPath.endsWith('/loops'))
|
|
30
|
+
return { status: 200, data: { loops } };
|
|
31
|
+
if (method === 'DELETE' && requestPath.includes('/loops/')) {
|
|
32
|
+
const id = requestPath.split('/').pop();
|
|
33
|
+
loops = loops.filter((loop) => loop.id !== id);
|
|
34
|
+
push({ channel: 'loop-changed', payload: { chatId: 'chat-1', loops } });
|
|
35
|
+
return { status: 200, data: { success: true } };
|
|
36
|
+
}
|
|
37
|
+
if (method === 'GET' && requestPath.endsWith('/live-stream')) {
|
|
38
|
+
return {
|
|
39
|
+
status: 200,
|
|
40
|
+
data: liveActive ? { active: true, content: 'goal wake running' } : { active: false },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (method === 'GET' && requestPath.endsWith('/messages')) {
|
|
44
|
+
return {
|
|
45
|
+
status: 200,
|
|
46
|
+
data: {
|
|
47
|
+
messages: proactiveMessages,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
22
51
|
return { status: 200, data: {} };
|
|
23
52
|
},
|
|
24
53
|
async *stream(_requestPath, body, signal) {
|
|
25
54
|
const message = String(body.message ?? '');
|
|
55
|
+
if (message.startsWith('/goal ')) {
|
|
56
|
+
goal = { objective: message.slice('/goal '.length), phase: 'active', turns: 1 };
|
|
57
|
+
push({ channel: 'goal-changed', payload: { chatId: 'chat-1', goal } });
|
|
58
|
+
yield { type: 'content', content: 'goal accepted' };
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
liveActive = true;
|
|
61
|
+
push({
|
|
62
|
+
channel: 'chat-turn-started',
|
|
63
|
+
payload: { chatId: 'chat-1', trigger: 'goal', sourceId: 'goal-1' },
|
|
64
|
+
});
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
liveActive = false;
|
|
67
|
+
proactiveMessages.push({
|
|
68
|
+
role: 'user',
|
|
69
|
+
content: '<objective>hidden</objective>',
|
|
70
|
+
messageMetadata: JSON.stringify({
|
|
71
|
+
internal: true,
|
|
72
|
+
trigger: 'goal',
|
|
73
|
+
sourceId: 'goal-1',
|
|
74
|
+
}),
|
|
75
|
+
}, { role: 'assistant', content: 'goal wake completed' });
|
|
76
|
+
goal = { ...goal, turns: 2 };
|
|
77
|
+
push({
|
|
78
|
+
channel: 'chat-turn-finished',
|
|
79
|
+
payload: { chatId: 'chat-1', trigger: 'goal', status: 'completed' },
|
|
80
|
+
});
|
|
81
|
+
}, 250);
|
|
82
|
+
}, 100);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (message.startsWith('/loop ')) {
|
|
86
|
+
const finite = message.includes('until complete');
|
|
87
|
+
const id = finite ? 'loop-finite' : 'loop-recurring';
|
|
88
|
+
loops = [{
|
|
89
|
+
id,
|
|
90
|
+
prompt: finite ? 'monitor demo until complete' : 'recurring demo',
|
|
91
|
+
intervalSeconds: 1,
|
|
92
|
+
}];
|
|
93
|
+
push({ channel: 'loop-changed', payload: { chatId: 'chat-1', loops } });
|
|
94
|
+
yield { type: 'content', content: `loop armed ${id}` };
|
|
95
|
+
if (finite)
|
|
96
|
+
scheduleFiniteLoopWake(id, 1);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
26
99
|
if (message.includes('hang')) {
|
|
27
100
|
yield { type: 'content', content: 'running' };
|
|
28
101
|
await new Promise((resolve) => signal.addEventListener('abort', () => resolve()));
|
|
@@ -65,6 +138,48 @@ const client = {
|
|
|
65
138
|
waiter(null);
|
|
66
139
|
},
|
|
67
140
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
141
|
+
function scheduleFiniteLoopWake(id, attempt) {
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
liveActive = true;
|
|
144
|
+
push({
|
|
145
|
+
channel: 'chat-turn-started',
|
|
146
|
+
payload: { chatId: 'chat-1', trigger: 'loop', sourceId: id },
|
|
147
|
+
});
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
liveActive = false;
|
|
150
|
+
proactiveMessages.push({
|
|
151
|
+
role: 'user',
|
|
152
|
+
content: 'hidden loop wake',
|
|
153
|
+
messageMetadata: JSON.stringify({
|
|
154
|
+
internal: true,
|
|
155
|
+
trigger: 'loop',
|
|
156
|
+
sourceId: id,
|
|
157
|
+
}),
|
|
158
|
+
}, {
|
|
159
|
+
role: 'assistant',
|
|
160
|
+
content: attempt === 1 ? 'loop monitoring pending' : 'loop reached terminal state',
|
|
161
|
+
});
|
|
162
|
+
if (attempt === 2) {
|
|
163
|
+
loops = [];
|
|
164
|
+
push({ channel: 'loop-changed', payload: { chatId: 'chat-1', loops } });
|
|
165
|
+
}
|
|
166
|
+
push({
|
|
167
|
+
channel: 'chat-turn-finished',
|
|
168
|
+
payload: { chatId: 'chat-1', trigger: 'loop', sourceId: id, status: 'completed' },
|
|
169
|
+
});
|
|
170
|
+
if (attempt === 1)
|
|
171
|
+
scheduleFiniteLoopWake(id, 2);
|
|
172
|
+
}, 200);
|
|
173
|
+
}, 150);
|
|
174
|
+
}
|
|
175
|
+
const defaultCli = process.argv.includes('--default-cli');
|
|
176
|
+
const code = defaultCli
|
|
177
|
+
? await createCli({
|
|
178
|
+
argv: [],
|
|
179
|
+
createClient: async () => client,
|
|
180
|
+
installSignals: false,
|
|
181
|
+
})
|
|
182
|
+
: await runTui({ client, product: 'Demo' });
|
|
183
|
+
if (!defaultCli)
|
|
184
|
+
await client.close();
|
|
185
|
+
process.exit(code);
|
package/dist/tui/transcript.js
CHANGED
|
@@ -66,11 +66,16 @@ function clip(text) {
|
|
|
66
66
|
export function historyRows(messages) {
|
|
67
67
|
const rows = [];
|
|
68
68
|
for (const message of inTimeOrder(messages)) {
|
|
69
|
+
const meta = parseMeta(message.messageMetadata);
|
|
69
70
|
if (message.role === 'user') {
|
|
71
|
+
if (meta.internal === true) {
|
|
72
|
+
const trigger = meta.trigger === 'loop' ? 'Loop 触发' : '目标续跑';
|
|
73
|
+
rows.push({ kind: 'tree', text: trigger });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
70
76
|
rows.push({ kind: 'user', text: message.content ?? '' });
|
|
71
77
|
continue;
|
|
72
78
|
}
|
|
73
|
-
const meta = parseMeta(message.messageMetadata);
|
|
74
79
|
const timeline = Array.isArray(meta.timeline) ? meta.timeline : null;
|
|
75
80
|
if (timeline) {
|
|
76
81
|
let sawText = false;
|
package/dist/tui/view.d.ts
CHANGED
package/dist/tui/view.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxRenderable,
|
|
1
|
+
import { BoxRenderable, ScrollBoxRenderable, TextRenderable, bold, dim as dimText, italic, reverse, strikethrough, t, underline, } from '@opentui/core';
|
|
2
2
|
import { splitAtCursor } from './editor.js';
|
|
3
3
|
import { childLines } from './children.js';
|
|
4
4
|
import { approvalLines, attachmentText, bannerLines, chatText, composerRows, draftHint, footerText, helpLines, pickText, readOnlyText, reasoningBlock, toolBlock, } from './screen.js';
|
|
@@ -34,16 +34,8 @@ export class OpenTuiView {
|
|
|
34
34
|
bodyKey = '';
|
|
35
35
|
overlayKey = '';
|
|
36
36
|
picksKey = '';
|
|
37
|
-
syntax;
|
|
38
37
|
constructor(renderer) {
|
|
39
38
|
this.renderer = renderer;
|
|
40
|
-
try {
|
|
41
|
-
this.syntax = SyntaxStyle.create();
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
void error;
|
|
45
|
-
this.syntax = null;
|
|
46
|
-
}
|
|
47
39
|
const shell = new BoxRenderable(renderer, {
|
|
48
40
|
width: '100%',
|
|
49
41
|
height: '100%',
|
|
@@ -261,7 +253,7 @@ export class OpenTuiView {
|
|
|
261
253
|
return;
|
|
262
254
|
}
|
|
263
255
|
screen.lines.forEach((line, index) => {
|
|
264
|
-
this.body.add(transcriptRow(renderer, line,
|
|
256
|
+
this.body.add(transcriptRow(renderer, line, `line-${index}`));
|
|
265
257
|
});
|
|
266
258
|
for (const line of childLines(screen.children ?? []))
|
|
267
259
|
this.body.add(text(renderer, tool, line));
|
|
@@ -284,7 +276,7 @@ function bodyKey(screen) {
|
|
|
284
276
|
children: screen.children ?? [],
|
|
285
277
|
});
|
|
286
278
|
}
|
|
287
|
-
function transcriptRow(renderer, line,
|
|
279
|
+
function transcriptRow(renderer, line, id) {
|
|
288
280
|
if (line.kind === 'reasoning') {
|
|
289
281
|
const card = new BoxRenderable(renderer, {
|
|
290
282
|
id,
|
|
@@ -316,20 +308,46 @@ function transcriptRow(renderer, line, syntax, id) {
|
|
|
316
308
|
if (line.kind === 'user') {
|
|
317
309
|
return text(renderer, user, `user ${line.text ?? ''}`, id);
|
|
318
310
|
}
|
|
319
|
-
if (line.kind === 'assistant'
|
|
320
|
-
return
|
|
321
|
-
id,
|
|
322
|
-
content: line.text && line.text.length > 0 ? line.text : ' ',
|
|
323
|
-
syntaxStyle: syntax,
|
|
324
|
-
fg: ink,
|
|
325
|
-
conceal: true,
|
|
326
|
-
streaming: line.streaming === true,
|
|
327
|
-
width: '100%',
|
|
328
|
-
});
|
|
311
|
+
if (line.kind === 'assistant') {
|
|
312
|
+
return markdown(renderer, line.text ?? '', id);
|
|
329
313
|
}
|
|
330
314
|
const plain = plainMarkdown(line.text ?? '');
|
|
331
315
|
return text(renderer, ink, plain.length > 0 ? plain : ' ', id);
|
|
332
316
|
}
|
|
317
|
+
function markdown(renderer, source, id) {
|
|
318
|
+
const box = new BoxRenderable(renderer, {
|
|
319
|
+
id,
|
|
320
|
+
width: '100%',
|
|
321
|
+
flexDirection: 'column',
|
|
322
|
+
});
|
|
323
|
+
let fenced = false;
|
|
324
|
+
for (const raw of source.replace(/\r\n/g, '\n').split('\n')) {
|
|
325
|
+
if (raw.trim().startsWith('```')) {
|
|
326
|
+
fenced = !fenced;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (fenced) {
|
|
330
|
+
box.add(text(renderer, tool, raw.length > 0 ? raw : ' '));
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const content = plainMarkdown(raw);
|
|
334
|
+
if (/^#{1,6}\s+/.test(raw))
|
|
335
|
+
box.add(text(renderer, user, t `${bold(content)}`));
|
|
336
|
+
else if (/^\s*>/.test(raw))
|
|
337
|
+
box.add(text(renderer, dim, t `${italic(content.replace(/^\s*>\s?/, ''))}`));
|
|
338
|
+
else if (/\*\*[^*\n]+\*\*/.test(raw))
|
|
339
|
+
box.add(text(renderer, ink, t `${bold(content)}`));
|
|
340
|
+
else if (/~~[^~\n]+~~/.test(raw))
|
|
341
|
+
box.add(text(renderer, ink, t `${strikethrough(content)}`));
|
|
342
|
+
else if (/\[[^\]\n]+\]\([^)]+\)/.test(raw))
|
|
343
|
+
box.add(text(renderer, user, t `${underline(content)}`));
|
|
344
|
+
else if (/`[^`\n]+`/.test(raw))
|
|
345
|
+
box.add(text(renderer, tool, content));
|
|
346
|
+
else
|
|
347
|
+
box.add(text(renderer, ink, content.length > 0 ? content : ' '));
|
|
348
|
+
}
|
|
349
|
+
return box;
|
|
350
|
+
}
|
|
333
351
|
function text(renderer, fg, content = '', id) {
|
|
334
352
|
return new TextRenderable(renderer, { ...(id ? { id } : {}), content, fg, wrapMode: 'word' });
|
|
335
353
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.63",
|
|
4
4
|
"description": "Product-neutral command line for a Steerable host. Products supply the bin name and brand.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@earendil-works/pi-tui": "0.87.1",
|
|
40
40
|
"@opentui/core": "0.5.12",
|
|
41
|
-
"@steerable/agent-client": "0.6.
|
|
42
|
-
"@steerable/agent-protocol": "0.6.
|
|
43
|
-
"@steerable/agent-shell": "0.6.
|
|
41
|
+
"@steerable/agent-client": "0.6.63",
|
|
42
|
+
"@steerable/agent-protocol": "0.6.63",
|
|
43
|
+
"@steerable/agent-shell": "0.6.63"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^22.10.5",
|
|
@@ -51,7 +51,9 @@
|
|
|
51
51
|
"gen": "echo agent-cli",
|
|
52
52
|
"build": "tsc -p tsconfig.json",
|
|
53
53
|
"test": "vitest run",
|
|
54
|
-
"
|
|
54
|
+
"test:tui": "vitest run tests/tui.test.ts tests/tui-framework.test.ts tests/tui-edge-cases.test.ts tests/tui-open-renderer.test.ts tests/tui-platform.test.ts tests/tui-case-coverage.test.ts",
|
|
55
|
+
"test:tui:pty": "vitest run tests/tui-tmux.test.ts",
|
|
56
|
+
"lint": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
55
57
|
"check:drift": "echo ok"
|
|
56
58
|
}
|
|
57
59
|
}
|