@autopilot-agent/cli 0.0.0-stage → 0.2.0
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 +52 -2
- package/dist/activity-animation.d.ts +59 -0
- package/dist/activity-animation.js +148 -0
- package/dist/animation-clock.d.ts +22 -0
- package/dist/animation-clock.js +76 -0
- package/dist/composition-root.d.ts +105 -0
- package/dist/composition-root.js +600 -0
- package/dist/context-report.d.ts +20 -0
- package/dist/context-report.js +173 -0
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +741 -0
- package/dist/design-tokens.d.ts +38 -0
- package/dist/design-tokens.js +38 -0
- package/dist/error-diagnostics.d.ts +8 -0
- package/dist/error-diagnostics.js +62 -0
- package/dist/event-projector.d.ts +26 -0
- package/dist/event-projector.js +335 -0
- package/dist/file-mention-context.d.ts +34 -0
- package/dist/file-mention-context.js +173 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/main.d.ts +40 -0
- package/dist/main.js +543 -0
- package/dist/parser.d.ts +17 -0
- package/dist/parser.js +400 -0
- package/dist/renderer.d.ts +9 -0
- package/dist/renderer.js +62 -0
- package/dist/scroll-viewport.d.ts +22 -0
- package/dist/scroll-viewport.js +62 -0
- package/dist/termination-format.d.ts +4 -0
- package/dist/termination-format.js +18 -0
- package/dist/timeline-lines.d.ts +38 -0
- package/dist/timeline-lines.js +104 -0
- package/dist/tui/app.d.ts +30 -0
- package/dist/tui/app.js +413 -0
- package/dist/tui/approval.d.ts +19 -0
- package/dist/tui/approval.js +41 -0
- package/dist/tui/brand/brand-header.d.ts +20 -0
- package/dist/tui/brand/brand-header.js +22 -0
- package/dist/tui/brand/intro.d.ts +12 -0
- package/dist/tui/brand/intro.js +88 -0
- package/dist/tui/brand/pixel.d.ts +8 -0
- package/dist/tui/brand/pixel.js +50 -0
- package/dist/tui/constants.d.ts +56 -0
- package/dist/tui/constants.js +57 -0
- package/dist/tui/context.d.ts +11 -0
- package/dist/tui/context.js +12 -0
- package/dist/tui/diff-stats.d.ts +8 -0
- package/dist/tui/diff-stats.js +7 -0
- package/dist/tui/format.d.ts +75 -0
- package/dist/tui/format.js +178 -0
- package/dist/tui/hooks/use-file-mention-index.d.ts +15 -0
- package/dist/tui/hooks/use-file-mention-index.js +59 -0
- package/dist/tui/hooks/use-pickers.d.ts +47 -0
- package/dist/tui/hooks/use-pickers.js +288 -0
- package/dist/tui/hooks/use-prompt-input.d.ts +54 -0
- package/dist/tui/hooks/use-prompt-input.js +258 -0
- package/dist/tui/hooks/use-status-view.d.ts +8 -0
- package/dist/tui/hooks/use-status-view.js +13 -0
- package/dist/tui/hooks/use-viewport.d.ts +22 -0
- package/dist/tui/hooks/use-viewport.js +117 -0
- package/dist/tui/input/file-mention.d.ts +33 -0
- package/dist/tui/input/file-mention.js +112 -0
- package/dist/tui/input/graphemes.d.ts +5 -0
- package/dist/tui/input/graphemes.js +10 -0
- package/dist/tui/input/keys.d.ts +16 -0
- package/dist/tui/input/keys.js +58 -0
- package/dist/tui/input/prompt-layout.d.ts +27 -0
- package/dist/tui/input/prompt-layout.js +84 -0
- package/dist/tui/input-area.d.ts +53 -0
- package/dist/tui/input-area.js +89 -0
- package/dist/tui/jobs.d.ts +10 -0
- package/dist/tui/jobs.js +24 -0
- package/dist/tui/lifecycle.d.ts +23 -0
- package/dist/tui/lifecycle.js +114 -0
- package/dist/tui/markdown/diff.d.ts +8 -0
- package/dist/tui/markdown/diff.js +19 -0
- package/dist/tui/markdown/inline.d.ts +4 -0
- package/dist/tui/markdown/inline.js +32 -0
- package/dist/tui/markdown/markdown.d.ts +17 -0
- package/dist/tui/markdown/markdown.js +117 -0
- package/dist/tui/markdown/table.d.ts +19 -0
- package/dist/tui/markdown/table.js +74 -0
- package/dist/tui/performance-diagnostics.d.ts +7 -0
- package/dist/tui/performance-diagnostics.js +57 -0
- package/dist/tui/pickers/effort.d.ts +10 -0
- package/dist/tui/pickers/effort.js +12 -0
- package/dist/tui/pickers/file-mention.d.ts +11 -0
- package/dist/tui/pickers/file-mention.js +22 -0
- package/dist/tui/pickers/model.d.ts +10 -0
- package/dist/tui/pickers/model.js +12 -0
- package/dist/tui/pickers/permission.d.ts +10 -0
- package/dist/tui/pickers/permission.js +13 -0
- package/dist/tui/pickers/skill.d.ts +13 -0
- package/dist/tui/pickers/skill.js +25 -0
- package/dist/tui/pickers/slash.d.ts +10 -0
- package/dist/tui/pickers/slash.js +11 -0
- package/dist/tui/prompt-input.d.ts +11 -0
- package/dist/tui/prompt-input.js +73 -0
- package/dist/tui/render-mode.d.ts +20 -0
- package/dist/tui/render-mode.js +31 -0
- package/dist/tui/run.d.ts +6 -0
- package/dist/tui/run.js +237 -0
- package/dist/tui/status-line.d.ts +16 -0
- package/dist/tui/status-line.js +37 -0
- package/dist/tui/store.d.ts +4 -0
- package/dist/tui/store.js +380 -0
- package/dist/tui/timeline/live.d.ts +16 -0
- package/dist/tui/timeline/live.js +153 -0
- package/dist/tui/timeline/presentation-timeline.d.ts +15 -0
- package/dist/tui/timeline/presentation-timeline.js +128 -0
- package/dist/tui/timeline/progress.d.ts +12 -0
- package/dist/tui/timeline/progress.js +43 -0
- package/dist/tui/timeline/static-timeline.d.ts +13 -0
- package/dist/tui/timeline/static-timeline.js +131 -0
- package/dist/tui/timeline/tool-tree.d.ts +7 -0
- package/dist/tui/timeline/tool-tree.js +25 -0
- package/dist/tui/timeline/turn-items.d.ts +12 -0
- package/dist/tui/timeline/turn-items.js +28 -0
- package/dist/tui/timeline/turn.d.ts +19 -0
- package/dist/tui/timeline/turn.js +21 -0
- package/dist/tui/todo-list.d.ts +50 -0
- package/dist/tui/todo-list.js +120 -0
- package/dist/tui/transcript.d.ts +24 -0
- package/dist/tui/transcript.js +34 -0
- package/dist/tui/trust-dialog.d.ts +51 -0
- package/dist/tui/trust-dialog.js +127 -0
- package/dist/tui/types.d.ts +189 -0
- package/dist/tui/types.js +5 -0
- package/dist/tui/viewport/selection.d.ts +27 -0
- package/dist/tui/viewport/selection.js +79 -0
- package/dist/tui/viewport/sources.d.ts +29 -0
- package/dist/tui/viewport/sources.js +592 -0
- package/dist/tui/viewport/viewport.d.ts +12 -0
- package/dist/tui/viewport/viewport.js +91 -0
- package/dist/tui-input-history.d.ts +23 -0
- package/dist/tui-input-history.js +42 -0
- package/dist/tui-input-router.d.ts +24 -0
- package/dist/tui-input-router.js +115 -0
- package/dist/tui.d.ts +15 -0
- package/dist/tui.js +13 -0
- package/dist/types.d.ts +467 -0
- package/dist/types.js +7 -0
- package/package.json +58 -4
package/README.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @autopilot-agent/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Autopilot 的终端宿主包。普通用户安装主包 `autopilot-agent@0.2.0`,无需分别安装各个 scoped 包:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g autopilot-agent@0.2.0
|
|
7
|
+
ap --help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
完整安装、Anthropic / Chat Completions 配置、使用和限制见 [主包手册](https://github.com/C-Clay-W/autopilot/blob/main/packages/autopilot-agent/README.md),完整 argv / slash 语法见 [CLI 参考](https://github.com/C-Clay-W/autopilot/blob/main/agent/doc/CLI-Reference.md)。开发参考随源码更新,可能先于已发布版本的行为。
|
|
11
|
+
|
|
12
|
+
## 结构与职责
|
|
13
|
+
|
|
14
|
+
- parser.ts 解析 argv 与交互命令,main.ts 编排入口、输出及退出。
|
|
15
|
+
- composition-root.ts 注入共享 Core Runtime 与 Node Adapters,controller.ts 管理任务、审批与恢复。
|
|
16
|
+
- tui/ 使用 React / Ink;renderer.ts 与 event-projector.ts 支持纯文本和 JSONL。
|
|
17
|
+
- 内部依赖为 Core、Adapters、Presentation,0.2.0 使用匹配的精确版本。
|
|
18
|
+
|
|
19
|
+
本包的 bin 提供 ap/autopilot,主入口包再转发至本包的 dist/main.js,不复制 Runtime。TTY 使用交互界面;显式 --output text/jsonl 用于脚本,JSONL 不输出 ANSI 或交互提示。
|
|
20
|
+
|
|
21
|
+
## 编程入口
|
|
22
|
+
|
|
23
|
+
包根导出 parseArgv、parseSlashAction、renderHelp、createCliCompositionRoot、createCliController、main、事件投影和 TUI 相关类型。优先把它作为终端应用使用;自行嵌入时需要处理输入输出、Trust、审批和退出清理,不在模块导入时自动启动任务。
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { parseArgv, renderHelp } from "@autopilot-agent/cli";
|
|
27
|
+
|
|
28
|
+
const parsed = parseArgv(["chat", "解释项目结构", "--output", "text"]);
|
|
29
|
+
console.log(parsed);
|
|
30
|
+
console.log(renderHelp());
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 源码开发
|
|
34
|
+
|
|
35
|
+
从 monorepo 根安装依赖,依次构建 Core、Adapters、Presentation,再执行:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run build -w apps/cli
|
|
39
|
+
node apps/cli/dist/main.js --help
|
|
40
|
+
npm run test -w apps/cli -- test/parser.test.ts
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
build 使用 TypeScript,输出 dist/*.js 与声明,不在 npm 包中发布源码映射和测试。当前 Node 声明为 >=20.11.0 <22;建议整仓构建使用 Node 20.20.2。
|
|
44
|
+
|
|
45
|
+
CLI 使用用户级 settings.json,默认工作区为当前目录,可显式指定 --workspace-root。CLI 不读取开发仓库 Web env,也不因配置中含 RAG 字段而自动启用私有知识库工具。重要项目保留版本控制,OS 沙箱、MCP 和平台限制见主包手册。
|
|
46
|
+
|
|
47
|
+
## 0.2.0 版本
|
|
48
|
+
|
|
49
|
+
本包随 npm 0.2.0 发布,内部依赖使用匹配的精确版本。Windows 命令、后台 Job 与 host MCP 逐次审批后以当前用户权限直接执行,审批不提供 OS 隔离;macOS 继续使用 Seatbelt。实机与进程树生命周期仍需按实际验证确认。
|
|
50
|
+
|
|
51
|
+
## 许可证
|
|
52
|
+
|
|
53
|
+
公开源码采用 [MIT 许可证](https://github.com/C-Clay-W/autopilot/blob/main/LICENSE)。第三方依赖保留各自许可证。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI TUI 活动状态与离散动画。
|
|
3
|
+
*
|
|
4
|
+
* 状态由安全事件类型归约得到,动画只消费显式状态和共享时钟;稳定文案始终
|
|
5
|
+
* 可见。活动 glyph 采用 Claude Code 的单字符 ping-pong 形式,不把状态包装成
|
|
6
|
+
* 调试风格的进度条。
|
|
7
|
+
*/
|
|
8
|
+
import { type ReactNode } from "react";
|
|
9
|
+
import type { CliTurnStatus } from "./types.js";
|
|
10
|
+
import type { TuiAnimationClock } from "./animation-clock.js";
|
|
11
|
+
export type CliActivityPhase = "requesting" | "thinking" | "waiting" | "transition" | "responding" | "stalled" | "terminal";
|
|
12
|
+
export interface CliActivityState {
|
|
13
|
+
phase: CliActivityPhase;
|
|
14
|
+
lastProgressAt: number;
|
|
15
|
+
transitionStartedAt?: number;
|
|
16
|
+
hasActiveTool: boolean;
|
|
17
|
+
stallable: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CliActivityView {
|
|
20
|
+
phase: CliActivityPhase;
|
|
21
|
+
label: string;
|
|
22
|
+
frame: string;
|
|
23
|
+
}
|
|
24
|
+
interface CliActivityEventLike {
|
|
25
|
+
type: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const CLI_ACTIVITY_STALLED_AFTER_MS = 20000;
|
|
28
|
+
export declare const CLI_ACTIVITY_TRANSITION_MS = 2000;
|
|
29
|
+
/** 与 CC SpinnerGlyph 一致,glyph 每 120ms 前进一格;共享时钟仍可用 50ms tick。 */
|
|
30
|
+
export declare const CLI_ACTIVITY_SPINNER_FRAME_MS = 120;
|
|
31
|
+
/** 创建一个等待 Provider 首个事件的初始活动状态。 */
|
|
32
|
+
export declare function createInitialCliActivityState(now: number): CliActivityState;
|
|
33
|
+
/** 按安全事件类型归约活动状态,不读取事件文案或模型原始输出。 */
|
|
34
|
+
export declare function reduceCliActivityState(previous: CliActivityState, event: CliActivityEventLike, now: number): CliActivityState;
|
|
35
|
+
/** 把投影终态映射为不会继续驱动动画的活动状态。 */
|
|
36
|
+
export declare function settleCliActivityState(previous: CliActivityState, status: CliTurnStatus, now: number): CliActivityState;
|
|
37
|
+
/** 返回指定活动阶段对应的单 glyph 帧;外层 Box 负责固定占位宽度。 */
|
|
38
|
+
export declare function getCliActivityFrame(phase: CliActivityPhase, now: number): string;
|
|
39
|
+
/** 根据当前时间解析过场、stalled 和审批等待,形成最终可见活动状态。 */
|
|
40
|
+
export declare function resolveCliActivityView(input: {
|
|
41
|
+
activity: CliActivityState;
|
|
42
|
+
status: CliTurnStatus;
|
|
43
|
+
now: number;
|
|
44
|
+
waitingForApproval?: boolean;
|
|
45
|
+
}): CliActivityView | undefined;
|
|
46
|
+
/** 渲染稳定活动文案和可选的固定宽度离散帧。 */
|
|
47
|
+
export declare function TuiActivityLine(props: {
|
|
48
|
+
activity: CliActivityState;
|
|
49
|
+
status: CliTurnStatus;
|
|
50
|
+
waitingForApproval: boolean;
|
|
51
|
+
useColor: boolean;
|
|
52
|
+
screenReader: boolean;
|
|
53
|
+
reducedMotion: boolean;
|
|
54
|
+
animationActive: boolean;
|
|
55
|
+
clock: TuiAnimationClock;
|
|
56
|
+
startedAt?: number;
|
|
57
|
+
activeForm?: string;
|
|
58
|
+
}): ReactNode;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* CLI TUI 活动状态与离散动画。
|
|
4
|
+
*
|
|
5
|
+
* 状态由安全事件类型归约得到,动画只消费显式状态和共享时钟;稳定文案始终
|
|
6
|
+
* 可见。活动 glyph 采用 Claude Code 的单字符 ping-pong 形式,不把状态包装成
|
|
7
|
+
* 调试风格的进度条。
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useState } from "react";
|
|
10
|
+
import { Box, Text } from "ink";
|
|
11
|
+
import { formatTuiDuration } from "./tui/format.js";
|
|
12
|
+
export const CLI_ACTIVITY_STALLED_AFTER_MS = 20_000;
|
|
13
|
+
export const CLI_ACTIVITY_TRANSITION_MS = 2_000;
|
|
14
|
+
/** CC v2.1.88 的默认 spinner glyph;首尾重复保证 ping-pong 转向自然。 */
|
|
15
|
+
const CLI_ACTIVITY_SPINNER_FRAMES = ["·", "✢", "*", "✶", "✻", "✽", "✽", "✻", "✶", "*", "✢", "·"];
|
|
16
|
+
/** 与 CC SpinnerGlyph 一致,glyph 每 120ms 前进一格;共享时钟仍可用 50ms tick。 */
|
|
17
|
+
export const CLI_ACTIVITY_SPINNER_FRAME_MS = 120;
|
|
18
|
+
/** 创建一个等待 Provider 首个事件的初始活动状态。 */
|
|
19
|
+
export function createInitialCliActivityState(now) {
|
|
20
|
+
return {
|
|
21
|
+
phase: "requesting",
|
|
22
|
+
lastProgressAt: now,
|
|
23
|
+
hasActiveTool: false,
|
|
24
|
+
stallable: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** 将活动状态推进到指定稳定阶段,并记录本次可观察进展时间。 */
|
|
28
|
+
function advanceCliActivityState(previous, phase, now, options = {}) {
|
|
29
|
+
return {
|
|
30
|
+
...previous,
|
|
31
|
+
phase,
|
|
32
|
+
lastProgressAt: now,
|
|
33
|
+
hasActiveTool: options.hasActiveTool ?? previous.hasActiveTool,
|
|
34
|
+
stallable: options.stallable ?? previous.stallable,
|
|
35
|
+
transitionStartedAt: options.transitionStartedAt,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** 按安全事件类型归约活动状态,不读取事件文案或模型原始输出。 */
|
|
39
|
+
export function reduceCliActivityState(previous, event, now) {
|
|
40
|
+
switch (event.type) {
|
|
41
|
+
case "turn_started":
|
|
42
|
+
return createInitialCliActivityState(now);
|
|
43
|
+
case "reasoning_summary":
|
|
44
|
+
case "reasoning_hidden":
|
|
45
|
+
case "model_recovery":
|
|
46
|
+
case "context_overflow":
|
|
47
|
+
case "context_compact_started":
|
|
48
|
+
case "context_compacted":
|
|
49
|
+
return advanceCliActivityState(previous, "thinking", now, { hasActiveTool: false, stallable: true });
|
|
50
|
+
case "tool_started":
|
|
51
|
+
return advanceCliActivityState(previous, "transition", now, { hasActiveTool: true, stallable: false, transitionStartedAt: now });
|
|
52
|
+
case "tool_result":
|
|
53
|
+
return advanceCliActivityState(previous, "transition", now, { hasActiveTool: false, stallable: true, transitionStartedAt: now });
|
|
54
|
+
case "approval_required":
|
|
55
|
+
return advanceCliActivityState(previous, "waiting", now, { hasActiveTool: false, stallable: false });
|
|
56
|
+
case "answer_delta":
|
|
57
|
+
return advanceCliActivityState(previous, "responding", now, { hasActiveTool: false, stallable: true });
|
|
58
|
+
case "status":
|
|
59
|
+
return previous;
|
|
60
|
+
default:
|
|
61
|
+
return previous;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** 把投影终态映射为不会继续驱动动画的活动状态。 */
|
|
65
|
+
export function settleCliActivityState(previous, status, now) {
|
|
66
|
+
if (status === "idle" || status === "running" || status === "waiting_approval")
|
|
67
|
+
return previous;
|
|
68
|
+
return advanceCliActivityState(previous, "terminal", now, { hasActiveTool: false, stallable: false });
|
|
69
|
+
}
|
|
70
|
+
/** 返回指定活动阶段对应的单 glyph 帧;外层 Box 负责固定占位宽度。 */
|
|
71
|
+
export function getCliActivityFrame(phase, now) {
|
|
72
|
+
if (phase === "terminal")
|
|
73
|
+
return "";
|
|
74
|
+
return CLI_ACTIVITY_SPINNER_FRAMES[Math.floor(Math.max(0, now) / CLI_ACTIVITY_SPINNER_FRAME_MS) % CLI_ACTIVITY_SPINNER_FRAMES.length] ?? "·";
|
|
75
|
+
}
|
|
76
|
+
function formatCliActivityElapsed(durationMs) {
|
|
77
|
+
const totalSeconds = Math.max(1, Math.floor(Math.max(0, durationMs) / 1_000));
|
|
78
|
+
if (totalSeconds < 60)
|
|
79
|
+
return `${totalSeconds}s`;
|
|
80
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
81
|
+
const seconds = totalSeconds % 60;
|
|
82
|
+
if (minutes < 60)
|
|
83
|
+
return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
84
|
+
return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, "0")}m ${String(seconds).padStart(2, "0")}s`;
|
|
85
|
+
}
|
|
86
|
+
/** 返回活动阶段的稳定文案,供动画降级和 screen reader 使用。 */
|
|
87
|
+
function getCliActivityLabel(phase, input = {}) {
|
|
88
|
+
switch (phase) {
|
|
89
|
+
case "requesting": return "Preparing · waiting for first event";
|
|
90
|
+
case "thinking": return "Thinking · planning next step";
|
|
91
|
+
case "waiting": return input.hasActiveTool ? "Waiting · tool result" : "Waiting · provider response";
|
|
92
|
+
case "transition": return "Running action · starting tool";
|
|
93
|
+
case "responding": return "Answering · receiving output";
|
|
94
|
+
case "stalled": return `Still working · no new event for ${formatCliActivityElapsed(input.idleForMs ?? 0)}`;
|
|
95
|
+
case "terminal": return "Completed";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** 根据当前时间解析过场、stalled 和审批等待,形成最终可见活动状态。 */
|
|
99
|
+
export function resolveCliActivityView(input) {
|
|
100
|
+
const { activity, status, now } = input;
|
|
101
|
+
if (status === "idle")
|
|
102
|
+
return undefined;
|
|
103
|
+
if (status !== "running" && status !== "waiting_approval")
|
|
104
|
+
return { phase: "terminal", label: status, frame: "" };
|
|
105
|
+
if (input.waitingForApproval || status === "waiting_approval") {
|
|
106
|
+
return { phase: "waiting", label: "Waiting for approval", frame: getCliActivityFrame("waiting", now) };
|
|
107
|
+
}
|
|
108
|
+
let phase = activity.phase;
|
|
109
|
+
if (phase === "transition" && activity.transitionStartedAt !== undefined && now - activity.transitionStartedAt >= CLI_ACTIVITY_TRANSITION_MS) {
|
|
110
|
+
phase = activity.hasActiveTool ? "waiting" : "thinking";
|
|
111
|
+
}
|
|
112
|
+
if (activity.hasActiveTool && phase !== "transition")
|
|
113
|
+
phase = "waiting";
|
|
114
|
+
if (activity.stallable && !activity.hasActiveTool && phase !== "waiting" && now - activity.lastProgressAt >= CLI_ACTIVITY_STALLED_AFTER_MS) {
|
|
115
|
+
phase = "stalled";
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
phase,
|
|
119
|
+
label: getCliActivityLabel(phase, { idleForMs: Math.max(0, now - activity.lastProgressAt), hasActiveTool: activity.hasActiveTool }),
|
|
120
|
+
frame: getCliActivityFrame(phase, now),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** 渲染稳定活动文案和可选的固定宽度离散帧。 */
|
|
124
|
+
export function TuiActivityLine(props) {
|
|
125
|
+
const [clockTime, setClockTime] = useState(props.clock.now());
|
|
126
|
+
const view = resolveCliActivityView({ activity: props.activity, status: props.status, now: clockTime, waitingForApproval: props.waitingForApproval });
|
|
127
|
+
const shouldAnimate = props.animationActive && !props.screenReader && !props.reducedMotion && view !== undefined && view.phase !== "terminal";
|
|
128
|
+
const shouldTick = shouldAnimate || (props.startedAt !== undefined && (props.status === "running" || props.status === "waiting_approval"));
|
|
129
|
+
useEffect(function subscribeActivityClock() {
|
|
130
|
+
props.clock.setActive(shouldTick);
|
|
131
|
+
if (!shouldTick) {
|
|
132
|
+
setClockTime(props.clock.now());
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
setClockTime(props.clock.now());
|
|
136
|
+
return props.clock.subscribe(function updateActivityClock(time) {
|
|
137
|
+
// 禁动画仍按秒刷新累计时间,复用同一枚时钟。
|
|
138
|
+
setClockTime(function next(previous) { return shouldAnimate || Math.floor(previous / 1000) !== Math.floor(time / 1000) ? time : previous; });
|
|
139
|
+
});
|
|
140
|
+
}, [props.clock, shouldAnimate, shouldTick]);
|
|
141
|
+
if (!view)
|
|
142
|
+
return null;
|
|
143
|
+
if (view.phase === "terminal")
|
|
144
|
+
return null;
|
|
145
|
+
const frame = shouldAnimate ? view.frame : "·";
|
|
146
|
+
const isStalled = view.phase === "stalled";
|
|
147
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { bold: isStalled, color: props.useColor ? "ansi256(208)" : undefined, children: frame }) }), _jsxs(Text, { bold: isStalled, dimColor: props.useColor && !isStalled, children: [props.activeForm ?? view.label, props.startedAt !== undefined ? ` · ${formatTuiDuration(Math.max(0, clockTime - props.startedAt))}` : ""] })] }));
|
|
148
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI TUI 共享动画时钟。
|
|
3
|
+
*
|
|
4
|
+
* 时钟只负责向订阅者发出当前时间,不持有任何业务状态;终端活动区不可见、
|
|
5
|
+
* screen reader、reduced-motion 或终态时可以停止订阅,测试也可以注入时间和调度器。
|
|
6
|
+
*/
|
|
7
|
+
export interface TuiAnimationClock {
|
|
8
|
+
now(): number;
|
|
9
|
+
subscribe(listener: (time: number) => void): () => void;
|
|
10
|
+
setActive(active: boolean): void;
|
|
11
|
+
tick(): void;
|
|
12
|
+
dispose(): void;
|
|
13
|
+
}
|
|
14
|
+
/** 共享时钟可注入的调度器配置,避免动画单测依赖真实 setInterval。 */
|
|
15
|
+
export interface TuiAnimationClockOptions {
|
|
16
|
+
now?: () => number;
|
|
17
|
+
frameMs?: number;
|
|
18
|
+
schedule?: (listener: () => void, frameMs: number) => unknown;
|
|
19
|
+
cancel?: (handle: unknown) => void;
|
|
20
|
+
}
|
|
21
|
+
/** 创建可暂停、可注入且只在有订阅者时运行的 TUI 时钟。 */
|
|
22
|
+
export declare function createTuiAnimationClock(options?: TuiAnimationClockOptions): TuiAnimationClock;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI TUI 共享动画时钟。
|
|
3
|
+
*
|
|
4
|
+
* 时钟只负责向订阅者发出当前时间,不持有任何业务状态;终端活动区不可见、
|
|
5
|
+
* screen reader、reduced-motion 或终态时可以停止订阅,测试也可以注入时间和调度器。
|
|
6
|
+
*/
|
|
7
|
+
/** 使用 Node 默认定时器启动一枚共享时钟任务。 */
|
|
8
|
+
function scheduleDefaultTimer(listener, frameMs) {
|
|
9
|
+
return setInterval(listener, frameMs);
|
|
10
|
+
}
|
|
11
|
+
/** 清理由 Node 默认定时器创建的共享时钟任务。 */
|
|
12
|
+
function cancelDefaultTimer(handle) {
|
|
13
|
+
clearInterval(handle);
|
|
14
|
+
}
|
|
15
|
+
/** 创建可暂停、可注入且只在有订阅者时运行的 TUI 时钟。 */
|
|
16
|
+
export function createTuiAnimationClock(options = {}) {
|
|
17
|
+
const now = options.now ?? function readCurrentTime() { return Date.now(); };
|
|
18
|
+
const frameMs = Math.max(16, options.frameMs ?? 50);
|
|
19
|
+
const schedule = options.schedule ?? scheduleDefaultTimer;
|
|
20
|
+
const cancel = options.cancel ?? cancelDefaultTimer;
|
|
21
|
+
const listeners = new Set();
|
|
22
|
+
let timer;
|
|
23
|
+
let active = true;
|
|
24
|
+
let disposed = false;
|
|
25
|
+
/** 停止当前定时器,确保没有订阅者或时钟暂停时不继续唤醒进程。 */
|
|
26
|
+
function stopTimer() {
|
|
27
|
+
if (timer === undefined)
|
|
28
|
+
return;
|
|
29
|
+
cancel(timer);
|
|
30
|
+
timer = undefined;
|
|
31
|
+
}
|
|
32
|
+
/** 根据订阅者和 active 状态启动唯一的共享定时器。 */
|
|
33
|
+
function ensureTimer() {
|
|
34
|
+
if (disposed || !active || listeners.size === 0 || timer !== undefined)
|
|
35
|
+
return;
|
|
36
|
+
timer = schedule(function emitScheduledTick() { clock.tick(); }, frameMs);
|
|
37
|
+
}
|
|
38
|
+
const clock = {
|
|
39
|
+
now,
|
|
40
|
+
subscribe(listener) {
|
|
41
|
+
if (disposed)
|
|
42
|
+
return function ignoreDisposedSubscription() { };
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
ensureTimer();
|
|
45
|
+
return function unsubscribe() {
|
|
46
|
+
listeners.delete(listener);
|
|
47
|
+
if (listeners.size === 0)
|
|
48
|
+
stopTimer();
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
setActive(nextActive) {
|
|
52
|
+
if (disposed || active === nextActive)
|
|
53
|
+
return;
|
|
54
|
+
active = nextActive;
|
|
55
|
+
if (!active)
|
|
56
|
+
stopTimer();
|
|
57
|
+
else
|
|
58
|
+
ensureTimer();
|
|
59
|
+
},
|
|
60
|
+
tick() {
|
|
61
|
+
if (disposed || !active)
|
|
62
|
+
return;
|
|
63
|
+
const time = now();
|
|
64
|
+
for (const listener of [...listeners])
|
|
65
|
+
listener(time);
|
|
66
|
+
},
|
|
67
|
+
dispose() {
|
|
68
|
+
if (disposed)
|
|
69
|
+
return;
|
|
70
|
+
disposed = true;
|
|
71
|
+
stopTimer();
|
|
72
|
+
listeners.clear();
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
return clock;
|
|
76
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Composition Root。
|
|
3
|
+
*
|
|
4
|
+
* 负责选择 Core 默认 Runtime、共享 Node Adapter、canonical workspaceRoot、
|
|
5
|
+
* trust policy 和显式 stateRoot;不负责命令解析、事件投影或 Runtime 状态机。
|
|
6
|
+
*/
|
|
7
|
+
import { type AgentRuntime, type CapabilityLoader, type CreateRuntimeBootstrapServiceOptions, type RuntimeBootstrapService, type SafeBootstrapConfig, type SandboxCapabilityReport, type WorkspaceTrustEntry, type WorkspaceTrustPolicy } from "@autopilot-agent/core";
|
|
8
|
+
import type { CliRuntimeStatusDefaults, CliSkillSummary } from "./types.js";
|
|
9
|
+
import { type WorkspaceRegistryEntry, type NodeCapabilityLoader, type NodeSettingsLoader } from "@autopilot-agent/adapters";
|
|
10
|
+
import type { AutopilotSettingsScope } from "@autopilot-agent/core";
|
|
11
|
+
/** Doctor 单项结果;只含状态和脱敏摘要,绝不包含路径、凭据或 Provider 正文。 */
|
|
12
|
+
export interface CliDoctorCheck {
|
|
13
|
+
name: string;
|
|
14
|
+
status: "PASS" | "WARN" | "FAIL" | "SKIP";
|
|
15
|
+
detail: string;
|
|
16
|
+
}
|
|
17
|
+
/** CLI Composition Root 的装配选项。 */
|
|
18
|
+
export interface CreateCliCompositionRootOptions {
|
|
19
|
+
config?: SafeBootstrapConfig;
|
|
20
|
+
stateRoot?: string;
|
|
21
|
+
trustRoot?: string;
|
|
22
|
+
workspaceRoot?: string;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
trustPolicy?: WorkspaceTrustPolicy;
|
|
26
|
+
capabilityLoader?: CliCapabilityLoader;
|
|
27
|
+
defaultRunConfig?: CreateRuntimeBootstrapServiceOptions["defaultRunConfig"];
|
|
28
|
+
/** 已信任 settings Loader 的测试 seam;生产默认创建 Node Loader。 */
|
|
29
|
+
settingsLoader?: NodeSettingsLoader;
|
|
30
|
+
/** Doctor 连通性探测的测试 seam;生产默认使用全局 fetch。 */
|
|
31
|
+
connectivityFetch?: typeof fetch;
|
|
32
|
+
/** Web Search 请求的 fake HTTP 测试 seam;生产默认使用平台原生 fetch。 */
|
|
33
|
+
webSearchFetch?: typeof fetch;
|
|
34
|
+
}
|
|
35
|
+
/** CLI 能力加载器:沿用 Node 的开发能力,但禁止 RAG loader 进入 CLI 装配边界。 */
|
|
36
|
+
export type CliCapabilityLoader = CapabilityLoader & Partial<Pick<NodeCapabilityLoader, "probeWebSearchConnectivity">>;
|
|
37
|
+
/** CLI Composition Root 对外暴露的启动边界。 */
|
|
38
|
+
export interface CliCompositionRoot {
|
|
39
|
+
readonly stateRoot: string;
|
|
40
|
+
/** 仅由已解析的业务命令调用,保障当前用户配置根存在且安全。 */
|
|
41
|
+
ensureUserConfigDirectory(): void;
|
|
42
|
+
/** Trust 成功后创建当前项目的 .autopilot 与临时文件目录,不生成 settings 文件。 */
|
|
43
|
+
ensureProjectAutopilotDirectory(workspaceRoot: string): void;
|
|
44
|
+
establishTrust(): Promise<{
|
|
45
|
+
workspaceRoot: string;
|
|
46
|
+
trustEntry: WorkspaceTrustEntry;
|
|
47
|
+
}>;
|
|
48
|
+
/** Trust 前解析 canonical 工作区并读取信任状态;未信任时返回记录而不抛 WorkspaceTrustError。 */
|
|
49
|
+
resolveTrust(): Promise<{
|
|
50
|
+
workspaceRoot: string;
|
|
51
|
+
trustEntry: WorkspaceTrustEntry;
|
|
52
|
+
}>;
|
|
53
|
+
/** 授予精确 canonical 工作区信任并返回持久化后的记录。 */
|
|
54
|
+
grantTrust(workspaceRoot: string): Promise<WorkspaceTrustEntry>;
|
|
55
|
+
/** 读取指定工作区的当前信任状态。 */
|
|
56
|
+
getTrustStatus(workspaceRoot: string): Promise<WorkspaceTrustEntry>;
|
|
57
|
+
/** 撤销当前 canonical workspace 的 Trust;调用后不得继续创建 Runtime。 */
|
|
58
|
+
revokeTrust(workspaceRoot: string): Promise<WorkspaceTrustEntry>;
|
|
59
|
+
/** 读取宿主级工作区索引;不会扫描磁盘或创建状态根。 */
|
|
60
|
+
listWorkspaceRegistry(): WorkspaceRegistryEntry[];
|
|
61
|
+
/** 仅注销 Registry 索引条目,不删除任何工作区数据。 */
|
|
62
|
+
removeWorkspaceRegistry(workspaceId: string): boolean;
|
|
63
|
+
/** 读取显式 scope 的脱敏 settings。 */
|
|
64
|
+
showConfig(scope: AutopilotSettingsScope, workspaceRoot: string): Record<string, unknown>;
|
|
65
|
+
/** 读取显式 scope 下允许公开的单个 settings 值。 */
|
|
66
|
+
getConfig(scope: AutopilotSettingsScope, key: string, workspaceRoot: string): unknown;
|
|
67
|
+
/** 写入显式 scope 下允许公开的单个 settings 值。 */
|
|
68
|
+
setConfig(scope: AutopilotSettingsScope, key: string, value: string, workspaceRoot: string): Record<string, unknown>;
|
|
69
|
+
/** 执行默认离线或显式受限连通性 Doctor 检查。 */
|
|
70
|
+
doctor(workspaceRoot: string, connectivity: boolean): Promise<CliDoctorCheck[]>;
|
|
71
|
+
/** 读取当前平台沙箱能力报告(脱敏);绑定传入的 canonical workspace。 */
|
|
72
|
+
sandboxStatus(workspaceRoot: string): Promise<SandboxCapabilityReport>;
|
|
73
|
+
/** 显式触发沙箱 setup(Windows UAC 安装;macOS 仅返回当前能力报告)。 */
|
|
74
|
+
sandboxSetup(workspaceRoot: string): Promise<SandboxCapabilityReport>;
|
|
75
|
+
createRuntime(workspaceRoot: string): Promise<AgentRuntime>;
|
|
76
|
+
bootstrap(): Promise<AgentRuntime>;
|
|
77
|
+
getBootstrapService(): RuntimeBootstrapService;
|
|
78
|
+
/** 只向 Controller 暴露可安全显示的已热更新 settings 默认值。 */
|
|
79
|
+
getRuntimeStatusDefaults(): CliRuntimeStatusDefaults;
|
|
80
|
+
/** 返回不含凭据的首次运行配置诊断;undefined 表示可继续启动。 */
|
|
81
|
+
getStartupConfigurationError(): string | undefined;
|
|
82
|
+
listSkills(): Promise<CliSkillSummary[]>;
|
|
83
|
+
resolveSkill(skill: string): Promise<{
|
|
84
|
+
skillId: string;
|
|
85
|
+
contentHash: string;
|
|
86
|
+
}>;
|
|
87
|
+
/** 返回不含环境变量的 MCP server 审批摘要与执行隔离事实。 */
|
|
88
|
+
listMcp(workspaceRoot: string): Promise<Array<{
|
|
89
|
+
serverName: string;
|
|
90
|
+
program: string;
|
|
91
|
+
args: string[];
|
|
92
|
+
cwd: string;
|
|
93
|
+
approved: boolean;
|
|
94
|
+
executionMode: "sandbox" | "host";
|
|
95
|
+
isolation: "sandboxed" | "unisolated";
|
|
96
|
+
}>>;
|
|
97
|
+
/** 保存精确 configHash 绑定的 workspace grant;调用方必须已完成 CLI 确认。 */
|
|
98
|
+
approveMcp(workspaceRoot: string, serverName: string): Promise<{
|
|
99
|
+
serverName: string;
|
|
100
|
+
}>;
|
|
101
|
+
/** 删除 server 当前 configHash 对应的 workspace grant。 */
|
|
102
|
+
revokeMcp(workspaceRoot: string, serverName: string): Promise<boolean>;
|
|
103
|
+
}
|
|
104
|
+
/** 创建 CLI Composition Root;只有 Runtime bootstrap 的 trust 成功阶段才初始化状态 manifest。 */
|
|
105
|
+
export declare function createCliCompositionRoot(options?: CreateCliCompositionRootOptions): CliCompositionRoot;
|