@clawos-dev/clawd 0.2.121 → 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 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: join12 } = require("path");
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"] || join12(__dirname, "lib", "worker.js");
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: join12, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
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"] || join12(__dirname, "worker.js");
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"] || join12(__dirname, "worker.js");
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 join12(__dirname, "..", "file.js");
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 join12 = ",";
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
- join12 = `,
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 += join12;
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 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
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
- join12 = `,
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 = join12;
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 = join12;
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 join12 = ",";
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
- join12 = `,
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 += join12;
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 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
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
- join12 = `,
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 = join12;
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 join13 = `,
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 += join13;
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 += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
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 join12 = `,
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, join12, maximumBreadth);
9581
+ res += stringifyTypedArray(value, join14, maximumBreadth);
9517
9582
  keys = keys.slice(value.length);
9518
9583
  maximumPropertiesToStringify -= value.length;
9519
- separator = join12;
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 = join12;
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 = join12;
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 join12(s) {
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 = import_node_path46.default.dirname(opts.recordPath);
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
- import_node_fs31.default.mkdirSync(dir, { recursive: true });
30595
- stream = import_node_fs31.default.createWriteStream(opts.recordPath, { flags: "a" });
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 import_node_fs31, import_node_path46;
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
- import_node_fs31 = __toESM(require("fs"), 1);
30625
- import_node_path46 = __toESM(require("path"), 1);
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, import_node_fs32.mkdtempSync)(import_node_path47.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
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, import_node_fs32.rmSync)(cwd, { recursive: true, force: true });
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 import_node_fs32, import_node_os19, import_node_path47;
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
- import_node_fs32 = require("fs");
30904
+ import_node_fs33 = require("fs");
30840
30905
  import_node_os19 = __toESM(require("os"), 1);
30841
- import_node_path47 = __toESM(require("path"), 1);
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 import_node_path45 = __toESM(require("path"), 1);
31077
- var import_node_fs30 = __toESM(require("fs"), 1);
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 import_node_path39 = __toESM(require("path"), 1);
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(import_node_path39.default.join(root, ent.name), ent.name));
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 = import_node_path39.default.join(dir, "manifest.json");
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 import_node_path40 = __toESM(require("path"), 1);
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 = import_node_path40.default.join(deps.root, deps.extId);
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 import_node_fs29 = require("fs");
42144
- var import_node_child_process8 = require("child_process");
42145
- var import_node_path41 = require("path");
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, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
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, import_node_path41.join)(this.projectsRoot(), name);
42736
+ return (0, import_node_path43.join)(this.projectsRoot(), name);
42165
42737
  }
42166
42738
  projectsRoot() {
42167
- return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
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 import_node_fs29.promises.readdir(this.projectsRoot());
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 import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
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 import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
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 import_node_fs29.promises.mkdir(dir, { recursive: true });
42230
- await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
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, import_node_path41.join)(this.personaRoot, SCAFFOLD_SCRIPT_REL);
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, import_node_child_process8.spawn)("bash", [scriptPath, name, template], {
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 import_node_fs29.promises.rm(dir, { recursive: true, force: true });
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 import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
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 import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
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 import_node_child_process9 = require("child_process");
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, import_node_child_process9.spawn)("sh", ["-c", cmd], {
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 import_node_child_process10 = require("child_process");
42729
- var import_node_path42 = __toESM(require("path"), 1);
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 = import_node_path42.default.join(this.root, extId);
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, import_node_child_process10.spawn)("sh", ["-c", cmd], {
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 import_node_path43 = __toESM(require("path"), 1);
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(import_node_path43.default.dirname(this.filePath), { recursive: true });
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 import_node_path44 = __toESM(require("path"), 1);
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 = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
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 = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
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 = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
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: import_node_path45.default.join(config.dataDir, "clawd.log")
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(import_node_path45.default.join(config.dataDir, "personas"));
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: import_node_path45.default.join(config.dataDir, "personas"),
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 = import_node_path45.default.isAbsolute(input.relPath) ? input.relPath : import_node_path45.default.join(input.cwd, input.relPath);
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 = import_node_fs30.default.statSync(absPath).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
- import_node_path45.default.join(config.dataDir, "personas/persona-app-builder")
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: import_node_path45.default.join(config.dataDir, "personas")
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: import_node_path45.default.join(config.dataDir, "personas"),
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: ${import_node_path45.default.join(config.dataDir, "frpc.toml")}`,
43599
- `Frpc log: ${import_node_path45.default.join(config.dataDir, "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 = import_node_path45.default.join(config.dataDir, "connect.txt");
43613
- import_node_fs30.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
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 = import_node_path45.default.join(dataDir, "persons");
44321
+ const dir = import_node_path47.default.join(dataDir, "persons");
43683
44322
  try {
43684
- import_node_fs30.default.rmSync(dir, { recursive: true, force: true });
44323
+ import_node_fs31.default.rmSync(dir, { recursive: true, force: true });
43685
44324
  } catch {
43686
44325
  }
43687
44326
  }