@clawos-dev/clawd 0.2.123-beta.243.5a9518b → 0.2.123-beta.244.5e5107f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -207,7 +207,16 @@ var init_methods = __esm({
207
207
  "appBuilder:reportStage",
208
208
  // 显式控制 fix (spec 2026-06-02-app-builder-explicit-dev-server-control):用户点"收起预览"按钮触发,
209
209
  // 对称 startDevServer。砍掉了 setActiveSession(dev server 不再跟用户 focus 走),切 session 不动 dev server。
210
- "appBuilder:stopDevServer"
210
+ "appBuilder:stopDevServer",
211
+ // 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design):
212
+ // publish:PreviewPane「发布上线」按钮 onClick 调本 RPC,daemon 内串行化 + spawn publish.sh +
213
+ // 广播阶段进度帧 (appBuilder:publish-progress / publish-failed)。同 project 已有 in-flight job
214
+ // 返回 already-publishing + 既有 jobId(UI 不报错继续订阅)。失败时 daemon 调 manager.send()
215
+ // 注入 user_text 到 chat,由 assistant 接管完成部署。
216
+ // dismissPublishJob:取消 in-flight publish(SIGTERM 子进程 + 清字段)或清掉 daemon 崩溃留下的
217
+ // 中断残骸 publishJob 字段。UI 端 [取消] / [interrupted 取消] 都走这个 RPC。
218
+ "appBuilder:publish",
219
+ "appBuilder:dismissPublishJob"
211
220
  ];
212
221
  }
213
222
  });
@@ -4557,7 +4566,7 @@ var init_persona_schemas = __esm({
4557
4566
  });
4558
4567
 
