@autopilot-agent/cli 0.0.0-stage → 0.1.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 +180 -2
- package/dist/activity-animation.d.ts +57 -0
- package/dist/activity-animation.js +145 -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 +588 -0
- package/dist/context-report.d.ts +20 -0
- package/dist/context-report.js +174 -0
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +681 -0
- package/dist/design-tokens.d.ts +34 -0
- package/dist/design-tokens.js +34 -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 +333 -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 +391 -0
- package/dist/renderer.d.ts +9 -0
- package/dist/renderer.js +48 -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 +17 -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 +410 -0
- package/dist/tui/approval.d.ts +19 -0
- package/dist/tui/approval.js +37 -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 +176 -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 +344 -0
- package/dist/tui/timeline/live.d.ts +16 -0
- package/dist/tui/timeline/live.js +151 -0
- package/dist/tui/timeline/presentation-timeline.d.ts +14 -0
- package/dist/tui/timeline/presentation-timeline.js +109 -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 +121 -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.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 +175 -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 +585 -0
- package/dist/tui/viewport/viewport.d.ts +12 -0
- package/dist/tui/viewport/viewport.js +87 -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 +454 -0
- package/dist/types.js +7 -0
- package/package.json +58 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** CLI TUI 语义色 token;组件只引用 token,不直接写危险态颜色。 */
|
|
2
|
+
export declare const CLI_TUI_COLORS: {
|
|
3
|
+
readonly accent: "ansi256(208)";
|
|
4
|
+
readonly permissionFullAccess: "#ff6b80";
|
|
5
|
+
readonly copilotBlue: "ansi256(33)";
|
|
6
|
+
readonly copilotBlueBright: "ansi256(39)";
|
|
7
|
+
readonly copilotPurple: "ansi256(99)";
|
|
8
|
+
readonly copilotGreen: "ansi256(77)";
|
|
9
|
+
readonly copilotInk: "ansi256(255)";
|
|
10
|
+
readonly copilotMuted: "ansi256(246)";
|
|
11
|
+
readonly copilotOutline: "ansi256(243)";
|
|
12
|
+
};
|
|
13
|
+
/** Design.md 中橙色强调色的 ANSI 256 语义映射;NO_COLOR 时完全不输出颜色。 */
|
|
14
|
+
export declare const TUI_ACCENT_COLOR: "ansi256(208)";
|
|
15
|
+
/** 工具徽标 chip 的强调背景色;与唯一强调色同源,只用于标注当前活动步骤。 */
|
|
16
|
+
export declare const TUI_ACCENT_BG_COLOR: "ansi256(208)";
|
|
17
|
+
/** 工具徽标 chip 上的深色粗体文字色;对应 Web 的 text-ink-on-dark 槽位语义。 */
|
|
18
|
+
export declare const TUI_CHIP_TEXT_COLOR = "ansi256(16)";
|
|
19
|
+
/** 工具成功 glyph 的辅助绿;状态永远带文字标签,不靠颜色传达。 */
|
|
20
|
+
export declare const TUI_TOOL_SUCCESS_COLOR = "ansi256(46)";
|
|
21
|
+
/** 工具失败文字的辅助红;状态永远带文字标签,不靠颜色传达。 */
|
|
22
|
+
export declare const TUI_TOOL_ERROR_COLOR = "ansi256(196)";
|
|
23
|
+
/** Skill 已启用开关的语义色。 */
|
|
24
|
+
export declare const TUI_SKILL_ON_COLOR = "ansi256(46)";
|
|
25
|
+
/** Skill 未启用开关的语义色。 */
|
|
26
|
+
export declare const TUI_SKILL_OFF_COLOR = "ansi256(196)";
|
|
27
|
+
/** 动态等待区(引导/排队输入)的背景色。 */
|
|
28
|
+
export declare const TUI_WAITING_BACKGROUND_COLOR = "ansi256(236)";
|
|
29
|
+
/** unified diff 新增行的语义色。 */
|
|
30
|
+
export declare const TUI_DIFF_ADDED_COLOR = "ansi256(46)";
|
|
31
|
+
/** unified diff 删除行的语义色。 */
|
|
32
|
+
export declare const TUI_DIFF_REMOVED_COLOR = "ansi256(196)";
|
|
33
|
+
/** 输入区(prompt)的背景色。 */
|
|
34
|
+
export declare const TUI_PROMPT_BACKGROUND_COLOR = "ansi256(236)";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** CLI TUI 语义色 token;组件只引用 token,不直接写危险态颜色。 */
|
|
2
|
+
export const CLI_TUI_COLORS = {
|
|
3
|
+
accent: "ansi256(208)",
|
|
4
|
+
permissionFullAccess: "#ff6b80",
|
|
5
|
+
copilotBlue: "ansi256(33)",
|
|
6
|
+
copilotBlueBright: "ansi256(39)",
|
|
7
|
+
copilotPurple: "ansi256(99)",
|
|
8
|
+
copilotGreen: "ansi256(77)",
|
|
9
|
+
copilotInk: "ansi256(255)",
|
|
10
|
+
copilotMuted: "ansi256(246)",
|
|
11
|
+
copilotOutline: "ansi256(243)",
|
|
12
|
+
};
|
|
13
|
+
/** Design.md 中橙色强调色的 ANSI 256 语义映射;NO_COLOR 时完全不输出颜色。 */
|
|
14
|
+
export const TUI_ACCENT_COLOR = CLI_TUI_COLORS.accent;
|
|
15
|
+
/** 工具徽标 chip 的强调背景色;与唯一强调色同源,只用于标注当前活动步骤。 */
|
|
16
|
+
export const TUI_ACCENT_BG_COLOR = CLI_TUI_COLORS.accent;
|
|
17
|
+
/** 工具徽标 chip 上的深色粗体文字色;对应 Web 的 text-ink-on-dark 槽位语义。 */
|
|
18
|
+
export const TUI_CHIP_TEXT_COLOR = "ansi256(16)";
|
|
19
|
+
/** 工具成功 glyph 的辅助绿;状态永远带文字标签,不靠颜色传达。 */
|
|
20
|
+
export const TUI_TOOL_SUCCESS_COLOR = "ansi256(46)";
|
|
21
|
+
/** 工具失败文字的辅助红;状态永远带文字标签,不靠颜色传达。 */
|
|
22
|
+
export const TUI_TOOL_ERROR_COLOR = "ansi256(196)";
|
|
23
|
+
/** Skill 已启用开关的语义色。 */
|
|
24
|
+
export const TUI_SKILL_ON_COLOR = "ansi256(46)";
|
|
25
|
+
/** Skill 未启用开关的语义色。 */
|
|
26
|
+
export const TUI_SKILL_OFF_COLOR = "ansi256(196)";
|
|
27
|
+
/** 动态等待区(引导/排队输入)的背景色。 */
|
|
28
|
+
export const TUI_WAITING_BACKGROUND_COLOR = "ansi256(236)";
|
|
29
|
+
/** unified diff 新增行的语义色。 */
|
|
30
|
+
export const TUI_DIFF_ADDED_COLOR = "ansi256(46)";
|
|
31
|
+
/** unified diff 删除行的语义色。 */
|
|
32
|
+
export const TUI_DIFF_REMOVED_COLOR = "ansi256(196)";
|
|
33
|
+
/** 输入区(prompt)的背景色。 */
|
|
34
|
+
export const TUI_PROMPT_BACKGROUND_COLOR = "ansi256(236)";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** CLI 内部错误的安全分类:只返回封闭类别与稳定错误码,不读取异常正文。 */
|
|
2
|
+
export type CliErrorCategory = "config_directory" | "workspace_trust" | "agent_runtime" | "node_state" | "node_chat" | "unknown";
|
|
3
|
+
export interface CliErrorClassification {
|
|
4
|
+
category: CliErrorCategory;
|
|
5
|
+
code: string;
|
|
6
|
+
}
|
|
7
|
+
/** 只从稳定异常类型与显式码白名单分类;未知或被篡改的码会回退到通用标记。 */
|
|
8
|
+
export declare function classifyCliError(error: unknown): CliErrorClassification;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** CLI 内部错误的安全分类:只返回封闭类别与稳定错误码,不读取异常正文。 */
|
|
2
|
+
import { NodeChatError, NodeStateError } from "@autopilot-agent/adapters";
|
|
3
|
+
import { AgentRuntimeError, WorkspaceTrustError } from "@autopilot-agent/core";
|
|
4
|
+
const agentErrorCodes = new Set([
|
|
5
|
+
"SESSION_MODE_MISMATCH", "CHAT_RETRIEVAL_MODE_LOCKED", "CHAT_TOOL_FORBIDDEN", "CHAT_RAG_EVIDENCE_INSUFFICIENT",
|
|
6
|
+
"WORKSPACE_REQUIRED", "WORK_HISTORY_READ_ONLY", "SESSION_NOT_FOUND", "SESSION_TITLE_INVALID", "TURN_NOT_FOUND",
|
|
7
|
+
"TURN_NOT_RESUMABLE", "TURN_ALREADY_RUNNING", "CHECKPOINT_NOT_FOUND", "PROJECT_NOT_FOUND", "NOT_IMPLEMENTED",
|
|
8
|
+
"RUNTIME_ERROR", "INVALID_RUN_CONFIG", "PROVIDER_UNAUTHORIZED", "MODEL_NOT_FOUND", "MODEL_NOT_SUPPORTED",
|
|
9
|
+
"REASONING_EFFORT_NOT_SUPPORTED", "TURN_FROZEN_MISMATCH", "CREDENTIAL_REF_INVALID", "MISSING_CREDENTIAL",
|
|
10
|
+
"PROVIDER_PROTOCOL_REQUIRED", "UNSUPPORTED_API_PROTOCOL", "INVALID_PROTOCOL_PROFILE", "DUPLICATE_API_PROTOCOL_ADAPTER",
|
|
11
|
+
"CANONICAL_MESSAGE_INVALID", "APPROVAL_NOT_FOUND", "APPROVAL_MISMATCH", "APPROVAL_ALREADY_SUBMITTED",
|
|
12
|
+
"QUESTION_NOT_FOUND", "QUESTION_ALREADY_ANSWERED", "QUESTION_ANSWER_INVALID", "QUESTION_TURN_STATE_INVALID",
|
|
13
|
+
"JOB_NOT_FOUND", "JOB_INPUT_INVALID", "JOB_CURSOR_INVALID", "JOB_OWNER_UNAVAILABLE", "JOB_OWNER_UNKNOWN",
|
|
14
|
+
"JOB_STATE_CONFLICT", "JOB_STOP_TIMEOUT", "JOB_STOP_FAILED", "JOB_STORAGE_FAILED", "JOB_SPAWN_FAILED",
|
|
15
|
+
"JOB_STORE_CAPABILITY_UNSUPPORTED", "SESSION_HAS_ACTIVE_JOBS", "INTERACTIVE_TOOL_CONFLICT", "INBOX_INPUT_NOT_FOUND",
|
|
16
|
+
"INVALID_INPUT",
|
|
17
|
+
]);
|
|
18
|
+
const nodeStateErrorCodes = new Set([
|
|
19
|
+
"STATE_SESSION_CORRUPTED", "STATE_STORAGE_UNAVAILABLE", "STATE_STORAGE_UNSAFE_PATH", "STATE_SCHEMA_UNSAFE_PATH",
|
|
20
|
+
"STATE_SCHEMA_BUSY", "STATE_SCHEMA_RECOVERY_REQUIRED", "STATE_SCHEMA_MANIFEST_CORRUPTED", "STATE_SCHEMA_VERSION_INVALID",
|
|
21
|
+
"STATE_SCHEMA_VERSION_UNSUPPORTED", "STATE_SCHEMA_MIGRATION_MISSING", "STATE_SCHEMA_MIGRATION_FAILED",
|
|
22
|
+
]);
|
|
23
|
+
const nodeChatErrorCodes = new Set([
|
|
24
|
+
"CHAT_STATE_ROOT_INVALID", "CHAT_ATTACHMENT_UNAVAILABLE", "CHAT_ATTACHMENT_CANCELLED", "CHAT_ATTACHMENT_PARSER_UNAVAILABLE",
|
|
25
|
+
"CHAT_ATTACHMENT_CLEANUP_FAILED", "CHAT_SESSION_DELETING", "CHAT_UPLOAD_MODE_MISMATCH", "CHAT_ATTACHMENT_STORE_REQUIRED",
|
|
26
|
+
"CHAT_KNOWLEDGE_UNAVAILABLE",
|
|
27
|
+
]);
|
|
28
|
+
const configDirectoryErrorCodes = new Set([
|
|
29
|
+
"AUTOPILOT_HOME_UNRESOLVED", "AUTOPILOT_CONFIG_ROOT_NOT_DIRECTORY", "AUTOPILOT_CONFIG_ROOT_UNSAFE_LINK",
|
|
30
|
+
"AUTOPILOT_CONFIG_ROOT_UNAVAILABLE",
|
|
31
|
+
]);
|
|
32
|
+
/** 只从稳定异常类型与显式码白名单分类;未知或被篡改的码会回退到通用标记。 */
|
|
33
|
+
export function classifyCliError(error) {
|
|
34
|
+
try {
|
|
35
|
+
if (hasStableConfigDirectoryErrorCode(error))
|
|
36
|
+
return { category: "config_directory", code: error.code };
|
|
37
|
+
if (error instanceof WorkspaceTrustError)
|
|
38
|
+
return { category: "workspace_trust", code: "WORKSPACE_TRUST_REQUIRED" };
|
|
39
|
+
if (error instanceof AgentRuntimeError) {
|
|
40
|
+
const code = agentErrorCodes.has(error.code) ? error.code : "AGENT_RUNTIME_ERROR";
|
|
41
|
+
return { category: "agent_runtime", code };
|
|
42
|
+
}
|
|
43
|
+
if (error instanceof NodeStateError) {
|
|
44
|
+
const code = nodeStateErrorCodes.has(error.code) ? error.code : "NODE_STATE_ERROR";
|
|
45
|
+
return { category: "node_state", code };
|
|
46
|
+
}
|
|
47
|
+
if (error instanceof NodeChatError) {
|
|
48
|
+
const code = nodeChatErrorCodes.has(error.code) ? error.code : "NODE_CHAT_ERROR";
|
|
49
|
+
return { category: "node_chat", code };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { category: "unknown", code: "CLI_RUNTIME_ERROR" };
|
|
54
|
+
}
|
|
55
|
+
return { category: "unknown", code: "CLI_RUNTIME_ERROR" };
|
|
56
|
+
}
|
|
57
|
+
/** 只接受 CLI 已公开的配置目录错误码,不回显任意对象上的 code 字符串。 */
|
|
58
|
+
function hasStableConfigDirectoryErrorCode(error) {
|
|
59
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
60
|
+
&& typeof error.code === "string"
|
|
61
|
+
&& configDirectoryErrorCodes.has(error.code);
|
|
62
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI AgentEvent 安全投影器。
|
|
3
|
+
*
|
|
4
|
+
* 它在宿主边界补充本地 seq,并把 Core 事件归约为不包含 checkpoint、冻结
|
|
5
|
+
* 工具输入、raw reasoning 或绝对受控路径的公开 ViewModel。
|
|
6
|
+
*/
|
|
7
|
+
import { type AgentEvent, type RecoverableTurnView } from "@autopilot-agent/core";
|
|
8
|
+
import type { CliEventEnvelope, CliProjection } from "./types.js";
|
|
9
|
+
/** 创建空的 CLI 投影状态。 */
|
|
10
|
+
export declare function createInitialCliProjection(): CliProjection;
|
|
11
|
+
/** 归约单个本地事件信封;同一 turn/seq 重复信封不会再次产生公开事件。 */
|
|
12
|
+
export declare function reduceCliEvent(previous: CliProjection, envelope: CliEventEnvelope): CliProjection;
|
|
13
|
+
/** 生成本地单调 seq 的事件投影器;continue 阶段沿用同一实例,不重置 seq。 */
|
|
14
|
+
export declare function createCliEventProjector(options?: {
|
|
15
|
+
now?: () => Date;
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
}): {
|
|
18
|
+
project(event: AgentEvent): {
|
|
19
|
+
envelope: CliEventEnvelope;
|
|
20
|
+
projection: CliProjection;
|
|
21
|
+
added: boolean;
|
|
22
|
+
};
|
|
23
|
+
getProjection(): CliProjection;
|
|
24
|
+
setSessionId(sessionId: string): void;
|
|
25
|
+
restore(view: RecoverableTurnView): CliProjection;
|
|
26
|
+
};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI AgentEvent 安全投影器。
|
|
3
|
+
*
|
|
4
|
+
* 它在宿主边界补充本地 seq,并把 Core 事件归约为不包含 checkpoint、冻结
|
|
5
|
+
* 工具输入、raw reasoning 或绝对受控路径的公开 ViewModel。
|
|
6
|
+
*/
|
|
7
|
+
import { sanitizeAgentTerminationView } from "@autopilot-agent/core";
|
|
8
|
+
import { createPresentationState, getPresentationCopy, getToolPresentationProfile, reducePresentationInput } from "@autopilot-agent/presentation";
|
|
9
|
+
/** 创建空的 CLI 投影状态。 */
|
|
10
|
+
export function createInitialCliProjection() {
|
|
11
|
+
return {
|
|
12
|
+
status: "idle",
|
|
13
|
+
recoverable: false,
|
|
14
|
+
answer: "",
|
|
15
|
+
activeSkills: [],
|
|
16
|
+
events: [],
|
|
17
|
+
presentation: createPresentationState(),
|
|
18
|
+
seenKeys: new Set(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** 将内部 checkpoint 状态映射为 CLI 可见的生命周期状态。 */
|
|
22
|
+
function mapCheckpointStatus(status) {
|
|
23
|
+
if (status === "awaiting_approval")
|
|
24
|
+
return "waiting_approval";
|
|
25
|
+
if (status === "awaiting_user_input")
|
|
26
|
+
return "waiting_user_input";
|
|
27
|
+
if (status === "interrupted_recoverable")
|
|
28
|
+
return "interrupted_recoverable";
|
|
29
|
+
if (status === "interrupted")
|
|
30
|
+
return "interrupted";
|
|
31
|
+
if (status === "limit_reached")
|
|
32
|
+
return "limit_reached";
|
|
33
|
+
if (status === "completed")
|
|
34
|
+
return "completed";
|
|
35
|
+
if (status === "failed")
|
|
36
|
+
return "failed";
|
|
37
|
+
return "running";
|
|
38
|
+
}
|
|
39
|
+
/** 对 CLI 可见的文本做长度、路径和常见凭据字段脱敏。 */
|
|
40
|
+
function sanitizeText(value, maxLength = 4_000) {
|
|
41
|
+
const text = String(value ?? "").slice(0, maxLength);
|
|
42
|
+
return text
|
|
43
|
+
.replace(/(?:[A-Za-z]:[\\/]|\\\\|\/(?:Users|home|private|tmp|var|mnt|workspace|Volumes)\/)[^\s"'`]+/gu, "[path]")
|
|
44
|
+
.replace(/\b(api[_-]?key|access[_-]?token|password|secret|dsn)\s*[:=]\s*[^\s,;]+/giu, function redactSensitiveField(_match, key) {
|
|
45
|
+
return `${key}=[redacted]`;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** 只保留模型/工具边界允许公开的稳定错误码。 */
|
|
49
|
+
function safeErrorCode(value) {
|
|
50
|
+
const text = String(value ?? "");
|
|
51
|
+
const knownCodes = [
|
|
52
|
+
"MODEL_OUTPUT_LIMIT",
|
|
53
|
+
"EMPTY_RESPONSE",
|
|
54
|
+
"STREAM_INCOMPLETE",
|
|
55
|
+
"TOOL_CALL_INVALID_JSON",
|
|
56
|
+
"TOOL_CALL_TRUNCATED",
|
|
57
|
+
"PROVIDER_UNAUTHORIZED",
|
|
58
|
+
"MODEL_NOT_FOUND",
|
|
59
|
+
"RUNTIME_ERROR",
|
|
60
|
+
"COMMAND_FAILED",
|
|
61
|
+
"TOOL_TIMEOUT",
|
|
62
|
+
];
|
|
63
|
+
return knownCodes.find(function findKnownCode(code) { return text.includes(code); });
|
|
64
|
+
}
|
|
65
|
+
/** 仅透传 Core 固定的安全终态原因,避免暴露 Provider 原文、路径或凭据。 */
|
|
66
|
+
function safeTurnFailureReason(value) {
|
|
67
|
+
const text = String(value ?? "");
|
|
68
|
+
const safeReasons = [
|
|
69
|
+
"修改任务未完成写后验证,不能标记为完成",
|
|
70
|
+
"修改任务未完成真实写入,不能标记为完成",
|
|
71
|
+
"模型连续返回空响应,未执行工具或产生可显示答复",
|
|
72
|
+
"供应商连接持续失败,已停止自动恢复",
|
|
73
|
+
"供应商请求持续超时,已停止自动恢复",
|
|
74
|
+
"供应商请求持续被限流,已停止自动恢复",
|
|
75
|
+
"供应商服务持续不可用,已停止自动恢复",
|
|
76
|
+
"供应商流式响应持续中断,已停止自动恢复",
|
|
77
|
+
"模型返回非法工具参数 JSON,格式修复后仍失败",
|
|
78
|
+
"模型输出连续截断,已达到最大恢复次数",
|
|
79
|
+
"上下文溢出后已完成一次压缩重试,compact 熔断器已阻止重复请求",
|
|
80
|
+
"reactive compact 失败,已熔断以避免重复请求",
|
|
81
|
+
"Agent Loop 执行失败",
|
|
82
|
+
];
|
|
83
|
+
return safeReasons.find(function matchSafeReason(reason) { return text === reason; });
|
|
84
|
+
}
|
|
85
|
+
/** 复制投影状态,确保 reducer 不修改调用方持有的旧快照。 */
|
|
86
|
+
function cloneProjection(previous, envelope) {
|
|
87
|
+
const seenKeys = new Set(previous.seenKeys);
|
|
88
|
+
seenKeys.add(`${envelope.turnId}:${envelope.seq}`);
|
|
89
|
+
return {
|
|
90
|
+
...previous,
|
|
91
|
+
turnId: envelope.turnId,
|
|
92
|
+
lastSeq: Math.max(previous.lastSeq ?? 0, envelope.seq),
|
|
93
|
+
activeSkills: previous.activeSkills.map(function copySkill(skill) { return { ...skill }; }),
|
|
94
|
+
events: [...previous.events],
|
|
95
|
+
seenKeys,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** 比较两份 activeSkills 身份,避免 checkpoint 事件重复渲染技能记录。 */
|
|
99
|
+
function sameSkills(first, second) {
|
|
100
|
+
return first.length === second.length && first.every(function matchSkill(skill, index) {
|
|
101
|
+
const other = second[index];
|
|
102
|
+
return other?.skillId === skill.skillId && other.contentHash === skill.contentHash;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** 从 checkpoint 提取只包含 Skill 身份的安全投影。 */
|
|
106
|
+
function applyCheckpoint(projection, checkpoint, seq, turnId) {
|
|
107
|
+
projection.status = mapCheckpointStatus(checkpoint.status);
|
|
108
|
+
if (checkpoint.status !== "awaiting_approval")
|
|
109
|
+
projection.pendingApproval = undefined;
|
|
110
|
+
const activeSkills = (checkpoint.activeSkills ?? []).map(function copySkill(skill) {
|
|
111
|
+
return { skillId: skill.skillId, contentHash: skill.contentHash };
|
|
112
|
+
});
|
|
113
|
+
if (sameSkills(projection.activeSkills, activeSkills))
|
|
114
|
+
return;
|
|
115
|
+
projection.activeSkills = activeSkills;
|
|
116
|
+
projection.events.push({ type: "active_skills", seq, turnId, skills: activeSkills });
|
|
117
|
+
}
|
|
118
|
+
/** 将安全事件追加到投影事件列表。 */
|
|
119
|
+
function appendEvent(projection, event) {
|
|
120
|
+
projection.events.push(event);
|
|
121
|
+
}
|
|
122
|
+
/** 将一个 Core AgentEvent 归约为兼容 Plain/JSONL 的安全 CLI 公开事件。 */
|
|
123
|
+
function projectEvent(envelope, projection) {
|
|
124
|
+
const event = envelope.event;
|
|
125
|
+
const base = { seq: envelope.seq, turnId: envelope.turnId };
|
|
126
|
+
switch (event.type) {
|
|
127
|
+
case "turn_started":
|
|
128
|
+
projection.status = "running";
|
|
129
|
+
projection.recoverable = false;
|
|
130
|
+
return { ...base, type: "turn_started", stepCount: event.stepCount };
|
|
131
|
+
case "reasoning_summary":
|
|
132
|
+
return { ...base, type: "reasoning_summary", summary: sanitizeText(event.summary) };
|
|
133
|
+
case "reasoning_delta":
|
|
134
|
+
return { ...base, type: "reasoning_hidden" };
|
|
135
|
+
case "answer_delta":
|
|
136
|
+
{
|
|
137
|
+
const delta = sanitizeText(event.delta);
|
|
138
|
+
projection.answer += delta;
|
|
139
|
+
return { ...base, type: "answer_delta", delta };
|
|
140
|
+
}
|
|
141
|
+
case "answer_reset":
|
|
142
|
+
case "answer_discard":
|
|
143
|
+
projection.answer = "";
|
|
144
|
+
return { ...base, type: "answer_reset" };
|
|
145
|
+
case "tool_started":
|
|
146
|
+
{
|
|
147
|
+
const profile = getToolPresentationProfile(event.toolName);
|
|
148
|
+
return {
|
|
149
|
+
...base,
|
|
150
|
+
type: "tool_started",
|
|
151
|
+
callId: event.callId,
|
|
152
|
+
toolName: sanitizeText(event.toolName, 160),
|
|
153
|
+
summary: getPresentationCopy(profile.runningTitleKey),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
case "tool_progress":
|
|
157
|
+
return { ...base, type: "tool_progress", callId: event.callId, toolName: sanitizeText(event.toolName, 160), tail: sanitizeText(event.tail, 160) };
|
|
158
|
+
case "tool_result":
|
|
159
|
+
{
|
|
160
|
+
const result = event.result;
|
|
161
|
+
const errorCode = "error" in result && result.error ? safeErrorCode(result.error.code) : undefined;
|
|
162
|
+
const profile = getToolPresentationProfile(event.toolName);
|
|
163
|
+
// 展示事实由 Core 在 schema 后生成;不回读旧 event.summary/diff 或 ToolExecutionResult.output。
|
|
164
|
+
const display = result.display;
|
|
165
|
+
const diff = display?.diff?.text;
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
type: "tool_result",
|
|
169
|
+
callId: event.callId,
|
|
170
|
+
toolName: sanitizeText(event.toolName, 160),
|
|
171
|
+
status: result.status,
|
|
172
|
+
summary: display?.summary ?? getPresentationCopy(profile.completedTitleKey),
|
|
173
|
+
errorCode,
|
|
174
|
+
diff,
|
|
175
|
+
display,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
case "approval_required":
|
|
179
|
+
{
|
|
180
|
+
const display = event.result.approval.display;
|
|
181
|
+
const reason = display?.actionPreview ?? display?.operation ?? "需要确认";
|
|
182
|
+
const allowedDecisions = display?.allowedDecisions ?? ["deny"];
|
|
183
|
+
projection.status = "waiting_approval";
|
|
184
|
+
projection.pendingApproval = {
|
|
185
|
+
approvalId: event.result.approval.approvalId,
|
|
186
|
+
callId: event.callId,
|
|
187
|
+
toolName: sanitizeText(event.toolName, 160),
|
|
188
|
+
reason,
|
|
189
|
+
risk: display?.risk,
|
|
190
|
+
allowedDecisions,
|
|
191
|
+
display,
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
...base,
|
|
195
|
+
type: "approval_required",
|
|
196
|
+
approvalId: event.result.approval.approvalId,
|
|
197
|
+
callId: event.callId,
|
|
198
|
+
toolName: sanitizeText(event.toolName, 160),
|
|
199
|
+
reason,
|
|
200
|
+
risk: display?.risk,
|
|
201
|
+
allowedDecisions,
|
|
202
|
+
display,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
case "input_queued":
|
|
206
|
+
return { ...base, type: "input_queued", inputId: event.inputId, delivery: event.delivery };
|
|
207
|
+
case "todo_updated":
|
|
208
|
+
// Todo 字段已由 Core schema 限长并拒绝路径/凭据片段;宿主只做事件转发,不解析 raw Tool args。
|
|
209
|
+
return { ...base, type: "todo_updated", todos: event.todos, counts: event.counts };
|
|
210
|
+
case "input_joined":
|
|
211
|
+
case "input_consumed":
|
|
212
|
+
applyCheckpoint(projection, event.checkpoint, envelope.seq, envelope.turnId);
|
|
213
|
+
return { ...base, type: event.type, inputId: event.inputId };
|
|
214
|
+
case "model_recovery":
|
|
215
|
+
return {
|
|
216
|
+
...base,
|
|
217
|
+
type: "model_recovery",
|
|
218
|
+
reason: safeErrorCode(event.reason) ?? "MODEL_RECOVERY",
|
|
219
|
+
attempt: event.attempt,
|
|
220
|
+
maxAttempts: event.maxAttempts,
|
|
221
|
+
};
|
|
222
|
+
case "context_overflow":
|
|
223
|
+
applyCheckpoint(projection, event.checkpoint, envelope.seq, envelope.turnId);
|
|
224
|
+
return { ...base, type: "context_overflow" };
|
|
225
|
+
case "context_compact_started":
|
|
226
|
+
return { ...base, type: "context_compact_started" };
|
|
227
|
+
case "context_compacted":
|
|
228
|
+
return { ...base, type: "context_compacted", preTokens: event.preTokens, postTokens: event.postTokens };
|
|
229
|
+
case "context_compact_failed":
|
|
230
|
+
return { ...base, type: "context_compact_failed" };
|
|
231
|
+
case "context_compact_circuit_open":
|
|
232
|
+
return { ...base, type: "context_compact_circuit_open" };
|
|
233
|
+
case "context_usage_updated":
|
|
234
|
+
// Core 出口已对 usage 做白名单投影;它只刷新状态栏事实,不进入 transcript 时间线。
|
|
235
|
+
projection.contextUsage = event.usage;
|
|
236
|
+
return { ...base, type: "context_usage", usage: event.usage };
|
|
237
|
+
case "turn_paused":
|
|
238
|
+
case "turn_completed":
|
|
239
|
+
case "turn_interrupted":
|
|
240
|
+
case "turn_failed":
|
|
241
|
+
case "turn_limit_reached":
|
|
242
|
+
applyCheckpoint(projection, event.checkpoint, envelope.seq, envelope.turnId);
|
|
243
|
+
{
|
|
244
|
+
const termination = sanitizeAgentTerminationView(event.termination);
|
|
245
|
+
projection.recoverable = termination?.recoverable === true;
|
|
246
|
+
}
|
|
247
|
+
if (event.type === "turn_failed") {
|
|
248
|
+
const failedStatus = projection.status === "idle" ? "failed" : projection.status;
|
|
249
|
+
const termination = sanitizeAgentTerminationView(event.termination);
|
|
250
|
+
return {
|
|
251
|
+
...base,
|
|
252
|
+
type: "status",
|
|
253
|
+
status: failedStatus,
|
|
254
|
+
code: safeErrorCode(event.error) ?? "RUNTIME_ERROR",
|
|
255
|
+
reason: termination?.reason ? undefined : safeTurnFailureReason(event.error),
|
|
256
|
+
...(termination ? { termination } : {}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const status = projection.status === "idle" ? "running" : projection.status;
|
|
260
|
+
const termination = sanitizeAgentTerminationView(event.termination);
|
|
261
|
+
return { ...base, type: "status", status, ...(termination ? { termination } : {}) };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** 归约单个本地事件信封;同一 turn/seq 重复信封不会再次产生公开事件。 */
|
|
265
|
+
export function reduceCliEvent(previous, envelope) {
|
|
266
|
+
const key = `${envelope.turnId}:${envelope.seq}`;
|
|
267
|
+
if (previous.seenKeys.has(key))
|
|
268
|
+
return previous;
|
|
269
|
+
const next = cloneProjection(previous, envelope);
|
|
270
|
+
next.presentation = reducePresentationInput(previous.presentation, {
|
|
271
|
+
kind: "runtime_event",
|
|
272
|
+
envelope: {
|
|
273
|
+
seq: envelope.seq,
|
|
274
|
+
sessionId: envelope.sessionId ?? "cli-local-session",
|
|
275
|
+
turnId: envelope.turnId,
|
|
276
|
+
timestamp: envelope.timestamp,
|
|
277
|
+
event: envelope.event,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
const projected = projectEvent(envelope, next);
|
|
281
|
+
if (projected)
|
|
282
|
+
appendEvent(next, projected);
|
|
283
|
+
return next;
|
|
284
|
+
}
|
|
285
|
+
/** 生成本地单调 seq 的事件投影器;continue 阶段沿用同一实例,不重置 seq。 */
|
|
286
|
+
export function createCliEventProjector(options = {}) {
|
|
287
|
+
const now = options.now ?? function currentTime() { return new Date(); };
|
|
288
|
+
const nextSeqByTurn = new Map();
|
|
289
|
+
let sessionId = options.sessionId;
|
|
290
|
+
let projection = createInitialCliProjection();
|
|
291
|
+
return {
|
|
292
|
+
project(event) {
|
|
293
|
+
const currentSeq = nextSeqByTurn.get(event.turnId) ?? 0;
|
|
294
|
+
const envelope = {
|
|
295
|
+
seq: currentSeq + 1,
|
|
296
|
+
sessionId,
|
|
297
|
+
turnId: event.turnId,
|
|
298
|
+
timestamp: now().toISOString(),
|
|
299
|
+
event,
|
|
300
|
+
};
|
|
301
|
+
nextSeqByTurn.set(event.turnId, envelope.seq);
|
|
302
|
+
const previousLength = projection.events.length;
|
|
303
|
+
projection = reduceCliEvent(projection, envelope);
|
|
304
|
+
return { envelope, projection, added: projection.events.length > previousLength };
|
|
305
|
+
},
|
|
306
|
+
getProjection() {
|
|
307
|
+
return projection;
|
|
308
|
+
},
|
|
309
|
+
setSessionId(nextSessionId) {
|
|
310
|
+
sessionId = nextSessionId;
|
|
311
|
+
},
|
|
312
|
+
restore(view) {
|
|
313
|
+
const restored = createInitialCliProjection();
|
|
314
|
+
restored.turnId = view.turnId;
|
|
315
|
+
restored.lastSeq = view.lastSeq;
|
|
316
|
+
restored.status = view.status === "awaiting_approval"
|
|
317
|
+
? "waiting_approval"
|
|
318
|
+
: view.status === "awaiting_user_input"
|
|
319
|
+
? "waiting_user_input"
|
|
320
|
+
: view.status;
|
|
321
|
+
restored.recoverable = view.recoverable;
|
|
322
|
+
if (view.termination) {
|
|
323
|
+
restored.events.push({ type: "status", seq: view.lastSeq ?? 0, turnId: view.turnId, status: restored.status, termination: sanitizeAgentTerminationView(view.termination) });
|
|
324
|
+
}
|
|
325
|
+
restored.activeSkills = view.activeSkills.map(function copySkill(skill) { return { ...skill }; });
|
|
326
|
+
if (view.approval)
|
|
327
|
+
restored.pendingApproval = { ...view.approval };
|
|
328
|
+
projection = restored;
|
|
329
|
+
nextSeqByTurn.set(view.turnId, view.lastSeq ?? 0);
|
|
330
|
+
return projection;
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 提交侧 @文件 mention 解析:从 prompt 原文提取 @ 引用,校验工作区边界后读取文件或
|
|
3
|
+
* 列目录一层,把有界内容以 `<system-reminder>` 附加块按出现顺序拼回 content;
|
|
4
|
+
* 失败路径(未找到/越界/过大/读取失败)降级为 notice,不阻断提交。
|
|
5
|
+
*/
|
|
6
|
+
/** resolveFileMentionContext 依赖的最小文件系统接缝;测试可注入受控实现。 */
|
|
7
|
+
export interface FileMentionFs {
|
|
8
|
+
/** 返回目标的存在性形态与字节大小;目标不存在时抛错。 */
|
|
9
|
+
stat(absolutePath: string): Promise<{
|
|
10
|
+
isFile: boolean;
|
|
11
|
+
isDirectory: boolean;
|
|
12
|
+
size: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** 以 UTF-8 读取目标文件全文。 */
|
|
15
|
+
readFile(absolutePath: string): Promise<string>;
|
|
16
|
+
/** 列出目录一层条目(不递归)。 */
|
|
17
|
+
listDirectory(absolutePath: string): Promise<Array<{
|
|
18
|
+
name: string;
|
|
19
|
+
isDirectory: boolean;
|
|
20
|
+
}>>;
|
|
21
|
+
}
|
|
22
|
+
/** resolveFileMentionContext 的返回契约;无有效 @ 引用时 content 保持原文。 */
|
|
23
|
+
export interface ResolvedFileMentionContext {
|
|
24
|
+
/** 增强后的 prompt:原文 + `<system-reminder>` 附加块(至少一个 @ 引用成功时)。 */
|
|
25
|
+
content: string;
|
|
26
|
+
/** 面向用户的失败提示,按 mention 出现顺序。 */
|
|
27
|
+
notices: string[];
|
|
28
|
+
}
|
|
29
|
+
/** 从 prompt 原文提取 @ 引用的相对路径;`@` 必须位于行首或空白后,去重保序,`\` 归一为 `/`。 */
|
|
30
|
+
export declare function extractFileMentions(content: string): string[];
|
|
31
|
+
/** 解析 prompt 中的全部 @ 引用并构造增强 content;至少一个引用成功时在原文后追加附加块。 */
|
|
32
|
+
export declare function resolveFileMentionContext(content: string, workspaceRoot: string, options?: {
|
|
33
|
+
fs?: FileMentionFs;
|
|
34
|
+
}): Promise<ResolvedFileMentionContext>;
|