@clawos-dev/clawd 0.2.127 → 0.2.128-beta.254.609d525

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/cli.cjs CHANGED
@@ -195,6 +195,10 @@ var init_methods = __esm({
195
195
  // updateProjectPort: 校验新端口段内 + 未被别的 project 占 + 重启 dev server
196
196
  // setProdUrl: 发布上线后 assistant 调一次写入 .clawd-project.json.prodUrl(取代 prod marker)
197
197
  "appBuilder:listProjects",
198
+ // getProject:按 sessionId 取单个绑定 project(含 status)。people/mirror 跨 daemon 场景:
199
+ // 本机 owner 在远端是 guest,listProjects 被 per-user 隔离拿不到别人的 project;getProject
200
+ // 按 session 可见性授权(ensureSessionAccess read)放行。本地也复用(省掉拉全量再 find)。
201
+ "appBuilder:getProject",
198
202
  "appBuilder:createProject",
199
203
  "appBuilder:deleteProject",
200
204
  "appBuilder:updateProjectPort",
@@ -4566,7 +4570,7 @@ var init_persona_schemas = __esm({
4566
4570
  });
4567
4571
 
4568
4572
  // ../protocol/src/schemas.ts
4569
- var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
4573
+ var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, GetProjectArgsSchema, GetProjectResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
4570
4574
  var init_schemas = __esm({
4571
4575
  "../protocol/src/schemas.ts"() {
4572
4576
  "use strict";
@@ -5202,7 +5206,7 @@ var init_schemas = __esm({
5202
5206
  httpToken: external_exports.string().optional()
5203
5207
  });
5204
5208
  PROJECT_PORT_MIN = 6173;
5205
- PROJECT_PORT_MAX = 6182;
5209
+ PROJECT_PORT_MAX = 6272;
5206
5210
  projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
5207
5211
  DEFAULT_DEV_COMMAND = "cd server && pnpm dev";
5208
5212
  ProjectMetadataSchema = external_exports.object({
@@ -5289,6 +5293,13 @@ var init_schemas = __esm({
5289
5293
  ListProjectsResultSchema = external_exports.object({
5290
5294
  projects: external_exports.array(ProjectWithStatusSchema)
5291
5295
  }).strict();
5296
+ GetProjectArgsSchema = external_exports.object({
5297
+ sessionId: external_exports.string().min(1)
5298
+ }).strict();
5299
+ GetProjectResultSchema = external_exports.object({
5300
+ /** session 未绑 project / project 不存在时为 null(UI 走单栏 / 折叠) */
5301
+ project: ProjectWithStatusSchema.nullable()
5302
+ }).strict();
5292
5303
  CreateProjectArgsSchema = external_exports.object({
5293
5304
  /** 必填:caller session,daemon 自动绑过去(spec 2026-06-02 §3 invariant 1 Session ↔ 1 Project) */
5294
5305
  sessionId: external_exports.string().min(1),
@@ -7082,7 +7093,7 @@ var require_atomic_sleep = __commonJS({
7082
7093
  var require_sonic_boom = __commonJS({
7083
7094
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
7084
7095
  "use strict";
7085
- var fs49 = require("fs");
7096
+ var fs50 = require("fs");
7086
7097
  var EventEmitter3 = require("events");
7087
7098
  var inherits = require("util").inherits;
7088
7099
  var path60 = require("path");
@@ -7139,20 +7150,20 @@ var require_sonic_boom = __commonJS({
7139
7150
  const mode = sonic.mode;
7140
7151
  if (sonic.sync) {
7141
7152
  try {
7142
- if (sonic.mkdir) fs49.mkdirSync(path60.dirname(file), { recursive: true });
7143
- const fd = fs49.openSync(file, flags, mode);
7153
+ if (sonic.mkdir) fs50.mkdirSync(path60.dirname(file), { recursive: true });
7154
+ const fd = fs50.openSync(file, flags, mode);
7144
7155
  fileOpened(null, fd);
7145
7156
  } catch (err) {
7146
7157
  fileOpened(err);
7147
7158
  throw err;
7148
7159
  }
7149
7160
  } else if (sonic.mkdir) {
7150
- fs49.mkdir(path60.dirname(file), { recursive: true }, (err) => {
7161
+ fs50.mkdir(path60.dirname(file), { recursive: true }, (err) => {
7151
7162
  if (err) return fileOpened(err);
7152
- fs49.open(file, flags, mode, fileOpened);
7163
+ fs50.open(file, flags, mode, fileOpened);
7153
7164
  });
7154
7165
  } else {
7155
- fs49.open(file, flags, mode, fileOpened);
7166
+ fs50.open(file, flags, mode, fileOpened);
7156
7167
  }
7157
7168
  }
7158
7169
  function SonicBoom(opts) {
@@ -7193,8 +7204,8 @@ var require_sonic_boom = __commonJS({
7193
7204
  this.flush = flushBuffer;
7194
7205
  this.flushSync = flushBufferSync;
7195
7206
  this._actualWrite = actualWriteBuffer;
7196
- fsWriteSync = () => fs49.writeSync(this.fd, this._writingBuf);
7197
- fsWrite = () => fs49.write(this.fd, this._writingBuf, this.release);
7207
+ fsWriteSync = () => fs50.writeSync(this.fd, this._writingBuf);
7208
+ fsWrite = () => fs50.write(this.fd, this._writingBuf, this.release);
7198
7209
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
7199
7210
  this._writingBuf = "";
7200
7211
  this.write = write;
@@ -7203,15 +7214,15 @@ var require_sonic_boom = __commonJS({
7203
7214
  this._actualWrite = actualWrite;
7204
7215
  fsWriteSync = () => {
7205
7216
  if (Buffer.isBuffer(this._writingBuf)) {
7206
- return fs49.writeSync(this.fd, this._writingBuf);
7217
+ return fs50.writeSync(this.fd, this._writingBuf);
7207
7218
  }
7208
- return fs49.writeSync(this.fd, this._writingBuf, "utf8");
7219
+ return fs50.writeSync(this.fd, this._writingBuf, "utf8");
7209
7220
  };
7210
7221
  fsWrite = () => {
7211
7222
  if (Buffer.isBuffer(this._writingBuf)) {
7212
- return fs49.write(this.fd, this._writingBuf, this.release);
7223
+ return fs50.write(this.fd, this._writingBuf, this.release);
7213
7224
  }
7214
- return fs49.write(this.fd, this._writingBuf, "utf8", this.release);
7225
+ return fs50.write(this.fd, this._writingBuf, "utf8", this.release);
7215
7226
  };
7216
7227
  } else {
7217
7228
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -7268,7 +7279,7 @@ var require_sonic_boom = __commonJS({
7268
7279
  }
7269
7280
  }
7270
7281
  if (this._fsync) {
7271
- fs49.fsyncSync(this.fd);
7282
+ fs50.fsyncSync(this.fd);
7272
7283
  }
7273
7284
  const len = this._len;
7274
7285
  if (this._reopening) {
@@ -7382,7 +7393,7 @@ var require_sonic_boom = __commonJS({
7382
7393
  const onDrain = () => {
7383
7394
  if (!this._fsync) {
7384
7395
  try {
7385
- fs49.fsync(this.fd, (err) => {
7396
+ fs50.fsync(this.fd, (err) => {
7386
7397
  this._flushPending = false;
7387
7398
  cb(err);
7388
7399
  });
@@ -7484,7 +7495,7 @@ var require_sonic_boom = __commonJS({
7484
7495
  const fd = this.fd;
7485
7496
  this.once("ready", () => {
7486
7497
  if (fd !== this.fd) {
7487
- fs49.close(fd, (err) => {
7498
+ fs50.close(fd, (err) => {
7488
7499
  if (err) {
7489
7500
  return this.emit("error", err);
7490
7501
  }
@@ -7533,7 +7544,7 @@ var require_sonic_boom = __commonJS({
7533
7544
  buf = this._bufs[0];
7534
7545
  }
7535
7546
  try {
7536
- const n = Buffer.isBuffer(buf) ? fs49.writeSync(this.fd, buf) : fs49.writeSync(this.fd, buf, "utf8");
7547
+ const n = Buffer.isBuffer(buf) ? fs50.writeSync(this.fd, buf) : fs50.writeSync(this.fd, buf, "utf8");
7537
7548
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
7538
7549
  buf = releasedBufObj.writingBuf;
7539
7550
  this._len = releasedBufObj.len;
@@ -7549,7 +7560,7 @@ var require_sonic_boom = __commonJS({
7549
7560
  }
7550
7561
  }
7551
7562
  try {
7552
- fs49.fsyncSync(this.fd);
7563
+ fs50.fsyncSync(this.fd);
7553
7564
  } catch {
7554
7565
  }
7555
7566
  }
@@ -7570,7 +7581,7 @@ var require_sonic_boom = __commonJS({
7570
7581
  buf = mergeBuf(this._bufs[0], this._lens[0]);
7571
7582
  }
7572
7583
  try {
7573
- const n = fs49.writeSync(this.fd, buf);
7584
+ const n = fs50.writeSync(this.fd, buf);
7574
7585
  buf = buf.subarray(n);
7575
7586
  this._len = Math.max(this._len - n, 0);
7576
7587
  if (buf.length <= 0) {
@@ -7598,13 +7609,13 @@ var require_sonic_boom = __commonJS({
7598
7609
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
7599
7610
  if (this.sync) {
7600
7611
  try {
7601
- const written = Buffer.isBuffer(this._writingBuf) ? fs49.writeSync(this.fd, this._writingBuf) : fs49.writeSync(this.fd, this._writingBuf, "utf8");
7612
+ const written = Buffer.isBuffer(this._writingBuf) ? fs50.writeSync(this.fd, this._writingBuf) : fs50.writeSync(this.fd, this._writingBuf, "utf8");
7602
7613
  release(null, written);
7603
7614
  } catch (err) {
7604
7615
  release(err);
7605
7616
  }
7606
7617
  } else {
7607
- fs49.write(this.fd, this._writingBuf, release);
7618
+ fs50.write(this.fd, this._writingBuf, release);
7608
7619
  }
7609
7620
  }
7610
7621
  function actualWriteBuffer() {
@@ -7613,7 +7624,7 @@ var require_sonic_boom = __commonJS({
7613
7624
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
7614
7625
  if (this.sync) {
7615
7626
  try {
7616
- const written = fs49.writeSync(this.fd, this._writingBuf);
7627
+ const written = fs50.writeSync(this.fd, this._writingBuf);
7617
7628
  release(null, written);
7618
7629
  } catch (err) {
7619
7630
  release(err);
@@ -7622,7 +7633,7 @@ var require_sonic_boom = __commonJS({
7622
7633
  if (kCopyBuffer) {
7623
7634
  this._writingBuf = Buffer.from(this._writingBuf);
7624
7635
  }
7625
- fs49.write(this.fd, this._writingBuf, release);
7636
+ fs50.write(this.fd, this._writingBuf, release);
7626
7637
  }
7627
7638
  }
7628
7639
  function actualClose(sonic) {
@@ -7638,12 +7649,12 @@ var require_sonic_boom = __commonJS({
7638
7649
  sonic._lens = [];
7639
7650
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
7640
7651
  try {
7641
- fs49.fsync(sonic.fd, closeWrapped);
7652
+ fs50.fsync(sonic.fd, closeWrapped);
7642
7653
  } catch {
7643
7654
  }
7644
7655
  function closeWrapped() {
7645
7656
  if (sonic.fd !== 1 && sonic.fd !== 2) {
7646
- fs49.close(sonic.fd, done);
7657
+ fs50.close(sonic.fd, done);
7647
7658
  } else {
7648
7659
  done();
7649
7660
  }
@@ -8322,7 +8333,7 @@ var require_transport = __commonJS({
8322
8333
  "use strict";
8323
8334
  var { createRequire } = require("module");
8324
8335
  var getCallers = require_caller();
8325
- var { join: join15, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
8336
+ var { join: join15, isAbsolute: isAbsolute2, sep: sep2 } = require("path");
8326
8337
  var sleep = require_atomic_sleep();
8327
8338
  var onExit = require_on_exit_leak_free();
8328
8339
  var ThreadStream = require_thread_stream();
@@ -8430,7 +8441,7 @@ var require_transport = __commonJS({
8430
8441
  let fixTarget2;
8431
8442
  for (const filePath of callers) {
8432
8443
  try {
8433
- const context = filePath === "node:repl" ? process.cwd() + sep3 : filePath;
8444
+ const context = filePath === "node:repl" ? process.cwd() + sep2 : filePath;
8434
8445
  fixTarget2 = createRequire(context).resolve(origin);
8435
8446
  break;
8436
8447
  } catch (err) {
@@ -10850,7 +10861,7 @@ var init_lib = __esm({
10850
10861
  tokenize: function tokenize(value) {
10851
10862
  return Array.from(value);
10852
10863
  },
10853
- join: function join4(chars) {
10864
+ join: function join5(chars) {
10854
10865
  return chars.join("");
10855
10866
  },
10856
10867
  postProcess: function postProcess(changeObjects) {
@@ -11705,27 +11716,6 @@ var init_claude_history = __esm({
11705
11716
  }
11706
11717
  });
11707
11718
 
11708
- // src/tools/sandbox.ts
11709
- function shouldSandbox(cwd, personaRoot) {
11710
- if (!personaRoot) return false;
11711
- const sep3 = personaRoot.endsWith(path13.sep) ? "" : path13.sep;
11712
- return cwd.startsWith(personaRoot + sep3) && cwd !== personaRoot;
11713
- }
11714
- function inferSandboxSettingsPath(cwd, personaRoot) {
11715
- if (!shouldSandbox(cwd, personaRoot)) return null;
11716
- const rel = path13.relative(personaRoot, cwd);
11717
- const personaId = rel.split(path13.sep)[0];
11718
- if (!personaId) return null;
11719
- return path13.join(personaRoot, personaId, ".clawd", "sandbox-settings.json");
11720
- }
11721
- var path13;
11722
- var init_sandbox = __esm({
11723
- "src/tools/sandbox.ts"() {
11724
- "use strict";
11725
- path13 = __toESM(require("path"), 1);
11726
- }
11727
- });
11728
-
11729
11719
  // src/tools/claude.ts
11730
11720
  function macOSDesktopCandidates(home) {
11731
11721
  return [
@@ -11790,9 +11780,11 @@ function buildSpawnArgs(ctx) {
11790
11780
  throw new Error(`unexpected personaMode: ${String(_exhaustive)}`);
11791
11781
  }
11792
11782
  }
11793
- const sandboxSettings = ctx.extraSettings ?? inferSandboxSettingsPath(ctx.cwd, ctx.personaRoot);
11794
- if (sandboxSettings) args.push("--settings", sandboxSettings);
11783
+ if (ctx.extraSettings) args.push("--settings", ctx.extraSettings);
11795
11784
  if (ctx.extraSystemPrompt) args.push("--append-system-prompt", ctx.extraSystemPrompt);
11785
+ if (ctx.addDirs) {
11786
+ for (const dir of ctx.addDirs) args.push("--add-dir", dir);
11787
+ }
11796
11788
  if (ctx.effort) args.push("--effort", ctx.effort);
11797
11789
  if (ctx.toolSessionId) args.push("--resume", ctx.toolSessionId);
11798
11790
  return args;
@@ -12191,7 +12183,6 @@ var init_claude = __esm({
12191
12183
  init_protocol();
12192
12184
  init_claude_history();
12193
12185
  init_tool_result_extra();
12194
- init_sandbox();
12195
12186
  ATTACHMENT_SILENT_SUBTYPES2 = /* @__PURE__ */ new Set([
12196
12187
  "hook_additional_context",
12197
12188
  "hook_success",
@@ -30745,8 +30736,8 @@ function startRunCaseRecorder(opts) {
30745
30736
  });
30746
30737
  const ensureStream = () => {
30747
30738
  if (stream) return stream;
30748
- import_node_fs32.default.mkdirSync(dir, { recursive: true });
30749
- stream = import_node_fs32.default.createWriteStream(opts.recordPath, { flags: "a" });
30739
+ import_node_fs33.default.mkdirSync(dir, { recursive: true });
30740
+ stream = import_node_fs33.default.createWriteStream(opts.recordPath, { flags: "a" });
30750
30741
  stream.on("close", () => closedResolve());
30751
30742
  return stream;
30752
30743
  };
@@ -30771,11 +30762,11 @@ function startRunCaseRecorder(opts) {
30771
30762
  };
30772
30763
  return { tap, close, closed };
30773
30764
  }
30774
- var import_node_fs32, import_node_path48;
30765
+ var import_node_fs33, import_node_path48;
30775
30766
  var init_recorder = __esm({
30776
30767
  "src/run-case/recorder.ts"() {
30777
30768
  "use strict";
30778
- import_node_fs32 = __toESM(require("fs"), 1);
30769
+ import_node_fs33 = __toESM(require("fs"), 1);
30779
30770
  import_node_path48 = __toESM(require("path"), 1);
30780
30771
  }
30781
30772
  });
@@ -30819,7 +30810,7 @@ var init_wire = __esm({
30819
30810
  // src/run-case/controller.ts
30820
30811
  async function runController(opts) {
30821
30812
  const now = opts.now ?? Date.now;
30822
- const cwd = opts.cwd ?? (0, import_node_fs33.mkdtempSync)(import_node_path49.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30813
+ const cwd = opts.cwd ?? (0, import_node_fs34.mkdtempSync)(import_node_path49.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30823
30814
  const ownsCwd = opts.cwd === void 0;
30824
30815
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
30825
30816
  const spawnCtx = { cwd };
@@ -30980,17 +30971,17 @@ async function runController(opts) {
30980
30971
  if (sigintHandler) process.off("SIGINT", sigintHandler);
30981
30972
  if (ownsCwd) {
30982
30973
  try {
30983
- (0, import_node_fs33.rmSync)(cwd, { recursive: true, force: true });
30974
+ (0, import_node_fs34.rmSync)(cwd, { recursive: true, force: true });
30984
30975
  } catch {
30985
30976
  }
30986
30977
  }
30987
30978
  return exitCode ?? 0;
30988
30979
  }
30989
- var import_node_fs33, import_node_os19, import_node_path49;
30980
+ var import_node_fs34, import_node_os19, import_node_path49;
30990
30981
  var init_controller = __esm({
30991
30982
  "src/run-case/controller.ts"() {
30992
30983
  "use strict";
30993
- import_node_fs33 = require("fs");
30984
+ import_node_fs34 = require("fs");
30994
30985
  import_node_os19 = __toESM(require("os"), 1);
30995
30986
  import_node_path49 = __toESM(require("path"), 1);
30996
30987
  init_claude();
@@ -31228,7 +31219,7 @@ Env (advanced):
31228
31219
 
31229
31220
  // src/index.ts
31230
31221
  var import_node_path47 = __toESM(require("path"), 1);
31231
- var import_node_fs31 = __toESM(require("fs"), 1);
31222
+ var import_node_fs32 = __toESM(require("fs"), 1);
31232
31223
 
31233
31224
  // src/logger.ts
31234
31225
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -31266,7 +31257,7 @@ function createLogger(opts = {}) {
31266
31257
  }
31267
31258
 
31268
31259
  // src/session/store-factory.ts
31269
- var path5 = __toESM(require("path"), 1);
31260
+ var path6 = __toESM(require("path"), 1);
31270
31261
 
31271
31262
  // src/session/store.ts
31272
31263
  var import_node_fs3 = __toESM(require("fs"), 1);
@@ -31275,6 +31266,22 @@ init_protocol();
31275
31266
 
31276
31267
  // src/session/scope.ts
31277
31268
  var import_node_path3 = __toESM(require("path"), 1);
31269
+
31270
+ // src/session/user-workdir.ts
31271
+ var path3 = __toESM(require("path"), 1);
31272
+ function safeUserId(userId) {
31273
+ const base = userId.replace(/[^a-zA-Z0-9_\-.]/g, "_");
31274
+ const cleaned = base.replace(/^\.+/, (dots) => "_".repeat(dots.length));
31275
+ if (!cleaned) throw new Error(`deriveUserWorkDir: invalid userId: ${userId}`);
31276
+ return cleaned.replace(/\.\./g, "__");
31277
+ }
31278
+ function deriveUserWorkDir(userId, usersRoot) {
31279
+ if (!userId) throw new Error("deriveUserWorkDir: userId required");
31280
+ if (!usersRoot) throw new Error("deriveUserWorkDir: usersRoot required");
31281
+ return path3.join(usersRoot, safeUserId(userId));
31282
+ }
31283
+
31284
+ // src/session/scope.ts
31278
31285
  function scopeKey(scope) {
31279
31286
  if (scope.kind === "default") return "default";
31280
31287
  if (scope.mode === "guest") return `persona:${scope.personaId}:guest:${scope.capId}`;
@@ -31285,15 +31292,18 @@ function scopeSubPath(scope) {
31285
31292
  if (scope.mode === "guest") return [scope.personaId, "guests", scope.capId];
31286
31293
  return [scope.personaId, scope.mode];
31287
31294
  }
31288
- function metaFromScope(scope, personaRoot) {
31295
+ function metaFromScope(scope, personaRoot, usersRoot) {
31289
31296
  if (scope.kind === "default") return void 0;
31290
31297
  if (scope.mode === "owner") {
31291
31298
  return { idleKillEnabled: false, personaMode: "owner" };
31292
31299
  }
31300
+ const userWorkDir = deriveUserWorkDir(scope.capId, usersRoot);
31293
31301
  return {
31294
31302
  idleKillEnabled: true,
31295
31303
  personaMode: "guest",
31296
- extraSettings: import_node_path3.default.join(personaRoot, scope.personaId, ".clawd", "sandbox-settings.json")
31304
+ extraSettings: import_node_path3.default.join(personaRoot, scope.personaId, ".clawd", "sandbox-settings.json"),
31305
+ userWorkDir,
31306
+ addDirs: [userWorkDir]
31297
31307
  };
31298
31308
  }
31299
31309
  function scopeForFile(file, ownerPrincipalId) {
@@ -31403,10 +31413,10 @@ var SessionStoreFactory = class {
31403
31413
  }
31404
31414
  // ---- root path 派生(暴露给 migration / revoke cascade 等外部消费方) ----
31405
31415
  bareRoot() {
31406
- return path5.join(this.dataDir, "sessions");
31416
+ return path6.join(this.dataDir, "sessions");
31407
31417
  }
31408
31418
  vmOwnerRoot(personaId) {
31409
- return path5.join(
31419
+ return path6.join(
31410
31420
  this.dataDir,
31411
31421
  "personas",
31412
31422
  safeFileName(personaId),
@@ -31416,7 +31426,7 @@ var SessionStoreFactory = class {
31416
31426
  );
31417
31427
  }
31418
31428
  vmGuestRoot(personaId, capId) {
31419
- return path5.join(
31429
+ return path6.join(
31420
31430
  this.dataDir,
31421
31431
  "personas",
31422
31432
  safeFileName(personaId),
@@ -31550,7 +31560,12 @@ var LISTENER_TEMPLATE = `# \u8FDE\u63A5\u4E0A\u4E0B\u6587
31550
31560
  \u81EA\u6211\u4ECB\u7ECD\u65F6\u4F7F\u7528\u7B2C\u4E09\u4EBA\u79F0\u6307\u4EE3 owner\uFF0C\u4F8B\u5982\u300C\u6211\u662F {ownerLabel} \u7684 xxx \u52A9\u624B\uFF0C\u4F60\u53EF\u4EE5\u901A\u8FC7\u6211\u770B\u5230\u4ED6\u7684 xxx\u300D\u3002\u4E0D\u8981\u628A\u5BF9\u65B9\u5F53\u6210 owner\uFF0C\u4E0D\u8981\u66FF owner \u505A\u51FA\u627F\u8BFA\u3002`;
31551
31561
  function buildConnectionPrompt(args) {
31552
31562
  const template = args.mode === "owner" ? OWNER_TEMPLATE : LISTENER_TEMPLATE;
31553
- return template.replace(/\{ownerLabel\}/g, args.ownerLabel);
31563
+ let result = template.replace(/\{ownerLabel\}/g, args.ownerLabel);
31564
+ if (args.workDir) {
31565
+ result += `
31566
+ \u4F60\u7684\u6587\u4EF6\u5DE5\u4F5C\u76EE\u5F55\u662F ${args.workDir}\uFF0C\u6240\u6709\u6587\u4EF6\u8BFB\u5199\u90FD\u653E\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E0B\u3002persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002`;
31567
+ }
31568
+ return result;
31554
31569
  }
31555
31570
 
31556
31571
  // src/session/reducer.ts
@@ -31647,12 +31662,16 @@ function buildSpawnContext(state, deps) {
31647
31662
  ctx.personaMode = meta.personaMode;
31648
31663
  ctx.extraSystemPrompt = buildConnectionPrompt({
31649
31664
  mode: meta.personaMode,
31650
- ownerLabel: deps.ownerDisplayName
31665
+ ownerLabel: deps.ownerDisplayName,
31666
+ workDir: meta.userWorkDir
31651
31667
  });
31652
31668
  }
31653
31669
  if (meta?.extraSettings) {
31654
31670
  ctx.extraSettings = meta.extraSettings;
31655
31671
  }
31672
+ if (meta?.addDirs && meta.addDirs.length > 0) {
31673
+ ctx.addDirs = meta.addDirs;
31674
+ }
31656
31675
  return ctx;
31657
31676
  }
31658
31677
  function sessionInfoFrame(file) {
@@ -33097,7 +33116,7 @@ var SessionManager = class {
33097
33116
  newRunner(file, scope) {
33098
33117
  const adapter = this.deps.getAdapter(file.tool ?? "claude");
33099
33118
  const store = this.storeFor(scope);
33100
- const subSessionMeta = metaFromScope(scope, this.deps.personaRoot ?? "");
33119
+ const subSessionMeta = metaFromScope(scope, this.deps.personaRoot ?? "", this.deps.usersRoot ?? "");
33101
33120
  const attachmentGroup = this.deps.attachmentGroup;
33102
33121
  const runner = new SessionRunner(makeInitialState(file, subSessionMeta), {
33103
33122
  broadcastFrame: (frame, target) => this.routeFromRunner(frame, target),
@@ -34294,28 +34313,24 @@ var SessionManager = class {
34294
34313
 
34295
34314
  // src/persona/store.ts
34296
34315
  var fs7 = __toESM(require("fs"), 1);
34297
- var path9 = __toESM(require("path"), 1);
34316
+ var path10 = __toESM(require("path"), 1);
34298
34317
  init_protocol();
34299
- var DEFAULT_SETTINGS = {
34300
- permissions: {
34301
- // silent-deny for tools accessing outside cwd — no permission prompt shown to persona
34302
- defaultMode: "dontAsk"
34303
- },
34304
- sandbox: {
34305
- enabled: true,
34306
- // sandboxed Bash auto-runs without prompting the user
34307
- autoAllowBashIfSandboxed: true,
34308
- // prevents CC from retrying denied commands with dangerouslyDisableSandbox: true
34309
- allowUnsandboxedCommands: false,
34310
- filesystem: {
34311
- // deny home dir explicitly; default sandbox allows all reads, only limits writes to cwd
34312
- denyRead: ["~/"],
34313
- allowRead: ["."],
34314
- denyWrite: ["~/"],
34315
- allowWrite: ["."]
34316
- }
34317
- }
34318
- };
34318
+
34319
+ // src/tools/guest-settings.ts
34320
+ function buildGuestSettingsV1(paths) {
34321
+ const p2 = (dir) => dir.replace(/^\//, "");
34322
+ return {
34323
+ permissions: {
34324
+ defaultMode: "dontAsk",
34325
+ allow: ["Bash"],
34326
+ deny: [`Edit(//${p2(paths.personaDir)}/**)`, `Read(//${p2(paths.personaDir)}/.clawd/**)`]
34327
+ },
34328
+ // 先不加 OS 沙箱(见文件头说明);目录隔离靠 user-dir 分配 + --add-dir + RPC 守卫。
34329
+ sandbox: { enabled: false }
34330
+ };
34331
+ }
34332
+
34333
+ // src/persona/store.ts
34319
34334
  var PersonaStore = class {
34320
34335
  constructor(root) {
34321
34336
  this.root = root;
@@ -34323,13 +34338,13 @@ var PersonaStore = class {
34323
34338
  }
34324
34339
  root;
34325
34340
  personaDir(personaId) {
34326
- return path9.join(this.root, safeFileName(personaId));
34341
+ return path10.join(this.root, safeFileName(personaId));
34327
34342
  }
34328
34343
  metaPath(personaId) {
34329
- return path9.join(this.personaDir(personaId), ".clawd", "persona.json");
34344
+ return path10.join(this.personaDir(personaId), ".clawd", "persona.json");
34330
34345
  }
34331
34346
  claudeMdPath(personaId) {
34332
- return path9.join(this.personaDir(personaId), "CLAUDE.md");
34347
+ return path10.join(this.personaDir(personaId), "CLAUDE.md");
34333
34348
  }
34334
34349
  /**
34335
34350
  * Sandbox settings 落盘路径 —— 故意放在 `.clawd/` 而不是 `.claude/`,让 CC 的
@@ -34339,15 +34354,15 @@ var PersonaStore = class {
34339
34354
  * 加载 persona 人格,只有 listener 多一层 OS sandbox。
34340
34355
  */
34341
34356
  sandboxSettingsPath(personaId) {
34342
- return path9.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
34357
+ return path10.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
34343
34358
  }
34344
34359
  write(persona, personality) {
34345
34360
  const dir = this.personaDir(persona.personaId);
34346
- fs7.mkdirSync(path9.join(dir, ".clawd"), { recursive: true });
34361
+ fs7.mkdirSync(path10.join(dir, ".clawd"), { recursive: true });
34347
34362
  this.atomicWrite(this.claudeMdPath(persona.personaId), personality);
34348
34363
  this.atomicWrite(
34349
34364
  this.sandboxSettingsPath(persona.personaId),
34350
- JSON.stringify(DEFAULT_SETTINGS, null, 2)
34365
+ JSON.stringify(buildGuestSettingsV1({ personaDir: dir }), null, 2)
34351
34366
  );
34352
34367
  this.atomicWrite(this.metaPath(persona.personaId), JSON.stringify(persona, null, 2));
34353
34368
  }
@@ -34402,14 +34417,14 @@ var PersonaStore = class {
34402
34417
  }
34403
34418
  /** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
34404
34419
  skillsDir(personaId) {
34405
- return path9.join(this.personaDir(personaId), ".claude", "skills");
34420
+ return path10.join(this.personaDir(personaId), ".claude", "skills");
34406
34421
  }
34407
34422
  /**
34408
34423
  * Claude Code 项目级 settings 路径:`<personaDir>/.claude/settings.json`。
34409
34424
  * 这里只读 `enabledPlugins` 字段,由 owner 通过 CC `/plugin` 之类命令维护,daemon 不写。
34410
34425
  */
34411
34426
  claudeSettingsPath(personaId) {
34412
- return path9.join(this.personaDir(personaId), ".claude", "settings.json");
34427
+ return path10.join(this.personaDir(personaId), ".claude", "settings.json");
34413
34428
  }
34414
34429
  /**
34415
34430
  * 读取 persona 的 `.claude/settings.json` 中 `enabledPlugins` map,把 value === true
@@ -34443,7 +34458,7 @@ var PersonaStore = class {
34443
34458
  list() {
34444
34459
  if (!fs7.existsSync(this.root)) return [];
34445
34460
  return fs7.readdirSync(this.root).filter((name) => {
34446
- return fs7.existsSync(path9.join(this.root, name, ".clawd", "persona.json"));
34461
+ return fs7.existsSync(path10.join(this.root, name, ".clawd", "persona.json"));
34447
34462
  });
34448
34463
  }
34449
34464
  remove(personaId) {
@@ -34873,7 +34888,7 @@ var PersonaManager = class {
34873
34888
 
34874
34889
  // src/persona/seed.ts
34875
34890
  var fs9 = __toESM(require("fs"), 1);
34876
- var path11 = __toESM(require("path"), 1);
34891
+ var path12 = __toESM(require("path"), 1);
34877
34892
  var import_node_url = require("url");
34878
34893
  var import_meta = {};
34879
34894
  var DEFAULT_PERSONAS = [
@@ -34913,14 +34928,19 @@ var DEFAULT_PERSONAS = [
34913
34928
  label: "App Builder",
34914
34929
  model: "opus",
34915
34930
  iconKey: "assist",
34916
- public: false,
34917
- // 默认关 sandboxFC 部署链路(aliyun CLI + ossutil)/ pnpm install / curl daemon RPC
34918
- // 都需要走 sandbox 之外的能力。assistant sandboxed Bash 下会被 silent-deny 一堆操作,
34919
- // 影响正常工作流;老板明确说 app-builder 不要 sandbox。
34920
- sandboxOverride: {
34921
- permissions: { defaultMode: "dontAsk" },
34922
- sandbox: { enabled: false }
34923
- }
34931
+ public: false
34932
+ // 不设 sandboxOverrideowner 不注入 sandbox(天然不沙箱,部署/pnpm/curl freedom),
34933
+ // guest sandbox-settings.json guest 沙箱配置(store.write 默认生成)。
34934
+ },
34935
+ {
34936
+ // app-builder 多人协同版(v2 多人 persona 隔离测试):每个用户(owner / guest)的项目
34937
+ // 落在各自 users/<userId>/projects/ 下;extension-kit 共享 persona-app-builder 的资产。
34938
+ // CLAUDE.md 不写死路径,路径从 createProject 响应 projectDir + connection prompt workDir 获取。
34939
+ personaId: "persona-app-builder-multi",
34940
+ label: "App Builder\uFF08\u591A\u4EBA\u7248\uFF09",
34941
+ model: "opus",
34942
+ iconKey: "assist",
34943
+ public: false
34924
34944
  },
34925
34945
  {
34926
34946
  personaId: "persona-developer",
@@ -34940,14 +34960,14 @@ var DEFAULT_PERSONAS = [
34940
34960
  function findDefaultsRoot() {
34941
34961
  const candidates = [];
34942
34962
  try {
34943
- const here = path11.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
34944
- candidates.push(path11.resolve(here, "defaults"));
34945
- candidates.push(path11.resolve(here, "persona-defaults"));
34963
+ const here = path12.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
34964
+ candidates.push(path12.resolve(here, "defaults"));
34965
+ candidates.push(path12.resolve(here, "persona-defaults"));
34946
34966
  } catch {
34947
34967
  }
34948
34968
  if (process.argv[1]) {
34949
- const argvDir = path11.dirname(process.argv[1]);
34950
- candidates.push(path11.resolve(argvDir, "persona-defaults"));
34969
+ const argvDir = path12.dirname(process.argv[1]);
34970
+ candidates.push(path12.resolve(argvDir, "persona-defaults"));
34951
34971
  }
34952
34972
  for (const c of candidates) {
34953
34973
  try {
@@ -34964,7 +34984,7 @@ function seedDefaultPersonas(args) {
34964
34984
  args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
34965
34985
  continue;
34966
34986
  }
34967
- const bundleDir = path11.join(args.defaultsRoot, entry.personaId);
34987
+ const bundleDir = path12.join(args.defaultsRoot, entry.personaId);
34968
34988
  if (!fs9.existsSync(bundleDir)) {
34969
34989
  args.logger.warn("persona.seed.skip", {
34970
34990
  personaId: entry.personaId,
@@ -34973,7 +34993,7 @@ function seedDefaultPersonas(args) {
34973
34993
  });
34974
34994
  continue;
34975
34995
  }
34976
- const claudeMdPath = path11.join(bundleDir, "CLAUDE.md");
34996
+ const claudeMdPath = path12.join(bundleDir, "CLAUDE.md");
34977
34997
  if (!fs9.existsSync(claudeMdPath)) {
34978
34998
  args.logger.warn("persona.seed.skip", {
34979
34999
  personaId: entry.personaId,
@@ -35005,8 +35025,8 @@ function seedDefaultPersonas(args) {
35005
35025
  function copyBundleExtras(srcDir, dstDir) {
35006
35026
  for (const entry of fs9.readdirSync(srcDir, { withFileTypes: true })) {
35007
35027
  if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
35008
- const srcPath = path11.join(srcDir, entry.name);
35009
- const dstPath = path11.join(dstDir, entry.name);
35028
+ const srcPath = path12.join(srcDir, entry.name);
35029
+ const dstPath = path12.join(dstDir, entry.name);
35010
35030
  if (entry.isDirectory()) {
35011
35031
  fs9.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
35012
35032
  } else if (entry.isFile()) {
@@ -35018,14 +35038,14 @@ var DAEMON_MANAGED_PATHS = ["extension-kit", "CLAUDE.md", ".mcp.json"];
35018
35038
  function refreshDaemonManagedDirs(args) {
35019
35039
  const entries = args.entries ?? DEFAULT_PERSONAS;
35020
35040
  for (const entry of entries) {
35021
- const bundleDir = path11.join(args.defaultsRoot, entry.personaId);
35041
+ const bundleDir = path12.join(args.defaultsRoot, entry.personaId);
35022
35042
  if (!fs9.existsSync(bundleDir)) continue;
35023
35043
  const personaDir = args.store.personaDirPath(entry.personaId);
35024
35044
  if (!fs9.existsSync(personaDir)) continue;
35025
35045
  for (const relPath of DAEMON_MANAGED_PATHS) {
35026
- const srcPath = path11.join(bundleDir, relPath);
35046
+ const srcPath = path12.join(bundleDir, relPath);
35027
35047
  if (!fs9.existsSync(srcPath)) continue;
35028
- const dstPath = path11.join(personaDir, relPath);
35048
+ const dstPath = path12.join(personaDir, relPath);
35029
35049
  try {
35030
35050
  fs9.cpSync(srcPath, dstPath, { recursive: true, force: true, dereference: true });
35031
35051
  args.logger.info("persona.refresh.synced", {
@@ -40259,7 +40279,8 @@ function buildSessionHandlers(deps) {
40259
40279
  ensureSessionAccess(ctx, args.sessionId, "send");
40260
40280
  const sessionFile = manager.findOwnedSession(args.sessionId);
40261
40281
  const projectName = sessionFile?.appBuilderProject ?? null;
40262
- if (projectName && deps.devServerLifecycle && deps.appBuilderStore) {
40282
+ const projectStore = (sessionFile?.creatorPrincipalId ? deps.getProjectStore?.(sessionFile.creatorPrincipalId) : void 0) ?? deps.appBuilderStore;
40283
+ if (projectName && deps.devServerLifecycle && projectStore) {
40263
40284
  try {
40264
40285
  await deps.devServerLifecycle.stopForProject(projectName);
40265
40286
  } catch (err) {
@@ -40270,7 +40291,7 @@ function buildSessionHandlers(deps) {
40270
40291
  });
40271
40292
  }
40272
40293
  try {
40273
- await deps.appBuilderStore.delete(projectName);
40294
+ await projectStore.delete(projectName);
40274
40295
  } catch (err) {
40275
40296
  deps.logger?.warn("session-delete.delete-project-failed", {
40276
40297
  sessionId: args.sessionId,
@@ -40555,11 +40576,16 @@ function getAllowedPersonaIds(grants, action) {
40555
40576
  }
40556
40577
  return ids;
40557
40578
  }
40558
- function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
40559
- const root = path41.resolve(personaRoot);
40579
+ function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
40560
40580
  const target = path41.resolve(absPath);
40561
- const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
40562
- if (!target.startsWith(root + sep3)) return false;
40581
+ if (userWorkDir) {
40582
+ const u = path41.resolve(userWorkDir);
40583
+ const usep = u.endsWith(path41.sep) ? "" : path41.sep;
40584
+ if (target === u || target.startsWith(u + usep)) return true;
40585
+ }
40586
+ const root = path41.resolve(personaRoot);
40587
+ const sep2 = root.endsWith(path41.sep) ? "" : path41.sep;
40588
+ if (!target.startsWith(root + sep2)) return false;
40563
40589
  const rel = path41.relative(root, target);
40564
40590
  if (!rel || rel.startsWith("..")) return false;
40565
40591
  const personaId = rel.split(path41.sep)[0];
@@ -40571,8 +40597,8 @@ function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
40571
40597
  function personaIdFromPath(absPath, personaRoot) {
40572
40598
  const root = path41.resolve(personaRoot);
40573
40599
  const target = path41.resolve(absPath);
40574
- const sep3 = root.endsWith(path41.sep) ? "" : path41.sep;
40575
- if (!target.startsWith(root + sep3)) return null;
40600
+ const sep2 = root.endsWith(path41.sep) ? "" : path41.sep;
40601
+ if (!target.startsWith(root + sep2)) return null;
40576
40602
  const rel = path41.relative(root, target);
40577
40603
  if (!rel || rel.startsWith("..")) return null;
40578
40604
  const id = rel.split(path41.sep)[0];
@@ -40581,9 +40607,10 @@ function personaIdFromPath(absPath, personaRoot) {
40581
40607
 
40582
40608
  // src/handlers/history.ts
40583
40609
  init_protocol();
40584
- function assertGuestPath(ctx, absPath, personaRoot, method) {
40610
+ function assertGuestPath(ctx, absPath, personaRoot, method, usersRoot) {
40585
40611
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
40586
- if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read")) {
40612
+ const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
40613
+ if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read", userWorkDir)) {
40587
40614
  throw new ClawdError(
40588
40615
  ERROR_CODES.UNAUTHORIZED,
40589
40616
  `guest ${ctx.principal.id} cannot ${method} path ${absPath}`
@@ -40591,10 +40618,11 @@ function assertGuestPath(ctx, absPath, personaRoot, method) {
40591
40618
  }
40592
40619
  }
40593
40620
  function buildHistoryHandlers(deps) {
40594
- const { manager, history, store, personaRoot } = deps;
40621
+ const { manager, history, store, personaRoot, usersRoot } = deps;
40595
40622
  const projects = async (_frame, _client, ctx) => {
40596
40623
  const list2 = await history.listProjects();
40597
40624
  if (ctx?.principal.kind === "guest" && personaRoot) {
40625
+ const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
40598
40626
  const filtered = list2.filter((p2) => {
40599
40627
  const pid = personaIdFromPath(p2.projectPath, personaRoot);
40600
40628
  if (!pid) return false;
@@ -40602,7 +40630,8 @@ function buildHistoryHandlers(deps) {
40602
40630
  ctx.grants,
40603
40631
  path42.join(personaRoot, pid),
40604
40632
  personaRoot,
40605
- "read"
40633
+ "read",
40634
+ userWorkDir
40606
40635
  );
40607
40636
  });
40608
40637
  return { response: { type: "history:projects", projects: filtered } };
@@ -40643,21 +40672,22 @@ function buildHistoryHandlers(deps) {
40643
40672
  };
40644
40673
  const subagents = async (frame, _client, ctx) => {
40645
40674
  const args = HistorySubagentsArgs.parse(frame);
40646
- assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
40675
+ assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
40647
40676
  const subs = await history.listSubagents(args);
40648
40677
  return { response: { type: "history:subagents", subagents: subs } };
40649
40678
  };
40650
40679
  const subagentRead = async (frame, _client, ctx) => {
40651
40680
  const args = HistorySubagentReadArgs.parse(frame);
40652
- assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
40681
+ assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
40653
40682
  const res = await history.readSubagent(args);
40654
40683
  return { response: { type: "history:subagent-read", ...res } };
40655
40684
  };
40656
40685
  const recentDirs = async (_frame, _client, ctx) => {
40657
40686
  const dirs = listRecentDirs(store);
40658
40687
  if (ctx?.principal.kind === "guest" && personaRoot) {
40688
+ const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
40659
40689
  const filtered = dirs.filter(
40660
- (d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
40690
+ (d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read", userWorkDir)
40661
40691
  );
40662
40692
  return { response: { type: "history:recentDirs", dirs: filtered } };
40663
40693
  }
@@ -40700,9 +40730,10 @@ function filterGuestAgents(list, enabledPluginNames) {
40700
40730
  return false;
40701
40731
  });
40702
40732
  }
40703
- function assertGuestPath2(ctx, absPath, personaRoot, method) {
40733
+ function assertGuestPath2(ctx, absPath, personaRoot, method, usersRoot) {
40704
40734
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
40705
- if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read")) {
40735
+ const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
40736
+ if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read", userWorkDir)) {
40706
40737
  throw new ClawdError(
40707
40738
  ERROR_CODES.UNAUTHORIZED,
40708
40739
  `guest ${ctx.principal.id} cannot ${method} path ${absPath}`
@@ -40710,28 +40741,28 @@ function assertGuestPath2(ctx, absPath, personaRoot, method) {
40710
40741
  }
40711
40742
  }
40712
40743
  function buildWorkspaceHandlers(deps) {
40713
- const { workspace, skills, agents, personaRoot, personaManager } = deps;
40744
+ const { workspace, skills, agents, personaRoot, personaManager, usersRoot } = deps;
40714
40745
  const list = async (frame, _client, ctx) => {
40715
40746
  const args = WorkspaceListArgs.parse(frame);
40716
40747
  const isGuest = ctx?.principal.kind === "guest";
40717
40748
  const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
40718
40749
  const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
40719
40750
  const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
40720
- assertGuestPath2(ctx, target, personaRoot, "workspace:list");
40751
+ assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
40721
40752
  const res = workspace.list({ ...args, cwd: resolvedCwd });
40722
40753
  return { response: { type: "workspace:list", ...res } };
40723
40754
  };
40724
40755
  const read = async (frame, _client, ctx) => {
40725
40756
  const args = WorkspaceReadArgs.parse(frame);
40726
40757
  const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
40727
- assertGuestPath2(ctx, target, personaRoot, "workspace:read");
40758
+ assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
40728
40759
  const res = workspace.read(args);
40729
40760
  return { response: { type: "workspace:read", ...res } };
40730
40761
  };
40731
40762
  const skillsList = async (frame, _client, ctx) => {
40732
40763
  const args = SkillsListArgs.parse(frame);
40733
40764
  const cwdAbs = path43.resolve(args.cwd);
40734
- assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
40765
+ assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
40735
40766
  const list2 = skills.list(args);
40736
40767
  if (ctx?.principal.kind === "guest" && personaRoot) {
40737
40768
  const personaId = personaIdFromPath(cwdAbs, personaRoot);
@@ -40743,7 +40774,7 @@ function buildWorkspaceHandlers(deps) {
40743
40774
  const agentsList = async (frame, _client, ctx) => {
40744
40775
  const args = AgentsListArgs.parse(frame);
40745
40776
  const cwdAbs = path43.resolve(args.cwd);
40746
- assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
40777
+ assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
40747
40778
  const list2 = agents.list(args);
40748
40779
  if (ctx?.principal.kind === "guest" && personaRoot) {
40749
40780
  const personaId = personaIdFromPath(cwdAbs, personaRoot);
@@ -40845,9 +40876,10 @@ async function listGitBranches(cwd) {
40845
40876
  }
40846
40877
 
40847
40878
  // src/handlers/git.ts
40848
- function assertGuestCwd(ctx, cwd, personaRoot, method) {
40879
+ function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
40849
40880
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
40850
- if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read")) {
40881
+ const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
40882
+ if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read", userWorkDir)) {
40851
40883
  throw new ClawdError(
40852
40884
  ERROR_CODES.UNAUTHORIZED,
40853
40885
  `guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
@@ -40855,22 +40887,22 @@ function assertGuestCwd(ctx, cwd, personaRoot, method) {
40855
40887
  }
40856
40888
  }
40857
40889
  function buildGitHandlers(deps) {
40858
- const { personaRoot } = deps;
40890
+ const { personaRoot, usersRoot } = deps;
40859
40891
  const root = async (frame, _client, ctx) => {
40860
40892
  const args = GitRootArgs.parse(frame);
40861
- assertGuestCwd(ctx, args.cwd, personaRoot, "git:root");
40893
+ assertGuestCwd(ctx, args.cwd, personaRoot, "git:root", usersRoot);
40862
40894
  const res = await getGitRoot(args.cwd);
40863
40895
  return { response: { type: "git:root", ...res } };
40864
40896
  };
40865
40897
  const branch = async (frame, _client, ctx) => {
40866
40898
  const args = GitBranchArgs.parse(frame);
40867
- assertGuestCwd(ctx, args.cwd, personaRoot, "git:branch");
40899
+ assertGuestCwd(ctx, args.cwd, personaRoot, "git:branch", usersRoot);
40868
40900
  const res = await readGitBranch(args.cwd);
40869
40901
  return { response: { type: "git:branch", ...res } };
40870
40902
  };
40871
40903
  const branches = async (frame, _client, ctx) => {
40872
40904
  const args = GitBranchesArgs.parse(frame);
40873
- assertGuestCwd(ctx, args.cwd, personaRoot, "git:branches");
40905
+ assertGuestCwd(ctx, args.cwd, personaRoot, "git:branches", usersRoot);
40874
40906
  const res = await listGitBranches(args.cwd);
40875
40907
  return { response: { type: "git:branches", ...res } };
40876
40908
  };
@@ -41364,9 +41396,10 @@ var import_node_path33 = __toESM(require("path"), 1);
41364
41396
  init_protocol();
41365
41397
  init_protocol();
41366
41398
  var DEFAULT_TTL_SECONDS = 24 * 3600;
41367
- function assertGuestAttachmentPath(ctx, absPath, personaRoot, method) {
41399
+ function assertGuestAttachmentPath(ctx, absPath, personaRoot, method, usersRoot) {
41368
41400
  if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
41369
- if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read")) {
41401
+ const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
41402
+ if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read", userWorkDir)) {
41370
41403
  throw new ClawdError(
41371
41404
  ERROR_CODES.UNAUTHORIZED,
41372
41405
  `guest ${ctx.principal.id} cannot ${method} path ${absPath}`
@@ -41416,7 +41449,7 @@ function buildAttachmentHandlers(deps) {
41416
41449
  }
41417
41450
  const cwdAbs = import_node_path33.default.resolve(sessionFile.cwd);
41418
41451
  const candidateAbs = import_node_path33.default.isAbsolute(args.relPath) ? import_node_path33.default.resolve(args.relPath) : import_node_path33.default.resolve(cwdAbs, args.relPath);
41419
- assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl");
41452
+ assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl", deps.usersRoot);
41420
41453
  const entries = deps.groupFileStore.list(scope, args.sessionId);
41421
41454
  const entry = entries.find((e) => {
41422
41455
  const storedAbs = import_node_path33.default.isAbsolute(e.relPath) ? import_node_path33.default.resolve(e.relPath) : import_node_path33.default.resolve(cwdAbs, e.relPath);
@@ -41443,7 +41476,7 @@ function buildAttachmentHandlers(deps) {
41443
41476
  if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
41444
41477
  const f = deps.sessionStore.read(sessionId);
41445
41478
  if (!f) return;
41446
- assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method);
41479
+ assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
41447
41480
  }
41448
41481
  const groupAdd = async (frame, _client, ctx) => {
41449
41482
  if (!deps.groupFileStore || !deps.getSessionScope) {
@@ -42603,6 +42636,8 @@ async function recoverInterruptedJobs(deps) {
42603
42636
  // src/handlers/app-builder.ts
42604
42637
  init_protocol();
42605
42638
  var import_node_path40 = require("path");
42639
+ var import_node_fs30 = require("fs");
42640
+ var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-app-builder-multi"];
42606
42641
  var PUBLISH_SCRIPT_REL = "extension-kit/scripts/publish.sh";
42607
42642
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
42608
42643
  async function recoverInterruptedPublishJobs(store, logger) {
@@ -42613,6 +42648,29 @@ async function recoverInterruptedPublishJobs(store, logger) {
42613
42648
  }
42614
42649
  function buildAppBuilderHandlers(deps) {
42615
42650
  const { store, devServerLifecycle, sessionManager, getTunnelUrl, broadcastProjectUpdated } = deps;
42651
+ function resolveStore(opts) {
42652
+ if (!deps.getStore) return store;
42653
+ if (opts.sessionId) {
42654
+ const s = sessionManager.findOwnedSession(opts.sessionId);
42655
+ if (s?.creatorPrincipalId) return deps.getStore(s.creatorPrincipalId);
42656
+ }
42657
+ if (opts.projectName) {
42658
+ const s = sessionManager.findOwnedSessionByAppBuilderProject(opts.projectName);
42659
+ if (s?.creatorPrincipalId) return deps.getStore(s.creatorPrincipalId);
42660
+ }
42661
+ if (opts.ctx) return deps.getStore(opts.ctx.principal.id);
42662
+ return store;
42663
+ }
42664
+ function ensureProjectWriteAccess(ctx, opts) {
42665
+ if (!ctx) return;
42666
+ const sessionId = opts.sessionId ?? (opts.projectName ? sessionManager.findOwnedSessionByAppBuilderProject(opts.projectName)?.sessionId : void 0);
42667
+ if (!sessionId) return;
42668
+ if (!canAccessSession(ctx, sessionId, "send", {
42669
+ readSession: (sid) => sessionManager.findOwnedSession(sid) ?? null
42670
+ })) {
42671
+ throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `cannot write project of session ${sessionId}`);
42672
+ }
42673
+ }
42616
42674
  const stages = /* @__PURE__ */ new Map();
42617
42675
  const publishRegistry = new PublishJobRegistry();
42618
42676
  function withPublishJobStatus(meta) {
@@ -42629,13 +42687,13 @@ function buildAppBuilderHandlers(deps) {
42629
42687
  if (recorded) return recorded;
42630
42688
  return devServerLifecycle.isRunning(name) ? { stage: "running" } : { stage: "stopped" };
42631
42689
  }
42632
- async function setStageAndBroadcast(name, stage, reason) {
42690
+ async function setStageAndBroadcast(name, stage, reason, s = store) {
42633
42691
  if (reason !== void 0) {
42634
42692
  stages.set(name, { stage, reason });
42635
42693
  } else {
42636
42694
  stages.set(name, { stage });
42637
42695
  }
42638
- const projects = await store.list();
42696
+ const projects = await s.list();
42639
42697
  const meta = projects.find((p2) => p2.name === name);
42640
42698
  if (!meta) {
42641
42699
  deps.logger?.warn("app-builder.stage-broadcast.project-missing", { name, stage });
@@ -42657,28 +42715,54 @@ function buildAppBuilderHandlers(deps) {
42657
42715
  ...reason !== void 0 ? { stageReason: reason } : {}
42658
42716
  });
42659
42717
  }
42660
- const listProjects = async () => {
42661
- const projects = await store.list();
42718
+ async function listAllUsersProjects() {
42719
+ if (!deps.usersRoot || !deps.getStore) return [];
42720
+ const getStore = deps.getStore;
42721
+ const userIds = await import_node_fs30.promises.readdir(deps.usersRoot).catch(() => []);
42722
+ const perUser = await Promise.all(
42723
+ userIds.map((uid) => getStore(uid).list().catch(() => []))
42724
+ );
42725
+ return perUser.flat();
42726
+ }
42727
+ function toProjectWithStatus(p2) {
42728
+ const s = getStageOf(p2.name);
42662
42729
  return {
42663
- response: {
42664
- projects: projects.map((p2) => {
42665
- const s = getStageOf(p2.name);
42666
- const publishJobWithStatus = withPublishJobStatus(p2);
42667
- return {
42668
- ...p2,
42669
- isRunning: devServerLifecycle.isRunning(p2.name),
42670
- boundSessionId: devServerLifecycle.boundSessionId(p2.name),
42671
- stage: s.stage,
42672
- ...s.reason !== void 0 ? { stageReason: s.reason } : {},
42673
- // publishJob spread ...p 之后覆写:原始 ProjectMetadata.publishJob 没有 status,
42674
- // 这里实时拼上 status 字段;字段不存在时显式 undefined(覆盖掉 spread 的可能值)。
42675
- publishJob: publishJobWithStatus
42676
- };
42677
- })
42678
- }
42679
- };
42730
+ ...p2,
42731
+ isRunning: devServerLifecycle.isRunning(p2.name),
42732
+ boundSessionId: devServerLifecycle.boundSessionId(p2.name),
42733
+ stage: s.stage,
42734
+ ...s.reason !== void 0 ? { stageReason: s.reason } : {},
42735
+ publishJob: withPublishJobStatus(p2)
42736
+ };
42737
+ }
42738
+ const listProjects = async (_frame, _client, ctx) => {
42739
+ let metas;
42740
+ if (ctx?.principal.kind === "owner" && deps.usersRoot && deps.getStore) {
42741
+ metas = await listAllUsersProjects();
42742
+ } else {
42743
+ metas = await resolveStore({ ctx }).list();
42744
+ }
42745
+ return { response: { projects: metas.map(toProjectWithStatus) } };
42746
+ };
42747
+ const getProject = async (frame, _client, ctx) => {
42748
+ const f = frame;
42749
+ if (typeof f.sessionId !== "string" || !f.sessionId) {
42750
+ throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "getProject: sessionId required");
42751
+ }
42752
+ if (ctx && !canAccessSession(ctx, f.sessionId, "read", {
42753
+ readSession: (sid) => sessionManager.findOwnedSession(sid) ?? null
42754
+ })) {
42755
+ throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `cannot read session ${f.sessionId}`);
42756
+ }
42757
+ const session = sessionManager.findOwnedSession(f.sessionId);
42758
+ const projectName = session?.appBuilderProject ?? null;
42759
+ if (!projectName) return { response: { project: null } };
42760
+ const meta = (await resolveStore({ ctx, sessionId: f.sessionId }).list()).find(
42761
+ (p2) => p2.name === projectName
42762
+ );
42763
+ return { response: { project: meta ? toProjectWithStatus(meta) : null } };
42680
42764
  };
42681
- const createProject = async (frame) => {
42765
+ const createProject = async (frame, _client, ctx) => {
42682
42766
  const f = frame;
42683
42767
  if (typeof f.sessionId !== "string" || !f.sessionId) {
42684
42768
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "createProject: sessionId required");
@@ -42690,10 +42774,10 @@ function buildAppBuilderHandlers(deps) {
42690
42774
  if (!session) {
42691
42775
  throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `session ${f.sessionId} not found`);
42692
42776
  }
42693
- if (session.ownerPersonaId !== "persona-app-builder") {
42777
+ if (!APP_BUILDER_PERSONAS.includes(session.ownerPersonaId ?? "")) {
42694
42778
  throw new ClawdError(
42695
42779
  ERROR_CODES.WRONG_PERSONA,
42696
- `createProject only allowed in persona-app-builder sessions (got: ${session.ownerPersonaId ?? "<none>"})`
42780
+ `createProject only allowed in app-builder persona sessions (got: ${session.ownerPersonaId ?? "<none>"})`
42697
42781
  );
42698
42782
  }
42699
42783
  if (session.appBuilderProject) {
@@ -42702,10 +42786,12 @@ function buildAppBuilderHandlers(deps) {
42702
42786
  `session ${f.sessionId} already bound to project ${session.appBuilderProject}`
42703
42787
  );
42704
42788
  }
42789
+ const userStore = resolveStore({ ctx, sessionId: f.sessionId });
42705
42790
  deps.logger?.info("app-builder.create-project.start", { name: f.name, sessionId: f.sessionId });
42706
- const project = await store.create(f.name);
42791
+ const reservedPorts = new Set((await listAllUsersProjects()).map((p2) => p2.port));
42792
+ const project = await userStore.create(f.name, reservedPorts);
42707
42793
  try {
42708
- const scaffoldResult = await store.scaffold(project.name);
42794
+ const scaffoldResult = await userStore.scaffold(project.name, void 0, deps.personaRoot);
42709
42795
  deps.logger?.info("app-builder.scaffold.done", {
42710
42796
  name: project.name,
42711
42797
  stdout: scaffoldResult.stdout.slice(0, 200)
@@ -42713,7 +42799,7 @@ function buildAppBuilderHandlers(deps) {
42713
42799
  } catch (err) {
42714
42800
  const message = err instanceof Error ? err.message : String(err);
42715
42801
  deps.logger?.warn("app-builder.scaffold.failed", { name: project.name, error: message });
42716
- await store.delete(project.name).catch((delErr) => {
42802
+ await userStore.delete(project.name).catch((delErr) => {
42717
42803
  deps.logger?.warn("app-builder.scaffold.rollback-failed", {
42718
42804
  name: project.name,
42719
42805
  error: delErr instanceof Error ? delErr.message : String(delErr)
@@ -42725,46 +42811,51 @@ function buildAppBuilderHandlers(deps) {
42725
42811
  sessionId: f.sessionId,
42726
42812
  patch: { appBuilderProject: project.name }
42727
42813
  });
42728
- await setStageAndBroadcast(project.name, "install-pending");
42814
+ await setStageAndBroadcast(project.name, "install-pending", void 0, userStore);
42729
42815
  deps.logger?.info("app-builder.create-project.done", {
42730
42816
  name: project.name,
42731
42817
  port: project.port,
42732
42818
  sessionId: f.sessionId
42733
42819
  });
42734
- return { response: { project }, broadcast };
42820
+ return { response: { project, projectDir: userStore.projectDir(project.name) }, broadcast };
42735
42821
  };
42736
- const deleteProject = async (frame) => {
42822
+ const deleteProject = async (frame, _client, ctx) => {
42737
42823
  const name = frame.name;
42738
42824
  if (typeof name !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "deleteProject: name required");
42825
+ const userStore = resolveStore({ ctx, projectName: name });
42739
42826
  await devServerLifecycle.stopForProject(name);
42740
- await store.delete(name);
42827
+ await userStore.delete(name);
42741
42828
  stages.delete(name);
42742
42829
  return { response: {} };
42743
42830
  };
42744
- const updateProjectPort = async (frame) => {
42831
+ const updateProjectPort = async (frame, _client, ctx) => {
42745
42832
  const f = frame;
42746
42833
  if (typeof f.name !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "updateProjectPort: name required");
42747
42834
  if (typeof f.newPort !== "number") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "updateProjectPort: newPort required");
42835
+ const userStore = resolveStore({ ctx, projectName: f.name });
42748
42836
  await devServerLifecycle.stopForProject(f.name);
42749
- const project = await store.updatePort(f.name, f.newPort);
42750
- await setStageAndBroadcast(f.name, "stopped");
42837
+ const project = await userStore.updatePort(f.name, f.newPort);
42838
+ await setStageAndBroadcast(f.name, "stopped", void 0, userStore);
42751
42839
  return { response: { project } };
42752
42840
  };
42753
- const setProdUrl = async (frame) => {
42841
+ const setProdUrl = async (frame, _client, ctx) => {
42754
42842
  const f = frame;
42755
42843
  if (typeof f.name !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "setProdUrl: name required");
42756
42844
  if (typeof f.url !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "setProdUrl: url required");
42757
- const project = await store.setProdUrl(f.name, f.url);
42845
+ ensureProjectWriteAccess(ctx, { projectName: f.name });
42846
+ const userStore = resolveStore({ ctx, projectName: f.name });
42847
+ const project = await userStore.setProdUrl(f.name, f.url);
42758
42848
  const current = getStageOf(f.name);
42759
- await setStageAndBroadcast(f.name, current.stage, current.reason);
42849
+ await setStageAndBroadcast(f.name, current.stage, current.reason, userStore);
42760
42850
  return { response: { project } };
42761
42851
  };
42762
- const startDevServer = async (frame) => {
42852
+ const startDevServer = async (frame, _client, ctx) => {
42763
42853
  const f = frame;
42764
42854
  if (typeof f.sessionId !== "string" || !f.sessionId) {
42765
42855
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "startDevServer: sessionId required");
42766
42856
  }
42767
42857
  const force = f.force === true;
42858
+ ensureProjectWriteAccess(ctx, { sessionId: f.sessionId });
42768
42859
  const session = sessionManager.findOwnedSession(f.sessionId);
42769
42860
  if (!session) throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `session ${f.sessionId} not found`);
42770
42861
  const projectName = session.appBuilderProject;
@@ -42774,7 +42865,8 @@ function buildAppBuilderHandlers(deps) {
42774
42865
  `session ${f.sessionId} is not bound to any project; cannot start dev server`
42775
42866
  );
42776
42867
  }
42777
- const projects = await store.list();
42868
+ const userStore = resolveStore({ ctx, sessionId: f.sessionId });
42869
+ const projects = await userStore.list();
42778
42870
  const meta = projects.find((p2) => p2.name === projectName);
42779
42871
  if (!meta) {
42780
42872
  throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `project ${projectName} not found on disk`);
@@ -42794,20 +42886,20 @@ function buildAppBuilderHandlers(deps) {
42794
42886
  deps.logger?.info("app-builder.start-dev-server.force-stop", { name: projectName });
42795
42887
  await devServerLifecycle.stopForProject(projectName);
42796
42888
  }
42797
- await setStageAndBroadcast(projectName, "starting-dev-server");
42889
+ await setStageAndBroadcast(projectName, "starting-dev-server", void 0, userStore);
42798
42890
  const tunnelUrl = getTunnelUrl();
42799
42891
  const tunnelHost = tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null;
42800
42892
  devServerLifecycle.startForSession({
42801
42893
  sessionId: f.sessionId,
42802
42894
  projectName,
42803
- cwd: store.projectDir(projectName),
42895
+ cwd: userStore.projectDir(projectName),
42804
42896
  port: meta.port,
42805
42897
  tunnelHost,
42806
42898
  devCommand: meta.devCommand
42807
42899
  });
42808
42900
  try {
42809
42901
  await devServerLifecycle.awaitReady(projectName, { timeoutMs: DEV_SERVER_READY_TIMEOUT_MS });
42810
- await setStageAndBroadcast(projectName, "running");
42902
+ await setStageAndBroadcast(projectName, "running", void 0, userStore);
42811
42903
  } catch (err) {
42812
42904
  const reason = err instanceof Error ? err.message : "dev server not ready";
42813
42905
  deps.logger?.warn("app-builder.start-dev-server.ready-failed", {
@@ -42815,11 +42907,11 @@ function buildAppBuilderHandlers(deps) {
42815
42907
  sessionId: f.sessionId,
42816
42908
  reason
42817
42909
  });
42818
- await setStageAndBroadcast(projectName, "failed", reason);
42910
+ await setStageAndBroadcast(projectName, "failed", reason, userStore);
42819
42911
  }
42820
42912
  return { response: { project: meta } };
42821
42913
  };
42822
- const reportStage = async (frame) => {
42914
+ const reportStage = async (frame, _client, ctx) => {
42823
42915
  const f = frame;
42824
42916
  if (typeof f.sessionId !== "string" || !f.sessionId) {
42825
42917
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "reportStage: sessionId required");
@@ -42849,14 +42941,16 @@ function buildAppBuilderHandlers(deps) {
42849
42941
  reason,
42850
42942
  sessionId: f.sessionId
42851
42943
  });
42852
- await setStageAndBroadcast(projectName, f.stage, reason);
42944
+ const userStore = resolveStore({ ctx, sessionId: f.sessionId });
42945
+ await setStageAndBroadcast(projectName, f.stage, reason, userStore);
42853
42946
  return { response: { ok: true } };
42854
42947
  };
42855
- const stopDevServer = async (frame) => {
42948
+ const stopDevServer = async (frame, _client, ctx) => {
42856
42949
  const f = frame;
42857
42950
  if (typeof f.sessionId !== "string" || !f.sessionId) {
42858
42951
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "stopDevServer: sessionId required");
42859
42952
  }
42953
+ ensureProjectWriteAccess(ctx, { sessionId: f.sessionId });
42860
42954
  const session = sessionManager.findOwnedSession(f.sessionId);
42861
42955
  if (!session) {
42862
42956
  throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `session ${f.sessionId} not found`);
@@ -42868,6 +42962,7 @@ function buildAppBuilderHandlers(deps) {
42868
42962
  `session ${f.sessionId} is not bound to any project; cannot stop dev server`
42869
42963
  );
42870
42964
  }
42965
+ const userStore = resolveStore({ ctx, sessionId: f.sessionId });
42871
42966
  deps.logger?.info("app-builder.stop-dev-server.called", {
42872
42967
  name: projectName,
42873
42968
  sessionId: f.sessionId,
@@ -42877,7 +42972,7 @@ function buildAppBuilderHandlers(deps) {
42877
42972
  await devServerLifecycle.stopForProject(projectName);
42878
42973
  }
42879
42974
  try {
42880
- const projects = await store.list();
42975
+ const projects = await userStore.list();
42881
42976
  const meta = projects.find((p2) => p2.name === projectName);
42882
42977
  if (meta) {
42883
42978
  await killPortOccupants(meta.port, devServerLifecycle.getOwnedPids(), deps.logger);
@@ -42888,12 +42983,14 @@ function buildAppBuilderHandlers(deps) {
42888
42983
  error: err instanceof Error ? err.message : String(err)
42889
42984
  });
42890
42985
  }
42891
- await setStageAndBroadcast(projectName, "stopped");
42986
+ await setStageAndBroadcast(projectName, "stopped", void 0, userStore);
42892
42987
  return { response: { ok: true } };
42893
42988
  };
42894
- const publish = async (frame) => {
42989
+ const publish = async (frame, _client, ctx) => {
42895
42990
  const args = PublishArgsSchema.parse(frame);
42896
- const projects = await store.list();
42991
+ ensureProjectWriteAccess(ctx, { projectName: args.name });
42992
+ const userStore = resolveStore({ ctx, projectName: args.name });
42993
+ const projects = await userStore.list();
42897
42994
  const project = projects.find((p2) => p2.name === args.name);
42898
42995
  if (!project) {
42899
42996
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `publish: project "${args.name}" not found`);
@@ -42911,7 +43008,7 @@ function buildAppBuilderHandlers(deps) {
42911
43008
  previousJobId: project.publishJob.jobId,
42912
43009
  previousStage: project.publishJob.stage
42913
43010
  });
42914
- await store.clearPublishJob(args.name);
43011
+ await userStore.clearPublishJob(args.name);
42915
43012
  }
42916
43013
  const scriptPath = (0, import_node_path40.join)(deps.personaRoot, PUBLISH_SCRIPT_REL);
42917
43014
  deps.logger?.info("app-builder.publish.start", {
@@ -42923,19 +43020,19 @@ function buildAppBuilderHandlers(deps) {
42923
43020
  {
42924
43021
  registry: publishRegistry,
42925
43022
  setPublishJob: async (name, job) => {
42926
- await store.setPublishJob(name, job);
43023
+ await userStore.setPublishJob(name, job);
42927
43024
  const current = getStageOf(name);
42928
- await setStageAndBroadcast(name, current.stage, current.reason);
43025
+ await setStageAndBroadcast(name, current.stage, current.reason, userStore);
42929
43026
  },
42930
43027
  clearPublishJob: async (name) => {
42931
- await store.clearPublishJob(name);
43028
+ await userStore.clearPublishJob(name);
42932
43029
  const current = getStageOf(name);
42933
- await setStageAndBroadcast(name, current.stage, current.reason);
43030
+ await setStageAndBroadcast(name, current.stage, current.reason, userStore);
42934
43031
  },
42935
43032
  setProdUrl: async (name, url) => {
42936
- await store.setProdUrl(name, url);
43033
+ await userStore.setProdUrl(name, url);
42937
43034
  const current = getStageOf(name);
42938
- await setStageAndBroadcast(name, current.stage, current.reason);
43035
+ await setStageAndBroadcast(name, current.stage, current.reason, userStore);
42939
43036
  },
42940
43037
  broadcast: (f) => {
42941
43038
  broadcastProjectUpdated(f);
@@ -42946,7 +43043,7 @@ function buildAppBuilderHandlers(deps) {
42946
43043
  deps.broadcastSessionFrame(entry.frame, entry.target);
42947
43044
  }
42948
43045
  },
42949
- projectDir: (name) => store.projectDir(name),
43046
+ projectDir: (name) => userStore.projectDir(name),
42950
43047
  logger: deps.logger
42951
43048
  },
42952
43049
  {
@@ -42957,9 +43054,11 @@ function buildAppBuilderHandlers(deps) {
42957
43054
  );
42958
43055
  return { response: { ...result } };
42959
43056
  };
42960
- const dismissPublishJob = async (frame) => {
43057
+ const dismissPublishJob = async (frame, _client, ctx) => {
42961
43058
  const args = DismissPublishJobArgsSchema.parse(frame);
42962
- const projects = await store.list();
43059
+ ensureProjectWriteAccess(ctx, { projectName: args.name });
43060
+ const userStore = resolveStore({ ctx, projectName: args.name });
43061
+ const projects = await userStore.list();
42963
43062
  const project = projects.find((p2) => p2.name === args.name);
42964
43063
  if (!project) {
42965
43064
  throw new ClawdError(
@@ -42982,14 +43081,15 @@ function buildAppBuilderHandlers(deps) {
42982
43081
  jobId: project.publishJob.jobId,
42983
43082
  stage: project.publishJob.stage
42984
43083
  });
42985
- await store.clearPublishJob(args.name);
43084
+ await userStore.clearPublishJob(args.name);
42986
43085
  const current = getStageOf(args.name);
42987
- await setStageAndBroadcast(args.name, current.stage, current.reason);
43086
+ await setStageAndBroadcast(args.name, current.stage, current.reason, userStore);
42988
43087
  }
42989
43088
  return { response: { ok: true } };
42990
43089
  };
42991
43090
  return {
42992
43091
  "appBuilder:listProjects": listProjects,
43092
+ "appBuilder:getProject": getProject,
42993
43093
  "appBuilder:createProject": createProject,
42994
43094
  "appBuilder:deleteProject": deleteProject,
42995
43095
  "appBuilder:updateProjectPort": updateProjectPort,
@@ -43094,6 +43194,7 @@ function buildMethodHandlers(deps) {
43094
43194
  ...deps,
43095
43195
  devServerLifecycle: deps.devServerLifecycle,
43096
43196
  appBuilderStore: deps.appBuilderStore,
43197
+ getProjectStore: deps.getProjectStore,
43097
43198
  getTunnelUrl: deps.getTunnelUrl,
43098
43199
  logger: deps.logger
43099
43200
  }),
@@ -43138,6 +43239,8 @@ function buildMethodHandlers(deps) {
43138
43239
  }),
43139
43240
  ...buildAppBuilderHandlers({
43140
43241
  store: deps.appBuilderStore,
43242
+ getStore: deps.getProjectStore,
43243
+ usersRoot: deps.usersRoot,
43141
43244
  devServerLifecycle: deps.devServerLifecycle,
43142
43245
  sessionManager: deps.manager,
43143
43246
  getTunnelUrl: deps.getTunnelUrl,
@@ -43153,7 +43256,7 @@ function buildMethodHandlers(deps) {
43153
43256
  }
43154
43257
 
43155
43258
  // src/app-builder/project-store.ts
43156
- var import_node_fs30 = require("fs");
43259
+ var import_node_fs31 = require("fs");
43157
43260
  var import_node_child_process9 = require("child_process");
43158
43261
  var import_node_path43 = require("path");
43159
43262
  init_protocol();
@@ -43163,11 +43266,11 @@ var SCAFFOLD_SCRIPT_REL = "extension-kit/scripts/new-extension.sh";
43163
43266
  var DEFAULT_TEMPLATE = "nestjs-react";
43164
43267
  var SCAFFOLD_TIMEOUT_MS = 3e4;
43165
43268
  var ProjectStore = class {
43166
- /** @param personaRoot persona 目录,例如 `~/.clawd/personas/persona-app-builder/` */
43167
- constructor(personaRoot) {
43168
- this.personaRoot = personaRoot;
43269
+ /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
43270
+ constructor(root) {
43271
+ this.root = root;
43169
43272
  }
43170
- personaRoot;
43273
+ root;
43171
43274
  /** projects/<name>/.clawd-project.json 路径 */
43172
43275
  metaPath(name) {
43173
43276
  return (0, import_node_path43.join)(this.projectsRoot(), name, META_FILE);
@@ -43177,12 +43280,12 @@ var ProjectStore = class {
43177
43280
  return (0, import_node_path43.join)(this.projectsRoot(), name);
43178
43281
  }
43179
43282
  projectsRoot() {
43180
- return (0, import_node_path43.join)(this.personaRoot, PROJECTS_DIR);
43283
+ return (0, import_node_path43.join)(this.root, PROJECTS_DIR);
43181
43284
  }
43182
43285
  async list() {
43183
43286
  let entries;
43184
43287
  try {
43185
- entries = await import_node_fs30.promises.readdir(this.projectsRoot());
43288
+ entries = await import_node_fs31.promises.readdir(this.projectsRoot());
43186
43289
  } catch (err) {
43187
43290
  if (err.code === "ENOENT") return [];
43188
43291
  throw err;
@@ -43190,7 +43293,7 @@ var ProjectStore = class {
43190
43293
  const out = [];
43191
43294
  for (const name of entries) {
43192
43295
  try {
43193
- const raw = await import_node_fs30.promises.readFile(this.metaPath(name), "utf8");
43296
+ const raw = await import_node_fs31.promises.readFile(this.metaPath(name), "utf8");
43194
43297
  const json = JSON.parse(raw);
43195
43298
  let migrated = false;
43196
43299
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -43201,7 +43304,7 @@ var ProjectStore = class {
43201
43304
  if (parsed.success) {
43202
43305
  out.push(parsed.data);
43203
43306
  if (migrated) {
43204
- void import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
43307
+ void import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
43205
43308
  });
43206
43309
  }
43207
43310
  }
@@ -43210,12 +43313,18 @@ var ProjectStore = class {
43210
43313
  }
43211
43314
  return out.sort((a, b2) => a.name.localeCompare(b2.name));
43212
43315
  }
43213
- async create(name) {
43316
+ /**
43317
+ * @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
43318
+ * dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
43319
+ * 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
43320
+ */
43321
+ async create(name, reservedPorts) {
43214
43322
  const existing = await this.list();
43215
43323
  if (existing.some((p2) => p2.name === name)) {
43216
43324
  throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
43217
43325
  }
43218
43326
  const usedPorts = new Set(existing.map((p2) => p2.port));
43327
+ if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
43219
43328
  let port = -1;
43220
43329
  for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
43221
43330
  if (!usedPorts.has(p2)) {
@@ -43239,8 +43348,8 @@ var ProjectStore = class {
43239
43348
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
43240
43349
  }
43241
43350
  const dir = this.projectDir(name);
43242
- await import_node_fs30.promises.mkdir(dir, { recursive: true });
43243
- await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
43351
+ await import_node_fs31.promises.mkdir(dir, { recursive: true });
43352
+ await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
43244
43353
  return meta;
43245
43354
  }
43246
43355
  /**
@@ -43251,11 +43360,12 @@ var ProjectStore = class {
43251
43360
  * spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
43252
43361
  * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
43253
43362
  */
43254
- async scaffold(name, template = DEFAULT_TEMPLATE) {
43255
- const scriptPath = (0, import_node_path43.join)(this.personaRoot, SCAFFOLD_SCRIPT_REL);
43363
+ async scaffold(name, template = DEFAULT_TEMPLATE, personaRoot = this.root) {
43364
+ const scriptPath = (0, import_node_path43.join)(personaRoot, SCAFFOLD_SCRIPT_REL);
43365
+ const destDir = this.projectDir(name);
43256
43366
  return await new Promise((resolve6, reject) => {
43257
- const child = (0, import_node_child_process9.spawn)("bash", [scriptPath, name, template], {
43258
- cwd: this.personaRoot,
43367
+ const child = (0, import_node_child_process9.spawn)("bash", [scriptPath, name, template, destDir], {
43368
+ cwd: personaRoot,
43259
43369
  env: { ...process.env, PATH: process.env.PATH ?? "" },
43260
43370
  stdio: ["ignore", "pipe", "pipe"]
43261
43371
  });
@@ -43284,7 +43394,7 @@ var ProjectStore = class {
43284
43394
  }
43285
43395
  async delete(name) {
43286
43396
  const dir = this.projectDir(name);
43287
- await import_node_fs30.promises.rm(dir, { recursive: true, force: true });
43397
+ await import_node_fs31.promises.rm(dir, { recursive: true, force: true });
43288
43398
  }
43289
43399
  async updatePort(name, newPort) {
43290
43400
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -43300,7 +43410,7 @@ var ProjectStore = class {
43300
43410
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
43301
43411
  }
43302
43412
  const updated = { ...target, port: newPort };
43303
- await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
43413
+ await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
43304
43414
  return updated;
43305
43415
  }
43306
43416
  /**
@@ -43317,7 +43427,7 @@ var ProjectStore = class {
43317
43427
  if (!validated.success) {
43318
43428
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
43319
43429
  }
43320
- await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43430
+ await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43321
43431
  return validated.data;
43322
43432
  }
43323
43433
  /**
@@ -43338,7 +43448,7 @@ var ProjectStore = class {
43338
43448
  if (!validated.success) {
43339
43449
  throw new Error(`invalid publishJob: ${validated.error.message}`);
43340
43450
  }
43341
- await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43451
+ await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43342
43452
  return validated.data;
43343
43453
  }
43344
43454
  /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
@@ -43353,7 +43463,7 @@ var ProjectStore = class {
43353
43463
  if (!validated.success) {
43354
43464
  throw new Error(`failed to clear publishJob: ${validated.error.message}`);
43355
43465
  }
43356
- await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43466
+ await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43357
43467
  return validated.data;
43358
43468
  }
43359
43469
  };
@@ -43755,22 +43865,24 @@ var METHOD_GRANT_MAP = {
43755
43865
  // 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
43756
43866
  // 不暴露给 guest(远端接入者)。
43757
43867
  "appBuilder:listProjects": ADMIN_ANY,
43868
+ // getProject 走 capability-scoped(区别于其它 appBuilder 的 owner-only ADMIN_ANY):mirror/people
43869
+ // 的本机 owner 在远端是 guest,需能调 getProject 拿它 mirror 的 session 的 project。dispatcher
43870
+ // 放行后由 handler 内 canAccessSession(read) 按 session 可见性授权(防 guest 越权拿别人 project)。
43871
+ "appBuilder:getProject": CAPABILITY_SCOPED,
43758
43872
  "appBuilder:createProject": ADMIN_ANY,
43759
43873
  "appBuilder:deleteProject": ADMIN_ANY,
43760
43874
  "appBuilder:updateProjectPort": ADMIN_ANY,
43761
- // 单栏默认 refactor (spec 2026-06-02):发布上线后 assistant 调写公网 URL。
43762
- "appBuilder:setProdUrl": ADMIN_ANY,
43763
- // 单栏 refactor §5.6.2 stage fix:assistant scaffold + install 完成后调 startDevServer 起进程;
43764
- // 中间用 reportStage 上报阶段(scaffolding / installing / failed)。
43765
- "appBuilder:startDevServer": ADMIN_ANY,
43875
+ // people/mirror 协同写操作(spec 2026-06-04-people-mode-app-builder-write-and-push):以下 5 个写
43876
+ // method 走 CAPABILITY_SCOPED(不再 owner-only ADMIN_ANY),handler 内 ensureProjectWriteAccess
43877
+ // session 可见性(canAccessSession 'send')授权——mirror 的本机 owner 在远端是 guest,能 mirror
43878
+ // session 就能操作它绑定的 project(发布凭证是 persona 共享资产,老板决策"统一 session 授权")。
43879
+ "appBuilder:setProdUrl": CAPABILITY_SCOPED,
43880
+ "appBuilder:startDevServer": CAPABILITY_SCOPED,
43881
+ // reportStage 仍 owner-only:assistant 走 HTTP RPC 上报(不过 WS admin grant),UI 不调它。
43766
43882
  "appBuilder:reportStage": ADMIN_ANY,
43767
- // 显式控制 fix (spec 2026-06-02-app-builder-explicit-dev-server-control):用户点"收起预览"触发停 dev server,
43768
- // 对称 startDevServer。同时砍了 setActiveSession(dev server 不再 follow-the-eyes)。
43769
- "appBuilder:stopDevServer": ADMIN_ANY,
43770
- // 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design):
43771
- // owner-only:PreviewPane「发布上线」按钮直接调 publish;UI [取消] / interrupted 恢复按钮调 dismissPublishJob。
43772
- "appBuilder:publish": ADMIN_ANY,
43773
- "appBuilder:dismissPublishJob": ADMIN_ANY
43883
+ "appBuilder:stopDevServer": CAPABILITY_SCOPED,
43884
+ "appBuilder:publish": CAPABILITY_SCOPED,
43885
+ "appBuilder:dismissPublishJob": CAPABILITY_SCOPED
43774
43886
  };
43775
43887
  function computeGrantForFrame(method, frame) {
43776
43888
  const rule = METHOD_GRANT_MAP[method];
@@ -44279,6 +44391,7 @@ async function startDaemon(config) {
44279
44391
  const history = new ClaudeHistoryReader();
44280
44392
  let transport = null;
44281
44393
  const personaStore = new PersonaStore(import_node_path47.default.join(config.dataDir, "personas"));
44394
+ const usersRoot = import_node_path47.default.join(config.dataDir, "users");
44282
44395
  const defaultsRoot = findDefaultsRoot();
44283
44396
  if (defaultsRoot) {
44284
44397
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -44297,6 +44410,7 @@ async function startDaemon(config) {
44297
44410
  historyReader: history,
44298
44411
  dataDir: config.dataDir,
44299
44412
  personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
44413
+ usersRoot,
44300
44414
  personaStore,
44301
44415
  ownerDisplayName,
44302
44416
  ownerPrincipalId,
@@ -44328,7 +44442,7 @@ async function startDaemon(config) {
44328
44442
  const absPath = import_node_path47.default.isAbsolute(input.relPath) ? input.relPath : import_node_path47.default.join(input.cwd, input.relPath);
44329
44443
  let size = 0;
44330
44444
  try {
44331
- size = import_node_fs31.default.statSync(absPath).size;
44445
+ size = import_node_fs32.default.statSync(absPath).size;
44332
44446
  } catch (err) {
44333
44447
  logger.warn("attachment.onFileEdit stat failed", {
44334
44448
  sessionId: input.sessionId,
@@ -44457,9 +44571,8 @@ async function startDaemon(config) {
44457
44571
  ttcAuthBase: TTC_HOSTS.auth,
44458
44572
  getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
44459
44573
  });
44460
- const appBuilderStore = new ProjectStore(
44461
- import_node_path47.default.join(config.dataDir, "personas/persona-app-builder")
44462
- );
44574
+ const getProjectStore = (userId) => new ProjectStore(deriveUserWorkDir(userId, usersRoot));
44575
+ const appBuilderStore = getProjectStore(ownerPrincipalId);
44463
44576
  recoverInterruptedPublishJobs(appBuilderStore, logger).catch((err) => {
44464
44577
  logger.warn("app-builder.publish.recover-startup-failed", {
44465
44578
  error: err instanceof Error ? err.message : String(err)
@@ -44510,11 +44623,14 @@ async function startDaemon(config) {
44510
44623
  // group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
44511
44624
  // 'persona/<pid>/owner',default 走 'default'。
44512
44625
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
44513
- // guest path guard:candidate 必须在 personaRoot 子树下
44514
- personaRoot: import_node_path47.default.join(config.dataDir, "personas")
44626
+ // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
44627
+ personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
44628
+ usersRoot
44515
44629
  },
44516
44630
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
44517
44631
  personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
44632
+ // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
44633
+ usersRoot,
44518
44634
  // capability:list / delete handler 依赖
44519
44635
  capabilityManager,
44520
44636
  // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
@@ -44582,10 +44698,22 @@ async function startDaemon(config) {
44582
44698
  },
44583
44699
  // app-builder
44584
44700
  appBuilderStore,
44701
+ getProjectStore,
44585
44702
  devServerLifecycle: devServerSupervisor,
44586
44703
  // 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
44587
44704
  // 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
44588
- broadcastProjectUpdated: (event) => wsServer?.broadcastToOwners(event),
44705
+ // people/mirror(spec 2026-06-04-people-mode-app-builder-write-and-push):额外推给订阅该 project
44706
+ // 绑定 session 的 client(mirror guest 经 session:observe 订阅),让远端协同者也看到 stage / 发布进度。
44707
+ // 同一帧 owner 也走 project-updated 复用给 publish-progress/failed(app-builder.ts 内 broadcast 闭包):
44708
+ // name 在 event.project.name(project-updated)或 event.name(publish 帧)。owner 双栏会同时命中
44709
+ // owner-broadcast + session-broadcast,但 UI patch 幂等(同 project 覆盖)无害。
44710
+ broadcastProjectUpdated: (event) => {
44711
+ const ev = event;
44712
+ wsServer?.broadcastToOwners(ev);
44713
+ const projectName = event.project?.name ?? event.name;
44714
+ const boundSid = projectName ? manager.findOwnedSessionByAppBuilderProject(projectName)?.sessionId : void 0;
44715
+ if (boundSid) wsServer?.broadcastToSession(boundSid, ev);
44716
+ },
44589
44717
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
44590
44718
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
44591
44719
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
@@ -44852,7 +44980,7 @@ ${bar}
44852
44980
  `);
44853
44981
  try {
44854
44982
  const connectPath = import_node_path47.default.join(config.dataDir, "connect.txt");
44855
- import_node_fs31.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
44983
+ import_node_fs32.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
44856
44984
  } catch {
44857
44985
  }
44858
44986
  } catch (err) {
@@ -44925,7 +45053,7 @@ ${bar}
44925
45053
  function migrateDropPersonsDir(dataDir) {
44926
45054
  const dir = import_node_path47.default.join(dataDir, "persons");
44927
45055
  try {
44928
- import_node_fs31.default.rmSync(dir, { recursive: true, force: true });
45056
+ import_node_fs32.default.rmSync(dir, { recursive: true, force: true });
44929
45057
  } catch {
44930
45058
  }
44931
45059
  }