4559
4568
  // ../protocol/src/schemas.ts
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;
4569
+ var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
4561
4570
  var init_schemas = __esm({
4562
4571
  "../protocol/src/schemas.ts"() {
4563
4572
  "use strict";
@@ -5218,7 +5227,22 @@ var init_schemas = __esm({
5218
5227
  * optional:缺省时 supervisor 用默认 /Local:\s+https?:\/\//(vite "Local: http://..." 输出)。
5219
5228
  * 老板换非 vite 技术栈(Next.js "Ready in"、webpack "Compiled" 等)时自定义这个字段。
5220
5229
  */
5221
- readyPattern: external_exports.string().optional()
5230
+ readyPattern: external_exports.string().optional(),
5231
+ /**
5232
+ * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
5233
+ * in-flight 发布 job 的元数据。仅承载"in-flight"或"中断残骸"两种状态:
5234
+ * - daemon spawn publish.sh + 收到第一个 ::stage::build marker 时写入
5235
+ * - 每次 stage 切换更新 stage 字段
5236
+ * - 成功 / 正常失败 / 用户 dismiss 时立即清掉
5237
+ * - 只有 daemon 崩才会留残骸(子进程随 daemon 一起死)
5238
+ * status 字段在 ProjectWithStatus 派生而不在这里 —— 本 schema 是落盘形态,
5239
+ * "in-flight vs interrupted" 区分由 daemon 响应时实时拼 registry 状态得出。
5240
+ */
5241
+ publishJob: external_exports.object({
5242
+ jobId: external_exports.string().min(1),
5243
+ stage: external_exports.enum(["build", "deploy", "verify"]),
5244
+ startedAt: external_exports.number().int().nonnegative()
5245
+ }).optional()
5222
5246
  });
5223
5247
  PROJECT_STAGE_VALUES = [
5224
5248
  "install-pending",
@@ -5245,7 +5269,21 @@ var init_schemas = __esm({
5245
5269
  */
5246
5270
  stage: ProjectStageSchema.optional(),
5247
5271
  /** stage='failed' 时携带失败原因 */
5248
- stageReason: external_exports.string().optional()
5272
+ stageReason: external_exports.string().optional(),
5273
+ /**
5274
+ * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.3):
5275
+ * ProjectMetadata.publishJob 的 status 派生版。daemon 响应时按以下规则实时拼装:
5276
+ * - 落盘字段存在 + PublishJobRegistry 有匹配 jobId → 'in-flight'
5277
+ * - 落盘字段存在 + Registry 无匹配(jobId 对不上或 entry 缺失)→ 'interrupted'(daemon 崩残骸)
5278
+ * - 落盘字段不存在 → undefined(字段省略)
5279
+ * UI 用 status 决定 PreviewPane 渲染分支(in-flight 三段 checklist / interrupted 恢复条)。
5280
+ */
5281
+ publishJob: external_exports.object({
5282
+ jobId: external_exports.string().min(1),
5283
+ stage: external_exports.enum(["build", "deploy", "verify"]),
5284
+ startedAt: external_exports.number().int().nonnegative(),
5285
+ status: external_exports.enum(["in-flight", "interrupted"])
5286
+ }).optional()
5249
5287
  });
5250
5288
  ListProjectsArgsSchema = external_exports.object({}).strict();
5251
5289
  ListProjectsResultSchema = external_exports.object({
@@ -5310,6 +5348,33 @@ var init_schemas = __esm({
5310
5348
  /** stage='failed' 时的失败原因 */
5311
5349
  stageReason: external_exports.string().optional()
5312
5350
  }).strict();
5351
+ PublishArgsSchema = external_exports.object({
5352
+ name: external_exports.string().regex(projectNameRegex)
5353
+ });
5354
+ PublishResultSchema = external_exports.object({
5355
+ jobId: external_exports.string().min(1),
5356
+ status: external_exports.enum(["started", "already-publishing"])
5357
+ }).strict();
5358
+ DismissPublishJobArgsSchema = external_exports.object({
5359
+ name: external_exports.string().regex(projectNameRegex)
5360
+ });
5361
+ DismissPublishJobResultSchema = external_exports.object({
5362
+ ok: external_exports.literal(true)
5363
+ }).strict();
5364
+ AppBuilderPublishProgressEventSchema = external_exports.object({
5365
+ type: external_exports.literal("appBuilder:publish-progress"),
5366
+ name: external_exports.string().regex(projectNameRegex),
5367
+ jobId: external_exports.string().min(1),
5368
+ stage: external_exports.enum(["build", "deploy", "verify"]),
5369
+ status: external_exports.enum(["started", "completed"])
5370
+ }).strict();
5371
+ AppBuilderPublishFailedEventSchema = external_exports.object({
5372
+ type: external_exports.literal("appBuilder:publish-failed"),
5373
+ name: external_exports.string().regex(projectNameRegex),
5374
+ jobId: external_exports.string().min(1),
5375
+ stage: external_exports.enum(["build", "deploy", "verify", "unknown"]),
5376
+ errorSummary: external_exports.string()
5377
+ }).strict();
5313
5378
  }
5314
5379
  });
5315
5380
 
@@ -7835,7 +7900,7 @@ var require_thread_stream = __commonJS({
7835
7900
  var { version: version2 } = require_package();
7836
7901
  var { EventEmitter: EventEmitter3 } = require("events");
7837
7902
  var { Worker } = require("worker_threads");
7838
- var { join: join13 } = require("path");
7903
+ var { join: join15 } = require("path");
7839
7904
  var { pathToFileURL } = require("url");
7840
7905
  var { wait } = require_wait();
7841
7906
  var {
@@ -7871,7 +7936,7 @@ var require_thread_stream = __commonJS({
7871
7936
  function createWorker(stream, opts) {
7872
7937
  const { filename, workerData } = opts;
7873
7938
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
7874
- const toExecute = bundlerOverrides["thread-stream-worker"] || join13(__dirname, "lib", "worker.js");
7939
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join15(__dirname, "lib", "worker.js");
7875
7940
  const worker = new Worker(toExecute, {
7876
7941
  ...opts.workerOpts,
7877
7942
  trackUnmanagedFds: false,
@@ -8257,7 +8322,7 @@ var require_transport = __commonJS({
8257
8322
  "use strict";
8258
8323
  var { createRequire } = require("module");
8259
8324
  var getCallers = require_caller();
8260
- var { join: join13, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
8325
+ var { join: join15, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
8261
8326
  var sleep = require_atomic_sleep();
8262
8327
  var onExit = require_on_exit_leak_free();
8263
8328
  var ThreadStream = require_thread_stream();
@@ -8320,7 +8385,7 @@ var require_transport = __commonJS({
8320
8385
  throw new Error("only one of target or targets can be specified");
8321
8386
  }
8322
8387
  if (targets) {
8323
- target = bundlerOverrides["pino-worker"] || join13(__dirname, "worker.js");
8388
+ target = bundlerOverrides["pino-worker"] || join15(__dirname, "worker.js");
8324
8389
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
8325
8390
  return {
8326
8391
  ...dest,
@@ -8338,7 +8403,7 @@ var require_transport = __commonJS({
8338
8403
  });
8339
8404
  });
8340
8405
  } else if (pipeline3) {
8341
- target = bundlerOverrides["pino-worker"] || join13(__dirname, "worker.js");
8406
+ target = bundlerOverrides["pino-worker"] || join15(__dirname, "worker.js");
8342
8407
  options.pipelines = [pipeline3.map((dest) => {
8343
8408
  return {
8344
8409
  ...dest,
@@ -8360,7 +8425,7 @@ var require_transport = __commonJS({
8360
8425
  return origin;
8361
8426
  }
8362
8427
  if (origin === "pino/file") {
8363
- return join13(__dirname, "..", "file.js");
8428
+ return join15(__dirname, "..", "file.js");
8364
8429
  }
8365
8430
  let fixTarget2;
8366
8431
  for (const filePath of callers) {
@@ -9350,7 +9415,7 @@ var require_safe_stable_stringify = __commonJS({
9350
9415
  return circularValue;
9351
9416
  }
9352
9417
  let res = "";
9353
- let join13 = ",";
9418
+ let join15 = ",";
9354
9419
  const originalIndentation = indentation;
9355
9420
  if (Array.isArray(value)) {
9356
9421
  if (value.length === 0) {
@@ -9364,7 +9429,7 @@ var require_safe_stable_stringify = __commonJS({
9364
9429
  indentation += spacer;
9365
9430
  res += `
9366
9431
  ${indentation}`;
9367
- join13 = `,
9432
+ join15 = `,
9368
9433
  ${indentation}`;
9369
9434
  }
9370
9435
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -9372,13 +9437,13 @@ ${indentation}`;
9372
9437
  for (; i < maximumValuesToStringify - 1; i++) {
9373
9438
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
9374
9439
  res += tmp2 !== void 0 ? tmp2 : "null";
9375
- res += join13;
9440
+ res += join15;
9376
9441
  }
9377
9442
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
9378
9443
  res += tmp !== void 0 ? tmp : "null";
9379
9444
  if (value.length - 1 > maximumBreadth) {
9380
9445
  const removedKeys = value.length - maximumBreadth - 1;
9381
- res += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
9446
+ res += `${join15}"... ${getItemCount(removedKeys)} not stringified"`;
9382
9447
  }
9383
9448
  if (spacer !== "") {
9384
9449
  res += `
@@ -9399,7 +9464,7 @@ ${originalIndentation}`;
9399
9464
  let separator = "";
9400
9465
  if (spacer !== "") {
9401
9466
  indentation += spacer;
9402
- join13 = `,
9467
+ join15 = `,
9403
9468
  ${indentation}`;
9404
9469
  whitespace = " ";
9405
9470
  }
@@ -9413,13 +9478,13 @@ ${indentation}`;
9413
9478
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
9414
9479
  if (tmp !== void 0) {
9415
9480
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
9416
- separator = join13;
9481
+ separator = join15;
9417
9482
  }
9418
9483
  }
9419
9484
  if (keyLength > maximumBreadth) {
9420
9485
  const removedKeys = keyLength - maximumBreadth;
9421
9486
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
9422
- separator = join13;
9487
+ separator = join15;
9423
9488
  }
9424
9489
  if (spacer !== "" && separator.length > 1) {
9425
9490
  res = `
@@ -9460,7 +9525,7 @@ ${originalIndentation}`;
9460
9525
  }
9461
9526
  const originalIndentation = indentation;
9462
9527
  let res = "";
9463
- let join13 = ",";
9528
+ let join15 = ",";
9464
9529
  if (Array.isArray(value)) {
9465
9530
  if (value.length === 0) {
9466
9531
  return "[]";
@@ -9473,7 +9538,7 @@ ${originalIndentation}`;
9473
9538
  indentation += spacer;
9474
9539
  res += `
9475
9540
  ${indentation}`;
9476
- join13 = `,
9541
+ join15 = `,
9477
9542
  ${indentation}`;
9478
9543
  }
9479
9544
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -9481,13 +9546,13 @@ ${indentation}`;
9481
9546
  for (; i < maximumValuesToStringify - 1; i++) {
9482
9547
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
9483
9548
  res += tmp2 !== void 0 ? tmp2 : "null";
9484
- res += join13;
9549
+ res += join15;
9485
9550
  }
9486
9551
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
9487
9552
  res += tmp !== void 0 ? tmp : "null";
9488
9553
  if (value.length - 1 > maximumBreadth) {
9489
9554
  const removedKeys = value.length - maximumBreadth - 1;
9490
- res += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
9555
+ res += `${join15}"... ${getItemCount(removedKeys)} not stringified"`;
9491
9556
  }
9492
9557
  if (spacer !== "") {
9493
9558
  res += `
@@ -9500,7 +9565,7 @@ ${originalIndentation}`;
9500
9565
  let whitespace = "";
9501
9566
  if (spacer !== "") {
9502
9567
  indentation += spacer;
9503
- join13 = `,
9568
+ join15 = `,
9504
9569
  ${indentation}`;
9505
9570
  whitespace = " ";
9506
9571
  }
@@ -9509,7 +9574,7 @@ ${indentation}`;
9509
9574
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
9510
9575
  if (tmp !== void 0) {
9511
9576
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
9512
- separator = join13;
9577
+ separator = join15;
9513
9578
  }
9514
9579
  }
9515
9580
  if (spacer !== "" && separator.length > 1) {
@@ -9567,20 +9632,20 @@ ${originalIndentation}`;
9567
9632
  indentation += spacer;
9568
9633
  let res2 = `
9569
9634
  ${indentation}`;
9570
- const join14 = `,
9635
+ const join16 = `,
9571
9636
  ${indentation}`;
9572
9637
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
9573
9638
  let i = 0;
9574
9639
  for (; i < maximumValuesToStringify - 1; i++) {
9575
9640
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
9576
9641
  res2 += tmp2 !== void 0 ? tmp2 : "null";
9577
- res2 += join14;
9642
+ res2 += join16;
9578
9643
  }
9579
9644
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
9580
9645
  res2 += tmp !== void 0 ? tmp : "null";
9581
9646
  if (value.length - 1 > maximumBreadth) {
9582
9647
  const removedKeys = value.length - maximumBreadth - 1;
9583
- res2 += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
9648
+ res2 += `${join16}"... ${getItemCount(removedKeys)} not stringified"`;
9584
9649
  }
9585
9650
  res2 += `
9586
9651
  ${originalIndentation}`;
@@ -9596,16 +9661,16 @@ ${originalIndentation}`;
9596
9661
  return '"[Object]"';
9597
9662
  }
9598
9663
  indentation += spacer;
9599
- const join13 = `,
9664
+ const join15 = `,
9600
9665
  ${indentation}`;
9601
9666
  let res = "";
9602
9667
  let separator = "";
9603
9668
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
9604
9669
  if (isTypedArrayWithEntries(value)) {
9605
- res += stringifyTypedArray(value, join13, maximumBreadth);
9670
+ res += stringifyTypedArray(value, join15, maximumBreadth);
9606
9671
  keys = keys.slice(value.length);
9607
9672
  maximumPropertiesToStringify -= value.length;
9608
- separator = join13;
9673
+ separator = join15;
9609
9674
  }
9610
9675
  if (deterministic) {
9611
9676
  keys = sort(keys, comparator);
@@ -9616,13 +9681,13 @@ ${indentation}`;
9616
9681
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
9617
9682
  if (tmp !== void 0) {
9618
9683
  res += `${separator}${strEscape(key2)}: ${tmp}`;
9619
- separator = join13;
9684
+ separator = join15;
9620
9685
  }
9621
9686
  }
9622
9687
  if (keyLength > maximumBreadth) {
9623
9688
  const removedKeys = keyLength - maximumBreadth;
9624
9689
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
9625
- separator = join13;
9690
+ separator = join15;
9626
9691
  }
9627
9692
  if (separator !== "") {
9628
9693
  res = `
@@ -21209,7 +21274,7 @@ var require_BufferList = __commonJS({
21209
21274
  this.head = this.tail = null;
21210
21275
  this.length = 0;
21211
21276
  };
21212
- BufferList.prototype.join = function join13(s) {
21277
+ BufferList.prototype.join = function join15(s) {
21213
21278
  if (this.length === 0) return "";
21214
21279
  var p2 = this.head;
21215
21280
  var ret = "" + p2.data;
@@ -30666,7 +30731,7 @@ var require_lib3 = __commonJS({
30666
30731
  // src/run-case/recorder.ts
30667
30732
  function startRunCaseRecorder(opts) {
30668
30733
  const now = opts.now ?? Date.now;
30669
- const dir = import_node_path46.default.dirname(opts.recordPath);
30734
+ const dir = import_node_path48.default.dirname(opts.recordPath);
30670
30735
  let stream = null;
30671
30736
  let closing = false;
30672
30737
  let closedSettled = false;
@@ -30680,8 +30745,8 @@ function startRunCaseRecorder(opts) {
30680
30745
  });
30681
30746
  const ensureStream = () => {
30682
30747
  if (stream) return stream;
30683
- import_node_fs31.default.mkdirSync(dir, { recursive: true });
30684
- stream = import_node_fs31.default.createWriteStream(opts.recordPath, { flags: "a" });
30748
+ import_node_fs32.default.mkdirSync(dir, { recursive: true });
30749
+ stream = import_node_fs32.default.createWriteStream(opts.recordPath, { flags: "a" });
30685
30750
  stream.on("close", () => closedResolve());
30686
30751
  return stream;
30687
30752
  };
@@ -30706,12 +30771,12 @@ function startRunCaseRecorder(opts) {
30706
30771
  };
30707
30772
  return { tap, close, closed };
30708
30773
  }
30709
- var import_node_fs31, import_node_path46;
30774
+ var import_node_fs32, import_node_path48;
30710
30775
  var init_recorder = __esm({
30711
30776
  "src/run-case/recorder.ts"() {
30712
30777
  "use strict";
30713
- import_node_fs31 = __toESM(require("fs"), 1);
30714
- import_node_path46 = __toESM(require("path"), 1);
30778
+ import_node_fs32 = __toESM(require("fs"), 1);
30779
+ import_node_path48 = __toESM(require("path"), 1);
30715
30780
  }
30716
30781
  });
30717
30782
 
@@ -30754,7 +30819,7 @@ var init_wire = __esm({
30754
30819
  // src/run-case/controller.ts
30755
30820
  async function runController(opts) {
30756
30821
  const now = opts.now ?? Date.now;
30757
- const cwd = opts.cwd ?? (0, import_node_fs32.mkdtempSync)(import_node_path47.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30822
+ const cwd = opts.cwd ?? (0, import_node_fs33.mkdtempSync)(import_node_path49.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
30758
30823
  const ownsCwd = opts.cwd === void 0;
30759
30824
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
30760
30825
  const spawnCtx = { cwd };
@@ -30915,19 +30980,19 @@ async function runController(opts) {
30915
30980
  if (sigintHandler) process.off("SIGINT", sigintHandler);
30916
30981
  if (ownsCwd) {
30917
30982
  try {
30918
- (0, import_node_fs32.rmSync)(cwd, { recursive: true, force: true });
30983
+ (0, import_node_fs33.rmSync)(cwd, { recursive: true, force: true });
30919
30984
  } catch {
30920
30985
  }
30921
30986
  }
30922
30987
  return exitCode ?? 0;
30923
30988
  }
30924
- var import_node_fs32, import_node_os19, import_node_path47;
30989
+ var import_node_fs33, import_node_os19, import_node_path49;
30925
30990
  var init_controller = __esm({
30926
30991
  "src/run-case/controller.ts"() {
30927
30992
  "use strict";
30928
- import_node_fs32 = require("fs");
30993
+ import_node_fs33 = require("fs");
30929
30994
  import_node_os19 = __toESM(require("os"), 1);
30930
- import_node_path47 = __toESM(require("path"), 1);
30995
+ import_node_path49 = __toESM(require("path"), 1);
30931
30996
  init_claude();
30932
30997
  init_stdout_splitter();
30933
30998
  init_permission_stdio();
@@ -31162,8 +31227,8 @@ Env (advanced):
31162
31227
  `;
31163
31228
 
31164
31229
  // src/index.ts
31165
- var import_node_path45 = __toESM(require("path"), 1);
31166
- var import_node_fs30 = __toESM(require("fs"), 1);
31230
+ var import_node_path47 = __toESM(require("path"), 1);
31231
+ var import_node_fs31 = __toESM(require("fs"), 1);
31167
31232
 
31168
31233
  // src/logger.ts
31169
31234
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -32970,6 +33035,14 @@ var SessionManager = class {
32970
33035
  // guest sessions 由 handlers/session.ts list 处按 ctx.principal.kind === 'guest' 做权限过滤
32971
33036
  // (creatorPrincipalId === capId), 这里仅负责数据源汇总.
32972
33037
  // listener sub-session 不走这条(listener 走 persona:listSubSessions RPC 单独入口)。
33038
+ /**
33039
+ * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.2.2):
33040
+ * 反查绑定到指定 app-builder project 的 session。按 invariant "1 Session ↔ 1 Project"
33041
+ * 应当唯一;找不到 → undefined(项目已成孤儿)。
33042
+ */
33043
+ findOwnedSessionByAppBuilderProject(projectName) {
33044
+ return this.listAllOwned().find((file) => file.appBuilderProject === projectName);
33045
+ }
32973
33046
  listAllOwned() {
32974
33047
  const out = [];
32975
33048
  out.push(...this.deps.store.list());
@@ -34941,6 +35014,34 @@ function copyBundleExtras(srcDir, dstDir) {
34941
35014
  }
34942
35015
  }
34943
35016
  }
35017
+ var DAEMON_MANAGED_DIRS = ["extension-kit"];
35018
+ function refreshDaemonManagedDirs(args) {
35019
+ const entries = args.entries ?? DEFAULT_PERSONAS;
35020
+ for (const entry of entries) {
35021
+ const bundleDir = path11.join(args.defaultsRoot, entry.personaId);
35022
+ if (!fs9.existsSync(bundleDir)) continue;
35023
+ const personaDir = args.store.personaDirPath(entry.personaId);
35024
+ if (!fs9.existsSync(personaDir)) continue;
35025
+ for (const subdir of DAEMON_MANAGED_DIRS) {
35026
+ const srcPath = path11.join(bundleDir, subdir);
35027
+ if (!fs9.existsSync(srcPath)) continue;
35028
+ const dstPath = path11.join(personaDir, subdir);
35029
+ try {
35030
+ fs9.cpSync(srcPath, dstPath, { recursive: true, force: true, dereference: true });
35031
+ args.logger.info("persona.refresh.synced", {
35032
+ personaId: entry.personaId,
35033
+ subdir
35034
+ });
35035
+ } catch (err) {
35036
+ args.logger.warn("persona.refresh.failed", {
35037
+ personaId: entry.personaId,
35038
+ subdir,
35039
+ error: err instanceof Error ? err.message : String(err)
35040
+ });
35041
+ }
35042
+ }
35043
+ }
35044
+ }
34944
35045
 
34945
35046
  // src/index.ts
34946
35047
  init_claude();
@@ -42156,12 +42257,373 @@ function listPidsOnPort(port) {
42156
42257
  });
42157
42258
  }
42158
42259
 
42260
+ // src/app-builder/publish-registry.ts
42261
+ var import_node_crypto14 = require("crypto");
42262
+ var PublishJobRegistry = class {
42263
+ jobs = /* @__PURE__ */ new Map();
42264
+ has(name) {
42265
+ return this.jobs.has(name);
42266
+ }
42267
+ get(name) {
42268
+ return this.jobs.get(name);
42269
+ }
42270
+ /**
42271
+ * args.jobId 仅用于测试注入;生产代码不传,registry 自己生成。
42272
+ * args.now 仅用于测试注入;生产代码不传,用 Date.now()。
42273
+ */
42274
+ create(args) {
42275
+ if (this.jobs.has(args.name)) {
42276
+ throw new Error(`already publishing: ${args.name}`);
42277
+ }
42278
+ const jobId = args.jobId ?? `job-${(0, import_node_crypto14.randomUUID)()}`;
42279
+ this.jobs.set(args.name, {
42280
+ jobId,
42281
+ name: args.name,
42282
+ stage: "build",
42283
+ sessionId: args.sessionId,
42284
+ childProc: args.childProc,
42285
+ dismissed: false,
42286
+ sawAnyStage: false,
42287
+ startedAt: args.now ?? Date.now()
42288
+ });
42289
+ return jobId;
42290
+ }
42291
+ updateStage(name, stage) {
42292
+ const job = this.jobs.get(name);
42293
+ if (!job) return;
42294
+ job.stage = stage;
42295
+ job.sawAnyStage = true;
42296
+ }
42297
+ markDismissed(name) {
42298
+ const job = this.jobs.get(name);
42299
+ if (!job) return;
42300
+ job.dismissed = true;
42301
+ }
42302
+ remove(name) {
42303
+ const job = this.jobs.get(name);
42304
+ if (!job) return void 0;
42305
+ this.jobs.delete(name);
42306
+ return { sessionId: job.sessionId };
42307
+ }
42308
+ };
42309
+
42310
+ // src/app-builder/publish-job-runner.ts
42311
+ var import_node_child_process8 = require("child_process");
42312
+ var import_node_fs29 = require("fs");
42313
+ var import_node_path39 = require("path");
42314
+
42315
+ // src/app-builder/publish-stage-parser.ts
42316
+ var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
42317
+ var PROD_URL_RE = /^\s*::prod-url::(\S+)\s*$/;
42318
+ var URL_RE = /\bhttps?:\/\/[^\s]+/;
42319
+ function parsePublishLine(line) {
42320
+ const stageMatch = STAGE_RE.exec(line);
42321
+ if (stageMatch) return { kind: "stage", stage: stageMatch[1] };
42322
+ const urlMatch = PROD_URL_RE.exec(line);
42323
+ if (urlMatch) return { kind: "prod-url", url: urlMatch[1] };
42324
+ return null;
42325
+ }
42326
+ function extractProdUrl(text) {
42327
+ const m2 = URL_RE.exec(text);
42328
+ return m2 ? m2[0] : null;
42329
+ }
42330
+ function tailStderrLines(buf, n) {
42331
+ if (!buf) return "";
42332
+ const lines = buf.split("\n");
42333
+ if (lines.length <= n) return buf;
42334
+ return lines.slice(lines.length - n).join("\n");
42335
+ }
42336
+
42337
+ // src/app-builder/publish-job-runner.ts
42338
+ async function startPublishJob(deps, args) {
42339
+ const { registry: registry2, projectDir } = deps;
42340
+ const spawn9 = deps.spawnImpl ?? import_node_child_process8.spawn;
42341
+ if (registry2.has(args.name)) {
42342
+ return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
42343
+ }
42344
+ const projDir = projectDir(args.name);
42345
+ const logPath = (0, import_node_path39.join)(projDir, ".publish.log");
42346
+ let logStream = null;
42347
+ try {
42348
+ logStream = (0, import_node_fs29.createWriteStream)(logPath, { flags: "w" });
42349
+ } catch {
42350
+ logStream = null;
42351
+ }
42352
+ const child = spawn9("bash", [args.scriptPath, projDir], {
42353
+ cwd: projDir,
42354
+ env: process.env,
42355
+ stdio: ["ignore", "pipe", "pipe"]
42356
+ });
42357
+ const jobId = registry2.create({
42358
+ name: args.name,
42359
+ sessionId: args.sessionId,
42360
+ childProc: child
42361
+ });
42362
+ deps.logger?.info("app-builder.publish.spawned", {
42363
+ name: args.name,
42364
+ jobId,
42365
+ pid: child.pid ?? null,
42366
+ scriptPath: args.scriptPath,
42367
+ logPath
42368
+ });
42369
+ child.on("error", (err) => {
42370
+ deps.logger?.warn("app-builder.publish.spawn-error", {
42371
+ name: args.name,
42372
+ jobId,
42373
+ error: err instanceof Error ? err.message : String(err)
42374
+ });
42375
+ void handleExit(deps, {
42376
+ name: args.name,
42377
+ jobId,
42378
+ code: null,
42379
+ signal: null,
42380
+ urlCandidate: null,
42381
+ stderrTail: err instanceof Error ? err.message : String(err),
42382
+ logPath
42383
+ });
42384
+ });
42385
+ let urlFromMarker = null;
42386
+ let urlFallback = null;
42387
+ let stdoutBuf = "";
42388
+ child.stdout?.on("data", (chunk) => {
42389
+ if (logStream) logStream.write(chunk);
42390
+ stdoutBuf += chunk.toString();
42391
+ let nlIdx;
42392
+ while ((nlIdx = stdoutBuf.indexOf("\n")) >= 0) {
42393
+ const line = stdoutBuf.slice(0, nlIdx);
42394
+ stdoutBuf = stdoutBuf.slice(nlIdx + 1);
42395
+ handleStdoutLine(deps, args.name, jobId, line);
42396
+ const parsedForUrl = parsePublishLine(line);
42397
+ if (parsedForUrl?.kind === "prod-url") urlFromMarker = parsedForUrl.url;
42398
+ const url = extractProdUrl(line);
42399
+ if (url && !urlFallback) urlFallback = url;
42400
+ }
42401
+ });
42402
+ let stderrBuf = "";
42403
+ child.stderr?.on("data", (chunk) => {
42404
+ if (logStream) logStream.write(chunk);
42405
+ stderrBuf += chunk.toString();
42406
+ });
42407
+ child.on("exit", (code, signal) => {
42408
+ if (logStream) logStream.end();
42409
+ const urlCandidate = urlFromMarker ?? urlFallback;
42410
+ deps.logger?.info("app-builder.publish.child-exit", {
42411
+ name: args.name,
42412
+ jobId,
42413
+ code,
42414
+ signal,
42415
+ hadUrl: !!urlCandidate,
42416
+ urlSource: urlFromMarker ? "marker" : urlFallback ? "fallback" : "none",
42417
+ stderrBytes: stderrBuf.length
42418
+ });
42419
+ void handleExit(deps, {
42420
+ name: args.name,
42421
+ jobId,
42422
+ code,
42423
+ signal,
42424
+ urlCandidate,
42425
+ stderrTail: tailStderrLines(stderrBuf, 20),
42426
+ logPath
42427
+ });
42428
+ });
42429
+ return { jobId, status: "started" };
42430
+ }
42431
+ function handleStdoutLine(deps, name, jobId, line) {
42432
+ const parsed = parsePublishLine(line);
42433
+ if (parsed?.kind !== "stage") return;
42434
+ advanceStage(deps, name, jobId, parsed.stage);
42435
+ }
42436
+ function advanceStage(deps, name, jobId, next) {
42437
+ const job = deps.registry.get(name);
42438
+ if (!job) return;
42439
+ const prev = job.stage;
42440
+ const wasFirstStage = !job.sawAnyStage;
42441
+ deps.logger?.info("app-builder.publish.stage-advance", {
42442
+ name,
42443
+ jobId,
42444
+ prev: wasFirstStage ? null : prev,
42445
+ next,
42446
+ firstStage: wasFirstStage
42447
+ });
42448
+ if (wasFirstStage) {
42449
+ deps.registry.updateStage(name, next);
42450
+ void deps.setPublishJob(name, {
42451
+ jobId,
42452
+ stage: next,
42453
+ startedAt: job.startedAt
42454
+ });
42455
+ deps.broadcast({
42456
+ type: "appBuilder:publish-progress",
42457
+ name,
42458
+ jobId,
42459
+ stage: next,
42460
+ status: "started"
42461
+ });
42462
+ return;
42463
+ }
42464
+ if (prev === next) {
42465
+ return;
42466
+ }
42467
+ deps.broadcast({
42468
+ type: "appBuilder:publish-progress",
42469
+ name,
42470
+ jobId,
42471
+ stage: prev,
42472
+ status: "completed"
42473
+ });
42474
+ deps.registry.updateStage(name, next);
42475
+ void deps.setPublishJob(name, {
42476
+ jobId,
42477
+ stage: next,
42478
+ startedAt: job.startedAt
42479
+ });
42480
+ deps.broadcast({
42481
+ type: "appBuilder:publish-progress",
42482
+ name,
42483
+ jobId,
42484
+ stage: next,
42485
+ status: "started"
42486
+ });
42487
+ }
42488
+ async function handleExit(deps, args) {
42489
+ const job = deps.registry.get(args.name);
42490
+ if (args.signal === "SIGTERM" && job?.dismissed) {
42491
+ try {
42492
+ await deps.clearPublishJob(args.name);
42493
+ } catch (err) {
42494
+ console.error("[publish] clearPublishJob failed (dismissed branch)", err);
42495
+ }
42496
+ deps.registry.remove(args.name);
42497
+ return;
42498
+ }
42499
+ if (args.code === 0 && args.urlCandidate) {
42500
+ if (job?.sawAnyStage) {
42501
+ deps.broadcast({
42502
+ type: "appBuilder:publish-progress",
42503
+ name: args.name,
42504
+ jobId: args.jobId,
42505
+ stage: job.stage,
42506
+ status: "completed"
42507
+ });
42508
+ }
42509
+ try {
42510
+ await deps.setProdUrl(args.name, args.urlCandidate);
42511
+ } catch (err) {
42512
+ console.error("[publish] setProdUrl failed; falling through to failure branch", err);
42513
+ await emitFailure(deps, args, "\u8C03 setProdUrl \u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)));
42514
+ return;
42515
+ }
42516
+ try {
42517
+ await deps.clearPublishJob(args.name);
42518
+ } catch (err) {
42519
+ console.error("[publish] clearPublishJob failed (success branch)", err);
42520
+ }
42521
+ deps.registry.remove(args.name);
42522
+ return;
42523
+ }
42524
+ await emitFailure(deps, args, buildErrorSummary(args));
42525
+ }
42526
+ async function emitFailure(deps, args, errorSummary) {
42527
+ const job = deps.registry.get(args.name);
42528
+ const failStage = job?.sawAnyStage ? job.stage : "unknown";
42529
+ deps.logger?.warn("app-builder.publish.failed", {
42530
+ name: args.name,
42531
+ jobId: args.jobId,
42532
+ stage: failStage,
42533
+ errorSummary: errorSummary.slice(0, 500)
42534
+ });
42535
+ try {
42536
+ await deps.clearPublishJob(args.name);
42537
+ } catch (err) {
42538
+ deps.logger?.warn("app-builder.publish.clear-field-failed", {
42539
+ name: args.name,
42540
+ jobId: args.jobId,
42541
+ error: err instanceof Error ? err.message : String(err)
42542
+ });
42543
+ }
42544
+ const removed = deps.registry.remove(args.name);
42545
+ deps.broadcast({
42546
+ type: "appBuilder:publish-failed",
42547
+ name: args.name,
42548
+ jobId: args.jobId,
42549
+ stage: failStage,
42550
+ errorSummary
42551
+ });
42552
+ if (removed) {
42553
+ const msg = formatFailureMessage(failStage, errorSummary, args.logPath);
42554
+ try {
42555
+ await deps.injectUserText(removed.sessionId, msg);
42556
+ deps.logger?.info("app-builder.publish.failure-injected-to-chat", {
42557
+ name: args.name,
42558
+ jobId: args.jobId,
42559
+ sessionId: removed.sessionId
42560
+ });
42561
+ } catch (err) {
42562
+ deps.logger?.warn("app-builder.publish.inject-user-text-failed", {
42563
+ name: args.name,
42564
+ jobId: args.jobId,
42565
+ sessionId: removed.sessionId,
42566
+ error: err instanceof Error ? err.message : String(err)
42567
+ });
42568
+ }
42569
+ } else {
42570
+ deps.logger?.warn("app-builder.publish.no-registry-entry-on-failure", {
42571
+ name: args.name,
42572
+ jobId: args.jobId
42573
+ });
42574
+ }
42575
+ }
42576
+ function buildErrorSummary(args) {
42577
+ if (args.signal) {
42578
+ return `publish.sh \u88AB\u4E2D\u65AD\uFF08signal: ${args.signal}\uFF09`;
42579
+ }
42580
+ if (args.code === 0 && !args.urlCandidate) {
42581
+ return "publish.sh \u6210\u529F\u9000\u51FA\u4F46\u672A\u8F93\u51FA\u516C\u7F51 URL";
42582
+ }
42583
+ return args.stderrTail || `publish.sh \u5F02\u5E38\u9000\u51FA\uFF08exit code: ${args.code}\uFF09`;
42584
+ }
42585
+ function formatFailureMessage(stage, errorSummary, logPath) {
42586
+ return `\u53D1\u5E03\u5931\u8D25\uFF1A[${stage}] \u9636\u6BB5
42587
+ ${errorSummary}
42588
+
42589
+ \u5B8C\u6574\u65E5\u5FD7\uFF1A${logPath}
42590
+ \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`;
42591
+ }
42592
+ async function recoverInterruptedJobs(deps) {
42593
+ const projects = await deps.listAllProjects();
42594
+ for (const p2 of projects) {
42595
+ if (p2.publishJob) {
42596
+ deps.log(
42597
+ `[publish] found interrupted job ${p2.publishJob.jobId} for ${p2.name}, stage=${p2.publishJob.stage}`
42598
+ );
42599
+ }
42600
+ }
42601
+ }
42602
+
42159
42603
  // src/handlers/app-builder.ts
42160
42604
  init_protocol();
42605
+ var import_node_path40 = require("path");
42606
+ var PUBLISH_SCRIPT_REL = "extension-kit/scripts/publish.sh";
42161
42607
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
42608
+ async function recoverInterruptedPublishJobs(store, logger) {
42609
+ await recoverInterruptedJobs({
42610
+ listAllProjects: async () => store.list(),
42611
+ log: (msg) => logger?.info(msg)
42612
+ });
42613
+ }
42162
42614
  function buildAppBuilderHandlers(deps) {
42163
42615
  const { store, devServerLifecycle, sessionManager, getTunnelUrl, broadcastProjectUpdated } = deps;
42164
42616
  const stages = /* @__PURE__ */ new Map();
42617
+ const publishRegistry = new PublishJobRegistry();
42618
+ function withPublishJobStatus(meta) {
42619
+ if (!meta.publishJob) return void 0;
42620
+ const job = publishRegistry.get(meta.name);
42621
+ const inFlight = !!job && job.jobId === meta.publishJob.jobId;
42622
+ return {
42623
+ ...meta.publishJob,
42624
+ status: inFlight ? "in-flight" : "interrupted"
42625
+ };
42626
+ }
42165
42627
  function getStageOf(name) {
42166
42628
  const recorded = stages.get(name);
42167
42629
  if (recorded) return recorded;
@@ -42180,6 +42642,7 @@ function buildAppBuilderHandlers(deps) {
42180
42642
  return;
42181
42643
  }
42182
42644
  deps.logger?.info("app-builder.stage-transition", { name, stage, reason });
42645
+ const publishJobWithStatus = withPublishJobStatus(meta);
42183
42646
  broadcastProjectUpdated({
42184
42647
  type: "appBuilder:project-updated",
42185
42648
  project: {
@@ -42187,7 +42650,8 @@ function buildAppBuilderHandlers(deps) {
42187
42650
  isRunning: devServerLifecycle.isRunning(name),
42188
42651
  boundSessionId: devServerLifecycle.boundSessionId(name),
42189
42652
  stage,
42190
- ...reason !== void 0 ? { stageReason: reason } : {}
42653
+ ...reason !== void 0 ? { stageReason: reason } : {},
42654
+ ...publishJobWithStatus !== void 0 ? { publishJob: publishJobWithStatus } : { publishJob: void 0 }
42191
42655
  },
42192
42656
  stage,
42193
42657
  ...reason !== void 0 ? { stageReason: reason } : {}
@@ -42199,12 +42663,16 @@ function buildAppBuilderHandlers(deps) {
42199
42663
  response: {
42200
42664
  projects: projects.map((p2) => {
42201
42665
  const s = getStageOf(p2.name);
42666
+ const publishJobWithStatus = withPublishJobStatus(p2);
42202
42667
  return {
42203
42668
  ...p2,
42204
42669
  isRunning: devServerLifecycle.isRunning(p2.name),
42205
42670
  boundSessionId: devServerLifecycle.boundSessionId(p2.name),
42206
42671
  stage: s.stage,
42207
- ...s.reason !== void 0 ? { stageReason: s.reason } : {}
42672
+ ...s.reason !== void 0 ? { stageReason: s.reason } : {},
42673
+ // publishJob 在 spread ...p 之后覆写:原始 ProjectMetadata.publishJob 没有 status,
42674
+ // 这里实时拼上 status 字段;字段不存在时显式 undefined(覆盖掉 spread 的可能值)。
42675
+ publishJob: publishJobWithStatus
42208
42676
  };
42209
42677
  })
42210
42678
  }
@@ -42423,6 +42891,103 @@ function buildAppBuilderHandlers(deps) {
42423
42891
  await setStageAndBroadcast(projectName, "stopped");
42424
42892
  return { response: { ok: true } };
42425
42893
  };
42894
+ const publish = async (frame) => {
42895
+ const args = PublishArgsSchema.parse(frame);
42896
+ const projects = await store.list();
42897
+ const project = projects.find((p2) => p2.name === args.name);
42898
+ if (!project) {
42899
+ throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `publish: project "${args.name}" not found`);
42900
+ }
42901
+ const boundSession = sessionManager.findOwnedSessionByAppBuilderProject(args.name);
42902
+ if (!boundSession) {
42903
+ throw new ClawdError(
42904
+ ERROR_CODES.VALIDATION_ERROR,
42905
+ `publish: project "${args.name}" has no bound session (orphan); cannot inject failure to chat`
42906
+ );
42907
+ }
42908
+ if (project.publishJob && !publishRegistry.has(args.name)) {
42909
+ deps.logger?.info("app-builder.publish.clear-interrupted", {
42910
+ name: args.name,
42911
+ previousJobId: project.publishJob.jobId,
42912
+ previousStage: project.publishJob.stage
42913
+ });
42914
+ await store.clearPublishJob(args.name);
42915
+ }
42916
+ const scriptPath = (0, import_node_path40.join)(deps.personaRoot, PUBLISH_SCRIPT_REL);
42917
+ deps.logger?.info("app-builder.publish.start", {
42918
+ name: args.name,
42919
+ sessionId: boundSession.sessionId,
42920
+ scriptPath
42921
+ });
42922
+ const result = await startPublishJob(
42923
+ {
42924
+ registry: publishRegistry,
42925
+ setPublishJob: async (name, job) => {
42926
+ await store.setPublishJob(name, job);
42927
+ const current = getStageOf(name);
42928
+ await setStageAndBroadcast(name, current.stage, current.reason);
42929
+ },
42930
+ clearPublishJob: async (name) => {
42931
+ await store.clearPublishJob(name);
42932
+ const current = getStageOf(name);
42933
+ await setStageAndBroadcast(name, current.stage, current.reason);
42934
+ },
42935
+ setProdUrl: async (name, url) => {
42936
+ await store.setProdUrl(name, url);
42937
+ const current = getStageOf(name);
42938
+ await setStageAndBroadcast(name, current.stage, current.reason);
42939
+ },
42940
+ broadcast: (f) => {
42941
+ broadcastProjectUpdated(f);
42942
+ },
42943
+ injectUserText: async (sessionId, text) => {
42944
+ const { broadcast } = sessionManager.send({ sessionId, text });
42945
+ for (const entry of broadcast) {
42946
+ deps.broadcastSessionFrame(entry.frame, entry.target);
42947
+ }
42948
+ },
42949
+ projectDir: (name) => store.projectDir(name),
42950
+ logger: deps.logger
42951
+ },
42952
+ {
42953
+ name: args.name,
42954
+ sessionId: boundSession.sessionId,
42955
+ scriptPath
42956
+ }
42957
+ );
42958
+ return { response: { ...result } };
42959
+ };
42960
+ const dismissPublishJob = async (frame) => {
42961
+ const args = DismissPublishJobArgsSchema.parse(frame);
42962
+ const projects = await store.list();
42963
+ const project = projects.find((p2) => p2.name === args.name);
42964
+ if (!project) {
42965
+ throw new ClawdError(
42966
+ ERROR_CODES.VALIDATION_ERROR,
42967
+ `dismissPublishJob: project "${args.name}" not found`
42968
+ );
42969
+ }
42970
+ const job = publishRegistry.get(args.name);
42971
+ if (job) {
42972
+ deps.logger?.info("app-builder.publish.dismiss.in-flight", {
42973
+ name: args.name,
42974
+ jobId: job.jobId,
42975
+ stage: job.stage
42976
+ });
42977
+ publishRegistry.markDismissed(args.name);
42978
+ job.childProc.kill("SIGTERM");
42979
+ } else if (project.publishJob) {
42980
+ deps.logger?.info("app-builder.publish.dismiss.interrupted", {
42981
+ name: args.name,
42982
+ jobId: project.publishJob.jobId,
42983
+ stage: project.publishJob.stage
42984
+ });
42985
+ await store.clearPublishJob(args.name);
42986
+ const current = getStageOf(args.name);
42987
+ await setStageAndBroadcast(args.name, current.stage, current.reason);
42988
+ }
42989
+ return { response: { ok: true } };
42990
+ };
42426
42991
  return {
42427
42992
  "appBuilder:listProjects": listProjects,
42428
42993
  "appBuilder:createProject": createProject,
@@ -42431,13 +42996,15 @@ function buildAppBuilderHandlers(deps) {
42431
42996
  "appBuilder:setProdUrl": setProdUrl,
42432
42997
  "appBuilder:startDevServer": startDevServer,
42433
42998
  "appBuilder:reportStage": reportStage,
42434
- "appBuilder:stopDevServer": stopDevServer
42999
+ "appBuilder:stopDevServer": stopDevServer,
43000
+ "appBuilder:publish": publish,
43001
+ "appBuilder:dismissPublishJob": dismissPublishJob
42435
43002
  };
42436
43003
  }
42437
43004
 
42438
43005
  // src/extension/registry.ts
42439
43006
  var import_promises8 = __toESM(require("fs/promises"), 1);
42440
- var import_node_path39 = __toESM(require("path"), 1);
43007
+ var import_node_path41 = __toESM(require("path"), 1);
42441
43008
  async function loadAll(root) {
42442
43009
  let entries;
42443
43010
  try {
@@ -42450,13 +43017,13 @@ async function loadAll(root) {
42450
43017
  for (const ent of entries) {
42451
43018
  if (!ent.isDirectory()) continue;
42452
43019
  if (ent.name.startsWith(".")) continue;
42453
- records.push(await loadOne(import_node_path39.default.join(root, ent.name), ent.name));
43020
+ records.push(await loadOne(import_node_path41.default.join(root, ent.name), ent.name));
42454
43021
  }
42455
43022
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
42456
43023
  return records;
42457
43024
  }
42458
43025
  async function loadOne(dir, dirName) {
42459
- const manifestPath = import_node_path39.default.join(dir, "manifest.json");
43026
+ const manifestPath = import_node_path41.default.join(dir, "manifest.json");
42460
43027
  let raw;
42461
43028
  try {
42462
43029
  raw = await import_promises8.default.readFile(manifestPath, "utf8");
@@ -42501,7 +43068,7 @@ async function loadOne(dir, dirName) {
42501
43068
 
42502
43069
  // src/extension/uninstall.ts
42503
43070
  var import_promises9 = __toESM(require("fs/promises"), 1);
42504
- var import_node_path40 = __toESM(require("path"), 1);
43071
+ var import_node_path42 = __toESM(require("path"), 1);
42505
43072
  var UninstallError = class extends Error {
42506
43073
  constructor(code, message) {
42507
43074
  super(message);
@@ -42510,7 +43077,7 @@ var UninstallError = class extends Error {
42510
43077
  code;
42511
43078
  };
42512
43079
  async function uninstall(deps) {
42513
- const dir = import_node_path40.default.join(deps.root, deps.extId);
43080
+ const dir = import_node_path42.default.join(deps.root, deps.extId);
42514
43081
  try {
42515
43082
  await import_promises9.default.access(dir);
42516
43083
  } catch {
@@ -42575,15 +43142,20 @@ function buildMethodHandlers(deps) {
42575
43142
  sessionManager: deps.manager,
42576
43143
  getTunnelUrl: deps.getTunnelUrl,
42577
43144
  broadcastProjectUpdated: deps.broadcastProjectUpdated,
42578
- logger: deps.logger
43145
+ logger: deps.logger,
43146
+ // 发布上线脚手架化 (spec 2026-06-03 §5.2):
43147
+ // personaRoot 用于拼 publish.sh 绝对路径;broadcastSessionFrame 用于失败回灌路径
43148
+ // (runner 调 manager.send 后逐帧 dispatch 到 transport)。
43149
+ personaRoot: deps.appBuilderPersonaRoot,
43150
+ broadcastSessionFrame: deps.broadcastSessionFrame
42579
43151
  })
42580
43152
  };
42581
43153
  }
42582
43154
 
42583
43155
  // src/app-builder/project-store.ts
42584
- var import_node_fs29 = require("fs");
42585
- var import_node_child_process8 = require("child_process");
42586
- var import_node_path41 = require("path");
43156
+ var import_node_fs30 = require("fs");
43157
+ var import_node_child_process9 = require("child_process");
43158
+ var import_node_path43 = require("path");
42587
43159
  init_protocol();
42588
43160
  var PROJECTS_DIR = "projects";
42589
43161
  var META_FILE = ".clawd-project.json";
@@ -42598,19 +43170,19 @@ var ProjectStore = class {
42598
43170
  personaRoot;
42599
43171
  /** projects/<name>/.clawd-project.json 路径 */
42600
43172
  metaPath(name) {
42601
- return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
43173
+ return (0, import_node_path43.join)(this.projectsRoot(), name, META_FILE);
42602
43174
  }
42603
43175
  /** projects/<name>/ 目录路径(cwd 用) */
42604
43176
  projectDir(name) {
42605
- return (0, import_node_path41.join)(this.projectsRoot(), name);
43177
+ return (0, import_node_path43.join)(this.projectsRoot(), name);
42606
43178
  }
42607
43179
  projectsRoot() {
42608
- return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
43180
+ return (0, import_node_path43.join)(this.personaRoot, PROJECTS_DIR);
42609
43181
  }
42610
43182
  async list() {
42611
43183
  let entries;
42612
43184
  try {
42613
- entries = await import_node_fs29.promises.readdir(this.projectsRoot());
43185
+ entries = await import_node_fs30.promises.readdir(this.projectsRoot());
42614
43186
  } catch (err) {
42615
43187
  if (err.code === "ENOENT") return [];
42616
43188
  throw err;
@@ -42618,7 +43190,7 @@ var ProjectStore = class {
42618
43190
  const out = [];
42619
43191
  for (const name of entries) {
42620
43192
  try {
42621
- const raw = await import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
43193
+ const raw = await import_node_fs30.promises.readFile(this.metaPath(name), "utf8");
42622
43194
  const json = JSON.parse(raw);
42623
43195
  let migrated = false;
42624
43196
  if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
@@ -42629,7 +43201,7 @@ var ProjectStore = class {
42629
43201
  if (parsed.success) {
42630
43202
  out.push(parsed.data);
42631
43203
  if (migrated) {
42632
- void import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
43204
+ void import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
42633
43205
  });
42634
43206
  }
42635
43207
  }
@@ -42667,8 +43239,8 @@ var ProjectStore = class {
42667
43239
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
42668
43240
  }
42669
43241
  const dir = this.projectDir(name);
42670
- await import_node_fs29.promises.mkdir(dir, { recursive: true });
42671
- await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
43242
+ await import_node_fs30.promises.mkdir(dir, { recursive: true });
43243
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
42672
43244
  return meta;
42673
43245
  }
42674
43246
  /**
@@ -42680,9 +43252,9 @@ var ProjectStore = class {
42680
43252
  * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
42681
43253
  */
42682
43254
  async scaffold(name, template = DEFAULT_TEMPLATE) {
42683
- const scriptPath = (0, import_node_path41.join)(this.personaRoot, SCAFFOLD_SCRIPT_REL);
43255
+ const scriptPath = (0, import_node_path43.join)(this.personaRoot, SCAFFOLD_SCRIPT_REL);
42684
43256
  return await new Promise((resolve6, reject) => {
42685
- const child = (0, import_node_child_process8.spawn)("bash", [scriptPath, name, template], {
43257
+ const child = (0, import_node_child_process9.spawn)("bash", [scriptPath, name, template], {
42686
43258
  cwd: this.personaRoot,
42687
43259
  env: { ...process.env, PATH: process.env.PATH ?? "" },
42688
43260
  stdio: ["ignore", "pipe", "pipe"]
@@ -42712,7 +43284,7 @@ var ProjectStore = class {
42712
43284
  }
42713
43285
  async delete(name) {
42714
43286
  const dir = this.projectDir(name);
42715
- await import_node_fs29.promises.rm(dir, { recursive: true, force: true });
43287
+ await import_node_fs30.promises.rm(dir, { recursive: true, force: true });
42716
43288
  }
42717
43289
  async updatePort(name, newPort) {
42718
43290
  if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
@@ -42728,7 +43300,7 @@ var ProjectStore = class {
42728
43300
  throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
42729
43301
  }
42730
43302
  const updated = { ...target, port: newPort };
42731
- await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
43303
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
42732
43304
  return updated;
42733
43305
  }
42734
43306
  /**
@@ -42745,13 +43317,49 @@ var ProjectStore = class {
42745
43317
  if (!validated.success) {
42746
43318
  throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
42747
43319
  }
42748
- await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43320
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43321
+ return validated.data;
43322
+ }
43323
+ /**
43324
+ * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
43325
+ * 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
43326
+ * - daemon 收到第一个 ::stage::build marker 时写入
43327
+ * - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
43328
+ * - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
43329
+ * - 只有 daemon 崩才会留残骸
43330
+ * 写入时保留所有其他字段(prodUrl 等不受影响)。
43331
+ */
43332
+ async setPublishJob(name, job) {
43333
+ const list = await this.list();
43334
+ const target = list.find((p2) => p2.name === name);
43335
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
43336
+ const updated = { ...target, publishJob: job };
43337
+ const validated = ProjectMetadataSchema.safeParse(updated);
43338
+ if (!validated.success) {
43339
+ throw new Error(`invalid publishJob: ${validated.error.message}`);
43340
+ }
43341
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43342
+ return validated.data;
43343
+ }
43344
+ /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
43345
+ async clearPublishJob(name) {
43346
+ const list = await this.list();
43347
+ const target = list.find((p2) => p2.name === name);
43348
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
43349
+ if (!target.publishJob) return target;
43350
+ const { publishJob: _drop, ...rest } = target;
43351
+ void _drop;
43352
+ const validated = ProjectMetadataSchema.safeParse(rest);
43353
+ if (!validated.success) {
43354
+ throw new Error(`failed to clear publishJob: ${validated.error.message}`);
43355
+ }
43356
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
42749
43357
  return validated.data;
42750
43358
  }
42751
43359
  };
42752
43360
 
42753
43361
  // src/app-builder/dev-server-supervisor.ts
42754
- var import_node_child_process9 = require("child_process");
43362
+ var import_node_child_process10 = require("child_process");
42755
43363
  var import_node_events2 = require("events");
42756
43364
  var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
42757
43365
  var DevServerSupervisor = class extends import_node_events2.EventEmitter {
@@ -42788,7 +43396,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
42788
43396
  tunnelHost: args.tunnelHost,
42789
43397
  devCommand: cmd
42790
43398
  });
42791
- const child = (0, import_node_child_process9.spawn)("sh", ["-c", cmd], {
43399
+ const child = (0, import_node_child_process10.spawn)("sh", ["-c", cmd], {
42792
43400
  cwd: args.cwd,
42793
43401
  env,
42794
43402
  stdio: "pipe",
@@ -43158,7 +43766,11 @@ var METHOD_GRANT_MAP = {
43158
43766
  "appBuilder:reportStage": ADMIN_ANY,
43159
43767
  // 显式控制 fix (spec 2026-06-02-app-builder-explicit-dev-server-control):用户点"收起预览"触发停 dev server,
43160
43768
  // 对称 startDevServer。同时砍了 setActiveSession(dev server 不再 follow-the-eyes)。
43161
- "appBuilder:stopDevServer": ADMIN_ANY
43769
+ "appBuilder:stopDevServer": ADMIN_ANY,
43770
+ // 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design):
43771
+ // owner-only:PreviewPane「发布上线」按钮直接调 publish;UI [取消] / interrupted 恢复按钮调 dismissPublishJob。
43772
+ "appBuilder:publish": ADMIN_ANY,
43773
+ "appBuilder:dismissPublishJob": ADMIN_ANY
43162
43774
  };
43163
43775
  function computeGrantForFrame(method, frame) {
43164
43776
  const rule = METHOD_GRANT_MAP[method];
@@ -43174,8 +43786,8 @@ function computeGrantForFrame(method, frame) {
43174
43786
  }
43175
43787
 
43176
43788
  // src/extension/runtime.ts
43177
- var import_node_child_process10 = require("child_process");
43178
- var import_node_path42 = __toESM(require("path"), 1);
43789
+ var import_node_child_process11 = require("child_process");
43790
+ var import_node_path44 = __toESM(require("path"), 1);
43179
43791
  var import_promises10 = require("timers/promises");
43180
43792
 
43181
43793
  // src/extension/port-allocator.ts
@@ -43276,13 +43888,13 @@ var Runtime = class {
43276
43888
  /\$CLAWOS_EXT_PORT/g,
43277
43889
  String(port)
43278
43890
  );
43279
- const dir = import_node_path42.default.join(this.root, extId);
43891
+ const dir = import_node_path44.default.join(this.root, extId);
43280
43892
  const env = {
43281
43893
  ...process.env,
43282
43894
  CLAWOS_EXT_PORT: String(port),
43283
43895
  CLAWOS_EXT_ID: extId
43284
43896
  };
43285
- const child = (0, import_node_child_process10.spawn)("sh", ["-c", cmd], {
43897
+ const child = (0, import_node_child_process11.spawn)("sh", ["-c", cmd], {
43286
43898
  cwd: dir,
43287
43899
  env,
43288
43900
  stdio: ["ignore", "pipe", "pipe"],
@@ -43388,7 +44000,7 @@ ${handle.stderrTail}`
43388
44000
 
43389
44001
  // src/extension/published-channels.ts
43390
44002
  var import_promises11 = __toESM(require("fs/promises"), 1);
43391
- var import_node_path43 = __toESM(require("path"), 1);
44003
+ var import_node_path45 = __toESM(require("path"), 1);
43392
44004
  init_zod();
43393
44005
  var PublishedChannelsError = class extends Error {
43394
44006
  constructor(code, message) {
@@ -43487,7 +44099,7 @@ var PublishedChannelStore = class {
43487
44099
  )
43488
44100
  };
43489
44101
  const tmp = `${this.filePath}.tmp`;
43490
- await import_promises11.default.mkdir(import_node_path43.default.dirname(this.filePath), { recursive: true });
44102
+ await import_promises11.default.mkdir(import_node_path45.default.dirname(this.filePath), { recursive: true });
43491
44103
  await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
43492
44104
  await import_promises11.default.rename(tmp, this.filePath);
43493
44105
  }
@@ -43495,7 +44107,7 @@ var PublishedChannelStore = class {
43495
44107
 
43496
44108
  // src/extension/bundle-cache.ts
43497
44109
  var import_promises12 = __toESM(require("fs/promises"), 1);
43498
- var import_node_path44 = __toESM(require("path"), 1);
44110
+ var import_node_path46 = __toESM(require("path"), 1);
43499
44111
  var BundleCache = class {
43500
44112
  constructor(rootDir) {
43501
44113
  this.rootDir = rootDir;
@@ -43504,14 +44116,14 @@ var BundleCache = class {
43504
44116
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
43505
44117
  async write(snapshotHash, buffer) {
43506
44118
  await import_promises12.default.mkdir(this.rootDir, { recursive: true });
43507
- const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
44119
+ const file = import_node_path46.default.join(this.rootDir, `${snapshotHash}.zip`);
43508
44120
  const tmp = `${file}.tmp`;
43509
44121
  await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
43510
44122
  await import_promises12.default.rename(tmp, file);
43511
44123
  }
43512
44124
  /** Returns the bundle bytes, or null when the file doesn't exist. */
43513
44125
  async read(snapshotHash) {
43514
- const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
44126
+ const file = import_node_path46.default.join(this.rootDir, `${snapshotHash}.zip`);
43515
44127
  try {
43516
44128
  return await import_promises12.default.readFile(file);
43517
44129
  } catch (e) {
@@ -43521,7 +44133,7 @@ var BundleCache = class {
43521
44133
  }
43522
44134
  /** Idempotent — missing file is not an error. */
43523
44135
  async delete(snapshotHash) {
43524
- const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
44136
+ const file = import_node_path46.default.join(this.rootDir, `${snapshotHash}.zip`);
43525
44137
  await import_promises12.default.rm(file, { force: true });
43526
44138
  }
43527
44139
  };
@@ -43530,7 +44142,7 @@ var BundleCache = class {
43530
44142
  async function startDaemon(config) {
43531
44143
  const logger = createLogger({
43532
44144
  level: config.logLevel,
43533
- file: import_node_path45.default.join(config.dataDir, "clawd.log")
44145
+ file: import_node_path47.default.join(config.dataDir, "clawd.log")
43534
44146
  });
43535
44147
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
43536
44148
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -43666,10 +44278,11 @@ async function startDaemon(config) {
43666
44278
  const agents = new AgentsScanner();
43667
44279
  const history = new ClaudeHistoryReader();
43668
44280
  let transport = null;
43669
- const personaStore = new PersonaStore(import_node_path45.default.join(config.dataDir, "personas"));
44281
+ const personaStore = new PersonaStore(import_node_path47.default.join(config.dataDir, "personas"));
43670
44282
  const defaultsRoot = findDefaultsRoot();
43671
44283
  if (defaultsRoot) {
43672
44284
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
44285
+ refreshDaemonManagedDirs({ store: personaStore, defaultsRoot, logger });
43673
44286
  } else {
43674
44287
  logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
43675
44288
  }
@@ -43683,7 +44296,7 @@ async function startDaemon(config) {
43683
44296
  getAdapter,
43684
44297
  historyReader: history,
43685
44298
  dataDir: config.dataDir,
43686
- personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
44299
+ personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
43687
44300
  personaStore,
43688
44301
  ownerDisplayName,
43689
44302
  ownerPrincipalId,
@@ -43712,10 +44325,10 @@ async function startDaemon(config) {
43712
44325
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
43713
44326
  attachmentGroup: {
43714
44327
  onFileEdit: (input) => {
43715
- const absPath = import_node_path45.default.isAbsolute(input.relPath) ? input.relPath : import_node_path45.default.join(input.cwd, input.relPath);
44328
+ const absPath = import_node_path47.default.isAbsolute(input.relPath) ? input.relPath : import_node_path47.default.join(input.cwd, input.relPath);
43716
44329
  let size = 0;
43717
44330
  try {
43718
- size = import_node_fs30.default.statSync(absPath).size;
44331
+ size = import_node_fs31.default.statSync(absPath).size;
43719
44332
  } catch (err) {
43720
44333
  logger.warn("attachment.onFileEdit stat failed", {
43721
44334
  sessionId: input.sessionId,
@@ -43845,8 +44458,13 @@ async function startDaemon(config) {
43845
44458
  getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
43846
44459
  });
43847
44460
  const appBuilderStore = new ProjectStore(
43848
- import_node_path45.default.join(config.dataDir, "personas/persona-app-builder")
44461
+ import_node_path47.default.join(config.dataDir, "personas/persona-app-builder")
43849
44462
  );
44463
+ recoverInterruptedPublishJobs(appBuilderStore, logger).catch((err) => {
44464
+ logger.warn("app-builder.publish.recover-startup-failed", {
44465
+ error: err instanceof Error ? err.message : String(err)
44466
+ });
44467
+ });
43850
44468
  const devServerSupervisor = new DevServerSupervisor();
43851
44469
  devServerSupervisor.setLogger(logger);
43852
44470
  const appBuilderPortRange = [];
@@ -43893,10 +44511,10 @@ async function startDaemon(config) {
43893
44511
  // 'persona/<pid>/owner',default 走 'default'。
43894
44512
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
43895
44513
  // guest path guard:candidate 必须在 personaRoot 子树下
43896
- personaRoot: import_node_path45.default.join(config.dataDir, "personas")
44514
+ personaRoot: import_node_path47.default.join(config.dataDir, "personas")
43897
44515
  },
43898
44516
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
43899
- personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
44517
+ personaRoot: import_node_path47.default.join(config.dataDir, "personas"),
43900
44518
  // capability:list / delete handler 依赖
43901
44519
  capabilityManager,
43902
44520
  // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
@@ -43968,6 +44586,27 @@ async function startDaemon(config) {
43968
44586
  // 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
43969
44587
  // 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
43970
44588
  broadcastProjectUpdated: (event) => wsServer?.broadcastToOwners(event),
44589
+ // 发布上线脚手架化 (spec 2026-06-03 §5.2):
44590
+ // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
44591
+ // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
44592
+ appBuilderPersonaRoot: import_node_path47.default.join(config.dataDir, "personas", "persona-app-builder"),
44593
+ // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
44594
+ // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
44595
+ // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
44596
+ broadcastSessionFrame: (frame, target) => {
44597
+ if (target === "all") {
44598
+ transport?.broadcastAll(frame);
44599
+ return;
44600
+ }
44601
+ const sid = frame.sessionId;
44602
+ if (!sid) return;
44603
+ if (target === "first-subscriber") {
44604
+ const handle = transport?.firstSubscriber(sid);
44605
+ if (handle) handle.send(frame);
44606
+ return;
44607
+ }
44608
+ transport?.broadcastToSession(sid, frame);
44609
+ },
43971
44610
  logger
43972
44611
  });
43973
44612
  let handlers = makeHandlers();
@@ -44198,8 +44837,8 @@ async function startDaemon(config) {
44198
44837
  const lines = [
44199
44838
  `Tunnel: ${r.url}`,
44200
44839
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
44201
- `Frpc config: ${import_node_path45.default.join(config.dataDir, "frpc.toml")}`,
44202
- `Frpc log: ${import_node_path45.default.join(config.dataDir, "frpc.log")}`
44840
+ `Frpc config: ${import_node_path47.default.join(config.dataDir, "frpc.toml")}`,
44841
+ `Frpc log: ${import_node_path47.default.join(config.dataDir, "frpc.log")}`
44203
44842
  ];
44204
44843
  const width = Math.max(...lines.map((l) => l.length));
44205
44844
  const bar = "\u2550".repeat(width + 4);
@@ -44212,8 +44851,8 @@ ${bar}
44212
44851
 
44213
44852
  `);
44214
44853
  try {
44215
- const connectPath = import_node_path45.default.join(config.dataDir, "connect.txt");
44216
- import_node_fs30.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
44854
+ const connectPath = import_node_path47.default.join(config.dataDir, "connect.txt");
44855
+ import_node_fs31.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
44217
44856
  } catch {
44218
44857
  }
44219
44858
  } catch (err) {
@@ -44284,9 +44923,9 @@ ${bar}
44284
44923
  };
44285
44924
  }
44286
44925
  function migrateDropPersonsDir(dataDir) {
44287
- const dir = import_node_path45.default.join(dataDir, "persons");
44926
+ const dir = import_node_path47.default.join(dataDir, "persons");
44288
44927
  try {
44289
- import_node_fs30.default.rmSync(dir, { recursive: true, force: true });
44928
+ import_node_fs31.default.rmSync(dir, { recursive: true, force: true });
44290
44929
  } catch {
44291
44930
  }
44292
44931
  }