@clawos-dev/clawd 0.2.512 → 0.2.513
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/app-builder-plugin/mcp-server.cjs +681 -1039
- package/dist/cli.cjs +283 -2144
- package/dist/deploy-kit/scripts/publish.sh +1 -1
- package/dist/dispatch/mcp-server.cjs +1 -238
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +85 -245
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +1 -3
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +18 -80
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +5 -16
- package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +28 -37
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/package.json +1 -3
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/main.ts +18 -80
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/vite.config.js +5 -16
- package/dist/rpc-tool/mcp-server.cjs +1 -1
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +1 -1
- package/dist/share-ui/assets/guest--nOZRIff.js +594 -0
- package/dist/share-ui/assets/guest-UkS4T7xe.css +32 -0
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
- package/dist/share-ui/assets/guest-CQRWDJBR.css +0 -32
- package/dist/share-ui/assets/guest-D9IrvPIs.js +0 -599
|
@@ -3227,8 +3227,8 @@ var require_utils = __commonJS({
|
|
|
3227
3227
|
}
|
|
3228
3228
|
return ind;
|
|
3229
3229
|
}
|
|
3230
|
-
function removeDotSegments(
|
|
3231
|
-
let input =
|
|
3230
|
+
function removeDotSegments(path4) {
|
|
3231
|
+
let input = path4;
|
|
3232
3232
|
const output = [];
|
|
3233
3233
|
let nextSlash = -1;
|
|
3234
3234
|
let len = 0;
|
|
@@ -3480,8 +3480,8 @@ var require_schemes = __commonJS({
|
|
|
3480
3480
|
wsComponent.secure = void 0;
|
|
3481
3481
|
}
|
|
3482
3482
|
if (wsComponent.resourceName) {
|
|
3483
|
-
const [
|
|
3484
|
-
wsComponent.path =
|
|
3483
|
+
const [path4, query] = wsComponent.resourceName.split("?");
|
|
3484
|
+
wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
|
|
3485
3485
|
wsComponent.query = query;
|
|
3486
3486
|
wsComponent.resourceName = void 0;
|
|
3487
3487
|
}
|
|
@@ -4453,13 +4453,13 @@ var require_core = __commonJS({
|
|
|
4453
4453
|
}, warn() {
|
|
4454
4454
|
}, error() {
|
|
4455
4455
|
} };
|
|
4456
|
-
function getLogger(
|
|
4457
|
-
if (
|
|
4456
|
+
function getLogger(logger2) {
|
|
4457
|
+
if (logger2 === false)
|
|
4458
4458
|
return noLogs;
|
|
4459
|
-
if (
|
|
4459
|
+
if (logger2 === void 0)
|
|
4460
4460
|
return console;
|
|
4461
|
-
if (
|
|
4462
|
-
return
|
|
4461
|
+
if (logger2.log && logger2.warn && logger2.error)
|
|
4462
|
+
return logger2;
|
|
4463
4463
|
throw new Error("logger must implement log, warn and error methods");
|
|
4464
4464
|
}
|
|
4465
4465
|
var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
|
|
@@ -6874,12 +6874,12 @@ var require_dist = __commonJS({
|
|
|
6874
6874
|
throw new Error(`Unknown format "${name}"`);
|
|
6875
6875
|
return f;
|
|
6876
6876
|
};
|
|
6877
|
-
function addFormats(ajv, list,
|
|
6877
|
+
function addFormats(ajv, list, fs7, exportName) {
|
|
6878
6878
|
var _a;
|
|
6879
6879
|
var _b;
|
|
6880
6880
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6881
6881
|
for (const f of list)
|
|
6882
|
-
ajv.addFormat(f,
|
|
6882
|
+
ajv.addFormat(f, fs7[f]);
|
|
6883
6883
|
}
|
|
6884
6884
|
module2.exports = exports2 = formatsPlugin;
|
|
6885
6885
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -6894,15 +6894,11 @@ __export(mcp_server_exports, {
|
|
|
6894
6894
|
handleListProjects: () => handleListProjects,
|
|
6895
6895
|
handlePublish: () => handlePublish,
|
|
6896
6896
|
handleRemoveProject: () => handleRemoveProject,
|
|
6897
|
-
handleReportStage: () => handleReportStage,
|
|
6898
|
-
handleSetProdUrl: () => handleSetProdUrl,
|
|
6899
|
-
handleStartDevServer: () => handleStartDevServer,
|
|
6900
|
-
handleUpdateProjectPort: () => handleUpdateProjectPort,
|
|
6901
6897
|
readEnv: () => readEnv,
|
|
6902
6898
|
registerTools: () => registerTools
|
|
6903
6899
|
});
|
|
6904
6900
|
module.exports = __toCommonJS(mcp_server_exports);
|
|
6905
|
-
var
|
|
6901
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
6906
6902
|
|
|
6907
6903
|
// ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
6908
6904
|
var external_exports = {};
|
|
@@ -7382,8 +7378,8 @@ function getErrorMap() {
|
|
|
7382
7378
|
|
|
7383
7379
|
// ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
7384
7380
|
var makeIssue = (params) => {
|
|
7385
|
-
const { data, path, errorMaps, issueData } = params;
|
|
7386
|
-
const fullPath = [...
|
|
7381
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
7382
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
7387
7383
|
const fullIssue = {
|
|
7388
7384
|
...issueData,
|
|
7389
7385
|
path: fullPath
|
|
@@ -7499,11 +7495,11 @@ var errorUtil;
|
|
|
7499
7495
|
|
|
7500
7496
|
// ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
7501
7497
|
var ParseInputLazyPath = class {
|
|
7502
|
-
constructor(parent, value,
|
|
7498
|
+
constructor(parent, value, path4, key) {
|
|
7503
7499
|
this._cachedPath = [];
|
|
7504
7500
|
this.parent = parent;
|
|
7505
7501
|
this.data = value;
|
|
7506
|
-
this._path =
|
|
7502
|
+
this._path = path4;
|
|
7507
7503
|
this._key = key;
|
|
7508
7504
|
}
|
|
7509
7505
|
get path() {
|
|
@@ -11140,10 +11136,10 @@ function assignProp(target, prop, value) {
|
|
|
11140
11136
|
configurable: true
|
|
11141
11137
|
});
|
|
11142
11138
|
}
|
|
11143
|
-
function getElementAtPath(obj,
|
|
11144
|
-
if (!
|
|
11139
|
+
function getElementAtPath(obj, path4) {
|
|
11140
|
+
if (!path4)
|
|
11145
11141
|
return obj;
|
|
11146
|
-
return
|
|
11142
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
11147
11143
|
}
|
|
11148
11144
|
function promiseAllObject(promisesObj) {
|
|
11149
11145
|
const keys = Object.keys(promisesObj);
|
|
@@ -11463,11 +11459,11 @@ function aborted(x, startIndex = 0) {
|
|
|
11463
11459
|
}
|
|
11464
11460
|
return false;
|
|
11465
11461
|
}
|
|
11466
|
-
function prefixIssues(
|
|
11462
|
+
function prefixIssues(path4, issues) {
|
|
11467
11463
|
return issues.map((iss) => {
|
|
11468
11464
|
var _a;
|
|
11469
11465
|
(_a = iss).path ?? (_a.path = []);
|
|
11470
|
-
iss.path.unshift(
|
|
11466
|
+
iss.path.unshift(path4);
|
|
11471
11467
|
return iss;
|
|
11472
11468
|
});
|
|
11473
11469
|
}
|
|
@@ -21116,11 +21112,6 @@ var StdioServerTransport = class {
|
|
|
21116
21112
|
}
|
|
21117
21113
|
};
|
|
21118
21114
|
|
|
21119
|
-
// src/app-builder-plugin/project-store.ts
|
|
21120
|
-
var import_node_fs = require("fs");
|
|
21121
|
-
var import_node_child_process = require("child_process");
|
|
21122
|
-
var import_node_path = require("path");
|
|
21123
|
-
|
|
21124
21115
|
// ../protocol/src/attachment-schemas.ts
|
|
21125
21116
|
var TOKEN_ROLES = ["owner"];
|
|
21126
21117
|
var GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
|
|
@@ -21532,6 +21523,7 @@ var DeployFinishScanArgsSchema = external_exports.object({
|
|
|
21532
21523
|
personaId: external_exports.string().min(1),
|
|
21533
21524
|
notes: external_exports.array(external_exports.string()).default([])
|
|
21534
21525
|
}).strict();
|
|
21526
|
+
var PERSONA_REPO_ORG = "ottin4ttc";
|
|
21535
21527
|
|
|
21536
21528
|
// ../protocol/src/contact-remote-access.ts
|
|
21537
21529
|
var ContactSetRemoteAccessArgsSchema = external_exports.object({
|
|
@@ -22919,10 +22911,6 @@ var SessionFileSchema = external_exports.object({
|
|
|
22919
22911
|
* 本机资源指针——mirror 上与 ownerPersonaId 同地点 strip(#750 防线)。
|
|
22920
22912
|
*/
|
|
22921
22913
|
projectPath: external_exports.string().min(1).optional(),
|
|
22922
|
-
// app-builder Project 反向索引(spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md)。
|
|
22923
|
-
// 当此 session 绑定到某个 build 中的 project 时填写;name 与 projects/<name>/ 子目录同名,
|
|
22924
|
-
// 受 projectNameSchema regex 约束。daemon session:resume 时校验该目录存在,不存在则拒。
|
|
22925
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
|
|
22926
22914
|
// listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
|
|
22927
22915
|
// 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
|
|
22928
22916
|
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
@@ -23257,10 +23245,6 @@ var SessionCreateArgs = external_exports.object({
|
|
|
23257
23245
|
// owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
|
|
23258
23246
|
// 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
|
|
23259
23247
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
23260
|
-
// 单栏默认 refactor (spec 2026-06-02):appBuilderProject 字段已从 SessionCreateArgs 砍掉。
|
|
23261
|
-
// 绑定路径收敛到 appBuilder:createProject —— 先创建空 session(仅传 ownerPersonaId=persona-app-builder),
|
|
23262
|
-
// 后通过 createProject RPC 创建 project 并 daemon 内部 mutate 写入 appBuilderProject。
|
|
23263
|
-
// SessionFile schema 仍保留该字段供 daemon 内部 mutate(manager.update 内部调用通道)。
|
|
23264
23248
|
/**
|
|
23265
23249
|
* 新建会话选择的 project(spec 2026-08-17-projects-sidebar §4.2):
|
|
23266
23250
|
* - 单独出现:daemon 以它为 cwd(等价传 cwd)
|
|
@@ -23347,9 +23331,6 @@ var SessionUpdateArgs = external_exports.object({
|
|
|
23347
23331
|
// 从所有 sidebar 消费者视野消失(App.visibleSessions / buildSessionTree),用户看不见
|
|
23348
23332
|
// 也删不掉,再被 sweepEphemeralOnStartup 静默删除——数据丢失级别的口子。
|
|
23349
23333
|
ephemeral: external_exports.literal(false).optional()
|
|
23350
|
-
// 注意:appBuilderProject 字段已从 patch 中砍掉(spec 2026-06-02-app-builder-single-pane-default-design)。
|
|
23351
|
-
// 绑定单向不可变 —— 由 appBuilder:createProject 在 daemon 内部一次写入;UI / assistant 不再
|
|
23352
|
-
// 通过 sessionUpdate 修改这个字段。SessionFile 仍保留 appBuilderProject 字段供 daemon 内部 mutate。
|
|
23353
23334
|
})
|
|
23354
23335
|
});
|
|
23355
23336
|
var QueuedMessage = external_exports.object({
|
|
@@ -23696,189 +23677,6 @@ var InfoResponseSchema = external_exports.object({
|
|
|
23696
23677
|
* 探测见 daemon/src/cli-probe/probe.ts。旧 daemon 不发 → undefined。 */
|
|
23697
23678
|
globalCliVersion: external_exports.string().nullable().optional()
|
|
23698
23679
|
});
|
|
23699
|
-
var PROJECT_PORT_MIN = 6173;
|
|
23700
|
-
var PROJECT_PORT_MAX = 6272;
|
|
23701
|
-
var projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
|
|
23702
|
-
var DEFAULT_DEV_COMMAND = "cd server && pnpm dev";
|
|
23703
|
-
var ProjectMetadataSchema = external_exports.object({
|
|
23704
|
-
name: external_exports.string().regex(projectNameRegex, {
|
|
23705
|
-
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
23706
|
-
}),
|
|
23707
|
-
port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
|
|
23708
|
-
createdAt: external_exports.string().datetime(),
|
|
23709
|
-
/** dev server 启动命令(shell 字符串);占位符 $CLAWD_PREVIEW_PORT 替换 + env 注入 */
|
|
23710
|
-
devCommand: external_exports.string().min(1),
|
|
23711
|
-
/**
|
|
23712
|
-
* 发布上线后的公网 URL(spec 2026-06-02-app-builder-single-pane-default-design §5.6.1)。
|
|
23713
|
-
* assistant 跑完 publish.sh 后调 appBuilder:setProdUrl 写入;UI 读这个字段切预览栏到线上 URL,
|
|
23714
|
-
* 取代旧 `<builder-preview url=... label="prod" />` marker 机制。
|
|
23715
|
-
* 老 project 元数据无此字段(optional 兼容)。
|
|
23716
|
-
*/
|
|
23717
|
-
prodUrl: external_exports.string().url().optional(),
|
|
23718
|
-
/**
|
|
23719
|
-
* dev server 就绪 stdout regex(spec 2026-06-02-app-builder-explicit-dev-server-control §2)。
|
|
23720
|
-
* supervisor spawn 后会监听子进程 stdout,命中此 regex 视为 dev server 真 ready;
|
|
23721
|
-
* 仅当 await ready 后才把 stage 切到 'running',避免 cold start race 导致 iframe 502。
|
|
23722
|
-
* optional:缺省时 supervisor 用默认 /Local:\s+https?:\/\//(vite "Local: http://..." 输出)。
|
|
23723
|
-
* 老板换非 vite 技术栈(Next.js "Ready in"、webpack "Compiled" 等)时自定义这个字段。
|
|
23724
|
-
*/
|
|
23725
|
-
readyPattern: external_exports.string().optional(),
|
|
23726
|
-
/**
|
|
23727
|
-
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
|
|
23728
|
-
* in-flight 发布 job 的元数据。仅承载"in-flight"或"中断残骸"两种状态:
|
|
23729
|
-
* - daemon spawn publish.sh + 收到第一个 ::stage::build marker 时写入
|
|
23730
|
-
* - 每次 stage 切换更新 stage 字段
|
|
23731
|
-
* - 成功 / 正常失败 / 用户 dismiss 时立即清掉
|
|
23732
|
-
* - 只有 daemon 崩才会留残骸(子进程随 daemon 一起死)
|
|
23733
|
-
* status 字段在 ProjectWithStatus 派生而不在这里 —— 本 schema 是落盘形态,
|
|
23734
|
-
* "in-flight vs interrupted" 区分由 daemon 响应时实时拼 registry 状态得出。
|
|
23735
|
-
*/
|
|
23736
|
-
publishJob: external_exports.object({
|
|
23737
|
-
jobId: external_exports.string().min(1),
|
|
23738
|
-
stage: external_exports.enum(["build", "deploy", "verify"]),
|
|
23739
|
-
startedAt: external_exports.number().int().nonnegative()
|
|
23740
|
-
}).optional()
|
|
23741
|
-
});
|
|
23742
|
-
var PROJECT_STAGE_VALUES = [
|
|
23743
|
-
"install-pending",
|
|
23744
|
-
// createProject 刚返回,scaffold 已完成(daemon 同步内联跑),等 assistant pnpm install
|
|
23745
|
-
"installing",
|
|
23746
|
-
// assistant pnpm install 中
|
|
23747
|
-
"starting-dev-server",
|
|
23748
|
-
// startDevServer 已调,supervisor spawn 子进程中(await ready 期间也在此 stage)
|
|
23749
|
-
"running",
|
|
23750
|
-
// dev server 跑起来了(supervisor stdout 命中 readyPattern + 端口已监听)
|
|
23751
|
-
"stopped",
|
|
23752
|
-
// 用户显式点"收起预览"停掉 / daemon 重启后默认;UI 渲染折叠条 + "展开预览"按钮
|
|
23753
|
-
"failed"
|
|
23754
|
-
// 某个阶段失败(stageReason 字段携带原因,含 dev server cold start timeout)
|
|
23755
|
-
];
|
|
23756
|
-
var ProjectStageSchema = external_exports.enum(PROJECT_STAGE_VALUES);
|
|
23757
|
-
var ProjectWithStatusSchema = ProjectMetadataSchema.extend({
|
|
23758
|
-
isRunning: external_exports.boolean(),
|
|
23759
|
-
boundSessionId: external_exports.string().nullable(),
|
|
23760
|
-
/**
|
|
23761
|
-
* 单栏 refactor stage fix (spec 2026-06-02 §5.6.2):daemon 内存维护的当前 stage。
|
|
23762
|
-
* UI 据此切 PreviewPane spinner / iframe / error。
|
|
23763
|
-
* optional 兼容启动后 daemon 还没有 stage 记录的 fresh project(按 isRunning 推断兜底)。
|
|
23764
|
-
*/
|
|
23765
|
-
stage: ProjectStageSchema.optional(),
|
|
23766
|
-
/** stage='failed' 时携带失败原因 */
|
|
23767
|
-
stageReason: external_exports.string().optional(),
|
|
23768
|
-
/**
|
|
23769
|
-
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.3):
|
|
23770
|
-
* ProjectMetadata.publishJob 的 status 派生版。daemon 响应时按以下规则实时拼装:
|
|
23771
|
-
* - 落盘字段存在 + PublishJobRegistry 有匹配 jobId → 'in-flight'
|
|
23772
|
-
* - 落盘字段存在 + Registry 无匹配(jobId 对不上或 entry 缺失)→ 'interrupted'(daemon 崩残骸)
|
|
23773
|
-
* - 落盘字段不存在 → undefined(字段省略)
|
|
23774
|
-
* UI 用 status 决定 PreviewPane 渲染分支(in-flight 三段 checklist / interrupted 恢复条)。
|
|
23775
|
-
*/
|
|
23776
|
-
publishJob: external_exports.object({
|
|
23777
|
-
jobId: external_exports.string().min(1),
|
|
23778
|
-
stage: external_exports.enum(["build", "deploy", "verify"]),
|
|
23779
|
-
startedAt: external_exports.number().int().nonnegative(),
|
|
23780
|
-
status: external_exports.enum(["in-flight", "interrupted"])
|
|
23781
|
-
}).optional()
|
|
23782
|
-
});
|
|
23783
|
-
var ListProjectsArgsSchema = external_exports.object({}).strict();
|
|
23784
|
-
var ListProjectsResultSchema = external_exports.object({
|
|
23785
|
-
projects: external_exports.array(ProjectWithStatusSchema)
|
|
23786
|
-
}).strict();
|
|
23787
|
-
var GetProjectArgsSchema = external_exports.object({
|
|
23788
|
-
sessionId: external_exports.string().min(1)
|
|
23789
|
-
}).strict();
|
|
23790
|
-
var GetProjectResultSchema = external_exports.object({
|
|
23791
|
-
/** session 未绑 project / project 不存在时为 null(UI 走单栏 / 折叠) */
|
|
23792
|
-
project: ProjectWithStatusSchema.nullable()
|
|
23793
|
-
}).strict();
|
|
23794
|
-
var CreateProjectArgsSchema = external_exports.object({
|
|
23795
|
-
/** 必填:caller session,daemon 自动绑过去(spec 2026-06-02 §3 invariant 1 Session ↔ 1 Project) */
|
|
23796
|
-
sessionId: external_exports.string().min(1),
|
|
23797
|
-
// .min(1):改造前手写校验是 `!f.name` 明确拒空串;空 name 会让 projectDir('')
|
|
23798
|
-
// 落到 projects 根目录,scaffold 往根上铺模板。
|
|
23799
|
-
name: external_exports.string().min(1)
|
|
23800
|
-
}).strict();
|
|
23801
|
-
var CreateProjectResultSchema = external_exports.object({
|
|
23802
|
-
project: ProjectMetadataSchema
|
|
23803
|
-
}).strict();
|
|
23804
|
-
var DeleteProjectArgsSchema = external_exports.object({
|
|
23805
|
-
name: external_exports.string()
|
|
23806
|
-
}).strict();
|
|
23807
|
-
var DeleteProjectResultSchema = external_exports.object({}).strict();
|
|
23808
|
-
var UpdateProjectPortArgsSchema = external_exports.object({
|
|
23809
|
-
name: external_exports.string(),
|
|
23810
|
-
newPort: external_exports.number().int()
|
|
23811
|
-
}).strict();
|
|
23812
|
-
var UpdateProjectPortResultSchema = external_exports.object({
|
|
23813
|
-
project: ProjectMetadataSchema
|
|
23814
|
-
}).strict();
|
|
23815
|
-
var SetProdUrlArgsSchema = external_exports.object({
|
|
23816
|
-
name: external_exports.string().regex(projectNameRegex),
|
|
23817
|
-
url: external_exports.string().url()
|
|
23818
|
-
}).strict();
|
|
23819
|
-
var SetProdUrlResultSchema = external_exports.object({
|
|
23820
|
-
project: ProjectMetadataSchema
|
|
23821
|
-
}).strict();
|
|
23822
|
-
var StartDevServerArgsSchema = external_exports.object({
|
|
23823
|
-
sessionId: external_exports.string().min(1),
|
|
23824
|
-
force: external_exports.boolean().optional()
|
|
23825
|
-
}).strict();
|
|
23826
|
-
var StartDevServerResultSchema = external_exports.object({
|
|
23827
|
-
project: ProjectMetadataSchema
|
|
23828
|
-
}).strict();
|
|
23829
|
-
var ASSISTANT_REPORTABLE_STAGES = [
|
|
23830
|
-
"installing",
|
|
23831
|
-
"failed"
|
|
23832
|
-
];
|
|
23833
|
-
var ReportStageArgsSchema = external_exports.object({
|
|
23834
|
-
sessionId: external_exports.string().min(1),
|
|
23835
|
-
stage: external_exports.enum(ASSISTANT_REPORTABLE_STAGES),
|
|
23836
|
-
/** stage='failed' 时必填的原因(一句话),其它 stage optional */
|
|
23837
|
-
reason: external_exports.string().optional()
|
|
23838
|
-
}).strict();
|
|
23839
|
-
var ReportStageResultSchema = external_exports.object({
|
|
23840
|
-
ok: external_exports.literal(true)
|
|
23841
|
-
}).strict();
|
|
23842
|
-
var StopDevServerArgsSchema = external_exports.object({
|
|
23843
|
-
sessionId: external_exports.string().min(1)
|
|
23844
|
-
}).strict();
|
|
23845
|
-
var StopDevServerResultSchema = external_exports.object({
|
|
23846
|
-
ok: external_exports.literal(true)
|
|
23847
|
-
}).strict();
|
|
23848
|
-
var AppBuilderProjectUpdatedEventSchema = external_exports.object({
|
|
23849
|
-
type: external_exports.literal("appBuilder:project-updated"),
|
|
23850
|
-
project: ProjectWithStatusSchema,
|
|
23851
|
-
stage: ProjectStageSchema,
|
|
23852
|
-
/** stage='failed' 时的失败原因 */
|
|
23853
|
-
stageReason: external_exports.string().optional()
|
|
23854
|
-
}).strict();
|
|
23855
|
-
var PublishArgsSchema = external_exports.object({
|
|
23856
|
-
name: external_exports.string().regex(projectNameRegex)
|
|
23857
|
-
});
|
|
23858
|
-
var PublishResultSchema = external_exports.object({
|
|
23859
|
-
jobId: external_exports.string().min(1),
|
|
23860
|
-
status: external_exports.enum(["started", "already-publishing"])
|
|
23861
|
-
}).strict();
|
|
23862
|
-
var DismissPublishJobArgsSchema = external_exports.object({
|
|
23863
|
-
name: external_exports.string().regex(projectNameRegex)
|
|
23864
|
-
});
|
|
23865
|
-
var DismissPublishJobResultSchema = external_exports.object({
|
|
23866
|
-
ok: external_exports.literal(true)
|
|
23867
|
-
}).strict();
|
|
23868
|
-
var AppBuilderPublishProgressEventSchema = external_exports.object({
|
|
23869
|
-
type: external_exports.literal("appBuilder:publish-progress"),
|
|
23870
|
-
name: external_exports.string().regex(projectNameRegex),
|
|
23871
|
-
jobId: external_exports.string().min(1),
|
|
23872
|
-
stage: external_exports.enum(["build", "deploy", "verify"]),
|
|
23873
|
-
status: external_exports.enum(["started", "completed"])
|
|
23874
|
-
}).strict();
|
|
23875
|
-
var AppBuilderPublishFailedEventSchema = external_exports.object({
|
|
23876
|
-
type: external_exports.literal("appBuilder:publish-failed"),
|
|
23877
|
-
name: external_exports.string().regex(projectNameRegex),
|
|
23878
|
-
jobId: external_exports.string().min(1),
|
|
23879
|
-
stage: external_exports.enum(["build", "deploy", "verify", "unknown"]),
|
|
23880
|
-
errorSummary: external_exports.string()
|
|
23881
|
-
}).strict();
|
|
23882
23680
|
|
|
23883
23681
|
// ../protocol/src/methods.ts
|
|
23884
23682
|
var NO_ARGS = external_exports.object({});
|
|
@@ -23914,7 +23712,7 @@ var METHOD_DOCS = {
|
|
|
23914
23712
|
args: SessionUpdateArgs
|
|
23915
23713
|
},
|
|
23916
23714
|
"session:delete": {
|
|
23917
|
-
summary: "\u5220\u4F1A\u8BDD\uFF1A\u505C\u8FDB\u7A0B + \u5220 SessionFile
|
|
23715
|
+
summary: "\u5220\u4F1A\u8BDD\uFF1A\u505C\u8FDB\u7A0B + \u5220 SessionFile",
|
|
23918
23716
|
args: SessionIdArgs
|
|
23919
23717
|
},
|
|
23920
23718
|
"session:send": {
|
|
@@ -24505,50 +24303,6 @@ var METHOD_DOCS = {
|
|
|
24505
24303
|
"device:connections": {
|
|
24506
24304
|
summary: '\u5217\u51FA\u672C daemon \u5F53\u524D\u6240\u6709 owner WS \u8FDE\u63A5\uFF08\u672C\u673A UI / \u624B\u673A / \u7B2C\u4E8C\u53F0\u684C\u9762\uFF09+ \u5404\u81EA clientInfo\u3002owner-only\uFF1Bguest \u8FDE\u63A5\u4E0D\u5728\u5217\uFF08\u4E0D\u662F"\u6211\u7684\u8BBE\u5907"\u4E14\u4E0D\u5411 UI \u6CC4\u9732 guest \u4FE1\u606F\uFF09',
|
|
24507
24305
|
args: NO_ARGS
|
|
24508
|
-
},
|
|
24509
|
-
"appBuilder:listProjects": {
|
|
24510
|
-
summary: "\u5217\u51FA app-builder project\uFF08\u542B dev server \u72B6\u6001\uFF09",
|
|
24511
|
-
args: ListProjectsArgsSchema
|
|
24512
|
-
},
|
|
24513
|
-
"appBuilder:getProject": {
|
|
24514
|
-
summary: "\u6309 sessionId \u53D6\u5355\u4E2A\u7ED1\u5B9A\u7684 project\uFF08\u542B status\uFF09",
|
|
24515
|
-
args: GetProjectArgsSchema
|
|
24516
|
-
},
|
|
24517
|
-
"appBuilder:createProject": {
|
|
24518
|
-
summary: "\u7ED9\u4F1A\u8BDD\u7ED1\u5B9A\u65B0 project\uFF1A\u6821\u9A8C\u540D + \u4ECE PROJECT_PORT_MIN..MAX \u6BB5\uFF08\u5F53\u524D 6173-6272\uFF09\u5206\u914D\u672A\u5360\u7528\u7AEF\u53E3\uFF08\u8DE8 user \u5168\u5C40\u552F\u4E00\uFF0C\u6C60\u6EE1\u62A5\u9519\uFF09+ \u5EFA\u76EE\u5F55\u5199\u5143\u6570\u636E + **\u540C\u6B65 scaffold**\uFF08spawn \u811A\u672C\u628A nestjs-react \u6A21\u677F\u62F7\u8FDB\u53BB\uFF1B\u5931\u8D25\u4F1A\u5220\u6389\u521A\u5EFA\u7684\u76EE\u5F55\u518D\u629B INTERNAL\uFF0C\u4E0D\u7559\u534A\u6210\u54C1\uFF09\u3002**\u88C5\u4F9D\u8D56\u548C\u8D77 dev server \u90FD\u4E0D\u5728\u8FD9\u91CC**\uFF1A\u843D\u5730 stage=install-pending\uFF0C\u8C03\u7528\u65B9\u81EA\u5DF1 pnpm install + appBuilder:reportStage + appBuilder:startDevServer",
|
|
24519
|
-
args: CreateProjectArgsSchema
|
|
24520
|
-
},
|
|
24521
|
-
"appBuilder:deleteProject": {
|
|
24522
|
-
summary: "\u505C dev server + \u5220 project \u76EE\u5F55\uFF08\u7ED1\u5B9A\u7684\u4F1A\u8BDD\u4E0D\u5728\u8FD9\u91CC\u5220\uFF09",
|
|
24523
|
-
args: DeleteProjectArgsSchema
|
|
24524
|
-
},
|
|
24525
|
-
"appBuilder:updateProjectPort": {
|
|
24526
|
-
summary: "\u6539 dev server \u7AEF\u53E3\uFF08\u6821\u9A8C\u5728 PROJECT_PORT_MIN..MAX \u6BB5\u5185\u3001\u5F53\u524D 6173-6272\uFF1B**\u649E\u8F66\u53EA\u67E5\u8C03\u7528\u65B9\u81EA\u5DF1\u540D\u4E0B\u7684 project**\uFF0C\u4E0D\u50CF createProject \u90A3\u6837\u8DE8 user \u67E5\uFF09\uFF1A\u505C\u65E7\u7AEF\u53E3 dev server + \u6539 .clawd-project.json + \u7F6E stage=stopped\u3002**\u4E0D\u81EA\u52A8\u62C9\u8D77\u65B0\u7AEF\u53E3\u7684 dev server**\u2014\u2014\u8981\u8D77\u5F97\u518D\u8C03 appBuilder:startDevServer\uFF08\u90A3\u6761\u624D\u8D70 readiness probe\uFF09",
|
|
24527
|
-
args: UpdateProjectPortArgsSchema
|
|
24528
|
-
},
|
|
24529
|
-
"appBuilder:setProdUrl": {
|
|
24530
|
-
summary: "\u53D1\u5E03\u4E0A\u7EBF\u540E\u56DE\u586B\u7EBF\u4E0A\u5730\u5740\u5230 .clawd-project.json",
|
|
24531
|
-
args: SetProdUrlArgsSchema
|
|
24532
|
-
},
|
|
24533
|
-
"appBuilder:startDevServer": {
|
|
24534
|
-
summary: "scaffold + \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210\u540E\u663E\u5F0F\u62C9\u8D77 dev server\uFF08createProject \u4E0D\u518D\u81EA\u52A8\u8D77\uFF09",
|
|
24535
|
-
args: StartDevServerArgsSchema
|
|
24536
|
-
},
|
|
24537
|
-
"appBuilder:reportStage": {
|
|
24538
|
-
summary: "\u4E0A\u62A5\u811A\u624B\u67B6\u9636\u6BB5\uFF08scaffolding / installing / failed \u7B49\uFF09\uFF0Cdaemon \u5E7F\u64AD\u5E27\u8BA9 UI \u5207 PreviewPane",
|
|
24539
|
-
args: ReportStageArgsSchema
|
|
24540
|
-
},
|
|
24541
|
-
"appBuilder:stopDevServer": {
|
|
24542
|
-
summary: "\u6536\u8D77\u9884\u89C8\uFF1A\u505C\u6389 dev server\uFF08\u5BF9\u79F0 startDevServer\uFF09",
|
|
24543
|
-
args: StopDevServerArgsSchema
|
|
24544
|
-
},
|
|
24545
|
-
"appBuilder:publish": {
|
|
24546
|
-
summary: "\u53D1\u5E03\u4E0A\u7EBF\uFF1A\u4E32\u884C\u5316 spawn publish.sh \u5E76\u5E7F\u64AD\u9636\u6BB5\u8FDB\u5EA6\u5E27\uFF1B\u540C project \u5DF2\u6709 in-flight job \u65F6\u8FD4\u56DE already-publishing",
|
|
24547
|
-
args: PublishArgsSchema
|
|
24548
|
-
},
|
|
24549
|
-
"appBuilder:dismissPublishJob": {
|
|
24550
|
-
summary: "\u53D6\u6D88 in-flight publish\uFF08SIGTERM \u5B50\u8FDB\u7A0B\uFF09\u6216\u6E05\u6389 daemon \u5D29\u6E83\u7559\u4E0B\u7684\u6B8B\u9AB8 publishJob \u5B57\u6BB5",
|
|
24551
|
-
args: DismissPublishJobArgsSchema
|
|
24552
24306
|
}
|
|
24553
24307
|
};
|
|
24554
24308
|
var METHOD_NAMES = Object.keys(METHOD_DOCS);
|
|
@@ -24630,37 +24384,43 @@ var INNER_TAG_ESCAPE_RE = new RegExp(`<(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g")
|
|
|
24630
24384
|
var INNER_TAG_UNESCAPE_RE = new RegExp(`<\\\\(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
|
|
24631
24385
|
|
|
24632
24386
|
// src/app-builder-plugin/project-store.ts
|
|
24387
|
+
var import_node_fs = require("fs");
|
|
24388
|
+
var import_node_child_process = require("child_process");
|
|
24389
|
+
var import_node_path = require("path");
|
|
24633
24390
|
var PROJECTS_DIR = "projects";
|
|
24634
24391
|
var META_FILE = ".clawd-project.json";
|
|
24635
24392
|
var SCAFFOLD_TIMEOUT_MS = 3e4;
|
|
24636
|
-
|
|
24637
|
-
|
|
24638
|
-
|
|
24639
|
-
|
|
24640
|
-
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
);
|
|
24644
|
-
metaWriteQueues.set(metaPath, settled);
|
|
24645
|
-
void settled.then(() => {
|
|
24646
|
-
if (metaWriteQueues.get(metaPath) === settled) metaWriteQueues.delete(metaPath);
|
|
24647
|
-
});
|
|
24648
|
-
return next;
|
|
24393
|
+
async function exists(p) {
|
|
24394
|
+
try {
|
|
24395
|
+
await import_node_fs.promises.stat(p);
|
|
24396
|
+
return true;
|
|
24397
|
+
} catch {
|
|
24398
|
+
return false;
|
|
24399
|
+
}
|
|
24649
24400
|
}
|
|
24401
|
+
var PROJECT_NAME_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
24402
|
+
var AppProjectMetadataSchema = external_exports.object({
|
|
24403
|
+
name: external_exports.string().regex(PROJECT_NAME_RE, {
|
|
24404
|
+
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
24405
|
+
}),
|
|
24406
|
+
createdAt: external_exports.string().datetime(),
|
|
24407
|
+
/** createProject 建好的 GitHub 仓库地址(ottin4ttc/<name>-<slug>)。 */
|
|
24408
|
+
repoUrl: external_exports.string().url().optional(),
|
|
24409
|
+
/** 最近一次发布成功的公网地址。 */
|
|
24410
|
+
prodUrl: external_exports.string().url().optional(),
|
|
24411
|
+
publishedAt: external_exports.string().datetime().optional(),
|
|
24412
|
+
/** 发布那一刻的 HEAD;项目不是 git 仓库(老项目)时缺席。 */
|
|
24413
|
+
publishedCommit: external_exports.string().min(1).optional()
|
|
24414
|
+
});
|
|
24650
24415
|
var ProjectStore = class {
|
|
24651
|
-
/** @param root projects 父目录,例如 `~/.clawd/users/<userId
|
|
24416
|
+
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/` */
|
|
24652
24417
|
constructor(root) {
|
|
24653
24418
|
this.root = root;
|
|
24654
24419
|
}
|
|
24655
24420
|
root;
|
|
24656
|
-
serializeWrite(name, op) {
|
|
24657
|
-
return serializeMetaWrite(this.metaPath(name), op);
|
|
24658
|
-
}
|
|
24659
|
-
/** projects/<name>/.clawd-project.json 路径 */
|
|
24660
24421
|
metaPath(name) {
|
|
24661
24422
|
return (0, import_node_path.join)(this.projectsRoot(), name, META_FILE);
|
|
24662
24423
|
}
|
|
24663
|
-
/** projects/<name>/ 目录路径(cwd 用) */
|
|
24664
24424
|
projectDir(name) {
|
|
24665
24425
|
return (0, import_node_path.join)(this.projectsRoot(), name);
|
|
24666
24426
|
}
|
|
@@ -24677,73 +24437,40 @@ var ProjectStore = class {
|
|
|
24677
24437
|
}
|
|
24678
24438
|
const out = [];
|
|
24679
24439
|
for (const name of entries) {
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
const json = JSON.parse(raw);
|
|
24683
|
-
let migrated = false;
|
|
24684
|
-
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
24685
|
-
json.devCommand = DEFAULT_DEV_COMMAND;
|
|
24686
|
-
migrated = true;
|
|
24687
|
-
}
|
|
24688
|
-
const parsed = ProjectMetadataSchema.safeParse(json);
|
|
24689
|
-
if (parsed.success) {
|
|
24690
|
-
out.push(parsed.data);
|
|
24691
|
-
if (migrated) {
|
|
24692
|
-
void import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
24693
|
-
});
|
|
24694
|
-
}
|
|
24695
|
-
}
|
|
24696
|
-
} catch {
|
|
24697
|
-
}
|
|
24440
|
+
const meta = await this.readMeta(name);
|
|
24441
|
+
if (meta) out.push(meta);
|
|
24698
24442
|
}
|
|
24699
24443
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
24700
24444
|
}
|
|
24701
|
-
|
|
24702
|
-
|
|
24703
|
-
|
|
24704
|
-
|
|
24705
|
-
|
|
24706
|
-
|
|
24707
|
-
|
|
24708
|
-
|
|
24709
|
-
|
|
24710
|
-
|
|
24711
|
-
const usedPorts = new Set(existing.map((p) => p.port));
|
|
24712
|
-
if (reservedPorts) for (const p of reservedPorts) usedPorts.add(p);
|
|
24713
|
-
let port = -1;
|
|
24714
|
-
for (let p = PROJECT_PORT_MIN; p <= PROJECT_PORT_MAX; p++) {
|
|
24715
|
-
if (!usedPorts.has(p)) {
|
|
24716
|
-
port = p;
|
|
24717
|
-
break;
|
|
24718
|
-
}
|
|
24719
|
-
}
|
|
24720
|
-
if (port < 0) {
|
|
24721
|
-
throw new Error(
|
|
24722
|
-
`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`
|
|
24723
|
-
);
|
|
24445
|
+
async get(name) {
|
|
24446
|
+
return this.readMeta(name);
|
|
24447
|
+
}
|
|
24448
|
+
async readMeta(name) {
|
|
24449
|
+
try {
|
|
24450
|
+
const raw = await import_node_fs.promises.readFile(this.metaPath(name), "utf8");
|
|
24451
|
+
const parsed = AppProjectMetadataSchema.safeParse(JSON.parse(raw));
|
|
24452
|
+
return parsed.success ? parsed.data : void 0;
|
|
24453
|
+
} catch {
|
|
24454
|
+
return void 0;
|
|
24724
24455
|
}
|
|
24725
|
-
|
|
24726
|
-
|
|
24727
|
-
|
|
24728
|
-
|
|
24729
|
-
devCommand: DEFAULT_DEV_COMMAND
|
|
24730
|
-
};
|
|
24731
|
-
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
24456
|
+
}
|
|
24457
|
+
async create(name) {
|
|
24458
|
+
const meta = { name, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
24459
|
+
const validated = AppProjectMetadataSchema.safeParse(meta);
|
|
24732
24460
|
if (!validated.success) {
|
|
24733
24461
|
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
24734
24462
|
}
|
|
24735
|
-
|
|
24736
|
-
|
|
24737
|
-
|
|
24738
|
-
|
|
24463
|
+
if (await exists(this.projectDir(name))) {
|
|
24464
|
+
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728\uFF08\u76EE\u5F55 ${this.projectDir(name)} \u5DF2\u5728\uFF09`);
|
|
24465
|
+
}
|
|
24466
|
+
await import_node_fs.promises.mkdir(this.projectDir(name), { recursive: true });
|
|
24467
|
+
await this.write(validated.data);
|
|
24468
|
+
return validated.data;
|
|
24739
24469
|
}
|
|
24740
24470
|
/**
|
|
24741
|
-
* 同步内联 scaffold:spawn `
|
|
24742
|
-
* 必须在 `create()`
|
|
24743
|
-
* 失败抛错;上层(
|
|
24744
|
-
*
|
|
24745
|
-
* spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
|
|
24746
|
-
* 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
|
|
24471
|
+
* 同步内联 scaffold:spawn `new-extension.sh <name> <模板源> <项目目录>` 等 exit。
|
|
24472
|
+
* 必须在 `create()` 之后调(脚本要求目标目录已存在且只含 .clawd-project.json)。
|
|
24473
|
+
* 失败抛错;上层(core.createProject)catch 后回滚。
|
|
24747
24474
|
*/
|
|
24748
24475
|
async scaffold(name, templateSrcDir, scaffoldScriptPath, extraEnv) {
|
|
24749
24476
|
const destDir = this.projectDir(name);
|
|
@@ -24778,151 +24505,415 @@ var ProjectStore = class {
|
|
|
24778
24505
|
});
|
|
24779
24506
|
}
|
|
24780
24507
|
async delete(name) {
|
|
24781
|
-
|
|
24782
|
-
await import_node_fs.promises.rm(dir, { recursive: true, force: true });
|
|
24783
|
-
}
|
|
24784
|
-
async updatePort(name, newPort) {
|
|
24785
|
-
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
24786
|
-
throw new Error(
|
|
24787
|
-
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
24788
|
-
);
|
|
24789
|
-
}
|
|
24790
|
-
return await this.serializeWrite(name, async () => {
|
|
24791
|
-
const list = await this.list();
|
|
24792
|
-
const target = list.find((p) => p.name === name);
|
|
24793
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24794
|
-
const conflict = list.find((p) => p.name !== name && p.port === newPort);
|
|
24795
|
-
if (conflict) {
|
|
24796
|
-
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
24797
|
-
}
|
|
24798
|
-
const updated = { ...target, port: newPort };
|
|
24799
|
-
await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
24800
|
-
return updated;
|
|
24801
|
-
});
|
|
24508
|
+
await import_node_fs.promises.rm(this.projectDir(name), { recursive: true, force: true });
|
|
24802
24509
|
}
|
|
24803
|
-
|
|
24804
|
-
|
|
24805
|
-
* UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
|
|
24806
|
-
* 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
|
|
24807
|
-
*/
|
|
24808
|
-
async setProdUrl(name, url) {
|
|
24809
|
-
return await this.serializeWrite(name, async () => {
|
|
24810
|
-
const list = await this.list();
|
|
24811
|
-
const target = list.find((p) => p.name === name);
|
|
24812
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24813
|
-
const updated = { ...target, prodUrl: url };
|
|
24814
|
-
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
24815
|
-
if (!validated.success) {
|
|
24816
|
-
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
24817
|
-
}
|
|
24818
|
-
await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
24819
|
-
return validated.data;
|
|
24820
|
-
});
|
|
24510
|
+
async setRepoUrl(name, repoUrl) {
|
|
24511
|
+
return this.patch(name, { repoUrl });
|
|
24821
24512
|
}
|
|
24822
|
-
|
|
24823
|
-
|
|
24824
|
-
* 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
|
|
24825
|
-
* - daemon 收到第一个 ::stage::build marker 时写入
|
|
24826
|
-
* - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
|
|
24827
|
-
* - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
|
|
24828
|
-
* - 只有 daemon 崩才会留残骸
|
|
24829
|
-
* 写入时保留所有其他字段(prodUrl 等不受影响)。
|
|
24830
|
-
*/
|
|
24831
|
-
async setPublishJob(name, job) {
|
|
24832
|
-
return await this.serializeWrite(name, async () => {
|
|
24833
|
-
const list = await this.list();
|
|
24834
|
-
const target = list.find((p) => p.name === name);
|
|
24835
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24836
|
-
const updated = { ...target, publishJob: job };
|
|
24837
|
-
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
24838
|
-
if (!validated.success) {
|
|
24839
|
-
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
24840
|
-
}
|
|
24841
|
-
await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
24842
|
-
return validated.data;
|
|
24843
|
-
});
|
|
24513
|
+
async setPublished(name, fields) {
|
|
24514
|
+
return this.patch(name, fields);
|
|
24844
24515
|
}
|
|
24845
|
-
/**
|
|
24846
|
-
async
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
|
|
24855
|
-
|
|
24856
|
-
|
|
24857
|
-
|
|
24858
|
-
await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
24859
|
-
return validated.data;
|
|
24860
|
-
});
|
|
24516
|
+
/** 读全量 → 改几个字段 → 校验 → 整份写回。其他字段保持原样。 */
|
|
24517
|
+
async patch(name, fields) {
|
|
24518
|
+
const current = await this.readMeta(name);
|
|
24519
|
+
if (!current) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24520
|
+
const validated = AppProjectMetadataSchema.safeParse({ ...current, ...fields });
|
|
24521
|
+
if (!validated.success) {
|
|
24522
|
+
throw new Error(`invalid metadata for "${name}": ${validated.error.message}`);
|
|
24523
|
+
}
|
|
24524
|
+
await this.write(validated.data);
|
|
24525
|
+
return validated.data;
|
|
24526
|
+
}
|
|
24527
|
+
async write(meta) {
|
|
24528
|
+
await import_node_fs.promises.writeFile(this.metaPath(meta.name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
24861
24529
|
}
|
|
24862
24530
|
};
|
|
24863
24531
|
|
|
24864
24532
|
// src/app-builder-plugin/core.ts
|
|
24865
24533
|
var import_node_child_process3 = require("child_process");
|
|
24534
|
+
var import_node_fs3 = require("fs");
|
|
24535
|
+
var import_node_path5 = require("path");
|
|
24536
|
+
|
|
24537
|
+
// src/deploy/repo.ts
|
|
24538
|
+
var import_node_child_process2 = require("child_process");
|
|
24539
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
24540
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
24541
|
+
|
|
24542
|
+
// src/deploy/deploy-file.ts
|
|
24543
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
24544
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
24545
|
+
|
|
24546
|
+
// src/deploy/connectors.ts
|
|
24547
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
24548
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
24549
|
+
|
|
24550
|
+
// src/deploy/redact.ts
|
|
24551
|
+
var KNOWN_SECRET_SHAPES = [
|
|
24552
|
+
/LTAI[A-Za-z0-9]{6,}/,
|
|
24553
|
+
/eyJ[A-Za-z0-9_-]{10,}/,
|
|
24554
|
+
/sk-[A-Za-z0-9_-]{16,}/,
|
|
24555
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/,
|
|
24556
|
+
/AKIA[A-Z0-9]{12,}/
|
|
24557
|
+
];
|
|
24558
|
+
var BARE_SECRET = new RegExp(KNOWN_SECRET_SHAPES.map((re) => re.source).join("|"), "g");
|
|
24559
|
+
|
|
24560
|
+
// src/deploy/connectors.ts
|
|
24561
|
+
var CliConnectorFindingSchema = external_exports.object({
|
|
24562
|
+
name: ConnectorNameSchema,
|
|
24563
|
+
fields: external_exports.array(ConnectorFieldSchema).default([])
|
|
24564
|
+
});
|
|
24565
|
+
|
|
24566
|
+
// src/deploy/deploy-file.ts
|
|
24567
|
+
var DEPLOY_FILE = import_node_path3.default.join(".clawd", "deploy.json");
|
|
24568
|
+
var SCAN_FILE = import_node_path3.default.join(".clawd", "deploy-scan.json");
|
|
24569
|
+
|
|
24570
|
+
// src/deploy/templates.ts
|
|
24571
|
+
var CLAWOS_REPO = "ottin4ttc/clawos";
|
|
24572
|
+
var CLAWOS_REF = "release";
|
|
24573
|
+
var L = [
|
|
24574
|
+
"name: clawd deploy",
|
|
24575
|
+
"",
|
|
24576
|
+
"# \u7531 clawd \u751F\u6210\u3001\u7531 clawd \u7EF4\u62A4\uFF1A\u6BCF\u6B21\u300C\u53D1\u5E03\u300D\u90FD\u4F1A\u7528\u5F53\u524D\u6A21\u677F\u91CD\u5199\u8FD9\u4EFD\u6587\u4EF6\u518D\u63D0\u4EA4\uFF0C\u624B\u6539\u4F1A\u88AB\u76D6\u6389\u3002",
|
|
24577
|
+
"# \u4E09\u6BB5\uFF1Abuild \u6784 persona \u5C42\u955C\u50CF\u63A8 ACR \u2192 smoke \u5728\u96C6\u7FA4\u4E0A\u771F\u8D77\u4E00\u6B21 \u2192 deploy \u5728 gateway \u767B\u8BB0\u6210\u6B63\u5F0F\u3002",
|
|
24578
|
+
"# \u6784\u5EFA\u5668 / \u6D41\u6C34\u7EBF\u811A\u672C / \u968F\u884C\u8D44\u4EA7\u5728 ottin4ttc/clawos \u4ED3\u5E93\u91CC\uFF0C\u8FD9\u91CC checkout \u4E00\u4EFD\u6765\u7528\u3002",
|
|
24579
|
+
"#",
|
|
24580
|
+
"# \u8981\u7684\u7EC4\u7EC7\u7EA7 Secrets\uFF1AALI_DOCKER_USERNAME / ALI_DOCKER_PASSWORD\uFF08\u63A8 ACR\uFF09\u3001CLAWOS_READ_TOKEN",
|
|
24581
|
+
"#\uFF08\u8BFB clawos \u4ED3\u5E93\uFF09\u3001CLAWD_GATEWAY_ADMIN_KEY\uFF1B\u7EC4\u7EC7\u7EA7 Variable\uFF1ACLAWD_GATEWAY_URL\u3002",
|
|
24582
|
+
"# \u4ED3\u5E93\u7EA7\u540C\u540D\u914D\u7F6E\u4F1A\u76D6\u8FC7\u7EC4\u7EC7\u7EA7\u2014\u2014\u67D0\u4E2A persona \u8981\u6307\u5230\u53E6\u4E00\u53F0 gateway\uFF0C\u5728\u81EA\u5DF1\u4ED3\u5E93\u91CC\u914D\u5373\u53EF\u3002",
|
|
24583
|
+
"",
|
|
24584
|
+
"on:",
|
|
24585
|
+
" push:",
|
|
24586
|
+
" branches: [main]",
|
|
24587
|
+
" workflow_dispatch:",
|
|
24588
|
+
"",
|
|
24589
|
+
"concurrency:",
|
|
24590
|
+
" # \u540C\u4E00\u4E2A\u4ED3\u5E93\u7684\u53D1\u5E03\u6392\u961F\u8DD1\uFF0C\u4E0D\u53D6\u6D88\uFF1A\u4E0A\u4E00\u6B21\u53EF\u80FD\u5DF2\u7ECF\u5728\u767B\u8BB0\u6B63\u5F0F\u4E86",
|
|
24591
|
+
" group: persona-cloud-${{ github.repository }}",
|
|
24592
|
+
"",
|
|
24593
|
+
"env:",
|
|
24594
|
+
" REGISTRY: registry.cn-hangzhou.aliyuncs.com",
|
|
24595
|
+
" VPC_REGISTRY: registry-vpc.cn-hangzhou.aliyuncs.com",
|
|
24596
|
+
" IMAGE_NAME: ttc-prod/clawd-persona-cloud",
|
|
24597
|
+
` CLAWOS_REPO: ${CLAWOS_REPO}`,
|
|
24598
|
+
` CLAWOS_REF: ${CLAWOS_REF}`,
|
|
24599
|
+
"",
|
|
24600
|
+
"permissions:",
|
|
24601
|
+
" contents: read",
|
|
24602
|
+
"",
|
|
24603
|
+
"jobs:",
|
|
24604
|
+
" build:",
|
|
24605
|
+
// name 就是段 key:daemon 按 jobs API 回的 name 认段(那接口不返回 job 的 key)。
|
|
24606
|
+
// 中文说明归 clawd 的上云页面,这里保持机器好认。
|
|
24607
|
+
" name: build",
|
|
24608
|
+
" runs-on: ubuntu-latest",
|
|
24609
|
+
" timeout-minutes: 30",
|
|
24610
|
+
" outputs:",
|
|
24611
|
+
" tag: ${{ steps.plan.outputs.tag }}",
|
|
24612
|
+
" image: ${{ steps.plan.outputs.image }}",
|
|
24613
|
+
" persona_id: ${{ steps.plan.outputs.persona_id }}",
|
|
24614
|
+
" env_names: ${{ steps.plan.outputs.env_names }}",
|
|
24615
|
+
// 云上跑哪个模型 = 发布这一刻 persona 配的(build 段从仓库里的 .clawd/persona.json 读)。
|
|
24616
|
+
// smoke 与 deploy 都要:smoke 得用正式那份模型探活,否则验的不是将要上线的东西。
|
|
24617
|
+
" model: ${{ steps.plan.outputs.model }}",
|
|
24618
|
+
" effort: ${{ steps.plan.outputs.effort }}",
|
|
24619
|
+
// 装了哪些连接器(JSON 数组)。gateway 按它决定要不要为任务签工牌、把授权链接拼进 prompt
|
|
24620
|
+
//(design 2026-09-09-cloud-connector-credentials)。名字形状在 deploy-file.mjs 卡死成 [a-z0-9-],
|
|
24621
|
+
// 所以下面单引号包住直接拼进命令行是安全的。
|
|
24622
|
+
" connectors: ${{ steps.plan.outputs.connectors }}",
|
|
24623
|
+
" steps:",
|
|
24624
|
+
" - name: Checkout persona",
|
|
24625
|
+
" uses: actions/checkout@v4",
|
|
24626
|
+
" with:",
|
|
24627
|
+
" path: persona",
|
|
24628
|
+
"",
|
|
24629
|
+
" - name: Checkout clawos\uFF08\u6784\u5EFA\u5668 / \u6D41\u6C34\u7EBF\u811A\u672C / \u968F\u884C\u8D44\u4EA7\uFF09",
|
|
24630
|
+
" uses: actions/checkout@v4",
|
|
24631
|
+
" with:",
|
|
24632
|
+
" repository: ${{ env.CLAWOS_REPO }}",
|
|
24633
|
+
" ref: ${{ env.CLAWOS_REF }}",
|
|
24634
|
+
" token: ${{ secrets.CLAWOS_READ_TOKEN }}",
|
|
24635
|
+
" path: clawos",
|
|
24636
|
+
"",
|
|
24637
|
+
" - name: \u8BFB\u90E8\u7F72\u6587\u4EF6",
|
|
24638
|
+
" id: plan",
|
|
24639
|
+
" run: |",
|
|
24640
|
+
" node clawos/cloud-host/pipeline/deploy-file.mjs \\",
|
|
24641
|
+
" --persona-dir persona \\",
|
|
24642
|
+
" --repo-name '${{ github.event.repository.name }}' \\",
|
|
24643
|
+
" --sha '${{ github.sha }}' \\",
|
|
24644
|
+
' --image-repo "$VPC_REGISTRY/$IMAGE_NAME" \\',
|
|
24645
|
+
' --assets clawos/cloud-host/provision/manifests/assets.json >> "$GITHUB_OUTPUT"',
|
|
24646
|
+
"",
|
|
24647
|
+
" - name: \u51ED\u636E\u9F50\u4E0D\u9F50\uFF08\u7F3A\u54EA\u51E0\u4E2A\u540D\u5B57\u4E00\u6B21\u5217\u5168\uFF09",
|
|
24648
|
+
" env:",
|
|
24649
|
+
" CLAWD_SECRETS_JSON: ${{ toJSON(secrets) }}",
|
|
24650
|
+
" CLAWD_ENV_NAMES: ${{ steps.plan.outputs.env_names }}",
|
|
24651
|
+
" run: node clawos/cloud-host/pipeline/check-secrets.mjs",
|
|
24652
|
+
"",
|
|
24653
|
+
" - uses: pnpm/action-setup@v4",
|
|
24654
|
+
" if: steps.plan.outputs.asset_builds != ''",
|
|
24655
|
+
" with:",
|
|
24656
|
+
" version: 10",
|
|
24657
|
+
"",
|
|
24658
|
+
" - uses: actions/setup-node@v4",
|
|
24659
|
+
" if: steps.plan.outputs.asset_builds != ''",
|
|
24660
|
+
" with:",
|
|
24661
|
+
" node-version: 20",
|
|
24662
|
+
" cache: pnpm",
|
|
24663
|
+
" cache-dependency-path: clawos/clawd/pnpm-lock.yaml",
|
|
24664
|
+
"",
|
|
24665
|
+
" - name: \u6784\u968F\u884C\u8D44\u4EA7\uFF08\u6E05\u5355\u91CC\u70B9\u8FC7\u540D\u3001\u4E14\u662F\u4ED3\u5E93\u6784\u5EFA\u4EA7\u7269\u7684\u90A3\u4E9B\uFF09",
|
|
24666
|
+
" # \u547D\u4EE4\u6765\u81EA assets.json\uFF0C\u5B83\u81EA\u5DF1\u5E26\u88C5\u4F9D\u8D56\u90A3\u4E00\u6B65\u3002< /dev/null\uFF1A\u522B\u8BA9\u67D0\u6761\u547D\u4EE4\u628A here-string \u91CC\u5269\u4E0B\u7684\u884C\u5F53 stdin \u5403\u6389",
|
|
24667
|
+
" if: steps.plan.outputs.asset_builds != ''",
|
|
24668
|
+
" working-directory: clawos",
|
|
24669
|
+
" run: |",
|
|
24670
|
+
" set -e",
|
|
24671
|
+
" while IFS= read -r cmd; do",
|
|
24672
|
+
' [ -n "$cmd" ] || continue',
|
|
24673
|
+
' echo "$ $cmd"',
|
|
24674
|
+
' bash -c "$cmd" < /dev/null',
|
|
24675
|
+
" done <<< '${{ steps.plan.outputs.asset_builds }}'",
|
|
24676
|
+
"",
|
|
24677
|
+
" - name: Login to ACR",
|
|
24678
|
+
" uses: aliyun/acr-login@v1",
|
|
24679
|
+
" with:",
|
|
24680
|
+
" login-server: ${{ env.REGISTRY }}",
|
|
24681
|
+
' username: "${{ secrets.ALI_DOCKER_USERNAME }}"',
|
|
24682
|
+
' password: "${{ secrets.ALI_DOCKER_PASSWORD }}"',
|
|
24683
|
+
"",
|
|
24684
|
+
" - name: \u6784 persona \u5C42",
|
|
24685
|
+
" # \u6784\u5EFA\u5668\u6574\u76EE\u5F55\u62F7\uFF0C`.git` \u4E0D\u662F\u4EBA\u683C\uFF0C\u5148\u5220\u3002\u5E95\u5EA7\u7528 :base \u79FB\u52A8 tag\u2014\u2014runner \u6BCF\u6B21\u90FD\u662F\u5E72\u51C0\u673A\u5668\u3001",
|
|
24686
|
+
" # \u5FC5\u7136\u62C9\u6700\u65B0\uFF1Bpersona \u5C42\u81EA\u5DF1\u7684 tag \u5E26 commit sha\u3001\u4E0D\u53EF\u53D8\uFF0C\u96C6\u7FA4\u6309\u5B83\u62C9\u6CA1\u6709\u6B67\u4E49\u3002",
|
|
24687
|
+
" run: |",
|
|
24688
|
+
" rm -rf persona/.git",
|
|
24689
|
+
" node clawos/cloud-host/provision/build-image.mjs \\",
|
|
24690
|
+
" --manifest persona/.clawd/deploy.json \\",
|
|
24691
|
+
' --from "$REGISTRY/$IMAGE_NAME:base" \\',
|
|
24692
|
+
" --persona-dir persona \\",
|
|
24693
|
+
// persona 在镜像里落 /clawd/personas/<id>/(与本机同构),id 是 gateway 登记的名字;同 smoke / register 用的那个输出
|
|
24694
|
+
" --persona-id '${{ steps.plan.outputs.persona_id }}' \\",
|
|
24695
|
+
' --tag "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
|
|
24696
|
+
"",
|
|
24697
|
+
" - name: \u955C\u50CF\u65AD\u8A00\uFF08\u6E05\u5355\u91CC\u7684 CLI \u90FD\u5728\u3001bundle \u91CC persona \u5728\uFF09",
|
|
24698
|
+
" # /opt/versions.sh \u6BCF\u884C\u90FD\u4EE5 `|| echo` \u6536\u5C3E\u3001\u6C38\u8FDC exit 0\uFF0C\u53EA\u80FD\u9A8C\u8F93\u51FA\uFF1A\u6BB5\u6570\u8981\u7B49\u4E8E\u6E05\u5355\u91CC\u7684",
|
|
24699
|
+
" # CLI \u6570\uFF0C\u6BCF\u6BB5\u4E0B\u9762\u8981\u6709\u7248\u672C\u53F7\u3002\u955C\u50CF ENTRYPOINT \u662F sh -c\uFF0C\u547D\u4EE4\u8981\u4F5C\u4E3A\u4E00\u4E2A\u5B57\u7B26\u4E32\u4F20\u3002",
|
|
24700
|
+
" run: |",
|
|
24701
|
+
' IMG="$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
|
|
24702
|
+
` OUT=$(docker run --rm "$IMG" '/opt/versions.sh')`,
|
|
24703
|
+
' echo "$OUT"',
|
|
24704
|
+
` WANT=$(node -e 'const m=JSON.parse(require("fs").readFileSync("persona/.clawd/deploy.json","utf8"));console.log(((m.manifest??m).clis??[]).length)')`,
|
|
24705
|
+
` echo "$OUT" | awk -v want="$WANT" '`,
|
|
24706
|
+
' /^--- / { name=$2; if ((getline v) <= 0) v=""; n++',
|
|
24707
|
+
' if (v !~ /[0-9]+\\.[0-9]+/) { printf " \u2717 %s \u6CA1\u62A5\u51FA\u7248\u672C\u53F7\uFF1A%s\\n", name, v > "/dev/stderr"; bad++ } }',
|
|
24708
|
+
' END { if (n != want) { printf " \u2717 \u955C\u50CF\u91CC\u53EA\u6709 %d \u4E2A CLI \u6BB5\uFF0C\u6E05\u5355\u8981\u6C42 %d \u4E2A\\n", n, want > "/dev/stderr"; bad++ }',
|
|
24709
|
+
" exit bad ? 1 : 0 }'",
|
|
24710
|
+
' echo " \u2713 $WANT \u4E2A CLI \u90FD\u5728\u4E14\u62A5\u5F97\u51FA\u7248\u672C"',
|
|
24711
|
+
` docker run --rm "$IMG" 'test -d /clawd/personas/\${{ steps.plan.outputs.persona_id }}' \\`,
|
|
24712
|
+
' || { echo " \u2717 /clawd/personas/${{ steps.plan.outputs.persona_id }} \u4E0D\u5728" >&2; exit 1; }',
|
|
24713
|
+
' echo " \u2713 persona \u76EE\u5F55\u5728"',
|
|
24714
|
+
"",
|
|
24715
|
+
" - name: Push to ACR",
|
|
24716
|
+
' run: docker push "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
|
|
24717
|
+
"",
|
|
24718
|
+
" smoke:",
|
|
24719
|
+
" name: smoke",
|
|
24720
|
+
" needs: build",
|
|
24721
|
+
" runs-on: ubuntu-latest",
|
|
24722
|
+
" timeout-minutes: 20",
|
|
24723
|
+
" steps:",
|
|
24724
|
+
" - name: Checkout clawos\uFF08\u53EA\u8981\u6D41\u6C34\u7EBF\u811A\u672C\uFF09",
|
|
24725
|
+
" uses: actions/checkout@v4",
|
|
24726
|
+
" with:",
|
|
24727
|
+
" repository: ${{ env.CLAWOS_REPO }}",
|
|
24728
|
+
" ref: ${{ env.CLAWOS_REF }}",
|
|
24729
|
+
" token: ${{ secrets.CLAWOS_READ_TOKEN }}",
|
|
24730
|
+
" path: clawos",
|
|
24731
|
+
" sparse-checkout: cloud-host/pipeline",
|
|
24732
|
+
"",
|
|
24733
|
+
" - name: \u4E34\u65F6\u767B\u8BB0 \u2192 \u8D77\u4E00\u6B21 Pod \u2192 \u64A4\u9500",
|
|
24734
|
+
" # \u767B\u8BB0\u6B63\u5F0F\u53EA\u662F\u5F80 gateway \u5199\u4E00\u884C\uFF0Cgateway \u4E0D\u78B0\u955C\u50CF\uFF1B\u96C6\u7FA4\u62C9\u955C\u50CF\u53D1\u751F\u5728\u7B2C\u4E00\u4E2A\u4EFB\u52A1\u5230\u6765\u65F6\u3002",
|
|
24735
|
+
" # \u6CA1\u6709\u8FD9\u4E00\u6B65\uFF0C\u6D41\u6C34\u7EBF\u7EFF\u4E86\u53EA\u4EE3\u8868\u300C\u767B\u8BB0\u4E86\u300D\u3002",
|
|
24736
|
+
" env:",
|
|
24737
|
+
" CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
|
|
24738
|
+
" CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
|
|
24739
|
+
" CLAWD_SECRETS_JSON: ${{ toJSON(secrets) }}",
|
|
24740
|
+
" CLAWD_ENV_NAMES: ${{ needs.build.outputs.env_names }}",
|
|
24741
|
+
" run: |",
|
|
24742
|
+
" node clawos/cloud-host/pipeline/smoke.mjs \\",
|
|
24743
|
+
" --image '${{ needs.build.outputs.image }}' \\",
|
|
24744
|
+
" --persona-id '${{ needs.build.outputs.persona_id }}' \\",
|
|
24745
|
+
" --model '${{ needs.build.outputs.model }}' \\",
|
|
24746
|
+
" --effort '${{ needs.build.outputs.effort }}' \\",
|
|
24747
|
+
" --connectors '${{ needs.build.outputs.connectors }}' \\",
|
|
24748
|
+
" --run-number '${{ github.run_number }}'",
|
|
24749
|
+
"",
|
|
24750
|
+
" deploy:",
|
|
24751
|
+
" name: deploy",
|
|
24752
|
+
" needs: [build, smoke]",
|
|
24753
|
+
" runs-on: ubuntu-latest",
|
|
24754
|
+
" # \u4ED3\u5E93\u914D\u4E86 production \u73AF\u5883\u7684\u300C\u987B\u5BA1\u6838\u4EBA\u6279\u51C6\u300D\uFF0Cjob \u5C31\u505C\u5728\u8FD9\u91CC\u7B49\u4EBA\u70B9\uFF1B\u6CA1\u914D\u5C31\u76F4\u63A5\u8FC7\u3002",
|
|
24755
|
+
" environment: production",
|
|
24756
|
+
" steps:",
|
|
24757
|
+
" - name: Checkout clawos\uFF08\u53EA\u8981\u6D41\u6C34\u7EBF\u811A\u672C\uFF09",
|
|
24758
|
+
" uses: actions/checkout@v4",
|
|
24759
|
+
" with:",
|
|
24760
|
+
" repository: ${{ env.CLAWOS_REPO }}",
|
|
24761
|
+
" ref: ${{ env.CLAWOS_REF }}",
|
|
24762
|
+
" token: ${{ secrets.CLAWOS_READ_TOKEN }}",
|
|
24763
|
+
" path: clawos",
|
|
24764
|
+
" sparse-checkout: cloud-host/pipeline",
|
|
24765
|
+
"",
|
|
24766
|
+
" - name: \u767B\u8BB0\u6B63\u5F0F",
|
|
24767
|
+
" env:",
|
|
24768
|
+
" CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
|
|
24769
|
+
" CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
|
|
24770
|
+
" CLAWD_SECRETS_JSON: ${{ toJSON(secrets) }}",
|
|
24771
|
+
" CLAWD_ENV_NAMES: ${{ needs.build.outputs.env_names }}",
|
|
24772
|
+
" run: |",
|
|
24773
|
+
" node clawos/cloud-host/pipeline/register.mjs \\",
|
|
24774
|
+
" --image '${{ needs.build.outputs.image }}' \\",
|
|
24775
|
+
" --persona-id '${{ needs.build.outputs.persona_id }}' \\",
|
|
24776
|
+
" --model '${{ needs.build.outputs.model }}' \\",
|
|
24777
|
+
" --effort '${{ needs.build.outputs.effort }}' \\",
|
|
24778
|
+
" --connectors '${{ needs.build.outputs.connectors }}'",
|
|
24779
|
+
""
|
|
24780
|
+
];
|
|
24781
|
+
var WORKFLOW_TEMPLATE = L.join("\n");
|
|
24866
24782
|
|
|
24867
|
-
// src/
|
|
24868
|
-
var
|
|
24869
|
-
var
|
|
24870
|
-
|
|
24871
|
-
|
|
24872
|
-
|
|
24873
|
-
|
|
24874
|
-
|
|
24875
|
-
|
|
24876
|
-
|
|
24877
|
-
|
|
24878
|
-
|
|
24879
|
-
|
|
24880
|
-
|
|
24881
|
-
|
|
24882
|
-
|
|
24883
|
-
|
|
24884
|
-
|
|
24885
|
-
|
|
24886
|
-
|
|
24887
|
-
|
|
24888
|
-
|
|
24889
|
-
|
|
24890
|
-
|
|
24891
|
-
|
|
24892
|
-
|
|
24893
|
-
|
|
24894
|
-
|
|
24783
|
+
// src/deploy/repo.ts
|
|
24784
|
+
var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path4.default.sep).join("/");
|
|
24785
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
24786
|
+
var runCommand = (cmd, args, opts = {}) => new Promise((resolve) => {
|
|
24787
|
+
const child = (0, import_node_child_process2.spawn)(cmd, args, {
|
|
24788
|
+
cwd: opts.cwd,
|
|
24789
|
+
stdio: [opts.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
24790
|
+
});
|
|
24791
|
+
let stdout = "";
|
|
24792
|
+
let stderr = "";
|
|
24793
|
+
child.stdout?.on("data", (d) => {
|
|
24794
|
+
stdout += d.toString("utf8");
|
|
24795
|
+
});
|
|
24796
|
+
child.stderr?.on("data", (d) => {
|
|
24797
|
+
stderr += d.toString("utf8");
|
|
24798
|
+
});
|
|
24799
|
+
const timer = setTimeout(() => {
|
|
24800
|
+
child.kill("SIGKILL");
|
|
24801
|
+
stderr += `
|
|
24802
|
+
[clawd] \u8D85\u65F6\uFF08>${Math.round((opts.timeoutMs ?? DEFAULT_TIMEOUT_MS) / 1e3)}s\uFF09\uFF0C\u5DF2\u7ECF\u628A\u5B83\u6390\u4E86
|
|
24803
|
+
`;
|
|
24804
|
+
}, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
24805
|
+
child.on("error", (err) => {
|
|
24806
|
+
clearTimeout(timer);
|
|
24807
|
+
resolve({ code: null, stdout, stderr, spawnError: err.message });
|
|
24808
|
+
});
|
|
24809
|
+
child.on("close", (code) => {
|
|
24810
|
+
clearTimeout(timer);
|
|
24811
|
+
resolve({ code, stdout, stderr });
|
|
24812
|
+
});
|
|
24813
|
+
if (opts.stdin !== void 0 && child.stdin) {
|
|
24814
|
+
child.stdin.on("error", () => {
|
|
24895
24815
|
});
|
|
24896
|
-
|
|
24897
|
-
}
|
|
24898
|
-
updateStage(name, stage) {
|
|
24899
|
-
const job = this.jobs.get(name);
|
|
24900
|
-
if (!job) return;
|
|
24901
|
-
job.stage = stage;
|
|
24902
|
-
job.sawAnyStage = true;
|
|
24903
|
-
}
|
|
24904
|
-
markDismissed(name) {
|
|
24905
|
-
const job = this.jobs.get(name);
|
|
24906
|
-
if (!job) return;
|
|
24907
|
-
job.dismissed = true;
|
|
24816
|
+
child.stdin.end(opts.stdin);
|
|
24908
24817
|
}
|
|
24909
|
-
|
|
24910
|
-
|
|
24911
|
-
|
|
24912
|
-
|
|
24913
|
-
|
|
24818
|
+
});
|
|
24819
|
+
var RepoError = class extends Error {
|
|
24820
|
+
};
|
|
24821
|
+
var tail = (s, n = 400) => s.trim().slice(-n);
|
|
24822
|
+
async function git(exec, dir, args, opts) {
|
|
24823
|
+
return exec("git", args, { cwd: dir, ...opts });
|
|
24824
|
+
}
|
|
24825
|
+
async function mustGit(exec, dir, args, what, opts) {
|
|
24826
|
+
const r = await git(exec, dir, args, opts);
|
|
24827
|
+
if (r.spawnError) throw new RepoError(`\u672C\u673A\u8DD1\u4E0D\u4E86 git\uFF1A${r.spawnError}`);
|
|
24828
|
+
if (r.code !== 0) throw new RepoError(`${what}\u6CA1\u6210\uFF08git \u9000\u51FA\u7801 ${r.code}\uFF09\uFF1A${tail(r.stderr) || tail(r.stdout)}`);
|
|
24829
|
+
return r.stdout.trim();
|
|
24830
|
+
}
|
|
24831
|
+
async function ghReady(exec) {
|
|
24832
|
+
const r = await exec("gh", ["auth", "status"], { timeoutMs: 15e3 });
|
|
24833
|
+
if (r.spawnError || r.code !== 0) {
|
|
24834
|
+
return {
|
|
24835
|
+
ok: false,
|
|
24836
|
+
message: "\u672C\u673A\u8FD8\u6CA1\u6709 GitHub \u547D\u4EE4\u884C\u5DE5\u5177\uFF08gh\uFF09\uFF0C\u6216\u8005\u8FD8\u6CA1\u767B\u5F55\u3002\u5EFA\u4ED3\u5E93\u3001\u63A8\u9001\u3001\u5199 Secrets \u90FD\u9760\u5B83\uFF1A\n\n brew install gh\n gh auth login\n\n\u767B\u5F55\u7684\u8D26\u53F7\u8981\u662F\u7EC4\u7EC7\u7684\u6210\u5458\u3002\u5F04\u597D\u540E\u518D\u70B9\u4E00\u6B21\u3002"
|
|
24837
|
+
};
|
|
24914
24838
|
}
|
|
24839
|
+
return { ok: true };
|
|
24840
|
+
}
|
|
24841
|
+
async function isToplevel(exec, dir) {
|
|
24842
|
+
const r = await git(exec, dir, ["rev-parse", "--show-toplevel"]);
|
|
24843
|
+
if (r.spawnError || r.code !== 0) return false;
|
|
24844
|
+
return samePath(dir, r.stdout.trim());
|
|
24845
|
+
}
|
|
24846
|
+
async function samePath(a, b) {
|
|
24847
|
+
const real = async (x) => {
|
|
24848
|
+
try {
|
|
24849
|
+
return await import_promises2.default.realpath(x);
|
|
24850
|
+
} catch {
|
|
24851
|
+
return import_node_path4.default.resolve(x);
|
|
24852
|
+
}
|
|
24853
|
+
};
|
|
24854
|
+
return await real(a) === await real(b);
|
|
24855
|
+
}
|
|
24856
|
+
async function repoInfo(exec, dir) {
|
|
24857
|
+
if (!await isToplevel(exec, dir)) return void 0;
|
|
24858
|
+
const origin = await git(exec, dir, ["remote", "get-url", "origin"]);
|
|
24859
|
+
if (origin.spawnError || origin.code !== 0) return void 0;
|
|
24860
|
+
const url = origin.stdout.trim();
|
|
24861
|
+
const branch = (await git(exec, dir, ["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim() || "main";
|
|
24862
|
+
const head = (await git(exec, dir, ["rev-parse", "HEAD"])).stdout.trim() || "(\u8FD8\u6CA1\u6709\u63D0\u4EA4)";
|
|
24863
|
+
const status = await git(exec, dir, ["status", "--porcelain", "--", ".", `:(exclude)${DEPLOY_FILE_REL}`]);
|
|
24864
|
+
const dirty = status.spawnError !== void 0 || status.code !== 0 || status.stdout.trim().length > 0;
|
|
24865
|
+
const aheadRaw = await git(exec, dir, ["rev-list", "--count", "@{u}..HEAD"]);
|
|
24866
|
+
const ahead = aheadRaw.code === 0 ? Number(aheadRaw.stdout.trim()) || 0 : 0;
|
|
24867
|
+
return { url, branch, head, dirty, ahead };
|
|
24868
|
+
}
|
|
24869
|
+
async function identityArgs(exec, dir) {
|
|
24870
|
+
const email2 = await git(exec, dir, ["config", "user.email"]);
|
|
24871
|
+
const name = await git(exec, dir, ["config", "user.name"]);
|
|
24872
|
+
const has = (r) => r.code === 0 && r.stdout.trim().length > 0;
|
|
24873
|
+
if (has(email2) && has(name)) return [];
|
|
24874
|
+
return ["-c", "user.name=clawd", "-c", "user.email=clawd@clawos.local"];
|
|
24875
|
+
}
|
|
24876
|
+
var RepoAlreadyExistsError = class extends RepoError {
|
|
24877
|
+
constructor(slug) {
|
|
24878
|
+
super(`\u7EC4\u7EC7\u91CC\u5DF2\u7ECF\u6709 https://github.com/${slug} \u4E86`);
|
|
24879
|
+
this.slug = slug;
|
|
24880
|
+
}
|
|
24881
|
+
slug;
|
|
24915
24882
|
};
|
|
24916
|
-
|
|
24917
|
-
|
|
24918
|
-
|
|
24919
|
-
|
|
24920
|
-
|
|
24883
|
+
async function createOrgRepo(exec, args) {
|
|
24884
|
+
const { dir, org, repoName, message } = args;
|
|
24885
|
+
if (!await isToplevel(exec, dir)) {
|
|
24886
|
+
await mustGit(exec, dir, ["init", "-b", "main"], "\u521D\u59CB\u5316\u4ED3\u5E93");
|
|
24887
|
+
}
|
|
24888
|
+
const identity = await identityArgs(exec, dir);
|
|
24889
|
+
await mustGit(exec, dir, ["add", "-A"], "\u6682\u5B58\u6587\u4EF6");
|
|
24890
|
+
await mustGit(exec, dir, [...identity, "commit", "--allow-empty", "-m", message], "\u9996\u6B21\u63D0\u4EA4");
|
|
24891
|
+
const slug = `${org}/${repoName}`;
|
|
24892
|
+
const r = await exec(
|
|
24893
|
+
"gh",
|
|
24894
|
+
["repo", "create", slug, "--private", "--source", dir, "--remote", "origin", "--push"],
|
|
24895
|
+
{ cwd: dir, timeoutMs: 12e4 }
|
|
24896
|
+
);
|
|
24897
|
+
if (r.spawnError) throw new RepoError(`\u672C\u673A\u8DD1\u4E0D\u4E86 gh\uFF1A${r.spawnError}`);
|
|
24898
|
+
if (r.code !== 0) {
|
|
24899
|
+
const err = tail(r.stderr) || tail(r.stdout);
|
|
24900
|
+
if (/already exists/i.test(err)) throw new RepoAlreadyExistsError(slug);
|
|
24901
|
+
throw new RepoError(`\u5EFA\u4ED3\u5E93\u6CA1\u6210\uFF08gh repo create \u9000\u51FA\u7801 ${r.code}\uFF09\uFF1A${err}`);
|
|
24902
|
+
}
|
|
24903
|
+
const info = await repoInfo(exec, dir);
|
|
24904
|
+
return { slug, url: info?.url ?? `https://github.com/${slug}` };
|
|
24905
|
+
}
|
|
24906
|
+
async function workTreeState(exec, dir) {
|
|
24907
|
+
if (!await isToplevel(exec, dir)) return { isRepo: false };
|
|
24908
|
+
const status = await git(exec, dir, ["status", "--porcelain"]);
|
|
24909
|
+
const dirty = status.spawnError !== void 0 || status.code !== 0 || status.stdout.trim().length > 0;
|
|
24910
|
+
const head = await git(exec, dir, ["rev-parse", "HEAD"]);
|
|
24911
|
+
return { isRepo: true, dirty, head: head.code === 0 ? head.stdout.trim() || null : null };
|
|
24912
|
+
}
|
|
24921
24913
|
|
|
24922
24914
|
// src/app-builder-plugin/publish-stage-parser.ts
|
|
24923
24915
|
var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
|
|
24924
24916
|
var PROD_URL_RE = /^\s*::prod-url::(\S+)\s*$/;
|
|
24925
|
-
var URL_RE = /\bhttps?:\/\/[^\s]+/;
|
|
24926
24917
|
function parsePublishLine(line) {
|
|
24927
24918
|
const stageMatch = STAGE_RE.exec(line);
|
|
24928
24919
|
if (stageMatch) return { kind: "stage", stage: stageMatch[1] };
|
|
@@ -24930,10 +24921,6 @@ function parsePublishLine(line) {
|
|
|
24930
24921
|
if (urlMatch) return { kind: "prod-url", url: urlMatch[1] };
|
|
24931
24922
|
return null;
|
|
24932
24923
|
}
|
|
24933
|
-
function extractProdUrl(text) {
|
|
24934
|
-
const m = URL_RE.exec(text);
|
|
24935
|
-
return m ? m[0] : null;
|
|
24936
|
-
}
|
|
24937
24924
|
function tailStderrLines(buf, n) {
|
|
24938
24925
|
if (!buf) return "";
|
|
24939
24926
|
const lines = buf.split("\n");
|
|
@@ -24941,347 +24928,148 @@ function tailStderrLines(buf, n) {
|
|
|
24941
24928
|
return lines.slice(lines.length - n).join("\n");
|
|
24942
24929
|
}
|
|
24943
24930
|
|
|
24944
|
-
// src/app-builder-plugin/
|
|
24945
|
-
|
|
24946
|
-
|
|
24947
|
-
const
|
|
24948
|
-
|
|
24949
|
-
|
|
24950
|
-
|
|
24951
|
-
|
|
24952
|
-
const
|
|
24953
|
-
let
|
|
24931
|
+
// src/app-builder-plugin/core.ts
|
|
24932
|
+
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
24933
|
+
async function createProject(args) {
|
|
24934
|
+
const { store, name, logger: logger2 } = args;
|
|
24935
|
+
const exec = args.exec ?? runCommand;
|
|
24936
|
+
const gh = await ghReady(exec);
|
|
24937
|
+
if (!gh.ok) throw new Error(gh.message);
|
|
24938
|
+
await store.create(name);
|
|
24939
|
+
const dir = store.projectDir(name);
|
|
24940
|
+
let repo2;
|
|
24954
24941
|
try {
|
|
24955
|
-
|
|
24956
|
-
|
|
24957
|
-
|
|
24958
|
-
|
|
24959
|
-
|
|
24960
|
-
|
|
24961
|
-
|
|
24962
|
-
|
|
24963
|
-
|
|
24964
|
-
|
|
24965
|
-
|
|
24966
|
-
|
|
24967
|
-
|
|
24968
|
-
|
|
24969
|
-
|
|
24970
|
-
sessionId: args.sessionId,
|
|
24971
|
-
childProc: child
|
|
24972
|
-
});
|
|
24973
|
-
deps.logger?.info("app-builder.publish.spawned", {
|
|
24974
|
-
name: args.name,
|
|
24975
|
-
jobId,
|
|
24976
|
-
pid: child.pid ?? null,
|
|
24977
|
-
scriptPath: args.scriptPath,
|
|
24978
|
-
logPath
|
|
24979
|
-
});
|
|
24980
|
-
child.on("error", (err) => {
|
|
24981
|
-
deps.logger?.warn("app-builder.publish.spawn-error", {
|
|
24982
|
-
name: args.name,
|
|
24983
|
-
jobId,
|
|
24984
|
-
error: err instanceof Error ? err.message : String(err)
|
|
24985
|
-
});
|
|
24986
|
-
void handleExit(deps, {
|
|
24987
|
-
name: args.name,
|
|
24988
|
-
jobId,
|
|
24989
|
-
code: null,
|
|
24990
|
-
signal: null,
|
|
24991
|
-
urlCandidate: null,
|
|
24992
|
-
stderrTail: err instanceof Error ? err.message : String(err),
|
|
24993
|
-
logPath
|
|
24994
|
-
});
|
|
24995
|
-
});
|
|
24996
|
-
let urlFromMarker = null;
|
|
24997
|
-
let urlFallback = null;
|
|
24998
|
-
let stdoutBuf = "";
|
|
24999
|
-
child.stdout?.on("data", (chunk) => {
|
|
25000
|
-
if (logStream) logStream.write(chunk);
|
|
25001
|
-
stdoutBuf += chunk.toString();
|
|
25002
|
-
let nlIdx;
|
|
25003
|
-
while ((nlIdx = stdoutBuf.indexOf("\n")) >= 0) {
|
|
25004
|
-
const line = stdoutBuf.slice(0, nlIdx);
|
|
25005
|
-
stdoutBuf = stdoutBuf.slice(nlIdx + 1);
|
|
25006
|
-
handleStdoutLine(deps, args.name, jobId, line);
|
|
25007
|
-
const parsedForUrl = parsePublishLine(line);
|
|
25008
|
-
if (parsedForUrl?.kind === "prod-url") urlFromMarker = parsedForUrl.url;
|
|
25009
|
-
const url = extractProdUrl(line);
|
|
25010
|
-
if (url && !urlFallback) urlFallback = url;
|
|
25011
|
-
}
|
|
25012
|
-
});
|
|
25013
|
-
let stderrBuf = "";
|
|
25014
|
-
child.stderr?.on("data", (chunk) => {
|
|
25015
|
-
if (logStream) logStream.write(chunk);
|
|
25016
|
-
stderrBuf += chunk.toString();
|
|
25017
|
-
});
|
|
25018
|
-
child.on("exit", (code, signal) => {
|
|
25019
|
-
if (logStream) logStream.end();
|
|
25020
|
-
const urlCandidate = urlFromMarker ?? urlFallback;
|
|
25021
|
-
deps.logger?.info("app-builder.publish.child-exit", {
|
|
25022
|
-
name: args.name,
|
|
25023
|
-
jobId,
|
|
25024
|
-
code,
|
|
25025
|
-
signal,
|
|
25026
|
-
hadUrl: !!urlCandidate,
|
|
25027
|
-
urlSource: urlFromMarker ? "marker" : urlFallback ? "fallback" : "none",
|
|
25028
|
-
stderrBytes: stderrBuf.length
|
|
25029
|
-
});
|
|
25030
|
-
void handleExit(deps, {
|
|
25031
|
-
name: args.name,
|
|
25032
|
-
jobId,
|
|
25033
|
-
code,
|
|
25034
|
-
signal,
|
|
25035
|
-
urlCandidate,
|
|
25036
|
-
stderrTail: tailStderrLines(stderrBuf, 20),
|
|
25037
|
-
logPath
|
|
25038
|
-
});
|
|
25039
|
-
});
|
|
25040
|
-
return { jobId, status: "started" };
|
|
25041
|
-
}
|
|
25042
|
-
function handleStdoutLine(deps, name, jobId, line) {
|
|
25043
|
-
const parsed = parsePublishLine(line);
|
|
25044
|
-
if (parsed?.kind !== "stage") return;
|
|
25045
|
-
advanceStage(deps, name, jobId, parsed.stage);
|
|
25046
|
-
}
|
|
25047
|
-
function advanceStage(deps, name, jobId, next) {
|
|
25048
|
-
const job = deps.registry.get(name);
|
|
25049
|
-
if (!job) return;
|
|
25050
|
-
const prev = job.stage;
|
|
25051
|
-
const wasFirstStage = !job.sawAnyStage;
|
|
25052
|
-
deps.logger?.info("app-builder.publish.stage-advance", {
|
|
25053
|
-
name,
|
|
25054
|
-
jobId,
|
|
25055
|
-
prev: wasFirstStage ? null : prev,
|
|
25056
|
-
next,
|
|
25057
|
-
firstStage: wasFirstStage
|
|
25058
|
-
});
|
|
25059
|
-
if (wasFirstStage) {
|
|
25060
|
-
deps.registry.updateStage(name, next);
|
|
25061
|
-
void deps.setPublishJob(name, {
|
|
25062
|
-
jobId,
|
|
25063
|
-
stage: next,
|
|
25064
|
-
startedAt: job.startedAt
|
|
25065
|
-
});
|
|
25066
|
-
deps.broadcast({
|
|
25067
|
-
type: "appBuilder:publish-progress",
|
|
25068
|
-
name,
|
|
25069
|
-
jobId,
|
|
25070
|
-
stage: next,
|
|
25071
|
-
status: "started"
|
|
24942
|
+
const scaffold = await store.scaffold(name, args.templateSrcDir, args.scaffoldScript, args.extraEnv);
|
|
24943
|
+
logger2?.info("app-builder.scaffold.done", { name, stdout: scaffold.stdout.slice(0, 200) });
|
|
24944
|
+
const slug = await readSlug(dir);
|
|
24945
|
+
if (!slug) logger2?.warn("app-builder.create.no-slug", { name });
|
|
24946
|
+
const repoName = slug ? `${name}-${slug}` : name;
|
|
24947
|
+
repo2 = await createOrgRepo(exec, { dir, org: args.org, repoName, message: `clawd: \u65B0\u5EFA ${name}` });
|
|
24948
|
+
const commit = await headCommit(exec, dir);
|
|
24949
|
+
const project = await store.setRepoUrl(name, repo2.url);
|
|
24950
|
+
logger2?.info("app-builder.create.done", { name, repoUrl: repo2.url, commit });
|
|
24951
|
+
return { project, projectDir: dir, repoUrl: repo2.url, commit };
|
|
24952
|
+
} catch (err) {
|
|
24953
|
+
const message = errMsg(err);
|
|
24954
|
+
logger2?.warn("app-builder.create.failed", { name, error: message });
|
|
24955
|
+
await store.delete(name).catch((e) => {
|
|
24956
|
+
logger2?.warn("app-builder.create.rollback-failed", { name, error: errMsg(e) });
|
|
25072
24957
|
});
|
|
25073
|
-
|
|
24958
|
+
const orphan = repo2 ? `\uFF1BGitHub \u4ED3\u5E93 ${repo2.url} \u5DF2\u7ECF\u5EFA\u597D\u63A8\u4E0A\u53BB\u4E86\uFF0C\u672C\u5730\u56DE\u6EDA\u5220\u4E0D\u6389\u5B83\uFF0C\u91CD\u8BD5\u524D\u5148\u53BB GitHub \u5220\u6389` : "";
|
|
24959
|
+
throw new Error(`createProject \u6CA1\u6210\uFF0C\u521A\u5EFA\u7684\u76EE\u5F55\u5DF2\u56DE\u6EDA\uFF1A${message}${orphan}`);
|
|
25074
24960
|
}
|
|
25075
|
-
if (prev === next) {
|
|
25076
|
-
return;
|
|
25077
|
-
}
|
|
25078
|
-
deps.broadcast({
|
|
25079
|
-
type: "appBuilder:publish-progress",
|
|
25080
|
-
name,
|
|
25081
|
-
jobId,
|
|
25082
|
-
stage: prev,
|
|
25083
|
-
status: "completed"
|
|
25084
|
-
});
|
|
25085
|
-
deps.registry.updateStage(name, next);
|
|
25086
|
-
void deps.setPublishJob(name, {
|
|
25087
|
-
jobId,
|
|
25088
|
-
stage: next,
|
|
25089
|
-
startedAt: job.startedAt
|
|
25090
|
-
});
|
|
25091
|
-
deps.broadcast({
|
|
25092
|
-
type: "appBuilder:publish-progress",
|
|
25093
|
-
name,
|
|
25094
|
-
jobId,
|
|
25095
|
-
stage: next,
|
|
25096
|
-
status: "started"
|
|
25097
|
-
});
|
|
25098
24961
|
}
|
|
25099
|
-
async function
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
25105
|
-
console.error("[publish] clearPublishJob failed (dismissed branch)", err);
|
|
25106
|
-
}
|
|
25107
|
-
deps.registry.remove(args.name);
|
|
25108
|
-
deps.onSettled?.({ kind: "dismissed" });
|
|
25109
|
-
return;
|
|
25110
|
-
}
|
|
25111
|
-
if (args.code === 0 && args.urlCandidate) {
|
|
25112
|
-
if (job?.sawAnyStage) {
|
|
25113
|
-
deps.broadcast({
|
|
25114
|
-
type: "appBuilder:publish-progress",
|
|
25115
|
-
name: args.name,
|
|
25116
|
-
jobId: args.jobId,
|
|
25117
|
-
stage: job.stage,
|
|
25118
|
-
status: "completed"
|
|
25119
|
-
});
|
|
25120
|
-
}
|
|
25121
|
-
try {
|
|
25122
|
-
await deps.setProdUrl(args.name, args.urlCandidate);
|
|
25123
|
-
} catch (err) {
|
|
25124
|
-
console.error("[publish] setProdUrl failed; falling through to failure branch", err);
|
|
25125
|
-
await emitFailure(deps, args, "\u8C03 setProdUrl \u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)));
|
|
25126
|
-
return;
|
|
25127
|
-
}
|
|
25128
|
-
try {
|
|
25129
|
-
await deps.clearPublishJob(args.name);
|
|
25130
|
-
} catch (err) {
|
|
25131
|
-
console.error("[publish] clearPublishJob failed (success branch)", err);
|
|
25132
|
-
}
|
|
25133
|
-
deps.registry.remove(args.name);
|
|
25134
|
-
deps.onSettled?.({ kind: "published", url: args.urlCandidate });
|
|
25135
|
-
return;
|
|
24962
|
+
async function readSlug(projectDir) {
|
|
24963
|
+
let conf;
|
|
24964
|
+
try {
|
|
24965
|
+
conf = await import_node_fs3.promises.readFile((0, import_node_path5.join)(projectDir, "ext.conf"), "utf8");
|
|
24966
|
+
} catch {
|
|
24967
|
+
return null;
|
|
25136
24968
|
}
|
|
25137
|
-
|
|
24969
|
+
const m = /^SLUG=([A-Za-z0-9]+)\s*$/m.exec(conf);
|
|
24970
|
+
return m ? m[1] : null;
|
|
24971
|
+
}
|
|
24972
|
+
async function headCommit(exec, dir) {
|
|
24973
|
+
const r = await exec("git", ["rev-parse", "HEAD"], { cwd: dir });
|
|
24974
|
+
return r.code === 0 ? r.stdout.trim() : "";
|
|
25138
24975
|
}
|
|
25139
|
-
|
|
25140
|
-
|
|
25141
|
-
const
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
24976
|
+
var PUBLISH_TIMEOUT_MS = 15 * 6e4;
|
|
24977
|
+
async function runPublish(args) {
|
|
24978
|
+
const { store, name, logger: logger2 } = args;
|
|
24979
|
+
const exec = args.exec ?? runCommand;
|
|
24980
|
+
const spawnFn = args.spawnImpl ?? import_node_child_process3.spawn;
|
|
24981
|
+
const timeoutMs = args.timeoutMs ?? PUBLISH_TIMEOUT_MS;
|
|
24982
|
+
if (!await store.get(name)) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24983
|
+
const dir = store.projectDir(name);
|
|
24984
|
+
const tree = await workTreeState(exec, dir);
|
|
24985
|
+
if (tree.isRepo && tree.dirty) {
|
|
24986
|
+
throw new Error("\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u7684\u6539\u52A8\uFF1A\u5148 commit + push \u518D\u53D1\u5E03\uFF0C\u7EBF\u4E0A\u5FC5\u987B\u5BF9\u5E94\u4ED3\u5E93\u91CC\u7684\u4E00\u4E2A commit");
|
|
24987
|
+
}
|
|
24988
|
+
const head = tree.isRepo ? tree.head : null;
|
|
24989
|
+
const logPath = (0, import_node_path5.join)(dir, ".publish.log");
|
|
24990
|
+
const log2 = (0, import_node_fs3.createWriteStream)(logPath, { flags: "w" });
|
|
24991
|
+
log2.on("error", () => {
|
|
25147
24992
|
});
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
24993
|
+
logger2?.info("app-builder.publish.start", { name, scriptPath: args.scriptPath, head });
|
|
24994
|
+
return await new Promise((resolve, reject) => {
|
|
24995
|
+
const child = spawnFn("bash", [args.scriptPath, dir, args.personaRoot], {
|
|
24996
|
+
cwd: dir,
|
|
24997
|
+
// 凭据就在本进程 env 里(宿主交进来的),照单全给脚本
|
|
24998
|
+
env: process.env,
|
|
24999
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
25155
25000
|
});
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25159
|
-
|
|
25160
|
-
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
|
|
25164
|
-
|
|
25165
|
-
|
|
25166
|
-
|
|
25167
|
-
|
|
25168
|
-
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
|
|
25172
|
-
|
|
25173
|
-
|
|
25174
|
-
|
|
25175
|
-
deps.logger?.warn("app-builder.publish.inject-user-text-failed", {
|
|
25176
|
-
name: args.name,
|
|
25177
|
-
jobId: args.jobId,
|
|
25178
|
-
sessionId: removed.sessionId,
|
|
25179
|
-
error: err instanceof Error ? err.message : String(err)
|
|
25180
|
-
});
|
|
25181
|
-
}
|
|
25182
|
-
} else {
|
|
25183
|
-
deps.logger?.warn("app-builder.publish.no-registry-entry-on-failure", {
|
|
25184
|
-
name: args.name,
|
|
25185
|
-
jobId: args.jobId
|
|
25001
|
+
let stdout = "";
|
|
25002
|
+
let stderr = "";
|
|
25003
|
+
let partial2 = "";
|
|
25004
|
+
let stage = "unknown";
|
|
25005
|
+
let prodUrl = null;
|
|
25006
|
+
let timedOut = false;
|
|
25007
|
+
const feed = (line) => {
|
|
25008
|
+
const p = parsePublishLine(line);
|
|
25009
|
+
if (p?.kind === "stage") stage = p.stage;
|
|
25010
|
+
else if (p?.kind === "prod-url") prodUrl = p.url;
|
|
25011
|
+
};
|
|
25012
|
+
child.stdout?.on("data", (d) => {
|
|
25013
|
+
const s = d.toString("utf8");
|
|
25014
|
+
stdout += s;
|
|
25015
|
+
log2.write(s);
|
|
25016
|
+
partial2 += s;
|
|
25017
|
+
const lines = partial2.split("\n");
|
|
25018
|
+
partial2 = lines.pop() ?? "";
|
|
25019
|
+
for (const l of lines) feed(l);
|
|
25186
25020
|
});
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
}
|
|
25199
|
-
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
|
|
25207
|
-
|
|
25208
|
-
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
clearPublishJob: async (name) => {
|
|
25227
|
-
await store.clearPublishJob(name);
|
|
25228
|
-
await changed(name);
|
|
25229
|
-
},
|
|
25230
|
-
setProdUrl: async (name, url) => {
|
|
25231
|
-
await store.setProdUrl(name, url);
|
|
25232
|
-
await changed(name);
|
|
25233
|
-
},
|
|
25234
|
-
broadcast: (frame) => hooks?.broadcast?.(frame),
|
|
25235
|
-
injectUserText: async (sessionId, text) => {
|
|
25236
|
-
await hooks?.injectUserText?.(sessionId, text);
|
|
25237
|
-
},
|
|
25238
|
-
projectDir: (name) => store.projectDir(name),
|
|
25239
|
-
...hooks?.onSettled ? { onSettled: hooks.onSettled } : {},
|
|
25240
|
-
...logger ? { logger } : {}
|
|
25241
|
-
},
|
|
25242
|
-
{
|
|
25243
|
-
name: args.name,
|
|
25244
|
-
sessionId: args.sessionId,
|
|
25245
|
-
scriptPath: args.scriptPath,
|
|
25246
|
-
personaRoot: args.personaRoot,
|
|
25247
|
-
...args.extraEnv ? { extraEnv: args.extraEnv } : {}
|
|
25021
|
+
child.stderr?.on("data", (d) => {
|
|
25022
|
+
const s = d.toString("utf8");
|
|
25023
|
+
stderr += s;
|
|
25024
|
+
log2.write(s);
|
|
25025
|
+
});
|
|
25026
|
+
const timeoutSummary = `publish.sh \u8D85\u8FC7 ${Math.round(timeoutMs / 6e4)} \u5206\u949F\u6CA1\u7ED3\u675F\uFF0C\u5DF2\u7ECF\u628A\u5B83\u6390\u4E86`;
|
|
25027
|
+
const timer = setTimeout(() => {
|
|
25028
|
+
timedOut = true;
|
|
25029
|
+
child.kill("SIGKILL");
|
|
25030
|
+
log2.end();
|
|
25031
|
+
logger2?.warn("app-builder.publish.timeout", { name, stage });
|
|
25032
|
+
resolve({ ok: false, stage, errorSummary: timeoutSummary, logPath });
|
|
25033
|
+
}, timeoutMs);
|
|
25034
|
+
child.on("error", (err) => {
|
|
25035
|
+
clearTimeout(timer);
|
|
25036
|
+
log2.end();
|
|
25037
|
+
reject(new Error(`publish spawn failed: ${err.message}`));
|
|
25038
|
+
});
|
|
25039
|
+
child.on("close", (code, signal) => {
|
|
25040
|
+
clearTimeout(timer);
|
|
25041
|
+
if (timedOut) return;
|
|
25042
|
+
if (partial2) feed(partial2);
|
|
25043
|
+
log2.end();
|
|
25044
|
+
void (async () => {
|
|
25045
|
+
if (code === 0 && prodUrl) {
|
|
25046
|
+
const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25047
|
+
try {
|
|
25048
|
+
await store.setPublished(name, {
|
|
25049
|
+
prodUrl,
|
|
25050
|
+
publishedAt,
|
|
25051
|
+
...head ? { publishedCommit: head } : {}
|
|
25052
|
+
});
|
|
25053
|
+
} catch (err) {
|
|
25054
|
+
reject(new Error(`\u53D1\u5E03\u6210\u529F\u4F46\u5199\u56DE\u5143\u6570\u636E\u5931\u8D25\uFF1A${errMsg(err)}`));
|
|
25055
|
+
return;
|
|
25056
|
+
}
|
|
25057
|
+
logger2?.info("app-builder.publish.done", { name, prodUrl, head });
|
|
25058
|
+
resolve({ ok: true, prodUrl, commit: head, publishedAt, logPath });
|
|
25059
|
+
return;
|
|
25248
25060
|
}
|
|
25249
|
-
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
if (!job) return false;
|
|
25254
|
-
registry2.markDismissed(name);
|
|
25255
|
-
job.childProc.kill("SIGTERM");
|
|
25256
|
-
return true;
|
|
25257
|
-
}
|
|
25258
|
-
};
|
|
25259
|
-
}
|
|
25260
|
-
async function createProjectOnDisk(args) {
|
|
25261
|
-
const { store, name, templateSrcDir, scaffoldScript, logger } = args;
|
|
25262
|
-
const project = await store.create(name, args.reservedPorts);
|
|
25263
|
-
try {
|
|
25264
|
-
const result = await store.scaffold(name, templateSrcDir, scaffoldScript, args.extraEnv);
|
|
25265
|
-
logger?.info("app-builder.scaffold.done", { name, stdout: result.stdout.slice(0, 200) });
|
|
25266
|
-
} catch (err) {
|
|
25267
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
25268
|
-
logger?.warn("app-builder.scaffold.failed", { name, error: message });
|
|
25269
|
-
await store.delete(name).catch((delErr) => {
|
|
25270
|
-
logger?.warn("app-builder.scaffold.rollback-failed", {
|
|
25271
|
-
name,
|
|
25272
|
-
error: delErr instanceof Error ? delErr.message : String(delErr)
|
|
25273
|
-
});
|
|
25061
|
+
const errorSummary = code === 0 ? "\u811A\u672C\u9000\u51FA 0 \u4F46\u6CA1\u6709\u8F93\u51FA ::prod-url:: \u6807\u8BB0\uFF0C\u62FF\u4E0D\u5230\u516C\u7F51\u5730\u5740" : tailStderrLines(stderr, 20) || tailStderrLines(stdout, 20) || `exit ${code ?? signal}`;
|
|
25062
|
+
logger2?.warn("app-builder.publish.failed", { name, stage, code, signal, timedOut });
|
|
25063
|
+
resolve({ ok: false, stage, errorSummary, logPath });
|
|
25064
|
+
})();
|
|
25274
25065
|
});
|
|
25275
|
-
|
|
25276
|
-
}
|
|
25277
|
-
return { project, projectDir: store.projectDir(name) };
|
|
25066
|
+
});
|
|
25278
25067
|
}
|
|
25279
25068
|
var REMOVE_TIMEOUT_MS = 10 * 6e4;
|
|
25280
25069
|
async function runRemoveExtension(args) {
|
|
25281
25070
|
const spawnFn = args.spawnImpl ?? import_node_child_process3.spawn;
|
|
25282
25071
|
return await new Promise((resolve, reject) => {
|
|
25283
25072
|
const child = spawnFn("bash", [args.script, args.projectDir, args.personaRoot], {
|
|
25284
|
-
// 凭据在 process.env 里(gateway → MCP server 的 per-server env → 本进程)
|
|
25285
25073
|
env: process.env,
|
|
25286
25074
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25287
25075
|
});
|
|
@@ -25301,7 +25089,7 @@ async function runRemoveExtension(args) {
|
|
|
25301
25089
|
clearTimeout(timer);
|
|
25302
25090
|
reject(new Error(`remove-extension spawn failed: ${err.message}`));
|
|
25303
25091
|
});
|
|
25304
|
-
child.on("
|
|
25092
|
+
child.on("close", (code) => {
|
|
25305
25093
|
clearTimeout(timer);
|
|
25306
25094
|
args.logger?.info("app-builder.remove.exit", { projectDir: args.projectDir, code });
|
|
25307
25095
|
resolve({ code, stdout, stderr });
|
|
@@ -25316,7 +25104,7 @@ function log(msg, info) {
|
|
|
25316
25104
|
`;
|
|
25317
25105
|
if (LOG_PATH) {
|
|
25318
25106
|
try {
|
|
25319
|
-
|
|
25107
|
+
import_node_fs4.default.appendFileSync(LOG_PATH, line);
|
|
25320
25108
|
return;
|
|
25321
25109
|
} catch {
|
|
25322
25110
|
}
|
|
@@ -25326,282 +25114,145 @@ function log(msg, info) {
|
|
|
25326
25114
|
} catch {
|
|
25327
25115
|
}
|
|
25328
25116
|
}
|
|
25117
|
+
var logger = { info: log, warn: log };
|
|
25329
25118
|
var ok = (payload) => ({
|
|
25330
|
-
content: [{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload) }]
|
|
25119
|
+
content: [{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2) }]
|
|
25331
25120
|
});
|
|
25332
25121
|
var fail = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
25122
|
+
var errMsg2 = (err) => err instanceof Error ? err.message : String(err);
|
|
25123
|
+
var ENV_NAMES = {
|
|
25124
|
+
root: "CLAWD_APP_BUILDER_ROOT",
|
|
25125
|
+
personaRoot: "CLAWD_APP_BUILDER_PERSONA_ROOT",
|
|
25126
|
+
templateDir: "CLAWD_APP_BUILDER_TEMPLATE_DIR",
|
|
25127
|
+
scaffoldScript: "CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT",
|
|
25128
|
+
publishScript: "CLAWD_APP_BUILDER_PUBLISH_SCRIPT",
|
|
25129
|
+
removeScript: "CLAWD_APP_BUILDER_REMOVE_SCRIPT"
|
|
25130
|
+
};
|
|
25333
25131
|
function readEnv(env = process.env) {
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
25337
|
-
|
|
25338
|
-
...env.CLAWD_PERSONA_ID ? { personaId: env.CLAWD_PERSONA_ID } : {},
|
|
25339
|
-
root: env.CLAWD_APP_BUILDER_ROOT || process.cwd(),
|
|
25340
|
-
...env.CLAWD_APP_BUILDER_TEMPLATE_DIR ? { templateDir: env.CLAWD_APP_BUILDER_TEMPLATE_DIR } : {},
|
|
25341
|
-
...env.CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT ? { scaffoldScript: env.CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT } : {},
|
|
25342
|
-
...env.CLAWD_APP_BUILDER_PUBLISH_SCRIPT ? { publishScript: env.CLAWD_APP_BUILDER_PUBLISH_SCRIPT } : {},
|
|
25343
|
-
...env.CLAWD_APP_BUILDER_PERSONA_ROOT ? { personaRoot: env.CLAWD_APP_BUILDER_PERSONA_ROOT } : {},
|
|
25344
|
-
...env.CLAWD_APP_BUILDER_REMOVE_SCRIPT ? { removeScript: env.CLAWD_APP_BUILDER_REMOVE_SCRIPT } : {}
|
|
25345
|
-
};
|
|
25346
|
-
}
|
|
25347
|
-
function hasDaemon(env) {
|
|
25348
|
-
return Boolean(env.daemonUrl && env.daemonToken);
|
|
25349
|
-
}
|
|
25350
|
-
async function callDaemon(ctx, method, args) {
|
|
25351
|
-
const env = ctx.env;
|
|
25352
|
-
if (!hasDaemon(env)) return fail(`${method}: no daemon`);
|
|
25353
|
-
const f = ctx.fetch ?? fetch;
|
|
25354
|
-
const url = `${env.daemonUrl}/rpc/${method}`;
|
|
25355
|
-
log("daemon call", { method });
|
|
25356
|
-
let res;
|
|
25357
|
-
try {
|
|
25358
|
-
res = await f(url, {
|
|
25359
|
-
method: "POST",
|
|
25360
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${env.daemonToken}` },
|
|
25361
|
-
body: JSON.stringify({ ...env.sessionId ? { sessionId: env.sessionId } : {}, ...args })
|
|
25362
|
-
});
|
|
25363
|
-
} catch (err) {
|
|
25364
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
25365
|
-
log("daemon call threw", { method, msg });
|
|
25366
|
-
return fail(`${method} transport failed (is the daemon up?): ${msg}`);
|
|
25367
|
-
}
|
|
25368
|
-
let json;
|
|
25369
|
-
try {
|
|
25370
|
-
json = await res.json();
|
|
25371
|
-
} catch {
|
|
25372
|
-
log("daemon call non-JSON", { method, status: res.status });
|
|
25373
|
-
return fail(`${method} returned non-JSON (HTTP ${res.status})`);
|
|
25374
|
-
}
|
|
25375
|
-
if (json.ok === false) {
|
|
25376
|
-
log("daemon call rpc error", { method, error: json.error });
|
|
25377
|
-
return fail(`${method} failed: ${json.error}: ${json.message}`);
|
|
25132
|
+
const out = { repoOrg: env.CLAWD_APP_BUILDER_REPO_ORG || PERSONA_REPO_ORG };
|
|
25133
|
+
for (const [key, name] of Object.entries(ENV_NAMES)) {
|
|
25134
|
+
const v = env[name];
|
|
25135
|
+
if (v) out[key] = v;
|
|
25378
25136
|
}
|
|
25379
|
-
|
|
25380
|
-
return ok(json.result);
|
|
25137
|
+
return out;
|
|
25381
25138
|
}
|
|
25382
|
-
function
|
|
25383
|
-
|
|
25139
|
+
function missingEnv(env, keys) {
|
|
25140
|
+
const missing = keys.filter((k) => !env[k]).map((k) => ENV_NAMES[k]);
|
|
25141
|
+
return missing.length ? `env not set: ${missing.join(", ")}` : null;
|
|
25384
25142
|
}
|
|
25385
25143
|
async function handleCreateProject(input, ctx) {
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
return fail(
|
|
25390
|
-
"createProject: no daemon, and CLAWD_APP_BUILDER_TEMPLATE_DIR / CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT are not set; cannot scaffold."
|
|
25391
|
-
);
|
|
25392
|
-
}
|
|
25144
|
+
const miss = missingEnv(ctx.env, ["root", "templateDir", "scaffoldScript"]);
|
|
25145
|
+
if (miss) return fail(`createProject: ${miss}`);
|
|
25146
|
+
const env = ctx.env;
|
|
25393
25147
|
try {
|
|
25394
|
-
const
|
|
25395
|
-
store:
|
|
25148
|
+
const r = await createProject({
|
|
25149
|
+
store: new ProjectStore(env.root),
|
|
25396
25150
|
name: input.name,
|
|
25397
|
-
templateSrcDir: templateDir,
|
|
25398
|
-
scaffoldScript,
|
|
25399
|
-
|
|
25151
|
+
templateSrcDir: env.templateDir,
|
|
25152
|
+
scaffoldScript: env.scaffoldScript,
|
|
25153
|
+
org: env.repoOrg,
|
|
25154
|
+
...ctx.exec ? { exec: ctx.exec } : {},
|
|
25155
|
+
logger
|
|
25156
|
+
});
|
|
25157
|
+
log("createProject done", { name: input.name, projectDir: r.projectDir, repoUrl: r.repoUrl });
|
|
25158
|
+
return ok({
|
|
25159
|
+
name: r.project.name,
|
|
25160
|
+
projectDir: r.projectDir,
|
|
25161
|
+
repoUrl: r.repoUrl,
|
|
25162
|
+
commit: r.commit,
|
|
25163
|
+
next: "cd \u8FDB projectDir \u88C5\u4F9D\u8D56\uFF08server/ \u4E0E web/ \u5404\u4E00\u5957 pnpm install\uFF09\uFF0C\u4E4B\u540E\u6BCF\u5B8C\u6210\u4E00\u6BB5\u5C31 commit + push"
|
|
25400
25164
|
});
|
|
25401
|
-
log("createProject done (no daemon)", { name: input.name, projectDir: created.projectDir });
|
|
25402
|
-
return ok(created);
|
|
25403
25165
|
} catch (err) {
|
|
25404
|
-
return fail(`createProject failed: ${
|
|
25166
|
+
return fail(`createProject failed: ${errMsg2(err)}`);
|
|
25405
25167
|
}
|
|
25406
25168
|
}
|
|
25407
25169
|
async function handleListProjects(ctx) {
|
|
25408
|
-
|
|
25170
|
+
const miss = missingEnv(ctx.env, ["root"]);
|
|
25171
|
+
if (miss) return fail(`listProjects: ${miss}`);
|
|
25409
25172
|
try {
|
|
25410
|
-
|
|
25173
|
+
const store = new ProjectStore(ctx.env.root);
|
|
25174
|
+
const projects = (await store.list()).map((p) => ({ ...p, projectDir: store.projectDir(p.name) }));
|
|
25175
|
+
return ok({ projects });
|
|
25411
25176
|
} catch (err) {
|
|
25412
|
-
return fail(`listProjects failed: ${
|
|
25413
|
-
}
|
|
25414
|
-
}
|
|
25415
|
-
async function handleReportStage(input, ctx) {
|
|
25416
|
-
if (hasDaemon(ctx.env)) {
|
|
25417
|
-
return callDaemon(ctx, "appBuilder:reportStage", {
|
|
25418
|
-
stage: input.stage,
|
|
25419
|
-
...input.reason !== void 0 ? { reason: input.reason } : {}
|
|
25420
|
-
});
|
|
25421
|
-
}
|
|
25422
|
-
log("reportStage skipped (no daemon)", { stage: input.stage });
|
|
25423
|
-
return ok({ ok: true, skipped: "no daemon; stage reporting is a local-UI concern" });
|
|
25424
|
-
}
|
|
25425
|
-
async function handleStartDevServer(input, ctx) {
|
|
25426
|
-
if (hasDaemon(ctx.env)) {
|
|
25427
|
-
return callDaemon(ctx, "appBuilder:startDevServer", {
|
|
25428
|
-
...input.force !== void 0 ? { force: input.force } : {}
|
|
25429
|
-
});
|
|
25177
|
+
return fail(`listProjects failed: ${errMsg2(err)}`);
|
|
25430
25178
|
}
|
|
25431
|
-
return fail(
|
|
25432
|
-
"startDevServer is a local-only capability (daemon dev server supervisor + port pool); it is not available in this environment."
|
|
25433
|
-
);
|
|
25434
25179
|
}
|
|
25435
|
-
async function
|
|
25436
|
-
|
|
25437
|
-
|
|
25180
|
+
async function handlePublish(input, ctx) {
|
|
25181
|
+
const miss = missingEnv(ctx.env, ["root", "publishScript", "personaRoot"]);
|
|
25182
|
+
if (miss) return fail(`publish: ${miss}`);
|
|
25183
|
+
const env = ctx.env;
|
|
25184
|
+
try {
|
|
25185
|
+
const r = await runPublish({
|
|
25186
|
+
store: new ProjectStore(env.root),
|
|
25438
25187
|
name: input.name,
|
|
25439
|
-
|
|
25188
|
+
scriptPath: env.publishScript,
|
|
25189
|
+
personaRoot: env.personaRoot,
|
|
25190
|
+
...ctx.exec ? { exec: ctx.exec } : {},
|
|
25191
|
+
...ctx.spawnImpl ? { spawnImpl: ctx.spawnImpl } : {},
|
|
25192
|
+
logger
|
|
25440
25193
|
});
|
|
25441
|
-
|
|
25442
|
-
|
|
25443
|
-
"updateProjectPort is a local-only capability (dev server port pool); it is not available in this environment."
|
|
25444
|
-
);
|
|
25445
|
-
}
|
|
25446
|
-
async function handleSetProdUrl(input, ctx) {
|
|
25447
|
-
if (hasDaemon(ctx.env)) {
|
|
25448
|
-
return callDaemon(ctx, "appBuilder:setProdUrl", { name: input.name, url: input.url });
|
|
25449
|
-
}
|
|
25450
|
-
try {
|
|
25451
|
-
return ok({ project: await storeOf(ctx.env).setProdUrl(input.name, input.url) });
|
|
25452
|
-
} catch (err) {
|
|
25453
|
-
return fail(`setProdUrl failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
25454
|
-
}
|
|
25455
|
-
}
|
|
25456
|
-
async function handlePublish(input, ctx) {
|
|
25457
|
-
if (hasDaemon(ctx.env)) return callDaemon(ctx, "appBuilder:publish", { name: input.name });
|
|
25458
|
-
const { publishScript, personaRoot } = ctx.env;
|
|
25459
|
-
if (!publishScript) {
|
|
25460
|
-
return fail("publish: no daemon, and CLAWD_APP_BUILDER_PUBLISH_SCRIPT is not set.");
|
|
25461
|
-
}
|
|
25462
|
-
const service = ctx.publishService ?? createPublishService();
|
|
25463
|
-
const store = storeOf(ctx.env);
|
|
25464
|
-
let settle = () => {
|
|
25465
|
-
};
|
|
25466
|
-
const done = new Promise((resolve) => {
|
|
25467
|
-
settle = resolve;
|
|
25468
|
-
});
|
|
25469
|
-
const publishStore = {
|
|
25470
|
-
setPublishJob: (name, job) => store.setPublishJob(name, job),
|
|
25471
|
-
clearPublishJob: (name) => store.clearPublishJob(name),
|
|
25472
|
-
setProdUrl: (name, url) => store.setProdUrl(name, url),
|
|
25473
|
-
projectDir: (name) => store.projectDir(name)
|
|
25474
|
-
};
|
|
25475
|
-
const started = await service.start({
|
|
25476
|
-
name: input.name,
|
|
25477
|
-
sessionId: ctx.env.sessionId ?? "",
|
|
25478
|
-
scriptPath: publishScript,
|
|
25479
|
-
personaRoot: personaRoot ?? "",
|
|
25480
|
-
store: publishStore,
|
|
25481
|
-
hooks: {
|
|
25482
|
-
broadcast: (frame) => log("publish frame", frame),
|
|
25483
|
-
onSettled: (outcome) => {
|
|
25484
|
-
log("publish settled", outcome);
|
|
25485
|
-
if (outcome.kind === "published") {
|
|
25486
|
-
settle(ok({ status: "published", name: input.name, prodUrl: outcome.url }));
|
|
25487
|
-
} else if (outcome.kind === "failed") {
|
|
25488
|
-
settle(fail(`\u53D1\u5E03\u5931\u8D25\uFF1A[${outcome.stage}] \u9636\u6BB5
|
|
25489
|
-
${outcome.errorSummary}`));
|
|
25490
|
-
} else {
|
|
25491
|
-
settle(fail("publish was dismissed before it finished"));
|
|
25492
|
-
}
|
|
25493
|
-
}
|
|
25494
|
-
},
|
|
25495
|
-
logger: {
|
|
25496
|
-
info: (m, i) => log(m, i),
|
|
25497
|
-
warn: (m, i) => log(m, i)
|
|
25194
|
+
if (r.ok) {
|
|
25195
|
+
return ok({ status: "published", name: input.name, prodUrl: r.prodUrl, commit: r.commit, publishedAt: r.publishedAt });
|
|
25498
25196
|
}
|
|
25499
|
-
|
|
25500
|
-
|
|
25501
|
-
|
|
25197
|
+
return fail(`\u53D1\u5E03\u5931\u8D25\uFF1A[${r.stage}] \u9636\u6BB5
|
|
25198
|
+
${r.errorSummary}
|
|
25199
|
+
|
|
25200
|
+
\u5B8C\u6574\u65E5\u5FD7\uFF1A${r.logPath}`);
|
|
25201
|
+
} catch (err) {
|
|
25202
|
+
return fail(`publish failed: ${errMsg2(err)}`);
|
|
25502
25203
|
}
|
|
25503
|
-
return await done;
|
|
25504
25204
|
}
|
|
25505
25205
|
async function handleRemoveProject(input, ctx) {
|
|
25506
|
-
|
|
25507
|
-
|
|
25508
|
-
|
|
25509
|
-
|
|
25510
|
-
}
|
|
25511
|
-
const { removeScript, personaRoot } = ctx.env;
|
|
25512
|
-
if (!removeScript) {
|
|
25513
|
-
return fail("removeProject: no daemon, and CLAWD_APP_BUILDER_REMOVE_SCRIPT is not set.");
|
|
25514
|
-
}
|
|
25515
|
-
const store = storeOf(ctx.env);
|
|
25206
|
+
const miss = missingEnv(ctx.env, ["root", "removeScript", "personaRoot"]);
|
|
25207
|
+
if (miss) return fail(`removeProject: ${miss}`);
|
|
25208
|
+
const env = ctx.env;
|
|
25209
|
+
const store = new ProjectStore(env.root);
|
|
25516
25210
|
try {
|
|
25517
|
-
|
|
25211
|
+
if (!await store.get(input.name)) return fail(`removeProject: project "${input.name}" not found / \u4E0D\u5B58\u5728`);
|
|
25518
25212
|
const r = await runRemoveExtension({
|
|
25519
|
-
script: removeScript,
|
|
25520
|
-
projectDir,
|
|
25521
|
-
personaRoot: personaRoot
|
|
25522
|
-
|
|
25213
|
+
script: env.removeScript,
|
|
25214
|
+
projectDir: store.projectDir(input.name),
|
|
25215
|
+
personaRoot: env.personaRoot,
|
|
25216
|
+
...ctx.spawnImpl ? { spawnImpl: ctx.spawnImpl } : {},
|
|
25217
|
+
logger
|
|
25523
25218
|
});
|
|
25524
25219
|
const text = `exit=${r.code}
|
|
25525
25220
|
--- stdout ---
|
|
25526
25221
|
${r.stdout}
|
|
25527
25222
|
--- stderr ---
|
|
25528
25223
|
${r.stderr}`;
|
|
25529
|
-
|
|
25224
|
+
if (r.code !== 0) return fail(text);
|
|
25225
|
+
await store.delete(input.name);
|
|
25226
|
+
return ok(`${text}
|
|
25227
|
+
\u672C\u5730\u76EE\u5F55\u5DF2\u5220\u9664\uFF1BGitHub \u4ED3\u5E93\u4FDD\u7559\uFF0C\u8981\u5220\u81EA\u5DF1\u53BB GitHub \u5F52\u6863\u3002`);
|
|
25530
25228
|
} catch (err) {
|
|
25531
|
-
return fail(`removeProject failed: ${
|
|
25229
|
+
return fail(`removeProject failed: ${errMsg2(err)}`);
|
|
25532
25230
|
}
|
|
25533
25231
|
}
|
|
25534
25232
|
function registerTools(server, ctx) {
|
|
25535
25233
|
server.registerTool(
|
|
25536
25234
|
"createProject",
|
|
25537
25235
|
{
|
|
25538
|
-
title: "Create
|
|
25539
|
-
description: "Create a new app
|
|
25540
|
-
inputSchema: { name: external_exports.string().min(1).describe("project name given by the user") }
|
|
25236
|
+
title: "Create an app project (scaffold + git + GitHub repo)",
|
|
25237
|
+
description: "Create a new app project in the current user workspace: scaffold the starter template, `git init` with a first commit, and create a private GitHub repo under the org and push to it. This is the ONLY way to create a project \u2014 never mkdir one yourself, and never trust a path suggested elsewhere. Returns { name, projectDir, repoUrl, commit }; projectDir is the absolute path you must cd into. Fails (and rolls back) if the name is taken, the scaffold fails, or gh is not installed / logged in.",
|
|
25238
|
+
inputSchema: { name: external_exports.string().min(1).describe("project name given by the user (kebab-case)") }
|
|
25541
25239
|
},
|
|
25542
25240
|
async (input) => handleCreateProject(input, ctx)
|
|
25543
25241
|
);
|
|
25544
25242
|
server.registerTool(
|
|
25545
25243
|
"listProjects",
|
|
25546
25244
|
{
|
|
25547
|
-
title: "List app
|
|
25548
|
-
description: "List the
|
|
25245
|
+
title: "List app projects",
|
|
25246
|
+
description: "List the projects in the current user workspace: name / projectDir / repoUrl / prodUrl / publishedAt / publishedCommit. Use it to find the directory of an existing app before changing it, or to check for a name clash before createProject. A project without repoUrl predates git support.",
|
|
25549
25247
|
inputSchema: {}
|
|
25550
25248
|
},
|
|
25551
25249
|
async () => handleListProjects(ctx)
|
|
25552
25250
|
);
|
|
25553
|
-
server.registerTool(
|
|
25554
|
-
"reportStage",
|
|
25555
|
-
{
|
|
25556
|
-
title: "Report scaffolding stage",
|
|
25557
|
-
description: 'Report the current stage of the project so the UI preview pane can follow along. Only "installing" and "failed" are reportable by the assistant; the rest are set by the host. Pass reason when failing. Silently skipped in environments with no UI.',
|
|
25558
|
-
inputSchema: {
|
|
25559
|
-
stage: external_exports.string().describe('"installing" or "failed"'),
|
|
25560
|
-
reason: external_exports.string().optional().describe('one-line cause, required when stage is "failed"')
|
|
25561
|
-
}
|
|
25562
|
-
},
|
|
25563
|
-
async (input) => handleReportStage(input, ctx)
|
|
25564
|
-
);
|
|
25565
|
-
server.registerTool(
|
|
25566
|
-
"startDevServer",
|
|
25567
|
-
{
|
|
25568
|
-
title: "Start the project dev server",
|
|
25569
|
-
description: "Start the dev server for this session project after dependencies are installed. Idempotent: a running server is left alone unless force is true. Local-only capability \u2014 unavailable when there is no host daemon.",
|
|
25570
|
-
inputSchema: {
|
|
25571
|
-
force: external_exports.boolean().optional().describe("stop and respawn even if it is already running")
|
|
25572
|
-
}
|
|
25573
|
-
},
|
|
25574
|
-
async (input) => handleStartDevServer(input, ctx)
|
|
25575
|
-
);
|
|
25576
|
-
server.registerTool(
|
|
25577
|
-
"updateProjectPort",
|
|
25578
|
-
{
|
|
25579
|
-
title: "Change the dev server port of a project",
|
|
25580
|
-
description: "Change which local port this project's dev server listens on. Normally the user does this from the project settings menu \u2014 only call it when they ask you to. Local-only capability.",
|
|
25581
|
-
inputSchema: {
|
|
25582
|
-
name: external_exports.string().min(1).describe("project name"),
|
|
25583
|
-
newPort: external_exports.number().int().describe("new port, must be inside the app-builder port range")
|
|
25584
|
-
}
|
|
25585
|
-
},
|
|
25586
|
-
async (input) => handleUpdateProjectPort(input, ctx)
|
|
25587
|
-
);
|
|
25588
|
-
server.registerTool(
|
|
25589
|
-
"setProdUrl",
|
|
25590
|
-
{
|
|
25591
|
-
title: "Record the public URL of a published project",
|
|
25592
|
-
description: "Write the public URL of a successfully published project into its .clawd-project.json so the preview switches to the live site. Call it after a publish that you drove by hand.",
|
|
25593
|
-
inputSchema: {
|
|
25594
|
-
name: external_exports.string().min(1).describe("project name"),
|
|
25595
|
-
url: external_exports.string().url().describe("public https URL")
|
|
25596
|
-
}
|
|
25597
|
-
},
|
|
25598
|
-
async (input) => handleSetProdUrl(input, ctx)
|
|
25599
|
-
);
|
|
25600
25251
|
server.registerTool(
|
|
25601
25252
|
"publish",
|
|
25602
25253
|
{
|
|
25603
25254
|
title: "Publish the project to the public internet",
|
|
25604
|
-
description: "Run the publish pipeline (build \u2192 deploy \u2192 verify) for
|
|
25255
|
+
description: "Run the publish pipeline (build \u2192 deploy to Aliyun FC \u2192 verify) and wait for it to finish. Returns the public URL (and the commit it corresponds to) on success; on failure returns the failed stage, the error tail and the path of the full log. Refuses to run with uncommitted changes \u2014 commit and push first. Never run publish.sh yourself: the credentials only exist in this tool's process.",
|
|
25605
25256
|
inputSchema: { name: external_exports.string().min(1).describe("project name") }
|
|
25606
25257
|
},
|
|
25607
25258
|
async (input) => handlePublish(input, ctx)
|
|
@@ -25609,8 +25260,8 @@ function registerTools(server, ctx) {
|
|
|
25609
25260
|
server.registerTool(
|
|
25610
25261
|
"removeProject",
|
|
25611
25262
|
{
|
|
25612
|
-
title: "Tear down a
|
|
25613
|
-
description: "Tear down everything a published project created (FC function / trigger / custom domain, plus the DROP statements for its Supabase tables)
|
|
25263
|
+
title: "Tear down a project",
|
|
25264
|
+
description: "Tear down everything a published project created (FC function / trigger / custom domain, plus the DROP statements for its Supabase tables) and then delete the local project directory. The GitHub repo is kept. Returns the script output verbatim \u2014 read it, it may ask you to run a few DROPs through the supabase MCP. Confirm with the user before calling.",
|
|
25614
25265
|
inputSchema: { name: external_exports.string().min(1).describe("project name") }
|
|
25615
25266
|
},
|
|
25616
25267
|
async (input) => handleRemoveProject(input, ctx)
|
|
@@ -25618,13 +25269,8 @@ function registerTools(server, ctx) {
|
|
|
25618
25269
|
}
|
|
25619
25270
|
async function main() {
|
|
25620
25271
|
const env = readEnv();
|
|
25621
|
-
log("boot", {
|
|
25622
|
-
|
|
25623
|
-
mode: hasDaemon(env) ? "daemon" : "standalone",
|
|
25624
|
-
personaId: env.personaId ?? "(none)",
|
|
25625
|
-
root: env.root
|
|
25626
|
-
});
|
|
25627
|
-
const server = new McpServer({ name: "clawd-app-builder", version: "0.1.0" });
|
|
25272
|
+
log("boot", { pid: process.pid, root: env.root ?? "(unset)", personaRoot: env.personaRoot ?? "(unset)" });
|
|
25273
|
+
const server = new McpServer({ name: "clawd-app-builder", version: "0.2.0" });
|
|
25628
25274
|
registerTools(server, { env });
|
|
25629
25275
|
await server.connect(new StdioServerTransport());
|
|
25630
25276
|
}
|
|
@@ -25642,10 +25288,6 @@ if (isMainModule) {
|
|
|
25642
25288
|
handleListProjects,
|
|
25643
25289
|
handlePublish,
|
|
25644
25290
|
handleRemoveProject,
|
|
25645
|
-
handleReportStage,
|
|
25646
|
-
handleSetProdUrl,
|
|
25647
|
-
handleStartDevServer,
|
|
25648
|
-
handleUpdateProjectPort,
|
|
25649
25291
|
readEnv,
|
|
25650
25292
|
registerTools
|
|
25651
25293
|
});
|