@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
package/dist/preload.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Demo bypass: pre-seed the renderer's localStorage with a long-lived JWT so
|
|
4
|
+
// the bundled web UI can talk to deeppath-api without going through the login
|
|
5
|
+
// flow. This is intentionally limited to the agent shell and only activates
|
|
6
|
+
// when AGENT_DEMO_TOKEN is set in the launch environment. Remove the env var
|
|
7
|
+
// (or leave it unset) for a normal authenticated experience.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const DEMO_TOKEN_KEY = 'deeppath_access_token';
|
|
10
|
+
const DEMO_USER_KEY = 'deeppath_user';
|
|
11
|
+
const demoToken = process.env.AGENT_DEMO_TOKEN;
|
|
12
|
+
const demoUserJson = process.env.AGENT_DEMO_USER;
|
|
13
|
+
function seedDemoCredentials() {
|
|
14
|
+
if (!demoToken)
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
if (window.localStorage.getItem(DEMO_TOKEN_KEY) !== demoToken) {
|
|
18
|
+
window.localStorage.setItem(DEMO_TOKEN_KEY, demoToken);
|
|
19
|
+
}
|
|
20
|
+
if (demoUserJson && !window.localStorage.getItem(DEMO_USER_KEY)) {
|
|
21
|
+
window.localStorage.setItem(DEMO_USER_KEY, demoUserJson);
|
|
22
|
+
}
|
|
23
|
+
const expires = new Date(Date.now() + 30 * 864e5).toUTCString();
|
|
24
|
+
document.cookie = `${DEMO_TOKEN_KEY}=${encodeURIComponent(demoToken)}; expires=${expires}; path=/; SameSite=Lax`;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
console.error('[agent-shell] failed to seed demo token', err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
seedDemoCredentials();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// window/document may not be ready yet; retry on DOMContentLoaded.
|
|
35
|
+
}
|
|
36
|
+
window.addEventListener('DOMContentLoaded', seedDemoCredentials);
|
|
37
|
+
/**
|
|
38
|
+
* 构建并暴露 window.electron API(2.3 pack-preload 组合)。
|
|
39
|
+
*
|
|
40
|
+
* 包贡献经参数合并进顶层命名空间(如某包给出 `{ <pack>, <pack>Mock }`
|
|
41
|
+
* 这样的 invoke 型命名空间);shell 本体不含任何
|
|
42
|
+
* 包符号。由产品 preload 入口(products/<id>/preload.ts)或 dev 入口
|
|
43
|
+
* (devtools/dev-preload.ts)调用。
|
|
44
|
+
*/
|
|
45
|
+
export function buildPreloadApi(packContributions = {}) {
|
|
46
|
+
const electronAPI = {
|
|
47
|
+
runtime: 'local',
|
|
48
|
+
platform: process.platform,
|
|
49
|
+
onThemeChanged: (callback) => {
|
|
50
|
+
ipcRenderer.on('theme-changed', (_event, isDark) => callback(isDark));
|
|
51
|
+
},
|
|
52
|
+
offThemeChanged: () => {
|
|
53
|
+
ipcRenderer.removeAllListeners('theme-changed');
|
|
54
|
+
},
|
|
55
|
+
retryConnection: () => {
|
|
56
|
+
ipcRenderer.send('retry-connection');
|
|
57
|
+
},
|
|
58
|
+
checkNetworkStatus: async () => {
|
|
59
|
+
return await ipcRenderer.invoke('check-network-status');
|
|
60
|
+
},
|
|
61
|
+
onNetworkStatusChanged: (callback) => {
|
|
62
|
+
ipcRenderer.on('network-status-changed', callback);
|
|
63
|
+
},
|
|
64
|
+
offNetworkStatusChanged: () => {
|
|
65
|
+
ipcRenderer.removeAllListeners('network-status-changed');
|
|
66
|
+
},
|
|
67
|
+
showNotification: (payload) => {
|
|
68
|
+
ipcRenderer.send('show-notification', payload);
|
|
69
|
+
},
|
|
70
|
+
onNotificationClicked: (callback) => {
|
|
71
|
+
ipcRenderer.on('notification-clicked', (_event, data) => callback(data));
|
|
72
|
+
},
|
|
73
|
+
offNotificationClicked: () => {
|
|
74
|
+
ipcRenderer.removeAllListeners('notification-clicked');
|
|
75
|
+
},
|
|
76
|
+
onMenuNewChat: (callback) => {
|
|
77
|
+
ipcRenderer.on('menu:new-chat', () => callback());
|
|
78
|
+
},
|
|
79
|
+
offMenuNewChat: () => {
|
|
80
|
+
ipcRenderer.removeAllListeners('menu:new-chat');
|
|
81
|
+
},
|
|
82
|
+
onMenuOpenTerminal: (callback) => {
|
|
83
|
+
ipcRenderer.on('menu:open-terminal', () => callback());
|
|
84
|
+
},
|
|
85
|
+
offMenuOpenTerminal: () => {
|
|
86
|
+
ipcRenderer.removeAllListeners('menu:open-terminal');
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* AI 标题异步就绪。Backend 在每条 chat 的首条助手回复完成后 fire-and-forget
|
|
90
|
+
* 跑 LLM 生成标题;完成后通过这个通道把 `{chatId, title}` 广播到所有 renderer。
|
|
91
|
+
*
|
|
92
|
+
* 返回 unsubscribe 函数——和 onMenuNewChat 不一样,这个通道有可能多处订阅
|
|
93
|
+
* (主窗 + 未来的设置窗),所以用 add/remove 配对而不是 removeAllListeners。
|
|
94
|
+
*/
|
|
95
|
+
onChatTitleUpdated: (callback) => {
|
|
96
|
+
const handler = (_event, payload) => {
|
|
97
|
+
if (typeof payload?.chatId === 'string' && typeof payload?.title === 'string') {
|
|
98
|
+
callback({ chatId: payload.chatId, title: payload.title });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
ipcRenderer.on('chat-title-updated', handler);
|
|
102
|
+
return () => {
|
|
103
|
+
ipcRenderer.removeListener('chat-title-updated', handler);
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* 会话补建通知:Backend 在向本地不存在的 chatId 首次发送时按 URL 里的 id
|
|
108
|
+
* 现场补建会话,并广播 `chat-created`({chatId, agentId})。返回 unsubscribe
|
|
109
|
+
* (多处订阅用 add/remove 配对)。
|
|
110
|
+
*/
|
|
111
|
+
onChatCreated: (callback) => {
|
|
112
|
+
const handler = (_event, payload) => {
|
|
113
|
+
if (typeof payload?.chatId === 'string' && payload.chatId) {
|
|
114
|
+
callback({
|
|
115
|
+
chatId: payload.chatId,
|
|
116
|
+
agentId: typeof payload.agentId === 'string' ? payload.agentId : null,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
ipcRenderer.on('chat-created', handler);
|
|
121
|
+
return () => {
|
|
122
|
+
ipcRenderer.removeListener('chat-created', handler);
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* 场景包广播事件订阅(3.1 通用化):按通道名订阅主进程广播,载荷
|
|
127
|
+
* 原样透传(收窄校验在订阅方——包——一侧)。通道名约定带包前缀
|
|
128
|
+
* (如 `<pack>:updated`)。返回解除订阅函数。
|
|
129
|
+
*/
|
|
130
|
+
onPackEvent: (channel, callback) => {
|
|
131
|
+
const handler = (_event, payload) => {
|
|
132
|
+
callback(payload);
|
|
133
|
+
};
|
|
134
|
+
ipcRenderer.on(channel, handler);
|
|
135
|
+
return () => {
|
|
136
|
+
ipcRenderer.removeListener(channel, handler);
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* 4.6a 后台任务状态推送:任务到达终态(completed/failed)或 worktree
|
|
141
|
+
* 合并/丢弃完成时,主进程广播 `task-updated`。载荷只带 chatId/taskId/
|
|
142
|
+
* status——renderer 收到后重新拉任务列表(GET /chats/:id/tasks),
|
|
143
|
+
* 不把整条任务记录塞进 IPC(避免与 SQLite 双写漂移)。
|
|
144
|
+
*/
|
|
145
|
+
onTaskUpdated: (callback) => {
|
|
146
|
+
const handler = (_event, payload) => {
|
|
147
|
+
if (typeof payload?.chatId === 'string' &&
|
|
148
|
+
typeof payload?.taskId === 'string' &&
|
|
149
|
+
typeof payload?.status === 'string') {
|
|
150
|
+
callback({ chatId: payload.chatId, taskId: payload.taskId, status: payload.status });
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
ipcRenderer.on('task-updated', handler);
|
|
154
|
+
return () => {
|
|
155
|
+
ipcRenderer.removeListener('task-updated', handler);
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
onTaskProcess: (callback) => {
|
|
159
|
+
const handler = (_event, payload) => {
|
|
160
|
+
if (typeof payload?.chatId !== 'string' || typeof payload?.taskId !== 'string')
|
|
161
|
+
return;
|
|
162
|
+
callback({
|
|
163
|
+
chatId: payload.chatId,
|
|
164
|
+
taskId: payload.taskId,
|
|
165
|
+
timeline: payload.timeline,
|
|
166
|
+
live: payload.live === true,
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
ipcRenderer.on('task-process', handler);
|
|
170
|
+
return () => {
|
|
171
|
+
ipcRenderer.removeListener('task-process', handler);
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
/**
|
|
175
|
+
* W4-1 审批代数:sidecar 的 ApprovalExecutor 经反向通道请示,主进程把
|
|
176
|
+
* 请求广播到 renderer(approval:request),审批弹窗应答后走
|
|
177
|
+
* approval:decide 回到主进程。订阅返回 unsubscribe;同一时刻只有一个
|
|
178
|
+
* 弹窗监听者(AgentPage 挂载的 ApprovalModalHost)。
|
|
179
|
+
*/
|
|
180
|
+
approval: {
|
|
181
|
+
onRequest: (callback) => {
|
|
182
|
+
const handler = (_event, payload) => {
|
|
183
|
+
const p = payload;
|
|
184
|
+
if (typeof p?.requestId === 'string' && typeof p?.toolName === 'string') {
|
|
185
|
+
callback(payload);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
ipcRenderer.on('approval:request', handler);
|
|
189
|
+
return () => {
|
|
190
|
+
ipcRenderer.removeListener('approval:request', handler);
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
decide: async (decision) => {
|
|
194
|
+
await ipcRenderer.invoke('approval:decide', decision);
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* W8 结构化提问:sidecar 的 ask_user 工具经反向通道请示,主进程把
|
|
199
|
+
* 请求广播到 renderer(ask-user:request),问题卡片应答后走
|
|
200
|
+
* ask-user:answer 回到主进程。订阅返回 unsubscribe;同一时刻只有一个
|
|
201
|
+
* 卡片监听者(AgentLayout 挂载的 AskUserModalHost)。
|
|
202
|
+
*/
|
|
203
|
+
askUser: {
|
|
204
|
+
onRequest: (callback) => {
|
|
205
|
+
const handler = (_event, payload) => {
|
|
206
|
+
const p = payload;
|
|
207
|
+
if (typeof p?.requestId === 'string' && Array.isArray(p?.questions)) {
|
|
208
|
+
callback(payload);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
ipcRenderer.on('ask-user:request', handler);
|
|
212
|
+
return () => {
|
|
213
|
+
ipcRenderer.removeListener('ask-user:request', handler);
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
answer: async (reply) => {
|
|
217
|
+
await ipcRenderer.invoke('ask-user:answer', reply);
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
local: {
|
|
221
|
+
selectDirectory: async (options) => {
|
|
222
|
+
return await ipcRenderer.invoke('local:select-directory', options);
|
|
223
|
+
},
|
|
224
|
+
saveTextFile: async (options) => {
|
|
225
|
+
return await ipcRenderer.invoke('local:save-text-file', options);
|
|
226
|
+
},
|
|
227
|
+
captureScreenshot: async (rect) => {
|
|
228
|
+
return await ipcRenderer.invoke('local:capture-screenshot', rect ? { rect } : {});
|
|
229
|
+
},
|
|
230
|
+
execShell: async (request) => {
|
|
231
|
+
return await ipcRenderer.invoke('local:exec-shell', request);
|
|
232
|
+
},
|
|
233
|
+
readFile: async (request) => {
|
|
234
|
+
return await ipcRenderer.invoke('local:read-file', request);
|
|
235
|
+
},
|
|
236
|
+
writeFile: async (request) => {
|
|
237
|
+
return await ipcRenderer.invoke('local:write-file', request);
|
|
238
|
+
},
|
|
239
|
+
openPath: async (request) => {
|
|
240
|
+
return await ipcRenderer.invoke('local:open-path', request);
|
|
241
|
+
},
|
|
242
|
+
listScripts: async () => {
|
|
243
|
+
return await ipcRenderer.invoke('local:list-scripts');
|
|
244
|
+
},
|
|
245
|
+
addScript: async (input) => {
|
|
246
|
+
return await ipcRenderer.invoke('local:add-script', input);
|
|
247
|
+
},
|
|
248
|
+
updateScript: async (id, updates) => {
|
|
249
|
+
return await ipcRenderer.invoke('local:update-script', { id, updates });
|
|
250
|
+
},
|
|
251
|
+
deleteScript: async (id) => {
|
|
252
|
+
return await ipcRenderer.invoke('local:delete-script', id);
|
|
253
|
+
},
|
|
254
|
+
runScript: async (id) => {
|
|
255
|
+
return await ipcRenderer.invoke('local:run-script', id);
|
|
256
|
+
},
|
|
257
|
+
getPlatformInfo: () => ({ platform: process.platform }),
|
|
258
|
+
updateSafetyConfig: async (config) => {
|
|
259
|
+
return await ipcRenderer.invoke('local:update-safety-config', config);
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
/** 会话附件:把源文件路径 / base64 字节拷贝进会话空间(`attachments:save`)。 */
|
|
263
|
+
attachments: {
|
|
264
|
+
save: async (input) => {
|
|
265
|
+
return await ipcRenderer.invoke('attachments:save', input);
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
localBackend: {
|
|
269
|
+
request: async (input) => {
|
|
270
|
+
const result = await ipcRenderer.invoke('local-backend:request', input);
|
|
271
|
+
if (!result.ok) {
|
|
272
|
+
const err = new Error(result.error || 'Local backend request failed');
|
|
273
|
+
err.status = result.status;
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
return result.data;
|
|
277
|
+
},
|
|
278
|
+
/**
|
|
279
|
+
* 启动一条本地后端流。
|
|
280
|
+
* 注意:Electron contextBridge 不支持把 ReadableStream 跨上下文传给 renderer
|
|
281
|
+
* (structured clone 会把它剥成空对象,导致 web UI 拿到的 Response.body 永远不出数据)。
|
|
282
|
+
* 这里只暴露 `startStream(input, onEvent)` 原语,由 web UI 端在 renderer 上下文
|
|
283
|
+
* 内自己构造 ReadableStream 并喂入 onEvent 回调(callback 跨上下文是 proxied 的)。
|
|
284
|
+
*
|
|
285
|
+
* 返回 streamId 用于后续 cancelStream(id)。
|
|
286
|
+
*/
|
|
287
|
+
startStream: async (input, onEvent) => {
|
|
288
|
+
const result = (await ipcRenderer.invoke('local-backend:stream', input));
|
|
289
|
+
if (!result.ok || !result.streamId) {
|
|
290
|
+
try {
|
|
291
|
+
onEvent({ type: 'error', error: result.error || `start stream failed (${result.status ?? 500})` });
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// best-effort: callback 抛错不应影响 IPC 路径
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const channel = `local-backend:stream:${result.streamId}`;
|
|
299
|
+
const handler = (_event, payload) => {
|
|
300
|
+
try {
|
|
301
|
+
onEvent(payload);
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
console.error('[preload] stream event callback threw', err);
|
|
305
|
+
}
|
|
306
|
+
if (payload.type === 'end' || payload.type === 'error') {
|
|
307
|
+
ipcRenderer.removeListener(channel, handler);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
ipcRenderer.on(channel, handler);
|
|
311
|
+
return result.streamId;
|
|
312
|
+
},
|
|
313
|
+
cancelStream: (streamId) => {
|
|
314
|
+
ipcRenderer.send(`local-backend:stream:${streamId}:cancel`);
|
|
315
|
+
},
|
|
316
|
+
/**
|
|
317
|
+
* 轮中转向:把一条用户消息注入该 chat 正在运行的 CoreLoop 回合。
|
|
318
|
+
* 返回 true 表示已被接受(UI 立即上屏);false 表示当前没有可转向的
|
|
319
|
+
* 回合(TS 循环路径或回合刚结束)——调用方应保留草稿。
|
|
320
|
+
*/
|
|
321
|
+
steerChat: async (chatId, content) => {
|
|
322
|
+
const result = (await ipcRenderer.invoke('local-backend:steer', {
|
|
323
|
+
chatId,
|
|
324
|
+
content,
|
|
325
|
+
}));
|
|
326
|
+
return result?.ok === true;
|
|
327
|
+
},
|
|
328
|
+
traces: {
|
|
329
|
+
list: async (chatId, limit = 50) => {
|
|
330
|
+
const path = `/api/v2/local/traces?chatId=${encodeURIComponent(chatId)}&limit=${limit}`;
|
|
331
|
+
return await electronAPI.localBackend.request({ method: 'GET', path });
|
|
332
|
+
},
|
|
333
|
+
get: async (traceId) => {
|
|
334
|
+
const path = `/api/v2/local/traces/${encodeURIComponent(traceId)}`;
|
|
335
|
+
return await electronAPI.localBackend.request({ method: 'GET', path });
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
agent: {
|
|
340
|
+
hasToken: async () => {
|
|
341
|
+
return true;
|
|
342
|
+
},
|
|
343
|
+
provision: async (_userJWT) => {
|
|
344
|
+
return { success: true };
|
|
345
|
+
},
|
|
346
|
+
start: async () => {
|
|
347
|
+
return { success: true };
|
|
348
|
+
},
|
|
349
|
+
getStatus: async () => {
|
|
350
|
+
return { status: 'registered', agentId: 'local-agent' };
|
|
351
|
+
},
|
|
352
|
+
getInfo: async () => {
|
|
353
|
+
return {
|
|
354
|
+
id: 'local-agent',
|
|
355
|
+
machineId: 'local',
|
|
356
|
+
name: 'Local Agent',
|
|
357
|
+
platform: process.platform,
|
|
358
|
+
hostname: 'localhost',
|
|
359
|
+
shell: process.platform === 'darwin' ? 'zsh' : 'bash',
|
|
360
|
+
isOnline: true,
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
onNeedProvision: (_callback) => undefined,
|
|
364
|
+
offNeedProvision: () => undefined,
|
|
365
|
+
},
|
|
366
|
+
terminal: {
|
|
367
|
+
list: async () => {
|
|
368
|
+
return await ipcRenderer.invoke('terminal:list');
|
|
369
|
+
},
|
|
370
|
+
spawn: async (options = {}) => {
|
|
371
|
+
return await ipcRenderer.invoke('terminal:spawn', options);
|
|
372
|
+
},
|
|
373
|
+
ensure: async (options = {}) => {
|
|
374
|
+
return await ipcRenderer.invoke('terminal:ensure', options);
|
|
375
|
+
},
|
|
376
|
+
write: async (id, data) => {
|
|
377
|
+
return await ipcRenderer.invoke('terminal:write', { id, data });
|
|
378
|
+
},
|
|
379
|
+
resize: async (id, cols, rows) => {
|
|
380
|
+
return await ipcRenderer.invoke('terminal:resize', { id, cols, rows });
|
|
381
|
+
},
|
|
382
|
+
kill: async (id) => {
|
|
383
|
+
return await ipcRenderer.invoke('terminal:kill', id);
|
|
384
|
+
},
|
|
385
|
+
exec: async (payload) => {
|
|
386
|
+
return await ipcRenderer.invoke('terminal:exec', payload);
|
|
387
|
+
},
|
|
388
|
+
onData: (callback) => {
|
|
389
|
+
const fn = (_event, payload) => callback(payload);
|
|
390
|
+
ipcRenderer.on('terminal:data', fn);
|
|
391
|
+
return () => ipcRenderer.removeListener('terminal:data', fn);
|
|
392
|
+
},
|
|
393
|
+
onExit: (callback) => {
|
|
394
|
+
const fn = (_event, payload) => callback(payload);
|
|
395
|
+
ipcRenderer.on('terminal:exit', fn);
|
|
396
|
+
return () => ipcRenderer.removeListener('terminal:exit', fn);
|
|
397
|
+
},
|
|
398
|
+
onSpawned: (callback) => {
|
|
399
|
+
const fn = (_event, session) => callback(session);
|
|
400
|
+
ipcRenderer.on('terminal:spawned', fn);
|
|
401
|
+
return () => ipcRenderer.removeListener('terminal:spawned', fn);
|
|
402
|
+
},
|
|
403
|
+
onReveal: (callback) => {
|
|
404
|
+
const fn = (_event, payload) => callback(payload);
|
|
405
|
+
ipcRenderer.on('terminal:reveal', fn);
|
|
406
|
+
return () => ipcRenderer.removeListener('terminal:reveal', fn);
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
contextBridge.exposeInMainWorld('electron', { ...electronAPI, ...packContributions });
|
|
411
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 产品注入配置(3.1):shell 不硬编码任何产品特有的端点/链接/目录名,
|
|
3
|
+
* 由产品组装根(products/<id>/active.ts)在模块求值早期注入。
|
|
4
|
+
*
|
|
5
|
+
* 与 brand.ts 的分工:brand 是「产品身份」(显示名/自称/默认智能体),
|
|
6
|
+
* 本模块是「产品资源」(遥测端点、帮助链接、数据目录)。两者都由产品
|
|
7
|
+
* 组装根注入;缺省时 shell 表现为中性框架(无遥测、无帮助链接、
|
|
8
|
+
* 数据目录 .agent-shell)。
|
|
9
|
+
*
|
|
10
|
+
* 本模块只允许依赖 node 内置模块(与 brand.ts 同约束)。
|
|
11
|
+
*/
|
|
12
|
+
export interface ProductLinks {
|
|
13
|
+
/** 帮助菜单「打开发布页」URL(通常是产品的下载/更新通道页)。 */
|
|
14
|
+
releasePage?: string;
|
|
15
|
+
/** 帮助菜单「访问官网」URL。 */
|
|
16
|
+
website?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ProductConfig {
|
|
19
|
+
/**
|
|
20
|
+
* 云端遥测端点(insights flush 的 API base)。空 = 不上报(中性默认);
|
|
21
|
+
* 用户设置里的 apiBase 与环境变量仍可覆盖。
|
|
22
|
+
*/
|
|
23
|
+
insightsApiBase?: string;
|
|
24
|
+
/** 帮助菜单链接;缺省的项不渲染。 */
|
|
25
|
+
links?: ProductLinks;
|
|
26
|
+
/**
|
|
27
|
+
* BS 模式的 userData 目录名(~/ 下)。CS 模式由 Electron 按
|
|
28
|
+
* productName 分目录,不经此字段。
|
|
29
|
+
*/
|
|
30
|
+
dataDirName?: string;
|
|
31
|
+
/**
|
|
32
|
+
* 主 SQLite 文件名(userData 目录下)。产品必须显式声明以保住存量
|
|
33
|
+
* 数据;中性 shell 缺省 'agent-shell.db'。
|
|
34
|
+
*/
|
|
35
|
+
dbFileName?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 注入产品配置(产品组装根在第一个 import 时调用)。重复注入抛错
|
|
39
|
+
* (组装期笔误,fail fast——与 setProductBrand 同语义)。
|
|
40
|
+
*/
|
|
41
|
+
export declare function setProductConfig(config: ProductConfig): void;
|
|
42
|
+
/** 读取已注入的产品配置;未注入返回空对象(中性框架行为)。 */
|
|
43
|
+
export declare function getProductConfig(): ProductConfig;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 产品注入配置(3.1):shell 不硬编码任何产品特有的端点/链接/目录名,
|
|
3
|
+
* 由产品组装根(products/<id>/active.ts)在模块求值早期注入。
|
|
4
|
+
*
|
|
5
|
+
* 与 brand.ts 的分工:brand 是「产品身份」(显示名/自称/默认智能体),
|
|
6
|
+
* 本模块是「产品资源」(遥测端点、帮助链接、数据目录)。两者都由产品
|
|
7
|
+
* 组装根注入;缺省时 shell 表现为中性框架(无遥测、无帮助链接、
|
|
8
|
+
* 数据目录 .agent-shell)。
|
|
9
|
+
*
|
|
10
|
+
* 本模块只允许依赖 node 内置模块(与 brand.ts 同约束)。
|
|
11
|
+
*/
|
|
12
|
+
let productConfig = null;
|
|
13
|
+
/**
|
|
14
|
+
* 注入产品配置(产品组装根在第一个 import 时调用)。重复注入抛错
|
|
15
|
+
* (组装期笔误,fail fast——与 setProductBrand 同语义)。
|
|
16
|
+
*/
|
|
17
|
+
export function setProductConfig(config) {
|
|
18
|
+
if (productConfig) {
|
|
19
|
+
throw new Error('[product-config] product config already set');
|
|
20
|
+
}
|
|
21
|
+
productConfig = config;
|
|
22
|
+
}
|
|
23
|
+
/** 读取已注入的产品配置;未注入返回空对象(中性框架行为)。 */
|
|
24
|
+
export function getProductConfig() {
|
|
25
|
+
return productConfig ?? {};
|
|
26
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目注册表(项目模式)。
|
|
3
|
+
*
|
|
4
|
+
* 项目 = 名字 + 绑定的本地文件夹。chat 通过 `chat_sessions.project_id` 绑定
|
|
5
|
+
* 项目;绑定后该对话的文件读写与命令执行被硬限制在项目文件夹内(见
|
|
6
|
+
* tool-router.ts 的 ToolExecContext.projectRoot 与 local-executor.ts 的
|
|
7
|
+
* 路径围栏)。
|
|
8
|
+
*
|
|
9
|
+
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
10
|
+
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
11
|
+
* import electron,保持纯 Node 可测(与 mcp-server-registry.ts 同一模式)。
|
|
12
|
+
*/
|
|
13
|
+
export interface ProjectRecord {
|
|
14
|
+
id: string;
|
|
15
|
+
/** 用户可见名称(侧边栏分组标题)。 */
|
|
16
|
+
name: string;
|
|
17
|
+
/** 绑定的项目文件夹(绝对路径)。 */
|
|
18
|
+
folderPath: string;
|
|
19
|
+
/**
|
|
20
|
+
* W6-5 项目信任门控:项目目录里的 `AGENTS.md` / `CLAUDE.md` 等规则文件
|
|
21
|
+
* 是「项目作者写给 agent 的指令」——打开一个恶意仓库时,一段精心构造的
|
|
22
|
+
* 规则文件就能劫持 agent。因此项目级规则只在 `trusted: true` 时才加载进
|
|
23
|
+
* 模型上下文;默认不信任(安全缺省),用户显式授权后才加载,且可随时撤销。
|
|
24
|
+
* 旧记录没有此字段,读取时按 `false` 处理(见 {@link ProjectRegistry.isTrusted})。
|
|
25
|
+
*/
|
|
26
|
+
trusted?: boolean;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface CreateProjectInput {
|
|
31
|
+
name: string;
|
|
32
|
+
folderPath: string;
|
|
33
|
+
}
|
|
34
|
+
/** 最小 KV 存储接口,避免本模块直接依赖 electron-store。 */
|
|
35
|
+
export interface ProjectKvStore {
|
|
36
|
+
get(key: 'projects'): ProjectRecord[] | undefined;
|
|
37
|
+
set(key: 'projects', value: ProjectRecord[]): void;
|
|
38
|
+
}
|
|
39
|
+
export declare class ProjectRegistry {
|
|
40
|
+
private readonly store;
|
|
41
|
+
constructor(store: ProjectKvStore);
|
|
42
|
+
list(): ProjectRecord[];
|
|
43
|
+
get(idOrName: string): ProjectRecord | null;
|
|
44
|
+
create(input: CreateProjectInput): ProjectRecord;
|
|
45
|
+
update(id: string, updates: Partial<CreateProjectInput>): ProjectRecord;
|
|
46
|
+
delete(id: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* W6-5: read the trust flag, defaulting absent (legacy) records to
|
|
49
|
+
* `false` — fail-closed, so a project is never trusted unless the user
|
|
50
|
+
* explicitly said so.
|
|
51
|
+
*/
|
|
52
|
+
isTrusted(id: string): boolean;
|
|
53
|
+
/** W6-5: grant or revoke trust. Returns the updated record. */
|
|
54
|
+
setTrusted(id: string, trusted: boolean): ProjectRecord;
|
|
55
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目注册表(项目模式)。
|
|
3
|
+
*
|
|
4
|
+
* 项目 = 名字 + 绑定的本地文件夹。chat 通过 `chat_sessions.project_id` 绑定
|
|
5
|
+
* 项目;绑定后该对话的文件读写与命令执行被硬限制在项目文件夹内(见
|
|
6
|
+
* tool-router.ts 的 ToolExecContext.projectRoot 与 local-executor.ts 的
|
|
7
|
+
* 路径围栏)。
|
|
8
|
+
*
|
|
9
|
+
* 持久化在 userData/agent-projects.json。存储通过 {@link ProjectKvStore}
|
|
10
|
+
* 接口注入:main.ts 用 electron-store 实现,单测用内存实现——本模块不
|
|
11
|
+
* import electron,保持纯 Node 可测(与 mcp-server-registry.ts 同一模式)。
|
|
12
|
+
*/
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
14
|
+
const STORE_KEY = 'projects';
|
|
15
|
+
export class ProjectRegistry {
|
|
16
|
+
store;
|
|
17
|
+
constructor(store) {
|
|
18
|
+
this.store = store;
|
|
19
|
+
}
|
|
20
|
+
list() {
|
|
21
|
+
// 按创建时间升序:侧边栏项目组顺序稳定,早建的在前。
|
|
22
|
+
return [...(this.store.get(STORE_KEY) ?? [])].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
23
|
+
}
|
|
24
|
+
get(idOrName) {
|
|
25
|
+
const needle = idOrName.trim().toLowerCase();
|
|
26
|
+
return (this.list().find((p) => p.id === idOrName || p.name.toLowerCase() === needle) ?? null);
|
|
27
|
+
}
|
|
28
|
+
create(input) {
|
|
29
|
+
const name = input.name.trim();
|
|
30
|
+
const folderPath = input.folderPath.trim();
|
|
31
|
+
if (!name)
|
|
32
|
+
throw new Error('项目名称不能为空');
|
|
33
|
+
if (!folderPath)
|
|
34
|
+
throw new Error('项目文件夹不能为空');
|
|
35
|
+
if (this.get(name))
|
|
36
|
+
throw new Error(`已存在同名项目「${name}」`);
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
const entry = {
|
|
39
|
+
id: randomUUID(),
|
|
40
|
+
name,
|
|
41
|
+
folderPath,
|
|
42
|
+
createdAt: now,
|
|
43
|
+
updatedAt: now,
|
|
44
|
+
};
|
|
45
|
+
this.store.set(STORE_KEY, [...this.list(), entry]);
|
|
46
|
+
return entry;
|
|
47
|
+
}
|
|
48
|
+
update(id, updates) {
|
|
49
|
+
const projects = this.list();
|
|
50
|
+
const idx = projects.findIndex((p) => p.id === id);
|
|
51
|
+
if (idx === -1)
|
|
52
|
+
throw new Error('项目不存在');
|
|
53
|
+
const current = projects[idx];
|
|
54
|
+
const nextName = updates.name !== undefined ? updates.name.trim() : current.name;
|
|
55
|
+
if (!nextName)
|
|
56
|
+
throw new Error('项目名称不能为空');
|
|
57
|
+
const nameClash = projects.some((p) => p.id !== id && p.name.toLowerCase() === nextName.toLowerCase());
|
|
58
|
+
if (nameClash)
|
|
59
|
+
throw new Error(`已存在同名项目「${nextName}」`);
|
|
60
|
+
const nextFolder = updates.folderPath !== undefined
|
|
61
|
+
? updates.folderPath.trim()
|
|
62
|
+
: current.folderPath;
|
|
63
|
+
if (!nextFolder)
|
|
64
|
+
throw new Error('项目文件夹不能为空');
|
|
65
|
+
const next = {
|
|
66
|
+
...current,
|
|
67
|
+
name: nextName,
|
|
68
|
+
folderPath: nextFolder,
|
|
69
|
+
updatedAt: new Date().toISOString(),
|
|
70
|
+
};
|
|
71
|
+
projects[idx] = next;
|
|
72
|
+
this.store.set(STORE_KEY, projects);
|
|
73
|
+
return next;
|
|
74
|
+
}
|
|
75
|
+
delete(id) {
|
|
76
|
+
const projects = this.list();
|
|
77
|
+
const next = projects.filter((p) => p.id !== id);
|
|
78
|
+
if (next.length === projects.length)
|
|
79
|
+
return false;
|
|
80
|
+
this.store.set(STORE_KEY, next);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* W6-5: read the trust flag, defaulting absent (legacy) records to
|
|
85
|
+
* `false` — fail-closed, so a project is never trusted unless the user
|
|
86
|
+
* explicitly said so.
|
|
87
|
+
*/
|
|
88
|
+
isTrusted(id) {
|
|
89
|
+
return this.get(id)?.trusted === true;
|
|
90
|
+
}
|
|
91
|
+
/** W6-5: grant or revoke trust. Returns the updated record. */
|
|
92
|
+
setTrusted(id, trusted) {
|
|
93
|
+
const projects = this.list();
|
|
94
|
+
const idx = projects.findIndex((p) => p.id === id);
|
|
95
|
+
if (idx === -1)
|
|
96
|
+
throw new Error('项目不存在');
|
|
97
|
+
const next = {
|
|
98
|
+
...projects[idx],
|
|
99
|
+
trusted,
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
projects[idx] = next;
|
|
103
|
+
this.store.set(STORE_KEY, projects);
|
|
104
|
+
return next;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ProjectRules {
|
|
2
|
+
/** 实际发现的规则文件绝对路径,根→叶顺序(越靠后对当前项目越具体)。 */
|
|
3
|
+
files: string[];
|
|
4
|
+
/** 拼接好的注入文本(含每个文件的来源标注),已按上限截断。 */
|
|
5
|
+
content: string;
|
|
6
|
+
/** 是否有内容因上限被截断。 */
|
|
7
|
+
truncated: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 收集并拼接项目规则。`folderPath` 为绑定的项目根目录。
|
|
11
|
+
*
|
|
12
|
+
* 向上遍历在「用户 home 目录」处封顶(若项目不在 home 下则按 MAX_LEVELS
|
|
13
|
+
* 封顶)——不扫 `/`、`/etc` 这类与项目无关的祖先目录。
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadProjectRuleFiles(folderPath: string): ProjectRules;
|