@clawos-dev/clawd 0.2.127 → 0.2.128-beta.255.69495b2
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,9 @@ var DEFAULT_PERSONAS = [
|
|
|
34913
34928
|
label: "App Builder",
|
|
34914
34929
|
model: "opus",
|
|
34915
34930
|
iconKey: "assist",
|
|
34916
|
-
public: false
|
|
34917
|
-
//
|
|
34918
|
-
//
|
|
34919
|
-
// 影响正常工作流;老板明确说 app-builder 不要 sandbox。
|
|
34920
|
-
sandboxOverride: {
|
|
34921
|
-
permissions: { defaultMode: "dontAsk" },
|
|
34922
|
-
sandbox: { enabled: false }
|
|
34923
|
-
}
|
|
34931
|
+
public: false
|
|
34932
|
+
// 不设 sandboxOverride:owner 不注入 sandbox(天然不沙箱,部署/pnpm/curl 都 freedom),
|
|
34933
|
+
// guest 走 sandbox-settings.json 的 guest 沙箱配置(store.write 默认生成)。
|
|
34924
34934
|
},
|
|
34925
34935
|
{
|
|
34926
34936
|
personaId: "persona-developer",
|
|
@@ -34940,14 +34950,14 @@ var DEFAULT_PERSONAS = [
|
|
|
34940
34950
|
function findDefaultsRoot() {
|
|
34941
34951
|
const candidates = [];
|
|
34942
34952
|
try {
|
|
34943
|
-
const here =
|
|
34944
|
-
candidates.push(
|
|
34945
|
-
candidates.push(
|
|
34953
|
+
const here = path12.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
34954
|
+
candidates.push(path12.resolve(here, "defaults"));
|
|
34955
|
+
candidates.push(path12.resolve(here, "persona-defaults"));
|
|
34946
34956
|
} catch {
|
|
34947
34957
|
}
|
|
34948
34958
|
if (process.argv[1]) {
|
|
34949
|
-
const argvDir =
|
|
34950
|
-
candidates.push(
|
|
34959
|
+
const argvDir = path12.dirname(process.argv[1]);
|
|
34960
|
+
candidates.push(path12.resolve(argvDir, "persona-defaults"));
|
|
34951
34961
|
}
|
|
34952
34962
|
for (const c of candidates) {
|
|
34953
34963
|
try {
|
|
@@ -34964,7 +34974,7 @@ function seedDefaultPersonas(args) {
|
|
|
34964
34974
|
args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
|
|
34965
34975
|
continue;
|
|
34966
34976
|
}
|
|
34967
|
-
const bundleDir =
|
|
34977
|
+
const bundleDir = path12.join(args.defaultsRoot, entry.personaId);
|
|
34968
34978
|
if (!fs9.existsSync(bundleDir)) {
|
|
34969
34979
|
args.logger.warn("persona.seed.skip", {
|
|
34970
34980
|
personaId: entry.personaId,
|
|
@@ -34973,7 +34983,7 @@ function seedDefaultPersonas(args) {
|
|
|
34973
34983
|
});
|
|
34974
34984
|
continue;
|
|
34975
34985
|
}
|
|
34976
|
-
const claudeMdPath =
|
|
34986
|
+
const claudeMdPath = path12.join(bundleDir, "CLAUDE.md");
|
|
34977
34987
|
if (!fs9.existsSync(claudeMdPath)) {
|
|
34978
34988
|
args.logger.warn("persona.seed.skip", {
|
|
34979
34989
|
personaId: entry.personaId,
|
|
@@ -35005,8 +35015,8 @@ function seedDefaultPersonas(args) {
|
|
|
35005
35015
|
function copyBundleExtras(srcDir, dstDir) {
|
|
35006
35016
|
for (const entry of fs9.readdirSync(srcDir, { withFileTypes: true })) {
|
|
35007
35017
|
if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
|
|
35008
|
-
const srcPath =
|
|
35009
|
-
const dstPath =
|
|
35018
|
+
const srcPath = path12.join(srcDir, entry.name);
|
|
35019
|
+
const dstPath = path12.join(dstDir, entry.name);
|
|
35010
35020
|
if (entry.isDirectory()) {
|
|
35011
35021
|
fs9.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
|
|
35012
35022
|
} else if (entry.isFile()) {
|
|
@@ -35018,14 +35028,14 @@ var DAEMON_MANAGED_PATHS = ["extension-kit", "CLAUDE.md", ".mcp.json"];
|
|
|
35018
35028
|
function refreshDaemonManagedDirs(args) {
|
|
35019
35029
|
const entries = args.entries ?? DEFAULT_PERSONAS;
|
|
35020
35030
|
for (const entry of entries) {
|
|
35021
|
-
const bundleDir =
|
|
35031
|
+
const bundleDir = path12.join(args.defaultsRoot, entry.personaId);
|
|
35022
35032
|
if (!fs9.existsSync(bundleDir)) continue;
|
|
35023
35033
|
const personaDir = args.store.personaDirPath(entry.personaId);
|
|
35024
35034
|
if (!fs9.existsSync(personaDir)) continue;
|
|
35025
35035
|
for (const relPath of DAEMON_MANAGED_PATHS) {
|
|
35026
|
-
const srcPath =
|
|
35036
|
+
const srcPath = path12.join(bundleDir, relPath);
|
|
35027
35037
|
if (!fs9.existsSync(srcPath)) continue;
|
|
35028
|
-
const dstPath =
|
|
35038
|
+
const dstPath = path12.join(personaDir, relPath);
|
|
35029
35039
|
try {
|
|
35030
35040
|
fs9.cpSync(srcPath, dstPath, { recursive: true, force: true, dereference: true });
|
|
35031
35041
|
args.logger.info("persona.refresh.synced", {
|
|
@@ -40259,7 +40269,8 @@ function buildSessionHandlers(deps) {
|
|
|
40259
40269
|
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
40260
40270
|
const sessionFile = manager.findOwnedSession(args.sessionId);
|
|
40261
40271
|
const projectName = sessionFile?.appBuilderProject ?? null;
|
|
40262
|
-
|
|
40272
|
+
const projectStore = (sessionFile?.creatorPrincipalId ? deps.getProjectStore?.(sessionFile.creatorPrincipalId) : void 0) ?? deps.appBuilderStore;
|
|
40273
|
+
if (projectName && deps.devServerLifecycle && projectStore) {
|
|
40263
40274
|
try {
|
|
40264
40275
|
await deps.devServerLifecycle.stopForProject(projectName);
|
|
40265
40276
|
} catch (err) {
|
|
@@ -40270,7 +40281,7 @@ function buildSessionHandlers(deps) {
|
|
|
40270
40281
|
});
|
|
40271
40282
|
}
|
|
40272
40283
|
try {
|
|
40273
|
-
await
|
|
40284
|
+
await projectStore.delete(projectName);
|
|
40274
40285
|
} catch (err) {
|
|
40275
40286
|
deps.logger?.warn("session-delete.delete-project-failed", {
|
|
40276
40287
|
sessionId: args.sessionId,
|
|
@@ -40555,11 +40566,16 @@ function getAllowedPersonaIds(grants, action) {
|
|
|
40555
40566
|
}
|
|
40556
40567
|
return ids;
|
|
40557
40568
|
}
|
|
40558
|
-
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
|
|
40559
|
-
const root = path41.resolve(personaRoot);
|
|
40569
|
+
function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userWorkDir) {
|
|
40560
40570
|
const target = path41.resolve(absPath);
|
|
40561
|
-
|
|
40562
|
-
|
|
40571
|
+
if (userWorkDir) {
|
|
40572
|
+
const u = path41.resolve(userWorkDir);
|
|
40573
|
+
const usep = u.endsWith(path41.sep) ? "" : path41.sep;
|
|
40574
|
+
if (target === u || target.startsWith(u + usep)) return true;
|
|
40575
|
+
}
|
|
40576
|
+
const root = path41.resolve(personaRoot);
|
|
40577
|
+
const sep2 = root.endsWith(path41.sep) ? "" : path41.sep;
|
|
40578
|
+
if (!target.startsWith(root + sep2)) return false;
|
|
40563
40579
|
const rel = path41.relative(root, target);
|
|
40564
40580
|
if (!rel || rel.startsWith("..")) return false;
|
|
40565
40581
|
const personaId = rel.split(path41.sep)[0];
|
|
@@ -40571,8 +40587,8 @@ function isGuestPathAllowed(grants, absPath, personaRoot, action = "read") {
|
|
|
40571
40587
|
function personaIdFromPath(absPath, personaRoot) {
|
|
40572
40588
|
const root = path41.resolve(personaRoot);
|
|
40573
40589
|
const target = path41.resolve(absPath);
|
|
40574
|
-
const
|
|
40575
|
-
if (!target.startsWith(root +
|
|
40590
|
+
const sep2 = root.endsWith(path41.sep) ? "" : path41.sep;
|
|
40591
|
+
if (!target.startsWith(root + sep2)) return null;
|
|
40576
40592
|
const rel = path41.relative(root, target);
|
|
40577
40593
|
if (!rel || rel.startsWith("..")) return null;
|
|
40578
40594
|
const id = rel.split(path41.sep)[0];
|
|
@@ -40581,9 +40597,10 @@ function personaIdFromPath(absPath, personaRoot) {
|
|
|
40581
40597
|
|
|
40582
40598
|
// src/handlers/history.ts
|
|
40583
40599
|
init_protocol();
|
|
40584
|
-
function assertGuestPath(ctx, absPath, personaRoot, method) {
|
|
40600
|
+
function assertGuestPath(ctx, absPath, personaRoot, method, usersRoot) {
|
|
40585
40601
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40586
|
-
|
|
40602
|
+
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
40603
|
+
if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read", userWorkDir)) {
|
|
40587
40604
|
throw new ClawdError(
|
|
40588
40605
|
ERROR_CODES.UNAUTHORIZED,
|
|
40589
40606
|
`guest ${ctx.principal.id} cannot ${method} path ${absPath}`
|
|
@@ -40591,10 +40608,11 @@ function assertGuestPath(ctx, absPath, personaRoot, method) {
|
|
|
40591
40608
|
}
|
|
40592
40609
|
}
|
|
40593
40610
|
function buildHistoryHandlers(deps) {
|
|
40594
|
-
const { manager, history, store, personaRoot } = deps;
|
|
40611
|
+
const { manager, history, store, personaRoot, usersRoot } = deps;
|
|
40595
40612
|
const projects = async (_frame, _client, ctx) => {
|
|
40596
40613
|
const list2 = await history.listProjects();
|
|
40597
40614
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40615
|
+
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
40598
40616
|
const filtered = list2.filter((p2) => {
|
|
40599
40617
|
const pid = personaIdFromPath(p2.projectPath, personaRoot);
|
|
40600
40618
|
if (!pid) return false;
|
|
@@ -40602,7 +40620,8 @@ function buildHistoryHandlers(deps) {
|
|
|
40602
40620
|
ctx.grants,
|
|
40603
40621
|
path42.join(personaRoot, pid),
|
|
40604
40622
|
personaRoot,
|
|
40605
|
-
"read"
|
|
40623
|
+
"read",
|
|
40624
|
+
userWorkDir
|
|
40606
40625
|
);
|
|
40607
40626
|
});
|
|
40608
40627
|
return { response: { type: "history:projects", projects: filtered } };
|
|
@@ -40643,21 +40662,22 @@ function buildHistoryHandlers(deps) {
|
|
|
40643
40662
|
};
|
|
40644
40663
|
const subagents = async (frame, _client, ctx) => {
|
|
40645
40664
|
const args = HistorySubagentsArgs.parse(frame);
|
|
40646
|
-
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents");
|
|
40665
|
+
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagents", usersRoot);
|
|
40647
40666
|
const subs = await history.listSubagents(args);
|
|
40648
40667
|
return { response: { type: "history:subagents", subagents: subs } };
|
|
40649
40668
|
};
|
|
40650
40669
|
const subagentRead = async (frame, _client, ctx) => {
|
|
40651
40670
|
const args = HistorySubagentReadArgs.parse(frame);
|
|
40652
|
-
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read");
|
|
40671
|
+
assertGuestPath(ctx, path42.resolve(args.cwd), personaRoot, "history:subagent-read", usersRoot);
|
|
40653
40672
|
const res = await history.readSubagent(args);
|
|
40654
40673
|
return { response: { type: "history:subagent-read", ...res } };
|
|
40655
40674
|
};
|
|
40656
40675
|
const recentDirs = async (_frame, _client, ctx) => {
|
|
40657
40676
|
const dirs = listRecentDirs(store);
|
|
40658
40677
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40678
|
+
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
40659
40679
|
const filtered = dirs.filter(
|
|
40660
|
-
(d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read")
|
|
40680
|
+
(d) => isGuestPathAllowed(ctx.grants, path42.resolve(d.cwd), personaRoot, "read", userWorkDir)
|
|
40661
40681
|
);
|
|
40662
40682
|
return { response: { type: "history:recentDirs", dirs: filtered } };
|
|
40663
40683
|
}
|
|
@@ -40700,9 +40720,10 @@ function filterGuestAgents(list, enabledPluginNames) {
|
|
|
40700
40720
|
return false;
|
|
40701
40721
|
});
|
|
40702
40722
|
}
|
|
40703
|
-
function assertGuestPath2(ctx, absPath, personaRoot, method) {
|
|
40723
|
+
function assertGuestPath2(ctx, absPath, personaRoot, method, usersRoot) {
|
|
40704
40724
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40705
|
-
|
|
40725
|
+
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
40726
|
+
if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read", userWorkDir)) {
|
|
40706
40727
|
throw new ClawdError(
|
|
40707
40728
|
ERROR_CODES.UNAUTHORIZED,
|
|
40708
40729
|
`guest ${ctx.principal.id} cannot ${method} path ${absPath}`
|
|
@@ -40710,28 +40731,28 @@ function assertGuestPath2(ctx, absPath, personaRoot, method) {
|
|
|
40710
40731
|
}
|
|
40711
40732
|
}
|
|
40712
40733
|
function buildWorkspaceHandlers(deps) {
|
|
40713
|
-
const { workspace, skills, agents, personaRoot, personaManager } = deps;
|
|
40734
|
+
const { workspace, skills, agents, personaRoot, personaManager, usersRoot } = deps;
|
|
40714
40735
|
const list = async (frame, _client, ctx) => {
|
|
40715
40736
|
const args = WorkspaceListArgs.parse(frame);
|
|
40716
40737
|
const isGuest = ctx?.principal.kind === "guest";
|
|
40717
40738
|
const fallbackCwd = isGuest && personaRoot ? personaRoot : os15.homedir();
|
|
40718
40739
|
const resolvedCwd = path43.resolve(args.cwd ?? fallbackCwd);
|
|
40719
40740
|
const target = args.path ? path43.resolve(resolvedCwd, args.path) : resolvedCwd;
|
|
40720
|
-
assertGuestPath2(ctx, target, personaRoot, "workspace:list");
|
|
40741
|
+
assertGuestPath2(ctx, target, personaRoot, "workspace:list", usersRoot);
|
|
40721
40742
|
const res = workspace.list({ ...args, cwd: resolvedCwd });
|
|
40722
40743
|
return { response: { type: "workspace:list", ...res } };
|
|
40723
40744
|
};
|
|
40724
40745
|
const read = async (frame, _client, ctx) => {
|
|
40725
40746
|
const args = WorkspaceReadArgs.parse(frame);
|
|
40726
40747
|
const target = path43.isAbsolute(args.path) ? path43.resolve(args.path) : path43.resolve(args.cwd, args.path);
|
|
40727
|
-
assertGuestPath2(ctx, target, personaRoot, "workspace:read");
|
|
40748
|
+
assertGuestPath2(ctx, target, personaRoot, "workspace:read", usersRoot);
|
|
40728
40749
|
const res = workspace.read(args);
|
|
40729
40750
|
return { response: { type: "workspace:read", ...res } };
|
|
40730
40751
|
};
|
|
40731
40752
|
const skillsList = async (frame, _client, ctx) => {
|
|
40732
40753
|
const args = SkillsListArgs.parse(frame);
|
|
40733
40754
|
const cwdAbs = path43.resolve(args.cwd);
|
|
40734
|
-
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list");
|
|
40755
|
+
assertGuestPath2(ctx, cwdAbs, personaRoot, "skills:list", usersRoot);
|
|
40735
40756
|
const list2 = skills.list(args);
|
|
40736
40757
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40737
40758
|
const personaId = personaIdFromPath(cwdAbs, personaRoot);
|
|
@@ -40743,7 +40764,7 @@ function buildWorkspaceHandlers(deps) {
|
|
|
40743
40764
|
const agentsList = async (frame, _client, ctx) => {
|
|
40744
40765
|
const args = AgentsListArgs.parse(frame);
|
|
40745
40766
|
const cwdAbs = path43.resolve(args.cwd);
|
|
40746
|
-
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list");
|
|
40767
|
+
assertGuestPath2(ctx, cwdAbs, personaRoot, "agents:list", usersRoot);
|
|
40747
40768
|
const list2 = agents.list(args);
|
|
40748
40769
|
if (ctx?.principal.kind === "guest" && personaRoot) {
|
|
40749
40770
|
const personaId = personaIdFromPath(cwdAbs, personaRoot);
|
|
@@ -40845,9 +40866,10 @@ async function listGitBranches(cwd) {
|
|
|
40845
40866
|
}
|
|
40846
40867
|
|
|
40847
40868
|
// src/handlers/git.ts
|
|
40848
|
-
function assertGuestCwd(ctx, cwd, personaRoot, method) {
|
|
40869
|
+
function assertGuestCwd(ctx, cwd, personaRoot, method, usersRoot) {
|
|
40849
40870
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
40850
|
-
|
|
40871
|
+
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
40872
|
+
if (!isGuestPathAllowed(ctx.grants, path45.resolve(cwd), personaRoot, "read", userWorkDir)) {
|
|
40851
40873
|
throw new ClawdError(
|
|
40852
40874
|
ERROR_CODES.UNAUTHORIZED,
|
|
40853
40875
|
`guest ${ctx.principal.id} cannot ${method} cwd ${cwd}`
|
|
@@ -40855,22 +40877,22 @@ function assertGuestCwd(ctx, cwd, personaRoot, method) {
|
|
|
40855
40877
|
}
|
|
40856
40878
|
}
|
|
40857
40879
|
function buildGitHandlers(deps) {
|
|
40858
|
-
const { personaRoot } = deps;
|
|
40880
|
+
const { personaRoot, usersRoot } = deps;
|
|
40859
40881
|
const root = async (frame, _client, ctx) => {
|
|
40860
40882
|
const args = GitRootArgs.parse(frame);
|
|
40861
|
-
assertGuestCwd(ctx, args.cwd, personaRoot, "git:root");
|
|
40883
|
+
assertGuestCwd(ctx, args.cwd, personaRoot, "git:root", usersRoot);
|
|
40862
40884
|
const res = await getGitRoot(args.cwd);
|
|
40863
40885
|
return { response: { type: "git:root", ...res } };
|
|
40864
40886
|
};
|
|
40865
40887
|
const branch = async (frame, _client, ctx) => {
|
|
40866
40888
|
const args = GitBranchArgs.parse(frame);
|
|
40867
|
-
assertGuestCwd(ctx, args.cwd, personaRoot, "git:branch");
|
|
40889
|
+
assertGuestCwd(ctx, args.cwd, personaRoot, "git:branch", usersRoot);
|
|
40868
40890
|
const res = await readGitBranch(args.cwd);
|
|
40869
40891
|
return { response: { type: "git:branch", ...res } };
|
|
40870
40892
|
};
|
|
40871
40893
|
const branches = async (frame, _client, ctx) => {
|
|
40872
40894
|
const args = GitBranchesArgs.parse(frame);
|
|
40873
|
-
assertGuestCwd(ctx, args.cwd, personaRoot, "git:branches");
|
|
40895
|
+
assertGuestCwd(ctx, args.cwd, personaRoot, "git:branches", usersRoot);
|
|
40874
40896
|
const res = await listGitBranches(args.cwd);
|
|
40875
40897
|
return { response: { type: "git:branches", ...res } };
|
|
40876
40898
|
};
|
|
@@ -41364,9 +41386,10 @@ var import_node_path33 = __toESM(require("path"), 1);
|
|
|
41364
41386
|
init_protocol();
|
|
41365
41387
|
init_protocol();
|
|
41366
41388
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
41367
|
-
function assertGuestAttachmentPath(ctx, absPath, personaRoot, method) {
|
|
41389
|
+
function assertGuestAttachmentPath(ctx, absPath, personaRoot, method, usersRoot) {
|
|
41368
41390
|
if (!ctx || ctx.principal.kind !== "guest" || !personaRoot) return;
|
|
41369
|
-
|
|
41391
|
+
const userWorkDir = usersRoot ? deriveUserWorkDir(ctx.principal.id, usersRoot) : void 0;
|
|
41392
|
+
if (!isGuestPathAllowed(ctx.grants, absPath, personaRoot, "read", userWorkDir)) {
|
|
41370
41393
|
throw new ClawdError(
|
|
41371
41394
|
ERROR_CODES.UNAUTHORIZED,
|
|
41372
41395
|
`guest ${ctx.principal.id} cannot ${method} path ${absPath}`
|
|
@@ -41416,7 +41439,7 @@ function buildAttachmentHandlers(deps) {
|
|
|
41416
41439
|
}
|
|
41417
41440
|
const cwdAbs = import_node_path33.default.resolve(sessionFile.cwd);
|
|
41418
41441
|
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");
|
|
41442
|
+
assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl", deps.usersRoot);
|
|
41420
41443
|
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
41421
41444
|
const entry = entries.find((e) => {
|
|
41422
41445
|
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 +41466,7 @@ function buildAttachmentHandlers(deps) {
|
|
|
41443
41466
|
if (!ctx || ctx.principal.kind !== "guest" || !deps.personaRoot || !deps.sessionStore) return;
|
|
41444
41467
|
const f = deps.sessionStore.read(sessionId);
|
|
41445
41468
|
if (!f) return;
|
|
41446
|
-
assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method);
|
|
41469
|
+
assertGuestAttachmentPath(ctx, import_node_path33.default.resolve(f.cwd), deps.personaRoot, method, deps.usersRoot);
|
|
41447
41470
|
}
|
|
41448
41471
|
const groupAdd = async (frame, _client, ctx) => {
|
|
41449
41472
|
if (!deps.groupFileStore || !deps.getSessionScope) {
|
|
@@ -42603,6 +42626,8 @@ async function recoverInterruptedJobs(deps) {
|
|
|
42603
42626
|
// src/handlers/app-builder.ts
|
|
42604
42627
|
init_protocol();
|
|
42605
42628
|
var import_node_path40 = require("path");
|
|
42629
|
+
var import_node_fs30 = require("fs");
|
|
42630
|
+
var APP_BUILDER_PERSONAS = ["persona-app-builder"];
|
|
42606
42631
|
var PUBLISH_SCRIPT_REL = "extension-kit/scripts/publish.sh";
|
|
42607
42632
|
var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
|
|
42608
42633
|
async function recoverInterruptedPublishJobs(store, logger) {
|
|
@@ -42613,6 +42638,29 @@ async function recoverInterruptedPublishJobs(store, logger) {
|
|
|
42613
42638
|
}
|
|
42614
42639
|
function buildAppBuilderHandlers(deps) {
|
|
42615
42640
|
const { store, devServerLifecycle, sessionManager, getTunnelUrl, broadcastProjectUpdated } = deps;
|
|
42641
|
+
function resolveStore(opts) {
|
|
42642
|
+
if (!deps.getStore) return store;
|
|
42643
|
+
if (opts.sessionId) {
|
|
42644
|
+
const s = sessionManager.findOwnedSession(opts.sessionId);
|
|
42645
|
+
if (s?.creatorPrincipalId) return deps.getStore(s.creatorPrincipalId);
|
|
42646
|
+
}
|
|
42647
|
+
if (opts.projectName) {
|
|
42648
|
+
const s = sessionManager.findOwnedSessionByAppBuilderProject(opts.projectName);
|
|
42649
|
+
if (s?.creatorPrincipalId) return deps.getStore(s.creatorPrincipalId);
|
|
42650
|
+
}
|
|
42651
|
+
if (opts.ctx) return deps.getStore(opts.ctx.principal.id);
|
|
42652
|
+
return store;
|
|
42653
|
+
}
|
|
42654
|
+
function ensureProjectWriteAccess(ctx, opts) {
|
|
42655
|
+
if (!ctx) return;
|
|
42656
|
+
const sessionId = opts.sessionId ?? (opts.projectName ? sessionManager.findOwnedSessionByAppBuilderProject(opts.projectName)?.sessionId : void 0);
|
|
42657
|
+
if (!sessionId) return;
|
|
42658
|
+
if (!canAccessSession(ctx, sessionId, "send", {
|
|
42659
|
+
readSession: (sid) => sessionManager.findOwnedSession(sid) ?? null
|
|
42660
|
+
})) {
|
|
42661
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `cannot write project of session ${sessionId}`);
|
|
42662
|
+
}
|
|
42663
|
+
}
|
|
42616
42664
|
const stages = /* @__PURE__ */ new Map();
|
|
42617
42665
|
const publishRegistry = new PublishJobRegistry();
|
|
42618
42666
|
function withPublishJobStatus(meta) {
|
|
@@ -42629,13 +42677,13 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42629
42677
|
if (recorded) return recorded;
|
|
42630
42678
|
return devServerLifecycle.isRunning(name) ? { stage: "running" } : { stage: "stopped" };
|
|
42631
42679
|
}
|
|
42632
|
-
async function setStageAndBroadcast(name, stage, reason) {
|
|
42680
|
+
async function setStageAndBroadcast(name, stage, reason, s = store) {
|
|
42633
42681
|
if (reason !== void 0) {
|
|
42634
42682
|
stages.set(name, { stage, reason });
|
|
42635
42683
|
} else {
|
|
42636
42684
|
stages.set(name, { stage });
|
|
42637
42685
|
}
|
|
42638
|
-
const projects = await
|
|
42686
|
+
const projects = await s.list();
|
|
42639
42687
|
const meta = projects.find((p2) => p2.name === name);
|
|
42640
42688
|
if (!meta) {
|
|
42641
42689
|
deps.logger?.warn("app-builder.stage-broadcast.project-missing", { name, stage });
|
|
@@ -42657,28 +42705,54 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42657
42705
|
...reason !== void 0 ? { stageReason: reason } : {}
|
|
42658
42706
|
});
|
|
42659
42707
|
}
|
|
42660
|
-
|
|
42661
|
-
|
|
42708
|
+
async function listAllUsersProjects() {
|
|
42709
|
+
if (!deps.usersRoot || !deps.getStore) return [];
|
|
42710
|
+
const getStore = deps.getStore;
|
|
42711
|
+
const userIds = await import_node_fs30.promises.readdir(deps.usersRoot).catch(() => []);
|
|
42712
|
+
const perUser = await Promise.all(
|
|
42713
|
+
userIds.map((uid) => getStore(uid).list().catch(() => []))
|
|
42714
|
+
);
|
|
42715
|
+
return perUser.flat();
|
|
42716
|
+
}
|
|
42717
|
+
function toProjectWithStatus(p2) {
|
|
42718
|
+
const s = getStageOf(p2.name);
|
|
42662
42719
|
return {
|
|
42663
|
-
|
|
42664
|
-
|
|
42665
|
-
|
|
42666
|
-
|
|
42667
|
-
|
|
42668
|
-
|
|
42669
|
-
|
|
42670
|
-
|
|
42671
|
-
|
|
42672
|
-
|
|
42673
|
-
|
|
42674
|
-
|
|
42675
|
-
|
|
42676
|
-
|
|
42677
|
-
|
|
42678
|
-
|
|
42679
|
-
};
|
|
42720
|
+
...p2,
|
|
42721
|
+
isRunning: devServerLifecycle.isRunning(p2.name),
|
|
42722
|
+
boundSessionId: devServerLifecycle.boundSessionId(p2.name),
|
|
42723
|
+
stage: s.stage,
|
|
42724
|
+
...s.reason !== void 0 ? { stageReason: s.reason } : {},
|
|
42725
|
+
publishJob: withPublishJobStatus(p2)
|
|
42726
|
+
};
|
|
42727
|
+
}
|
|
42728
|
+
const listProjects = async (_frame, _client, ctx) => {
|
|
42729
|
+
let metas;
|
|
42730
|
+
if (ctx?.principal.kind === "owner" && deps.usersRoot && deps.getStore) {
|
|
42731
|
+
metas = await listAllUsersProjects();
|
|
42732
|
+
} else {
|
|
42733
|
+
metas = await resolveStore({ ctx }).list();
|
|
42734
|
+
}
|
|
42735
|
+
return { response: { projects: metas.map(toProjectWithStatus) } };
|
|
42680
42736
|
};
|
|
42681
|
-
const
|
|
42737
|
+
const getProject = async (frame, _client, ctx) => {
|
|
42738
|
+
const f = frame;
|
|
42739
|
+
if (typeof f.sessionId !== "string" || !f.sessionId) {
|
|
42740
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "getProject: sessionId required");
|
|
42741
|
+
}
|
|
42742
|
+
if (ctx && !canAccessSession(ctx, f.sessionId, "read", {
|
|
42743
|
+
readSession: (sid) => sessionManager.findOwnedSession(sid) ?? null
|
|
42744
|
+
})) {
|
|
42745
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `cannot read session ${f.sessionId}`);
|
|
42746
|
+
}
|
|
42747
|
+
const session = sessionManager.findOwnedSession(f.sessionId);
|
|
42748
|
+
const projectName = session?.appBuilderProject ?? null;
|
|
42749
|
+
if (!projectName) return { response: { project: null } };
|
|
42750
|
+
const meta = (await resolveStore({ ctx, sessionId: f.sessionId }).list()).find(
|
|
42751
|
+
(p2) => p2.name === projectName
|
|
42752
|
+
);
|
|
42753
|
+
return { response: { project: meta ? toProjectWithStatus(meta) : null } };
|
|
42754
|
+
};
|
|
42755
|
+
const createProject = async (frame, _client, ctx) => {
|
|
42682
42756
|
const f = frame;
|
|
42683
42757
|
if (typeof f.sessionId !== "string" || !f.sessionId) {
|
|
42684
42758
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "createProject: sessionId required");
|
|
@@ -42690,10 +42764,10 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42690
42764
|
if (!session) {
|
|
42691
42765
|
throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `session ${f.sessionId} not found`);
|
|
42692
42766
|
}
|
|
42693
|
-
if (session.ownerPersonaId
|
|
42767
|
+
if (!APP_BUILDER_PERSONAS.includes(session.ownerPersonaId ?? "")) {
|
|
42694
42768
|
throw new ClawdError(
|
|
42695
42769
|
ERROR_CODES.WRONG_PERSONA,
|
|
42696
|
-
`createProject only allowed in
|
|
42770
|
+
`createProject only allowed in app-builder persona sessions (got: ${session.ownerPersonaId ?? "<none>"})`
|
|
42697
42771
|
);
|
|
42698
42772
|
}
|
|
42699
42773
|
if (session.appBuilderProject) {
|
|
@@ -42702,10 +42776,12 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42702
42776
|
`session ${f.sessionId} already bound to project ${session.appBuilderProject}`
|
|
42703
42777
|
);
|
|
42704
42778
|
}
|
|
42779
|
+
const userStore = resolveStore({ ctx, sessionId: f.sessionId });
|
|
42705
42780
|
deps.logger?.info("app-builder.create-project.start", { name: f.name, sessionId: f.sessionId });
|
|
42706
|
-
const
|
|
42781
|
+
const reservedPorts = new Set((await listAllUsersProjects()).map((p2) => p2.port));
|
|
42782
|
+
const project = await userStore.create(f.name, reservedPorts);
|
|
42707
42783
|
try {
|
|
42708
|
-
const scaffoldResult = await
|
|
42784
|
+
const scaffoldResult = await userStore.scaffold(project.name, void 0, deps.personaRoot);
|
|
42709
42785
|
deps.logger?.info("app-builder.scaffold.done", {
|
|
42710
42786
|
name: project.name,
|
|
42711
42787
|
stdout: scaffoldResult.stdout.slice(0, 200)
|
|
@@ -42713,7 +42789,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42713
42789
|
} catch (err) {
|
|
42714
42790
|
const message = err instanceof Error ? err.message : String(err);
|
|
42715
42791
|
deps.logger?.warn("app-builder.scaffold.failed", { name: project.name, error: message });
|
|
42716
|
-
await
|
|
42792
|
+
await userStore.delete(project.name).catch((delErr) => {
|
|
42717
42793
|
deps.logger?.warn("app-builder.scaffold.rollback-failed", {
|
|
42718
42794
|
name: project.name,
|
|
42719
42795
|
error: delErr instanceof Error ? delErr.message : String(delErr)
|
|
@@ -42725,46 +42801,51 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42725
42801
|
sessionId: f.sessionId,
|
|
42726
42802
|
patch: { appBuilderProject: project.name }
|
|
42727
42803
|
});
|
|
42728
|
-
await setStageAndBroadcast(project.name, "install-pending");
|
|
42804
|
+
await setStageAndBroadcast(project.name, "install-pending", void 0, userStore);
|
|
42729
42805
|
deps.logger?.info("app-builder.create-project.done", {
|
|
42730
42806
|
name: project.name,
|
|
42731
42807
|
port: project.port,
|
|
42732
42808
|
sessionId: f.sessionId
|
|
42733
42809
|
});
|
|
42734
|
-
return { response: { project }, broadcast };
|
|
42810
|
+
return { response: { project, projectDir: userStore.projectDir(project.name) }, broadcast };
|
|
42735
42811
|
};
|
|
42736
|
-
const deleteProject = async (frame) => {
|
|
42812
|
+
const deleteProject = async (frame, _client, ctx) => {
|
|
42737
42813
|
const name = frame.name;
|
|
42738
42814
|
if (typeof name !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "deleteProject: name required");
|
|
42815
|
+
const userStore = resolveStore({ ctx, projectName: name });
|
|
42739
42816
|
await devServerLifecycle.stopForProject(name);
|
|
42740
|
-
await
|
|
42817
|
+
await userStore.delete(name);
|
|
42741
42818
|
stages.delete(name);
|
|
42742
42819
|
return { response: {} };
|
|
42743
42820
|
};
|
|
42744
|
-
const updateProjectPort = async (frame) => {
|
|
42821
|
+
const updateProjectPort = async (frame, _client, ctx) => {
|
|
42745
42822
|
const f = frame;
|
|
42746
42823
|
if (typeof f.name !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "updateProjectPort: name required");
|
|
42747
42824
|
if (typeof f.newPort !== "number") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "updateProjectPort: newPort required");
|
|
42825
|
+
const userStore = resolveStore({ ctx, projectName: f.name });
|
|
42748
42826
|
await devServerLifecycle.stopForProject(f.name);
|
|
42749
|
-
const project = await
|
|
42750
|
-
await setStageAndBroadcast(f.name, "stopped");
|
|
42827
|
+
const project = await userStore.updatePort(f.name, f.newPort);
|
|
42828
|
+
await setStageAndBroadcast(f.name, "stopped", void 0, userStore);
|
|
42751
42829
|
return { response: { project } };
|
|
42752
42830
|
};
|
|
42753
|
-
const setProdUrl = async (frame) => {
|
|
42831
|
+
const setProdUrl = async (frame, _client, ctx) => {
|
|
42754
42832
|
const f = frame;
|
|
42755
42833
|
if (typeof f.name !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "setProdUrl: name required");
|
|
42756
42834
|
if (typeof f.url !== "string") throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "setProdUrl: url required");
|
|
42757
|
-
|
|
42835
|
+
ensureProjectWriteAccess(ctx, { projectName: f.name });
|
|
42836
|
+
const userStore = resolveStore({ ctx, projectName: f.name });
|
|
42837
|
+
const project = await userStore.setProdUrl(f.name, f.url);
|
|
42758
42838
|
const current = getStageOf(f.name);
|
|
42759
|
-
await setStageAndBroadcast(f.name, current.stage, current.reason);
|
|
42839
|
+
await setStageAndBroadcast(f.name, current.stage, current.reason, userStore);
|
|
42760
42840
|
return { response: { project } };
|
|
42761
42841
|
};
|
|
42762
|
-
const startDevServer = async (frame) => {
|
|
42842
|
+
const startDevServer = async (frame, _client, ctx) => {
|
|
42763
42843
|
const f = frame;
|
|
42764
42844
|
if (typeof f.sessionId !== "string" || !f.sessionId) {
|
|
42765
42845
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "startDevServer: sessionId required");
|
|
42766
42846
|
}
|
|
42767
42847
|
const force = f.force === true;
|
|
42848
|
+
ensureProjectWriteAccess(ctx, { sessionId: f.sessionId });
|
|
42768
42849
|
const session = sessionManager.findOwnedSession(f.sessionId);
|
|
42769
42850
|
if (!session) throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `session ${f.sessionId} not found`);
|
|
42770
42851
|
const projectName = session.appBuilderProject;
|
|
@@ -42774,7 +42855,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42774
42855
|
`session ${f.sessionId} is not bound to any project; cannot start dev server`
|
|
42775
42856
|
);
|
|
42776
42857
|
}
|
|
42777
|
-
const
|
|
42858
|
+
const userStore = resolveStore({ ctx, sessionId: f.sessionId });
|
|
42859
|
+
const projects = await userStore.list();
|
|
42778
42860
|
const meta = projects.find((p2) => p2.name === projectName);
|
|
42779
42861
|
if (!meta) {
|
|
42780
42862
|
throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `project ${projectName} not found on disk`);
|
|
@@ -42794,20 +42876,20 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42794
42876
|
deps.logger?.info("app-builder.start-dev-server.force-stop", { name: projectName });
|
|
42795
42877
|
await devServerLifecycle.stopForProject(projectName);
|
|
42796
42878
|
}
|
|
42797
|
-
await setStageAndBroadcast(projectName, "starting-dev-server");
|
|
42879
|
+
await setStageAndBroadcast(projectName, "starting-dev-server", void 0, userStore);
|
|
42798
42880
|
const tunnelUrl = getTunnelUrl();
|
|
42799
42881
|
const tunnelHost = tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null;
|
|
42800
42882
|
devServerLifecycle.startForSession({
|
|
42801
42883
|
sessionId: f.sessionId,
|
|
42802
42884
|
projectName,
|
|
42803
|
-
cwd:
|
|
42885
|
+
cwd: userStore.projectDir(projectName),
|
|
42804
42886
|
port: meta.port,
|
|
42805
42887
|
tunnelHost,
|
|
42806
42888
|
devCommand: meta.devCommand
|
|
42807
42889
|
});
|
|
42808
42890
|
try {
|
|
42809
42891
|
await devServerLifecycle.awaitReady(projectName, { timeoutMs: DEV_SERVER_READY_TIMEOUT_MS });
|
|
42810
|
-
await setStageAndBroadcast(projectName, "running");
|
|
42892
|
+
await setStageAndBroadcast(projectName, "running", void 0, userStore);
|
|
42811
42893
|
} catch (err) {
|
|
42812
42894
|
const reason = err instanceof Error ? err.message : "dev server not ready";
|
|
42813
42895
|
deps.logger?.warn("app-builder.start-dev-server.ready-failed", {
|
|
@@ -42815,11 +42897,11 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42815
42897
|
sessionId: f.sessionId,
|
|
42816
42898
|
reason
|
|
42817
42899
|
});
|
|
42818
|
-
await setStageAndBroadcast(projectName, "failed", reason);
|
|
42900
|
+
await setStageAndBroadcast(projectName, "failed", reason, userStore);
|
|
42819
42901
|
}
|
|
42820
42902
|
return { response: { project: meta } };
|
|
42821
42903
|
};
|
|
42822
|
-
const reportStage = async (frame) => {
|
|
42904
|
+
const reportStage = async (frame, _client, ctx) => {
|
|
42823
42905
|
const f = frame;
|
|
42824
42906
|
if (typeof f.sessionId !== "string" || !f.sessionId) {
|
|
42825
42907
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "reportStage: sessionId required");
|
|
@@ -42849,14 +42931,16 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42849
42931
|
reason,
|
|
42850
42932
|
sessionId: f.sessionId
|
|
42851
42933
|
});
|
|
42852
|
-
|
|
42934
|
+
const userStore = resolveStore({ ctx, sessionId: f.sessionId });
|
|
42935
|
+
await setStageAndBroadcast(projectName, f.stage, reason, userStore);
|
|
42853
42936
|
return { response: { ok: true } };
|
|
42854
42937
|
};
|
|
42855
|
-
const stopDevServer = async (frame) => {
|
|
42938
|
+
const stopDevServer = async (frame, _client, ctx) => {
|
|
42856
42939
|
const f = frame;
|
|
42857
42940
|
if (typeof f.sessionId !== "string" || !f.sessionId) {
|
|
42858
42941
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, "stopDevServer: sessionId required");
|
|
42859
42942
|
}
|
|
42943
|
+
ensureProjectWriteAccess(ctx, { sessionId: f.sessionId });
|
|
42860
42944
|
const session = sessionManager.findOwnedSession(f.sessionId);
|
|
42861
42945
|
if (!session) {
|
|
42862
42946
|
throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, `session ${f.sessionId} not found`);
|
|
@@ -42868,6 +42952,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42868
42952
|
`session ${f.sessionId} is not bound to any project; cannot stop dev server`
|
|
42869
42953
|
);
|
|
42870
42954
|
}
|
|
42955
|
+
const userStore = resolveStore({ ctx, sessionId: f.sessionId });
|
|
42871
42956
|
deps.logger?.info("app-builder.stop-dev-server.called", {
|
|
42872
42957
|
name: projectName,
|
|
42873
42958
|
sessionId: f.sessionId,
|
|
@@ -42877,7 +42962,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42877
42962
|
await devServerLifecycle.stopForProject(projectName);
|
|
42878
42963
|
}
|
|
42879
42964
|
try {
|
|
42880
|
-
const projects = await
|
|
42965
|
+
const projects = await userStore.list();
|
|
42881
42966
|
const meta = projects.find((p2) => p2.name === projectName);
|
|
42882
42967
|
if (meta) {
|
|
42883
42968
|
await killPortOccupants(meta.port, devServerLifecycle.getOwnedPids(), deps.logger);
|
|
@@ -42888,12 +42973,14 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42888
42973
|
error: err instanceof Error ? err.message : String(err)
|
|
42889
42974
|
});
|
|
42890
42975
|
}
|
|
42891
|
-
await setStageAndBroadcast(projectName, "stopped");
|
|
42976
|
+
await setStageAndBroadcast(projectName, "stopped", void 0, userStore);
|
|
42892
42977
|
return { response: { ok: true } };
|
|
42893
42978
|
};
|
|
42894
|
-
const publish = async (frame) => {
|
|
42979
|
+
const publish = async (frame, _client, ctx) => {
|
|
42895
42980
|
const args = PublishArgsSchema.parse(frame);
|
|
42896
|
-
|
|
42981
|
+
ensureProjectWriteAccess(ctx, { projectName: args.name });
|
|
42982
|
+
const userStore = resolveStore({ ctx, projectName: args.name });
|
|
42983
|
+
const projects = await userStore.list();
|
|
42897
42984
|
const project = projects.find((p2) => p2.name === args.name);
|
|
42898
42985
|
if (!project) {
|
|
42899
42986
|
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `publish: project "${args.name}" not found`);
|
|
@@ -42911,7 +42998,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42911
42998
|
previousJobId: project.publishJob.jobId,
|
|
42912
42999
|
previousStage: project.publishJob.stage
|
|
42913
43000
|
});
|
|
42914
|
-
await
|
|
43001
|
+
await userStore.clearPublishJob(args.name);
|
|
42915
43002
|
}
|
|
42916
43003
|
const scriptPath = (0, import_node_path40.join)(deps.personaRoot, PUBLISH_SCRIPT_REL);
|
|
42917
43004
|
deps.logger?.info("app-builder.publish.start", {
|
|
@@ -42923,19 +43010,19 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42923
43010
|
{
|
|
42924
43011
|
registry: publishRegistry,
|
|
42925
43012
|
setPublishJob: async (name, job) => {
|
|
42926
|
-
await
|
|
43013
|
+
await userStore.setPublishJob(name, job);
|
|
42927
43014
|
const current = getStageOf(name);
|
|
42928
|
-
await setStageAndBroadcast(name, current.stage, current.reason);
|
|
43015
|
+
await setStageAndBroadcast(name, current.stage, current.reason, userStore);
|
|
42929
43016
|
},
|
|
42930
43017
|
clearPublishJob: async (name) => {
|
|
42931
|
-
await
|
|
43018
|
+
await userStore.clearPublishJob(name);
|
|
42932
43019
|
const current = getStageOf(name);
|
|
42933
|
-
await setStageAndBroadcast(name, current.stage, current.reason);
|
|
43020
|
+
await setStageAndBroadcast(name, current.stage, current.reason, userStore);
|
|
42934
43021
|
},
|
|
42935
43022
|
setProdUrl: async (name, url) => {
|
|
42936
|
-
await
|
|
43023
|
+
await userStore.setProdUrl(name, url);
|
|
42937
43024
|
const current = getStageOf(name);
|
|
42938
|
-
await setStageAndBroadcast(name, current.stage, current.reason);
|
|
43025
|
+
await setStageAndBroadcast(name, current.stage, current.reason, userStore);
|
|
42939
43026
|
},
|
|
42940
43027
|
broadcast: (f) => {
|
|
42941
43028
|
broadcastProjectUpdated(f);
|
|
@@ -42946,7 +43033,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42946
43033
|
deps.broadcastSessionFrame(entry.frame, entry.target);
|
|
42947
43034
|
}
|
|
42948
43035
|
},
|
|
42949
|
-
projectDir: (name) =>
|
|
43036
|
+
projectDir: (name) => userStore.projectDir(name),
|
|
42950
43037
|
logger: deps.logger
|
|
42951
43038
|
},
|
|
42952
43039
|
{
|
|
@@ -42957,9 +43044,11 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42957
43044
|
);
|
|
42958
43045
|
return { response: { ...result } };
|
|
42959
43046
|
};
|
|
42960
|
-
const dismissPublishJob = async (frame) => {
|
|
43047
|
+
const dismissPublishJob = async (frame, _client, ctx) => {
|
|
42961
43048
|
const args = DismissPublishJobArgsSchema.parse(frame);
|
|
42962
|
-
|
|
43049
|
+
ensureProjectWriteAccess(ctx, { projectName: args.name });
|
|
43050
|
+
const userStore = resolveStore({ ctx, projectName: args.name });
|
|
43051
|
+
const projects = await userStore.list();
|
|
42963
43052
|
const project = projects.find((p2) => p2.name === args.name);
|
|
42964
43053
|
if (!project) {
|
|
42965
43054
|
throw new ClawdError(
|
|
@@ -42982,14 +43071,15 @@ function buildAppBuilderHandlers(deps) {
|
|
|
42982
43071
|
jobId: project.publishJob.jobId,
|
|
42983
43072
|
stage: project.publishJob.stage
|
|
42984
43073
|
});
|
|
42985
|
-
await
|
|
43074
|
+
await userStore.clearPublishJob(args.name);
|
|
42986
43075
|
const current = getStageOf(args.name);
|
|
42987
|
-
await setStageAndBroadcast(args.name, current.stage, current.reason);
|
|
43076
|
+
await setStageAndBroadcast(args.name, current.stage, current.reason, userStore);
|
|
42988
43077
|
}
|
|
42989
43078
|
return { response: { ok: true } };
|
|
42990
43079
|
};
|
|
42991
43080
|
return {
|
|
42992
43081
|
"appBuilder:listProjects": listProjects,
|
|
43082
|
+
"appBuilder:getProject": getProject,
|
|
42993
43083
|
"appBuilder:createProject": createProject,
|
|
42994
43084
|
"appBuilder:deleteProject": deleteProject,
|
|
42995
43085
|
"appBuilder:updateProjectPort": updateProjectPort,
|
|
@@ -43094,6 +43184,7 @@ function buildMethodHandlers(deps) {
|
|
|
43094
43184
|
...deps,
|
|
43095
43185
|
devServerLifecycle: deps.devServerLifecycle,
|
|
43096
43186
|
appBuilderStore: deps.appBuilderStore,
|
|
43187
|
+
getProjectStore: deps.getProjectStore,
|
|
43097
43188
|
getTunnelUrl: deps.getTunnelUrl,
|
|
43098
43189
|
logger: deps.logger
|
|
43099
43190
|
}),
|
|
@@ -43138,6 +43229,8 @@ function buildMethodHandlers(deps) {
|
|
|
43138
43229
|
}),
|
|
43139
43230
|
...buildAppBuilderHandlers({
|
|
43140
43231
|
store: deps.appBuilderStore,
|
|
43232
|
+
getStore: deps.getProjectStore,
|
|
43233
|
+
usersRoot: deps.usersRoot,
|
|
43141
43234
|
devServerLifecycle: deps.devServerLifecycle,
|
|
43142
43235
|
sessionManager: deps.manager,
|
|
43143
43236
|
getTunnelUrl: deps.getTunnelUrl,
|
|
@@ -43153,7 +43246,7 @@ function buildMethodHandlers(deps) {
|
|
|
43153
43246
|
}
|
|
43154
43247
|
|
|
43155
43248
|
// src/app-builder/project-store.ts
|
|
43156
|
-
var
|
|
43249
|
+
var import_node_fs31 = require("fs");
|
|
43157
43250
|
var import_node_child_process9 = require("child_process");
|
|
43158
43251
|
var import_node_path43 = require("path");
|
|
43159
43252
|
init_protocol();
|
|
@@ -43163,11 +43256,11 @@ var SCAFFOLD_SCRIPT_REL = "extension-kit/scripts/new-extension.sh";
|
|
|
43163
43256
|
var DEFAULT_TEMPLATE = "nestjs-react";
|
|
43164
43257
|
var SCAFFOLD_TIMEOUT_MS = 3e4;
|
|
43165
43258
|
var ProjectStore = class {
|
|
43166
|
-
/** @param
|
|
43167
|
-
constructor(
|
|
43168
|
-
this.
|
|
43259
|
+
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
|
|
43260
|
+
constructor(root) {
|
|
43261
|
+
this.root = root;
|
|
43169
43262
|
}
|
|
43170
|
-
|
|
43263
|
+
root;
|
|
43171
43264
|
/** projects/<name>/.clawd-project.json 路径 */
|
|
43172
43265
|
metaPath(name) {
|
|
43173
43266
|
return (0, import_node_path43.join)(this.projectsRoot(), name, META_FILE);
|
|
@@ -43177,12 +43270,12 @@ var ProjectStore = class {
|
|
|
43177
43270
|
return (0, import_node_path43.join)(this.projectsRoot(), name);
|
|
43178
43271
|
}
|
|
43179
43272
|
projectsRoot() {
|
|
43180
|
-
return (0, import_node_path43.join)(this.
|
|
43273
|
+
return (0, import_node_path43.join)(this.root, PROJECTS_DIR);
|
|
43181
43274
|
}
|
|
43182
43275
|
async list() {
|
|
43183
43276
|
let entries;
|
|
43184
43277
|
try {
|
|
43185
|
-
entries = await
|
|
43278
|
+
entries = await import_node_fs31.promises.readdir(this.projectsRoot());
|
|
43186
43279
|
} catch (err) {
|
|
43187
43280
|
if (err.code === "ENOENT") return [];
|
|
43188
43281
|
throw err;
|
|
@@ -43190,7 +43283,7 @@ var ProjectStore = class {
|
|
|
43190
43283
|
const out = [];
|
|
43191
43284
|
for (const name of entries) {
|
|
43192
43285
|
try {
|
|
43193
|
-
const raw = await
|
|
43286
|
+
const raw = await import_node_fs31.promises.readFile(this.metaPath(name), "utf8");
|
|
43194
43287
|
const json = JSON.parse(raw);
|
|
43195
43288
|
let migrated = false;
|
|
43196
43289
|
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
@@ -43201,7 +43294,7 @@ var ProjectStore = class {
|
|
|
43201
43294
|
if (parsed.success) {
|
|
43202
43295
|
out.push(parsed.data);
|
|
43203
43296
|
if (migrated) {
|
|
43204
|
-
void
|
|
43297
|
+
void import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
43205
43298
|
});
|
|
43206
43299
|
}
|
|
43207
43300
|
}
|
|
@@ -43210,12 +43303,18 @@ var ProjectStore = class {
|
|
|
43210
43303
|
}
|
|
43211
43304
|
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
43212
43305
|
}
|
|
43213
|
-
|
|
43306
|
+
/**
|
|
43307
|
+
* @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
|
|
43308
|
+
* dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
|
|
43309
|
+
* 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
|
|
43310
|
+
*/
|
|
43311
|
+
async create(name, reservedPorts) {
|
|
43214
43312
|
const existing = await this.list();
|
|
43215
43313
|
if (existing.some((p2) => p2.name === name)) {
|
|
43216
43314
|
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
43217
43315
|
}
|
|
43218
43316
|
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
43317
|
+
if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
|
|
43219
43318
|
let port = -1;
|
|
43220
43319
|
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
43221
43320
|
if (!usedPorts.has(p2)) {
|
|
@@ -43239,8 +43338,8 @@ var ProjectStore = class {
|
|
|
43239
43338
|
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
43240
43339
|
}
|
|
43241
43340
|
const dir = this.projectDir(name);
|
|
43242
|
-
await
|
|
43243
|
-
await
|
|
43341
|
+
await import_node_fs31.promises.mkdir(dir, { recursive: true });
|
|
43342
|
+
await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
43244
43343
|
return meta;
|
|
43245
43344
|
}
|
|
43246
43345
|
/**
|
|
@@ -43251,11 +43350,12 @@ var ProjectStore = class {
|
|
|
43251
43350
|
* spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
|
|
43252
43351
|
* 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
|
|
43253
43352
|
*/
|
|
43254
|
-
async scaffold(name, template = DEFAULT_TEMPLATE) {
|
|
43255
|
-
const scriptPath = (0, import_node_path43.join)(
|
|
43353
|
+
async scaffold(name, template = DEFAULT_TEMPLATE, personaRoot = this.root) {
|
|
43354
|
+
const scriptPath = (0, import_node_path43.join)(personaRoot, SCAFFOLD_SCRIPT_REL);
|
|
43355
|
+
const destDir = this.projectDir(name);
|
|
43256
43356
|
return await new Promise((resolve6, reject) => {
|
|
43257
|
-
const child = (0, import_node_child_process9.spawn)("bash", [scriptPath, name, template], {
|
|
43258
|
-
cwd:
|
|
43357
|
+
const child = (0, import_node_child_process9.spawn)("bash", [scriptPath, name, template, destDir], {
|
|
43358
|
+
cwd: personaRoot,
|
|
43259
43359
|
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
43260
43360
|
stdio: ["ignore", "pipe", "pipe"]
|
|
43261
43361
|
});
|
|
@@ -43284,7 +43384,7 @@ var ProjectStore = class {
|
|
|
43284
43384
|
}
|
|
43285
43385
|
async delete(name) {
|
|
43286
43386
|
const dir = this.projectDir(name);
|
|
43287
|
-
await
|
|
43387
|
+
await import_node_fs31.promises.rm(dir, { recursive: true, force: true });
|
|
43288
43388
|
}
|
|
43289
43389
|
async updatePort(name, newPort) {
|
|
43290
43390
|
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
@@ -43300,7 +43400,7 @@ var ProjectStore = class {
|
|
|
43300
43400
|
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
43301
43401
|
}
|
|
43302
43402
|
const updated = { ...target, port: newPort };
|
|
43303
|
-
await
|
|
43403
|
+
await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
43304
43404
|
return updated;
|
|
43305
43405
|
}
|
|
43306
43406
|
/**
|
|
@@ -43317,7 +43417,7 @@ var ProjectStore = class {
|
|
|
43317
43417
|
if (!validated.success) {
|
|
43318
43418
|
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
43319
43419
|
}
|
|
43320
|
-
await
|
|
43420
|
+
await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
43321
43421
|
return validated.data;
|
|
43322
43422
|
}
|
|
43323
43423
|
/**
|
|
@@ -43338,7 +43438,7 @@ var ProjectStore = class {
|
|
|
43338
43438
|
if (!validated.success) {
|
|
43339
43439
|
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
43340
43440
|
}
|
|
43341
|
-
await
|
|
43441
|
+
await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
43342
43442
|
return validated.data;
|
|
43343
43443
|
}
|
|
43344
43444
|
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
@@ -43353,7 +43453,7 @@ var ProjectStore = class {
|
|
|
43353
43453
|
if (!validated.success) {
|
|
43354
43454
|
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
43355
43455
|
}
|
|
43356
|
-
await
|
|
43456
|
+
await import_node_fs31.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
43357
43457
|
return validated.data;
|
|
43358
43458
|
}
|
|
43359
43459
|
};
|
|
@@ -43755,22 +43855,24 @@ var METHOD_GRANT_MAP = {
|
|
|
43755
43855
|
// 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
|
|
43756
43856
|
// 不暴露给 guest(远端接入者)。
|
|
43757
43857
|
"appBuilder:listProjects": ADMIN_ANY,
|
|
43858
|
+
// getProject 走 capability-scoped(区别于其它 appBuilder 的 owner-only ADMIN_ANY):mirror/people
|
|
43859
|
+
// 的本机 owner 在远端是 guest,需能调 getProject 拿它 mirror 的 session 的 project。dispatcher
|
|
43860
|
+
// 放行后由 handler 内 canAccessSession(read) 按 session 可见性授权(防 guest 越权拿别人 project)。
|
|
43861
|
+
"appBuilder:getProject": CAPABILITY_SCOPED,
|
|
43758
43862
|
"appBuilder:createProject": ADMIN_ANY,
|
|
43759
43863
|
"appBuilder:deleteProject": ADMIN_ANY,
|
|
43760
43864
|
"appBuilder:updateProjectPort": ADMIN_ANY,
|
|
43761
|
-
//
|
|
43762
|
-
|
|
43763
|
-
//
|
|
43764
|
-
//
|
|
43765
|
-
"appBuilder:
|
|
43865
|
+
// people/mirror 协同写操作(spec 2026-06-04-people-mode-app-builder-write-and-push):以下 5 个写
|
|
43866
|
+
// method 走 CAPABILITY_SCOPED(不再 owner-only ADMIN_ANY),handler 内 ensureProjectWriteAccess
|
|
43867
|
+
// 按 session 可见性(canAccessSession 'send')授权——mirror 的本机 owner 在远端是 guest,能 mirror
|
|
43868
|
+
// 该 session 就能操作它绑定的 project(发布凭证是 persona 共享资产,老板决策"统一 session 授权")。
|
|
43869
|
+
"appBuilder:setProdUrl": CAPABILITY_SCOPED,
|
|
43870
|
+
"appBuilder:startDevServer": CAPABILITY_SCOPED,
|
|
43871
|
+
// reportStage 仍 owner-only:assistant 走 HTTP RPC 上报(不过 WS admin grant),UI 不调它。
|
|
43766
43872
|
"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
|
|
43873
|
+
"appBuilder:stopDevServer": CAPABILITY_SCOPED,
|
|
43874
|
+
"appBuilder:publish": CAPABILITY_SCOPED,
|
|
43875
|
+
"appBuilder:dismissPublishJob": CAPABILITY_SCOPED
|
|
43774
43876
|
};
|
|
43775
43877
|
function computeGrantForFrame(method, frame) {
|
|
43776
43878
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -44279,6 +44381,7 @@ async function startDaemon(config) {
|
|
|
44279
44381
|
const history = new ClaudeHistoryReader();
|
|
44280
44382
|
let transport = null;
|
|
44281
44383
|
const personaStore = new PersonaStore(import_node_path47.default.join(config.dataDir, "personas"));
|
|
44384
|
+
const usersRoot = import_node_path47.default.join(config.dataDir, "users");
|
|
44282
44385
|
const defaultsRoot = findDefaultsRoot();
|
|
44283
44386
|
if (defaultsRoot) {
|
|
44284
44387
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -44297,6 +44400,7 @@ async function startDaemon(config) {
|
|
|
44297
44400
|
historyReader: history,
|
|
44298
44401
|
dataDir: config.dataDir,
|
|
44299
44402
|
personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
|
|
44403
|
+
usersRoot,
|
|
44300
44404
|
personaStore,
|
|
44301
44405
|
ownerDisplayName,
|
|
44302
44406
|
ownerPrincipalId,
|
|
@@ -44328,7 +44432,7 @@ async function startDaemon(config) {
|
|
|
44328
44432
|
const absPath = import_node_path47.default.isAbsolute(input.relPath) ? input.relPath : import_node_path47.default.join(input.cwd, input.relPath);
|
|
44329
44433
|
let size = 0;
|
|
44330
44434
|
try {
|
|
44331
|
-
size =
|
|
44435
|
+
size = import_node_fs32.default.statSync(absPath).size;
|
|
44332
44436
|
} catch (err) {
|
|
44333
44437
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
44334
44438
|
sessionId: input.sessionId,
|
|
@@ -44457,9 +44561,8 @@ async function startDaemon(config) {
|
|
|
44457
44561
|
ttcAuthBase: TTC_HOSTS.auth,
|
|
44458
44562
|
getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
|
|
44459
44563
|
});
|
|
44460
|
-
const
|
|
44461
|
-
|
|
44462
|
-
);
|
|
44564
|
+
const getProjectStore = (userId) => new ProjectStore(deriveUserWorkDir(userId, usersRoot));
|
|
44565
|
+
const appBuilderStore = getProjectStore(ownerPrincipalId);
|
|
44463
44566
|
recoverInterruptedPublishJobs(appBuilderStore, logger).catch((err) => {
|
|
44464
44567
|
logger.warn("app-builder.publish.recover-startup-failed", {
|
|
44465
44568
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -44510,11 +44613,14 @@ async function startDaemon(config) {
|
|
|
44510
44613
|
// group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
|
|
44511
44614
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
44512
44615
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
44513
|
-
// guest path guard:candidate 必须在 personaRoot
|
|
44514
|
-
personaRoot: import_node_path47.default.join(config.dataDir, "personas")
|
|
44616
|
+
// guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
|
|
44617
|
+
personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
|
|
44618
|
+
usersRoot
|
|
44515
44619
|
},
|
|
44516
44620
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
44517
44621
|
personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
|
|
44622
|
+
// v2 多人 persona 隔离:handler 派生 guest user-dir 放行
|
|
44623
|
+
usersRoot,
|
|
44518
44624
|
// capability:list / delete handler 依赖
|
|
44519
44625
|
capabilityManager,
|
|
44520
44626
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
@@ -44582,10 +44688,22 @@ async function startDaemon(config) {
|
|
|
44582
44688
|
},
|
|
44583
44689
|
// app-builder
|
|
44584
44690
|
appBuilderStore,
|
|
44691
|
+
getProjectStore,
|
|
44585
44692
|
devServerLifecycle: devServerSupervisor,
|
|
44586
44693
|
// 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
|
|
44587
44694
|
// 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
|
|
44588
|
-
|
|
44695
|
+
// people/mirror(spec 2026-06-04-people-mode-app-builder-write-and-push):额外推给订阅该 project
|
|
44696
|
+
// 绑定 session 的 client(mirror guest 经 session:observe 订阅),让远端协同者也看到 stage / 发布进度。
|
|
44697
|
+
// 同一帧 owner 也走 project-updated 复用给 publish-progress/failed(app-builder.ts 内 broadcast 闭包):
|
|
44698
|
+
// name 在 event.project.name(project-updated)或 event.name(publish 帧)。owner 双栏会同时命中
|
|
44699
|
+
// owner-broadcast + session-broadcast,但 UI patch 幂等(同 project 覆盖)无害。
|
|
44700
|
+
broadcastProjectUpdated: (event) => {
|
|
44701
|
+
const ev = event;
|
|
44702
|
+
wsServer?.broadcastToOwners(ev);
|
|
44703
|
+
const projectName = event.project?.name ?? event.name;
|
|
44704
|
+
const boundSid = projectName ? manager.findOwnedSessionByAppBuilderProject(projectName)?.sessionId : void 0;
|
|
44705
|
+
if (boundSid) wsServer?.broadcastToSession(boundSid, ev);
|
|
44706
|
+
},
|
|
44589
44707
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
44590
44708
|
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
44591
44709
|
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
@@ -44852,7 +44970,7 @@ ${bar}
|
|
|
44852
44970
|
`);
|
|
44853
44971
|
try {
|
|
44854
44972
|
const connectPath = import_node_path47.default.join(config.dataDir, "connect.txt");
|
|
44855
|
-
|
|
44973
|
+
import_node_fs32.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
44856
44974
|
} catch {
|
|
44857
44975
|
}
|
|
44858
44976
|
} catch (err) {
|
|
@@ -44925,7 +45043,7 @@ ${bar}
|
|
|
44925
45043
|
function migrateDropPersonsDir(dataDir) {
|
|
44926
45044
|
const dir = import_node_path47.default.join(dataDir, "persons");
|
|
44927
45045
|
try {
|
|
44928
|
-
|
|
45046
|
+
import_node_fs32.default.rmSync(dir, { recursive: true, force: true });
|
|
44929
45047
|
} catch {
|
|
44930
45048
|
}
|
|
44931
45049
|
}
|