@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
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Composition Root。
|
|
3
|
+
*
|
|
4
|
+
* 负责选择 Core 默认 Runtime、共享 Node Adapter、canonical workspaceRoot、
|
|
5
|
+
* trust policy 和显式 stateRoot;不负责命令解析、事件投影或 Runtime 状态机。
|
|
6
|
+
*/
|
|
7
|
+
import { WEB_SEARCH_DATA_FLOW_NOTICE } from "@autopilot-agent/presentation";
|
|
8
|
+
import { createDefaultModelSamplerFactory, createRuntimeBootstrapService, createSafeBootstrapConfig, WorkspaceTrustError, getMcpServerGrantConstraint, } from "@autopilot-agent/core";
|
|
9
|
+
import { createNodeCredentialStore, createNodeCapabilityLoader, createNodeHostExecutionSandboxPort, createNodeSandboxPort, createNodeSettingsLoader, createNodeSettingsWriter, ensureUserConfigDirectory, createNodeRuntimePorts, createNodeTrustPolicy, createNodeWorkspaceRegistry, createNodeWorkspaceAdapter, createNodeWorkspaceId, getNodeStateCorruptionDiagnostics, getNodeStatePaths, getNodeStateSchemaDiagnostic, initializeNodeStateRoot, resolveNodeSettingsPaths, resolveNodeStateRoot, assertNodeStateSchema, } from "@autopilot-agent/adapters";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
/** CLI 专用模型行为补充:无结构化 answerer 时以普通正文完成最少必要澄清。 */
|
|
13
|
+
const CLI_TEXT_CLARIFICATION_INSTRUCTIONS = `当前宿主是 CLI,未提供 ask_user_question 或其他结构化用户回答能力。仅在完成任务确实缺少方向性信息、且不能从当前用户输入、会话历史或工作区安全获得时,才在普通助手正文中用最少必要的简短问题向用户澄清;提出问题后正常结束当前 Turn,等待用户下一条 prompt 作为同一 Session 的新 Turn 继续。不要臆造或调用 ask_user_question,不要把澄清伪装成审批,也不要在等待澄清时猜测并执行写入或命令工具。用户下一条 prompt 与任何 Other/custom 文本都不授予 Trust、权限或审批;后续工具调用仍必须遵守既有 Policy 与 Approval 约束。Agent 自身的临时脚本、截图裁剪和中间文件必须写入项目根目录的 .autopilot/tmp,不得写入 node_modules 或随意污染业务目录;用户明确指定的目标路径除外。`;
|
|
14
|
+
/** 创建项目级 Agent 运行目录;拒绝链接/文件替代目录,避免临时文件逃逸工作区。 */
|
|
15
|
+
function ensureProjectAutopilotDirectory(workspaceRoot) {
|
|
16
|
+
try {
|
|
17
|
+
const projectRoot = fs.realpathSync.native(path.resolve(workspaceRoot));
|
|
18
|
+
const autopilotRoot = path.join(projectRoot, ".autopilot");
|
|
19
|
+
const temporaryRoot = path.join(autopilotRoot, "tmp");
|
|
20
|
+
for (const directory of [autopilotRoot, temporaryRoot]) {
|
|
21
|
+
let entry;
|
|
22
|
+
try {
|
|
23
|
+
entry = fs.lstatSync(directory);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== "ENOENT")
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
if (entry?.isSymbolicLink() || entry && !entry.isDirectory())
|
|
30
|
+
throw new Error("unsafe project autopilot directory");
|
|
31
|
+
if (!entry)
|
|
32
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
33
|
+
const canonicalDirectory = fs.realpathSync.native(directory);
|
|
34
|
+
const relative = path.relative(projectRoot, canonicalDirectory);
|
|
35
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
|
|
36
|
+
throw new Error("project autopilot directory escaped workspace");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new Error("AUTOPILOT_PROJECT_DIRECTORY_UNAVAILABLE");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** 删除外部自定义 loader 中的 RAG 字段,防止 CLI 透传或触发 loadRag。 */
|
|
44
|
+
function omitCliRagCapabilityLoader(loader) {
|
|
45
|
+
const safeLoader = { ...loader };
|
|
46
|
+
delete safeLoader.loadRag;
|
|
47
|
+
return safeLoader;
|
|
48
|
+
}
|
|
49
|
+
/** 为任意 CLI Provider 工厂叠加宿主澄清指令,保留原采样和完成策略。 */
|
|
50
|
+
function withCliTextClarificationBehavior(factory) {
|
|
51
|
+
return {
|
|
52
|
+
create(input) {
|
|
53
|
+
const sampler = factory.create(input);
|
|
54
|
+
const profile = sampler.behaviorProfile;
|
|
55
|
+
return {
|
|
56
|
+
...sampler,
|
|
57
|
+
behaviorProfile: {
|
|
58
|
+
id: `${profile?.id ?? "cli"}-text-clarification`,
|
|
59
|
+
instructions: [profile?.instructions, CLI_TEXT_CLARIFICATION_INSTRUCTIONS].filter(Boolean).join("\n\n"),
|
|
60
|
+
completionPolicy: profile?.completionPolicy,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
getSupportedProviders() {
|
|
65
|
+
return factory.getSupportedProviders();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** 创建 CLI Composition Root;只有 Runtime bootstrap 的 trust 成功阶段才初始化状态 manifest。 */
|
|
70
|
+
export function createCliCompositionRoot(options = {}) {
|
|
71
|
+
const env = options.env ?? process.env;
|
|
72
|
+
// CLI 工作区候选优先级:显式 --workspace-root → AGENT_WORKSPACE_ROOT → 启动 cwd。
|
|
73
|
+
// 候选只在 CLI 层解析;Node WorkspaceAdapter 负责 canonicalize,不再向上探测 monorepo 根。
|
|
74
|
+
const workspaceCandidate = options.workspaceRoot ?? env.AGENT_WORKSPACE_ROOT ?? options.cwd ?? process.cwd();
|
|
75
|
+
const config = options.config ?? createSafeBootstrapConfig({
|
|
76
|
+
host: "cli",
|
|
77
|
+
workspaceRoot: workspaceCandidate,
|
|
78
|
+
});
|
|
79
|
+
const stateRoot = resolveNodeStateRoot({ stateRoot: options.stateRoot, workspaceRoot: workspaceCandidate, cwd: options.cwd });
|
|
80
|
+
const workspaceAdapter = createNodeWorkspaceAdapter({
|
|
81
|
+
workspaceRoot: config.workspaceRoot ?? workspaceCandidate,
|
|
82
|
+
cwd: options.cwd,
|
|
83
|
+
env,
|
|
84
|
+
});
|
|
85
|
+
const trustPolicy = options.trustPolicy ?? createNodeTrustPolicy({
|
|
86
|
+
stateRoot,
|
|
87
|
+
trustRoot: options.trustRoot,
|
|
88
|
+
});
|
|
89
|
+
const settingsLoader = options.settingsLoader ?? createNodeSettingsLoader();
|
|
90
|
+
const nodeCapabilityLoader = createNodeCapabilityLoader({
|
|
91
|
+
env,
|
|
92
|
+
stateRoot,
|
|
93
|
+
settingsLoader,
|
|
94
|
+
webSearchFetch: options.webSearchFetch,
|
|
95
|
+
webSearchConnectivityFetch: options.connectivityFetch,
|
|
96
|
+
});
|
|
97
|
+
const settingsWriter = createNodeSettingsWriter();
|
|
98
|
+
const workspaceRegistry = createNodeWorkspaceRegistry({ cwd: options.cwd });
|
|
99
|
+
let resolvedSettings;
|
|
100
|
+
let userSettingsPath;
|
|
101
|
+
let stopSettingsWatch;
|
|
102
|
+
/** 最近一次 createRuntime 的 canonical workspaceRoot;tempRoot 注入以其为准。 */
|
|
103
|
+
let activeWorkspaceRoot;
|
|
104
|
+
/** 返回当前 settings 选中模型对应的 route;firstParty 使用 CC 主配置。 */
|
|
105
|
+
function getSelectedProviderSettings(settings) {
|
|
106
|
+
const selectedRoute = settings.modelRef && settings.modelRef.provider !== "firstParty"
|
|
107
|
+
? settings.providerRoutes[settings.modelRef.provider]
|
|
108
|
+
: undefined;
|
|
109
|
+
if (selectedRoute) {
|
|
110
|
+
return {
|
|
111
|
+
apiProtocol: selectedRoute.apiProtocol,
|
|
112
|
+
protocolProfile: selectedRoute.protocolProfile,
|
|
113
|
+
baseUrl: selectedRoute.baseUrl,
|
|
114
|
+
credentialRef: selectedRoute.credentialRef,
|
|
115
|
+
maxOutputTokens: settings.firstParty.maxOutputTokens,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (settings.modelRef && settings.modelRef.provider !== "firstParty")
|
|
119
|
+
return undefined;
|
|
120
|
+
return {
|
|
121
|
+
apiProtocol: "anthropic-messages",
|
|
122
|
+
baseUrl: settings.firstParty.baseUrl,
|
|
123
|
+
credentialRef: settings.firstParty.credentialRef,
|
|
124
|
+
maxOutputTokens: settings.firstParty.maxOutputTokens,
|
|
125
|
+
compactThresholdTokens: settings.firstParty.compactWindow,
|
|
126
|
+
timeoutMs: settings.firstParty.timeoutMs,
|
|
127
|
+
subagentModelId: settings.firstParty.subagentModelId,
|
|
128
|
+
thinkingEnabled: settings.firstParty.thinkingEnabled,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** trust 后读取并监听 settings;每次回调只替换下一 Turn 的默认快照。 */
|
|
132
|
+
function activateSettings(workspaceRoot) {
|
|
133
|
+
const loaded = settingsLoader.load(workspaceRoot);
|
|
134
|
+
resolvedSettings = loaded.settings;
|
|
135
|
+
userSettingsPath = loaded.paths.user;
|
|
136
|
+
stopSettingsWatch?.();
|
|
137
|
+
stopSettingsWatch = settingsLoader.watch(workspaceRoot, function replaceSettings(next) {
|
|
138
|
+
resolvedSettings = next.settings;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** 只检查 Agent 主模型最小配置;不读取或显示任何真实 Key。 */
|
|
142
|
+
function getStartupConfigurationError() {
|
|
143
|
+
const settingsPath = userSettingsPath ?? resolveNodeSettingsPaths(config.workspaceRoot ?? workspaceCandidate).user;
|
|
144
|
+
const selectedProvider = resolvedSettings ? getSelectedProviderSettings(resolvedSettings) : undefined;
|
|
145
|
+
const hasPrimaryCredential = Boolean(selectedProvider?.credentialRef);
|
|
146
|
+
if (resolvedSettings?.modelRef && hasPrimaryCredential)
|
|
147
|
+
return undefined;
|
|
148
|
+
const missing = [];
|
|
149
|
+
if (!resolvedSettings?.modelRef)
|
|
150
|
+
missing.push("model or env.ANTHROPIC_MODEL");
|
|
151
|
+
if (!hasPrimaryCredential) {
|
|
152
|
+
missing.push(selectedProvider ? "selected provider credential" : "selected provider route and credential");
|
|
153
|
+
}
|
|
154
|
+
return `AUTOPILOT_CONFIGURATION_REQUIRED: ${settingsPath}; missing ${missing.join(", ")}`;
|
|
155
|
+
}
|
|
156
|
+
/** 从当前 CC settings 生成可切换模型目录;不按模型名猜测供应商能力。 */
|
|
157
|
+
function getModelCapabilities() {
|
|
158
|
+
if (!resolvedSettings)
|
|
159
|
+
return [];
|
|
160
|
+
const capabilities = [];
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
const firstPartyEfforts = ["none", "low", "medium", "high", "max"];
|
|
163
|
+
function getRouteRequestableEfforts(route, modelId) {
|
|
164
|
+
const model = route.models.find(function findModel(candidate) { return candidate.id === modelId; });
|
|
165
|
+
const profile = model?.protocolProfile ?? route.protocolProfile;
|
|
166
|
+
return route.apiProtocol === "openai-chat-completions"
|
|
167
|
+
&& (profile?.apiProtocol !== "openai-chat-completions" || !profile.supportsReasoningContent) ? ["none"] : firstPartyEfforts;
|
|
168
|
+
}
|
|
169
|
+
function addCapability(provider, modelId, requestableEfforts) {
|
|
170
|
+
const key = `${provider}/${modelId}`;
|
|
171
|
+
if (!modelId || seen.has(key))
|
|
172
|
+
return;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
capabilities.push({ provider, modelId, requestableEfforts });
|
|
175
|
+
}
|
|
176
|
+
// 开发态 CLI 可能仍链接上一轮 Core 产物;回退 availableModels 以保持 settings 热更新兼容。
|
|
177
|
+
const configuredModels = resolvedSettings.modelCandidates.length > 0
|
|
178
|
+
? resolvedSettings.modelCandidates
|
|
179
|
+
: resolvedSettings.availableModels;
|
|
180
|
+
for (const candidate of configuredModels) {
|
|
181
|
+
const separator = candidate.indexOf("/");
|
|
182
|
+
if (separator > 0 && separator < candidate.length - 1) {
|
|
183
|
+
const provider = candidate.slice(0, separator);
|
|
184
|
+
const modelId = candidate.slice(separator + 1);
|
|
185
|
+
const route = resolvedSettings.providerRoutes[provider];
|
|
186
|
+
if (route?.models.some(function hasModel(model) { return model.id === modelId; }))
|
|
187
|
+
addCapability(provider, modelId, getRouteRequestableEfforts(route, modelId));
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
addCapability("firstParty", candidate, firstPartyEfforts);
|
|
191
|
+
}
|
|
192
|
+
// Route 模型目录不能因 availableModels 是否声明而丢失,始终与 firstParty 目录合并。
|
|
193
|
+
for (const [provider, route] of Object.entries(resolvedSettings.providerRoutes)) {
|
|
194
|
+
for (const model of route.models)
|
|
195
|
+
addCapability(provider, model.id, getRouteRequestableEfforts(route, model.id));
|
|
196
|
+
}
|
|
197
|
+
if (resolvedSettings.modelRef) {
|
|
198
|
+
const provider = resolvedSettings.modelRef.provider;
|
|
199
|
+
const modelId = resolvedSettings.modelRef.modelId;
|
|
200
|
+
const route = resolvedSettings.providerRoutes[provider];
|
|
201
|
+
if (provider === "firstParty")
|
|
202
|
+
addCapability(provider, modelId, firstPartyEfforts);
|
|
203
|
+
else if (route?.models.some(function hasModel(model) { return model.id === modelId; }))
|
|
204
|
+
addCapability(provider, modelId, getRouteRequestableEfforts(route, modelId));
|
|
205
|
+
}
|
|
206
|
+
return capabilities;
|
|
207
|
+
}
|
|
208
|
+
/** 检查一个已存在状态根的可读写性,Doctor 不创建缺失目录。 */
|
|
209
|
+
function getExistingStateRootAccess() {
|
|
210
|
+
const statePaths = getNodeStatePaths(stateRoot);
|
|
211
|
+
if (!fs.existsSync(statePaths.root))
|
|
212
|
+
return "WARN";
|
|
213
|
+
try {
|
|
214
|
+
fs.accessSync(statePaths.root, fs.constants.R_OK | fs.constants.W_OK);
|
|
215
|
+
return "PASS";
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return "FAIL";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** 惰性创建的沙箱查询端口;绑定当前 canonical workspaceRoot,同一 root 复用实例。 */
|
|
222
|
+
let sandboxPort;
|
|
223
|
+
let sandboxPortRoot;
|
|
224
|
+
function getSandboxPort(workspaceRoot) {
|
|
225
|
+
// Talk-Plan1022 S1:Windows 宿主执行模式——status/setup/doctor 只报告停用事实,
|
|
226
|
+
// 不加载 SRT、不触发 UAC provision;macOS 装配保持不变。
|
|
227
|
+
if (process.platform === "win32") {
|
|
228
|
+
if (!sandboxPort || sandboxPortRoot !== workspaceRoot) {
|
|
229
|
+
sandboxPort = createNodeHostExecutionSandboxPort();
|
|
230
|
+
sandboxPortRoot = workspaceRoot;
|
|
231
|
+
}
|
|
232
|
+
return sandboxPort;
|
|
233
|
+
}
|
|
234
|
+
if (!sandboxPort || sandboxPortRoot !== workspaceRoot) {
|
|
235
|
+
sandboxPort = createNodeSandboxPort({
|
|
236
|
+
workspaceRoot,
|
|
237
|
+
stateRoot,
|
|
238
|
+
tempRoot: path.join(workspaceRoot, ".autopilot", "tmp"),
|
|
239
|
+
});
|
|
240
|
+
sandboxPortRoot = workspaceRoot;
|
|
241
|
+
}
|
|
242
|
+
return sandboxPort;
|
|
243
|
+
}
|
|
244
|
+
/** 显式 Doctor 连通性探测仅验证端点可达性,不发送凭据、模型或请求正文。 */
|
|
245
|
+
async function checkConnectivity(workspaceRoot) {
|
|
246
|
+
activateSettings(workspaceRoot);
|
|
247
|
+
const selectedProvider = resolvedSettings ? getSelectedProviderSettings(resolvedSettings) : undefined;
|
|
248
|
+
if (!resolvedSettings?.modelRef || !selectedProvider?.credentialRef || !selectedProvider.baseUrl && resolvedSettings.modelRef.provider !== "firstParty") {
|
|
249
|
+
return { name: "provider_connectivity", status: "WARN", detail: "provider configuration incomplete" };
|
|
250
|
+
}
|
|
251
|
+
const controller = new AbortController();
|
|
252
|
+
const timeout = setTimeout(function abortConnectivityProbe() { controller.abort(); }, 5_000);
|
|
253
|
+
try {
|
|
254
|
+
const response = await (options.connectivityFetch ?? globalThis.fetch)(selectedProvider.baseUrl ?? "https://api.anthropic.com", { method: "HEAD", redirect: "manual", signal: controller.signal });
|
|
255
|
+
return response.status >= 500
|
|
256
|
+
? { name: "provider_connectivity", status: "WARN", detail: "endpoint responded with server error" }
|
|
257
|
+
: { name: "provider_connectivity", status: "PASS", detail: "endpoint reachable" };
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
return {
|
|
261
|
+
name: "provider_connectivity",
|
|
262
|
+
status: "FAIL",
|
|
263
|
+
detail: error instanceof Error && error.name === "AbortError" ? "connection timed out" : "connection failed",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
clearTimeout(timeout);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** 检查 Search 凭据引用能否解析,真实 Key 只在局部变量中短暂存在且不进入结果。 */
|
|
271
|
+
async function hasWebSearchCredential(loaded) {
|
|
272
|
+
const configured = loaded.settings.webSearch;
|
|
273
|
+
if (!configured)
|
|
274
|
+
return false;
|
|
275
|
+
const credentialStore = createNodeCredentialStore({ env, userSettingsPath: loaded.paths.user });
|
|
276
|
+
try {
|
|
277
|
+
await credentialStore.resolve(configured.credentialRef);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** 显式 Doctor 通过 Provider 边界执行无凭据、无正文连通探测。 */
|
|
285
|
+
async function checkWebSearchConnectivity(workspaceRoot, credentialAvailable) {
|
|
286
|
+
if (!credentialAvailable)
|
|
287
|
+
return { name: "web_search_connectivity", status: "SKIP", detail: "credential unavailable" };
|
|
288
|
+
if (!safeBaseCapabilityLoader.probeWebSearchConnectivity) {
|
|
289
|
+
return { name: "web_search_connectivity", status: "WARN", detail: "probe unavailable" };
|
|
290
|
+
}
|
|
291
|
+
const controller = new AbortController();
|
|
292
|
+
/** 到达 Doctor 固定等待上限时终止无正文探测。 */
|
|
293
|
+
function abortWebSearchConnectivityProbe() {
|
|
294
|
+
controller.abort();
|
|
295
|
+
}
|
|
296
|
+
const timeout = setTimeout(abortWebSearchConnectivityProbe, 5_000);
|
|
297
|
+
try {
|
|
298
|
+
const status = await safeBaseCapabilityLoader.probeWebSearchConnectivity(workspaceRoot, controller.signal);
|
|
299
|
+
if (status === "unsupported") {
|
|
300
|
+
return { name: "web_search_connectivity", status: "WARN", detail: "provider unsupported" };
|
|
301
|
+
}
|
|
302
|
+
return status === "server_error"
|
|
303
|
+
? { name: "web_search_connectivity", status: "WARN", detail: "endpoint responded with server error" }
|
|
304
|
+
: { name: "web_search_connectivity", status: "PASS", detail: "endpoint reachable" };
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
return {
|
|
308
|
+
name: "web_search_connectivity",
|
|
309
|
+
status: "FAIL",
|
|
310
|
+
detail: error instanceof Error && error.name === "AbortError" ? "connection timed out" : "connection failed",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
clearTimeout(timeout);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const defaultCapabilityLoader = {
|
|
318
|
+
...nodeCapabilityLoader,
|
|
319
|
+
loadProvider() {
|
|
320
|
+
return createDefaultModelSamplerFactory();
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
const baseCapabilityLoader = options.capabilityLoader ?? defaultCapabilityLoader;
|
|
324
|
+
const safeBaseCapabilityLoader = omitCliRagCapabilityLoader(baseCapabilityLoader);
|
|
325
|
+
let loadedSkills;
|
|
326
|
+
const capabilityLoader = {
|
|
327
|
+
...safeBaseCapabilityLoader,
|
|
328
|
+
...(safeBaseCapabilityLoader.loadProvider ? {
|
|
329
|
+
async loadProvider(workspaceRoot) {
|
|
330
|
+
return withCliTextClarificationBehavior(await safeBaseCapabilityLoader.loadProvider(workspaceRoot));
|
|
331
|
+
},
|
|
332
|
+
} : {}),
|
|
333
|
+
async loadSkills(workspaceRoot) {
|
|
334
|
+
activateSettings(workspaceRoot);
|
|
335
|
+
const skills = safeBaseCapabilityLoader.loadSkills
|
|
336
|
+
? await safeBaseCapabilityLoader.loadSkills(workspaceRoot)
|
|
337
|
+
: undefined;
|
|
338
|
+
loadedSkills = skills;
|
|
339
|
+
return skills;
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
const bootstrapService = createRuntimeBootstrapService({
|
|
343
|
+
config,
|
|
344
|
+
workspaceAdapter,
|
|
345
|
+
trustPolicy,
|
|
346
|
+
capabilityLoader,
|
|
347
|
+
createPorts(workspaceRoot) {
|
|
348
|
+
initializeNodeStateRoot(stateRoot);
|
|
349
|
+
return createNodeRuntimePorts({
|
|
350
|
+
stateRoot,
|
|
351
|
+
trustRoot: options.trustRoot,
|
|
352
|
+
workspaceRoot,
|
|
353
|
+
env,
|
|
354
|
+
userSettingsPath: resolveNodeSettingsPaths(workspaceRoot).user,
|
|
355
|
+
// Talk-Plan0917:CLI 沙箱 tempRoot 精确指向 Trust 后项目内 .autopilot/tmp
|
|
356
|
+
//(目录由 ensureProjectAutopilotDirectory 创建并验证),不继承 ambient temp。
|
|
357
|
+
sandboxTempRoot: path.join(workspaceRoot, ".autopilot", "tmp"),
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
defaultRunConfig: {
|
|
361
|
+
...options.defaultRunConfig,
|
|
362
|
+
getRuntimeDefaults() {
|
|
363
|
+
if (!resolvedSettings)
|
|
364
|
+
return {};
|
|
365
|
+
const selectedProvider = getSelectedProviderSettings(resolvedSettings);
|
|
366
|
+
const visionRef = resolvedSettings.visionModelRef === false ? undefined : resolvedSettings.visionModelRef ?? resolvedSettings.modelRef;
|
|
367
|
+
const visionRoute = visionRef && resolvedSettings.providerRoutes[visionRef.provider];
|
|
368
|
+
return {
|
|
369
|
+
...(resolvedSettings.maxTurns !== undefined ? { maxSteps: resolvedSettings.maxTurns } : {}),
|
|
370
|
+
...(resolvedSettings.modelRef ? { modelRef: resolvedSettings.modelRef } : {}),
|
|
371
|
+
...(resolvedSettings.reasoningProfile ? { reasoningProfile: resolvedSettings.reasoningProfile } : {}),
|
|
372
|
+
...(selectedProvider?.credentialRef ? { credentialRef: selectedProvider.credentialRef } : {}),
|
|
373
|
+
// Talk-Plan0917 Stage 2:settings 只允许声明 read-only/workspace-write;
|
|
374
|
+
// tempRoot 固定指向当前 Runtime 工作区内的受控 .autopilot/tmp。
|
|
375
|
+
...(resolvedSettings.sandbox.mode ? { sandboxMode: resolvedSettings.sandbox.mode } : {}),
|
|
376
|
+
...(activeWorkspaceRoot ? { sandboxTempRoot: path.join(activeWorkspaceRoot, ".autopilot", "tmp") } : {}),
|
|
377
|
+
providerSettings: {
|
|
378
|
+
...(selectedProvider ? { primary: selectedProvider } : {}),
|
|
379
|
+
routes: resolvedSettings.providerRoutes,
|
|
380
|
+
...(visionRef ? { visionModelRef: visionRef, vision: visionRoute
|
|
381
|
+
? { apiProtocol: visionRoute.apiProtocol, baseUrl: visionRoute.baseUrl, credentialRef: visionRoute.credentialRef }
|
|
382
|
+
: selectedProvider } : {}),
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
/** 读取当前受信任 workspace 的受控 MCP 配置;缺失或未知名称稳定返回 undefined。 */
|
|
389
|
+
async function findMcpServer(workspaceRoot, serverName) {
|
|
390
|
+
const capability = safeBaseCapabilityLoader.loadMcp ? await safeBaseCapabilityLoader.loadMcp(workspaceRoot) : undefined;
|
|
391
|
+
return capability?.servers.find(function matchServer(server) { return server.name === serverName; });
|
|
392
|
+
}
|
|
393
|
+
/** 为 MCP 管理命令创建同一 stateRoot 下的 ApprovalStore,不装配模型或启动 MCP。 */
|
|
394
|
+
function createMcpApprovalStore(workspaceRoot) {
|
|
395
|
+
initializeNodeStateRoot(stateRoot);
|
|
396
|
+
return createNodeRuntimePorts({ stateRoot, workspaceRoot, env, userSettingsPath: resolveNodeSettingsPaths(workspaceRoot).user }).approvalStore;
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
stateRoot,
|
|
400
|
+
ensureUserConfigDirectory: function ensureConfigDirectory() {
|
|
401
|
+
ensureUserConfigDirectory();
|
|
402
|
+
},
|
|
403
|
+
ensureProjectAutopilotDirectory,
|
|
404
|
+
establishTrust: function establishTrust() {
|
|
405
|
+
return bootstrapService.establishTrust();
|
|
406
|
+
},
|
|
407
|
+
resolveTrust: async function resolveTrust() {
|
|
408
|
+
const candidate = config.workspaceRoot ?? (await workspaceAdapter.resolveWorkspaceRoot());
|
|
409
|
+
if (!candidate)
|
|
410
|
+
throw new WorkspaceTrustError("", "denied", "无法解析工作区根目录");
|
|
411
|
+
const canonicalRoot = await workspaceAdapter.canonicalizeWorkspaceRoot(candidate);
|
|
412
|
+
const trustEntry = await trustPolicy.getTrustStatus(canonicalRoot);
|
|
413
|
+
return { workspaceRoot: canonicalRoot, trustEntry };
|
|
414
|
+
},
|
|
415
|
+
grantTrust: function grantTrust(workspaceRoot) {
|
|
416
|
+
return trustPolicy.grantTrust(workspaceRoot);
|
|
417
|
+
},
|
|
418
|
+
getTrustStatus: function getTrustStatus(workspaceRoot) {
|
|
419
|
+
return trustPolicy.getTrustStatus(workspaceRoot);
|
|
420
|
+
},
|
|
421
|
+
revokeTrust: function revokeTrust(workspaceRoot) {
|
|
422
|
+
return trustPolicy.revokeTrust(workspaceRoot);
|
|
423
|
+
},
|
|
424
|
+
listWorkspaceRegistry: function listWorkspaceRegistry() {
|
|
425
|
+
return workspaceRegistry.list();
|
|
426
|
+
},
|
|
427
|
+
removeWorkspaceRegistry: function removeWorkspaceRegistry(workspaceId) {
|
|
428
|
+
return workspaceRegistry.remove(workspaceId);
|
|
429
|
+
},
|
|
430
|
+
showConfig: function showConfig(scope, workspaceRoot) {
|
|
431
|
+
return settingsWriter.show(scope, workspaceRoot);
|
|
432
|
+
},
|
|
433
|
+
getConfig: function getConfig(scope, key, workspaceRoot) {
|
|
434
|
+
return settingsWriter.get(scope, key, workspaceRoot);
|
|
435
|
+
},
|
|
436
|
+
setConfig: function setConfig(scope, key, value, workspaceRoot) {
|
|
437
|
+
return settingsWriter.set(scope, key, value, workspaceRoot);
|
|
438
|
+
},
|
|
439
|
+
doctor: async function doctor(workspaceRoot, connectivity) {
|
|
440
|
+
const checks = [{ name: "node", status: "PASS", detail: process.version }];
|
|
441
|
+
const workspaceId = createNodeWorkspaceId(workspaceRoot);
|
|
442
|
+
checks.push({ name: "workspace", status: "PASS", detail: `id=${workspaceId}` });
|
|
443
|
+
// 沙箱能力检查不依赖 Trust;报告只含 backend 与 enforcement,不含路径或命令明细。
|
|
444
|
+
try {
|
|
445
|
+
const sandbox = await getSandboxPort(workspaceRoot).status();
|
|
446
|
+
checks.push({ name: "sandbox", status: sandbox.enforcement === "full" ? "PASS" : "WARN", detail: `${sandbox.backend} enforcement=${sandbox.enforcement}` });
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
checks.push({ name: "sandbox", status: "WARN", detail: "sandbox status unavailable" });
|
|
450
|
+
}
|
|
451
|
+
const trust = await trustPolicy.getTrustStatus(workspaceRoot);
|
|
452
|
+
checks.push({ name: "trust", status: trust.status === "trusted" ? "PASS" : "WARN", detail: trust.status });
|
|
453
|
+
const rootAccess = getExistingStateRootAccess();
|
|
454
|
+
checks.push({ name: "state_root", status: rootAccess, detail: rootAccess === "WARN" ? "not initialized" : rootAccess === "PASS" ? "read/write" : "not accessible" });
|
|
455
|
+
if (rootAccess === "PASS") {
|
|
456
|
+
try {
|
|
457
|
+
assertNodeStateSchema(stateRoot);
|
|
458
|
+
const migration = getNodeStateSchemaDiagnostic(stateRoot);
|
|
459
|
+
checks.push({ name: "state_schema", status: "PASS", detail: migration ? migration.code : "compatible" });
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
checks.push({ name: "state_schema", status: "FAIL", detail: "schema invalid" });
|
|
463
|
+
}
|
|
464
|
+
const corruption = getNodeStateCorruptionDiagnostics(stateRoot);
|
|
465
|
+
checks.push({ name: "durable_jsonl", status: corruption.length > 0 ? "WARN" : "PASS", detail: corruption.length > 0 ? `${corruption.length} corrupted record(s)` : "clean" });
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
checks.push({ name: "state_schema", status: "SKIP", detail: "state root unavailable" });
|
|
469
|
+
checks.push({ name: "durable_jsonl", status: "SKIP", detail: "state root unavailable" });
|
|
470
|
+
}
|
|
471
|
+
if (trust.status !== "trusted") {
|
|
472
|
+
checks.push({ name: "settings", status: "SKIP", detail: "workspace not trusted" });
|
|
473
|
+
checks.push({ name: "web_search", status: "SKIP", detail: "workspace not trusted" });
|
|
474
|
+
checks.push({ name: "skills", status: "SKIP", detail: "workspace not trusted" });
|
|
475
|
+
checks.push({ name: "mcp", status: "SKIP", detail: "workspace not trusted" });
|
|
476
|
+
checks.push({ name: "provider_connectivity", status: "SKIP", detail: "workspace not trusted" });
|
|
477
|
+
checks.push({ name: "web_search_connectivity", status: "SKIP", detail: "workspace not trusted" });
|
|
478
|
+
return checks;
|
|
479
|
+
}
|
|
480
|
+
const loaded = settingsLoader.load(workspaceRoot);
|
|
481
|
+
const selectedProvider = getSelectedProviderSettings(loaded.settings);
|
|
482
|
+
const hasCredential = Boolean(selectedProvider?.credentialRef);
|
|
483
|
+
checks.push({ name: "settings", status: loaded.settings.diagnostics.length > 0 ? "WARN" : "PASS", detail: loaded.settings.diagnostics.length > 0 ? `${loaded.settings.diagnostics.length} diagnostic(s)` : "valid" });
|
|
484
|
+
checks.push({ name: "model", status: loaded.settings.modelRef ? "PASS" : "WARN", detail: loaded.settings.modelRef ? "configured" : "not configured" });
|
|
485
|
+
checks.push({ name: "credential", status: hasCredential ? "PASS" : "WARN", detail: hasCredential ? "configured" : "not configured" });
|
|
486
|
+
const webSearchCredentialAvailable = await hasWebSearchCredential(loaded);
|
|
487
|
+
checks.push({
|
|
488
|
+
name: "web_search",
|
|
489
|
+
status: !loaded.settings.webSearch ? "WARN" : webSearchCredentialAvailable ? "PASS" : "FAIL",
|
|
490
|
+
detail: `${!loaded.settings.webSearch ? "not configured" : webSearchCredentialAvailable ? "configured" : "credential unavailable"}; ${WEB_SEARCH_DATA_FLOW_NOTICE}`,
|
|
491
|
+
});
|
|
492
|
+
try {
|
|
493
|
+
const skills = nodeCapabilityLoader.loadSkills(workspaceRoot);
|
|
494
|
+
checks.push({ name: "skills", status: "PASS", detail: `${skills?.catalog.length ?? 0} discovered` });
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
checks.push({ name: "skills", status: "WARN", detail: "configuration unavailable" });
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
const mcp = nodeCapabilityLoader.loadMcp(workspaceRoot);
|
|
501
|
+
checks.push({ name: "mcp", status: "PASS", detail: `${mcp?.servers.length ?? 0} configured` });
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
checks.push({ name: "mcp", status: "WARN", detail: "configuration unavailable" });
|
|
505
|
+
}
|
|
506
|
+
checks.push(connectivity ? await checkConnectivity(workspaceRoot) : { name: "provider_connectivity", status: "SKIP", detail: "use --connectivity" });
|
|
507
|
+
checks.push(!loaded.settings.webSearch
|
|
508
|
+
? { name: "web_search_connectivity", status: "SKIP", detail: "not configured" }
|
|
509
|
+
: connectivity
|
|
510
|
+
? await checkWebSearchConnectivity(workspaceRoot, webSearchCredentialAvailable)
|
|
511
|
+
: { name: "web_search_connectivity", status: "SKIP", detail: "use --connectivity" });
|
|
512
|
+
return checks;
|
|
513
|
+
},
|
|
514
|
+
sandboxStatus: async function sandboxStatus(workspaceRoot) {
|
|
515
|
+
return getSandboxPort(workspaceRoot).status();
|
|
516
|
+
},
|
|
517
|
+
sandboxSetup: async function sandboxSetup(workspaceRoot) {
|
|
518
|
+
return getSandboxPort(workspaceRoot).setup();
|
|
519
|
+
},
|
|
520
|
+
createRuntime: async function createRuntime(workspaceRoot) {
|
|
521
|
+
activateSettings(workspaceRoot);
|
|
522
|
+
activeWorkspaceRoot = workspaceRoot;
|
|
523
|
+
const runtime = await bootstrapService.createRuntime(workspaceRoot);
|
|
524
|
+
workspaceRegistry.register(workspaceRoot);
|
|
525
|
+
return runtime;
|
|
526
|
+
},
|
|
527
|
+
bootstrap: async function bootstrap() {
|
|
528
|
+
const trusted = await bootstrapService.establishTrust();
|
|
529
|
+
return bootstrapService.createRuntime(trusted.workspaceRoot);
|
|
530
|
+
},
|
|
531
|
+
getBootstrapService: function getBootstrapService() {
|
|
532
|
+
return bootstrapService;
|
|
533
|
+
},
|
|
534
|
+
getRuntimeStatusDefaults: function getRuntimeStatusDefaults() {
|
|
535
|
+
const modelRef = resolvedSettings?.modelRef;
|
|
536
|
+
return {
|
|
537
|
+
...(modelRef ? { modelRef } : {}),
|
|
538
|
+
...(resolvedSettings?.reasoningProfile ? { reasoningProfile: resolvedSettings.reasoningProfile } : {}),
|
|
539
|
+
modelCapabilities: getModelCapabilities(),
|
|
540
|
+
};
|
|
541
|
+
},
|
|
542
|
+
getStartupConfigurationError,
|
|
543
|
+
listSkills: async function listSkills() {
|
|
544
|
+
if (!loadedSkills)
|
|
545
|
+
throw new Error("CLI_SKILL_CAPABILITY_UNAVAILABLE");
|
|
546
|
+
return loadedSkills.catalog.filter(function isUserInvocable(entry) {
|
|
547
|
+
return entry.userInvocable !== false;
|
|
548
|
+
}).map(function summarizeSkill(entry) {
|
|
549
|
+
return {
|
|
550
|
+
qualifiedName: entry.qualifiedName,
|
|
551
|
+
displayName: entry.displayName ?? entry.name,
|
|
552
|
+
description: entry.description,
|
|
553
|
+
};
|
|
554
|
+
}).sort(function sortByCanonicalName(left, right) { return left.qualifiedName.localeCompare(right.qualifiedName); });
|
|
555
|
+
},
|
|
556
|
+
resolveSkill: async function resolveSkill(skill) {
|
|
557
|
+
if (!loadedSkills)
|
|
558
|
+
throw new Error("CLI_SKILL_CAPABILITY_UNAVAILABLE");
|
|
559
|
+
const matches = loadedSkills.catalog.filter(function matchSkill(entry) {
|
|
560
|
+
return entry.qualifiedName === skill && entry.userInvocable !== false;
|
|
561
|
+
});
|
|
562
|
+
if (matches.length !== 1)
|
|
563
|
+
throw new Error("CLI_SKILL_NOT_FOUND");
|
|
564
|
+
const entry = matches[0];
|
|
565
|
+
const loaded = await loadedSkills.loadSkill({ skill: entry.qualifiedName });
|
|
566
|
+
if (loaded.skillId !== entry.skillId)
|
|
567
|
+
throw new Error("CLI_SKILL_ID_MISMATCH");
|
|
568
|
+
return { skillId: loaded.skillId, contentHash: loaded.contentHash };
|
|
569
|
+
},
|
|
570
|
+
listMcp: async function listMcp(workspaceRoot) {
|
|
571
|
+
const store = createMcpApprovalStore(workspaceRoot);
|
|
572
|
+
const capability = safeBaseCapabilityLoader.loadMcp ? await safeBaseCapabilityLoader.loadMcp(workspaceRoot) : undefined;
|
|
573
|
+
return Promise.all((capability?.servers ?? []).filter(function enabled(server) { return server.enabled !== false; }).map(async function summarize(server) {
|
|
574
|
+
const approved = await store.getPersistentGrant({ workspaceRoot, sessionId: "", toolName: "mcp_server_start", constraint: getMcpServerGrantConstraint(server) });
|
|
575
|
+
const executionMode = server.execution === "host" ? "host" : "sandbox";
|
|
576
|
+
return { serverName: server.name, program: server.command, args: server.args ?? [], cwd: server.cwd ?? ".", approved: Boolean(approved), executionMode, isolation: executionMode === "host" ? "unisolated" : "sandboxed" };
|
|
577
|
+
}));
|
|
578
|
+
},
|
|
579
|
+
approveMcp: async function approveMcp(workspaceRoot, serverName) {
|
|
580
|
+
const server = await findMcpServer(workspaceRoot, serverName);
|
|
581
|
+
if (!server)
|
|
582
|
+
throw new Error("MCP_SERVER_NOT_FOUND");
|
|
583
|
+
await createMcpApprovalStore(workspaceRoot).savePersistentGrant({
|
|
584
|
+
scope: "allow_workspace",
|
|
585
|
+
workspaceRoot,
|
|
586
|
+
toolName: "mcp_server_start",
|
|
587
|
+
constraint: getMcpServerGrantConstraint(server),
|
|
588
|
+
risk: "unknown",
|
|
589
|
+
createdAt: new Date().toISOString(),
|
|
590
|
+
});
|
|
591
|
+
return { serverName: server.name };
|
|
592
|
+
},
|
|
593
|
+
revokeMcp: async function revokeMcp(workspaceRoot, serverName) {
|
|
594
|
+
const server = await findMcpServer(workspaceRoot, serverName);
|
|
595
|
+
if (!server)
|
|
596
|
+
throw new Error("MCP_SERVER_NOT_FOUND");
|
|
597
|
+
return createMcpApprovalStore(workspaceRoot).deletePersistentGrant({ workspaceRoot, sessionId: "", toolName: "mcp_server_start", constraint: getMcpServerGrantConstraint(server) });
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Usage 安全报告格式化。
|
|
3
|
+
*
|
|
4
|
+
* 所有输入只来自 Core 白名单 ContextUsageView DTO;宿主只做展示转换,
|
|
5
|
+
* 不重算 token、不猜测窗口、不为非零分类强塞至少一格。
|
|
6
|
+
*/
|
|
7
|
+
import type { ContextUsageView } from "@autopilot-agent/core";
|
|
8
|
+
/**
|
|
9
|
+
* 生成 Context 报告文本行。
|
|
10
|
+
* grid=false(Plain/screen reader/NO_COLOR)跳过装饰网格但保留完整文本;
|
|
11
|
+
* 列数不足以并排时整体改为上下堆叠。
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatContextReportLines(usage: ContextUsageView | null, expanded: boolean, options: {
|
|
14
|
+
grid: boolean;
|
|
15
|
+
columns: number;
|
|
16
|
+
}): string[];
|
|
17
|
+
/** Plain/非 TTY 的稳定文本报告;始终跳过装饰网格。 */
|
|
18
|
+
export declare function formatPlainContextReport(usage: ContextUsageView | null, expanded: boolean): string[];
|
|
19
|
+
/** 状态栏只显示整个窗口的整数剩余百分比;计量来源留在 /context all。 */
|
|
20
|
+
export declare function formatTuiContextStatus(usage: ContextUsageView | null): string;
|