@steerable/agent-shell 0.8.0 → 0.8.2

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.
@@ -38,7 +38,7 @@ import { createJsonStore } from '../json-store.js';
38
38
  import { bindWorkspaceSkillRoots, listSkillRoots } from '../local-backend/skill-loader.js';
39
39
  import { collectPackExecWritableRoots } from '../local-backend/pack-turn-hooks.js';
40
40
  import { getChatAttachmentsDir } from '../attachments.js';
41
- import { ensureChatWorkspace } from '../project-home.js';
41
+ import { ensureChatWorkspace, ensureWritableRoots } from '../project-home.js';
42
42
  import { recordInsightTurn } from '../insights/record.js';
43
43
  import { llmService } from '../llm/index.js';
44
44
  import { isProductLlmLocked, resolveRuntimeLlmSettings } from '../host-tools-runtime.js';
@@ -323,12 +323,14 @@ export async function createHostRuntime(options) {
323
323
  ];
324
324
  },
325
325
  // 写:源文件夹、包声明的可写根,以及各自的子目录。附件目录保持只读。
326
+ // ensureWritableRoots 让这些名字先落成目录:Landlock/bwrap 在安装沙箱
327
+ // 规则时要 open 它们,声明了却还没建过的目录会让子进程起不来。
326
328
  resolveAdditionalWriteRoots: async (chatId) => {
327
329
  const project = await localBackendRouter.resolveChatProject(chatId);
328
- return [
330
+ return ensureWritableRoots([
329
331
  ...(project?.sourceFolders ?? []),
330
332
  ...collectPackExecWritableRoots(chatId),
331
- ];
333
+ ]);
332
334
  },
333
335
  approvalHandler: approvalBridge.handler,
334
336
  askUserHandler: askUserBridge.handler,
@@ -52,7 +52,7 @@ import { parseImageAttachments, processImageAttachments } from '../image-attachm
52
52
  import { chatAttachmentsDirPath, isValidChatAttachmentsKey } from '../attachments.js';
53
53
  import { canonicalFencePath, canonicalPathInside } from '../fence-path.js';
54
54
  import { listProjectRuleFiles, loadProjectRuleFiles } from '../project-rules.js';
55
- import { allocateProjectHome, chatWorkspacePath, ensureChatWorkspace, ensureProjectHome, expandUserPath, } from '../project-home.js';
55
+ import { allocateProjectHome, chatWorkspacePath, ensureChatWorkspace, ensureProjectHome, ensureWritableRoots, expandUserPath, } from '../project-home.js';
56
56
  import { registerLiveStream, getLiveStream, removeLiveStream } from './live-stream.js';
57
57
  import { compactHistory, forkHistory, rewindFrom } from './history-edit.js';
58
58
  import { copyForkDirectory, DEFAULT_FORK_WORKSPACE_MAX_BYTES, directorySizeBytes, forkPathRewriter, removeForkDirectory, } from './fork-copy.js';
