@clawos-dev/clawd 0.2.143-beta.300.c2a3792 → 0.2.143-beta.301.814d293
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 +299 -238
- package/dist/deploy-kit/.secrets/aliyun.env.example +4 -0
- package/dist/deploy-kit/scripts/new-extension.sh +22 -0
- package/dist/deploy-kit/scripts/publish.sh +100 -0
- package/dist/deploy-kit/scripts/publish.spec.ts +69 -0
- package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/remove-extension.sh +15 -26
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +13 -13
- package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +24 -18
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
- package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +48 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +28 -25
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +25 -0
- package/package.json +1 -1
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -66
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -110
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/contract/bootstrap +0 -22
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/ensure-toolchain.sh +0 -62
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/new-extension.sh +0 -66
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/publish.sh +0 -129
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/remove-extension.sh +0 -70
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/verify.sh +0 -38
- /package/dist/{persona-defaults/persona-app-builder → deploy-kit}/.secrets/aliyun.env +0 -0
- /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/contract/bootstrap +0 -0
- /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/ensure-toolchain.sh +0 -0
- /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/verify.sh +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -35316,6 +35316,56 @@ function migrateCodexSandbox(args) {
|
|
|
35316
35316
|
}
|
|
35317
35317
|
if (n > 0) args.logger.info("persona.codex-sandbox.done", { migrated: n });
|
|
35318
35318
|
}
|
|
35319
|
+
function findDeployKitRoot() {
|
|
35320
|
+
const candidates = [];
|
|
35321
|
+
try {
|
|
35322
|
+
const here = path11.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
35323
|
+
candidates.push(path11.resolve(here, "..", "deploy-kit"));
|
|
35324
|
+
} catch {
|
|
35325
|
+
}
|
|
35326
|
+
if (process.argv[1]) {
|
|
35327
|
+
candidates.push(path11.resolve(path11.dirname(process.argv[1]), "deploy-kit"));
|
|
35328
|
+
}
|
|
35329
|
+
for (const c of candidates) {
|
|
35330
|
+
try {
|
|
35331
|
+
if (fs9.statSync(c).isDirectory()) return c;
|
|
35332
|
+
} catch {
|
|
35333
|
+
}
|
|
35334
|
+
}
|
|
35335
|
+
return null;
|
|
35336
|
+
}
|
|
35337
|
+
function seedDeployKit(args) {
|
|
35338
|
+
const dst = path11.join(args.dataDir, "deploy-kit");
|
|
35339
|
+
fs9.cpSync(args.deployKitBundleRoot, dst, {
|
|
35340
|
+
recursive: true,
|
|
35341
|
+
dereference: true,
|
|
35342
|
+
force: false,
|
|
35343
|
+
errorOnExist: false
|
|
35344
|
+
});
|
|
35345
|
+
args.logger.info("deploy-kit.seed.done", { dst });
|
|
35346
|
+
}
|
|
35347
|
+
function refreshDeployKit(args) {
|
|
35348
|
+
const dst = path11.join(args.dataDir, "deploy-kit");
|
|
35349
|
+
if (!fs9.existsSync(dst)) {
|
|
35350
|
+
seedDeployKit(args);
|
|
35351
|
+
return;
|
|
35352
|
+
}
|
|
35353
|
+
for (const sub of ["scripts", "contract"]) {
|
|
35354
|
+
const s = path11.join(args.deployKitBundleRoot, sub);
|
|
35355
|
+
if (fs9.existsSync(s)) {
|
|
35356
|
+
fs9.cpSync(s, path11.join(dst, sub), { recursive: true, force: true, dereference: true });
|
|
35357
|
+
}
|
|
35358
|
+
}
|
|
35359
|
+
const secretsSrc = path11.join(args.deployKitBundleRoot, ".secrets");
|
|
35360
|
+
if (fs9.existsSync(secretsSrc)) {
|
|
35361
|
+
fs9.mkdirSync(path11.join(dst, ".secrets"), { recursive: true });
|
|
35362
|
+
for (const f of fs9.readdirSync(secretsSrc)) {
|
|
35363
|
+
if (!f.endsWith(".example")) continue;
|
|
35364
|
+
fs9.copyFileSync(path11.join(secretsSrc, f), path11.join(dst, ".secrets", f));
|
|
35365
|
+
}
|
|
35366
|
+
}
|
|
35367
|
+
args.logger.info("deploy-kit.refresh.done", { dst });
|
|
35368
|
+
}
|
|
35319
35369
|
|
|
35320
35370
|
// src/index.ts
|
|
35321
35371
|
init_claude();
|
|
@@ -43253,8 +43303,218 @@ function buildExtensionHandlers(deps) {
|
|
|
43253
43303
|
};
|
|
43254
43304
|
}
|
|
43255
43305
|
|
|
43256
|
-
// src/app-builder/
|
|
43306
|
+
// src/app-builder/project-store.ts
|
|
43307
|
+
var import_node_fs30 = require("fs");
|
|
43257
43308
|
var import_node_child_process11 = require("child_process");
|
|
43309
|
+
var import_node_path37 = require("path");
|
|
43310
|
+
init_protocol();
|
|
43311
|
+
var PROJECTS_DIR = "projects";
|
|
43312
|
+
var META_FILE = ".clawd-project.json";
|
|
43313
|
+
var DEFAULT_TEMPLATE = "nestjs-react";
|
|
43314
|
+
var SCAFFOLD_TIMEOUT_MS = 3e4;
|
|
43315
|
+
var ProjectStore = class {
|
|
43316
|
+
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
|
|
43317
|
+
constructor(root) {
|
|
43318
|
+
this.root = root;
|
|
43319
|
+
}
|
|
43320
|
+
root;
|
|
43321
|
+
/** projects/<name>/.clawd-project.json 路径 */
|
|
43322
|
+
metaPath(name) {
|
|
43323
|
+
return (0, import_node_path37.join)(this.projectsRoot(), name, META_FILE);
|
|
43324
|
+
}
|
|
43325
|
+
/** projects/<name>/ 目录路径(cwd 用) */
|
|
43326
|
+
projectDir(name) {
|
|
43327
|
+
return (0, import_node_path37.join)(this.projectsRoot(), name);
|
|
43328
|
+
}
|
|
43329
|
+
projectsRoot() {
|
|
43330
|
+
return (0, import_node_path37.join)(this.root, PROJECTS_DIR);
|
|
43331
|
+
}
|
|
43332
|
+
async list() {
|
|
43333
|
+
let entries;
|
|
43334
|
+
try {
|
|
43335
|
+
entries = await import_node_fs30.promises.readdir(this.projectsRoot());
|
|
43336
|
+
} catch (err) {
|
|
43337
|
+
if (err.code === "ENOENT") return [];
|
|
43338
|
+
throw err;
|
|
43339
|
+
}
|
|
43340
|
+
const out = [];
|
|
43341
|
+
for (const name of entries) {
|
|
43342
|
+
try {
|
|
43343
|
+
const raw = await import_node_fs30.promises.readFile(this.metaPath(name), "utf8");
|
|
43344
|
+
const json = JSON.parse(raw);
|
|
43345
|
+
let migrated = false;
|
|
43346
|
+
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
43347
|
+
json.devCommand = DEFAULT_DEV_COMMAND;
|
|
43348
|
+
migrated = true;
|
|
43349
|
+
}
|
|
43350
|
+
const parsed = ProjectMetadataSchema.safeParse(json);
|
|
43351
|
+
if (parsed.success) {
|
|
43352
|
+
out.push(parsed.data);
|
|
43353
|
+
if (migrated) {
|
|
43354
|
+
void import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
43355
|
+
});
|
|
43356
|
+
}
|
|
43357
|
+
}
|
|
43358
|
+
} catch {
|
|
43359
|
+
}
|
|
43360
|
+
}
|
|
43361
|
+
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
43362
|
+
}
|
|
43363
|
+
/**
|
|
43364
|
+
* @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
|
|
43365
|
+
* dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
|
|
43366
|
+
* 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
|
|
43367
|
+
*/
|
|
43368
|
+
async create(name, reservedPorts) {
|
|
43369
|
+
const existing = await this.list();
|
|
43370
|
+
if (existing.some((p2) => p2.name === name)) {
|
|
43371
|
+
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
43372
|
+
}
|
|
43373
|
+
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
43374
|
+
if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
|
|
43375
|
+
let port = -1;
|
|
43376
|
+
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
43377
|
+
if (!usedPorts.has(p2)) {
|
|
43378
|
+
port = p2;
|
|
43379
|
+
break;
|
|
43380
|
+
}
|
|
43381
|
+
}
|
|
43382
|
+
if (port < 0) {
|
|
43383
|
+
throw new Error(
|
|
43384
|
+
`port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
|
|
43385
|
+
);
|
|
43386
|
+
}
|
|
43387
|
+
const meta = {
|
|
43388
|
+
name,
|
|
43389
|
+
port,
|
|
43390
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43391
|
+
devCommand: DEFAULT_DEV_COMMAND
|
|
43392
|
+
};
|
|
43393
|
+
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
43394
|
+
if (!validated.success) {
|
|
43395
|
+
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
43396
|
+
}
|
|
43397
|
+
const dir = this.projectDir(name);
|
|
43398
|
+
await import_node_fs30.promises.mkdir(dir, { recursive: true });
|
|
43399
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
43400
|
+
return meta;
|
|
43401
|
+
}
|
|
43402
|
+
/**
|
|
43403
|
+
* 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
|
|
43404
|
+
* 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
|
|
43405
|
+
* 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
|
|
43406
|
+
*
|
|
43407
|
+
* spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
|
|
43408
|
+
* 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
|
|
43409
|
+
*/
|
|
43410
|
+
async scaffold(name, templateSrcDir, scaffoldScriptPath) {
|
|
43411
|
+
const destDir = this.projectDir(name);
|
|
43412
|
+
return await new Promise((resolve6, reject) => {
|
|
43413
|
+
const child = (0, import_node_child_process11.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
|
|
43414
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
43415
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
43416
|
+
});
|
|
43417
|
+
let stdout = "";
|
|
43418
|
+
let stderr = "";
|
|
43419
|
+
child.stdout.on("data", (d) => {
|
|
43420
|
+
stdout += d.toString("utf8");
|
|
43421
|
+
});
|
|
43422
|
+
child.stderr.on("data", (d) => {
|
|
43423
|
+
stderr += d.toString("utf8");
|
|
43424
|
+
});
|
|
43425
|
+
const timer = setTimeout(() => {
|
|
43426
|
+
child.kill("SIGKILL");
|
|
43427
|
+
reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scaffoldScriptPath} ${name}`));
|
|
43428
|
+
}, SCAFFOLD_TIMEOUT_MS);
|
|
43429
|
+
child.on("error", (err) => {
|
|
43430
|
+
clearTimeout(timer);
|
|
43431
|
+
reject(new Error(`scaffold spawn failed: ${err.message}`));
|
|
43432
|
+
});
|
|
43433
|
+
child.on("exit", (code, signal) => {
|
|
43434
|
+
clearTimeout(timer);
|
|
43435
|
+
if (code === 0) resolve6({ stdout, stderr });
|
|
43436
|
+
else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
|
|
43437
|
+
});
|
|
43438
|
+
});
|
|
43439
|
+
}
|
|
43440
|
+
async delete(name) {
|
|
43441
|
+
const dir = this.projectDir(name);
|
|
43442
|
+
await import_node_fs30.promises.rm(dir, { recursive: true, force: true });
|
|
43443
|
+
}
|
|
43444
|
+
async updatePort(name, newPort) {
|
|
43445
|
+
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
43446
|
+
throw new Error(
|
|
43447
|
+
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
43448
|
+
);
|
|
43449
|
+
}
|
|
43450
|
+
const list = await this.list();
|
|
43451
|
+
const target = list.find((p2) => p2.name === name);
|
|
43452
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
43453
|
+
const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
|
|
43454
|
+
if (conflict) {
|
|
43455
|
+
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
43456
|
+
}
|
|
43457
|
+
const updated = { ...target, port: newPort };
|
|
43458
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
43459
|
+
return updated;
|
|
43460
|
+
}
|
|
43461
|
+
/**
|
|
43462
|
+
* 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
|
|
43463
|
+
* UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
|
|
43464
|
+
* 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
|
|
43465
|
+
*/
|
|
43466
|
+
async setProdUrl(name, url) {
|
|
43467
|
+
const list = await this.list();
|
|
43468
|
+
const target = list.find((p2) => p2.name === name);
|
|
43469
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
43470
|
+
const updated = { ...target, prodUrl: url };
|
|
43471
|
+
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
43472
|
+
if (!validated.success) {
|
|
43473
|
+
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
43474
|
+
}
|
|
43475
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
43476
|
+
return validated.data;
|
|
43477
|
+
}
|
|
43478
|
+
/**
|
|
43479
|
+
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
|
|
43480
|
+
* 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
|
|
43481
|
+
* - daemon 收到第一个 ::stage::build marker 时写入
|
|
43482
|
+
* - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
|
|
43483
|
+
* - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
|
|
43484
|
+
* - 只有 daemon 崩才会留残骸
|
|
43485
|
+
* 写入时保留所有其他字段(prodUrl 等不受影响)。
|
|
43486
|
+
*/
|
|
43487
|
+
async setPublishJob(name, job) {
|
|
43488
|
+
const list = await this.list();
|
|
43489
|
+
const target = list.find((p2) => p2.name === name);
|
|
43490
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
43491
|
+
const updated = { ...target, publishJob: job };
|
|
43492
|
+
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
43493
|
+
if (!validated.success) {
|
|
43494
|
+
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
43495
|
+
}
|
|
43496
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
43497
|
+
return validated.data;
|
|
43498
|
+
}
|
|
43499
|
+
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
43500
|
+
async clearPublishJob(name) {
|
|
43501
|
+
const list = await this.list();
|
|
43502
|
+
const target = list.find((p2) => p2.name === name);
|
|
43503
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
43504
|
+
if (!target.publishJob) return target;
|
|
43505
|
+
const { publishJob: _drop, ...rest } = target;
|
|
43506
|
+
void _drop;
|
|
43507
|
+
const validated = ProjectMetadataSchema.safeParse(rest);
|
|
43508
|
+
if (!validated.success) {
|
|
43509
|
+
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
43510
|
+
}
|
|
43511
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
43512
|
+
return validated.data;
|
|
43513
|
+
}
|
|
43514
|
+
};
|
|
43515
|
+
|
|
43516
|
+
// src/app-builder/kill-port.ts
|
|
43517
|
+
var import_node_child_process12 = require("child_process");
|
|
43258
43518
|
async function killPortOccupants(port, ownedPids, logger) {
|
|
43259
43519
|
let pids;
|
|
43260
43520
|
try {
|
|
@@ -43296,7 +43556,7 @@ async function killPortOccupants(port, ownedPids, logger) {
|
|
|
43296
43556
|
}
|
|
43297
43557
|
function listPidsOnPort(port) {
|
|
43298
43558
|
return new Promise((resolve6, reject) => {
|
|
43299
|
-
(0,
|
|
43559
|
+
(0, import_node_child_process12.execFile)(
|
|
43300
43560
|
"lsof",
|
|
43301
43561
|
["-ti", `:${port}`],
|
|
43302
43562
|
{ timeout: 3e3 },
|
|
@@ -43368,9 +43628,9 @@ var PublishJobRegistry = class {
|
|
|
43368
43628
|
};
|
|
43369
43629
|
|
|
43370
43630
|
// src/app-builder/publish-job-runner.ts
|
|
43371
|
-
var
|
|
43372
|
-
var
|
|
43373
|
-
var
|
|
43631
|
+
var import_node_child_process13 = require("child_process");
|
|
43632
|
+
var import_node_fs31 = require("fs");
|
|
43633
|
+
var import_node_path38 = require("path");
|
|
43374
43634
|
|
|
43375
43635
|
// src/app-builder/publish-stage-parser.ts
|
|
43376
43636
|
var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
|
|
@@ -43397,19 +43657,19 @@ function tailStderrLines(buf, n) {
|
|
|
43397
43657
|
// src/app-builder/publish-job-runner.ts
|
|
43398
43658
|
async function startPublishJob(deps, args) {
|
|
43399
43659
|
const { registry: registry2, projectDir } = deps;
|
|
43400
|
-
const spawn12 = deps.spawnImpl ??
|
|
43660
|
+
const spawn12 = deps.spawnImpl ?? import_node_child_process13.spawn;
|
|
43401
43661
|
if (registry2.has(args.name)) {
|
|
43402
43662
|
return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
|
|
43403
43663
|
}
|
|
43404
43664
|
const projDir = projectDir(args.name);
|
|
43405
|
-
const logPath = (0,
|
|
43665
|
+
const logPath = (0, import_node_path38.join)(projDir, ".publish.log");
|
|
43406
43666
|
let logStream = null;
|
|
43407
43667
|
try {
|
|
43408
|
-
logStream = (0,
|
|
43668
|
+
logStream = (0, import_node_fs31.createWriteStream)(logPath, { flags: "w" });
|
|
43409
43669
|
} catch {
|
|
43410
43670
|
logStream = null;
|
|
43411
43671
|
}
|
|
43412
|
-
const child = spawn12("bash", [args.scriptPath, projDir], {
|
|
43672
|
+
const child = spawn12("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
|
|
43413
43673
|
cwd: projDir,
|
|
43414
43674
|
env: process.env,
|
|
43415
43675
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -43662,10 +43922,9 @@ async function recoverInterruptedJobs(deps) {
|
|
|
43662
43922
|
|
|
43663
43923
|
// src/handlers/app-builder.ts
|
|
43664
43924
|
init_protocol();
|
|
43665
|
-
var
|
|
43666
|
-
var
|
|
43925
|
+
var import_node_path39 = require("path");
|
|
43926
|
+
var import_node_fs32 = require("fs");
|
|
43667
43927
|
var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
|
|
43668
|
-
var PUBLISH_SCRIPT_REL = "extension-kit/scripts/publish.sh";
|
|
43669
43928
|
var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
|
|
43670
43929
|
async function recoverInterruptedPublishJobs(store, logger) {
|
|
43671
43930
|
await recoverInterruptedJobs({
|
|
@@ -43745,7 +44004,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
43745
44004
|
async function listAllUsersProjects() {
|
|
43746
44005
|
if (!deps.usersRoot || !deps.getStore) return [];
|
|
43747
44006
|
const getStore = deps.getStore;
|
|
43748
|
-
const userIds = await
|
|
44007
|
+
const userIds = await import_node_fs32.promises.readdir(deps.usersRoot).catch(() => []);
|
|
43749
44008
|
const perUser = await Promise.all(
|
|
43750
44009
|
userIds.map((uid) => getStore(uid).list().catch(() => []))
|
|
43751
44010
|
);
|
|
@@ -43821,7 +44080,9 @@ function buildAppBuilderHandlers(deps) {
|
|
|
43821
44080
|
const project = await userStore.create(f.name, reservedPorts);
|
|
43822
44081
|
try {
|
|
43823
44082
|
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
|
|
43824
|
-
const
|
|
44083
|
+
const templateSrcDir = (0, import_node_path39.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
|
|
44084
|
+
const scaffoldScript = (0, import_node_path39.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
|
|
44085
|
+
const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
|
|
43825
44086
|
deps.logger?.info("app-builder.scaffold.done", {
|
|
43826
44087
|
name: project.name,
|
|
43827
44088
|
stdout: scaffoldResult.stdout.slice(0, 200)
|
|
@@ -44040,11 +44301,13 @@ function buildAppBuilderHandlers(deps) {
|
|
|
44040
44301
|
});
|
|
44041
44302
|
await userStore.clearPublishJob(args.name);
|
|
44042
44303
|
}
|
|
44043
|
-
const
|
|
44304
|
+
const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
|
|
44305
|
+
const scriptPath = (0, import_node_path39.join)(deps.deployKitRoot, "scripts", "publish.sh");
|
|
44044
44306
|
deps.logger?.info("app-builder.publish.start", {
|
|
44045
44307
|
name: args.name,
|
|
44046
44308
|
sessionId: boundSession.sessionId,
|
|
44047
|
-
scriptPath
|
|
44309
|
+
scriptPath,
|
|
44310
|
+
personaRoot
|
|
44048
44311
|
});
|
|
44049
44312
|
const result = await startPublishJob(
|
|
44050
44313
|
{
|
|
@@ -44079,7 +44342,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
44079
44342
|
{
|
|
44080
44343
|
name: args.name,
|
|
44081
44344
|
sessionId: boundSession.sessionId,
|
|
44082
|
-
scriptPath
|
|
44345
|
+
scriptPath,
|
|
44346
|
+
personaRoot
|
|
44083
44347
|
}
|
|
44084
44348
|
);
|
|
44085
44349
|
return { response: { ...result } };
|
|
@@ -44134,7 +44398,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
44134
44398
|
|
|
44135
44399
|
// src/extension/registry.ts
|
|
44136
44400
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
44137
|
-
var
|
|
44401
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
44138
44402
|
async function loadAll(root) {
|
|
44139
44403
|
let entries;
|
|
44140
44404
|
try {
|
|
@@ -44147,13 +44411,13 @@ async function loadAll(root) {
|
|
|
44147
44411
|
for (const ent of entries) {
|
|
44148
44412
|
if (!ent.isDirectory()) continue;
|
|
44149
44413
|
if (ent.name.startsWith(".")) continue;
|
|
44150
|
-
records.push(await loadOne(
|
|
44414
|
+
records.push(await loadOne(import_node_path40.default.join(root, ent.name), ent.name));
|
|
44151
44415
|
}
|
|
44152
44416
|
records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
|
|
44153
44417
|
return records;
|
|
44154
44418
|
}
|
|
44155
44419
|
async function loadOne(dir, dirName) {
|
|
44156
|
-
const manifestPath =
|
|
44420
|
+
const manifestPath = import_node_path40.default.join(dir, "manifest.json");
|
|
44157
44421
|
let raw;
|
|
44158
44422
|
try {
|
|
44159
44423
|
raw = await import_promises8.default.readFile(manifestPath, "utf8");
|
|
@@ -44198,7 +44462,7 @@ async function loadOne(dir, dirName) {
|
|
|
44198
44462
|
|
|
44199
44463
|
// src/extension/uninstall.ts
|
|
44200
44464
|
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
44201
|
-
var
|
|
44465
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
44202
44466
|
var UninstallError = class extends Error {
|
|
44203
44467
|
constructor(code, message) {
|
|
44204
44468
|
super(message);
|
|
@@ -44207,7 +44471,7 @@ var UninstallError = class extends Error {
|
|
|
44207
44471
|
code;
|
|
44208
44472
|
};
|
|
44209
44473
|
async function uninstall(deps) {
|
|
44210
|
-
const dir =
|
|
44474
|
+
const dir = import_node_path41.default.join(deps.root, deps.extId);
|
|
44211
44475
|
try {
|
|
44212
44476
|
await import_promises9.default.access(dir);
|
|
44213
44477
|
} catch {
|
|
@@ -44277,228 +44541,16 @@ function buildMethodHandlers(deps) {
|
|
|
44277
44541
|
broadcastProjectUpdated: deps.broadcastProjectUpdated,
|
|
44278
44542
|
logger: deps.logger,
|
|
44279
44543
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
44280
|
-
// personaRoot
|
|
44281
|
-
//
|
|
44544
|
+
// personaRoot 仅作 resolvePersonaRoot 未注入时的兜底;deployKitRoot 为共享脚本根。
|
|
44545
|
+
// broadcastSessionFrame 用于失败回灌路径(runner 调 manager.send 后逐帧 dispatch 到 transport)。
|
|
44282
44546
|
personaRoot: deps.appBuilderPersonaRoot,
|
|
44547
|
+
deployKitRoot: deps.deployKitRoot,
|
|
44283
44548
|
resolvePersonaRoot: deps.resolvePersonaRoot,
|
|
44284
44549
|
broadcastSessionFrame: deps.broadcastSessionFrame
|
|
44285
44550
|
})
|
|
44286
44551
|
};
|
|
44287
44552
|
}
|
|
44288
44553
|
|
|
44289
|
-
// src/app-builder/project-store.ts
|
|
44290
|
-
var import_node_fs32 = require("fs");
|
|
44291
|
-
var import_node_child_process13 = require("child_process");
|
|
44292
|
-
var import_node_path41 = require("path");
|
|
44293
|
-
init_protocol();
|
|
44294
|
-
var PROJECTS_DIR = "projects";
|
|
44295
|
-
var META_FILE = ".clawd-project.json";
|
|
44296
|
-
var SCAFFOLD_SCRIPT_REL = "extension-kit/scripts/new-extension.sh";
|
|
44297
|
-
var DEFAULT_TEMPLATE = "nestjs-react";
|
|
44298
|
-
var SCAFFOLD_TIMEOUT_MS = 3e4;
|
|
44299
|
-
var ProjectStore = class {
|
|
44300
|
-
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
|
|
44301
|
-
constructor(root) {
|
|
44302
|
-
this.root = root;
|
|
44303
|
-
}
|
|
44304
|
-
root;
|
|
44305
|
-
/** projects/<name>/.clawd-project.json 路径 */
|
|
44306
|
-
metaPath(name) {
|
|
44307
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
|
|
44308
|
-
}
|
|
44309
|
-
/** projects/<name>/ 目录路径(cwd 用) */
|
|
44310
|
-
projectDir(name) {
|
|
44311
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name);
|
|
44312
|
-
}
|
|
44313
|
-
projectsRoot() {
|
|
44314
|
-
return (0, import_node_path41.join)(this.root, PROJECTS_DIR);
|
|
44315
|
-
}
|
|
44316
|
-
async list() {
|
|
44317
|
-
let entries;
|
|
44318
|
-
try {
|
|
44319
|
-
entries = await import_node_fs32.promises.readdir(this.projectsRoot());
|
|
44320
|
-
} catch (err) {
|
|
44321
|
-
if (err.code === "ENOENT") return [];
|
|
44322
|
-
throw err;
|
|
44323
|
-
}
|
|
44324
|
-
const out = [];
|
|
44325
|
-
for (const name of entries) {
|
|
44326
|
-
try {
|
|
44327
|
-
const raw = await import_node_fs32.promises.readFile(this.metaPath(name), "utf8");
|
|
44328
|
-
const json = JSON.parse(raw);
|
|
44329
|
-
let migrated = false;
|
|
44330
|
-
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
44331
|
-
json.devCommand = DEFAULT_DEV_COMMAND;
|
|
44332
|
-
migrated = true;
|
|
44333
|
-
}
|
|
44334
|
-
const parsed = ProjectMetadataSchema.safeParse(json);
|
|
44335
|
-
if (parsed.success) {
|
|
44336
|
-
out.push(parsed.data);
|
|
44337
|
-
if (migrated) {
|
|
44338
|
-
void import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
44339
|
-
});
|
|
44340
|
-
}
|
|
44341
|
-
}
|
|
44342
|
-
} catch {
|
|
44343
|
-
}
|
|
44344
|
-
}
|
|
44345
|
-
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
44346
|
-
}
|
|
44347
|
-
/**
|
|
44348
|
-
* @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
|
|
44349
|
-
* dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
|
|
44350
|
-
* 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
|
|
44351
|
-
*/
|
|
44352
|
-
async create(name, reservedPorts) {
|
|
44353
|
-
const existing = await this.list();
|
|
44354
|
-
if (existing.some((p2) => p2.name === name)) {
|
|
44355
|
-
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
44356
|
-
}
|
|
44357
|
-
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
44358
|
-
if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
|
|
44359
|
-
let port = -1;
|
|
44360
|
-
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
44361
|
-
if (!usedPorts.has(p2)) {
|
|
44362
|
-
port = p2;
|
|
44363
|
-
break;
|
|
44364
|
-
}
|
|
44365
|
-
}
|
|
44366
|
-
if (port < 0) {
|
|
44367
|
-
throw new Error(
|
|
44368
|
-
`port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
|
|
44369
|
-
);
|
|
44370
|
-
}
|
|
44371
|
-
const meta = {
|
|
44372
|
-
name,
|
|
44373
|
-
port,
|
|
44374
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44375
|
-
devCommand: DEFAULT_DEV_COMMAND
|
|
44376
|
-
};
|
|
44377
|
-
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
44378
|
-
if (!validated.success) {
|
|
44379
|
-
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
44380
|
-
}
|
|
44381
|
-
const dir = this.projectDir(name);
|
|
44382
|
-
await import_node_fs32.promises.mkdir(dir, { recursive: true });
|
|
44383
|
-
await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
44384
|
-
return meta;
|
|
44385
|
-
}
|
|
44386
|
-
/**
|
|
44387
|
-
* 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
|
|
44388
|
-
* 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
|
|
44389
|
-
* 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
|
|
44390
|
-
*
|
|
44391
|
-
* spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
|
|
44392
|
-
* 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
|
|
44393
|
-
*/
|
|
44394
|
-
async scaffold(name, template = DEFAULT_TEMPLATE, personaRoot = this.root) {
|
|
44395
|
-
const scriptPath = (0, import_node_path41.join)(personaRoot, SCAFFOLD_SCRIPT_REL);
|
|
44396
|
-
const destDir = this.projectDir(name);
|
|
44397
|
-
return await new Promise((resolve6, reject) => {
|
|
44398
|
-
const child = (0, import_node_child_process13.spawn)("bash", [scriptPath, name, template, destDir], {
|
|
44399
|
-
cwd: personaRoot,
|
|
44400
|
-
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
44401
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
44402
|
-
});
|
|
44403
|
-
let stdout = "";
|
|
44404
|
-
let stderr = "";
|
|
44405
|
-
child.stdout.on("data", (d) => {
|
|
44406
|
-
stdout += d.toString("utf8");
|
|
44407
|
-
});
|
|
44408
|
-
child.stderr.on("data", (d) => {
|
|
44409
|
-
stderr += d.toString("utf8");
|
|
44410
|
-
});
|
|
44411
|
-
const timer = setTimeout(() => {
|
|
44412
|
-
child.kill("SIGKILL");
|
|
44413
|
-
reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scriptPath} ${name} ${template}`));
|
|
44414
|
-
}, SCAFFOLD_TIMEOUT_MS);
|
|
44415
|
-
child.on("error", (err) => {
|
|
44416
|
-
clearTimeout(timer);
|
|
44417
|
-
reject(new Error(`scaffold spawn failed: ${err.message}`));
|
|
44418
|
-
});
|
|
44419
|
-
child.on("exit", (code, signal) => {
|
|
44420
|
-
clearTimeout(timer);
|
|
44421
|
-
if (code === 0) resolve6({ stdout, stderr });
|
|
44422
|
-
else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
|
|
44423
|
-
});
|
|
44424
|
-
});
|
|
44425
|
-
}
|
|
44426
|
-
async delete(name) {
|
|
44427
|
-
const dir = this.projectDir(name);
|
|
44428
|
-
await import_node_fs32.promises.rm(dir, { recursive: true, force: true });
|
|
44429
|
-
}
|
|
44430
|
-
async updatePort(name, newPort) {
|
|
44431
|
-
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
44432
|
-
throw new Error(
|
|
44433
|
-
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
44434
|
-
);
|
|
44435
|
-
}
|
|
44436
|
-
const list = await this.list();
|
|
44437
|
-
const target = list.find((p2) => p2.name === name);
|
|
44438
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
44439
|
-
const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
|
|
44440
|
-
if (conflict) {
|
|
44441
|
-
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
44442
|
-
}
|
|
44443
|
-
const updated = { ...target, port: newPort };
|
|
44444
|
-
await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
44445
|
-
return updated;
|
|
44446
|
-
}
|
|
44447
|
-
/**
|
|
44448
|
-
* 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
|
|
44449
|
-
* UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
|
|
44450
|
-
* 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
|
|
44451
|
-
*/
|
|
44452
|
-
async setProdUrl(name, url) {
|
|
44453
|
-
const list = await this.list();
|
|
44454
|
-
const target = list.find((p2) => p2.name === name);
|
|
44455
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
44456
|
-
const updated = { ...target, prodUrl: url };
|
|
44457
|
-
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
44458
|
-
if (!validated.success) {
|
|
44459
|
-
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
44460
|
-
}
|
|
44461
|
-
await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
44462
|
-
return validated.data;
|
|
44463
|
-
}
|
|
44464
|
-
/**
|
|
44465
|
-
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
|
|
44466
|
-
* 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
|
|
44467
|
-
* - daemon 收到第一个 ::stage::build marker 时写入
|
|
44468
|
-
* - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
|
|
44469
|
-
* - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
|
|
44470
|
-
* - 只有 daemon 崩才会留残骸
|
|
44471
|
-
* 写入时保留所有其他字段(prodUrl 等不受影响)。
|
|
44472
|
-
*/
|
|
44473
|
-
async setPublishJob(name, job) {
|
|
44474
|
-
const list = await this.list();
|
|
44475
|
-
const target = list.find((p2) => p2.name === name);
|
|
44476
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
44477
|
-
const updated = { ...target, publishJob: job };
|
|
44478
|
-
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
44479
|
-
if (!validated.success) {
|
|
44480
|
-
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
44481
|
-
}
|
|
44482
|
-
await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
44483
|
-
return validated.data;
|
|
44484
|
-
}
|
|
44485
|
-
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
44486
|
-
async clearPublishJob(name) {
|
|
44487
|
-
const list = await this.list();
|
|
44488
|
-
const target = list.find((p2) => p2.name === name);
|
|
44489
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
44490
|
-
if (!target.publishJob) return target;
|
|
44491
|
-
const { publishJob: _drop, ...rest } = target;
|
|
44492
|
-
void _drop;
|
|
44493
|
-
const validated = ProjectMetadataSchema.safeParse(rest);
|
|
44494
|
-
if (!validated.success) {
|
|
44495
|
-
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
44496
|
-
}
|
|
44497
|
-
await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
44498
|
-
return validated.data;
|
|
44499
|
-
}
|
|
44500
|
-
};
|
|
44501
|
-
|
|
44502
44554
|
// src/app-builder/dev-server-supervisor.ts
|
|
44503
44555
|
var import_node_child_process14 = require("child_process");
|
|
44504
44556
|
var import_node_events2 = require("events");
|
|
@@ -45430,6 +45482,13 @@ async function startDaemon(config) {
|
|
|
45430
45482
|
} else {
|
|
45431
45483
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
45432
45484
|
}
|
|
45485
|
+
const deployKitBundleRoot = findDeployKitRoot();
|
|
45486
|
+
if (deployKitBundleRoot) {
|
|
45487
|
+
seedDeployKit({ deployKitBundleRoot, dataDir: config.dataDir, logger });
|
|
45488
|
+
refreshDeployKit({ deployKitBundleRoot, dataDir: config.dataDir, logger });
|
|
45489
|
+
} else {
|
|
45490
|
+
logger.warn("deploy-kit.bundle-missing", {});
|
|
45491
|
+
}
|
|
45433
45492
|
migrateAgentsMirror({ store: personaStore, logger });
|
|
45434
45493
|
migrateCodexSandbox({ store: personaStore, logger });
|
|
45435
45494
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
@@ -45755,7 +45814,9 @@ async function startDaemon(config) {
|
|
|
45755
45814
|
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
45756
45815
|
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
45757
45816
|
appBuilderPersonaRoot: import_node_path45.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
45758
|
-
//
|
|
45817
|
+
// 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
|
|
45818
|
+
deployKitRoot: import_node_path45.default.join(config.dataDir, "deploy-kit"),
|
|
45819
|
+
// scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
|
|
45759
45820
|
resolvePersonaRoot: (personaId) => import_node_path45.default.join(config.dataDir, "personas", personaId),
|
|
45760
45821
|
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
45761
45822
|
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|