@steerable/agent-shell 0.6.31 → 0.6.32
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/dist/host/runtime.js +8 -1
- package/dist/local-backend/router.d.ts +7 -4
- package/dist/local-backend/router.js +36 -13
- package/dist/local-backend/task-service.d.ts +5 -0
- package/dist/local-backend/task-service.js +6 -2
- package/dist/native-folder-dialog.d.ts +14 -0
- package/dist/native-folder-dialog.js +62 -0
- package/dist/project-home.d.ts +17 -0
- package/dist/project-home.js +27 -0
- package/dist/server/http-server.js +6 -0
- package/dist/sidecar/boot.d.ts +1 -1
- package/dist/sidecar/reverse-tools.d.ts +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/package.json +4 -4
package/dist/host/runtime.js
CHANGED
|
@@ -28,6 +28,7 @@ import { createVisibleTerminalExec } from './visible-terminal-exec.js';
|
|
|
28
28
|
import { createJsonStore } from '../json-store.js';
|
|
29
29
|
import { bindWorkspaceSkillRoots } from '../local-backend/skill-loader.js';
|
|
30
30
|
import { getChatAttachmentsDir } from '../attachments.js';
|
|
31
|
+
import { ensureChatWorkspace } from '../project-home.js';
|
|
31
32
|
import { recordInsightTurn } from '../insights/record.js';
|
|
32
33
|
import { llmService } from '../llm/index.js';
|
|
33
34
|
import { getPackAssemblies, } from './pack-assembly.js';
|
|
@@ -150,6 +151,12 @@ export async function createHostRuntime(options) {
|
|
|
150
151
|
toolRouter,
|
|
151
152
|
worktreeService,
|
|
152
153
|
resolveChatProject: resolveChatProjectRoot,
|
|
154
|
+
resolveChatWorkspaceRoot: async (chatId) => {
|
|
155
|
+
const project = await resolveChatProjectRoot(chatId);
|
|
156
|
+
if (project)
|
|
157
|
+
return project.folderPath;
|
|
158
|
+
return ensureChatWorkspace(chatId);
|
|
159
|
+
},
|
|
153
160
|
broadcast,
|
|
154
161
|
});
|
|
155
162
|
toolRouter.setTaskServices({ taskService, worktreeService });
|
|
@@ -204,7 +211,7 @@ export async function createHostRuntime(options) {
|
|
|
204
211
|
void startHostSidecar({
|
|
205
212
|
store: defaultStore,
|
|
206
213
|
toolRouter,
|
|
207
|
-
resolveProjectRoot: async (chatId) =>
|
|
214
|
+
resolveProjectRoot: async (chatId) => localBackendRouter.resolveChatWorkspaceRoot(chatId),
|
|
208
215
|
// 项目模式下文件读写被围栏在项目目录内;会话附件目录是额外放行的
|
|
209
216
|
// 只读根,保证用户上传的文件即使在项目会话里也能被 agent 读回。
|
|
210
217
|
resolveAdditionalReadRoots: async (chatId) => {
|
|
@@ -98,16 +98,19 @@ export declare class LocalBackendRouter {
|
|
|
98
98
|
private runTitleGenInBackground;
|
|
99
99
|
/**
|
|
100
100
|
* 解析当前对话绑定的项目(项目模式)。chat 没绑项目、项目已被删除、或
|
|
101
|
-
* 注册表未注入时都返回 null
|
|
102
|
-
*
|
|
103
|
-
* Public: main.ts 的反向通道(reverse-tools.ts)经它给 CoreLoop 路径的
|
|
104
|
-
* 工具调用补上 projectRoot 围栏。
|
|
101
|
+
* 注册表未注入时都返回 null。无项目对话的可写根见
|
|
102
|
+
* {@link LocalBackendRouter.resolveChatWorkspaceRoot}。
|
|
105
103
|
*/
|
|
106
104
|
resolveChatProject(chatId: string): Promise<{
|
|
107
105
|
name: string;
|
|
108
106
|
folderPath: string;
|
|
109
107
|
sourceFolders: string[];
|
|
110
108
|
} | null>;
|
|
109
|
+
/**
|
|
110
|
+
* 本对话的可写根:绑了项目用项目家目录,否则在
|
|
111
|
+
* `Documents/<应用名>/conversations/<chatId>/` 建对话工作区。
|
|
112
|
+
*/
|
|
113
|
+
resolveChatWorkspaceRoot(chatId: string): Promise<string>;
|
|
111
114
|
/**
|
|
112
115
|
* 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
|
|
113
116
|
*
|
|
@@ -38,7 +38,7 @@ import { detectInterruptedTurn } from './interrupted-helper.js';
|
|
|
38
38
|
import { dropCurrentUserMessage } from './history-helper.js';
|
|
39
39
|
import { parseImageAttachments, processImageAttachments } from '../image-attachment.js';
|
|
40
40
|
import { loadProjectRuleFiles } from '../project-rules.js';
|
|
41
|
-
import { allocateProjectHome, ensureProjectHome } from '../project-home.js';
|
|
41
|
+
import { allocateProjectHome, ensureChatWorkspace, ensureProjectHome, } from '../project-home.js';
|
|
42
42
|
import { registerLiveStream, getLiveStream, removeLiveStream } from './live-stream.js';
|
|
43
43
|
import { beginPackTurnObservers, collectPackExecWritableRoots, collectPackForcedSkillVars, collectPackWorldState, } from './pack-turn-hooks.js';
|
|
44
44
|
import { matchPackBackendRoute } from './pack-backend-routes.js';
|
|
@@ -1749,7 +1749,7 @@ export class LocalBackendRouter {
|
|
|
1749
1749
|
}
|
|
1750
1750
|
// 正文里提到的路径能否点击打开:渲染层把行内代码里「像路径」的字面量
|
|
1751
1751
|
// 批量送来,这里落地成绝对路径并 stat 验证,只有真实存在的才回。相对
|
|
1752
|
-
//
|
|
1752
|
+
// 路径按对话绑定的项目根解析,无项目时按对话工作区。
|
|
1753
1753
|
if (method === 'POST' && pathname === '/api/v2/local/resolve-paths') {
|
|
1754
1754
|
const payload = this.toRecord(request.body);
|
|
1755
1755
|
const candidates = Array.isArray(payload.candidates)
|
|
@@ -1759,7 +1759,9 @@ export class LocalBackendRouter {
|
|
|
1759
1759
|
return { status: 200, data: { resolved: [] } };
|
|
1760
1760
|
}
|
|
1761
1761
|
const chatId = typeof payload.chatId === 'string' ? payload.chatId : '';
|
|
1762
|
-
const baseDir =
|
|
1762
|
+
const baseDir = chatId
|
|
1763
|
+
? await this.resolveChatWorkspaceRoot(chatId)
|
|
1764
|
+
: os.homedir();
|
|
1763
1765
|
const resolved = await resolveMentionedPaths({ candidates, baseDir });
|
|
1764
1766
|
return { status: 200, data: { resolved } };
|
|
1765
1767
|
}
|
|
@@ -2190,10 +2192,8 @@ export class LocalBackendRouter {
|
|
|
2190
2192
|
}
|
|
2191
2193
|
/**
|
|
2192
2194
|
* 解析当前对话绑定的项目(项目模式)。chat 没绑项目、项目已被删除、或
|
|
2193
|
-
* 注册表未注入时都返回 null
|
|
2194
|
-
*
|
|
2195
|
-
* Public: main.ts 的反向通道(reverse-tools.ts)经它给 CoreLoop 路径的
|
|
2196
|
-
* 工具调用补上 projectRoot 围栏。
|
|
2195
|
+
* 注册表未注入时都返回 null。无项目对话的可写根见
|
|
2196
|
+
* {@link LocalBackendRouter.resolveChatWorkspaceRoot}。
|
|
2197
2197
|
*/
|
|
2198
2198
|
async resolveChatProject(chatId) {
|
|
2199
2199
|
const projectId = (await this.store.getChat(chatId))?.projectId;
|
|
@@ -2208,6 +2208,16 @@ export class LocalBackendRouter {
|
|
|
2208
2208
|
sourceFolders: project.sourceFolders ?? [],
|
|
2209
2209
|
};
|
|
2210
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* 本对话的可写根:绑了项目用项目家目录,否则在
|
|
2213
|
+
* `Documents/<应用名>/conversations/<chatId>/` 建对话工作区。
|
|
2214
|
+
*/
|
|
2215
|
+
async resolveChatWorkspaceRoot(chatId) {
|
|
2216
|
+
const project = await this.resolveChatProject(chatId);
|
|
2217
|
+
if (project)
|
|
2218
|
+
return project.folderPath;
|
|
2219
|
+
return ensureChatWorkspace(chatId);
|
|
2220
|
+
}
|
|
2211
2221
|
/**
|
|
2212
2222
|
* 解析本轮生效的智能体:人设前言、技能勾选、工具策略同源于这一次解析。
|
|
2213
2223
|
*
|
|
@@ -2396,6 +2406,9 @@ export class LocalBackendRouter {
|
|
|
2396
2406
|
// 都追加)。硬围栏在 ToolRouter/LocalExecutor,这里让模型事先知道边界,
|
|
2397
2407
|
// 减少越界尝试。
|
|
2398
2408
|
const chatProject = await this.resolveChatProject(chatId);
|
|
2409
|
+
const workspaceRoot = chatProject
|
|
2410
|
+
? chatProject.folderPath
|
|
2411
|
+
: await this.resolveChatWorkspaceRoot(chatId);
|
|
2399
2412
|
if (chatProject) {
|
|
2400
2413
|
systemPrompt +=
|
|
2401
2414
|
`\n\n【项目模式】当前对话绑定项目「${chatProject.name}」,家目录:${chatProject.folderPath}\n` +
|
|
@@ -2423,6 +2436,14 @@ export class LocalBackendRouter {
|
|
|
2423
2436
|
}
|
|
2424
2437
|
}
|
|
2425
2438
|
}
|
|
2439
|
+
else {
|
|
2440
|
+
systemPrompt +=
|
|
2441
|
+
`\n\n【对话工作区】当前对话没有绑定项目。工作区:${workspaceRoot}\n` +
|
|
2442
|
+
`你的文件写入(local_write_file)和命令执行(local_exec_shell)都被限制在该目录内:` +
|
|
2443
|
+
`写入路径越界会被拒绝;命令默认在工作区下运行,显式指定的 cwd 越界也会被拒绝。` +
|
|
2444
|
+
`请一律使用工作区内的路径(相对路径按工作区解析)。` +
|
|
2445
|
+
`用户若要把对话绑到已有项目,可在输入框上方选择项目。`;
|
|
2446
|
+
}
|
|
2426
2447
|
// @引用的历史对话(payload.referencedChatIds):把被引用对话的最近消息
|
|
2427
2448
|
// 摘录注入为 user 消息,放在本对话历史之前。
|
|
2428
2449
|
const referencedChatMessages = await this.buildReferencedChatContextMessages(payload, chatId);
|
|
@@ -2877,16 +2898,18 @@ export class LocalBackendRouter {
|
|
|
2877
2898
|
// spec (default.harness.yaml) is the single source of truth for
|
|
2878
2899
|
// maxRounds / maxToolErrors, and an explicit request param overrides it.
|
|
2879
2900
|
// W4-2: per-exec sandbox for shell tool calls, default-on. Writable
|
|
2880
|
-
// roots =
|
|
2881
|
-
// empty list, so writes confine to system scratch dirs only.
|
|
2901
|
+
// roots = 项目家目录,或无项目时 Documents/<应用>/conversations/<chatId>。
|
|
2882
2902
|
// `execPolicy: 'full'`(输入框「完整权限」)关闭这一层,让 mkdir
|
|
2883
|
-
// Downloads
|
|
2903
|
+
// Downloads 这类工作区外写入不再被 Seatbelt 拦成 Operation not permitted。
|
|
2884
2904
|
const chatProject = await this.resolveChatProject(chatId);
|
|
2905
|
+
const workspaceRoot = chatProject
|
|
2906
|
+
? chatProject.folderPath
|
|
2907
|
+
: await this.resolveChatWorkspaceRoot(chatId);
|
|
2885
2908
|
const execSandbox = buildExecSandbox([
|
|
2886
2909
|
// 场景包声明的每会话可写根(如文档包在项目根之外落盘产物的
|
|
2887
2910
|
// 工作区)。
|
|
2888
2911
|
...collectPackExecWritableRoots(chatId),
|
|
2889
|
-
|
|
2912
|
+
workspaceRoot,
|
|
2890
2913
|
], { policy: parseExecPolicy(payload.execPolicy) });
|
|
2891
2914
|
// W4-1: approval algebra, host mode — every tool call is gated by the
|
|
2892
2915
|
// Electron approval UI over the reverse channel. The durable scope
|
|
@@ -2929,7 +2952,7 @@ export class LocalBackendRouter {
|
|
|
2929
2952
|
const turnStartedAtMs = Date.now();
|
|
2930
2953
|
const turnFileRoots = [
|
|
2931
2954
|
...collectPackExecWritableRoots(chatId),
|
|
2932
|
-
|
|
2955
|
+
workspaceRoot,
|
|
2933
2956
|
];
|
|
2934
2957
|
try {
|
|
2935
2958
|
const coreLoopOptions = {
|
|
@@ -3219,7 +3242,7 @@ export class LocalBackendRouter {
|
|
|
3219
3242
|
roots: turnFileRoots,
|
|
3220
3243
|
sinceMs: turnStartedAtMs,
|
|
3221
3244
|
actions: executedActions,
|
|
3222
|
-
projectRoot:
|
|
3245
|
+
projectRoot: workspaceRoot,
|
|
3223
3246
|
});
|
|
3224
3247
|
}
|
|
3225
3248
|
catch (turnFilesErr) {
|
|
@@ -55,6 +55,11 @@ export interface TaskServiceDeps {
|
|
|
55
55
|
name: string;
|
|
56
56
|
folderPath: string;
|
|
57
57
|
} | null>;
|
|
58
|
+
/**
|
|
59
|
+
* 无项目对话的可写根(Documents/<应用>/conversations/<chatId>)。
|
|
60
|
+
* 测试可不传,围栏回落为仅 scratch。
|
|
61
|
+
*/
|
|
62
|
+
resolveChatWorkspaceRoot?: (chatId: string) => Promise<string>;
|
|
58
63
|
/** 任务终态 / 推理过程广播(renderer 任务面板与右侧过程栏)。 */
|
|
59
64
|
broadcast?: (eventName: 'task-updated' | 'task-process', payload: Record<string, unknown>) => void;
|
|
60
65
|
/** 测试缝:替换真实流驱动(默认 streamCoreLoopTurn)。 */
|
|
@@ -272,8 +272,12 @@ export class TaskService {
|
|
|
272
272
|
const settings = llmService.getSettings();
|
|
273
273
|
const project = await this.deps.resolveChatProject(chatId);
|
|
274
274
|
// 4.6b 整合:worktree 任务的围栏根 = worktree 路径(更紧——任务摸不到
|
|
275
|
-
//
|
|
276
|
-
const fenceRoot = worktreePath ??
|
|
275
|
+
// 主检出);否则项目家目录,再否则对话工作区。
|
|
276
|
+
const fenceRoot = worktreePath ??
|
|
277
|
+
project?.folderPath ??
|
|
278
|
+
(this.deps.resolveChatWorkspaceRoot
|
|
279
|
+
? await this.deps.resolveChatWorkspaceRoot(chatId)
|
|
280
|
+
: null);
|
|
277
281
|
const execSandbox = buildExecSandbox(fenceRoot ? [fenceRoot] : []);
|
|
278
282
|
const approval = process.env.STEERABLE_APPROVAL === '0'
|
|
279
283
|
? undefined
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface NativeFolderDialogResult {
|
|
2
|
+
canceled: boolean;
|
|
3
|
+
filePaths: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface NativeFolderDialogOptions {
|
|
6
|
+
title?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface NativeFolderDialogDeps {
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
run?: (cmd: string, args: readonly string[]) => Promise<{
|
|
11
|
+
stdout: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function selectNativeDirectory(options?: NativeFolderDialogOptions, deps?: NativeFolderDialogDeps): Promise<NativeFolderDialogResult>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BS 宿主没有 Electron `dialog.showOpenDialog`(`ELECTRON_RUN_AS_NODE=1`)。
|
|
3
|
+
* 本模块用各平台系统选择器弹出「选文件夹」,返回与 preload
|
|
4
|
+
* `local.selectDirectory` 相同的 `{ canceled, filePaths }`。
|
|
5
|
+
*
|
|
6
|
+
* 取消、无 DISPLAY、命令不存在都记为 canceled,让 UI 走手动输入路径。
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
function sanitizePrompt(title) {
|
|
12
|
+
const raw = (title ?? '选择文件夹').replace(/[\r\n]+/g, ' ').slice(0, 80);
|
|
13
|
+
return raw.replace(/["\\]/g, '');
|
|
14
|
+
}
|
|
15
|
+
function picked(stdout) {
|
|
16
|
+
const folder = stdout.trim().replace(/[/\\]+$/, '');
|
|
17
|
+
return folder ? { canceled: false, filePaths: [folder] } : { canceled: true, filePaths: [] };
|
|
18
|
+
}
|
|
19
|
+
export async function selectNativeDirectory(options = {}, deps = {}) {
|
|
20
|
+
const platform = deps.platform ?? process.platform;
|
|
21
|
+
const run = deps.run ?? ((cmd, args) => execFileAsync(cmd, [...args]));
|
|
22
|
+
const title = sanitizePrompt(options.title);
|
|
23
|
+
try {
|
|
24
|
+
if (platform === 'darwin') {
|
|
25
|
+
const { stdout } = await run('osascript', [
|
|
26
|
+
'-e',
|
|
27
|
+
`POSIX path of (choose folder with prompt "${title}")`,
|
|
28
|
+
]);
|
|
29
|
+
return picked(stdout);
|
|
30
|
+
}
|
|
31
|
+
if (platform === 'win32') {
|
|
32
|
+
const escaped = title.replace(/'/g, "''");
|
|
33
|
+
const script = [
|
|
34
|
+
'Add-Type -AssemblyName System.Windows.Forms',
|
|
35
|
+
'$d = New-Object System.Windows.Forms.FolderBrowserDialog',
|
|
36
|
+
`$d.Description = '${escaped}'`,
|
|
37
|
+
'if ($d.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $d.SelectedPath }',
|
|
38
|
+
].join('; ');
|
|
39
|
+
const { stdout } = await run('powershell.exe', ['-NoProfile', '-Command', script]);
|
|
40
|
+
return picked(stdout);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const { stdout } = await run('zenity', [
|
|
44
|
+
'--file-selection',
|
|
45
|
+
'--directory',
|
|
46
|
+
`--title=${title}`,
|
|
47
|
+
]);
|
|
48
|
+
return picked(stdout);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
const { stdout } = await run('kdialog', [
|
|
52
|
+
'--getexistingdirectory',
|
|
53
|
+
process.env.HOME || '/',
|
|
54
|
+
title,
|
|
55
|
+
]);
|
|
56
|
+
return picked(stdout);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { canceled: true, filePaths: [] };
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/project-home.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* 家目录由本模块分配并创建。用户另加的源文件夹是附加只读根,
|
|
6
6
|
* 不替代这个家目录。
|
|
7
7
|
*/
|
|
8
|
+
/** 把 `~` / `~/…` 展开成绝对路径;其它输入原样(已 trim)。 */
|
|
9
|
+
export declare function expandUserPath(folder: string): string;
|
|
8
10
|
export declare function sanitizeProjectDirName(name: string): string;
|
|
9
11
|
/** `Documents/<应用显示名>`:该应用下所有托管项目的父目录。 */
|
|
10
12
|
export declare function appProjectsRoot(options?: {
|
|
@@ -21,3 +23,18 @@ export declare function allocateProjectHome(projectName: string, options?: {
|
|
|
21
23
|
exists?: (folderPath: string) => boolean;
|
|
22
24
|
}): string;
|
|
23
25
|
export declare function ensureProjectHome(folderPath: string): void;
|
|
26
|
+
/** 无项目对话的工作区子目录名(与托管项目家目录并列,避免撞名)。 */
|
|
27
|
+
export declare const CHAT_WORKSPACES_DIR = "conversations";
|
|
28
|
+
/**
|
|
29
|
+
* 无项目对话的工作区路径:`Documents/<应用名>/conversations/<chatId>/`。
|
|
30
|
+
* 不落盘;调用方用 {@link ensureChatWorkspace} 创建。
|
|
31
|
+
*/
|
|
32
|
+
export declare function chatWorkspacePath(chatId: string, options?: {
|
|
33
|
+
documentsDir?: string;
|
|
34
|
+
appFolderName?: string;
|
|
35
|
+
}): string;
|
|
36
|
+
/** 确保无项目对话工作区存在,返回绝对路径。 */
|
|
37
|
+
export declare function ensureChatWorkspace(chatId: string, options?: {
|
|
38
|
+
documentsDir?: string;
|
|
39
|
+
appFolderName?: string;
|
|
40
|
+
}): string;
|
package/dist/project-home.js
CHANGED
|
@@ -6,10 +6,21 @@
|
|
|
6
6
|
* 不替代这个家目录。
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'node:fs';
|
|
9
|
+
import os from 'node:os';
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import { getBrand } from './brand.js';
|
|
11
12
|
import { getDocumentsDir } from './runtime.js';
|
|
12
13
|
const UNSAFE_DIR_CHARS = /[\\/:*?"<>|]/g;
|
|
14
|
+
/** 把 `~` / `~/…` 展开成绝对路径;其它输入原样(已 trim)。 */
|
|
15
|
+
export function expandUserPath(folder) {
|
|
16
|
+
const trimmed = folder.trim();
|
|
17
|
+
if (trimmed === '~')
|
|
18
|
+
return os.homedir();
|
|
19
|
+
if (trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
|
|
20
|
+
return path.join(os.homedir(), trimmed.slice(2));
|
|
21
|
+
}
|
|
22
|
+
return trimmed;
|
|
23
|
+
}
|
|
13
24
|
export function sanitizeProjectDirName(name) {
|
|
14
25
|
const trimmed = name
|
|
15
26
|
.trim()
|
|
@@ -45,3 +56,19 @@ export function allocateProjectHome(projectName, options) {
|
|
|
45
56
|
export function ensureProjectHome(folderPath) {
|
|
46
57
|
fs.mkdirSync(folderPath, { recursive: true });
|
|
47
58
|
}
|
|
59
|
+
/** 无项目对话的工作区子目录名(与托管项目家目录并列,避免撞名)。 */
|
|
60
|
+
export const CHAT_WORKSPACES_DIR = 'conversations';
|
|
61
|
+
/**
|
|
62
|
+
* 无项目对话的工作区路径:`Documents/<应用名>/conversations/<chatId>/`。
|
|
63
|
+
* 不落盘;调用方用 {@link ensureChatWorkspace} 创建。
|
|
64
|
+
*/
|
|
65
|
+
export function chatWorkspacePath(chatId, options) {
|
|
66
|
+
const id = sanitizeProjectDirName(chatId);
|
|
67
|
+
return path.join(appProjectsRoot(options), CHAT_WORKSPACES_DIR, id);
|
|
68
|
+
}
|
|
69
|
+
/** 确保无项目对话工作区存在,返回绝对路径。 */
|
|
70
|
+
export function ensureChatWorkspace(chatId, options) {
|
|
71
|
+
const folderPath = chatWorkspacePath(chatId, options);
|
|
72
|
+
ensureProjectHome(folderPath);
|
|
73
|
+
return folderPath;
|
|
74
|
+
}
|
|
@@ -34,6 +34,7 @@ import { getSidecarSupervisor } from '../llm/index.js';
|
|
|
34
34
|
import { matchPackHttpRoute } from '../host/http-routes.js';
|
|
35
35
|
import { getBrand } from '../brand.js';
|
|
36
36
|
import { saveAttachmentFiles } from '../attachments.js';
|
|
37
|
+
import { selectNativeDirectory } from '../native-folder-dialog.js';
|
|
37
38
|
import { getAuthProvider } from '../auth/index.js';
|
|
38
39
|
/** 与 router.handleStream 内部的路由正则保持一致——只有这些路径是流式。 */
|
|
39
40
|
const STREAM_PATH_PATTERNS = [
|
|
@@ -250,6 +251,11 @@ export function createBsServer(deps) {
|
|
|
250
251
|
return;
|
|
251
252
|
}
|
|
252
253
|
// ─── local(对齐 local:* IPC) ───
|
|
254
|
+
if (method === 'POST' && pathname === '/host/local/select-directory') {
|
|
255
|
+
const title = typeof body.title === 'string' ? body.title : undefined;
|
|
256
|
+
sendJson(res, 200, await selectNativeDirectory({ title }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
253
259
|
if (method === 'POST' && pathname === '/host/local/exec-shell') {
|
|
254
260
|
const request = body;
|
|
255
261
|
const viaTerminal = await maybeExecInTerminal(request);
|
package/dist/sidecar/boot.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { SidecarReverseHandler } from './types.js';
|
|
|
5
5
|
export interface HostSidecarDeps {
|
|
6
6
|
store: ScopedStore;
|
|
7
7
|
toolRouter: ToolRouter;
|
|
8
|
-
/** W4-2
|
|
8
|
+
/** W4-2 围栏:chatId → 项目家目录或无项目对话工作区。 */
|
|
9
9
|
resolveProjectRoot: (chatId: string) => Promise<string | null>;
|
|
10
10
|
/**
|
|
11
11
|
* 项目模式之外额外放行的只读根(会话附件目录等)。文件写入仍只受
|
|
@@ -16,7 +16,7 @@ export interface ReverseToolDeps {
|
|
|
16
16
|
}) => void;
|
|
17
17
|
/**
|
|
18
18
|
* 项目模式:按 chatId 解析项目根(绝对路径),无绑定/已删除返回 null。
|
|
19
|
-
* main.ts
|
|
19
|
+
* main.ts 注入对话可写根(项目家目录或无项目对话工作区);未注入时
|
|
20
20
|
* 项目围栏不生效(CLI/test 场景)。
|
|
21
21
|
*/
|
|
22
22
|
resolveProjectRoot?: (chatId: string) => Promise<string | null>;
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ChatSessionRecord {
|
|
|
12
12
|
title: string;
|
|
13
13
|
userId: string;
|
|
14
14
|
agentId: string | null;
|
|
15
|
-
/** 项目模式:绑定的项目 id(ProjectRegistry)。null =
|
|
15
|
+
/** 项目模式:绑定的项目 id(ProjectRegistry)。null = 无项目对话(用对话工作区)。 */
|
|
16
16
|
projectId: string | null;
|
|
17
17
|
createdAt: string;
|
|
18
18
|
updatedAt: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.32",
|
|
4
4
|
"description": "Steerable framework — product-neutral desktop/headless agent host (Tier 5). Electron main + preload + headless HTTP server (BS) sharing one storage/sidecar/tooling core; scenario packs extend it through the @steerable/pack-sdk contract and are composed at build time by the consuming product.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"electron-store": "^8.1.0",
|
|
50
50
|
"he": "^1.2.0",
|
|
51
51
|
"node-pty": "^1.1.0",
|
|
52
|
-
"@steerable/agent-
|
|
53
|
-
"@steerable/pack-sdk": "0.6.
|
|
54
|
-
"@steerable/agent-
|
|
52
|
+
"@steerable/agent-protocol": "0.6.32",
|
|
53
|
+
"@steerable/pack-sdk": "0.6.32",
|
|
54
|
+
"@steerable/agent-harness": "0.6.32"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/better-sqlite3": "^7.6.13",
|