@clawos-dev/clawd 0.2.121-beta.238.7b49a98 → 0.2.122-beta.241.0e144f4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +736 -97
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +12 -1
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +17 -2
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +1 -1
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +18 -2
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/verify.sh +20 -2
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -202,7 +202,16 @@ var init_methods = __esm({
|
|
|
202
202
|
"appBuilder:reportStage",
|
|
203
203
|
// 显式控制 fix (spec 2026-06-02-app-builder-explicit-dev-server-control):用户点"收起预览"按钮触发,
|
|
204
204
|
// 对称 startDevServer。砍掉了 setActiveSession(dev server 不再跟用户 focus 走),切 session 不动 dev server。
|
|
205
|
-
"appBuilder:stopDevServer"
|
|
205
|
+
"appBuilder:stopDevServer",
|
|
206
|
+
// 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design):
|
|
207
|
+
// publish:PreviewPane「发布上线」按钮 onClick 调本 RPC,daemon 内串行化 + spawn publish.sh +
|
|
208
|
+
// 广播阶段进度帧 (appBuilder:publish-progress / publish-failed)。同 project 已有 in-flight job
|
|
209
|
+
// 返回 already-publishing + 既有 jobId(UI 不报错继续订阅)。失败时 daemon 调 manager.send()
|
|
210
|
+
// 注入 user_text 到 chat,由 assistant 接管完成部署。
|
|
211
|
+
// dismissPublishJob:取消 in-flight publish(SIGTERM 子进程 + 清字段)或清掉 daemon 崩溃留下的
|
|
212
|
+
// 中断残骸 publishJob 字段。UI 端 [取消] / [interrupted 取消] 都走这个 RPC。
|
|
213
|
+
"appBuilder:publish",
|
|
214
|
+
"appBuilder:dismissPublishJob"
|
|
206
215
|
];
|
|
207
216
|
}
|
|
208
217
|
});
|
|
@@ -4548,7 +4557,7 @@ var init_persona_schemas = __esm({
|
|
|
4548
4557
|
});
|
|
4549
4558
|
|
|
4550
4559
|
// ../protocol/src/schemas.ts
|
|
4551
|
-
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema;
|
|
4560
|
+
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
|
|
4552
4561
|
var init_schemas = __esm({
|
|
4553
4562
|
"../protocol/src/schemas.ts"() {
|
|
4554
4563
|
"use strict";
|
|
@@ -5225,7 +5234,22 @@ var init_schemas = __esm({
|
|
|
5225
5234
|
* optional:缺省时 supervisor 用默认 /Local:\s+https?:\/\//(vite "Local: http://..." 输出)。
|
|
5226
5235
|
* 老板换非 vite 技术栈(Next.js "Ready in"、webpack "Compiled" 等)时自定义这个字段。
|
|
5227
5236
|
*/
|
|
5228
|
-
readyPattern: external_exports.string().optional()
|
|
5237
|
+
readyPattern: external_exports.string().optional(),
|
|
5238
|
+
/**
|
|
5239
|
+
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
|
|
5240
|
+
* in-flight 发布 job 的元数据。仅承载"in-flight"或"中断残骸"两种状态:
|
|
5241
|
+
* - daemon spawn publish.sh + 收到第一个 ::stage::build marker 时写入
|
|
5242
|
+
* - 每次 stage 切换更新 stage 字段
|
|
5243
|
+
* - 成功 / 正常失败 / 用户 dismiss 时立即清掉
|
|
5244
|
+
* - 只有 daemon 崩才会留残骸(子进程随 daemon 一起死)
|
|
5245
|
+
* status 字段在 ProjectWithStatus 派生而不在这里 —— 本 schema 是落盘形态,
|
|
5246
|
+
* "in-flight vs interrupted" 区分由 daemon 响应时实时拼 registry 状态得出。
|
|
5247
|
+
*/
|
|
5248
|
+
publishJob: external_exports.object({
|
|
5249
|
+
jobId: external_exports.string().min(1),
|
|
5250
|
+
stage: external_exports.enum(["build", "deploy", "verify"]),
|
|
5251
|
+
startedAt: external_exports.number().int().nonnegative()
|
|
5252
|
+
}).optional()
|
|
5229
5253
|
});
|
|
5230
5254
|
PROJECT_STAGE_VALUES = [
|
|
5231
5255
|
"install-pending",
|
|
@@ -5252,7 +5276,21 @@ var init_schemas = __esm({
|
|
|
5252
5276
|
*/
|
|
5253
5277
|
stage: ProjectStageSchema.optional(),
|
|
5254
5278
|
/** stage='failed' 时携带失败原因 */
|
|
5255
|
-
stageReason: external_exports.string().optional()
|
|
5279
|
+
stageReason: external_exports.string().optional(),
|
|
5280
|
+
/**
|
|
5281
|
+
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.3):
|
|
5282
|
+
* ProjectMetadata.publishJob 的 status 派生版。daemon 响应时按以下规则实时拼装:
|
|
5283
|
+
* - 落盘字段存在 + PublishJobRegistry 有匹配 jobId → 'in-flight'
|
|
5284
|
+
* - 落盘字段存在 + Registry 无匹配(jobId 对不上或 entry 缺失)→ 'interrupted'(daemon 崩残骸)
|
|
5285
|
+
* - 落盘字段不存在 → undefined(字段省略)
|
|
5286
|
+
* UI 用 status 决定 PreviewPane 渲染分支(in-flight 三段 checklist / interrupted 恢复条)。
|
|
5287
|
+
*/
|
|
5288
|
+
publishJob: external_exports.object({
|
|
5289
|
+
jobId: external_exports.string().min(1),
|
|
5290
|
+
stage: external_exports.enum(["build", "deploy", "verify"]),
|
|
5291
|
+
startedAt: external_exports.number().int().nonnegative(),
|
|
5292
|
+
status: external_exports.enum(["in-flight", "interrupted"])
|
|
5293
|
+
}).optional()
|
|
5256
5294
|
});
|
|
5257
5295
|
ListProjectsArgsSchema = external_exports.object({}).strict();
|
|
5258
5296
|
ListProjectsResultSchema = external_exports.object({
|
|
@@ -5317,6 +5355,33 @@ var init_schemas = __esm({
|
|
|
5317
5355
|
/** stage='failed' 时的失败原因 */
|
|
5318
5356
|
stageReason: external_exports.string().optional()
|
|
5319
5357
|
}).strict();
|
|
5358
|
+
PublishArgsSchema = external_exports.object({
|
|
5359
|
+
name: external_exports.string().regex(projectNameRegex)
|
|
5360
|
+
});
|
|
5361
|
+
PublishResultSchema = external_exports.object({
|
|
5362
|
+
jobId: external_exports.string().min(1),
|
|
5363
|
+
status: external_exports.enum(["started", "already-publishing"])
|
|
5364
|
+
}).strict();
|
|
5365
|
+
DismissPublishJobArgsSchema = external_exports.object({
|
|
5366
|
+
name: external_exports.string().regex(projectNameRegex)
|
|
5367
|
+
});
|
|
5368
|
+
DismissPublishJobResultSchema = external_exports.object({
|
|
5369
|
+
ok: external_exports.literal(true)
|
|
5370
|
+
}).strict();
|
|
5371
|
+
AppBuilderPublishProgressEventSchema = external_exports.object({
|
|
5372
|
+
type: external_exports.literal("appBuilder:publish-progress"),
|
|
5373
|
+
name: external_exports.string().regex(projectNameRegex),
|
|
5374
|
+
jobId: external_exports.string().min(1),
|
|
5375
|
+
stage: external_exports.enum(["build", "deploy", "verify"]),
|
|
5376
|
+
status: external_exports.enum(["started", "completed"])
|
|
5377
|
+
}).strict();
|
|
5378
|
+
AppBuilderPublishFailedEventSchema = external_exports.object({
|
|
5379
|
+
type: external_exports.literal("appBuilder:publish-failed"),
|
|
5380
|
+
name: external_exports.string().regex(projectNameRegex),
|
|
5381
|
+
jobId: external_exports.string().min(1),
|
|
5382
|
+
stage: external_exports.enum(["build", "deploy", "verify", "unknown"]),
|
|
5383
|
+
errorSummary: external_exports.string()
|
|
5384
|
+
}).strict();
|
|
5320
5385
|
}
|
|
5321
5386
|
});
|
|
5322
5387
|
|
|
@@ -7746,7 +7811,7 @@ var require_thread_stream = __commonJS({
|
|
|
7746
7811
|
var { version: version2 } = require_package();
|
|
7747
7812
|
var { EventEmitter: EventEmitter3 } = require("events");
|
|
7748
7813
|
var { Worker } = require("worker_threads");
|
|
7749
|
-
var { join:
|
|
7814
|
+
var { join: join14 } = require("path");
|
|
7750
7815
|
var { pathToFileURL } = require("url");
|
|
7751
7816
|
var { wait } = require_wait();
|
|
7752
7817
|
var {
|
|
@@ -7782,7 +7847,7 @@ var require_thread_stream = __commonJS({
|
|
|
7782
7847
|
function createWorker(stream, opts) {
|
|
7783
7848
|
const { filename, workerData } = opts;
|
|
7784
7849
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7785
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7850
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join14(__dirname, "lib", "worker.js");
|
|
7786
7851
|
const worker = new Worker(toExecute, {
|
|
7787
7852
|
...opts.workerOpts,
|
|
7788
7853
|
trackUnmanagedFds: false,
|
|
@@ -8168,7 +8233,7 @@ var require_transport = __commonJS({
|
|
|
8168
8233
|
"use strict";
|
|
8169
8234
|
var { createRequire } = require("module");
|
|
8170
8235
|
var getCallers = require_caller();
|
|
8171
|
-
var { join:
|
|
8236
|
+
var { join: join14, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8172
8237
|
var sleep = require_atomic_sleep();
|
|
8173
8238
|
var onExit = require_on_exit_leak_free();
|
|
8174
8239
|
var ThreadStream = require_thread_stream();
|
|
@@ -8231,7 +8296,7 @@ var require_transport = __commonJS({
|
|
|
8231
8296
|
throw new Error("only one of target or targets can be specified");
|
|
8232
8297
|
}
|
|
8233
8298
|
if (targets) {
|
|
8234
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8299
|
+
target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
|
|
8235
8300
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8236
8301
|
return {
|
|
8237
8302
|
...dest,
|
|
@@ -8249,7 +8314,7 @@ var require_transport = __commonJS({
|
|
|
8249
8314
|
});
|
|
8250
8315
|
});
|
|
8251
8316
|
} else if (pipeline3) {
|
|
8252
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8317
|
+
target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
|
|
8253
8318
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8254
8319
|
return {
|
|
8255
8320
|
...dest,
|
|
@@ -8271,7 +8336,7 @@ var require_transport = __commonJS({
|
|
|
8271
8336
|
return origin;
|
|
8272
8337
|
}
|
|
8273
8338
|
if (origin === "pino/file") {
|
|
8274
|
-
return
|
|
8339
|
+
return join14(__dirname, "..", "file.js");
|
|
8275
8340
|
}
|
|
8276
8341
|
let fixTarget2;
|
|
8277
8342
|
for (const filePath of callers) {
|
|
@@ -9261,7 +9326,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9261
9326
|
return circularValue;
|
|
9262
9327
|
}
|
|
9263
9328
|
let res = "";
|
|
9264
|
-
let
|
|
9329
|
+
let join14 = ",";
|
|
9265
9330
|
const originalIndentation = indentation;
|
|
9266
9331
|
if (Array.isArray(value)) {
|
|
9267
9332
|
if (value.length === 0) {
|
|
@@ -9275,7 +9340,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9275
9340
|
indentation += spacer;
|
|
9276
9341
|
res += `
|
|
9277
9342
|
${indentation}`;
|
|
9278
|
-
|
|
9343
|
+
join14 = `,
|
|
9279
9344
|
${indentation}`;
|
|
9280
9345
|
}
|
|
9281
9346
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9283,13 +9348,13 @@ ${indentation}`;
|
|
|
9283
9348
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9284
9349
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9285
9350
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9286
|
-
res +=
|
|
9351
|
+
res += join14;
|
|
9287
9352
|
}
|
|
9288
9353
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9289
9354
|
res += tmp !== void 0 ? tmp : "null";
|
|
9290
9355
|
if (value.length - 1 > maximumBreadth) {
|
|
9291
9356
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9292
|
-
res += `${
|
|
9357
|
+
res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9293
9358
|
}
|
|
9294
9359
|
if (spacer !== "") {
|
|
9295
9360
|
res += `
|
|
@@ -9310,7 +9375,7 @@ ${originalIndentation}`;
|
|
|
9310
9375
|
let separator = "";
|
|
9311
9376
|
if (spacer !== "") {
|
|
9312
9377
|
indentation += spacer;
|
|
9313
|
-
|
|
9378
|
+
join14 = `,
|
|
9314
9379
|
${indentation}`;
|
|
9315
9380
|
whitespace = " ";
|
|
9316
9381
|
}
|
|
@@ -9324,13 +9389,13 @@ ${indentation}`;
|
|
|
9324
9389
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9325
9390
|
if (tmp !== void 0) {
|
|
9326
9391
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9327
|
-
separator =
|
|
9392
|
+
separator = join14;
|
|
9328
9393
|
}
|
|
9329
9394
|
}
|
|
9330
9395
|
if (keyLength > maximumBreadth) {
|
|
9331
9396
|
const removedKeys = keyLength - maximumBreadth;
|
|
9332
9397
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9333
|
-
separator =
|
|
9398
|
+
separator = join14;
|
|
9334
9399
|
}
|
|
9335
9400
|
if (spacer !== "" && separator.length > 1) {
|
|
9336
9401
|
res = `
|
|
@@ -9371,7 +9436,7 @@ ${originalIndentation}`;
|
|
|
9371
9436
|
}
|
|
9372
9437
|
const originalIndentation = indentation;
|
|
9373
9438
|
let res = "";
|
|
9374
|
-
let
|
|
9439
|
+
let join14 = ",";
|
|
9375
9440
|
if (Array.isArray(value)) {
|
|
9376
9441
|
if (value.length === 0) {
|
|
9377
9442
|
return "[]";
|
|
@@ -9384,7 +9449,7 @@ ${originalIndentation}`;
|
|
|
9384
9449
|
indentation += spacer;
|
|
9385
9450
|
res += `
|
|
9386
9451
|
${indentation}`;
|
|
9387
|
-
|
|
9452
|
+
join14 = `,
|
|
9388
9453
|
${indentation}`;
|
|
9389
9454
|
}
|
|
9390
9455
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9392,13 +9457,13 @@ ${indentation}`;
|
|
|
9392
9457
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9393
9458
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9394
9459
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9395
|
-
res +=
|
|
9460
|
+
res += join14;
|
|
9396
9461
|
}
|
|
9397
9462
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9398
9463
|
res += tmp !== void 0 ? tmp : "null";
|
|
9399
9464
|
if (value.length - 1 > maximumBreadth) {
|
|
9400
9465
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9401
|
-
res += `${
|
|
9466
|
+
res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9402
9467
|
}
|
|
9403
9468
|
if (spacer !== "") {
|
|
9404
9469
|
res += `
|
|
@@ -9411,7 +9476,7 @@ ${originalIndentation}`;
|
|
|
9411
9476
|
let whitespace = "";
|
|
9412
9477
|
if (spacer !== "") {
|
|
9413
9478
|
indentation += spacer;
|
|
9414
|
-
|
|
9479
|
+
join14 = `,
|
|
9415
9480
|
${indentation}`;
|
|
9416
9481
|
whitespace = " ";
|
|
9417
9482
|
}
|
|
@@ -9420,7 +9485,7 @@ ${indentation}`;
|
|
|
9420
9485
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9421
9486
|
if (tmp !== void 0) {
|
|
9422
9487
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9423
|
-
separator =
|
|
9488
|
+
separator = join14;
|
|
9424
9489
|
}
|
|
9425
9490
|
}
|
|
9426
9491
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9478,20 +9543,20 @@ ${originalIndentation}`;
|
|
|
9478
9543
|
indentation += spacer;
|
|
9479
9544
|
let res2 = `
|
|
9480
9545
|
${indentation}`;
|
|
9481
|
-
const
|
|
9546
|
+
const join15 = `,
|
|
9482
9547
|
${indentation}`;
|
|
9483
9548
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9484
9549
|
let i = 0;
|
|
9485
9550
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9486
9551
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9487
9552
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9488
|
-
res2 +=
|
|
9553
|
+
res2 += join15;
|
|
9489
9554
|
}
|
|
9490
9555
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9491
9556
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9492
9557
|
if (value.length - 1 > maximumBreadth) {
|
|
9493
9558
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9494
|
-
res2 += `${
|
|
9559
|
+
res2 += `${join15}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9495
9560
|
}
|
|
9496
9561
|
res2 += `
|
|
9497
9562
|
${originalIndentation}`;
|
|
@@ -9507,16 +9572,16 @@ ${originalIndentation}`;
|
|
|
9507
9572
|
return '"[Object]"';
|
|
9508
9573
|
}
|
|
9509
9574
|
indentation += spacer;
|
|
9510
|
-
const
|
|
9575
|
+
const join14 = `,
|
|
9511
9576
|
${indentation}`;
|
|
9512
9577
|
let res = "";
|
|
9513
9578
|
let separator = "";
|
|
9514
9579
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9515
9580
|
if (isTypedArrayWithEntries(value)) {
|
|
9516
|
-
res += stringifyTypedArray(value,
|
|
9581
|
+
res += stringifyTypedArray(value, join14, maximumBreadth);
|
|
9517
9582
|
keys = keys.slice(value.length);
|
|
9518
9583
|
maximumPropertiesToStringify -= value.length;
|
|
9519
|
-
separator =
|
|
9584
|
+
separator = join14;
|
|
9520
9585
|
}
|
|
9521
9586
|
if (deterministic) {
|
|
9522
9587
|
keys = sort(keys, comparator);
|
|
@@ -9527,13 +9592,13 @@ ${indentation}`;
|
|
|
9527
9592
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9528
9593
|
if (tmp !== void 0) {
|
|
9529
9594
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9530
|
-
separator =
|
|
9595
|
+
separator = join14;
|
|
9531
9596
|
}
|
|
9532
9597
|
}
|
|
9533
9598
|
if (keyLength > maximumBreadth) {
|
|
9534
9599
|
const removedKeys = keyLength - maximumBreadth;
|
|
9535
9600
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9536
|
-
separator =
|
|
9601
|
+
separator = join14;
|
|
9537
9602
|
}
|
|
9538
9603
|
if (separator !== "") {
|
|
9539
9604
|
res = `
|
|
@@ -21120,7 +21185,7 @@ var require_BufferList = __commonJS({
|
|
|
21120
21185
|
this.head = this.tail = null;
|
|
21121
21186
|
this.length = 0;
|
|
21122
21187
|
};
|
|
21123
|
-
BufferList.prototype.join = function
|
|
21188
|
+
BufferList.prototype.join = function join14(s) {
|
|
21124
21189
|
if (this.length === 0) return "";
|
|
21125
21190
|
var p2 = this.head;
|
|
21126
21191
|
var ret = "" + p2.data;
|
|
@@ -30577,7 +30642,7 @@ var require_lib3 = __commonJS({
|
|
|
30577
30642
|
// src/run-case/recorder.ts
|
|
30578
30643
|
function startRunCaseRecorder(opts) {
|
|
30579
30644
|
const now = opts.now ?? Date.now;
|
|
30580
|
-
const dir =
|
|
30645
|
+
const dir = import_node_path48.default.dirname(opts.recordPath);
|
|
30581
30646
|
let stream = null;
|
|
30582
30647
|
let closing = false;
|
|
30583
30648
|
let closedSettled = false;
|
|
@@ -30591,8 +30656,8 @@ function startRunCaseRecorder(opts) {
|
|
|
30591
30656
|
});
|
|
30592
30657
|
const ensureStream = () => {
|
|
30593
30658
|
if (stream) return stream;
|
|
30594
|
-
|
|
30595
|
-
stream =
|
|
30659
|
+
import_node_fs32.default.mkdirSync(dir, { recursive: true });
|
|
30660
|
+
stream = import_node_fs32.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
30596
30661
|
stream.on("close", () => closedResolve());
|
|
30597
30662
|
return stream;
|
|
30598
30663
|
};
|
|
@@ -30617,12 +30682,12 @@ function startRunCaseRecorder(opts) {
|
|
|
30617
30682
|
};
|
|
30618
30683
|
return { tap, close, closed };
|
|
30619
30684
|
}
|
|
30620
|
-
var
|
|
30685
|
+
var import_node_fs32, import_node_path48;
|
|
30621
30686
|
var init_recorder = __esm({
|
|
30622
30687
|
"src/run-case/recorder.ts"() {
|
|
30623
30688
|
"use strict";
|
|
30624
|
-
|
|
30625
|
-
|
|
30689
|
+
import_node_fs32 = __toESM(require("fs"), 1);
|
|
30690
|
+
import_node_path48 = __toESM(require("path"), 1);
|
|
30626
30691
|
}
|
|
30627
30692
|
});
|
|
30628
30693
|
|
|
@@ -30665,7 +30730,7 @@ var init_wire = __esm({
|
|
|
30665
30730
|
// src/run-case/controller.ts
|
|
30666
30731
|
async function runController(opts) {
|
|
30667
30732
|
const now = opts.now ?? Date.now;
|
|
30668
|
-
const cwd = opts.cwd ?? (0,
|
|
30733
|
+
const cwd = opts.cwd ?? (0, import_node_fs33.mkdtempSync)(import_node_path49.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
|
|
30669
30734
|
const ownsCwd = opts.cwd === void 0;
|
|
30670
30735
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
30671
30736
|
const spawnCtx = { cwd };
|
|
@@ -30826,19 +30891,19 @@ async function runController(opts) {
|
|
|
30826
30891
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
30827
30892
|
if (ownsCwd) {
|
|
30828
30893
|
try {
|
|
30829
|
-
(0,
|
|
30894
|
+
(0, import_node_fs33.rmSync)(cwd, { recursive: true, force: true });
|
|
30830
30895
|
} catch {
|
|
30831
30896
|
}
|
|
30832
30897
|
}
|
|
30833
30898
|
return exitCode ?? 0;
|
|
30834
30899
|
}
|
|
30835
|
-
var
|
|
30900
|
+
var import_node_fs33, import_node_os19, import_node_path49;
|
|
30836
30901
|
var init_controller = __esm({
|
|
30837
30902
|
"src/run-case/controller.ts"() {
|
|
30838
30903
|
"use strict";
|
|
30839
|
-
|
|
30904
|
+
import_node_fs33 = require("fs");
|
|
30840
30905
|
import_node_os19 = __toESM(require("os"), 1);
|
|
30841
|
-
|
|
30906
|
+
import_node_path49 = __toESM(require("path"), 1);
|
|
30842
30907
|
init_claude();
|
|
30843
30908
|
init_stdout_splitter();
|
|
30844
30909
|
init_permission_stdio();
|
|
@@ -31073,8 +31138,8 @@ Env (advanced):
|
|
|
31073
31138
|
`;
|
|
31074
31139
|
|
|
31075
31140
|
// src/index.ts
|
|
31076
|
-
var
|
|
31077
|
-
var
|
|
31141
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
31142
|
+
var import_node_fs31 = __toESM(require("fs"), 1);
|
|
31078
31143
|
|
|
31079
31144
|
// src/logger.ts
|
|
31080
31145
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -32881,6 +32946,14 @@ var SessionManager = class {
|
|
|
32881
32946
|
// guest sessions 由 handlers/session.ts list 处按 ctx.principal.kind === 'guest' 做权限过滤
|
|
32882
32947
|
// (creatorPrincipalId === capId), 这里仅负责数据源汇总.
|
|
32883
32948
|
// listener sub-session 不走这条(listener 走 persona:listSubSessions RPC 单独入口)。
|
|
32949
|
+
/**
|
|
32950
|
+
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.2.2):
|
|
32951
|
+
* 反查绑定到指定 app-builder project 的 session。按 invariant "1 Session ↔ 1 Project"
|
|
32952
|
+
* 应当唯一;找不到 → undefined(项目已成孤儿)。
|
|
32953
|
+
*/
|
|
32954
|
+
findOwnedSessionByAppBuilderProject(projectName) {
|
|
32955
|
+
return this.listAllOwned().find((file) => file.appBuilderProject === projectName);
|
|
32956
|
+
}
|
|
32884
32957
|
listAllOwned() {
|
|
32885
32958
|
const out = [];
|
|
32886
32959
|
out.push(...this.deps.store.list());
|
|
@@ -34853,6 +34926,34 @@ function copyBundleExtras(srcDir, dstDir) {
|
|
|
34853
34926
|
}
|
|
34854
34927
|
}
|
|
34855
34928
|
}
|
|
34929
|
+
var DAEMON_MANAGED_DIRS = ["extension-kit"];
|
|
34930
|
+
function refreshDaemonManagedDirs(args) {
|
|
34931
|
+
const entries = args.entries ?? DEFAULT_PERSONAS;
|
|
34932
|
+
for (const entry of entries) {
|
|
34933
|
+
const bundleDir = path11.join(args.defaultsRoot, entry.personaId);
|
|
34934
|
+
if (!fs9.existsSync(bundleDir)) continue;
|
|
34935
|
+
const personaDir = args.store.personaDirPath(entry.personaId);
|
|
34936
|
+
if (!fs9.existsSync(personaDir)) continue;
|
|
34937
|
+
for (const subdir of DAEMON_MANAGED_DIRS) {
|
|
34938
|
+
const srcPath = path11.join(bundleDir, subdir);
|
|
34939
|
+
if (!fs9.existsSync(srcPath)) continue;
|
|
34940
|
+
const dstPath = path11.join(personaDir, subdir);
|
|
34941
|
+
try {
|
|
34942
|
+
fs9.cpSync(srcPath, dstPath, { recursive: true, force: true, dereference: true });
|
|
34943
|
+
args.logger.info("persona.refresh.synced", {
|
|
34944
|
+
personaId: entry.personaId,
|
|
34945
|
+
subdir
|
|
34946
|
+
});
|
|
34947
|
+
} catch (err) {
|
|
34948
|
+
args.logger.warn("persona.refresh.failed", {
|
|
34949
|
+
personaId: entry.personaId,
|
|
34950
|
+
subdir,
|
|
34951
|
+
error: err instanceof Error ? err.message : String(err)
|
|
34952
|
+
});
|
|
34953
|
+
}
|
|
34954
|
+
}
|
|
34955
|
+
}
|
|
34956
|
+
}
|
|
34856
34957
|
|
|
34857
34958
|
// src/index.ts
|
|
34858
34959
|
init_claude();
|
|
@@ -41713,12 +41814,373 @@ function listPidsOnPort(port) {
|
|
|
41713
41814
|
});
|
|
41714
41815
|
}
|
|
41715
41816
|
|
|
41817
|
+
// src/app-builder/publish-registry.ts
|
|
41818
|
+
var import_node_crypto14 = require("crypto");
|
|
41819
|
+
var PublishJobRegistry = class {
|
|
41820
|
+
jobs = /* @__PURE__ */ new Map();
|
|
41821
|
+
has(name) {
|
|
41822
|
+
return this.jobs.has(name);
|
|
41823
|
+
}
|
|
41824
|
+
get(name) {
|
|
41825
|
+
return this.jobs.get(name);
|
|
41826
|
+
}
|
|
41827
|
+
/**
|
|
41828
|
+
* args.jobId 仅用于测试注入;生产代码不传,registry 自己生成。
|
|
41829
|
+
* args.now 仅用于测试注入;生产代码不传,用 Date.now()。
|
|
41830
|
+
*/
|
|
41831
|
+
create(args) {
|
|
41832
|
+
if (this.jobs.has(args.name)) {
|
|
41833
|
+
throw new Error(`already publishing: ${args.name}`);
|
|
41834
|
+
}
|
|
41835
|
+
const jobId = args.jobId ?? `job-${(0, import_node_crypto14.randomUUID)()}`;
|
|
41836
|
+
this.jobs.set(args.name, {
|
|
41837
|
+
jobId,
|
|
41838
|
+
name: args.name,
|
|
41839
|
+
stage: "build",
|
|
41840
|
+
sessionId: args.sessionId,
|
|
41841
|
+
childProc: args.childProc,
|
|
41842
|
+
dismissed: false,
|
|
41843
|
+
sawAnyStage: false,
|
|
41844
|
+
startedAt: args.now ?? Date.now()
|
|
41845
|
+
});
|
|
41846
|
+
return jobId;
|
|
41847
|
+
}
|
|
41848
|
+
updateStage(name, stage) {
|
|
41849
|
+
const job = this.jobs.get(name);
|
|
41850
|
+
if (!job) return;
|
|
41851
|
+
job.stage = stage;
|
|
41852
|
+
job.sawAnyStage = true;
|
|
41853
|
+
}
|
|
41854
|
+
markDismissed(name) {
|
|
41855
|
+
const job = this.jobs.get(name);
|
|
41856
|
+
if (!job) return;
|
|
41857
|
+
job.dismissed = true;
|
|
41858
|
+
}
|
|
41859
|
+
remove(name) {
|
|
41860
|
+
const job = this.jobs.get(name);
|
|
41861
|
+
if (!job) return void 0;
|
|
41862
|
+
this.jobs.delete(name);
|
|
41863
|
+
return { sessionId: job.sessionId };
|
|
41864
|
+
}
|
|
41865
|
+
};
|
|
41866
|
+
|
|
41867
|
+
// src/app-builder/publish-job-runner.ts
|
|
41868
|
+
var import_node_child_process8 = require("child_process");
|
|
41869
|
+
var import_node_fs29 = require("fs");
|
|
41870
|
+
var import_node_path39 = require("path");
|
|
41871
|
+
|
|
41872
|
+
// src/app-builder/publish-stage-parser.ts
|
|
41873
|
+
var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
|
|
41874
|
+
var PROD_URL_RE = /^\s*::prod-url::(\S+)\s*$/;
|
|
41875
|
+
var URL_RE = /\bhttps?:\/\/[^\s]+/;
|
|
41876
|
+
function parsePublishLine(line) {
|
|
41877
|
+
const stageMatch = STAGE_RE.exec(line);
|
|
41878
|
+
if (stageMatch) return { kind: "stage", stage: stageMatch[1] };
|
|
41879
|
+
const urlMatch = PROD_URL_RE.exec(line);
|
|
41880
|
+
if (urlMatch) return { kind: "prod-url", url: urlMatch[1] };
|
|
41881
|
+
return null;
|
|
41882
|
+
}
|
|
41883
|
+
function extractProdUrl(text) {
|
|
41884
|
+
const m2 = URL_RE.exec(text);
|
|
41885
|
+
return m2 ? m2[0] : null;
|
|
41886
|
+
}
|
|
41887
|
+
function tailStderrLines(buf, n) {
|
|
41888
|
+
if (!buf) return "";
|
|
41889
|
+
const lines = buf.split("\n");
|
|
41890
|
+
if (lines.length <= n) return buf;
|
|
41891
|
+
return lines.slice(lines.length - n).join("\n");
|
|
41892
|
+
}
|
|
41893
|
+
|
|
41894
|
+
// src/app-builder/publish-job-runner.ts
|
|
41895
|
+
async function startPublishJob(deps, args) {
|
|
41896
|
+
const { registry: registry2, projectDir } = deps;
|
|
41897
|
+
const spawn9 = deps.spawnImpl ?? import_node_child_process8.spawn;
|
|
41898
|
+
if (registry2.has(args.name)) {
|
|
41899
|
+
return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
|
|
41900
|
+
}
|
|
41901
|
+
const projDir = projectDir(args.name);
|
|
41902
|
+
const logPath = (0, import_node_path39.join)(projDir, ".publish.log");
|
|
41903
|
+
let logStream = null;
|
|
41904
|
+
try {
|
|
41905
|
+
logStream = (0, import_node_fs29.createWriteStream)(logPath, { flags: "w" });
|
|
41906
|
+
} catch {
|
|
41907
|
+
logStream = null;
|
|
41908
|
+
}
|
|
41909
|
+
const child = spawn9("bash", [args.scriptPath, projDir], {
|
|
41910
|
+
cwd: projDir,
|
|
41911
|
+
env: process.env,
|
|
41912
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
41913
|
+
});
|
|
41914
|
+
const jobId = registry2.create({
|
|
41915
|
+
name: args.name,
|
|
41916
|
+
sessionId: args.sessionId,
|
|
41917
|
+
childProc: child
|
|
41918
|
+
});
|
|
41919
|
+
deps.logger?.info("app-builder.publish.spawned", {
|
|
41920
|
+
name: args.name,
|
|
41921
|
+
jobId,
|
|
41922
|
+
pid: child.pid ?? null,
|
|
41923
|
+
scriptPath: args.scriptPath,
|
|
41924
|
+
logPath
|
|
41925
|
+
});
|
|
41926
|
+
child.on("error", (err) => {
|
|
41927
|
+
deps.logger?.warn("app-builder.publish.spawn-error", {
|
|
41928
|
+
name: args.name,
|
|
41929
|
+
jobId,
|
|
41930
|
+
error: err instanceof Error ? err.message : String(err)
|
|
41931
|
+
});
|
|
41932
|
+
void handleExit(deps, {
|
|
41933
|
+
name: args.name,
|
|
41934
|
+
jobId,
|
|
41935
|
+
code: null,
|
|
41936
|
+
signal: null,
|
|
41937
|
+
urlCandidate: null,
|
|
41938
|
+
stderrTail: err instanceof Error ? err.message : String(err),
|
|
41939
|
+
logPath
|
|
41940
|
+
});
|
|
41941
|
+
});
|
|
41942
|
+
let urlFromMarker = null;
|
|
41943
|
+
let urlFallback = null;
|
|
41944
|
+
let stdoutBuf = "";
|
|
41945
|
+
child.stdout?.on("data", (chunk) => {
|
|
41946
|
+
if (logStream) logStream.write(chunk);
|
|
41947
|
+
stdoutBuf += chunk.toString();
|
|
41948
|
+
let nlIdx;
|
|
41949
|
+
while ((nlIdx = stdoutBuf.indexOf("\n")) >= 0) {
|
|
41950
|
+
const line = stdoutBuf.slice(0, nlIdx);
|
|
41951
|
+
stdoutBuf = stdoutBuf.slice(nlIdx + 1);
|
|
41952
|
+
handleStdoutLine(deps, args.name, jobId, line);
|
|
41953
|
+
const parsedForUrl = parsePublishLine(line);
|
|
41954
|
+
if (parsedForUrl?.kind === "prod-url") urlFromMarker = parsedForUrl.url;
|
|
41955
|
+
const url = extractProdUrl(line);
|
|
41956
|
+
if (url && !urlFallback) urlFallback = url;
|
|
41957
|
+
}
|
|
41958
|
+
});
|
|
41959
|
+
let stderrBuf = "";
|
|
41960
|
+
child.stderr?.on("data", (chunk) => {
|
|
41961
|
+
if (logStream) logStream.write(chunk);
|
|
41962
|
+
stderrBuf += chunk.toString();
|
|
41963
|
+
});
|
|
41964
|
+
child.on("exit", (code, signal) => {
|
|
41965
|
+
if (logStream) logStream.end();
|
|
41966
|
+
const urlCandidate = urlFromMarker ?? urlFallback;
|
|
41967
|
+
deps.logger?.info("app-builder.publish.child-exit", {
|
|
41968
|
+
name: args.name,
|
|
41969
|
+
jobId,
|
|
41970
|
+
code,
|
|
41971
|
+
signal,
|
|
41972
|
+
hadUrl: !!urlCandidate,
|
|
41973
|
+
urlSource: urlFromMarker ? "marker" : urlFallback ? "fallback" : "none",
|
|
41974
|
+
stderrBytes: stderrBuf.length
|
|
41975
|
+
});
|
|
41976
|
+
void handleExit(deps, {
|
|
41977
|
+
name: args.name,
|
|
41978
|
+
jobId,
|
|
41979
|
+
code,
|
|
41980
|
+
signal,
|
|
41981
|
+
urlCandidate,
|
|
41982
|
+
stderrTail: tailStderrLines(stderrBuf, 20),
|
|
41983
|
+
logPath
|
|
41984
|
+
});
|
|
41985
|
+
});
|
|
41986
|
+
return { jobId, status: "started" };
|
|
41987
|
+
}
|
|
41988
|
+
function handleStdoutLine(deps, name, jobId, line) {
|
|
41989
|
+
const parsed = parsePublishLine(line);
|
|
41990
|
+
if (parsed?.kind !== "stage") return;
|
|
41991
|
+
advanceStage(deps, name, jobId, parsed.stage);
|
|
41992
|
+
}
|
|
41993
|
+
function advanceStage(deps, name, jobId, next) {
|
|
41994
|
+
const job = deps.registry.get(name);
|
|
41995
|
+
if (!job) return;
|
|
41996
|
+
const prev = job.stage;
|
|
41997
|
+
const wasFirstStage = !job.sawAnyStage;
|
|
41998
|
+
deps.logger?.info("app-builder.publish.stage-advance", {
|
|
41999
|
+
name,
|
|
42000
|
+
jobId,
|
|
42001
|
+
prev: wasFirstStage ? null : prev,
|
|
42002
|
+
next,
|
|
42003
|
+
firstStage: wasFirstStage
|
|
42004
|
+
});
|
|
42005
|
+
if (wasFirstStage) {
|
|
42006
|
+
deps.registry.updateStage(name, next);
|
|
42007
|
+
void deps.setPublishJob(name, {
|
|
42008
|
+
jobId,
|
|
42009
|
+
stage: next,
|
|
42010
|
+
startedAt: job.startedAt
|
|
42011
|
+
});
|
|
42012
|
+
deps.broadcast({
|
|
42013
|
+
type: "appBuilder:publish-progress",
|
|
42014
|
+
name,
|
|
42015
|
+
jobId,
|
|
42016
|
+
stage: next,
|
|
42017
|
+
status: "started"
|
|
42018
|
+
});
|
|
42019
|
+
return;
|
|
42020
|
+
}
|
|
42021
|
+
if (prev === next) {
|
|
42022
|
+
return;
|
|
42023
|
+
}
|
|
42024
|
+
deps.broadcast({
|
|
42025
|
+
type: "appBuilder:publish-progress",
|
|
42026
|
+
name,
|
|
42027
|
+
jobId,
|
|
42028
|
+
stage: prev,
|
|
42029
|
+
status: "completed"
|
|
42030
|
+
});
|
|
42031
|
+
deps.registry.updateStage(name, next);
|
|
42032
|
+
void deps.setPublishJob(name, {
|
|
42033
|
+
jobId,
|
|
42034
|
+
stage: next,
|
|
42035
|
+
startedAt: job.startedAt
|
|
42036
|
+
});
|
|
42037
|
+
deps.broadcast({
|
|
42038
|
+
type: "appBuilder:publish-progress",
|
|
42039
|
+
name,
|
|
42040
|
+
jobId,
|
|
42041
|
+
stage: next,
|
|
42042
|
+
status: "started"
|
|
42043
|
+
});
|
|
42044
|
+
}
|
|
42045
|
+
async function handleExit(deps, args) {
|
|
42046
|
+
const job = deps.registry.get(args.name);
|
|
42047
|
+
if (args.signal === "SIGTERM" && job?.dismissed) {
|
|
42048
|
+
try {
|
|
42049
|
+
await deps.clearPublishJob(args.name);
|
|
42050
|
+
} catch (err) {
|
|
42051
|
+
console.error("[publish] clearPublishJob failed (dismissed branch)", err);
|
|
42052
|
+
}
|
|
42053
|
+
deps.registry.remove(args.name);
|
|
42054
|
+
return;
|
|
42055
|
+
}
|
|
42056
|
+
if (args.code === 0 && args.urlCandidate) {
|
|
42057
|
+
if (job?.sawAnyStage) {
|
|
42058
|
+
deps.broadcast({
|
|
42059
|
+
type: "appBuilder:publish-progress",
|
|
42060
|
+
name: args.name,
|
|
42061
|
+
jobId: args.jobId,
|
|
42062
|
+
stage: job.stage,
|
|
42063
|
+
status: "completed"
|
|
42064
|
+
});
|
|
42065
|
+
}
|
|
42066
|
+
try {
|
|
42067
|
+
await deps.setProdUrl(args.name, args.urlCandidate);
|
|
42068
|
+
} catch (err) {
|
|
42069
|
+
console.error("[publish] setProdUrl failed; falling through to failure branch", err);
|
|
42070
|
+
await emitFailure(deps, args, "\u8C03 setProdUrl \u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)));
|
|
42071
|
+
return;
|
|
42072
|
+
}
|
|
42073
|
+
try {
|
|
42074
|
+
await deps.clearPublishJob(args.name);
|
|
42075
|
+
} catch (err) {
|
|
42076
|
+
console.error("[publish] clearPublishJob failed (success branch)", err);
|
|
42077
|
+
}
|
|
42078
|
+
deps.registry.remove(args.name);
|
|
42079
|
+
return;
|
|
42080
|
+
}
|
|
42081
|
+
await emitFailure(deps, args, buildErrorSummary(args));
|
|
42082
|
+
}
|
|
42083
|
+
async function emitFailure(deps, args, errorSummary) {
|
|
42084
|
+
const job = deps.registry.get(args.name);
|
|
42085
|
+
const failStage = job?.sawAnyStage ? job.stage : "unknown";
|
|
42086
|
+
deps.logger?.warn("app-builder.publish.failed", {
|
|
42087
|
+
name: args.name,
|
|
42088
|
+
jobId: args.jobId,
|
|
42089
|
+
stage: failStage,
|
|
42090
|
+
errorSummary: errorSummary.slice(0, 500)
|
|
42091
|
+
});
|
|
42092
|
+
try {
|
|
42093
|
+
await deps.clearPublishJob(args.name);
|
|
42094
|
+
} catch (err) {
|
|
42095
|
+
deps.logger?.warn("app-builder.publish.clear-field-failed", {
|
|
42096
|
+
name: args.name,
|
|
42097
|
+
jobId: args.jobId,
|
|
42098
|
+
error: err instanceof Error ? err.message : String(err)
|
|
42099
|
+
});
|
|
42100
|
+
}
|
|
42101
|
+
const removed = deps.registry.remove(args.name);
|
|
42102
|
+
deps.broadcast({
|
|
42103
|
+
type: "appBuilder:publish-failed",
|
|
42104
|
+
name: args.name,
|
|
42105
|
+
jobId: args.jobId,
|
|
42106
|
+
stage: failStage,
|
|
42107
|
+
errorSummary
|
|
42108
|
+
});
|
|
42109
|
+
if (removed) {
|
|
42110
|
+
const msg = formatFailureMessage(failStage, errorSummary, args.logPath);
|
|
42111
|
+
try {
|
|
42112
|
+
await deps.injectUserText(removed.sessionId, msg);
|
|
42113
|
+
deps.logger?.info("app-builder.publish.failure-injected-to-chat", {
|
|
42114
|
+
name: args.name,
|
|
42115
|
+
jobId: args.jobId,
|
|
42116
|
+
sessionId: removed.sessionId
|
|
42117
|
+
});
|
|
42118
|
+
} catch (err) {
|
|
42119
|
+
deps.logger?.warn("app-builder.publish.inject-user-text-failed", {
|
|
42120
|
+
name: args.name,
|
|
42121
|
+
jobId: args.jobId,
|
|
42122
|
+
sessionId: removed.sessionId,
|
|
42123
|
+
error: err instanceof Error ? err.message : String(err)
|
|
42124
|
+
});
|
|
42125
|
+
}
|
|
42126
|
+
} else {
|
|
42127
|
+
deps.logger?.warn("app-builder.publish.no-registry-entry-on-failure", {
|
|
42128
|
+
name: args.name,
|
|
42129
|
+
jobId: args.jobId
|
|
42130
|
+
});
|
|
42131
|
+
}
|
|
42132
|
+
}
|
|
42133
|
+
function buildErrorSummary(args) {
|
|
42134
|
+
if (args.signal) {
|
|
42135
|
+
return `publish.sh \u88AB\u4E2D\u65AD\uFF08signal: ${args.signal}\uFF09`;
|
|
42136
|
+
}
|
|
42137
|
+
if (args.code === 0 && !args.urlCandidate) {
|
|
42138
|
+
return "publish.sh \u6210\u529F\u9000\u51FA\u4F46\u672A\u8F93\u51FA\u516C\u7F51 URL";
|
|
42139
|
+
}
|
|
42140
|
+
return args.stderrTail || `publish.sh \u5F02\u5E38\u9000\u51FA\uFF08exit code: ${args.code}\uFF09`;
|
|
42141
|
+
}
|
|
42142
|
+
function formatFailureMessage(stage, errorSummary, logPath) {
|
|
42143
|
+
return `\u53D1\u5E03\u5931\u8D25\uFF1A[${stage}] \u9636\u6BB5
|
|
42144
|
+
${errorSummary}
|
|
42145
|
+
|
|
42146
|
+
\u5B8C\u6574\u65E5\u5FD7\uFF1A${logPath}
|
|
42147
|
+
\u8BF7\u4F60\u63A5\u7BA1\u5B8C\u6210\u672C\u6B21\u53D1\u5E03\uFF1A\u5148\u6839\u636E\u65E5\u5FD7\u4FEE\u590D\u95EE\u9898\uFF0C\u7136\u540E\u91CD\u8DD1 publish.sh \u76F4\u5230\u4E0A\u7EBF\u6210\u529F\uFF0C\u6700\u540E\u7528 appBuilder.setProdUrl \u5199\u5165\u516C\u7F51 URL\u3002`;
|
|
42148
|
+
}
|
|
42149
|
+
async function recoverInterruptedJobs(deps) {
|
|
42150
|
+
const projects = await deps.listAllProjects();
|
|
42151
|
+
for (const p2 of projects) {
|
|
42152
|
+
if (p2.publishJob) {
|
|
42153
|
+
deps.log(
|
|
42154
|
+
`[publish] found interrupted job ${p2.publishJob.jobId} for ${p2.name}, stage=${p2.publishJob.stage}`
|
|
42155
|
+
);
|
|
42156
|
+
}
|
|
42157
|
+
}
|
|
42158
|
+
}
|
|
42159
|
+
|
|
41716
42160
|
// src/handlers/app-builder.ts
|
|
41717
42161
|
init_protocol();
|
|
42162
|
+
var import_node_path40 = require("path");
|
|
42163
|
+
var PUBLISH_SCRIPT_REL = "extension-kit/scripts/publish.sh";
|
|
41718
42164
|
var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
|
|
42165
|
+
async function recoverInterruptedPublishJobs(store, logger) {
|
|
42166
|
+
await recoverInterruptedJobs({
|
|
42167
|
+
listAllProjects: async () => store.list(),
|
|
42168
|
+
log: (msg) => logger?.info(msg)
|
|
42169
|
+
});
|
|
42170
|
+
}
|
|
41719
42171
|
function buildAppBuilderHandlers(deps) {
|
|
41720
42172
|
const { store, devServerLifecycle, sessionManager, getTunnelUrl, broadcastProjectUpdated } = deps;
|
|
41721
42173
|
const stages = /* @__PURE__ */ new Map();
|
|
42174
|
+
const publishRegistry = new PublishJobRegistry();
|
|
42175
|
+
function withPublishJobStatus(meta) {
|
|
42176
|
+
if (!meta.publishJob) return void 0;
|
|
42177
|
+
const job = publishRegistry.get(meta.name);
|
|
42178
|
+
const inFlight = !!job && job.jobId === meta.publishJob.jobId;
|
|
42179
|
+
return {
|
|
42180
|
+
...meta.publishJob,
|
|
42181
|
+
status: inFlight ? "in-flight" : "interrupted"
|
|
42182
|
+
};
|
|
42183
|
+
}
|
|
41722
42184
|
function getStageOf(name) {
|
|
41723
42185
|
const recorded = stages.get(name);
|
|
41724
42186
|
if (recorded) return recorded;
|
|
@@ -41737,6 +42199,7 @@ function buildAppBuilderHandlers(deps) {
|
|
|
41737
42199
|
return;
|
|
41738
42200
|
}
|
|
41739
42201
|
deps.logger?.info("app-builder.stage-transition", { name, stage, reason });
|
|
42202
|
+
const publishJobWithStatus = withPublishJobStatus(meta);
|
|
41740
42203
|
broadcastProjectUpdated({
|
|
41741
42204
|
type: "appBuilder:project-updated",
|
|
41742
42205
|
project: {
|
|
@@ -41744,7 +42207,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
41744
42207
|
isRunning: devServerLifecycle.isRunning(name),
|
|
41745
42208
|
boundSessionId: devServerLifecycle.boundSessionId(name),
|
|
41746
42209
|
stage,
|
|
41747
|
-
...reason !== void 0 ? { stageReason: reason } : {}
|
|
42210
|
+
...reason !== void 0 ? { stageReason: reason } : {},
|
|
42211
|
+
...publishJobWithStatus !== void 0 ? { publishJob: publishJobWithStatus } : { publishJob: void 0 }
|
|
41748
42212
|
},
|
|
41749
42213
|
stage,
|
|
41750
42214
|
...reason !== void 0 ? { stageReason: reason } : {}
|
|
@@ -41756,12 +42220,16 @@ function buildAppBuilderHandlers(deps) {
|
|
|
41756
42220
|
response: {
|
|
41757
42221
|
projects: projects.map((p2) => {
|
|
41758
42222
|
const s = getStageOf(p2.name);
|
|
42223
|
+
const publishJobWithStatus = withPublishJobStatus(p2);
|
|
41759
42224
|
return {
|
|
41760
42225
|
...p2,
|
|
41761
42226
|
isRunning: devServerLifecycle.isRunning(p2.name),
|
|
41762
42227
|
boundSessionId: devServerLifecycle.boundSessionId(p2.name),
|
|
41763
42228
|
stage: s.stage,
|
|
41764
|
-
...s.reason !== void 0 ? { stageReason: s.reason } : {}
|
|
42229
|
+
...s.reason !== void 0 ? { stageReason: s.reason } : {},
|
|
42230
|
+
// publishJob 在 spread ...p 之后覆写:原始 ProjectMetadata.publishJob 没有 status,
|
|
42231
|
+
// 这里实时拼上 status 字段;字段不存在时显式 undefined(覆盖掉 spread 的可能值)。
|
|
42232
|
+
publishJob: publishJobWithStatus
|
|
41765
42233
|
};
|
|
41766
42234
|
})
|
|
41767
42235
|
}
|
|
@@ -41980,6 +42448,103 @@ function buildAppBuilderHandlers(deps) {
|
|
|
41980
42448
|
await setStageAndBroadcast(projectName, "stopped");
|
|
41981
42449
|
return { response: { ok: true } };
|
|
41982
42450
|
};
|
|
42451
|
+
const publish = async (frame) => {
|
|
42452
|
+
const args = PublishArgsSchema.parse(frame);
|
|
42453
|
+
const projects = await store.list();
|
|
42454
|
+
const project = projects.find((p2) => p2.name === args.name);
|
|
42455
|
+
if (!project) {
|
|
42456
|
+
throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `publish: project "${args.name}" not found`);
|
|
42457
|
+
}
|
|
42458
|
+
const boundSession = sessionManager.findOwnedSessionByAppBuilderProject(args.name);
|
|
42459
|
+
if (!boundSession) {
|
|
42460
|
+
throw new ClawdError(
|
|
42461
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
42462
|
+
`publish: project "${args.name}" has no bound session (orphan); cannot inject failure to chat`
|
|
42463
|
+
);
|
|
42464
|
+
}
|
|
42465
|
+
if (project.publishJob && !publishRegistry.has(args.name)) {
|
|
42466
|
+
deps.logger?.info("app-builder.publish.clear-interrupted", {
|
|
42467
|
+
name: args.name,
|
|
42468
|
+
previousJobId: project.publishJob.jobId,
|
|
42469
|
+
previousStage: project.publishJob.stage
|
|
42470
|
+
});
|
|
42471
|
+
await store.clearPublishJob(args.name);
|
|
42472
|
+
}
|
|
42473
|
+
const scriptPath = (0, import_node_path40.join)(deps.personaRoot, PUBLISH_SCRIPT_REL);
|
|
42474
|
+
deps.logger?.info("app-builder.publish.start", {
|
|
42475
|
+
name: args.name,
|
|
42476
|
+
sessionId: boundSession.sessionId,
|
|
42477
|
+
scriptPath
|
|
42478
|
+
});
|
|
42479
|
+
const result = await startPublishJob(
|
|
42480
|
+
{
|
|
42481
|
+
registry: publishRegistry,
|
|
42482
|
+
setPublishJob: async (name, job) => {
|
|
42483
|
+
await store.setPublishJob(name, job);
|
|
42484
|
+
const current = getStageOf(name);
|
|
42485
|
+
await setStageAndBroadcast(name, current.stage, current.reason);
|
|
42486
|
+
},
|
|
42487
|
+
clearPublishJob: async (name) => {
|
|
42488
|
+
await store.clearPublishJob(name);
|
|
42489
|
+
const current = getStageOf(name);
|
|
42490
|
+
await setStageAndBroadcast(name, current.stage, current.reason);
|
|
42491
|
+
},
|
|
42492
|
+
setProdUrl: async (name, url) => {
|
|
42493
|
+
await store.setProdUrl(name, url);
|
|
42494
|
+
const current = getStageOf(name);
|
|
42495
|
+
await setStageAndBroadcast(name, current.stage, current.reason);
|
|
42496
|
+
},
|
|
42497
|
+
broadcast: (f) => {
|
|
42498
|
+
broadcastProjectUpdated(f);
|
|
42499
|
+
},
|
|
42500
|
+
injectUserText: async (sessionId, text) => {
|
|
42501
|
+
const { broadcast } = sessionManager.send({ sessionId, text });
|
|
42502
|
+
for (const entry of broadcast) {
|
|
42503
|
+
deps.broadcastSessionFrame(entry.frame, entry.target);
|
|
42504
|
+
}
|
|
42505
|
+
},
|
|
42506
|
+
projectDir: (name) => store.projectDir(name),
|
|
42507
|
+
logger: deps.logger
|
|
42508
|
+
},
|
|
42509
|
+
{
|
|
42510
|
+
name: args.name,
|
|
42511
|
+
sessionId: boundSession.sessionId,
|
|
42512
|
+
scriptPath
|
|
42513
|
+
}
|
|
42514
|
+
);
|
|
42515
|
+
return { response: { ...result } };
|
|
42516
|
+
};
|
|
42517
|
+
const dismissPublishJob = async (frame) => {
|
|
42518
|
+
const args = DismissPublishJobArgsSchema.parse(frame);
|
|
42519
|
+
const projects = await store.list();
|
|
42520
|
+
const project = projects.find((p2) => p2.name === args.name);
|
|
42521
|
+
if (!project) {
|
|
42522
|
+
throw new ClawdError(
|
|
42523
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
42524
|
+
`dismissPublishJob: project "${args.name}" not found`
|
|
42525
|
+
);
|
|
42526
|
+
}
|
|
42527
|
+
const job = publishRegistry.get(args.name);
|
|
42528
|
+
if (job) {
|
|
42529
|
+
deps.logger?.info("app-builder.publish.dismiss.in-flight", {
|
|
42530
|
+
name: args.name,
|
|
42531
|
+
jobId: job.jobId,
|
|
42532
|
+
stage: job.stage
|
|
42533
|
+
});
|
|
42534
|
+
publishRegistry.markDismissed(args.name);
|
|
42535
|
+
job.childProc.kill("SIGTERM");
|
|
42536
|
+
} else if (project.publishJob) {
|
|
42537
|
+
deps.logger?.info("app-builder.publish.dismiss.interrupted", {
|
|
42538
|
+
name: args.name,
|
|
42539
|
+
jobId: project.publishJob.jobId,
|
|
42540
|
+
stage: project.publishJob.stage
|
|
42541
|
+
});
|
|
42542
|
+
await store.clearPublishJob(args.name);
|
|
42543
|
+
const current = getStageOf(args.name);
|
|
42544
|
+
await setStageAndBroadcast(args.name, current.stage, current.reason);
|
|
42545
|
+
}
|
|
42546
|
+
return { response: { ok: true } };
|
|
42547
|
+
};
|
|
41983
42548
|
return {
|
|
41984
42549
|
"appBuilder:listProjects": listProjects,
|
|
41985
42550
|
"appBuilder:createProject": createProject,
|
|
@@ -41988,13 +42553,15 @@ function buildAppBuilderHandlers(deps) {
|
|
|
41988
42553
|
"appBuilder:setProdUrl": setProdUrl,
|
|
41989
42554
|
"appBuilder:startDevServer": startDevServer,
|
|
41990
42555
|
"appBuilder:reportStage": reportStage,
|
|
41991
|
-
"appBuilder:stopDevServer": stopDevServer
|
|
42556
|
+
"appBuilder:stopDevServer": stopDevServer,
|
|
42557
|
+
"appBuilder:publish": publish,
|
|
42558
|
+
"appBuilder:dismissPublishJob": dismissPublishJob
|
|
41992
42559
|
};
|
|
41993
42560
|
}
|
|
41994
42561
|
|
|
41995
42562
|
// src/extension/registry.ts
|
|
41996
42563
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
41997
|
-
var
|
|
42564
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
41998
42565
|
async function loadAll(root) {
|
|
41999
42566
|
let entries;
|
|
42000
42567
|
try {
|
|
@@ -42007,13 +42574,13 @@ async function loadAll(root) {
|
|
|
42007
42574
|
for (const ent of entries) {
|
|
42008
42575
|
if (!ent.isDirectory()) continue;
|
|
42009
42576
|
if (ent.name.startsWith(".")) continue;
|
|
42010
|
-
records.push(await loadOne(
|
|
42577
|
+
records.push(await loadOne(import_node_path41.default.join(root, ent.name), ent.name));
|
|
42011
42578
|
}
|
|
42012
42579
|
records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
|
|
42013
42580
|
return records;
|
|
42014
42581
|
}
|
|
42015
42582
|
async function loadOne(dir, dirName) {
|
|
42016
|
-
const manifestPath =
|
|
42583
|
+
const manifestPath = import_node_path41.default.join(dir, "manifest.json");
|
|
42017
42584
|
let raw;
|
|
42018
42585
|
try {
|
|
42019
42586
|
raw = await import_promises8.default.readFile(manifestPath, "utf8");
|
|
@@ -42058,7 +42625,7 @@ async function loadOne(dir, dirName) {
|
|
|
42058
42625
|
|
|
42059
42626
|
// src/extension/uninstall.ts
|
|
42060
42627
|
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
42061
|
-
var
|
|
42628
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
42062
42629
|
var UninstallError = class extends Error {
|
|
42063
42630
|
constructor(code, message) {
|
|
42064
42631
|
super(message);
|
|
@@ -42067,7 +42634,7 @@ var UninstallError = class extends Error {
|
|
|
42067
42634
|
code;
|
|
42068
42635
|
};
|
|
42069
42636
|
async function uninstall(deps) {
|
|
42070
|
-
const dir =
|
|
42637
|
+
const dir = import_node_path42.default.join(deps.root, deps.extId);
|
|
42071
42638
|
try {
|
|
42072
42639
|
await import_promises9.default.access(dir);
|
|
42073
42640
|
} catch {
|
|
@@ -42134,15 +42701,20 @@ function buildMethodHandlers(deps) {
|
|
|
42134
42701
|
sessionManager: deps.manager,
|
|
42135
42702
|
getTunnelUrl: deps.getTunnelUrl,
|
|
42136
42703
|
broadcastProjectUpdated: deps.broadcastProjectUpdated,
|
|
42137
|
-
logger: deps.logger
|
|
42704
|
+
logger: deps.logger,
|
|
42705
|
+
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
42706
|
+
// personaRoot 用于拼 publish.sh 绝对路径;broadcastSessionFrame 用于失败回灌路径
|
|
42707
|
+
// (runner 调 manager.send 后逐帧 dispatch 到 transport)。
|
|
42708
|
+
personaRoot: deps.appBuilderPersonaRoot,
|
|
42709
|
+
broadcastSessionFrame: deps.broadcastSessionFrame
|
|
42138
42710
|
})
|
|
42139
42711
|
};
|
|
42140
42712
|
}
|
|
42141
42713
|
|
|
42142
42714
|
// src/app-builder/project-store.ts
|
|
42143
|
-
var
|
|
42144
|
-
var
|
|
42145
|
-
var
|
|
42715
|
+
var import_node_fs30 = require("fs");
|
|
42716
|
+
var import_node_child_process9 = require("child_process");
|
|
42717
|
+
var import_node_path43 = require("path");
|
|
42146
42718
|
init_protocol();
|
|
42147
42719
|
var PROJECTS_DIR = "projects";
|
|
42148
42720
|
var META_FILE = ".clawd-project.json";
|
|
@@ -42157,19 +42729,19 @@ var ProjectStore = class {
|
|
|
42157
42729
|
personaRoot;
|
|
42158
42730
|
/** projects/<name>/.clawd-project.json 路径 */
|
|
42159
42731
|
metaPath(name) {
|
|
42160
|
-
return (0,
|
|
42732
|
+
return (0, import_node_path43.join)(this.projectsRoot(), name, META_FILE);
|
|
42161
42733
|
}
|
|
42162
42734
|
/** projects/<name>/ 目录路径(cwd 用) */
|
|
42163
42735
|
projectDir(name) {
|
|
42164
|
-
return (0,
|
|
42736
|
+
return (0, import_node_path43.join)(this.projectsRoot(), name);
|
|
42165
42737
|
}
|
|
42166
42738
|
projectsRoot() {
|
|
42167
|
-
return (0,
|
|
42739
|
+
return (0, import_node_path43.join)(this.personaRoot, PROJECTS_DIR);
|
|
42168
42740
|
}
|
|
42169
42741
|
async list() {
|
|
42170
42742
|
let entries;
|
|
42171
42743
|
try {
|
|
42172
|
-
entries = await
|
|
42744
|
+
entries = await import_node_fs30.promises.readdir(this.projectsRoot());
|
|
42173
42745
|
} catch (err) {
|
|
42174
42746
|
if (err.code === "ENOENT") return [];
|
|
42175
42747
|
throw err;
|
|
@@ -42177,7 +42749,7 @@ var ProjectStore = class {
|
|
|
42177
42749
|
const out = [];
|
|
42178
42750
|
for (const name of entries) {
|
|
42179
42751
|
try {
|
|
42180
|
-
const raw = await
|
|
42752
|
+
const raw = await import_node_fs30.promises.readFile(this.metaPath(name), "utf8");
|
|
42181
42753
|
const json = JSON.parse(raw);
|
|
42182
42754
|
let migrated = false;
|
|
42183
42755
|
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
@@ -42188,7 +42760,7 @@ var ProjectStore = class {
|
|
|
42188
42760
|
if (parsed.success) {
|
|
42189
42761
|
out.push(parsed.data);
|
|
42190
42762
|
if (migrated) {
|
|
42191
|
-
void
|
|
42763
|
+
void import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
42192
42764
|
});
|
|
42193
42765
|
}
|
|
42194
42766
|
}
|
|
@@ -42226,8 +42798,8 @@ var ProjectStore = class {
|
|
|
42226
42798
|
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
42227
42799
|
}
|
|
42228
42800
|
const dir = this.projectDir(name);
|
|
42229
|
-
await
|
|
42230
|
-
await
|
|
42801
|
+
await import_node_fs30.promises.mkdir(dir, { recursive: true });
|
|
42802
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
42231
42803
|
return meta;
|
|
42232
42804
|
}
|
|
42233
42805
|
/**
|
|
@@ -42239,9 +42811,9 @@ var ProjectStore = class {
|
|
|
42239
42811
|
* 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
|
|
42240
42812
|
*/
|
|
42241
42813
|
async scaffold(name, template = DEFAULT_TEMPLATE) {
|
|
42242
|
-
const scriptPath = (0,
|
|
42814
|
+
const scriptPath = (0, import_node_path43.join)(this.personaRoot, SCAFFOLD_SCRIPT_REL);
|
|
42243
42815
|
return await new Promise((resolve6, reject) => {
|
|
42244
|
-
const child = (0,
|
|
42816
|
+
const child = (0, import_node_child_process9.spawn)("bash", [scriptPath, name, template], {
|
|
42245
42817
|
cwd: this.personaRoot,
|
|
42246
42818
|
env: { ...process.env, PATH: process.env.PATH ?? "" },
|
|
42247
42819
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -42271,7 +42843,7 @@ var ProjectStore = class {
|
|
|
42271
42843
|
}
|
|
42272
42844
|
async delete(name) {
|
|
42273
42845
|
const dir = this.projectDir(name);
|
|
42274
|
-
await
|
|
42846
|
+
await import_node_fs30.promises.rm(dir, { recursive: true, force: true });
|
|
42275
42847
|
}
|
|
42276
42848
|
async updatePort(name, newPort) {
|
|
42277
42849
|
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
@@ -42287,7 +42859,7 @@ var ProjectStore = class {
|
|
|
42287
42859
|
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
42288
42860
|
}
|
|
42289
42861
|
const updated = { ...target, port: newPort };
|
|
42290
|
-
await
|
|
42862
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
42291
42863
|
return updated;
|
|
42292
42864
|
}
|
|
42293
42865
|
/**
|
|
@@ -42304,13 +42876,49 @@ var ProjectStore = class {
|
|
|
42304
42876
|
if (!validated.success) {
|
|
42305
42877
|
throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
|
|
42306
42878
|
}
|
|
42307
|
-
await
|
|
42879
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
42880
|
+
return validated.data;
|
|
42881
|
+
}
|
|
42882
|
+
/**
|
|
42883
|
+
* 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
|
|
42884
|
+
* 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
|
|
42885
|
+
* - daemon 收到第一个 ::stage::build marker 时写入
|
|
42886
|
+
* - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
|
|
42887
|
+
* - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
|
|
42888
|
+
* - 只有 daemon 崩才会留残骸
|
|
42889
|
+
* 写入时保留所有其他字段(prodUrl 等不受影响)。
|
|
42890
|
+
*/
|
|
42891
|
+
async setPublishJob(name, job) {
|
|
42892
|
+
const list = await this.list();
|
|
42893
|
+
const target = list.find((p2) => p2.name === name);
|
|
42894
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
42895
|
+
const updated = { ...target, publishJob: job };
|
|
42896
|
+
const validated = ProjectMetadataSchema.safeParse(updated);
|
|
42897
|
+
if (!validated.success) {
|
|
42898
|
+
throw new Error(`invalid publishJob: ${validated.error.message}`);
|
|
42899
|
+
}
|
|
42900
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
42901
|
+
return validated.data;
|
|
42902
|
+
}
|
|
42903
|
+
/** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
|
|
42904
|
+
async clearPublishJob(name) {
|
|
42905
|
+
const list = await this.list();
|
|
42906
|
+
const target = list.find((p2) => p2.name === name);
|
|
42907
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
42908
|
+
if (!target.publishJob) return target;
|
|
42909
|
+
const { publishJob: _drop, ...rest } = target;
|
|
42910
|
+
void _drop;
|
|
42911
|
+
const validated = ProjectMetadataSchema.safeParse(rest);
|
|
42912
|
+
if (!validated.success) {
|
|
42913
|
+
throw new Error(`failed to clear publishJob: ${validated.error.message}`);
|
|
42914
|
+
}
|
|
42915
|
+
await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
|
|
42308
42916
|
return validated.data;
|
|
42309
42917
|
}
|
|
42310
42918
|
};
|
|
42311
42919
|
|
|
42312
42920
|
// src/app-builder/dev-server-supervisor.ts
|
|
42313
|
-
var
|
|
42921
|
+
var import_node_child_process10 = require("child_process");
|
|
42314
42922
|
var import_node_events2 = require("events");
|
|
42315
42923
|
var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
|
|
42316
42924
|
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
@@ -42347,7 +42955,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
|
42347
42955
|
tunnelHost: args.tunnelHost,
|
|
42348
42956
|
devCommand: cmd
|
|
42349
42957
|
});
|
|
42350
|
-
const child = (0,
|
|
42958
|
+
const child = (0, import_node_child_process10.spawn)("sh", ["-c", cmd], {
|
|
42351
42959
|
cwd: args.cwd,
|
|
42352
42960
|
env,
|
|
42353
42961
|
stdio: "pipe",
|
|
@@ -42709,7 +43317,11 @@ var METHOD_GRANT_MAP = {
|
|
|
42709
43317
|
"appBuilder:reportStage": ADMIN_ANY,
|
|
42710
43318
|
// 显式控制 fix (spec 2026-06-02-app-builder-explicit-dev-server-control):用户点"收起预览"触发停 dev server,
|
|
42711
43319
|
// 对称 startDevServer。同时砍了 setActiveSession(dev server 不再 follow-the-eyes)。
|
|
42712
|
-
"appBuilder:stopDevServer": ADMIN_ANY
|
|
43320
|
+
"appBuilder:stopDevServer": ADMIN_ANY,
|
|
43321
|
+
// 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design):
|
|
43322
|
+
// owner-only:PreviewPane「发布上线」按钮直接调 publish;UI [取消] / interrupted 恢复按钮调 dismissPublishJob。
|
|
43323
|
+
"appBuilder:publish": ADMIN_ANY,
|
|
43324
|
+
"appBuilder:dismissPublishJob": ADMIN_ANY
|
|
42713
43325
|
};
|
|
42714
43326
|
function computeGrantForFrame(method, frame) {
|
|
42715
43327
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -42725,8 +43337,8 @@ function computeGrantForFrame(method, frame) {
|
|
|
42725
43337
|
}
|
|
42726
43338
|
|
|
42727
43339
|
// src/extension/runtime.ts
|
|
42728
|
-
var
|
|
42729
|
-
var
|
|
43340
|
+
var import_node_child_process11 = require("child_process");
|
|
43341
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
42730
43342
|
var import_promises10 = require("timers/promises");
|
|
42731
43343
|
|
|
42732
43344
|
// src/extension/port-allocator.ts
|
|
@@ -42827,13 +43439,13 @@ var Runtime = class {
|
|
|
42827
43439
|
/\$CLAWOS_EXT_PORT/g,
|
|
42828
43440
|
String(port)
|
|
42829
43441
|
);
|
|
42830
|
-
const dir =
|
|
43442
|
+
const dir = import_node_path44.default.join(this.root, extId);
|
|
42831
43443
|
const env = {
|
|
42832
43444
|
...process.env,
|
|
42833
43445
|
CLAWOS_EXT_PORT: String(port),
|
|
42834
43446
|
CLAWOS_EXT_ID: extId
|
|
42835
43447
|
};
|
|
42836
|
-
const child = (0,
|
|
43448
|
+
const child = (0, import_node_child_process11.spawn)("sh", ["-c", cmd], {
|
|
42837
43449
|
cwd: dir,
|
|
42838
43450
|
env,
|
|
42839
43451
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -42939,7 +43551,7 @@ ${handle.stderrTail}`
|
|
|
42939
43551
|
|
|
42940
43552
|
// src/extension/published-channels.ts
|
|
42941
43553
|
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
42942
|
-
var
|
|
43554
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
42943
43555
|
init_zod();
|
|
42944
43556
|
var PublishedChannelsError = class extends Error {
|
|
42945
43557
|
constructor(code, message) {
|
|
@@ -43038,7 +43650,7 @@ var PublishedChannelStore = class {
|
|
|
43038
43650
|
)
|
|
43039
43651
|
};
|
|
43040
43652
|
const tmp = `${this.filePath}.tmp`;
|
|
43041
|
-
await import_promises11.default.mkdir(
|
|
43653
|
+
await import_promises11.default.mkdir(import_node_path45.default.dirname(this.filePath), { recursive: true });
|
|
43042
43654
|
await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
43043
43655
|
await import_promises11.default.rename(tmp, this.filePath);
|
|
43044
43656
|
}
|
|
@@ -43046,7 +43658,7 @@ var PublishedChannelStore = class {
|
|
|
43046
43658
|
|
|
43047
43659
|
// src/extension/bundle-cache.ts
|
|
43048
43660
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
43049
|
-
var
|
|
43661
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
43050
43662
|
var BundleCache = class {
|
|
43051
43663
|
constructor(rootDir) {
|
|
43052
43664
|
this.rootDir = rootDir;
|
|
@@ -43055,14 +43667,14 @@ var BundleCache = class {
|
|
|
43055
43667
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
43056
43668
|
async write(snapshotHash, buffer) {
|
|
43057
43669
|
await import_promises12.default.mkdir(this.rootDir, { recursive: true });
|
|
43058
|
-
const file =
|
|
43670
|
+
const file = import_node_path46.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
43059
43671
|
const tmp = `${file}.tmp`;
|
|
43060
43672
|
await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
|
|
43061
43673
|
await import_promises12.default.rename(tmp, file);
|
|
43062
43674
|
}
|
|
43063
43675
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
43064
43676
|
async read(snapshotHash) {
|
|
43065
|
-
const file =
|
|
43677
|
+
const file = import_node_path46.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
43066
43678
|
try {
|
|
43067
43679
|
return await import_promises12.default.readFile(file);
|
|
43068
43680
|
} catch (e) {
|
|
@@ -43072,7 +43684,7 @@ var BundleCache = class {
|
|
|
43072
43684
|
}
|
|
43073
43685
|
/** Idempotent — missing file is not an error. */
|
|
43074
43686
|
async delete(snapshotHash) {
|
|
43075
|
-
const file =
|
|
43687
|
+
const file = import_node_path46.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
43076
43688
|
await import_promises12.default.rm(file, { force: true });
|
|
43077
43689
|
}
|
|
43078
43690
|
};
|
|
@@ -43081,7 +43693,7 @@ var BundleCache = class {
|
|
|
43081
43693
|
async function startDaemon(config) {
|
|
43082
43694
|
const logger = createLogger({
|
|
43083
43695
|
level: config.logLevel,
|
|
43084
|
-
file:
|
|
43696
|
+
file: import_node_path47.default.join(config.dataDir, "clawd.log")
|
|
43085
43697
|
});
|
|
43086
43698
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
43087
43699
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -43168,10 +43780,11 @@ async function startDaemon(config) {
|
|
|
43168
43780
|
const agents = new AgentsScanner();
|
|
43169
43781
|
const history = new ClaudeHistoryReader();
|
|
43170
43782
|
let transport = null;
|
|
43171
|
-
const personaStore = new PersonaStore(
|
|
43783
|
+
const personaStore = new PersonaStore(import_node_path47.default.join(config.dataDir, "personas"));
|
|
43172
43784
|
const defaultsRoot = findDefaultsRoot();
|
|
43173
43785
|
if (defaultsRoot) {
|
|
43174
43786
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
43787
|
+
refreshDaemonManagedDirs({ store: personaStore, defaultsRoot, logger });
|
|
43175
43788
|
} else {
|
|
43176
43789
|
logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
|
|
43177
43790
|
}
|
|
@@ -43185,7 +43798,7 @@ async function startDaemon(config) {
|
|
|
43185
43798
|
getAdapter,
|
|
43186
43799
|
historyReader: history,
|
|
43187
43800
|
dataDir: config.dataDir,
|
|
43188
|
-
personaRoot:
|
|
43801
|
+
personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
|
|
43189
43802
|
personaStore,
|
|
43190
43803
|
ownerDisplayName,
|
|
43191
43804
|
ownerPrincipalId,
|
|
@@ -43214,10 +43827,10 @@ async function startDaemon(config) {
|
|
|
43214
43827
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
43215
43828
|
attachmentGroup: {
|
|
43216
43829
|
onFileEdit: (input) => {
|
|
43217
|
-
const absPath =
|
|
43830
|
+
const absPath = import_node_path47.default.isAbsolute(input.relPath) ? input.relPath : import_node_path47.default.join(input.cwd, input.relPath);
|
|
43218
43831
|
let size = 0;
|
|
43219
43832
|
try {
|
|
43220
|
-
size =
|
|
43833
|
+
size = import_node_fs31.default.statSync(absPath).size;
|
|
43221
43834
|
} catch (err) {
|
|
43222
43835
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
43223
43836
|
sessionId: input.sessionId,
|
|
@@ -43305,8 +43918,13 @@ async function startDaemon(config) {
|
|
|
43305
43918
|
await publishedChannelStore.load();
|
|
43306
43919
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
43307
43920
|
const appBuilderStore = new ProjectStore(
|
|
43308
|
-
|
|
43921
|
+
import_node_path47.default.join(config.dataDir, "personas/persona-app-builder")
|
|
43309
43922
|
);
|
|
43923
|
+
recoverInterruptedPublishJobs(appBuilderStore, logger).catch((err) => {
|
|
43924
|
+
logger.warn("app-builder.publish.recover-startup-failed", {
|
|
43925
|
+
error: err instanceof Error ? err.message : String(err)
|
|
43926
|
+
});
|
|
43927
|
+
});
|
|
43310
43928
|
const devServerSupervisor = new DevServerSupervisor();
|
|
43311
43929
|
devServerSupervisor.setLogger(logger);
|
|
43312
43930
|
const appBuilderPortRange = [];
|
|
@@ -43353,10 +43971,10 @@ async function startDaemon(config) {
|
|
|
43353
43971
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
43354
43972
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
43355
43973
|
// guest path guard:candidate 必须在 personaRoot 子树下
|
|
43356
|
-
personaRoot:
|
|
43974
|
+
personaRoot: import_node_path47.default.join(config.dataDir, "personas")
|
|
43357
43975
|
},
|
|
43358
43976
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
43359
|
-
personaRoot:
|
|
43977
|
+
personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
|
|
43360
43978
|
// capability:list / delete handler 依赖
|
|
43361
43979
|
capabilityManager,
|
|
43362
43980
|
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
@@ -43393,6 +44011,27 @@ async function startDaemon(config) {
|
|
|
43393
44011
|
// 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
|
|
43394
44012
|
// 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
|
|
43395
44013
|
broadcastProjectUpdated: (event) => wsServer?.broadcastToOwners(event),
|
|
44014
|
+
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
44015
|
+
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
44016
|
+
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
44017
|
+
appBuilderPersonaRoot: import_node_path47.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
44018
|
+
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
44019
|
+
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|
|
44020
|
+
// 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
|
|
44021
|
+
broadcastSessionFrame: (frame, target) => {
|
|
44022
|
+
if (target === "all") {
|
|
44023
|
+
transport?.broadcastAll(frame);
|
|
44024
|
+
return;
|
|
44025
|
+
}
|
|
44026
|
+
const sid = frame.sessionId;
|
|
44027
|
+
if (!sid) return;
|
|
44028
|
+
if (target === "first-subscriber") {
|
|
44029
|
+
const handle = transport?.firstSubscriber(sid);
|
|
44030
|
+
if (handle) handle.send(frame);
|
|
44031
|
+
return;
|
|
44032
|
+
}
|
|
44033
|
+
transport?.broadcastToSession(sid, frame);
|
|
44034
|
+
},
|
|
43396
44035
|
logger
|
|
43397
44036
|
});
|
|
43398
44037
|
const authResolver = new AuthContextResolver({
|
|
@@ -43595,8 +44234,8 @@ async function startDaemon(config) {
|
|
|
43595
44234
|
const lines = [
|
|
43596
44235
|
`Tunnel: ${r.url}`,
|
|
43597
44236
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
43598
|
-
`Frpc config: ${
|
|
43599
|
-
`Frpc log: ${
|
|
44237
|
+
`Frpc config: ${import_node_path47.default.join(config.dataDir, "frpc.toml")}`,
|
|
44238
|
+
`Frpc log: ${import_node_path47.default.join(config.dataDir, "frpc.log")}`
|
|
43600
44239
|
];
|
|
43601
44240
|
const width = Math.max(...lines.map((l) => l.length));
|
|
43602
44241
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -43609,8 +44248,8 @@ ${bar}
|
|
|
43609
44248
|
|
|
43610
44249
|
`);
|
|
43611
44250
|
try {
|
|
43612
|
-
const connectPath =
|
|
43613
|
-
|
|
44251
|
+
const connectPath = import_node_path47.default.join(config.dataDir, "connect.txt");
|
|
44252
|
+
import_node_fs31.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
43614
44253
|
} catch {
|
|
43615
44254
|
}
|
|
43616
44255
|
} catch (err) {
|
|
@@ -43679,9 +44318,9 @@ ${bar}
|
|
|
43679
44318
|
};
|
|
43680
44319
|
}
|
|
43681
44320
|
function migrateDropPersonsDir(dataDir) {
|
|
43682
|
-
const dir =
|
|
44321
|
+
const dir = import_node_path47.default.join(dataDir, "persons");
|
|
43683
44322
|
try {
|
|
43684
|
-
|
|
44323
|
+
import_node_fs31.default.rmSync(dir, { recursive: true, force: true });
|
|
43685
44324
|
} catch {
|
|
43686
44325
|
}
|
|
43687
44326
|
}
|