@@ -3778,11 +3778,11 @@ export class LocalBackendRouter {
3778
3778
  // (项目根 + 包工作区),与沙箱是否启用无关——「完整权限」下根列表只是
3779
3779
  // 不再被强制,产物仍大概率落在这里;根之外的显式写由工具参数并集补。
3780
3780
  const turnStartedAtMs = Date.now();
3781
- const turnFileRoots = [
3781
+ const turnFileRoots = ensureWritableRoots([
3782
3782
  ...collectPackExecWritableRoots(chatId),
3783
3783
  workspaceRoot,
3784
3784
  ...(chatProject?.sourceFolders ?? []),
3785
- ];
3785
+ ]);
3786
3786
  try {
3787
3787
  const coreLoopOptions = {
3788
3788
  supervisor,
@@ -239,8 +239,14 @@ export declare class LocalExecutor {
239
239
  /**
240
240
  * P2b:会话恢复时重灌 read-before-write 证据。sidecar 扫描持久记录里的
241
241
  * 文件工具结果,把 path → version 经 `read_state.seed` 推到这里——进程
242
- * 重启后自动 CAS 仍然成立(CC `seed_read_state` 语义)。键是 sidecar 侧
243
- * 已解析的绝对路径,原样存储;resolvePath 对绝对路径是恒等,查询能命中。
242
+ * 重启后自动 CAS 仍然成立(CC `seed_read_state` 语义)。
243
+ *
244
+ * 键必须过一遍 `resolvePath`:读证据的每一次读写都用规范化后的路径做键,
245
+ * 而 seed 来的路径是 sidecar 记录的原文。两者不一致时自动 CAS 永远查不
246
+ * 中,write/edit 会退回「未读先写已拒绝」。macOS 上 `/var/folders/…` 与
247
+ * `/private/var/folders/…` 就是这种情形(`os.tmpdir()` 返回未解析的
248
+ * 形式),Linux 的 `/tmp` 符号链接同理。
249
+ *
244
250
  * 返回实际收录的条目数(畸形条目跳过)。
245
251
  */
246
252
  seedReadState(state: Record<string, unknown>): number;
@@ -329,15 +329,21 @@ export class LocalExecutor {
329
329
  /**
330
330
  * P2b:会话恢复时重灌 read-before-write 证据。sidecar 扫描持久记录里的
331
331
  * 文件工具结果,把 path → version 经 `read_state.seed` 推到这里——进程
332
- * 重启后自动 CAS 仍然成立(CC `seed_read_state` 语义)。键是 sidecar 侧
333
- * 已解析的绝对路径,原样存储;resolvePath 对绝对路径是恒等,查询能命中。
332
+ * 重启后自动 CAS 仍然成立(CC `seed_read_state` 语义)。
333
+ *
334
+ * 键必须过一遍 `resolvePath`:读证据的每一次读写都用规范化后的路径做键,
335
+ * 而 seed 来的路径是 sidecar 记录的原文。两者不一致时自动 CAS 永远查不
336
+ * 中,write/edit 会退回「未读先写已拒绝」。macOS 上 `/var/folders/…` 与
337
+ * `/private/var/folders/…` 就是这种情形(`os.tmpdir()` 返回未解析的
338
+ * 形式),Linux 的 `/tmp` 符号链接同理。
339
+ *
334
340
  * 返回实际收录的条目数(畸形条目跳过)。
335
341
  */
336
342
  seedReadState(state) {
337
343
  let seeded = 0;
338
344
  for (const [p, version] of Object.entries(state)) {
339
345
  if (typeof p === 'string' && p.length > 0 && typeof version === 'string') {
340
- this.readFileState.set(p, version);
346
+ this.readFileState.set(this.resolvePath(p), version);
341
347
  seeded += 1;
342
348
  }
343
349
  }
@@ -23,6 +23,18 @@ export declare function allocateProjectHome(projectName: string, options?: {
23
23
  exists?: (folderPath: string) => boolean;
24
24
  }): string;
25
25
  export declare function ensureProjectHome(folderPath: string): void;
26
+ /**
27
+ * 把一批可写根落成真实目录,返回原列表(便于链式使用)。
28
+ *
29
+ * Linux 的 Landlock 在安装规则时逐个 open 这些路径,bwrap 则要 bind mount
30
+ * 它们;声明了但还没建过的目录会让被沙箱的子进程直接起不来
31
+ * (`writable root … does not exist`)。这与 supervisor 启动时对
32
+ * `sandboxWritableRoots` 做的 mkdir 是同一个前置条件。
33
+ *
34
+ * 建不出来的根**留在列表里**:沙箱应当因此明确拒绝这次调用,而不是悄悄
35
+ * 少一份写权限继续跑。
36
+ */
37
+ export declare function ensureWritableRoots(roots: readonly string[]): string[];
26
38
  /** 无项目对话的工作区子目录名(与托管项目家目录并列,避免撞名)。 */
27
39
  export declare const CHAT_WORKSPACES_DIR = "conversations";
28
40
  /**
@@ -56,6 +56,30 @@ export function allocateProjectHome(projectName, options) {
56
56
  export function ensureProjectHome(folderPath) {
57
57
  fs.mkdirSync(folderPath, { recursive: true });
58
58
  }
59
+ /**
60
+ * 把一批可写根落成真实目录,返回原列表(便于链式使用)。
61
+ *
62
+ * Linux 的 Landlock 在安装规则时逐个 open 这些路径,bwrap 则要 bind mount
63
+ * 它们;声明了但还没建过的目录会让被沙箱的子进程直接起不来
64
+ * (`writable root … does not exist`)。这与 supervisor 启动时对
65
+ * `sandboxWritableRoots` 做的 mkdir 是同一个前置条件。
66
+ *
67
+ * 建不出来的根**留在列表里**:沙箱应当因此明确拒绝这次调用,而不是悄悄
68
+ * 少一份写权限继续跑。
69
+ */
70
+ export function ensureWritableRoots(roots) {
71
+ for (const root of new Set(roots)) {
72
+ if (typeof root !== 'string' || !root.trim())
73
+ continue;
74
+ try {
75
+ fs.mkdirSync(root, { recursive: true });
76
+ }
77
+ catch (err) {
78
+ console.warn(`[project-home] cannot create writable root ${root}: ${err.message}`);
79
+ }
80
+ }
81
+ return [...roots];
82
+ }
59
83
  /** 无项目对话的工作区子目录名(与托管项目家目录并列,避免撞名)。 */
60
84
  export const CHAT_WORKSPACES_DIR = 'conversations';
61
85
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Steerable framework — product-neutral Node HostRuntime and headless HTTP server shared by Tauri desktop and browser deployments; scenario packs extend it through @steerable/pack-sdk.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://steerableframework.com/",
@@ -53,9 +53,9 @@
53
53
  "jpeg-js": "^0.4.4",
54
54
  "node-pty": "^1.1.0",
55
55
  "pngjs": "^7.0.0",
56
- "@steerable/agent-harness": "0.8.0",
57
- "@steerable/agent-protocol": "0.8.0",
58
- "@steerable/pack-sdk": "0.8.0"
56
+ "@steerable/agent-harness": "0.8.2",
57
+ "@steerable/agent-protocol": "0.8.2",
58
+ "@steerable/pack-sdk": "0.8.2"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/better-sqlite3": "^7.6.13",