@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 =
|
|
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
|
|
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)
|
|
7143
|
-
const fd =
|
|
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
|
-
|
|
7161
|
+
fs50.mkdir(path60.dirname(file), { recursive: true }, (err) => {
|
|
7151
7162
|
if (err) return fileOpened(err);
|
|
7152
|
-
|
|
7163
|
+
fs50.open(file, flags, mode, fileOpened);
|
|
7153
7164
|
});
|
|
7154
7165
|
} else {
|
|
7155
|
-
|
|
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 = () =>
|
|
7197
|
-
fsWrite = () =>
|
|
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
|
|
7217
|
+
return fs50.writeSync(this.fd, this._writingBuf);
|
|
7207
7218
|
}
|
|
7208
|
-
return
|
|
7219
|
+
return fs50.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7209
7220
|
};
|
|
7210
7221
|
fsWrite = () => {
|
|
7211
7222
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
7212
|
-
return
|
|
7223
|
+
return fs50.write(this.fd, this._writingBuf, this.release);
|
|
7213
7224
|
}
|
|
7214
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ?
|
|
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
|
-
|
|
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 =
|
|
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) ?
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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() +
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
30749
|
-
stream =
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
31416
|
+
return path6.join(this.dataDir, "sessions");
|
|
31407
31417
|
}
|
|
31408
31418
|
vmOwnerRoot(personaId) {
|
|
31409
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
34316
|
+
var path10 = __toESM(require("path"), 1);
|
|
34298
34317
|
init_protocol();
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
|
|
34304
|
-
|
|
34305
|
-
|
|
34306
|
-
|
|
34307
|
-
|
|
34308
|
-
|
|
34309
|
-
|
|
34310
|
-
|
|
34311
|
-
|
|
34312
|
-
|
|
34313
|
-
|
|
34314
|
-
|
|
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
|
|
34341
|
+
return path10.join(this.root, safeFileName(personaId));
|
|
34327
34342
|
}
|
|
34328
34343
|
metaPath(personaId) {
|
|
34329
|
-
return
|
|
34344
|
+
return path10.join(this.personaDir(personaId), ".clawd", "persona.json");
|
|
34330
34345
|
}
|
|
34331
34346
|
claudeMdPath(personaId) {
|
|
34332
|
-
return
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
34918
|
-
//
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
|
|
34931
|
+
public: false
|
|
34932
|
+
// 不设 sandboxOverride:owner 不注入 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 =
|
|
34944
|
-
candidates.push(
|
|
34945
|
-
candidates.push(
|
|
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 =
|
|
34950
|
-
candidates.push(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
35009
|
-
const dstPath =
|
|
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 =
|
|
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 =
|
|
35046
|
+
const srcPath = path12.join(bundleDir, relPath);
|
|
35027
35047
|
if (!fs9.existsSync(srcPath)) continue;
|
|
35028
|
-
const dstPath =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
40562
|
-
|
|
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
|
|
40575
|
-
if (!target.startsWith(root +
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
42661
|
-
|
|
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
|
-
|
|
42664
|
-
|
|
42665
|
-
|
|
42666
|
-
|
|
42667
|
-
|
|
42668
|
-
|
|
42669
|
-
|
|
42670
|
-
|
|
42671
|
-
|
|
42672
|
-
|
|
42673
|
-
|
|
42674
|
-
|
|
42675
|
-
|
|
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
|
|
42777
|
+
if (!APP_BUILDER_PERSONAS.includes(session.ownerPersonaId ?? "")) {
|
|
42694
42778
|
throw new ClawdError(
|
|
42695
42779
|
ERROR_CODES.WRONG_PERSONA,
|
|
42696
|
-
`createProject only allowed in
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
43167
|
-
constructor(
|
|
43168
|
-
this.
|
|
43269
|
+
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
|
|
43270
|
+
constructor(root) {
|
|
43271
|
+
this.root = root;
|
|
43169
43272
|
}
|
|
43170
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
43243
|
-
await
|
|
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)(
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
43762
|
-
|
|
43763
|
-
//
|
|
43764
|
-
//
|
|
43765
|
-
"appBuilder:
|
|
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
|
-
|
|
43768
|
-
|
|
43769
|
-
"appBuilder:
|
|
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 =
|
|
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
|
|
44461
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45056
|
+
import_node_fs32.default.rmSync(dir, { recursive: true, force: true });
|
|
44929
45057
|
} catch {
|
|
44930
45058
|
}
|
|
44931
45059
|
}
|