@steerable/agent-shell 0.6.15
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/LICENSE +91 -0
- package/contracts/tool-contract.json +326 -0
- package/dist/attachments.d.ts +41 -0
- package/dist/attachments.js +147 -0
- package/dist/brand.d.ts +24 -0
- package/dist/brand.js +92 -0
- package/dist/host/http-routes.d.ts +21 -0
- package/dist/host/http-routes.js +55 -0
- package/dist/host/ipc.d.ts +11 -0
- package/dist/host/ipc.js +20 -0
- package/dist/host/pack-assembly.d.ts +86 -0
- package/dist/host/pack-assembly.js +32 -0
- package/dist/host/runtime.d.ts +69 -0
- package/dist/host/runtime.js +207 -0
- package/dist/host/visible-terminal-exec.d.ts +21 -0
- package/dist/host/visible-terminal-exec.js +151 -0
- package/dist/hosted-web-search.d.ts +13 -0
- package/dist/hosted-web-search.js +82 -0
- package/dist/image-attachment.d.ts +39 -0
- package/dist/image-attachment.js +133 -0
- package/dist/insights/flush.d.ts +10 -0
- package/dist/insights/flush.js +139 -0
- package/dist/insights/record.d.ts +17 -0
- package/dist/insights/record.js +44 -0
- package/dist/json-store.d.ts +9 -0
- package/dist/json-store.js +22 -0
- package/dist/llm/index.d.ts +23 -0
- package/dist/llm/index.js +105 -0
- package/dist/llm/ollama.d.ts +23 -0
- package/dist/llm/ollama.js +242 -0
- package/dist/llm/openai-compat.d.ts +20 -0
- package/dist/llm/openai-compat.js +199 -0
- package/dist/llm/sidecar-provider.d.ts +37 -0
- package/dist/llm/sidecar-provider.js +163 -0
- package/dist/llm/tool-choice.d.ts +35 -0
- package/dist/llm/tool-choice.js +85 -0
- package/dist/llm/types.d.ts +122 -0
- package/dist/llm/types.js +1 -0
- package/dist/local-backend/agent-capability.d.ts +101 -0
- package/dist/local-backend/agent-capability.js +174 -0
- package/dist/local-backend/ai-title.d.ts +43 -0
- package/dist/local-backend/ai-title.js +173 -0
- package/dist/local-backend/auto-continue-helper.d.ts +80 -0
- package/dist/local-backend/auto-continue-helper.js +83 -0
- package/dist/local-backend/branch-helper.d.ts +24 -0
- package/dist/local-backend/branch-helper.js +27 -0
- package/dist/local-backend/context-compactor.d.ts +81 -0
- package/dist/local-backend/context-compactor.js +213 -0
- package/dist/local-backend/coreloop-stream.d.ts +245 -0
- package/dist/local-backend/coreloop-stream.js +277 -0
- package/dist/local-backend/deferred-detector.d.ts +15 -0
- package/dist/local-backend/deferred-detector.js +124 -0
- package/dist/local-backend/history-helper.d.ts +30 -0
- package/dist/local-backend/history-helper.js +34 -0
- package/dist/local-backend/interrupted-helper.d.ts +29 -0
- package/dist/local-backend/interrupted-helper.js +25 -0
- package/dist/local-backend/live-stream.d.ts +36 -0
- package/dist/local-backend/live-stream.js +23 -0
- package/dist/local-backend/llm-diagnose.d.ts +37 -0
- package/dist/local-backend/llm-diagnose.js +284 -0
- package/dist/local-backend/message-triggers.d.ts +27 -0
- package/dist/local-backend/message-triggers.js +67 -0
- package/dist/local-backend/pack-backend-routes.d.ts +31 -0
- package/dist/local-backend/pack-backend-routes.js +62 -0
- package/dist/local-backend/pack-turn-hooks.d.ts +37 -0
- package/dist/local-backend/pack-turn-hooks.js +67 -0
- package/dist/local-backend/prompt-builder.d.ts +101 -0
- package/dist/local-backend/prompt-builder.js +246 -0
- package/dist/local-backend/regenerate-helper.d.ts +62 -0
- package/dist/local-backend/regenerate-helper.js +75 -0
- package/dist/local-backend/router.d.ts +175 -0
- package/dist/local-backend/router.js +3139 -0
- package/dist/local-backend/skill-install.d.ts +19 -0
- package/dist/local-backend/skill-install.js +71 -0
- package/dist/local-backend/skill-loader.d.ts +92 -0
- package/dist/local-backend/skill-loader.js +146 -0
- package/dist/local-backend/skills/00-identity/SKILL.md +32 -0
- package/dist/local-backend/skills/10-goal/SKILL.md +59 -0
- package/dist/local-backend/skills/11-loop/SKILL.md +71 -0
- package/dist/local-backend/skills/12-create-skill/SKILL.md +88 -0
- package/dist/local-backend/skills/70-plan-mode/SKILL.md +58 -0
- package/dist/local-backend/skills/80-tool-usage/SKILL.md +70 -0
- package/dist/local-backend/skills/81-anti-deferred/SKILL.md +53 -0
- package/dist/local-backend/skills/82-data-grounding/SKILL.md +56 -0
- package/dist/local-backend/skills/85-local-exec/SKILL.md +86 -0
- package/dist/local-backend/skills/86-proactive-coding/SKILL.md +51 -0
- package/dist/local-backend/subagent-profiles.d.ts +30 -0
- package/dist/local-backend/subagent-profiles.js +74 -0
- package/dist/local-backend/task-process.d.ts +12 -0
- package/dist/local-backend/task-process.js +176 -0
- package/dist/local-backend/task-service.d.ts +135 -0
- package/dist/local-backend/task-service.js +565 -0
- package/dist/local-backend/turn-duration.d.ts +2 -0
- package/dist/local-backend/turn-duration.js +9 -0
- package/dist/local-backend/turn-timeline.d.ts +16 -0
- package/dist/local-backend/turn-timeline.js +42 -0
- package/dist/local-backend/worktree-service.d.ts +84 -0
- package/dist/local-backend/worktree-service.js +243 -0
- package/dist/local-edit.d.ts +48 -0
- package/dist/local-edit.js +44 -0
- package/dist/local-executor.d.ts +255 -0
- package/dist/local-executor.js +881 -0
- package/dist/local-script-registry.d.ts +28 -0
- package/dist/local-script-registry.js +63 -0
- package/dist/log.d.ts +13 -0
- package/dist/log.js +12 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +855 -0
- package/dist/mcp-executor.d.ts +45 -0
- package/dist/mcp-executor.js +241 -0
- package/dist/mcp-server-registry.d.ts +104 -0
- package/dist/mcp-server-registry.js +234 -0
- package/dist/preload-default.d.ts +1 -0
- package/dist/preload-default.js +9 -0
- package/dist/preload.cjs +395 -0
- package/dist/preload.d.ts +20 -0
- package/dist/preload.js +411 -0
- package/dist/product-config.d.ts +43 -0
- package/dist/product-config.js +26 -0
- package/dist/project-registry.d.ts +55 -0
- package/dist/project-registry.js +106 -0
- package/dist/project-rules.d.ts +15 -0
- package/dist/project-rules.js +102 -0
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.js +217 -0
- package/dist/scenario/pack.d.ts +8 -0
- package/dist/scenario/pack.js +1 -0
- package/dist/scenario/registry.d.ts +24 -0
- package/dist/scenario/registry.js +31 -0
- package/dist/server/http-server.d.ts +39 -0
- package/dist/server/http-server.js +361 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +107 -0
- package/dist/server/sse-bus.d.ts +14 -0
- package/dist/server/sse-bus.js +31 -0
- package/dist/shell-adapt.d.ts +21 -0
- package/dist/shell-adapt.js +104 -0
- package/dist/sidecar/boot.d.ts +36 -0
- package/dist/sidecar/boot.js +343 -0
- package/dist/sidecar/egress-hint.d.ts +15 -0
- package/dist/sidecar/egress-hint.js +46 -0
- package/dist/sidecar/egress-proxy.d.ts +183 -0
- package/dist/sidecar/egress-proxy.js +419 -0
- package/dist/sidecar/errors.d.ts +22 -0
- package/dist/sidecar/errors.js +38 -0
- package/dist/sidecar/exec-sandbox.d.ts +48 -0
- package/dist/sidecar/exec-sandbox.js +94 -0
- package/dist/sidecar/handle.d.ts +32 -0
- package/dist/sidecar/handle.js +53 -0
- package/dist/sidecar/index.d.ts +14 -0
- package/dist/sidecar/index.js +13 -0
- package/dist/sidecar/proxy-detect.d.ts +50 -0
- package/dist/sidecar/proxy-detect.js +182 -0
- package/dist/sidecar/reverse-approval.d.ts +55 -0
- package/dist/sidecar/reverse-approval.js +86 -0
- package/dist/sidecar/reverse-ask-user.d.ts +34 -0
- package/dist/sidecar/reverse-ask-user.js +59 -0
- package/dist/sidecar/reverse-spawn.d.ts +19 -0
- package/dist/sidecar/reverse-spawn.js +161 -0
- package/dist/sidecar/reverse-tools.d.ts +29 -0
- package/dist/sidecar/reverse-tools.js +106 -0
- package/dist/sidecar/safety-patterns.d.ts +41 -0
- package/dist/sidecar/safety-patterns.js +157 -0
- package/dist/sidecar/storage-path.d.ts +14 -0
- package/dist/sidecar/storage-path.js +35 -0
- package/dist/sidecar/supervisor.d.ts +218 -0
- package/dist/sidecar/supervisor.js +932 -0
- package/dist/sidecar/types.d.ts +601 -0
- package/dist/sidecar/types.js +1 -0
- package/dist/single-instance.d.ts +11 -0
- package/dist/single-instance.js +21 -0
- package/dist/storage/empty-chats.d.ts +9 -0
- package/dist/storage/empty-chats.js +16 -0
- package/dist/storage/index.d.ts +373 -0
- package/dist/storage/index.js +1158 -0
- package/dist/storage/insights-redact.d.ts +2 -0
- package/dist/storage/insights-redact.js +30 -0
- package/dist/storage/insights-settings.d.ts +53 -0
- package/dist/storage/insights-settings.js +92 -0
- package/dist/storage/llm-settings.d.ts +120 -0
- package/dist/storage/llm-settings.js +233 -0
- package/dist/storage/local-store-singleton.d.ts +28 -0
- package/dist/storage/local-store-singleton.js +38 -0
- package/dist/storage/message-order.d.ts +25 -0
- package/dist/storage/message-order.js +27 -0
- package/dist/storage/pack-migrations.d.ts +22 -0
- package/dist/storage/pack-migrations.js +24 -0
- package/dist/storage/pack-seeds.d.ts +36 -0
- package/dist/storage/pack-seeds.js +42 -0
- package/dist/storage/telemetry-settings.d.ts +38 -0
- package/dist/storage/telemetry-settings.js +59 -0
- package/dist/storage/usage-summary.d.ts +55 -0
- package/dist/storage/usage-summary.js +38 -0
- package/dist/storage/web-search-settings.d.ts +38 -0
- package/dist/storage/web-search-settings.js +74 -0
- package/dist/storage/write-lease.d.ts +26 -0
- package/dist/storage/write-lease.js +74 -0
- package/dist/terminal-manager.d.ts +83 -0
- package/dist/terminal-manager.js +506 -0
- package/dist/tool-router.d.ts +228 -0
- package/dist/tool-router.js +930 -0
- package/dist/tool-search-rank.d.ts +42 -0
- package/dist/tool-search-rank.js +96 -0
- package/package.json +67 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W-llm-diagnose: LLM link diagnosis orchestration.
|
|
3
|
+
*
|
|
4
|
+
* The settings page's "诊断" button runs a battery of probes against the
|
|
5
|
+
* configured provider: DNS resolution, TCP connect, TLS handshake, HTTP
|
|
6
|
+
* GET /models, and a minimal chat completion. Each probe is timed and
|
|
7
|
+
* classified so the UI can render a per-step verdict.
|
|
8
|
+
*
|
|
9
|
+
* The diagnosis runs in the host process (not the sidecar) because it
|
|
10
|
+
* needs to observe the *host's* network path — the sidecar's sandboxed
|
|
11
|
+
* view would hide proxy misconfigurations that the user needs to see.
|
|
12
|
+
*/
|
|
13
|
+
import { lookup } from 'node:dns/promises';
|
|
14
|
+
import { connect } from 'node:net';
|
|
15
|
+
import { request as httpsRequest } from 'node:https';
|
|
16
|
+
import { request as httpRequest } from 'node:http';
|
|
17
|
+
import { collectAmbientProxyEndpoints } from '../sidecar/proxy-detect.js';
|
|
18
|
+
import { egressFailureHint } from '../sidecar/egress-hint.js';
|
|
19
|
+
/**
|
|
20
|
+
* Run the full diagnosis battery against `baseUrl` with `apiKey`.
|
|
21
|
+
* `model` is used for the chat-completion probe; when omitted the
|
|
22
|
+
* battery stops after the HTTP probe.
|
|
23
|
+
*/
|
|
24
|
+
export async function diagnoseLlmConnection(options) {
|
|
25
|
+
const { baseUrl, apiKey, model, timeoutMs = 15_000 } = options;
|
|
26
|
+
const steps = [];
|
|
27
|
+
const ambientProxies = await collectAmbientProxyEndpoints();
|
|
28
|
+
let url;
|
|
29
|
+
try {
|
|
30
|
+
url = new URL(baseUrl);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
steps: [
|
|
36
|
+
{
|
|
37
|
+
name: 'parse-url',
|
|
38
|
+
ok: false,
|
|
39
|
+
durationMs: 0,
|
|
40
|
+
detail: `无法解析 baseUrl: ${baseUrl}`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
ambientProxies,
|
|
44
|
+
hint: 'baseUrl 格式不正确,请检查是否包含协议(http:// 或 https://)。',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const host = url.hostname;
|
|
48
|
+
const port = url.port ? Number(url.port) : url.protocol === 'https:' ? 443 : 80;
|
|
49
|
+
const isHttps = url.protocol === 'https:';
|
|
50
|
+
// Step 1: DNS resolution
|
|
51
|
+
const dnsStart = Date.now();
|
|
52
|
+
try {
|
|
53
|
+
const addresses = await lookup(host, { all: true });
|
|
54
|
+
steps.push({
|
|
55
|
+
name: 'dns',
|
|
56
|
+
ok: true,
|
|
57
|
+
durationMs: Date.now() - dnsStart,
|
|
58
|
+
detail: `解析到 ${addresses.length} 个地址: ${addresses.map((a) => a.address).join(', ')}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
steps.push({
|
|
64
|
+
name: 'dns',
|
|
65
|
+
ok: false,
|
|
66
|
+
durationMs: Date.now() - dnsStart,
|
|
67
|
+
detail: `DNS 解析失败: ${message}`,
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
steps,
|
|
72
|
+
ambientProxies,
|
|
73
|
+
hint: '域名解析失败,请检查 baseUrl 域名是否正确,或检查 DNS 配置。',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Step 2: TCP connect
|
|
77
|
+
const tcpStart = Date.now();
|
|
78
|
+
try {
|
|
79
|
+
await new Promise((resolve, reject) => {
|
|
80
|
+
const socket = connect(port, host, () => {
|
|
81
|
+
socket.destroy();
|
|
82
|
+
resolve();
|
|
83
|
+
});
|
|
84
|
+
socket.once('error', reject);
|
|
85
|
+
socket.setTimeout(timeoutMs, () => {
|
|
86
|
+
socket.destroy();
|
|
87
|
+
reject(new Error('ETIMEDOUT'));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
steps.push({
|
|
91
|
+
name: 'tcp',
|
|
92
|
+
ok: true,
|
|
93
|
+
durationMs: Date.now() - tcpStart,
|
|
94
|
+
detail: `TCP 连接 ${host}:${port} 成功`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
99
|
+
steps.push({
|
|
100
|
+
name: 'tcp',
|
|
101
|
+
ok: false,
|
|
102
|
+
durationMs: Date.now() - tcpStart,
|
|
103
|
+
detail: `TCP 连接失败: ${message}`,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
steps,
|
|
108
|
+
ambientProxies,
|
|
109
|
+
hint: egressFailureHint(message) ?? '无法连接到模型服务,请检查网络或防火墙配置。',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Step 3: TLS handshake (https only)
|
|
113
|
+
if (isHttps) {
|
|
114
|
+
const tlsStart = Date.now();
|
|
115
|
+
try {
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
const req = httpsRequest({
|
|
118
|
+
host,
|
|
119
|
+
port,
|
|
120
|
+
method: 'HEAD',
|
|
121
|
+
path: '/',
|
|
122
|
+
timeout: timeoutMs,
|
|
123
|
+
rejectUnauthorized: false, // diagnosis only — we want to see the cert, not enforce it
|
|
124
|
+
}, (res) => {
|
|
125
|
+
res.resume();
|
|
126
|
+
resolve();
|
|
127
|
+
});
|
|
128
|
+
req.once('error', reject);
|
|
129
|
+
req.once('timeout', () => {
|
|
130
|
+
req.destroy();
|
|
131
|
+
reject(new Error('ETIMEDOUT'));
|
|
132
|
+
});
|
|
133
|
+
req.end();
|
|
134
|
+
});
|
|
135
|
+
steps.push({
|
|
136
|
+
name: 'tls',
|
|
137
|
+
ok: true,
|
|
138
|
+
durationMs: Date.now() - tlsStart,
|
|
139
|
+
detail: 'TLS 握手成功',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
144
|
+
steps.push({
|
|
145
|
+
name: 'tls',
|
|
146
|
+
ok: false,
|
|
147
|
+
durationMs: Date.now() - tlsStart,
|
|
148
|
+
detail: `TLS 握手失败: ${message}`,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
steps,
|
|
153
|
+
ambientProxies,
|
|
154
|
+
hint: 'TLS 握手失败,可能是证书问题或代理拦截。请检查系统代理设置,或尝试 HTTP 直连(如果服务支持)。',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Step 4: HTTP GET /models
|
|
159
|
+
const httpStart = Date.now();
|
|
160
|
+
const modelsPath = url.pathname.replace(/\/$/, '') + '/models';
|
|
161
|
+
try {
|
|
162
|
+
const status = await new Promise((resolve, reject) => {
|
|
163
|
+
const requestFn = isHttps ? httpsRequest : httpRequest;
|
|
164
|
+
const req = requestFn({
|
|
165
|
+
host,
|
|
166
|
+
port,
|
|
167
|
+
method: 'GET',
|
|
168
|
+
path: modelsPath,
|
|
169
|
+
timeout: timeoutMs,
|
|
170
|
+
rejectUnauthorized: false,
|
|
171
|
+
headers: {
|
|
172
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
173
|
+
Accept: 'application/json',
|
|
174
|
+
},
|
|
175
|
+
}, (res) => {
|
|
176
|
+
res.resume();
|
|
177
|
+
resolve(res.statusCode ?? 0);
|
|
178
|
+
});
|
|
179
|
+
req.once('error', reject);
|
|
180
|
+
req.once('timeout', () => {
|
|
181
|
+
req.destroy();
|
|
182
|
+
reject(new Error('ETIMEDOUT'));
|
|
183
|
+
});
|
|
184
|
+
req.end();
|
|
185
|
+
});
|
|
186
|
+
const ok = status >= 200 && status < 500; // 4xx means the endpoint is reachable but auth/model is wrong
|
|
187
|
+
steps.push({
|
|
188
|
+
name: 'http-models',
|
|
189
|
+
ok,
|
|
190
|
+
durationMs: Date.now() - httpStart,
|
|
191
|
+
detail: `GET ${modelsPath} → HTTP ${status}`,
|
|
192
|
+
});
|
|
193
|
+
if (!ok) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
steps,
|
|
197
|
+
ambientProxies,
|
|
198
|
+
hint: `模型服务返回 HTTP ${status},请检查 baseUrl 与 API Key 配置。`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
204
|
+
steps.push({
|
|
205
|
+
name: 'http-models',
|
|
206
|
+
ok: false,
|
|
207
|
+
durationMs: Date.now() - httpStart,
|
|
208
|
+
detail: `GET ${modelsPath} 失败: ${message}`,
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
steps,
|
|
213
|
+
ambientProxies,
|
|
214
|
+
hint: egressFailureHint(message) ?? '无法访问模型服务,请检查网络或代理配置。',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Step 5: chat completion (optional)
|
|
218
|
+
if (model) {
|
|
219
|
+
const chatStart = Date.now();
|
|
220
|
+
const chatPath = url.pathname.replace(/\/$/, '') + '/chat/completions';
|
|
221
|
+
try {
|
|
222
|
+
const status = await new Promise((resolve, reject) => {
|
|
223
|
+
const requestFn = isHttps ? httpsRequest : httpRequest;
|
|
224
|
+
const req = requestFn({
|
|
225
|
+
host,
|
|
226
|
+
port,
|
|
227
|
+
method: 'POST',
|
|
228
|
+
path: chatPath,
|
|
229
|
+
timeout: timeoutMs,
|
|
230
|
+
rejectUnauthorized: false,
|
|
231
|
+
headers: {
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
234
|
+
},
|
|
235
|
+
}, (res) => {
|
|
236
|
+
res.resume();
|
|
237
|
+
resolve(res.statusCode ?? 0);
|
|
238
|
+
});
|
|
239
|
+
req.once('error', reject);
|
|
240
|
+
req.once('timeout', () => {
|
|
241
|
+
req.destroy();
|
|
242
|
+
reject(new Error('ETIMEDOUT'));
|
|
243
|
+
});
|
|
244
|
+
req.write(JSON.stringify({
|
|
245
|
+
model,
|
|
246
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
247
|
+
max_tokens: 1,
|
|
248
|
+
}));
|
|
249
|
+
req.end();
|
|
250
|
+
});
|
|
251
|
+
const ok = status >= 200 && status < 500;
|
|
252
|
+
steps.push({
|
|
253
|
+
name: 'chat-completion',
|
|
254
|
+
ok,
|
|
255
|
+
durationMs: Date.now() - chatStart,
|
|
256
|
+
detail: `POST ${chatPath} → HTTP ${status}`,
|
|
257
|
+
});
|
|
258
|
+
if (!ok) {
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
steps,
|
|
262
|
+
ambientProxies,
|
|
263
|
+
hint: `模型服务返回 HTTP ${status},请检查模型名与 API Key 配置。`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
269
|
+
steps.push({
|
|
270
|
+
name: 'chat-completion',
|
|
271
|
+
ok: false,
|
|
272
|
+
durationMs: Date.now() - chatStart,
|
|
273
|
+
detail: `POST ${chatPath} 失败: ${message}`,
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
steps,
|
|
278
|
+
ambientProxies,
|
|
279
|
+
hint: egressFailureHint(message) ?? '无法调用模型服务,请检查网络或代理配置。',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return { ok: true, steps, ambientProxies, hint: null };
|
|
284
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户消息里的 "/" 显式触发解析(本地技能 + MCP 工具)。
|
|
3
|
+
*
|
|
4
|
+
* 从 router.ts 的 cleanUserMessage 抽出的纯函数,便于单测。支持两类触发:
|
|
5
|
+
*
|
|
6
|
+
* /skillName 其余问题… → forcedSkillName(指定本地技能)
|
|
7
|
+
* /mcp__srv__tool 其余问题… → forcedMcpToolToken(指定 MCP 工具)
|
|
8
|
+
*
|
|
9
|
+
* 行首 token 信任选择器(技能不校验存在性;MCP token 经 resolver 规范大小写)。
|
|
10
|
+
* 句中 token 必须精确命中一个已安装技能或已注册 MCP 工具才生效,避免把
|
|
11
|
+
* 路径(/mnt/c)、URL、分数(1 /2)里的斜杠误判成触发。
|
|
12
|
+
*/
|
|
13
|
+
export interface MessageTriggers {
|
|
14
|
+
cleanText: string;
|
|
15
|
+
skillName?: string;
|
|
16
|
+
mcpToolToken?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface TriggerResolvers {
|
|
19
|
+
/** 名字是否对应一个已安装技能(大小写不敏感,含 displayName)。 */
|
|
20
|
+
isKnownSkill(name: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 把用户输入的 mcp token 规范成注册表里的精确 token(tool_call 必须用
|
|
23
|
+
* 规范名,用户手敲可能大小写不符);未启用 / 不存在返回 null。
|
|
24
|
+
*/
|
|
25
|
+
resolveMcpToolToken(token: string): string | null;
|
|
26
|
+
}
|
|
27
|
+
export declare function parseUserMessageTriggers(content: string, resolvers: TriggerResolvers): MessageTriggers;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户消息里的 "/" 显式触发解析(本地技能 + MCP 工具)。
|
|
3
|
+
*
|
|
4
|
+
* 从 router.ts 的 cleanUserMessage 抽出的纯函数,便于单测。支持两类触发:
|
|
5
|
+
*
|
|
6
|
+
* /skillName 其余问题… → forcedSkillName(指定本地技能)
|
|
7
|
+
* /mcp__srv__tool 其余问题… → forcedMcpToolToken(指定 MCP 工具)
|
|
8
|
+
*
|
|
9
|
+
* 行首 token 信任选择器(技能不校验存在性;MCP token 经 resolver 规范大小写)。
|
|
10
|
+
* 句中 token 必须精确命中一个已安装技能或已注册 MCP 工具才生效,避免把
|
|
11
|
+
* 路径(/mnt/c)、URL、分数(1 /2)里的斜杠误判成触发。
|
|
12
|
+
*/
|
|
13
|
+
const MCP_TOKEN_PATTERN = /^mcp__/i;
|
|
14
|
+
export function parseUserMessageTriggers(content, resolvers) {
|
|
15
|
+
const trimmed = content.trim();
|
|
16
|
+
if (trimmed.startsWith('/')) {
|
|
17
|
+
const spaceIndex = trimmed.indexOf(' ');
|
|
18
|
+
const rawToken = spaceIndex !== -1 ? trimmed.slice(1, spaceIndex).trim() : trimmed.slice(1).trim();
|
|
19
|
+
const remainText = spaceIndex !== -1 ? trimmed.slice(spaceIndex + 1).trim() : '';
|
|
20
|
+
// MCP 工具触发优先于技能:token 命名空间 mcp__* 与技能名天然隔离。
|
|
21
|
+
if (MCP_TOKEN_PATTERN.test(rawToken)) {
|
|
22
|
+
const token = resolvers.resolveMcpToolToken(rawToken) ?? rawToken;
|
|
23
|
+
const cleanText = remainText
|
|
24
|
+
? `【指定MCP工具: ${token}】${remainText}`
|
|
25
|
+
: `【指定MCP工具: ${token}】请调用该工具并展示其返回结果。`;
|
|
26
|
+
return { cleanText, mcpToolToken: token };
|
|
27
|
+
}
|
|
28
|
+
if (spaceIndex !== -1) {
|
|
29
|
+
const cleanText = `【指定本地技能: ${rawToken}】${remainText}`;
|
|
30
|
+
return { cleanText, skillName: rawToken };
|
|
31
|
+
}
|
|
32
|
+
const cleanText = `【指定本地技能: ${rawToken}】请启用并展示技能「${rawToken}」的作用。`;
|
|
33
|
+
return { cleanText, skillName: rawToken };
|
|
34
|
+
}
|
|
35
|
+
const match = trimmed.match(/(?:^|\s)\/([^\s/]+)/);
|
|
36
|
+
if (match && typeof match.index === 'number' && match[1]) {
|
|
37
|
+
const matchIndex = match.index;
|
|
38
|
+
const matchedText = match[0];
|
|
39
|
+
const typed = match[1];
|
|
40
|
+
if (MCP_TOKEN_PATTERN.test(typed)) {
|
|
41
|
+
const token = resolvers.resolveMcpToolToken(typed);
|
|
42
|
+
if (token) {
|
|
43
|
+
return buildMidMessageResult(trimmed, matchIndex, matchedText, typed, (remainText) => remainText
|
|
44
|
+
? `【指定MCP工具: ${token}】${remainText}`
|
|
45
|
+
: `【指定MCP工具: ${token}】请调用该工具并展示其返回结果。`, { mcpToolToken: token });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const candidate = typed.toLowerCase();
|
|
50
|
+
if (resolvers.isKnownSkill(candidate)) {
|
|
51
|
+
return buildMidMessageResult(trimmed, matchIndex, matchedText, typed, (remainText) => remainText
|
|
52
|
+
? `【指定本地技能: ${candidate}】${remainText}`
|
|
53
|
+
: `【指定本地技能: ${candidate}】请启用并展示技能「${candidate}」的作用。`, { skillName: candidate });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { cleanText: trimmed };
|
|
58
|
+
}
|
|
59
|
+
/** 句中 token:从原文里抠掉 token 后构造 cleanText,并合并触发字段。 */
|
|
60
|
+
function buildMidMessageResult(trimmed, matchIndex, matchedText, typedToken, buildCleanText, trigger) {
|
|
61
|
+
const tokenStart = matchIndex + matchedText.indexOf('/');
|
|
62
|
+
const tokenEnd = tokenStart + 1 + typedToken.length;
|
|
63
|
+
const remainText = (trimmed.slice(0, tokenStart) + trimmed.slice(tokenEnd))
|
|
64
|
+
.replace(/\s{2,}/g, ' ')
|
|
65
|
+
.trim();
|
|
66
|
+
return { cleanText: buildCleanText(remainText), ...trigger };
|
|
67
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 场景包 local-backend 路由注册表(1.2)。
|
|
3
|
+
*
|
|
4
|
+
* 与 `host/http-routes.ts`(BS 宿主 `/host/<packId>/*`)不同:本注册表管
|
|
5
|
+
* `/api/v2/*` 命名空间下的包路由——CS/BS 双宿主都经 LocalBackendRouter
|
|
6
|
+
* 服务这一命名空间(CS 走 localBackend.request IPC 桥,BS 走 HTTP),所以
|
|
7
|
+
* 包的业务路由必须挂在这里才能双宿主可达。
|
|
8
|
+
*
|
|
9
|
+
* 命名空间纪律:每次注册调用声明一个路径前缀(如 '/api/v2/<pack>-<feature>/'),
|
|
10
|
+
* 该批路由必须全部落在前缀下;匹配发生在宿主自带路由之后、fallback 之前,
|
|
11
|
+
* 包不能遮蔽宿主路由。
|
|
12
|
+
*/
|
|
13
|
+
import type { PackBackendRoute, PackBackendRouteRequest, PackBackendRouteResponse } from '@steerable/pack-sdk';
|
|
14
|
+
export type { PackBackendRoute, PackBackendRouteRequest, PackBackendRouteResponse };
|
|
15
|
+
interface RegisteredPackBackendRoute extends PackBackendRoute {
|
|
16
|
+
readonly packId: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 注册一批包路由;`pathPrefix` 是本批路由的命名空间前缀(必须以
|
|
20
|
+
* '/api/v2/' 开头),越出前缀的路由在组装期直接抛错。
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerPackBackendRoutes(packId: string, pathPrefix: string, routes: readonly PackBackendRoute[]): void;
|
|
23
|
+
/** 测试钩子:清空注册表。 */
|
|
24
|
+
export declare function resetPackBackendRoutes(): void;
|
|
25
|
+
/**
|
|
26
|
+
* 匹配已注册的包路由。段参数(`:id`)逐段捕获进 params;无匹配返回 null。
|
|
27
|
+
*/
|
|
28
|
+
export declare function matchPackBackendRoute(method: string, pathname: string): {
|
|
29
|
+
route: RegisteredPackBackendRoute;
|
|
30
|
+
params: Record<string, string>;
|
|
31
|
+
} | null;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 场景包 local-backend 路由注册表(1.2)。
|
|
3
|
+
*
|
|
4
|
+
* 与 `host/http-routes.ts`(BS 宿主 `/host/<packId>/*`)不同:本注册表管
|
|
5
|
+
* `/api/v2/*` 命名空间下的包路由——CS/BS 双宿主都经 LocalBackendRouter
|
|
6
|
+
* 服务这一命名空间(CS 走 localBackend.request IPC 桥,BS 走 HTTP),所以
|
|
7
|
+
* 包的业务路由必须挂在这里才能双宿主可达。
|
|
8
|
+
*
|
|
9
|
+
* 命名空间纪律:每次注册调用声明一个路径前缀(如 '/api/v2/<pack>-<feature>/'),
|
|
10
|
+
* 该批路由必须全部落在前缀下;匹配发生在宿主自带路由之后、fallback 之前,
|
|
11
|
+
* 包不能遮蔽宿主路由。
|
|
12
|
+
*/
|
|
13
|
+
const packBackendRoutes = [];
|
|
14
|
+
/**
|
|
15
|
+
* 注册一批包路由;`pathPrefix` 是本批路由的命名空间前缀(必须以
|
|
16
|
+
* '/api/v2/' 开头),越出前缀的路由在组装期直接抛错。
|
|
17
|
+
*/
|
|
18
|
+
export function registerPackBackendRoutes(packId, pathPrefix, routes) {
|
|
19
|
+
if (!pathPrefix.startsWith('/api/v2/')) {
|
|
20
|
+
throw new Error(`[packs] local-backend 路由前缀 "${pathPrefix}" 不合规:包 "${packId}" 的前缀必须在 /api/v2/ 命名空间下`);
|
|
21
|
+
}
|
|
22
|
+
for (const route of routes) {
|
|
23
|
+
if (!route.path.startsWith(pathPrefix)) {
|
|
24
|
+
throw new Error(`[packs] local-backend 路由 "${route.path}" 不合规:包 "${packId}" 该批路由必须在 ${pathPrefix} 前缀下`);
|
|
25
|
+
}
|
|
26
|
+
packBackendRoutes.push({ ...route, packId });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** 测试钩子:清空注册表。 */
|
|
30
|
+
export function resetPackBackendRoutes() {
|
|
31
|
+
packBackendRoutes.length = 0;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 匹配已注册的包路由。段参数(`:id`)逐段捕获进 params;无匹配返回 null。
|
|
35
|
+
*/
|
|
36
|
+
export function matchPackBackendRoute(method, pathname) {
|
|
37
|
+
for (const route of packBackendRoutes) {
|
|
38
|
+
if (route.method !== method)
|
|
39
|
+
continue;
|
|
40
|
+
const params = matchPath(route.path, pathname);
|
|
41
|
+
if (params)
|
|
42
|
+
return { route, params };
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function matchPath(pattern, pathname) {
|
|
47
|
+
const patternSegs = pattern.split('/');
|
|
48
|
+
const pathSegs = pathname.split('/');
|
|
49
|
+
if (patternSegs.length !== pathSegs.length)
|
|
50
|
+
return null;
|
|
51
|
+
const params = {};
|
|
52
|
+
for (let i = 0; i < patternSegs.length; i += 1) {
|
|
53
|
+
const seg = patternSegs[i];
|
|
54
|
+
if (seg.startsWith(':')) {
|
|
55
|
+
params[seg.slice(1)] = decodeURIComponent(pathSegs[i]);
|
|
56
|
+
}
|
|
57
|
+
else if (seg !== pathSegs[i]) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return params;
|
|
62
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 场景包的「回合级」钩子注册表(1.2 新增)。
|
|
3
|
+
*
|
|
4
|
+
* 包的路由/服务可以完全挂在注册表上,但有一类扩展发生在**每一条正在
|
|
5
|
+
* 流式的 CoreLoop 回合内部**。现存四个钩子位(以「文档预览包」为典型
|
|
6
|
+
* 用例):
|
|
7
|
+
*
|
|
8
|
+
* 1. `execWritableRoots`:包要把产物写进每会话的工作区,该目录在项目
|
|
9
|
+
* 根之外,必须随回合的 exec 沙箱放行(写,不是只读根);
|
|
10
|
+
* 2. `worldState`:把工作区路径喂给模型的 world-state,catalog 路径下
|
|
11
|
+
* 模型据此找到工作区;
|
|
12
|
+
* 3. `forcedSkillVars`:显式召唤包技能时把技能正文里的占位符渲染出
|
|
13
|
+
* 工作区路径;
|
|
14
|
+
* 4. `beginTurn` 返回的回合观察器:识别「本轮真的调用了包技能」
|
|
15
|
+
* (onToolStart),在工具落定后扫描工作区并向渲染层广播产物更新
|
|
16
|
+
* (onToolSettled)——替代前端轮询。
|
|
17
|
+
*
|
|
18
|
+
* 钩子按包注册、按回合拼装;未注册任何钩子的产品走零开销路径
|
|
19
|
+
* (空数组/undefined 合并,行为与注册前逐字节一致)。
|
|
20
|
+
*/
|
|
21
|
+
import type { PackTurnHooks, PackTurnObserver } from '@steerable/pack-sdk';
|
|
22
|
+
export type { PackTurnHooks, PackTurnObserver };
|
|
23
|
+
/** 注册包的回合钩子(重复注册同包后者覆盖——测试隔离靠 reset)。 */
|
|
24
|
+
export declare function registerPackTurnHooks(packId: string, hooks: PackTurnHooks): void;
|
|
25
|
+
/** 清空全部注册(测试用)。 */
|
|
26
|
+
export declare function resetPackTurnHooks(): void;
|
|
27
|
+
/** 全部激活包的 exec 可写根(按注册序拼接)。 */
|
|
28
|
+
export declare function collectPackExecWritableRoots(chatId: string): string[];
|
|
29
|
+
/** 全部激活包的 world-state 附加字段(后者覆盖同名键——包前缀命名避免碰撞)。 */
|
|
30
|
+
export declare function collectPackWorldState(chatId: string): Record<string, unknown>;
|
|
31
|
+
/** 全部激活包的强制技能变量(后者覆盖同名键)。 */
|
|
32
|
+
export declare function collectPackForcedSkillVars(chatId: string): Record<string, string>;
|
|
33
|
+
/** 回合开始:收集所有包的观察器(无注册时返回空数组,调用方零分支)。 */
|
|
34
|
+
export declare function beginPackTurnObservers(ctx: {
|
|
35
|
+
chatId: string;
|
|
36
|
+
broadcast: ((channel: string, payload: unknown) => void) | undefined;
|
|
37
|
+
}): PackTurnObserver[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 场景包的「回合级」钩子注册表(1.2 新增)。
|
|
3
|
+
*
|
|
4
|
+
* 包的路由/服务可以完全挂在注册表上,但有一类扩展发生在**每一条正在
|
|
5
|
+
* 流式的 CoreLoop 回合内部**。现存四个钩子位(以「文档预览包」为典型
|
|
6
|
+
* 用例):
|
|
7
|
+
*
|
|
8
|
+
* 1. `execWritableRoots`:包要把产物写进每会话的工作区,该目录在项目
|
|
9
|
+
* 根之外,必须随回合的 exec 沙箱放行(写,不是只读根);
|
|
10
|
+
* 2. `worldState`:把工作区路径喂给模型的 world-state,catalog 路径下
|
|
11
|
+
* 模型据此找到工作区;
|
|
12
|
+
* 3. `forcedSkillVars`:显式召唤包技能时把技能正文里的占位符渲染出
|
|
13
|
+
* 工作区路径;
|
|
14
|
+
* 4. `beginTurn` 返回的回合观察器:识别「本轮真的调用了包技能」
|
|
15
|
+
* (onToolStart),在工具落定后扫描工作区并向渲染层广播产物更新
|
|
16
|
+
* (onToolSettled)——替代前端轮询。
|
|
17
|
+
*
|
|
18
|
+
* 钩子按包注册、按回合拼装;未注册任何钩子的产品走零开销路径
|
|
19
|
+
* (空数组/undefined 合并,行为与注册前逐字节一致)。
|
|
20
|
+
*/
|
|
21
|
+
const hooksByPack = new Map();
|
|
22
|
+
/** 注册包的回合钩子(重复注册同包后者覆盖——测试隔离靠 reset)。 */
|
|
23
|
+
export function registerPackTurnHooks(packId, hooks) {
|
|
24
|
+
hooksByPack.set(packId, hooks);
|
|
25
|
+
}
|
|
26
|
+
/** 清空全部注册(测试用)。 */
|
|
27
|
+
export function resetPackTurnHooks() {
|
|
28
|
+
hooksByPack.clear();
|
|
29
|
+
}
|
|
30
|
+
/** 全部激活包的 exec 可写根(按注册序拼接)。 */
|
|
31
|
+
export function collectPackExecWritableRoots(chatId) {
|
|
32
|
+
const roots = [];
|
|
33
|
+
for (const hooks of hooksByPack.values()) {
|
|
34
|
+
roots.push(...(hooks.execWritableRoots?.(chatId) ?? []));
|
|
35
|
+
}
|
|
36
|
+
return roots;
|
|
37
|
+
}
|
|
38
|
+
/** 全部激活包的 world-state 附加字段(后者覆盖同名键——包前缀命名避免碰撞)。 */
|
|
39
|
+
export function collectPackWorldState(chatId) {
|
|
40
|
+
let merged = {};
|
|
41
|
+
for (const hooks of hooksByPack.values()) {
|
|
42
|
+
const extra = hooks.worldState?.(chatId);
|
|
43
|
+
if (extra)
|
|
44
|
+
merged = { ...merged, ...extra };
|
|
45
|
+
}
|
|
46
|
+
return merged;
|
|
47
|
+
}
|
|
48
|
+
/** 全部激活包的强制技能变量(后者覆盖同名键)。 */
|
|
49
|
+
export function collectPackForcedSkillVars(chatId) {
|
|
50
|
+
let merged = {};
|
|
51
|
+
for (const hooks of hooksByPack.values()) {
|
|
52
|
+
const extra = hooks.forcedSkillVars?.(chatId);
|
|
53
|
+
if (extra)
|
|
54
|
+
merged = { ...merged, ...extra };
|
|
55
|
+
}
|
|
56
|
+
return merged;
|
|
57
|
+
}
|
|
58
|
+
/** 回合开始:收集所有包的观察器(无注册时返回空数组,调用方零分支)。 */
|
|
59
|
+
export function beginPackTurnObservers(ctx) {
|
|
60
|
+
const observers = [];
|
|
61
|
+
for (const hooks of hooksByPack.values()) {
|
|
62
|
+
const observer = hooks.beginTurn?.(ctx);
|
|
63
|
+
if (observer)
|
|
64
|
+
observers.push(observer);
|
|
65
|
+
}
|
|
66
|
+
return observers;
|
|
67
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SystemPromptBuilder for the local desktop agent.
|
|
3
|
+
*
|
|
4
|
+
* Assembles a system prompt by loading skill modules from
|
|
5
|
+
* `src/local-backend/skills/<dir>/SKILL.md` and concatenating their bodies
|
|
6
|
+
* in directory-name order. Skills can be filtered by runtime conditions
|
|
7
|
+
* (e.g. `tool:<tool_name>`) and trimmed by character budget when too
|
|
8
|
+
* large (lowest-priority skills are dropped first).
|
|
9
|
+
*
|
|
10
|
+
* This is the TS counterpart of
|
|
11
|
+
* 上游 Python 服务的 SystemPromptBuilder。
|
|
12
|
+
*/
|
|
13
|
+
import { type SkillModule } from './skill-loader.js';
|
|
14
|
+
export interface BuildPromptOptions {
|
|
15
|
+
/** Runtime conditions e.g. `tool:<tool_name>`, `has-tools`. */
|
|
16
|
+
conditions?: Iterable<string>;
|
|
17
|
+
/** Per-tool names available this turn; auto-derives `tool:<name>` conditions. */
|
|
18
|
+
toolNames?: Iterable<string>;
|
|
19
|
+
/** Optional persona preamble (`AgentProfile.role_prompt`). Rendered first. */
|
|
20
|
+
personaPreamble?: string | null;
|
|
21
|
+
/** Optional per-chat custom system prompt; rendered after persona. */
|
|
22
|
+
chatSystemPrompt?: string | null;
|
|
23
|
+
/** Optional realityCheck suffix (e.g. "本轮可用工具…"). Rendered last. */
|
|
24
|
+
realityCheckSuffix?: string;
|
|
25
|
+
/** Character budget (rough proxy for tokens). Defaults to {@link DEFAULT_CHAR_BUDGET}. */
|
|
26
|
+
charBudget?: number;
|
|
27
|
+
/** Inject `has-tools` automatically when toolNames is non-empty. Default true. */
|
|
28
|
+
autoHasTools?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 用户通过 "/mcp__srv__tool" 显式指定的 MCP 工具。渲染为末尾指令模块,
|
|
31
|
+
* 配合首轮 tool_choice='required' 保证真的发起调用。
|
|
32
|
+
*/
|
|
33
|
+
forcedMcpTool?: ForcedMcpTool;
|
|
34
|
+
/** Override skills directory (mainly for tests; bypasses built-in + user dirs). */
|
|
35
|
+
skillsDir?: string;
|
|
36
|
+
/** Ignore all loading conditions and load all skills. */
|
|
37
|
+
ignoreConditions?: boolean;
|
|
38
|
+
/** Skill names to always drop, even under `ignoreConditions`. */
|
|
39
|
+
excludeSkillNames?: Iterable<string>;
|
|
40
|
+
/**
|
|
41
|
+
* A6 分层披露:true 时只注入 eager 层技能正文;catalog 层由 sidecar 以
|
|
42
|
+
* 「目录 + skill 工具按需加载」提供。CoreLoop 路径恒为 true。
|
|
43
|
+
*/
|
|
44
|
+
eagerOnly?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* 智能体勾选的技能别名(`skillIds`)。命中的技能正文无条件进系统提示词:
|
|
47
|
+
* 既绕过触发条件,也绕过 `eagerOnly` 的 catalog 层过滤。`excludeSkillNames`
|
|
48
|
+
* 仍然优先——模式级排除(plan 模式的执行类技能)不因勾选而失效。
|
|
49
|
+
*/
|
|
50
|
+
pinnedSkillNames?: Iterable<string>;
|
|
51
|
+
}
|
|
52
|
+
export interface BuiltPrompt {
|
|
53
|
+
prompt: string;
|
|
54
|
+
modules: SkillModule[];
|
|
55
|
+
droppedModules: string[];
|
|
56
|
+
conditions: string[];
|
|
57
|
+
}
|
|
58
|
+
/** 显式指定的 MCP 工具("/" 选择器 → cleanUserMessage → 此处)。 */
|
|
59
|
+
export interface ForcedMcpTool {
|
|
60
|
+
/** 完整一等工具名,如 mcp__demo_local__add。 */
|
|
61
|
+
token: string;
|
|
62
|
+
toolName: string;
|
|
63
|
+
serverName: string;
|
|
64
|
+
description: string;
|
|
65
|
+
/** false = 服务未启用/未连接或工具不存在——渲染"不可用"指令而非强制调用。 */
|
|
66
|
+
available: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Derive condition strings from the set of tool names available this turn.
|
|
70
|
+
*
|
|
71
|
+
* ['some_pack_tool', 'local_exec_shell']
|
|
72
|
+
* → ['tool:some_pack_tool', 'tool:local_exec_shell']
|
|
73
|
+
*/
|
|
74
|
+
export declare function conditionsFromTools(toolNames: Iterable<string>, autoHasTools?: boolean): Set<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Assemble the final system prompt:
|
|
77
|
+
*
|
|
78
|
+
* [persona preamble]? (agent role / DSL)
|
|
79
|
+
* [chat-level prompt]? (per-chat custom override)
|
|
80
|
+
* [skill 1 body]
|
|
81
|
+
* ...
|
|
82
|
+
* [skill N body]
|
|
83
|
+
* [reality check]? (turn-specific tool list / pollution warning)
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildSystemPrompt(options?: BuildPromptOptions): Promise<BuiltPrompt>;
|
|
86
|
+
/**
|
|
87
|
+
* 品牌占位符变量:技能正文里的 `{agentName}` / `{brandDisplayName}` /
|
|
88
|
+
* `{brandTagline}` 渲染为当前产品注入的品牌(brand.ts;未注入 = shell
|
|
89
|
+
* 中性默认)。eager 系统提示词路径统一注入;包技能同理可用。
|
|
90
|
+
*/
|
|
91
|
+
export declare function brandSkillVars(): Record<string, string>;
|
|
92
|
+
/** 技能正文渲染:把 `{scripts}` 及额外占位符解析为绝对路径。 */
|
|
93
|
+
export declare function renderSkillContent(m: SkillModule, vars?: Record<string, string>): string;
|
|
94
|
+
/** 用户显式指定技能的优先级指令头,渲染在该技能内容之前。 */
|
|
95
|
+
export declare function buildForcedSkillHeader(m: SkillModule): string;
|
|
96
|
+
/**
|
|
97
|
+
* "/技能名" 触发的一次性注入文本(A6):指令头 + 技能正文,由 router 并入
|
|
98
|
+
* 本轮最后一条用户消息——不再塞系统提示词尾部(系统提示词保持稳定,是
|
|
99
|
+
* prompt cache 的净收益;历史消息里也不会残留技能正文)。
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildForcedSkillMessage(m: SkillModule, vars?: Record<string, string>): string;
|