@clawos-dev/clawd 0.2.452 → 0.2.454

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
@@ -450,8 +450,8 @@ var init_parseUtil = __esm({
450
450
  init_errors();
451
451
  init_en();
452
452
  makeIssue = (params) => {
453
- const { data, path: path93, errorMaps, issueData } = params;
454
- const fullPath = [...path93, ...issueData.path || []];
453
+ const { data, path: path94, errorMaps, issueData } = params;
454
+ const fullPath = [...path94, ...issueData.path || []];
455
455
  const fullIssue = {
456
456
  ...issueData,
457
457
  path: fullPath
@@ -762,11 +762,11 @@ var init_types = __esm({
762
762
  init_parseUtil();
763
763
  init_util();
764
764
  ParseInputLazyPath = class {
765
- constructor(parent, value, path93, key) {
765
+ constructor(parent, value, path94, key) {
766
766
  this._cachedPath = [];
767
767
  this.parent = parent;
768
768
  this.data = value;
769
- this._path = path93;
769
+ this._path = path94;
770
770
  this._key = key;
771
771
  }
772
772
  get path() {
@@ -8804,7 +8804,7 @@ var init_methods = __esm({
8804
8804
  args: WorkspaceReadArgs
8805
8805
  },
8806
8806
  "deploy:start": {
8807
- summary: "\u6253\u5F00\u67D0\u4E2A persona \u7684\u90E8\u7F72\u5355\uFF1A**\u5DF2\u6709\u5C31\u539F\u6837\u8FD4\u56DE\u90A3\u5F20**\uFF08\u8349\u7A3F\u7EED\u586B\uFF0C\u4E0D\u91CD\u626B\uFF0C\u4E5F\u4E0D\u65B0\u5EFA\uFF09\uFF0C\u6CA1\u6709\u624D\u5EFA\u4E00\u5F20\u5E76**\u5F02\u6B65**\u8D77\u73AF\u5883\u626B\u63CF\u3002\u4E00\u4E2A persona \u540C\u65F6\u53EA\u6709\u4E00\u5F20\u5355\uFF0C\u8981\u91CD\u626B\u8D70 deploy:rescan\uFF1Bowner-only",
8807
+ summary: "\u5BF9\u67D0\u4E2A persona \u8D77\u4E00\u6B21**\u65B0\u7684**\u4E0A\u4E91\u626B\u63CF\uFF1A\u5EFA\u4E00\u5F20\u65B0\u5355\u636E\u5E76\u5F02\u6B65\u626B\uFF08\u4EBA\u4F1A\u70B9\u8FD9\u4E2A\u53EA\u53EF\u80FD\u662F\u56E0\u4E3A persona \u8FED\u4EE3\u4E86\uFF0C\u62FF\u65E7\u6E05\u5355\u53BB\u90E8\u7F72\u662F\u9519\u7684\uFF09\u3002**\u5DF2\u586B\u7684\u51ED\u636E\u503C\u548C\u5C65\u7EA6\u9009\u62E9\u4ECE\u4E0A\u4E00\u5F20\u5355\u7EE7\u627F\u8FC7\u6765**\uFF0C\u4E0D\u7528\u6BCF\u8FED\u4EE3\u4E00\u6B21\u91CD\u8D34\u4E00\u904D key\u3002\u5DF2\u7ECF\u5728\u626B\u7684\u8BDD\u8FD4\u56DE\u90A3\u5F20\u3001\u4E0D\u5E76\u53D1\u8D77\u7B2C\u4E8C\u4E2A\u626B\u63CF\u4F1A\u8BDD\u3002\u56DE\u770B\u65E7\u7684\u90A3\u6B21\u8D70 deploy:list + deploy:get\uFF1Bowner-only",
8808
8808
  args: DeployStartArgsSchema
8809
8809
  },
8810
8810
  "deploy:rescan": {
@@ -9823,8 +9823,8 @@ var require_req = __commonJS({
9823
9823
  if (req.originalUrl) {
9824
9824
  _req.url = req.originalUrl;
9825
9825
  } else {
9826
- const path93 = req.path;
9827
- _req.url = typeof path93 === "string" ? path93 : req.url ? req.url.path || req.url : void 0;
9826
+ const path94 = req.path;
9827
+ _req.url = typeof path94 === "string" ? path94 : req.url ? req.url.path || req.url : void 0;
9828
9828
  }
9829
9829
  if (req.query) {
9830
9830
  _req.query = req.query;
@@ -9989,14 +9989,14 @@ var require_redact = __commonJS({
9989
9989
  }
9990
9990
  return obj;
9991
9991
  }
9992
- function parsePath(path93) {
9992
+ function parsePath(path94) {
9993
9993
  const parts = [];
9994
9994
  let current = "";
9995
9995
  let inBrackets = false;
9996
9996
  let inQuotes = false;
9997
9997
  let quoteChar = "";
9998
- for (let i = 0; i < path93.length; i++) {
9999
- const char = path93[i];
9998
+ for (let i = 0; i < path94.length; i++) {
9999
+ const char = path94[i];
10000
10000
  if (!inBrackets && char === ".") {
10001
10001
  if (current) {
10002
10002
  parts.push(current);
@@ -10127,10 +10127,10 @@ var require_redact = __commonJS({
10127
10127
  return current;
10128
10128
  }
10129
10129
  function redactPaths(obj, paths, censor, remove = false) {
10130
- for (const path93 of paths) {
10131
- const parts = parsePath(path93);
10130
+ for (const path94 of paths) {
10131
+ const parts = parsePath(path94);
10132
10132
  if (parts.includes("*")) {
10133
- redactWildcardPath(obj, parts, censor, path93, remove);
10133
+ redactWildcardPath(obj, parts, censor, path94, remove);
10134
10134
  } else {
10135
10135
  if (remove) {
10136
10136
  removeKey(obj, parts);
@@ -10215,8 +10215,8 @@ var require_redact = __commonJS({
10215
10215
  }
10216
10216
  } else {
10217
10217
  if (afterWildcard.includes("*")) {
10218
- const wrappedCensor = typeof censor === "function" ? (value, path93) => {
10219
- const fullPath = [...pathArray.slice(0, pathLength), ...path93];
10218
+ const wrappedCensor = typeof censor === "function" ? (value, path94) => {
10219
+ const fullPath = [...pathArray.slice(0, pathLength), ...path94];
10220
10220
  return censor(value, fullPath);
10221
10221
  } : censor;
10222
10222
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -10251,8 +10251,8 @@ var require_redact = __commonJS({
10251
10251
  return null;
10252
10252
  }
10253
10253
  const pathStructure = /* @__PURE__ */ new Map();
10254
- for (const path93 of pathsToClone) {
10255
- const parts = parsePath(path93);
10254
+ for (const path94 of pathsToClone) {
10255
+ const parts = parsePath(path94);
10256
10256
  let current = pathStructure;
10257
10257
  for (let i = 0; i < parts.length; i++) {
10258
10258
  const part = parts[i];
@@ -10304,24 +10304,24 @@ var require_redact = __commonJS({
10304
10304
  }
10305
10305
  return cloneSelectively(obj, pathStructure);
10306
10306
  }
10307
- function validatePath(path93) {
10308
- if (typeof path93 !== "string") {
10307
+ function validatePath(path94) {
10308
+ if (typeof path94 !== "string") {
10309
10309
  throw new Error("Paths must be (non-empty) strings");
10310
10310
  }
10311
- if (path93 === "") {
10311
+ if (path94 === "") {
10312
10312
  throw new Error("Invalid redaction path ()");
10313
10313
  }
10314
- if (path93.includes("..")) {
10315
- throw new Error(`Invalid redaction path (${path93})`);
10314
+ if (path94.includes("..")) {
10315
+ throw new Error(`Invalid redaction path (${path94})`);
10316
10316
  }
10317
- if (path93.includes(",")) {
10318
- throw new Error(`Invalid redaction path (${path93})`);
10317
+ if (path94.includes(",")) {
10318
+ throw new Error(`Invalid redaction path (${path94})`);
10319
10319
  }
10320
10320
  let bracketCount = 0;
10321
10321
  let inQuotes = false;
10322
10322
  let quoteChar = "";
10323
- for (let i = 0; i < path93.length; i++) {
10324
- const char = path93[i];
10323
+ for (let i = 0; i < path94.length; i++) {
10324
+ const char = path94[i];
10325
10325
  if ((char === '"' || char === "'") && bracketCount > 0) {
10326
10326
  if (!inQuotes) {
10327
10327
  inQuotes = true;
@@ -10335,20 +10335,20 @@ var require_redact = __commonJS({
10335
10335
  } else if (char === "]" && !inQuotes) {
10336
10336
  bracketCount--;
10337
10337
  if (bracketCount < 0) {
10338
- throw new Error(`Invalid redaction path (${path93})`);
10338
+ throw new Error(`Invalid redaction path (${path94})`);
10339
10339
  }
10340
10340
  }
10341
10341
  }
10342
10342
  if (bracketCount !== 0) {
10343
- throw new Error(`Invalid redaction path (${path93})`);
10343
+ throw new Error(`Invalid redaction path (${path94})`);
10344
10344
  }
10345
10345
  }
10346
10346
  function validatePaths(paths) {
10347
10347
  if (!Array.isArray(paths)) {
10348
10348
  throw new TypeError("paths must be an array");
10349
10349
  }
10350
- for (const path93 of paths) {
10351
- validatePath(path93);
10350
+ for (const path94 of paths) {
10351
+ validatePath(path94);
10352
10352
  }
10353
10353
  }
10354
10354
  function slowRedact(options = {}) {
@@ -10516,8 +10516,8 @@ var require_redaction = __commonJS({
10516
10516
  if (shape[k2] === null) {
10517
10517
  o[k2] = (value) => topCensor(value, [k2]);
10518
10518
  } else {
10519
- const wrappedCensor = typeof censor === "function" ? (value, path93) => {
10520
- return censor(value, [k2, ...path93]);
10519
+ const wrappedCensor = typeof censor === "function" ? (value, path94) => {
10520
+ return censor(value, [k2, ...path94]);
10521
10521
  } : censor;
10522
10522
  o[k2] = Redact({
10523
10523
  paths: shape[k2],
@@ -10735,10 +10735,10 @@ var require_atomic_sleep = __commonJS({
10735
10735
  var require_sonic_boom = __commonJS({
10736
10736
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
10737
10737
  "use strict";
10738
- var fs82 = require("fs");
10738
+ var fs83 = require("fs");
10739
10739
  var EventEmitter3 = require("events");
10740
10740
  var inherits = require("util").inherits;
10741
- var path93 = require("path");
10741
+ var path94 = require("path");
10742
10742
  var sleep2 = require_atomic_sleep();
10743
10743
  var assert = require("assert");
10744
10744
  var BUSY_WRITE_TIMEOUT = 100;
@@ -10792,20 +10792,20 @@ var require_sonic_boom = __commonJS({
10792
10792
  const mode = sonic.mode;
10793
10793
  if (sonic.sync) {
10794
10794
  try {
10795
- if (sonic.mkdir) fs82.mkdirSync(path93.dirname(file), { recursive: true });
10796
- const fd = fs82.openSync(file, flags, mode);
10795
+ if (sonic.mkdir) fs83.mkdirSync(path94.dirname(file), { recursive: true });
10796
+ const fd = fs83.openSync(file, flags, mode);
10797
10797
  fileOpened(null, fd);
10798
10798
  } catch (err) {
10799
10799
  fileOpened(err);
10800
10800
  throw err;
10801
10801
  }
10802
10802
  } else if (sonic.mkdir) {
10803
- fs82.mkdir(path93.dirname(file), { recursive: true }, (err) => {
10803
+ fs83.mkdir(path94.dirname(file), { recursive: true }, (err) => {
10804
10804
  if (err) return fileOpened(err);
10805
- fs82.open(file, flags, mode, fileOpened);
10805
+ fs83.open(file, flags, mode, fileOpened);
10806
10806
  });
10807
10807
  } else {
10808
- fs82.open(file, flags, mode, fileOpened);
10808
+ fs83.open(file, flags, mode, fileOpened);
10809
10809
  }
10810
10810
  }
10811
10811
  function SonicBoom(opts) {
@@ -10846,8 +10846,8 @@ var require_sonic_boom = __commonJS({
10846
10846
  this.flush = flushBuffer;
10847
10847
  this.flushSync = flushBufferSync;
10848
10848
  this._actualWrite = actualWriteBuffer;
10849
- fsWriteSync = () => fs82.writeSync(this.fd, this._writingBuf);
10850
- fsWrite = () => fs82.write(this.fd, this._writingBuf, this.release);
10849
+ fsWriteSync = () => fs83.writeSync(this.fd, this._writingBuf);
10850
+ fsWrite = () => fs83.write(this.fd, this._writingBuf, this.release);
10851
10851
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
10852
10852
  this._writingBuf = "";
10853
10853
  this.write = write;
@@ -10856,15 +10856,15 @@ var require_sonic_boom = __commonJS({
10856
10856
  this._actualWrite = actualWrite;
10857
10857
  fsWriteSync = () => {
10858
10858
  if (Buffer.isBuffer(this._writingBuf)) {
10859
- return fs82.writeSync(this.fd, this._writingBuf);
10859
+ return fs83.writeSync(this.fd, this._writingBuf);
10860
10860
  }
10861
- return fs82.writeSync(this.fd, this._writingBuf, "utf8");
10861
+ return fs83.writeSync(this.fd, this._writingBuf, "utf8");
10862
10862
  };
10863
10863
  fsWrite = () => {
10864
10864
  if (Buffer.isBuffer(this._writingBuf)) {
10865
- return fs82.write(this.fd, this._writingBuf, this.release);
10865
+ return fs83.write(this.fd, this._writingBuf, this.release);
10866
10866
  }
10867
- return fs82.write(this.fd, this._writingBuf, "utf8", this.release);
10867
+ return fs83.write(this.fd, this._writingBuf, "utf8", this.release);
10868
10868
  };
10869
10869
  } else {
10870
10870
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -10921,7 +10921,7 @@ var require_sonic_boom = __commonJS({
10921
10921
  }
10922
10922
  }
10923
10923
  if (this._fsync) {
10924
- fs82.fsyncSync(this.fd);
10924
+ fs83.fsyncSync(this.fd);
10925
10925
  }
10926
10926
  const len = this._len;
10927
10927
  if (this._reopening) {
@@ -11035,7 +11035,7 @@ var require_sonic_boom = __commonJS({
11035
11035
  const onDrain = () => {
11036
11036
  if (!this._fsync) {
11037
11037
  try {
11038
- fs82.fsync(this.fd, (err) => {
11038
+ fs83.fsync(this.fd, (err) => {
11039
11039
  this._flushPending = false;
11040
11040
  cb(err);
11041
11041
  });
@@ -11137,7 +11137,7 @@ var require_sonic_boom = __commonJS({
11137
11137
  const fd = this.fd;
11138
11138
  this.once("ready", () => {
11139
11139
  if (fd !== this.fd) {
11140
- fs82.close(fd, (err) => {
11140
+ fs83.close(fd, (err) => {
11141
11141
  if (err) {
11142
11142
  return this.emit("error", err);
11143
11143
  }
@@ -11186,7 +11186,7 @@ var require_sonic_boom = __commonJS({
11186
11186
  buf = this._bufs[0];
11187
11187
  }
11188
11188
  try {
11189
- const n = Buffer.isBuffer(buf) ? fs82.writeSync(this.fd, buf) : fs82.writeSync(this.fd, buf, "utf8");
11189
+ const n = Buffer.isBuffer(buf) ? fs83.writeSync(this.fd, buf) : fs83.writeSync(this.fd, buf, "utf8");
11190
11190
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
11191
11191
  buf = releasedBufObj.writingBuf;
11192
11192
  this._len = releasedBufObj.len;
@@ -11202,7 +11202,7 @@ var require_sonic_boom = __commonJS({
11202
11202
  }
11203
11203
  }
11204
11204
  try {
11205
- fs82.fsyncSync(this.fd);
11205
+ fs83.fsyncSync(this.fd);
11206
11206
  } catch {
11207
11207
  }
11208
11208
  }
@@ -11223,7 +11223,7 @@ var require_sonic_boom = __commonJS({
11223
11223
  buf = mergeBuf(this._bufs[0], this._lens[0]);
11224
11224
  }
11225
11225
  try {
11226
- const n = fs82.writeSync(this.fd, buf);
11226
+ const n = fs83.writeSync(this.fd, buf);
11227
11227
  buf = buf.subarray(n);
11228
11228
  this._len = Math.max(this._len - n, 0);
11229
11229
  if (buf.length <= 0) {
@@ -11251,13 +11251,13 @@ var require_sonic_boom = __commonJS({
11251
11251
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
11252
11252
  if (this.sync) {
11253
11253
  try {
11254
- const written = Buffer.isBuffer(this._writingBuf) ? fs82.writeSync(this.fd, this._writingBuf) : fs82.writeSync(this.fd, this._writingBuf, "utf8");
11254
+ const written = Buffer.isBuffer(this._writingBuf) ? fs83.writeSync(this.fd, this._writingBuf) : fs83.writeSync(this.fd, this._writingBuf, "utf8");
11255
11255
  release(null, written);
11256
11256
  } catch (err) {
11257
11257
  release(err);
11258
11258
  }
11259
11259
  } else {
11260
- fs82.write(this.fd, this._writingBuf, release);
11260
+ fs83.write(this.fd, this._writingBuf, release);
11261
11261
  }
11262
11262
  }
11263
11263
  function actualWriteBuffer() {
@@ -11266,7 +11266,7 @@ var require_sonic_boom = __commonJS({
11266
11266
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
11267
11267
  if (this.sync) {
11268
11268
  try {
11269
- const written = fs82.writeSync(this.fd, this._writingBuf);
11269
+ const written = fs83.writeSync(this.fd, this._writingBuf);
11270
11270
  release(null, written);
11271
11271
  } catch (err) {
11272
11272
  release(err);
@@ -11275,7 +11275,7 @@ var require_sonic_boom = __commonJS({
11275
11275
  if (kCopyBuffer) {
11276
11276
  this._writingBuf = Buffer.from(this._writingBuf);
11277
11277
  }
11278
- fs82.write(this.fd, this._writingBuf, release);
11278
+ fs83.write(this.fd, this._writingBuf, release);
11279
11279
  }
11280
11280
  }
11281
11281
  function actualClose(sonic) {
@@ -11291,12 +11291,12 @@ var require_sonic_boom = __commonJS({
11291
11291
  sonic._lens = [];
11292
11292
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
11293
11293
  try {
11294
- fs82.fsync(sonic.fd, closeWrapped);
11294
+ fs83.fsync(sonic.fd, closeWrapped);
11295
11295
  } catch {
11296
11296
  }
11297
11297
  function closeWrapped() {
11298
11298
  if (sonic.fd !== 1 && sonic.fd !== 2) {
11299
- fs82.close(sonic.fd, done);
11299
+ fs83.close(sonic.fd, done);
11300
11300
  } else {
11301
11301
  done();
11302
11302
  }
@@ -14431,11 +14431,11 @@ var init_lib = __esm({
14431
14431
  }
14432
14432
  }
14433
14433
  },
14434
- addToPath: function addToPath(path93, added, removed, oldPosInc, options) {
14435
- var last = path93.lastComponent;
14434
+ addToPath: function addToPath(path94, added, removed, oldPosInc, options) {
14435
+ var last = path94.lastComponent;
14436
14436
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
14437
14437
  return {
14438
- oldPos: path93.oldPos + oldPosInc,
14438
+ oldPos: path94.oldPos + oldPosInc,
14439
14439
  lastComponent: {
14440
14440
  count: last.count + 1,
14441
14441
  added,
@@ -14445,7 +14445,7 @@ var init_lib = __esm({
14445
14445
  };
14446
14446
  } else {
14447
14447
  return {
14448
- oldPos: path93.oldPos + oldPosInc,
14448
+ oldPos: path94.oldPos + oldPosInc,
14449
14449
  lastComponent: {
14450
14450
  count: 1,
14451
14451
  added,
@@ -15027,10 +15027,10 @@ function attachmentToHistoryMessage(o, ts) {
15027
15027
  const memories = raw.map((m2) => {
15028
15028
  if (!m2 || typeof m2 !== "object") return null;
15029
15029
  const rec4 = m2;
15030
- const path93 = typeof rec4.path === "string" ? rec4.path : null;
15030
+ const path94 = typeof rec4.path === "string" ? rec4.path : null;
15031
15031
  const content = typeof rec4.content === "string" ? rec4.content : null;
15032
- if (!path93 || content == null) return null;
15033
- const entry = { path: path93, content };
15032
+ if (!path94 || content == null) return null;
15033
+ const entry = { path: path94, content };
15034
15034
  if (typeof rec4.mtimeMs === "number") entry.mtimeMs = rec4.mtimeMs;
15035
15035
  return entry;
15036
15036
  }).filter((m2) => m2 !== null);
@@ -15896,10 +15896,10 @@ function parseAttachment(obj) {
15896
15896
  const memories = raw.map((m2) => {
15897
15897
  if (!m2 || typeof m2 !== "object") return null;
15898
15898
  const rec4 = m2;
15899
- const path93 = typeof rec4.path === "string" ? rec4.path : null;
15899
+ const path94 = typeof rec4.path === "string" ? rec4.path : null;
15900
15900
  const content = typeof rec4.content === "string" ? rec4.content : null;
15901
- if (!path93 || content == null) return null;
15902
- const out = { path: path93, content };
15901
+ if (!path94 || content == null) return null;
15902
+ const out = { path: path94, content };
15903
15903
  if (typeof rec4.mtimeMs === "number") out.mtimeMs = rec4.mtimeMs;
15904
15904
  return out;
15905
15905
  }).filter((m2) => m2 !== null);
@@ -36873,8 +36873,8 @@ var require_utils = __commonJS({
36873
36873
  var result = transform[inputType][outputType](input);
36874
36874
  return result;
36875
36875
  };
36876
- exports2.resolve = function(path93) {
36877
- var parts = path93.split("/");
36876
+ exports2.resolve = function(path94) {
36877
+ var parts = path94.split("/");
36878
36878
  var result = [];
36879
36879
  for (var index = 0; index < parts.length; index++) {
36880
36880
  var part = parts[index];
@@ -42727,18 +42727,18 @@ var require_object = __commonJS({
42727
42727
  var object = new ZipObject(name, zipObjectContent, o);
42728
42728
  this.files[name] = object;
42729
42729
  };
42730
- var parentFolder = function(path93) {
42731
- if (path93.slice(-1) === "/") {
42732
- path93 = path93.substring(0, path93.length - 1);
42730
+ var parentFolder = function(path94) {
42731
+ if (path94.slice(-1) === "/") {
42732
+ path94 = path94.substring(0, path94.length - 1);
42733
42733
  }
42734
- var lastSlash = path93.lastIndexOf("/");
42735
- return lastSlash > 0 ? path93.substring(0, lastSlash) : "";
42734
+ var lastSlash = path94.lastIndexOf("/");
42735
+ return lastSlash > 0 ? path94.substring(0, lastSlash) : "";
42736
42736
  };
42737
- var forceTrailingSlash = function(path93) {
42738
- if (path93.slice(-1) !== "/") {
42739
- path93 += "/";
42737
+ var forceTrailingSlash = function(path94) {
42738
+ if (path94.slice(-1) !== "/") {
42739
+ path94 += "/";
42740
42740
  }
42741
- return path93;
42741
+ return path94;
42742
42742
  };
42743
42743
  var folderAdd = function(name, createFolders) {
42744
42744
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -43740,7 +43740,7 @@ var require_lib3 = __commonJS({
43740
43740
  // src/run-case/recorder.ts
43741
43741
  function startRunCaseRecorder(opts) {
43742
43742
  const now = opts.now ?? Date.now;
43743
- const dir = import_node_path75.default.dirname(opts.recordPath);
43743
+ const dir = import_node_path76.default.dirname(opts.recordPath);
43744
43744
  let stream = null;
43745
43745
  let closing = false;
43746
43746
  let closedSettled = false;
@@ -43754,8 +43754,8 @@ function startRunCaseRecorder(opts) {
43754
43754
  });
43755
43755
  const ensureStream = () => {
43756
43756
  if (stream) return stream;
43757
- import_node_fs54.default.mkdirSync(dir, { recursive: true });
43758
- stream = import_node_fs54.default.createWriteStream(opts.recordPath, { flags: "a" });
43757
+ import_node_fs55.default.mkdirSync(dir, { recursive: true });
43758
+ stream = import_node_fs55.default.createWriteStream(opts.recordPath, { flags: "a" });
43759
43759
  stream.on("close", () => closedResolve());
43760
43760
  return stream;
43761
43761
  };
@@ -43780,12 +43780,12 @@ function startRunCaseRecorder(opts) {
43780
43780
  };
43781
43781
  return { tap, close, closed };
43782
43782
  }
43783
- var import_node_fs54, import_node_path75;
43783
+ var import_node_fs55, import_node_path76;
43784
43784
  var init_recorder = __esm({
43785
43785
  "src/run-case/recorder.ts"() {
43786
43786
  "use strict";
43787
- import_node_fs54 = __toESM(require("fs"), 1);
43788
- import_node_path75 = __toESM(require("path"), 1);
43787
+ import_node_fs55 = __toESM(require("fs"), 1);
43788
+ import_node_path76 = __toESM(require("path"), 1);
43789
43789
  }
43790
43790
  });
43791
43791
 
@@ -43828,7 +43828,7 @@ var init_wire = __esm({
43828
43828
  // src/run-case/controller.ts
43829
43829
  async function runController(opts) {
43830
43830
  const now = opts.now ?? Date.now;
43831
- const cwd = opts.cwd ?? (0, import_node_fs55.mkdtempSync)(import_node_path76.default.join(import_node_os27.default.tmpdir(), "clawd-runcase-"));
43831
+ const cwd = opts.cwd ?? (0, import_node_fs56.mkdtempSync)(import_node_path77.default.join(import_node_os27.default.tmpdir(), "clawd-runcase-"));
43832
43832
  const ownsCwd = opts.cwd === void 0;
43833
43833
  const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
43834
43834
  const spawnCtx = { cwd };
@@ -43989,19 +43989,19 @@ async function runController(opts) {
43989
43989
  if (sigintHandler) process.off("SIGINT", sigintHandler);
43990
43990
  if (ownsCwd) {
43991
43991
  try {
43992
- (0, import_node_fs55.rmSync)(cwd, { recursive: true, force: true });
43992
+ (0, import_node_fs56.rmSync)(cwd, { recursive: true, force: true });
43993
43993
  } catch {
43994
43994
  }
43995
43995
  }
43996
43996
  return exitCode ?? 0;
43997
43997
  }
43998
- var import_node_fs55, import_node_os27, import_node_path76;
43998
+ var import_node_fs56, import_node_os27, import_node_path77;
43999
43999
  var init_controller = __esm({
44000
44000
  "src/run-case/controller.ts"() {
44001
44001
  "use strict";
44002
- import_node_fs55 = require("fs");
44002
+ import_node_fs56 = require("fs");
44003
44003
  import_node_os27 = __toESM(require("os"), 1);
44004
- import_node_path76 = __toESM(require("path"), 1);
44004
+ import_node_path77 = __toESM(require("path"), 1);
44005
44005
  init_claude();
44006
44006
  init_stdout_splitter();
44007
44007
  init_permission_stdio();
@@ -44276,8 +44276,8 @@ Env (advanced):
44276
44276
  `;
44277
44277
 
44278
44278
  // src/index.ts
44279
- var import_node_path74 = __toESM(require("path"), 1);
44280
- var import_node_fs53 = __toESM(require("fs"), 1);
44279
+ var import_node_path75 = __toESM(require("path"), 1);
44280
+ var import_node_fs54 = __toESM(require("fs"), 1);
44281
44281
  var import_node_os26 = __toESM(require("os"), 1);
44282
44282
  var import_node_crypto22 = require("crypto");
44283
44283
  var import_node_url4 = require("url");
@@ -45582,7 +45582,9 @@ function buildSpawnContext(state, deps) {
45582
45582
  if (dispatchId) env.CLAWD_DISPATCH_ID = dispatchId;
45583
45583
  const personaId2 = file.ownerPersonaId;
45584
45584
  if (personaId2) env.CLAWD_PERSONA_ID = personaId2;
45585
- const mcpConfigs = deps.getMcpConfigs?.() ?? [];
45585
+ const mcpConfigs = (deps.getMcpConfigs?.() ?? []).filter(
45586
+ (c) => !c.personas || personaId2 != null && c.personas.includes(personaId2)
45587
+ );
45586
45588
  const ctx = {
45587
45589
  cwd: file.cwd,
45588
45590
  toolSessionId: file.toolSessionId,
@@ -51424,8 +51426,8 @@ function turnStartInput(text) {
51424
51426
  const items = [];
51425
51427
  let leftover = text;
51426
51428
  for (const m2 of text.matchAll(SKILL_RE)) {
51427
- const [marker, name, path93] = m2;
51428
- items.push({ type: "skill", name, path: path93 });
51429
+ const [marker, name, path94] = m2;
51430
+ items.push({ type: "skill", name, path: path94 });
51429
51431
  leftover = leftover.replace(marker, "");
51430
51432
  }
51431
51433
  for (const m2 of text.matchAll(ATTACHMENT_RE2)) {
@@ -54423,13 +54425,20 @@ function buildDeployHandlers(deps) {
54423
54425
  if (!deps.hasPersona(args.personaId)) {
54424
54426
  throw new ClawdError(ERROR_CODES.PERSONA_NOT_FOUND, `PERSONA_NOT_FOUND: \u6CA1\u6709\u8FD9\u4E2A persona\uFF08${args.personaId}\uFF09`);
54425
54427
  }
54426
- const existing = deps.receipts.list().find((r) => r.personaId === args.personaId);
54427
- if (existing) return view(existing);
54428
+ const inFlight = deps.receipts.list().find((r) => r.personaId === args.personaId && r.status === "scanning");
54429
+ if (inFlight) return view(inFlight);
54430
+ const prev = deps.receipts.list().find((r) => r.personaId === args.personaId);
54428
54431
  const receipt = deps.receipts.create({
54429
54432
  deployId: deps.genId(),
54430
54433
  personaId: args.personaId,
54431
54434
  status: "scanning"
54432
54435
  });
54436
+ if (prev) {
54437
+ deps.secrets.copyDeployment(prev.deployId, receipt.deployId);
54438
+ if (Object.keys(prev.fulfillment).length > 0) {
54439
+ deps.receipts.update(receipt.deployId, { fulfillment: prev.fulfillment });
54440
+ }
54441
+ }
54433
54442
  const scanSessionId = deps.startScan(receipt.deployId, args.personaId);
54434
54443
  if (scanSessionId) deps.receipts.update(receipt.deployId, { scanSessionId });
54435
54444
  return view(deps.receipts.get(receipt.deployId) ?? receipt);
@@ -54704,6 +54713,18 @@ function createSecretStore(deps) {
54704
54713
  }
54705
54714
  scheduleFlush();
54706
54715
  },
54716
+ copyDeployment(fromDeployId, toDeployId) {
54717
+ const src = entries.filter((e) => e.deployId === fromDeployId);
54718
+ let copied = 0;
54719
+ for (const e of src) {
54720
+ const exists = entries.some((x) => sameScope(x, toDeployId, e.name, e.userId));
54721
+ if (exists) continue;
54722
+ entries.push({ ...e, deployId: toDeployId, updatedAt: deps.now() });
54723
+ copied++;
54724
+ }
54725
+ if (copied > 0) scheduleFlush();
54726
+ return copied;
54727
+ },
54707
54728
  resolve(deployId, userId) {
54708
54729
  const out = {};
54709
54730
  for (const e of entries) {
@@ -56518,13 +56539,13 @@ function mapSkillsListResponse(res) {
56518
56539
  const r = s ?? {};
56519
56540
  const name = str3(r.name);
56520
56541
  if (!name) continue;
56521
- const path93 = str3(r.path);
56542
+ const path94 = str3(r.path);
56522
56543
  const description = str3(r.description);
56523
56544
  const isPlugin = name.includes(":");
56524
56545
  out.push({
56525
56546
  name,
56526
56547
  source: isPlugin ? "plugin" : "project",
56527
- ...path93 ? { path: path93 } : {},
56548
+ ...path94 ? { path: path94 } : {},
56528
56549
  ...description ? { description } : {},
56529
56550
  ...isPlugin ? { plugin: name.split(":")[0] } : {}
56530
56551
  });
@@ -60846,9 +60867,9 @@ var CentralClientError = class extends Error {
60846
60867
  code;
60847
60868
  cause;
60848
60869
  };
60849
- async function centralRequest(opts, path93, init) {
60870
+ async function centralRequest(opts, path94, init) {
60850
60871
  const f = opts.fetchImpl ?? globalThis.fetch;
60851
- const url = `${opts.api.replace(/\/+$/, "")}${path93}`;
60872
+ const url = `${opts.api.replace(/\/+$/, "")}${path94}`;
60852
60873
  const ctrl = new AbortController();
60853
60874
  const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
60854
60875
  let res;
@@ -61329,7 +61350,7 @@ var ServerKeyStore = class {
61329
61350
  init_protocol();
61330
61351
 
61331
61352
  // src/handlers/index.ts
61332
- var import_node_path70 = __toESM(require("path"), 1);
61353
+ var import_node_path68 = __toESM(require("path"), 1);
61333
61354
 
61334
61355
  // src/handlers/session.ts
61335
61356
  init_protocol();
@@ -63670,7 +63691,7 @@ function computeMethodAccess(args) {
63670
63691
  }
63671
63692
 
63672
63693
  // src/version.ts
63673
- var version = "0.2.452".length > 0 ? "0.2.452" : "dev";
63694
+ var version = "0.2.454".length > 0 ? "0.2.454" : "dev";
63674
63695
 
63675
63696
  // src/cli-probe/probe.ts
63676
63697
  var fs68 = __toESM(require("fs"), 1);
@@ -64698,218 +64719,8 @@ function buildExtensionHandlers(deps) {
64698
64719
  };
64699
64720
  }
64700
64721
 
64701
- // src/app-builder/project-store.ts
64702
- var import_node_fs50 = require("fs");
64703
- var import_node_child_process15 = require("child_process");
64704
- var import_node_path65 = require("path");
64705
- init_protocol();
64706
- var PROJECTS_DIR = "projects";
64707
- var META_FILE = ".clawd-project.json";
64708
- var DEFAULT_TEMPLATE = "nestjs-react";
64709
- var SCAFFOLD_TIMEOUT_MS = 3e4;
64710
- var ProjectStore = class {
64711
- /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
64712
- constructor(root) {
64713
- this.root = root;
64714
- }
64715
- root;
64716
- /** projects/<name>/.clawd-project.json 路径 */
64717
- metaPath(name) {
64718
- return (0, import_node_path65.join)(this.projectsRoot(), name, META_FILE);
64719
- }
64720
- /** projects/<name>/ 目录路径(cwd 用) */
64721
- projectDir(name) {
64722
- return (0, import_node_path65.join)(this.projectsRoot(), name);
64723
- }
64724
- projectsRoot() {
64725
- return (0, import_node_path65.join)(this.root, PROJECTS_DIR);
64726
- }
64727
- async list() {
64728
- let entries;
64729
- try {
64730
- entries = await import_node_fs50.promises.readdir(this.projectsRoot());
64731
- } catch (err) {
64732
- if (err.code === "ENOENT") return [];
64733
- throw err;
64734
- }
64735
- const out = [];
64736
- for (const name of entries) {
64737
- try {
64738
- const raw = await import_node_fs50.promises.readFile(this.metaPath(name), "utf8");
64739
- const json = JSON.parse(raw);
64740
- let migrated = false;
64741
- if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
64742
- json.devCommand = DEFAULT_DEV_COMMAND;
64743
- migrated = true;
64744
- }
64745
- const parsed = ProjectMetadataSchema.safeParse(json);
64746
- if (parsed.success) {
64747
- out.push(parsed.data);
64748
- if (migrated) {
64749
- void import_node_fs50.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
64750
- });
64751
- }
64752
- }
64753
- } catch {
64754
- }
64755
- }
64756
- return out.sort((a, b2) => a.name.localeCompare(b2.name));
64757
- }
64758
- /**
64759
- * @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
64760
- * dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
64761
- * 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
64762
- */
64763
- async create(name, reservedPorts) {
64764
- const existing = await this.list();
64765
- if (existing.some((p2) => p2.name === name)) {
64766
- throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
64767
- }
64768
- const usedPorts = new Set(existing.map((p2) => p2.port));
64769
- if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
64770
- let port = -1;
64771
- for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
64772
- if (!usedPorts.has(p2)) {
64773
- port = p2;
64774
- break;
64775
- }
64776
- }
64777
- if (port < 0) {
64778
- throw new Error(
64779
- `port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
64780
- );
64781
- }
64782
- const meta = {
64783
- name,
64784
- port,
64785
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
64786
- devCommand: DEFAULT_DEV_COMMAND
64787
- };
64788
- const validated = ProjectMetadataSchema.safeParse(meta);
64789
- if (!validated.success) {
64790
- throw new Error(`invalid name "${name}": ${validated.error.message}`);
64791
- }
64792
- const dir = this.projectDir(name);
64793
- await import_node_fs50.promises.mkdir(dir, { recursive: true });
64794
- await import_node_fs50.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
64795
- return meta;
64796
- }
64797
- /**
64798
- * 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
64799
- * 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
64800
- * 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
64801
- *
64802
- * spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
64803
- * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
64804
- */
64805
- async scaffold(name, templateSrcDir, scaffoldScriptPath) {
64806
- const destDir = this.projectDir(name);
64807
- return await new Promise((resolve9, reject) => {
64808
- const child = (0, import_node_child_process15.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
64809
- env: { ...process.env, PATH: process.env.PATH ?? "" },
64810
- stdio: ["ignore", "pipe", "pipe"]
64811
- });
64812
- let stdout = "";
64813
- let stderr = "";
64814
- child.stdout.on("data", (d) => {
64815
- stdout += d.toString("utf8");
64816
- });
64817
- child.stderr.on("data", (d) => {
64818
- stderr += d.toString("utf8");
64819
- });
64820
- const timer = setTimeout(() => {
64821
- child.kill("SIGKILL");
64822
- reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scaffoldScriptPath} ${name}`));
64823
- }, SCAFFOLD_TIMEOUT_MS);
64824
- child.on("error", (err) => {
64825
- clearTimeout(timer);
64826
- reject(new Error(`scaffold spawn failed: ${err.message}`));
64827
- });
64828
- child.on("exit", (code, signal) => {
64829
- clearTimeout(timer);
64830
- if (code === 0) resolve9({ stdout, stderr });
64831
- else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
64832
- });
64833
- });
64834
- }
64835
- async delete(name) {
64836
- const dir = this.projectDir(name);
64837
- await import_node_fs50.promises.rm(dir, { recursive: true, force: true });
64838
- }
64839
- async updatePort(name, newPort) {
64840
- if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
64841
- throw new Error(
64842
- `port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
64843
- );
64844
- }
64845
- const list = await this.list();
64846
- const target = list.find((p2) => p2.name === name);
64847
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
64848
- const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
64849
- if (conflict) {
64850
- throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
64851
- }
64852
- const updated = { ...target, port: newPort };
64853
- await import_node_fs50.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
64854
- return updated;
64855
- }
64856
- /**
64857
- * 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
64858
- * UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
64859
- * 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
64860
- */
64861
- async setProdUrl(name, url) {
64862
- const list = await this.list();
64863
- const target = list.find((p2) => p2.name === name);
64864
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
64865
- const updated = { ...target, prodUrl: url };
64866
- const validated = ProjectMetadataSchema.safeParse(updated);
64867
- if (!validated.success) {
64868
- throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
64869
- }
64870
- await import_node_fs50.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
64871
- return validated.data;
64872
- }
64873
- /**
64874
- * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
64875
- * 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
64876
- * - daemon 收到第一个 ::stage::build marker 时写入
64877
- * - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
64878
- * - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
64879
- * - 只有 daemon 崩才会留残骸
64880
- * 写入时保留所有其他字段(prodUrl 等不受影响)。
64881
- */
64882
- async setPublishJob(name, job) {
64883
- const list = await this.list();
64884
- const target = list.find((p2) => p2.name === name);
64885
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
64886
- const updated = { ...target, publishJob: job };
64887
- const validated = ProjectMetadataSchema.safeParse(updated);
64888
- if (!validated.success) {
64889
- throw new Error(`invalid publishJob: ${validated.error.message}`);
64890
- }
64891
- await import_node_fs50.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
64892
- return validated.data;
64893
- }
64894
- /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
64895
- async clearPublishJob(name) {
64896
- const list = await this.list();
64897
- const target = list.find((p2) => p2.name === name);
64898
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
64899
- if (!target.publishJob) return target;
64900
- const { publishJob: _drop, ...rest } = target;
64901
- void _drop;
64902
- const validated = ProjectMetadataSchema.safeParse(rest);
64903
- if (!validated.success) {
64904
- throw new Error(`failed to clear publishJob: ${validated.error.message}`);
64905
- }
64906
- await import_node_fs50.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
64907
- return validated.data;
64908
- }
64909
- };
64910
-
64911
64722
  // src/app-builder/kill-port.ts
64912
- var import_node_child_process16 = require("child_process");
64723
+ var import_node_child_process15 = require("child_process");
64913
64724
  async function killPortOccupants(port, ownedPids, logger) {
64914
64725
  let pids;
64915
64726
  try {
@@ -64951,7 +64762,7 @@ async function killPortOccupants(port, ownedPids, logger) {
64951
64762
  }
64952
64763
  function listPidsOnPort(port) {
64953
64764
  return new Promise((resolve9, reject) => {
64954
- (0, import_node_child_process16.execFile)(
64765
+ (0, import_node_child_process15.execFile)(
64955
64766
  "lsof",
64956
64767
  ["-ti", `:${port}`],
64957
64768
  { timeout: 3e3 },
@@ -64972,361 +64783,15 @@ function listPidsOnPort(port) {
64972
64783
  });
64973
64784
  }
64974
64785
 
64975
- // src/app-builder/publish-registry.ts
64976
- var import_node_crypto20 = require("crypto");
64977
- var PublishJobRegistry = class {
64978
- jobs = /* @__PURE__ */ new Map();
64979
- has(name) {
64980
- return this.jobs.has(name);
64981
- }
64982
- get(name) {
64983
- return this.jobs.get(name);
64984
- }
64985
- /**
64986
- * args.jobId 仅用于测试注入;生产代码不传,registry 自己生成。
64987
- * args.now 仅用于测试注入;生产代码不传,用 Date.now()。
64988
- */
64989
- create(args) {
64990
- if (this.jobs.has(args.name)) {
64991
- throw new Error(`already publishing: ${args.name}`);
64992
- }
64993
- const jobId = args.jobId ?? `job-${(0, import_node_crypto20.randomUUID)()}`;
64994
- this.jobs.set(args.name, {
64995
- jobId,
64996
- name: args.name,
64997
- stage: "build",
64998
- sessionId: args.sessionId,
64999
- childProc: args.childProc,
65000
- dismissed: false,
65001
- sawAnyStage: false,
65002
- startedAt: args.now ?? Date.now()
65003
- });
65004
- return jobId;
65005
- }
65006
- updateStage(name, stage) {
65007
- const job = this.jobs.get(name);
65008
- if (!job) return;
65009
- job.stage = stage;
65010
- job.sawAnyStage = true;
65011
- }
65012
- markDismissed(name) {
65013
- const job = this.jobs.get(name);
65014
- if (!job) return;
65015
- job.dismissed = true;
65016
- }
65017
- remove(name) {
65018
- const job = this.jobs.get(name);
65019
- if (!job) return void 0;
65020
- this.jobs.delete(name);
65021
- return { sessionId: job.sessionId };
65022
- }
65023
- };
65024
-
65025
- // src/app-builder/publish-job-runner.ts
65026
- var import_node_child_process17 = require("child_process");
65027
- var import_node_fs51 = require("fs");
65028
- var import_node_path66 = require("path");
65029
-
65030
- // src/app-builder/publish-stage-parser.ts
65031
- var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
65032
- var PROD_URL_RE = /^\s*::prod-url::(\S+)\s*$/;
65033
- var URL_RE = /\bhttps?:\/\/[^\s]+/;
65034
- function parsePublishLine(line) {
65035
- const stageMatch = STAGE_RE.exec(line);
65036
- if (stageMatch) return { kind: "stage", stage: stageMatch[1] };
65037
- const urlMatch = PROD_URL_RE.exec(line);
65038
- if (urlMatch) return { kind: "prod-url", url: urlMatch[1] };
65039
- return null;
65040
- }
65041
- function extractProdUrl(text) {
65042
- const m2 = URL_RE.exec(text);
65043
- return m2 ? m2[0] : null;
65044
- }
65045
- function tailStderrLines(buf, n) {
65046
- if (!buf) return "";
65047
- const lines = buf.split("\n");
65048
- if (lines.length <= n) return buf;
65049
- return lines.slice(lines.length - n).join("\n");
65050
- }
65051
-
65052
- // src/app-builder/publish-job-runner.ts
65053
- async function startPublishJob(deps, args) {
65054
- const { registry: registry2, projectDir } = deps;
65055
- const spawn13 = deps.spawnImpl ?? import_node_child_process17.spawn;
65056
- if (registry2.has(args.name)) {
65057
- return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
65058
- }
65059
- const projDir = projectDir(args.name);
65060
- const logPath = (0, import_node_path66.join)(projDir, ".publish.log");
65061
- let logStream = null;
65062
- try {
65063
- logStream = (0, import_node_fs51.createWriteStream)(logPath, { flags: "w" });
65064
- } catch {
65065
- logStream = null;
65066
- }
65067
- const child = spawn13("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
65068
- cwd: projDir,
65069
- env: process.env,
65070
- stdio: ["ignore", "pipe", "pipe"]
65071
- });
65072
- const jobId = registry2.create({
65073
- name: args.name,
65074
- sessionId: args.sessionId,
65075
- childProc: child
65076
- });
65077
- deps.logger?.info("app-builder.publish.spawned", {
65078
- name: args.name,
65079
- jobId,
65080
- pid: child.pid ?? null,
65081
- scriptPath: args.scriptPath,
65082
- logPath
65083
- });
65084
- child.on("error", (err) => {
65085
- deps.logger?.warn("app-builder.publish.spawn-error", {
65086
- name: args.name,
65087
- jobId,
65088
- error: err instanceof Error ? err.message : String(err)
65089
- });
65090
- void handleExit(deps, {
65091
- name: args.name,
65092
- jobId,
65093
- code: null,
65094
- signal: null,
65095
- urlCandidate: null,
65096
- stderrTail: err instanceof Error ? err.message : String(err),
65097
- logPath
65098
- });
65099
- });
65100
- let urlFromMarker = null;
65101
- let urlFallback = null;
65102
- let stdoutBuf = "";
65103
- child.stdout?.on("data", (chunk) => {
65104
- if (logStream) logStream.write(chunk);
65105
- stdoutBuf += chunk.toString();
65106
- let nlIdx;
65107
- while ((nlIdx = stdoutBuf.indexOf("\n")) >= 0) {
65108
- const line = stdoutBuf.slice(0, nlIdx);
65109
- stdoutBuf = stdoutBuf.slice(nlIdx + 1);
65110
- handleStdoutLine(deps, args.name, jobId, line);
65111
- const parsedForUrl = parsePublishLine(line);
65112
- if (parsedForUrl?.kind === "prod-url") urlFromMarker = parsedForUrl.url;
65113
- const url = extractProdUrl(line);
65114
- if (url && !urlFallback) urlFallback = url;
65115
- }
65116
- });
65117
- let stderrBuf = "";
65118
- child.stderr?.on("data", (chunk) => {
65119
- if (logStream) logStream.write(chunk);
65120
- stderrBuf += chunk.toString();
65121
- });
65122
- child.on("exit", (code, signal) => {
65123
- if (logStream) logStream.end();
65124
- const urlCandidate = urlFromMarker ?? urlFallback;
65125
- deps.logger?.info("app-builder.publish.child-exit", {
65126
- name: args.name,
65127
- jobId,
65128
- code,
65129
- signal,
65130
- hadUrl: !!urlCandidate,
65131
- urlSource: urlFromMarker ? "marker" : urlFallback ? "fallback" : "none",
65132
- stderrBytes: stderrBuf.length
65133
- });
65134
- void handleExit(deps, {
65135
- name: args.name,
65136
- jobId,
65137
- code,
65138
- signal,
65139
- urlCandidate,
65140
- stderrTail: tailStderrLines(stderrBuf, 20),
65141
- logPath
65142
- });
65143
- });
65144
- return { jobId, status: "started" };
65145
- }
65146
- function handleStdoutLine(deps, name, jobId, line) {
65147
- const parsed = parsePublishLine(line);
65148
- if (parsed?.kind !== "stage") return;
65149
- advanceStage(deps, name, jobId, parsed.stage);
65150
- }
65151
- function advanceStage(deps, name, jobId, next) {
65152
- const job = deps.registry.get(name);
65153
- if (!job) return;
65154
- const prev = job.stage;
65155
- const wasFirstStage = !job.sawAnyStage;
65156
- deps.logger?.info("app-builder.publish.stage-advance", {
65157
- name,
65158
- jobId,
65159
- prev: wasFirstStage ? null : prev,
65160
- next,
65161
- firstStage: wasFirstStage
65162
- });
65163
- if (wasFirstStage) {
65164
- deps.registry.updateStage(name, next);
65165
- void deps.setPublishJob(name, {
65166
- jobId,
65167
- stage: next,
65168
- startedAt: job.startedAt
65169
- });
65170
- deps.broadcast({
65171
- type: "appBuilder:publish-progress",
65172
- name,
65173
- jobId,
65174
- stage: next,
65175
- status: "started"
65176
- });
65177
- return;
65178
- }
65179
- if (prev === next) {
65180
- return;
65181
- }
65182
- deps.broadcast({
65183
- type: "appBuilder:publish-progress",
65184
- name,
65185
- jobId,
65186
- stage: prev,
65187
- status: "completed"
65188
- });
65189
- deps.registry.updateStage(name, next);
65190
- void deps.setPublishJob(name, {
65191
- jobId,
65192
- stage: next,
65193
- startedAt: job.startedAt
65194
- });
65195
- deps.broadcast({
65196
- type: "appBuilder:publish-progress",
65197
- name,
65198
- jobId,
65199
- stage: next,
65200
- status: "started"
65201
- });
65202
- }
65203
- async function handleExit(deps, args) {
65204
- const job = deps.registry.get(args.name);
65205
- if (args.signal === "SIGTERM" && job?.dismissed) {
65206
- try {
65207
- await deps.clearPublishJob(args.name);
65208
- } catch (err) {
65209
- console.error("[publish] clearPublishJob failed (dismissed branch)", err);
65210
- }
65211
- deps.registry.remove(args.name);
65212
- return;
65213
- }
65214
- if (args.code === 0 && args.urlCandidate) {
65215
- if (job?.sawAnyStage) {
65216
- deps.broadcast({
65217
- type: "appBuilder:publish-progress",
65218
- name: args.name,
65219
- jobId: args.jobId,
65220
- stage: job.stage,
65221
- status: "completed"
65222
- });
65223
- }
65224
- try {
65225
- await deps.setProdUrl(args.name, args.urlCandidate);
65226
- } catch (err) {
65227
- console.error("[publish] setProdUrl failed; falling through to failure branch", err);
65228
- await emitFailure(deps, args, "\u8C03 setProdUrl \u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)));
65229
- return;
65230
- }
65231
- try {
65232
- await deps.clearPublishJob(args.name);
65233
- } catch (err) {
65234
- console.error("[publish] clearPublishJob failed (success branch)", err);
65235
- }
65236
- deps.registry.remove(args.name);
65237
- return;
65238
- }
65239
- await emitFailure(deps, args, buildErrorSummary(args));
65240
- }
65241
- async function emitFailure(deps, args, errorSummary) {
65242
- const job = deps.registry.get(args.name);
65243
- const failStage = job?.sawAnyStage ? job.stage : "unknown";
65244
- deps.logger?.warn("app-builder.publish.failed", {
65245
- name: args.name,
65246
- jobId: args.jobId,
65247
- stage: failStage,
65248
- errorSummary: errorSummary.slice(0, 500)
65249
- });
65250
- try {
65251
- await deps.clearPublishJob(args.name);
65252
- } catch (err) {
65253
- deps.logger?.warn("app-builder.publish.clear-field-failed", {
65254
- name: args.name,
65255
- jobId: args.jobId,
65256
- error: err instanceof Error ? err.message : String(err)
65257
- });
65258
- }
65259
- const removed = deps.registry.remove(args.name);
65260
- deps.broadcast({
65261
- type: "appBuilder:publish-failed",
65262
- name: args.name,
65263
- jobId: args.jobId,
65264
- stage: failStage,
65265
- errorSummary
65266
- });
65267
- if (removed) {
65268
- const msg = formatFailureMessage(failStage, errorSummary, args.logPath);
65269
- try {
65270
- await deps.injectUserText(removed.sessionId, msg);
65271
- deps.logger?.info("app-builder.publish.failure-injected-to-chat", {
65272
- name: args.name,
65273
- jobId: args.jobId,
65274
- sessionId: removed.sessionId
65275
- });
65276
- } catch (err) {
65277
- deps.logger?.warn("app-builder.publish.inject-user-text-failed", {
65278
- name: args.name,
65279
- jobId: args.jobId,
65280
- sessionId: removed.sessionId,
65281
- error: err instanceof Error ? err.message : String(err)
65282
- });
65283
- }
65284
- } else {
65285
- deps.logger?.warn("app-builder.publish.no-registry-entry-on-failure", {
65286
- name: args.name,
65287
- jobId: args.jobId
65288
- });
65289
- }
65290
- }
65291
- function buildErrorSummary(args) {
65292
- if (args.signal) {
65293
- return `publish.sh \u88AB\u4E2D\u65AD\uFF08signal: ${args.signal}\uFF09`;
65294
- }
65295
- if (args.code === 0 && !args.urlCandidate) {
65296
- return "publish.sh \u6210\u529F\u9000\u51FA\u4F46\u672A\u8F93\u51FA\u516C\u7F51 URL";
65297
- }
65298
- return args.stderrTail || `publish.sh \u5F02\u5E38\u9000\u51FA\uFF08exit code: ${args.code}\uFF09`;
65299
- }
65300
- function formatFailureMessage(stage, errorSummary, logPath) {
65301
- return `\u53D1\u5E03\u5931\u8D25\uFF1A[${stage}] \u9636\u6BB5
65302
- ${errorSummary}
65303
-
65304
- \u5B8C\u6574\u65E5\u5FD7\uFF1A${logPath}
65305
- \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`;
65306
- }
65307
- async function recoverInterruptedJobs(deps) {
65308
- const projects = await deps.listAllProjects();
65309
- for (const p2 of projects) {
65310
- if (p2.publishJob) {
65311
- deps.log(
65312
- `[publish] found interrupted job ${p2.publishJob.jobId} for ${p2.name}, stage=${p2.publishJob.stage}`
65313
- );
65314
- }
65315
- }
65316
- }
64786
+ // src/app-builder/ports.ts
64787
+ var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
65317
64788
 
65318
64789
  // src/handlers/app-builder.ts
65319
64790
  init_protocol();
65320
- var import_node_path67 = require("path");
65321
- var import_node_fs52 = require("fs");
65322
- var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
64791
+ var import_node_path65 = require("path");
64792
+ var import_node_fs50 = require("fs");
64793
+ var DEFAULT_TEMPLATE = "nestjs-react";
65323
64794
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
65324
- async function recoverInterruptedPublishJobs(store, logger) {
65325
- await recoverInterruptedJobs({
65326
- listAllProjects: async () => store.list(),
65327
- log: (msg) => logger?.info(msg)
65328
- });
65329
- }
65330
64795
  function buildAppBuilderHandlers(deps) {
65331
64796
  const { store, devServerLifecycle, sessionManager, getTunnelUrl, broadcastProjectUpdated } = deps;
65332
64797
  function resolveStore(opts) {
@@ -65353,11 +64818,9 @@ function buildAppBuilderHandlers(deps) {
65353
64818
  }
65354
64819
  }
65355
64820
  const stages = /* @__PURE__ */ new Map();
65356
- const publishRegistry = new PublishJobRegistry();
65357
64821
  function withPublishJobStatus(meta) {
65358
64822
  if (!meta.publishJob) return void 0;
65359
- const job = publishRegistry.get(meta.name);
65360
- const inFlight = !!job && job.jobId === meta.publishJob.jobId;
64823
+ const inFlight = deps.publishService.inFlightJobId(meta.name) === meta.publishJob.jobId;
65361
64824
  return {
65362
64825
  ...meta.publishJob,
65363
64826
  status: inFlight ? "in-flight" : "interrupted"
@@ -65399,7 +64862,7 @@ function buildAppBuilderHandlers(deps) {
65399
64862
  async function listAllUsersProjects() {
65400
64863
  if (!deps.usersRoot || !deps.getStore) return [];
65401
64864
  const getStore = deps.getStore;
65402
- const userIds = await import_node_fs52.promises.readdir(deps.usersRoot).catch(() => []);
64865
+ const userIds = await import_node_fs50.promises.readdir(deps.usersRoot).catch(() => []);
65403
64866
  const perUser = await Promise.all(
65404
64867
  userIds.map((uid) => getStore(uid).list().catch(() => []))
65405
64868
  );
@@ -65465,26 +64928,26 @@ function buildAppBuilderHandlers(deps) {
65465
64928
  const userStore = resolveStore({ ctx, sessionId: f.sessionId });
65466
64929
  deps.logger?.info("app-builder.create-project.start", { name: f.name, sessionId: f.sessionId });
65467
64930
  const reservedPorts = new Set((await listAllUsersProjects()).map((p2) => p2.port));
65468
- const project = await userStore.create(f.name, reservedPorts);
64931
+ const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
64932
+ let project;
64933
+ let projectDir;
65469
64934
  try {
65470
- const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
65471
- const templateSrcDir = (0, import_node_path67.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
65472
- const scaffoldScript = (0, import_node_path67.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
65473
- const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
65474
- deps.logger?.info("app-builder.scaffold.done", {
65475
- name: project.name,
65476
- stdout: scaffoldResult.stdout.slice(0, 200)
64935
+ const created = await deps.createProjectOnDisk({
64936
+ store: userStore,
64937
+ name: f.name,
64938
+ templateSrcDir: (0, import_node_path65.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE),
64939
+ scaffoldScript: (0, import_node_path65.join)(deps.deployKitRoot, "scripts", "new-extension.sh"),
64940
+ reservedPorts,
64941
+ ...deps.logger ? { logger: deps.logger } : {}
65477
64942
  });
64943
+ project = created.project;
64944
+ projectDir = created.projectDir;
65478
64945
  } catch (err) {
65479
64946
  const message = err instanceof Error ? err.message : String(err);
65480
- deps.logger?.warn("app-builder.scaffold.failed", { name: project.name, error: message });
65481
- await userStore.delete(project.name).catch((delErr) => {
65482
- deps.logger?.warn("app-builder.scaffold.rollback-failed", {
65483
- name: project.name,
65484
- error: delErr instanceof Error ? delErr.message : String(delErr)
65485
- });
65486
- });
65487
- throw new ClawdError(ERROR_CODES.INTERNAL, `scaffold failed: ${message}`);
64947
+ if (message.startsWith("scaffold failed")) {
64948
+ throw new ClawdError(ERROR_CODES.INTERNAL, message);
64949
+ }
64950
+ throw err;
65488
64951
  }
65489
64952
  const { broadcast } = sessionManager.update({
65490
64953
  sessionId: f.sessionId,
@@ -65496,7 +64959,7 @@ function buildAppBuilderHandlers(deps) {
65496
64959
  port: project.port,
65497
64960
  sessionId: f.sessionId
65498
64961
  });
65499
- return { response: { project, projectDir: userStore.projectDir(project.name) }, broadcast };
64962
+ return { response: { project, projectDir }, broadcast };
65500
64963
  };
65501
64964
  const deleteProject = async (frame, _client, ctx) => {
65502
64965
  const { name } = parseArgs2(DeleteProjectArgsSchema, frame, "deleteProject");
@@ -65664,7 +65127,7 @@ function buildAppBuilderHandlers(deps) {
65664
65127
  `publish: project "${args.name}" has no bound session (orphan); cannot inject failure to chat`
65665
65128
  );
65666
65129
  }
65667
- if (project.publishJob && !publishRegistry.has(args.name)) {
65130
+ if (project.publishJob && deps.publishService.inFlightJobId(args.name) === null) {
65668
65131
  deps.logger?.info("app-builder.publish.clear-interrupted", {
65669
65132
  name: args.name,
65670
65133
  previousJobId: project.publishJob.jobId,
@@ -65673,28 +65136,25 @@ function buildAppBuilderHandlers(deps) {
65673
65136
  await userStore.clearPublishJob(args.name);
65674
65137
  }
65675
65138
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
65676
- const scriptPath = (0, import_node_path67.join)(deps.deployKitRoot, "scripts", "publish.sh");
65139
+ const scriptPath = (0, import_node_path65.join)(deps.deployKitRoot, "scripts", "publish.sh");
65677
65140
  deps.logger?.info("app-builder.publish.start", {
65678
65141
  name: args.name,
65679
65142
  sessionId: boundSession.sessionId,
65680
65143
  scriptPath,
65681
65144
  personaRoot
65682
65145
  });
65683
- const result = await startPublishJob(
65684
- {
65685
- registry: publishRegistry,
65686
- setPublishJob: async (name, job) => {
65687
- await userStore.setPublishJob(name, job);
65688
- const current = getStageOf(name);
65689
- await setStageAndBroadcast(name, current.stage, current.reason, userStore);
65690
- },
65691
- clearPublishJob: async (name) => {
65692
- await userStore.clearPublishJob(name);
65693
- const current = getStageOf(name);
65694
- await setStageAndBroadcast(name, current.stage, current.reason, userStore);
65695
- },
65696
- setProdUrl: async (name, url) => {
65697
- await userStore.setProdUrl(name, url);
65146
+ const result = await deps.publishService.start({
65147
+ name: args.name,
65148
+ sessionId: boundSession.sessionId,
65149
+ scriptPath,
65150
+ personaRoot,
65151
+ store: userStore,
65152
+ hooks: {
65153
+ // 元数据每次写盘(publishJob 起落 / prodUrl 回填)后重推一帧:UI 渲染 PublishProgress
65154
+ // 三段 checklist 依赖 project.publishJob.status==='in-flight',而 publishJob 字段只有
65155
+ // 通过 project-updated 帧才能到达 UI(publish-progress 帧 UI 端只用来设 currentStage,
65156
+ // 不更新 currentProject)。prodUrl 同理,靠这条链路让 PreviewPane 切线上预览。
65157
+ onProjectChanged: async (name) => {
65698
65158
  const current = getStageOf(name);
65699
65159
  await setStageAndBroadcast(name, current.stage, current.reason, userStore);
65700
65160
  },
@@ -65713,17 +65173,10 @@ function buildAppBuilderHandlers(deps) {
65713
65173
  for (const entry of broadcast) {
65714
65174
  deps.broadcastSessionFrame(entry.frame, entry.target);
65715
65175
  }
65716
- },
65717
- projectDir: (name) => userStore.projectDir(name),
65718
- logger: deps.logger
65176
+ }
65719
65177
  },
65720
- {
65721
- name: args.name,
65722
- sessionId: boundSession.sessionId,
65723
- scriptPath,
65724
- personaRoot
65725
- }
65726
- );
65178
+ ...deps.logger ? { logger: deps.logger } : {}
65179
+ });
65727
65180
  return { response: { ...result } };
65728
65181
  };
65729
65182
  const dismissPublishJob = async (frame, _client, ctx) => {
@@ -65738,15 +65191,13 @@ function buildAppBuilderHandlers(deps) {
65738
65191
  `dismissPublishJob: project "${args.name}" not found`
65739
65192
  );
65740
65193
  }
65741
- const job = publishRegistry.get(args.name);
65742
- if (job) {
65194
+ const inFlightJobId = deps.publishService.inFlightJobId(args.name);
65195
+ if (inFlightJobId) {
65743
65196
  deps.logger?.info("app-builder.publish.dismiss.in-flight", {
65744
65197
  name: args.name,
65745
- jobId: job.jobId,
65746
- stage: job.stage
65198
+ jobId: inFlightJobId
65747
65199
  });
65748
- publishRegistry.markDismissed(args.name);
65749
- job.childProc.kill("SIGTERM");
65200
+ deps.publishService.dismiss(args.name);
65750
65201
  } else if (project.publishJob) {
65751
65202
  deps.logger?.info("app-builder.publish.dismiss.interrupted", {
65752
65203
  name: args.name,
@@ -66432,7 +65883,7 @@ function buildVisitorHandlers(deps) {
66432
65883
 
66433
65884
  // src/extension/registry.ts
66434
65885
  var import_promises16 = __toESM(require("fs/promises"), 1);
66435
- var import_node_path68 = __toESM(require("path"), 1);
65886
+ var import_node_path66 = __toESM(require("path"), 1);
66436
65887
  async function loadAll(root) {
66437
65888
  let entries;
66438
65889
  try {
@@ -66445,13 +65896,13 @@ async function loadAll(root) {
66445
65896
  for (const ent of entries) {
66446
65897
  if (!ent.isDirectory()) continue;
66447
65898
  if (ent.name.startsWith(".")) continue;
66448
- records.push(await loadOne(import_node_path68.default.join(root, ent.name), ent.name));
65899
+ records.push(await loadOne(import_node_path66.default.join(root, ent.name), ent.name));
66449
65900
  }
66450
65901
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
66451
65902
  return records;
66452
65903
  }
66453
65904
  async function loadOne(dir, dirName) {
66454
- const manifestPath = import_node_path68.default.join(dir, "manifest.json");
65905
+ const manifestPath = import_node_path66.default.join(dir, "manifest.json");
66455
65906
  let raw;
66456
65907
  try {
66457
65908
  raw = await import_promises16.default.readFile(manifestPath, "utf8");
@@ -66496,7 +65947,7 @@ async function loadOne(dir, dirName) {
66496
65947
 
66497
65948
  // src/extension/uninstall.ts
66498
65949
  var import_promises17 = __toESM(require("fs/promises"), 1);
66499
- var import_node_path69 = __toESM(require("path"), 1);
65950
+ var import_node_path67 = __toESM(require("path"), 1);
66500
65951
  var UninstallError = class extends Error {
66501
65952
  constructor(code, message) {
66502
65953
  super(message);
@@ -66505,7 +65956,7 @@ var UninstallError = class extends Error {
66505
65956
  code;
66506
65957
  };
66507
65958
  async function uninstall(deps) {
66508
- const dir = import_node_path69.default.join(deps.root, deps.extId);
65959
+ const dir = import_node_path67.default.join(deps.root, deps.extId);
66509
65960
  try {
66510
65961
  await import_promises17.default.access(dir);
66511
65962
  } catch {
@@ -66516,13 +65967,13 @@ async function uninstall(deps) {
66516
65967
  }
66517
65968
 
66518
65969
  // src/handlers/index.ts
66519
- var import_node_crypto21 = require("crypto");
65970
+ var import_node_crypto20 = require("crypto");
66520
65971
 
66521
65972
  // src/handlers/peer-exec.ts
66522
65973
  init_protocol();
66523
65974
 
66524
65975
  // src/peer-ops/run-command.ts
66525
- var import_node_child_process18 = require("child_process");
65976
+ var import_node_child_process16 = require("child_process");
66526
65977
  var import_node_os24 = __toESM(require("os"), 1);
66527
65978
  var DEFAULT_TIMEOUT_MS5 = 3e4;
66528
65979
  var MAX_TIMEOUT_MS = 3e5;
@@ -66530,7 +65981,7 @@ var MAX_OUTPUT_BYTES = 1048576;
66530
65981
  function runCommand(command, opts = {}) {
66531
65982
  const timeoutMs = Math.min(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS5, MAX_TIMEOUT_MS);
66532
65983
  return new Promise((resolve9) => {
66533
- const child = (0, import_node_child_process18.spawn)("bash", ["-c", command], { cwd: import_node_os24.default.homedir() });
65984
+ const child = (0, import_node_child_process16.spawn)("bash", ["-c", command], { cwd: import_node_os24.default.homedir() });
66534
65985
  const out = [];
66535
65986
  const err = [];
66536
65987
  let outBytes = 0;
@@ -66672,7 +66123,7 @@ function buildMethodHandlers(deps) {
66672
66123
  const c = deps.contactStore.get(deviceId);
66673
66124
  return c ? { deviceId: c.deviceId, remoteUrl: c.remoteUrl, connectToken: c.connectToken } : null;
66674
66125
  },
66675
- genId: () => (0, import_node_crypto21.randomUUID)(),
66126
+ genId: () => (0, import_node_crypto20.randomUUID)(),
66676
66127
  now: () => Date.now(),
66677
66128
  forwardInboxPostToPeer,
66678
66129
  logger: deps.logger
@@ -66715,6 +66166,8 @@ function buildMethodHandlers(deps) {
66715
66166
  ...buildAppBuilderHandlers({
66716
66167
  store: deps.appBuilderStore,
66717
66168
  getStore: deps.getProjectStore,
66169
+ publishService: deps.publishService,
66170
+ createProjectOnDisk: deps.createProjectOnDisk,
66718
66171
  usersRoot: deps.usersRoot,
66719
66172
  devServerLifecycle: deps.devServerLifecycle,
66720
66173
  sessionManager: deps.manager,
@@ -66738,7 +66191,7 @@ function buildMethodHandlers(deps) {
66738
66191
  broadcast: deps.broadcastToOwners
66739
66192
  }),
66740
66193
  ...buildMasterHandlers({
66741
- authorityTablePath: import_node_path70.default.join(deps.masterDataDir, "master-authority.json")
66194
+ authorityTablePath: import_node_path68.default.join(deps.masterDataDir, "master-authority.json")
66742
66195
  }),
66743
66196
  ...buildTopicHandlers({
66744
66197
  topicStore: deps.topicStore,
@@ -66769,23 +66222,23 @@ function buildMethodHandlers(deps) {
66769
66222
  init_protocol();
66770
66223
 
66771
66224
  // src/tools/version-probe.ts
66772
- var import_node_child_process20 = require("child_process");
66225
+ var import_node_child_process18 = require("child_process");
66773
66226
  init_claude();
66774
66227
 
66775
66228
  // src/tools/shell-path.ts
66776
- var import_node_child_process19 = require("child_process");
66229
+ var import_node_child_process17 = require("child_process");
66777
66230
  var import_node_os25 = __toESM(require("os"), 1);
66778
66231
  function refreshPathFromLoginShell() {
66779
66232
  if (process.platform === "win32") return;
66780
66233
  const shell = process.env.SHELL ?? import_node_os25.default.userInfo().shell ?? "/bin/zsh";
66781
66234
  try {
66782
- const out = (0, import_node_child_process19.execFileSync)(shell, ["-ilc", 'command echo -n "$PATH"'], {
66235
+ const out = (0, import_node_child_process17.execFileSync)(shell, ["-ilc", 'command echo -n "$PATH"'], {
66783
66236
  encoding: "utf8",
66784
66237
  timeout: 3e3,
66785
66238
  stdio: ["ignore", "pipe", "ignore"]
66786
66239
  });
66787
- const path93 = out.trim();
66788
- if (path93) process.env.PATH = path93;
66240
+ const path94 = out.trim();
66241
+ if (path94) process.env.PATH = path94;
66789
66242
  } catch {
66790
66243
  }
66791
66244
  }
@@ -66796,7 +66249,7 @@ function parseVersionOutput(out) {
66796
66249
  }
66797
66250
  function execVersion(bin) {
66798
66251
  return new Promise((resolve9, reject) => {
66799
- (0, import_node_child_process20.execFile)(bin, ["--version"], { timeout: 5e3 }, (err, stdout) => {
66252
+ (0, import_node_child_process18.execFile)(bin, ["--version"], { timeout: 5e3 }, (err, stdout) => {
66800
66253
  if (err) reject(err);
66801
66254
  else resolve9(String(stdout));
66802
66255
  });
@@ -66908,8 +66361,242 @@ function buildOnboardingHandlers(deps) {
66908
66361
  return { "onboarding:complete": complete, "tools:probe": toolsProbe, "onboarding:iosInterest": iosInterest };
66909
66362
  }
66910
66363
 
66364
+ // src/app-builder-plugin/project-store.ts
66365
+ var import_node_fs51 = require("fs");
66366
+ var import_node_child_process19 = require("child_process");
66367
+ var import_node_path69 = require("path");
66368
+ init_protocol();
66369
+ var PROJECTS_DIR = "projects";
66370
+ var META_FILE = ".clawd-project.json";
66371
+ var SCAFFOLD_TIMEOUT_MS = 3e4;
66372
+ var metaWriteQueues = /* @__PURE__ */ new Map();
66373
+ function serializeMetaWrite(metaPath, op) {
66374
+ const prev = metaWriteQueues.get(metaPath) ?? Promise.resolve();
66375
+ const next = prev.then(op, op);
66376
+ const settled = next.then(
66377
+ () => void 0,
66378
+ () => void 0
66379
+ );
66380
+ metaWriteQueues.set(metaPath, settled);
66381
+ void settled.then(() => {
66382
+ if (metaWriteQueues.get(metaPath) === settled) metaWriteQueues.delete(metaPath);
66383
+ });
66384
+ return next;
66385
+ }
66386
+ var ProjectStore = class {
66387
+ /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
66388
+ constructor(root) {
66389
+ this.root = root;
66390
+ }
66391
+ root;
66392
+ serializeWrite(name, op) {
66393
+ return serializeMetaWrite(this.metaPath(name), op);
66394
+ }
66395
+ /** projects/<name>/.clawd-project.json 路径 */
66396
+ metaPath(name) {
66397
+ return (0, import_node_path69.join)(this.projectsRoot(), name, META_FILE);
66398
+ }
66399
+ /** projects/<name>/ 目录路径(cwd 用) */
66400
+ projectDir(name) {
66401
+ return (0, import_node_path69.join)(this.projectsRoot(), name);
66402
+ }
66403
+ projectsRoot() {
66404
+ return (0, import_node_path69.join)(this.root, PROJECTS_DIR);
66405
+ }
66406
+ async list() {
66407
+ let entries;
66408
+ try {
66409
+ entries = await import_node_fs51.promises.readdir(this.projectsRoot());
66410
+ } catch (err) {
66411
+ if (err.code === "ENOENT") return [];
66412
+ throw err;
66413
+ }
66414
+ const out = [];
66415
+ for (const name of entries) {
66416
+ try {
66417
+ const raw = await import_node_fs51.promises.readFile(this.metaPath(name), "utf8");
66418
+ const json = JSON.parse(raw);
66419
+ let migrated = false;
66420
+ if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
66421
+ json.devCommand = DEFAULT_DEV_COMMAND;
66422
+ migrated = true;
66423
+ }
66424
+ const parsed = ProjectMetadataSchema.safeParse(json);
66425
+ if (parsed.success) {
66426
+ out.push(parsed.data);
66427
+ if (migrated) {
66428
+ void import_node_fs51.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
66429
+ });
66430
+ }
66431
+ }
66432
+ } catch {
66433
+ }
66434
+ }
66435
+ return out.sort((a, b2) => a.name.localeCompare(b2.name));
66436
+ }
66437
+ /**
66438
+ * @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
66439
+ * dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
66440
+ * 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
66441
+ */
66442
+ async create(name, reservedPorts) {
66443
+ const existing = await this.list();
66444
+ if (existing.some((p2) => p2.name === name)) {
66445
+ throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
66446
+ }
66447
+ const usedPorts = new Set(existing.map((p2) => p2.port));
66448
+ if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
66449
+ let port = -1;
66450
+ for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
66451
+ if (!usedPorts.has(p2)) {
66452
+ port = p2;
66453
+ break;
66454
+ }
66455
+ }
66456
+ if (port < 0) {
66457
+ throw new Error(
66458
+ `port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
66459
+ );
66460
+ }
66461
+ const meta = {
66462
+ name,
66463
+ port,
66464
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
66465
+ devCommand: DEFAULT_DEV_COMMAND
66466
+ };
66467
+ const validated = ProjectMetadataSchema.safeParse(meta);
66468
+ if (!validated.success) {
66469
+ throw new Error(`invalid name "${name}": ${validated.error.message}`);
66470
+ }
66471
+ const dir = this.projectDir(name);
66472
+ await import_node_fs51.promises.mkdir(dir, { recursive: true });
66473
+ await import_node_fs51.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
66474
+ return meta;
66475
+ }
66476
+ /**
66477
+ * 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
66478
+ * 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
66479
+ * 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
66480
+ *
66481
+ * spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
66482
+ * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
66483
+ */
66484
+ async scaffold(name, templateSrcDir, scaffoldScriptPath) {
66485
+ const destDir = this.projectDir(name);
66486
+ return await new Promise((resolve9, reject) => {
66487
+ const child = (0, import_node_child_process19.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
66488
+ env: { ...process.env, PATH: process.env.PATH ?? "" },
66489
+ stdio: ["ignore", "pipe", "pipe"]
66490
+ });
66491
+ let stdout = "";
66492
+ let stderr = "";
66493
+ child.stdout.on("data", (d) => {
66494
+ stdout += d.toString("utf8");
66495
+ });
66496
+ child.stderr.on("data", (d) => {
66497
+ stderr += d.toString("utf8");
66498
+ });
66499
+ const timer = setTimeout(() => {
66500
+ child.kill("SIGKILL");
66501
+ reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scaffoldScriptPath} ${name}`));
66502
+ }, SCAFFOLD_TIMEOUT_MS);
66503
+ child.on("error", (err) => {
66504
+ clearTimeout(timer);
66505
+ reject(new Error(`scaffold spawn failed: ${err.message}`));
66506
+ });
66507
+ child.on("exit", (code, signal) => {
66508
+ clearTimeout(timer);
66509
+ if (code === 0) resolve9({ stdout, stderr });
66510
+ else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
66511
+ });
66512
+ });
66513
+ }
66514
+ async delete(name) {
66515
+ const dir = this.projectDir(name);
66516
+ await import_node_fs51.promises.rm(dir, { recursive: true, force: true });
66517
+ }
66518
+ async updatePort(name, newPort) {
66519
+ if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
66520
+ throw new Error(
66521
+ `port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
66522
+ );
66523
+ }
66524
+ return await this.serializeWrite(name, async () => {
66525
+ const list = await this.list();
66526
+ const target = list.find((p2) => p2.name === name);
66527
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
66528
+ const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
66529
+ if (conflict) {
66530
+ throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
66531
+ }
66532
+ const updated = { ...target, port: newPort };
66533
+ await import_node_fs51.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
66534
+ return updated;
66535
+ });
66536
+ }
66537
+ /**
66538
+ * 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
66539
+ * UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
66540
+ * 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
66541
+ */
66542
+ async setProdUrl(name, url) {
66543
+ return await this.serializeWrite(name, async () => {
66544
+ const list = await this.list();
66545
+ const target = list.find((p2) => p2.name === name);
66546
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
66547
+ const updated = { ...target, prodUrl: url };
66548
+ const validated = ProjectMetadataSchema.safeParse(updated);
66549
+ if (!validated.success) {
66550
+ throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
66551
+ }
66552
+ await import_node_fs51.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
66553
+ return validated.data;
66554
+ });
66555
+ }
66556
+ /**
66557
+ * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
66558
+ * 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
66559
+ * - daemon 收到第一个 ::stage::build marker 时写入
66560
+ * - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
66561
+ * - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
66562
+ * - 只有 daemon 崩才会留残骸
66563
+ * 写入时保留所有其他字段(prodUrl 等不受影响)。
66564
+ */
66565
+ async setPublishJob(name, job) {
66566
+ return await this.serializeWrite(name, async () => {
66567
+ const list = await this.list();
66568
+ const target = list.find((p2) => p2.name === name);
66569
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
66570
+ const updated = { ...target, publishJob: job };
66571
+ const validated = ProjectMetadataSchema.safeParse(updated);
66572
+ if (!validated.success) {
66573
+ throw new Error(`invalid publishJob: ${validated.error.message}`);
66574
+ }
66575
+ await import_node_fs51.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
66576
+ return validated.data;
66577
+ });
66578
+ }
66579
+ /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
66580
+ async clearPublishJob(name) {
66581
+ return await this.serializeWrite(name, async () => {
66582
+ const list = await this.list();
66583
+ const target = list.find((p2) => p2.name === name);
66584
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
66585
+ if (!target.publishJob) return target;
66586
+ const { publishJob: _drop, ...rest } = target;
66587
+ void _drop;
66588
+ const validated = ProjectMetadataSchema.safeParse(rest);
66589
+ if (!validated.success) {
66590
+ throw new Error(`failed to clear publishJob: ${validated.error.message}`);
66591
+ }
66592
+ await import_node_fs51.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
66593
+ return validated.data;
66594
+ });
66595
+ }
66596
+ };
66597
+
66911
66598
  // src/app-builder/dev-server-supervisor.ts
66912
- var import_node_child_process21 = require("child_process");
66599
+ var import_node_child_process20 = require("child_process");
66913
66600
  var import_node_events2 = require("events");
66914
66601
  var DEFAULT_READY_PATTERN = /Local:\s+https?:\/\/|Nest application successfully started|server listening on/i;
66915
66602
  var DevServerSupervisor = class extends import_node_events2.EventEmitter {
@@ -66946,7 +66633,7 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
66946
66633
  tunnelHost: args.tunnelHost,
66947
66634
  devCommand: cmd
66948
66635
  });
66949
- const child = (0, import_node_child_process21.spawn)("sh", ["-c", cmd], {
66636
+ const child = (0, import_node_child_process20.spawn)("sh", ["-c", cmd], {
66950
66637
  cwd: args.cwd,
66951
66638
  env,
66952
66639
  stdio: "pipe",
@@ -67162,6 +66849,460 @@ var DevServerSupervisor = class extends import_node_events2.EventEmitter {
67162
66849
  }
67163
66850
  };
67164
66851
 
66852
+ // src/app-builder-plugin/publish-registry.ts
66853
+ var import_node_crypto21 = require("crypto");
66854
+ var PublishJobRegistry = class {
66855
+ jobs = /* @__PURE__ */ new Map();
66856
+ has(name) {
66857
+ return this.jobs.has(name);
66858
+ }
66859
+ get(name) {
66860
+ return this.jobs.get(name);
66861
+ }
66862
+ /**
66863
+ * args.jobId 仅用于测试注入;生产代码不传,registry 自己生成。
66864
+ * args.now 仅用于测试注入;生产代码不传,用 Date.now()。
66865
+ */
66866
+ create(args) {
66867
+ if (this.jobs.has(args.name)) {
66868
+ throw new Error(`already publishing: ${args.name}`);
66869
+ }
66870
+ const jobId = args.jobId ?? `job-${(0, import_node_crypto21.randomUUID)()}`;
66871
+ this.jobs.set(args.name, {
66872
+ jobId,
66873
+ name: args.name,
66874
+ stage: "build",
66875
+ sessionId: args.sessionId,
66876
+ childProc: args.childProc,
66877
+ dismissed: false,
66878
+ sawAnyStage: false,
66879
+ startedAt: args.now ?? Date.now()
66880
+ });
66881
+ return jobId;
66882
+ }
66883
+ updateStage(name, stage) {
66884
+ const job = this.jobs.get(name);
66885
+ if (!job) return;
66886
+ job.stage = stage;
66887
+ job.sawAnyStage = true;
66888
+ }
66889
+ markDismissed(name) {
66890
+ const job = this.jobs.get(name);
66891
+ if (!job) return;
66892
+ job.dismissed = true;
66893
+ }
66894
+ remove(name) {
66895
+ const job = this.jobs.get(name);
66896
+ if (!job) return void 0;
66897
+ this.jobs.delete(name);
66898
+ return { sessionId: job.sessionId };
66899
+ }
66900
+ };
66901
+
66902
+ // src/app-builder-plugin/publish-job-runner.ts
66903
+ var import_node_child_process21 = require("child_process");
66904
+ var import_node_fs52 = require("fs");
66905
+ var import_node_path70 = require("path");
66906
+
66907
+ // src/app-builder-plugin/publish-stage-parser.ts
66908
+ var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
66909
+ var PROD_URL_RE = /^\s*::prod-url::(\S+)\s*$/;
66910
+ var URL_RE = /\bhttps?:\/\/[^\s]+/;
66911
+ function parsePublishLine(line) {
66912
+ const stageMatch = STAGE_RE.exec(line);
66913
+ if (stageMatch) return { kind: "stage", stage: stageMatch[1] };
66914
+ const urlMatch = PROD_URL_RE.exec(line);
66915
+ if (urlMatch) return { kind: "prod-url", url: urlMatch[1] };
66916
+ return null;
66917
+ }
66918
+ function extractProdUrl(text) {
66919
+ const m2 = URL_RE.exec(text);
66920
+ return m2 ? m2[0] : null;
66921
+ }
66922
+ function tailStderrLines(buf, n) {
66923
+ if (!buf) return "";
66924
+ const lines = buf.split("\n");
66925
+ if (lines.length <= n) return buf;
66926
+ return lines.slice(lines.length - n).join("\n");
66927
+ }
66928
+
66929
+ // src/app-builder-plugin/publish-job-runner.ts
66930
+ async function startPublishJob(deps, args) {
66931
+ const { registry: registry2, projectDir } = deps;
66932
+ const spawn13 = deps.spawnImpl ?? import_node_child_process21.spawn;
66933
+ if (registry2.has(args.name)) {
66934
+ return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
66935
+ }
66936
+ const projDir = projectDir(args.name);
66937
+ const logPath = (0, import_node_path70.join)(projDir, ".publish.log");
66938
+ let logStream = null;
66939
+ try {
66940
+ logStream = (0, import_node_fs52.createWriteStream)(logPath, { flags: "w" });
66941
+ } catch {
66942
+ logStream = null;
66943
+ }
66944
+ const child = spawn13("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
66945
+ cwd: projDir,
66946
+ env: process.env,
66947
+ stdio: ["ignore", "pipe", "pipe"]
66948
+ });
66949
+ const jobId = registry2.create({
66950
+ name: args.name,
66951
+ sessionId: args.sessionId,
66952
+ childProc: child
66953
+ });
66954
+ deps.logger?.info("app-builder.publish.spawned", {
66955
+ name: args.name,
66956
+ jobId,
66957
+ pid: child.pid ?? null,
66958
+ scriptPath: args.scriptPath,
66959
+ logPath
66960
+ });
66961
+ child.on("error", (err) => {
66962
+ deps.logger?.warn("app-builder.publish.spawn-error", {
66963
+ name: args.name,
66964
+ jobId,
66965
+ error: err instanceof Error ? err.message : String(err)
66966
+ });
66967
+ void handleExit(deps, {
66968
+ name: args.name,
66969
+ jobId,
66970
+ code: null,
66971
+ signal: null,
66972
+ urlCandidate: null,
66973
+ stderrTail: err instanceof Error ? err.message : String(err),
66974
+ logPath
66975
+ });
66976
+ });
66977
+ let urlFromMarker = null;
66978
+ let urlFallback = null;
66979
+ let stdoutBuf = "";
66980
+ child.stdout?.on("data", (chunk) => {
66981
+ if (logStream) logStream.write(chunk);
66982
+ stdoutBuf += chunk.toString();
66983
+ let nlIdx;
66984
+ while ((nlIdx = stdoutBuf.indexOf("\n")) >= 0) {
66985
+ const line = stdoutBuf.slice(0, nlIdx);
66986
+ stdoutBuf = stdoutBuf.slice(nlIdx + 1);
66987
+ handleStdoutLine(deps, args.name, jobId, line);
66988
+ const parsedForUrl = parsePublishLine(line);
66989
+ if (parsedForUrl?.kind === "prod-url") urlFromMarker = parsedForUrl.url;
66990
+ const url = extractProdUrl(line);
66991
+ if (url && !urlFallback) urlFallback = url;
66992
+ }
66993
+ });
66994
+ let stderrBuf = "";
66995
+ child.stderr?.on("data", (chunk) => {
66996
+ if (logStream) logStream.write(chunk);
66997
+ stderrBuf += chunk.toString();
66998
+ });
66999
+ child.on("exit", (code, signal) => {
67000
+ if (logStream) logStream.end();
67001
+ const urlCandidate = urlFromMarker ?? urlFallback;
67002
+ deps.logger?.info("app-builder.publish.child-exit", {
67003
+ name: args.name,
67004
+ jobId,
67005
+ code,
67006
+ signal,
67007
+ hadUrl: !!urlCandidate,
67008
+ urlSource: urlFromMarker ? "marker" : urlFallback ? "fallback" : "none",
67009
+ stderrBytes: stderrBuf.length
67010
+ });
67011
+ void handleExit(deps, {
67012
+ name: args.name,
67013
+ jobId,
67014
+ code,
67015
+ signal,
67016
+ urlCandidate,
67017
+ stderrTail: tailStderrLines(stderrBuf, 20),
67018
+ logPath
67019
+ });
67020
+ });
67021
+ return { jobId, status: "started" };
67022
+ }
67023
+ function handleStdoutLine(deps, name, jobId, line) {
67024
+ const parsed = parsePublishLine(line);
67025
+ if (parsed?.kind !== "stage") return;
67026
+ advanceStage(deps, name, jobId, parsed.stage);
67027
+ }
67028
+ function advanceStage(deps, name, jobId, next) {
67029
+ const job = deps.registry.get(name);
67030
+ if (!job) return;
67031
+ const prev = job.stage;
67032
+ const wasFirstStage = !job.sawAnyStage;
67033
+ deps.logger?.info("app-builder.publish.stage-advance", {
67034
+ name,
67035
+ jobId,
67036
+ prev: wasFirstStage ? null : prev,
67037
+ next,
67038
+ firstStage: wasFirstStage
67039
+ });
67040
+ if (wasFirstStage) {
67041
+ deps.registry.updateStage(name, next);
67042
+ void deps.setPublishJob(name, {
67043
+ jobId,
67044
+ stage: next,
67045
+ startedAt: job.startedAt
67046
+ });
67047
+ deps.broadcast({
67048
+ type: "appBuilder:publish-progress",
67049
+ name,
67050
+ jobId,
67051
+ stage: next,
67052
+ status: "started"
67053
+ });
67054
+ return;
67055
+ }
67056
+ if (prev === next) {
67057
+ return;
67058
+ }
67059
+ deps.broadcast({
67060
+ type: "appBuilder:publish-progress",
67061
+ name,
67062
+ jobId,
67063
+ stage: prev,
67064
+ status: "completed"
67065
+ });
67066
+ deps.registry.updateStage(name, next);
67067
+ void deps.setPublishJob(name, {
67068
+ jobId,
67069
+ stage: next,
67070
+ startedAt: job.startedAt
67071
+ });
67072
+ deps.broadcast({
67073
+ type: "appBuilder:publish-progress",
67074
+ name,
67075
+ jobId,
67076
+ stage: next,
67077
+ status: "started"
67078
+ });
67079
+ }
67080
+ async function handleExit(deps, args) {
67081
+ const job = deps.registry.get(args.name);
67082
+ if (args.signal === "SIGTERM" && job?.dismissed) {
67083
+ try {
67084
+ await deps.clearPublishJob(args.name);
67085
+ } catch (err) {
67086
+ console.error("[publish] clearPublishJob failed (dismissed branch)", err);
67087
+ }
67088
+ deps.registry.remove(args.name);
67089
+ deps.onSettled?.({ kind: "dismissed" });
67090
+ return;
67091
+ }
67092
+ if (args.code === 0 && args.urlCandidate) {
67093
+ if (job?.sawAnyStage) {
67094
+ deps.broadcast({
67095
+ type: "appBuilder:publish-progress",
67096
+ name: args.name,
67097
+ jobId: args.jobId,
67098
+ stage: job.stage,
67099
+ status: "completed"
67100
+ });
67101
+ }
67102
+ try {
67103
+ await deps.setProdUrl(args.name, args.urlCandidate);
67104
+ } catch (err) {
67105
+ console.error("[publish] setProdUrl failed; falling through to failure branch", err);
67106
+ await emitFailure(deps, args, "\u8C03 setProdUrl \u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)));
67107
+ return;
67108
+ }
67109
+ try {
67110
+ await deps.clearPublishJob(args.name);
67111
+ } catch (err) {
67112
+ console.error("[publish] clearPublishJob failed (success branch)", err);
67113
+ }
67114
+ deps.registry.remove(args.name);
67115
+ deps.onSettled?.({ kind: "published", url: args.urlCandidate });
67116
+ return;
67117
+ }
67118
+ await emitFailure(deps, args, buildErrorSummary(args));
67119
+ }
67120
+ async function emitFailure(deps, args, errorSummary) {
67121
+ const job = deps.registry.get(args.name);
67122
+ const failStage = job?.sawAnyStage ? job.stage : "unknown";
67123
+ deps.logger?.warn("app-builder.publish.failed", {
67124
+ name: args.name,
67125
+ jobId: args.jobId,
67126
+ stage: failStage,
67127
+ errorSummary: errorSummary.slice(0, 500)
67128
+ });
67129
+ try {
67130
+ await deps.clearPublishJob(args.name);
67131
+ } catch (err) {
67132
+ deps.logger?.warn("app-builder.publish.clear-field-failed", {
67133
+ name: args.name,
67134
+ jobId: args.jobId,
67135
+ error: err instanceof Error ? err.message : String(err)
67136
+ });
67137
+ }
67138
+ const removed = deps.registry.remove(args.name);
67139
+ deps.broadcast({
67140
+ type: "appBuilder:publish-failed",
67141
+ name: args.name,
67142
+ jobId: args.jobId,
67143
+ stage: failStage,
67144
+ errorSummary
67145
+ });
67146
+ if (removed) {
67147
+ const msg = formatFailureMessage(failStage, errorSummary, args.logPath);
67148
+ try {
67149
+ await deps.injectUserText(removed.sessionId, msg);
67150
+ deps.logger?.info("app-builder.publish.failure-injected-to-chat", {
67151
+ name: args.name,
67152
+ jobId: args.jobId,
67153
+ sessionId: removed.sessionId
67154
+ });
67155
+ } catch (err) {
67156
+ deps.logger?.warn("app-builder.publish.inject-user-text-failed", {
67157
+ name: args.name,
67158
+ jobId: args.jobId,
67159
+ sessionId: removed.sessionId,
67160
+ error: err instanceof Error ? err.message : String(err)
67161
+ });
67162
+ }
67163
+ } else {
67164
+ deps.logger?.warn("app-builder.publish.no-registry-entry-on-failure", {
67165
+ name: args.name,
67166
+ jobId: args.jobId
67167
+ });
67168
+ }
67169
+ deps.onSettled?.({ kind: "failed", stage: failStage, errorSummary });
67170
+ }
67171
+ function buildErrorSummary(args) {
67172
+ if (args.signal) {
67173
+ return `publish.sh \u88AB\u4E2D\u65AD\uFF08signal: ${args.signal}\uFF09`;
67174
+ }
67175
+ if (args.code === 0 && !args.urlCandidate) {
67176
+ return "publish.sh \u6210\u529F\u9000\u51FA\u4F46\u672A\u8F93\u51FA\u516C\u7F51 URL";
67177
+ }
67178
+ return args.stderrTail || `publish.sh \u5F02\u5E38\u9000\u51FA\uFF08exit code: ${args.code}\uFF09`;
67179
+ }
67180
+ function formatFailureMessage(stage, errorSummary, logPath) {
67181
+ return `\u53D1\u5E03\u5931\u8D25\uFF1A[${stage}] \u9636\u6BB5
67182
+ ${errorSummary}
67183
+
67184
+ \u5B8C\u6574\u65E5\u5FD7\uFF1A${logPath}
67185
+ \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`;
67186
+ }
67187
+ async function recoverInterruptedJobs(deps) {
67188
+ const projects = await deps.listAllProjects();
67189
+ for (const p2 of projects) {
67190
+ if (p2.publishJob) {
67191
+ deps.log(
67192
+ `[publish] found interrupted job ${p2.publishJob.jobId} for ${p2.name}, stage=${p2.publishJob.stage}`
67193
+ );
67194
+ }
67195
+ }
67196
+ }
67197
+
67198
+ // src/app-builder-plugin/core.ts
67199
+ function createPublishService() {
67200
+ const registry2 = new PublishJobRegistry();
67201
+ return {
67202
+ inFlightJobId(name) {
67203
+ return registry2.get(name)?.jobId ?? null;
67204
+ },
67205
+ async start(args) {
67206
+ const { store, hooks, logger } = args;
67207
+ const changed = async (name) => {
67208
+ await hooks?.onProjectChanged?.(name);
67209
+ };
67210
+ return await startPublishJob(
67211
+ {
67212
+ registry: registry2,
67213
+ setPublishJob: async (name, job) => {
67214
+ await store.setPublishJob(name, job);
67215
+ await changed(name);
67216
+ },
67217
+ clearPublishJob: async (name) => {
67218
+ await store.clearPublishJob(name);
67219
+ await changed(name);
67220
+ },
67221
+ setProdUrl: async (name, url) => {
67222
+ await store.setProdUrl(name, url);
67223
+ await changed(name);
67224
+ },
67225
+ broadcast: (frame) => hooks?.broadcast?.(frame),
67226
+ injectUserText: async (sessionId, text) => {
67227
+ await hooks?.injectUserText?.(sessionId, text);
67228
+ },
67229
+ projectDir: (name) => store.projectDir(name),
67230
+ ...hooks?.onSettled ? { onSettled: hooks.onSettled } : {},
67231
+ ...logger ? { logger } : {}
67232
+ },
67233
+ {
67234
+ name: args.name,
67235
+ sessionId: args.sessionId,
67236
+ scriptPath: args.scriptPath,
67237
+ personaRoot: args.personaRoot
67238
+ }
67239
+ );
67240
+ },
67241
+ dismiss(name) {
67242
+ const job = registry2.get(name);
67243
+ if (!job) return false;
67244
+ registry2.markDismissed(name);
67245
+ job.childProc.kill("SIGTERM");
67246
+ return true;
67247
+ }
67248
+ };
67249
+ }
67250
+ async function createProjectOnDisk(args) {
67251
+ const { store, name, templateSrcDir, scaffoldScript, logger } = args;
67252
+ const project = await store.create(name, args.reservedPorts);
67253
+ try {
67254
+ const result = await store.scaffold(name, templateSrcDir, scaffoldScript);
67255
+ logger?.info("app-builder.scaffold.done", { name, stdout: result.stdout.slice(0, 200) });
67256
+ } catch (err) {
67257
+ const message = err instanceof Error ? err.message : String(err);
67258
+ logger?.warn("app-builder.scaffold.failed", { name, error: message });
67259
+ await store.delete(name).catch((delErr) => {
67260
+ logger?.warn("app-builder.scaffold.rollback-failed", {
67261
+ name,
67262
+ error: delErr instanceof Error ? delErr.message : String(delErr)
67263
+ });
67264
+ });
67265
+ throw new Error(`scaffold failed: ${message}`);
67266
+ }
67267
+ return { project, projectDir: store.projectDir(name) };
67268
+ }
67269
+ async function recoverInterruptedPublishJobs(store, logger) {
67270
+ await recoverInterruptedJobs({
67271
+ listAllProjects: async () => store.list(),
67272
+ log: (msg) => logger?.info(msg)
67273
+ });
67274
+ }
67275
+
67276
+ // src/app-builder-plugin/mcp-config.ts
67277
+ var import_node_fs53 = __toESM(require("fs"), 1);
67278
+ var import_node_path71 = __toESM(require("path"), 1);
67279
+ function appBuilderMcpConfigPath(dataDir) {
67280
+ return import_node_path71.default.join(dataDir, "app-builder.mcp.json");
67281
+ }
67282
+ function writeAppBuilderMcpConfig(args) {
67283
+ const cfgPath = appBuilderMcpConfigPath(args.dataDir);
67284
+ const cfg = {
67285
+ _comment: "daemon \u542F\u52A8\u81EA\u52A8\u751F\u6210\uFF1Bagent spawn \u901A\u8FC7 --mcp-config \u6CE8\u5165\u3002app-builder-plugin/mcp-server.cjs \u662F clawd-app-builder \u63D2\u4EF6\uFF08app-builder persona \u79C1\u6709\u7684\u540E\u7AEF\uFF1Aproject \u76EE\u5F55/\u5143\u6570\u636E + \u53D1\u5E03\u6D41\u6C34\u7EBF\uFF09\u3002\u6709 CLAWD_DAEMON_URL \u65F6\u8F6C\u8C03 appBuilder:* RPC\uFF08\u7AEF\u53E3\u6C60 / \u4F1A\u8BDD\u7ED1\u5B9A / UI \u5E7F\u64AD\u5F52 daemon\uFF09\uFF0C\u6CA1\u6709\u65F6\u7528\u540C\u4E00\u4EFD\u5185\u6838\u81EA\u5DF1\u5E72\uFF08\u4E91\u4E0A\u5BB9\u5668\uFF09\u3002\u975E app-builder \u7CFB persona \u7684\u4F1A\u8BDD\u91CC\u672C server \u4E0D\u6CE8\u518C\u4EFB\u4F55 tool\u3002",
67286
+ mcpServers: {
67287
+ "clawd-app-builder": {
67288
+ type: "stdio",
67289
+ command: "node",
67290
+ args: [args.serverScriptPath],
67291
+ env: {
67292
+ CLAWD_DAEMON_TOKEN: args.daemonToken,
67293
+ CLAWD_DAEMON_URL: args.daemonUrl,
67294
+ CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT: import_node_path71.default.join(args.deployKitScriptsDir, "new-extension.sh"),
67295
+ CLAWD_APP_BUILDER_PUBLISH_SCRIPT: import_node_path71.default.join(args.deployKitScriptsDir, "publish.sh"),
67296
+ ...args.logPath ? { CLAWD_APP_BUILDER_LOG: args.logPath } : {}
67297
+ }
67298
+ }
67299
+ }
67300
+ };
67301
+ import_node_fs53.default.mkdirSync(args.dataDir, { recursive: true });
67302
+ import_node_fs53.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
67303
+ return cfgPath;
67304
+ }
67305
+
67165
67306
  // src/handlers/dispatch-rpc.ts
67166
67307
  init_zod();
67167
67308
  init_protocol();
@@ -67199,7 +67340,7 @@ async function dispatchRpc(method, frame, client, ctx, deps) {
67199
67340
 
67200
67341
  // src/extension/runtime.ts
67201
67342
  var import_node_child_process22 = require("child_process");
67202
- var import_node_path71 = __toESM(require("path"), 1);
67343
+ var import_node_path72 = __toESM(require("path"), 1);
67203
67344
  var import_promises18 = require("timers/promises");
67204
67345
 
67205
67346
  // src/extension/port-allocator.ts
@@ -67300,7 +67441,7 @@ var Runtime = class {
67300
67441
  /\$CLAWOS_EXT_PORT/g,
67301
67442
  String(port)
67302
67443
  );
67303
- const dir = import_node_path71.default.join(this.root, extId);
67444
+ const dir = import_node_path72.default.join(this.root, extId);
67304
67445
  const env = {
67305
67446
  ...process.env,
67306
67447
  CLAWOS_EXT_PORT: String(port),
@@ -67412,7 +67553,7 @@ ${handle.stderrTail}`
67412
67553
 
67413
67554
  // src/extension/published-channels.ts
67414
67555
  var import_promises19 = __toESM(require("fs/promises"), 1);
67415
- var import_node_path72 = __toESM(require("path"), 1);
67556
+ var import_node_path73 = __toESM(require("path"), 1);
67416
67557
  init_zod();
67417
67558
  var PublishedChannelsError = class extends Error {
67418
67559
  constructor(code, message) {
@@ -67511,7 +67652,7 @@ var PublishedChannelStore = class {
67511
67652
  )
67512
67653
  };
67513
67654
  const tmp = `${this.filePath}.tmp`;
67514
- await import_promises19.default.mkdir(import_node_path72.default.dirname(this.filePath), { recursive: true });
67655
+ await import_promises19.default.mkdir(import_node_path73.default.dirname(this.filePath), { recursive: true });
67515
67656
  await import_promises19.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
67516
67657
  await import_promises19.default.rename(tmp, this.filePath);
67517
67658
  }
@@ -67519,7 +67660,7 @@ var PublishedChannelStore = class {
67519
67660
 
67520
67661
  // src/extension/bundle-cache.ts
67521
67662
  var import_promises20 = __toESM(require("fs/promises"), 1);
67522
- var import_node_path73 = __toESM(require("path"), 1);
67663
+ var import_node_path74 = __toESM(require("path"), 1);
67523
67664
  var BundleCache = class {
67524
67665
  constructor(rootDir) {
67525
67666
  this.rootDir = rootDir;
@@ -67528,14 +67669,14 @@ var BundleCache = class {
67528
67669
  /** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
67529
67670
  async write(snapshotHash, buffer) {
67530
67671
  await import_promises20.default.mkdir(this.rootDir, { recursive: true });
67531
- const file = import_node_path73.default.join(this.rootDir, `${snapshotHash}.zip`);
67672
+ const file = import_node_path74.default.join(this.rootDir, `${snapshotHash}.zip`);
67532
67673
  const tmp = `${file}.tmp`;
67533
67674
  await import_promises20.default.writeFile(tmp, buffer, { mode: 384 });
67534
67675
  await import_promises20.default.rename(tmp, file);
67535
67676
  }
67536
67677
  /** Returns the bundle bytes, or null when the file doesn't exist. */
67537
67678
  async read(snapshotHash) {
67538
- const file = import_node_path73.default.join(this.rootDir, `${snapshotHash}.zip`);
67679
+ const file = import_node_path74.default.join(this.rootDir, `${snapshotHash}.zip`);
67539
67680
  try {
67540
67681
  return await import_promises20.default.readFile(file);
67541
67682
  } catch (e) {
@@ -67545,7 +67686,7 @@ var BundleCache = class {
67545
67686
  }
67546
67687
  /** Idempotent — missing file is not an error. */
67547
67688
  async delete(snapshotHash) {
67548
- const file = import_node_path73.default.join(this.rootDir, `${snapshotHash}.zip`);
67689
+ const file = import_node_path74.default.join(this.rootDir, `${snapshotHash}.zip`);
67549
67690
  await import_promises20.default.rm(file, { force: true });
67550
67691
  }
67551
67692
  };
@@ -67568,24 +67709,24 @@ async function startDaemon(config) {
67568
67709
  sampling: logShippingCfg.sampling,
67569
67710
  homeDir: import_node_os26.default.homedir()
67570
67711
  });
67571
- const logDir = import_node_path74.default.join(config.dataDir, "log");
67572
- import_node_fs53.default.mkdirSync(logDir, { recursive: true });
67712
+ const logDir = import_node_path75.default.join(config.dataDir, "log");
67713
+ import_node_fs54.default.mkdirSync(logDir, { recursive: true });
67573
67714
  const logger = createLogger({
67574
67715
  level: config.logLevel,
67575
- file: import_node_path74.default.join(logDir, "clawd.log"),
67716
+ file: import_node_path75.default.join(logDir, "clawd.log"),
67576
67717
  logClient
67577
67718
  });
67578
67719
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
67579
67720
  const screenIdleProbeLogger = createFileOnlyLogger({
67580
- file: import_node_path74.default.join(logDir, "screen-idle-probe.log"),
67721
+ file: import_node_path75.default.join(logDir, "screen-idle-probe.log"),
67581
67722
  level: "debug"
67582
67723
  });
67583
67724
  logger.info("screen-idle probe logger enabled", {
67584
- file: import_node_path74.default.join(logDir, "screen-idle-probe.log")
67725
+ file: import_node_path75.default.join(logDir, "screen-idle-probe.log")
67585
67726
  });
67586
67727
  const CAP_TARGETS = [
67587
- import_node_path74.default.join(logDir, "clawd.log"),
67588
- import_node_path74.default.join(logDir, "screen-idle-probe.log")
67728
+ import_node_path75.default.join(logDir, "clawd.log"),
67729
+ import_node_path75.default.join(logDir, "screen-idle-probe.log")
67589
67730
  ];
67590
67731
  const LOG_CAP_MAX_BYTES = 10 * 1024 * 1024;
67591
67732
  const LOG_CAP_KEEP_BYTES = 5 * 1024 * 1024;
@@ -67707,8 +67848,8 @@ async function startDaemon(config) {
67707
67848
  const agents = new AgentsScanner();
67708
67849
  const history = new ClaudeHistoryReader();
67709
67850
  let transport = null;
67710
- const personaStore = new PersonaStore(import_node_path74.default.join(config.dataDir, "personas"));
67711
- const usersRoot = import_node_path74.default.join(config.dataDir, "users");
67851
+ const personaStore = new PersonaStore(import_node_path75.default.join(config.dataDir, "personas"));
67852
+ const usersRoot = import_node_path75.default.join(config.dataDir, "users");
67712
67853
  const defaultsRoot = findDefaultsRoot(logger);
67713
67854
  if (defaultsRoot) {
67714
67855
  seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
@@ -67732,7 +67873,7 @@ async function startDaemon(config) {
67732
67873
  retirePersonaBuilder({ store: personaStore, logger });
67733
67874
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
67734
67875
  const dispatchStore = createDispatchStore({
67735
- filePath: import_node_path74.default.join(config.dataDir, "dispatch.json"),
67876
+ filePath: import_node_path75.default.join(config.dataDir, "dispatch.json"),
67736
67877
  now: () => Date.now(),
67737
67878
  onFlushError: (err) => logger.error("dispatch store flush failed", {
67738
67879
  reason: err instanceof Error ? err.message : String(err)
@@ -67741,7 +67882,7 @@ async function startDaemon(config) {
67741
67882
  await dispatchStore.load();
67742
67883
  dispatchStore.markInterruptedAsFailed((/* @__PURE__ */ new Date()).toISOString());
67743
67884
  const channelStore = createChannelStore({
67744
- filePath: import_node_path74.default.join(config.dataDir, "channels.json"),
67885
+ filePath: import_node_path75.default.join(config.dataDir, "channels.json"),
67745
67886
  now: () => Date.now(),
67746
67887
  genId: () => `ch-${(0, import_node_crypto22.randomUUID)().slice(0, 8)}`,
67747
67888
  onFlushError: (err) => logger.error("channel store flush failed", {
@@ -67750,7 +67891,7 @@ async function startDaemon(config) {
67750
67891
  });
67751
67892
  await channelStore.load();
67752
67893
  const topicStore = createTopicStore({
67753
- filePath: import_node_path74.default.join(config.dataDir, "topics.json"),
67894
+ filePath: import_node_path75.default.join(config.dataDir, "topics.json"),
67754
67895
  now: () => Date.now(),
67755
67896
  genId: () => `tp-${(0, import_node_crypto22.randomUUID)().slice(0, 8)}`,
67756
67897
  onFlushError: (err) => logger.error("topic store flush failed", {
@@ -67777,18 +67918,18 @@ async function startDaemon(config) {
67777
67918
  });
67778
67919
  }
67779
67920
  });
67780
- const here = typeof __dirname === "string" ? __dirname : import_node_path74.default.dirname((0, import_node_url4.fileURLToPath)(import_meta7.url));
67921
+ const here = typeof __dirname === "string" ? __dirname : import_node_path75.default.dirname((0, import_node_url4.fileURLToPath)(import_meta7.url));
67781
67922
  const mcpConfigs = [];
67782
67923
  const mcpDaemonUrl = `http://127.0.0.1:${config.port}`;
67783
67924
  const dispatchServerCandidates = [
67784
- import_node_path74.default.join(here, "dispatch", "mcp-server.cjs"),
67925
+ import_node_path75.default.join(here, "dispatch", "mcp-server.cjs"),
67785
67926
  // 生产 dist/index → dist/dispatch/mcp-server.cjs
67786
- import_node_path74.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
67927
+ import_node_path75.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
67787
67928
  // dev tsx src/index → ../dist/dispatch/mcp-server.cjs
67788
67929
  ];
67789
- const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs53.default.existsSync(p2));
67930
+ const dispatchServerScriptPath = dispatchServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
67790
67931
  if (dispatchServerScriptPath) {
67791
- const dispatchLogPath = import_node_path74.default.join(logDir, "dispatch-mcp-server.log");
67932
+ const dispatchLogPath = import_node_path75.default.join(logDir, "dispatch-mcp-server.log");
67792
67933
  const dispatchCfgPath = writeDispatchMcpConfig({
67793
67934
  dataDir: config.dataDir,
67794
67935
  serverScriptPath: dispatchServerScriptPath,
@@ -67808,12 +67949,12 @@ async function startDaemon(config) {
67808
67949
  });
67809
67950
  }
67810
67951
  const ticketServerCandidates = [
67811
- import_node_path74.default.join(here, "ticket", "mcp-server.cjs"),
67812
- import_node_path74.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
67952
+ import_node_path75.default.join(here, "ticket", "mcp-server.cjs"),
67953
+ import_node_path75.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
67813
67954
  ];
67814
- const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs53.default.existsSync(p2));
67955
+ const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
67815
67956
  if (ticketServerScriptPath) {
67816
- const ticketLogPath = import_node_path74.default.join(logDir, "ticket-mcp-server.log");
67957
+ const ticketLogPath = import_node_path75.default.join(logDir, "ticket-mcp-server.log");
67817
67958
  const ticketCfgPath = writeTicketMcpConfig({
67818
67959
  dataDir: config.dataDir,
67819
67960
  serverScriptPath: ticketServerScriptPath,
@@ -67835,12 +67976,12 @@ async function startDaemon(config) {
67835
67976
  });
67836
67977
  }
67837
67978
  const shiftServerCandidates = [
67838
- import_node_path74.default.join(here, "shift", "mcp-server.cjs"),
67839
- import_node_path74.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
67979
+ import_node_path75.default.join(here, "shift", "mcp-server.cjs"),
67980
+ import_node_path75.default.join(here, "..", "dist", "shift", "mcp-server.cjs")
67840
67981
  ];
67841
- const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs53.default.existsSync(p2));
67982
+ const shiftServerScriptPath = shiftServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
67842
67983
  if (shiftServerScriptPath) {
67843
- const shiftLogPath = import_node_path74.default.join(logDir, "shift-mcp-server.log");
67984
+ const shiftLogPath = import_node_path75.default.join(logDir, "shift-mcp-server.log");
67844
67985
  const shiftCfgPath = await writeShiftMcpConfig({
67845
67986
  dataDir: config.dataDir,
67846
67987
  serverScriptPath: shiftServerScriptPath,
@@ -67861,12 +68002,12 @@ async function startDaemon(config) {
67861
68002
  );
67862
68003
  }
67863
68004
  const inboxServerCandidates = [
67864
- import_node_path74.default.join(here, "inbox", "mcp-server.cjs"),
67865
- import_node_path74.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
68005
+ import_node_path75.default.join(here, "inbox", "mcp-server.cjs"),
68006
+ import_node_path75.default.join(here, "..", "dist", "inbox", "mcp-server.cjs")
67866
68007
  ];
67867
- const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs53.default.existsSync(p2));
68008
+ const inboxServerScriptPath = inboxServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
67868
68009
  if (inboxServerScriptPath) {
67869
- const inboxLogPath = import_node_path74.default.join(logDir, "inbox-mcp-server.log");
68010
+ const inboxLogPath = import_node_path75.default.join(logDir, "inbox-mcp-server.log");
67870
68011
  const inboxCfgPath = await writeInboxMcpConfig({
67871
68012
  dataDir: config.dataDir,
67872
68013
  serverScriptPath: inboxServerScriptPath,
@@ -67887,10 +68028,10 @@ async function startDaemon(config) {
67887
68028
  );
67888
68029
  }
67889
68030
  const peerOpsServerCandidates = [
67890
- import_node_path74.default.join(here, "peer-ops", "mcp-server.cjs"),
67891
- import_node_path74.default.join(here, "..", "dist", "peer-ops", "mcp-server.cjs")
68031
+ import_node_path75.default.join(here, "peer-ops", "mcp-server.cjs"),
68032
+ import_node_path75.default.join(here, "..", "dist", "peer-ops", "mcp-server.cjs")
67892
68033
  ];
67893
- const peerOpsServerScriptPath = peerOpsServerCandidates.find((p2) => import_node_fs53.default.existsSync(p2));
68034
+ const peerOpsServerScriptPath = peerOpsServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
67894
68035
  if (peerOpsServerScriptPath) {
67895
68036
  const peerOpsCfgPath = writePeerOpsMcpConfig({
67896
68037
  dataDir: config.dataDir,
@@ -67910,12 +68051,12 @@ async function startDaemon(config) {
67910
68051
  );
67911
68052
  }
67912
68053
  const rpcToolServerCandidates = [
67913
- import_node_path74.default.join(here, "rpc-tool", "mcp-server.cjs"),
67914
- import_node_path74.default.join(here, "..", "dist", "rpc-tool", "mcp-server.cjs")
68054
+ import_node_path75.default.join(here, "rpc-tool", "mcp-server.cjs"),
68055
+ import_node_path75.default.join(here, "..", "dist", "rpc-tool", "mcp-server.cjs")
67915
68056
  ];
67916
- const rpcToolServerScriptPath = rpcToolServerCandidates.find((p2) => import_node_fs53.default.existsSync(p2));
68057
+ const rpcToolServerScriptPath = rpcToolServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
67917
68058
  if (rpcToolServerScriptPath) {
67918
- const rpcToolLogPath = import_node_path74.default.join(logDir, "rpc-tool-mcp-server.log");
68059
+ const rpcToolLogPath = import_node_path75.default.join(logDir, "rpc-tool-mcp-server.log");
67919
68060
  const rpcToolCfgPath = writeRpcToolMcpConfig({
67920
68061
  dataDir: config.dataDir,
67921
68062
  serverScriptPath: rpcToolServerScriptPath,
@@ -67935,8 +68076,35 @@ async function startDaemon(config) {
67935
68076
  { tried: rpcToolServerCandidates }
67936
68077
  );
67937
68078
  }
68079
+ const appBuilderServerCandidates = [
68080
+ import_node_path75.default.join(here, "app-builder-plugin", "mcp-server.cjs"),
68081
+ import_node_path75.default.join(here, "..", "dist", "app-builder-plugin", "mcp-server.cjs")
68082
+ ];
68083
+ const appBuilderServerScriptPath = appBuilderServerCandidates.find((p2) => import_node_fs54.default.existsSync(p2));
68084
+ if (appBuilderServerScriptPath) {
68085
+ const appBuilderLogPath = import_node_path75.default.join(logDir, "app-builder-mcp-server.log");
68086
+ const appBuilderCfgPath = writeAppBuilderMcpConfig({
68087
+ dataDir: config.dataDir,
68088
+ serverScriptPath: appBuilderServerScriptPath,
68089
+ daemonToken: resolvedAuthToken,
68090
+ daemonUrl: mcpDaemonUrl,
68091
+ deployKitScriptsDir: import_node_path75.default.join(config.dataDir, "deploy-kit", "scripts"),
68092
+ logPath: appBuilderLogPath
68093
+ });
68094
+ mcpConfigs.push({ id: "app-builder", path: appBuilderCfgPath, personas: APP_BUILDER_PERSONAS });
68095
+ logger.info("app-builder.mcp.json written", {
68096
+ path: appBuilderCfgPath,
68097
+ server: appBuilderServerScriptPath,
68098
+ serverLog: appBuilderLogPath
68099
+ });
68100
+ } else {
68101
+ logger.warn(
68102
+ "app-builder-plugin/mcp-server.cjs not found; clawd-app-builder tools disabled (dev tsx \u6A21\u5F0F\u4E0B\u9700\u5148\u8DD1\u8FC7 build)",
68103
+ { tried: appBuilderServerCandidates }
68104
+ );
68105
+ }
67938
68106
  const shiftStore = createShiftStore({
67939
- filePath: import_node_path74.default.join(config.dataDir, "shift.json"),
68107
+ filePath: import_node_path75.default.join(config.dataDir, "shift.json"),
67940
68108
  ownerIdProvider: () => ownerPrincipalId,
67941
68109
  now: () => Date.now()
67942
68110
  });
@@ -67958,7 +68126,7 @@ async function startDaemon(config) {
67958
68126
  getAdapter,
67959
68127
  historyReader: history,
67960
68128
  dataDir: config.dataDir,
67961
- personaRoot: import_node_path74.default.join(config.dataDir, "personas"),
68129
+ personaRoot: import_node_path75.default.join(config.dataDir, "personas"),
67962
68130
  // dispatch / shift 自动化 spawn 按 persona 绑定 tool 起 B session(codex persona 用 codex)。
67963
68131
  // personaRegistry 声明在 manager 之后(同作用域 const,调用时机在启动完成后,无 TDZ 风险)。
67964
68132
  getPersonaDefaults: (personaId2) => personaRegistry.get(personaId2),
@@ -68012,10 +68180,10 @@ async function startDaemon(config) {
68012
68180
  // 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
68013
68181
  attachmentGroup: {
68014
68182
  onFileEdit: (input) => {
68015
- const absPath = import_node_path74.default.isAbsolute(input.relPath) ? input.relPath : import_node_path74.default.join(input.cwd, input.relPath);
68183
+ const absPath = import_node_path75.default.isAbsolute(input.relPath) ? input.relPath : import_node_path75.default.join(input.cwd, input.relPath);
68016
68184
  let size = 0;
68017
68185
  try {
68018
- size = import_node_fs53.default.statSync(absPath).size;
68186
+ size = import_node_fs54.default.statSync(absPath).size;
68019
68187
  } catch (err) {
68020
68188
  logger.warn("attachment.onFileEdit stat failed", {
68021
68189
  sessionId: input.sessionId,
@@ -68045,9 +68213,9 @@ async function startDaemon(config) {
68045
68213
  logger.info("projects.seed-dropped", { dropped: droppedSeed.dropped, backup: droppedSeed.backup });
68046
68214
  }
68047
68215
  const projectsStore = new ProjectsStore({
68048
- file: import_node_path74.default.join(config.dataDir, "projects.json"),
68216
+ file: import_node_path75.default.join(config.dataDir, "projects.json"),
68049
68217
  clawdDir: config.dataDir,
68050
- codexStateFile: import_node_path74.default.join(import_node_os26.default.homedir(), ".codex", ".codex-global-state.json"),
68218
+ codexStateFile: import_node_path75.default.join(import_node_os26.default.homedir(), ".codex", ".codex-global-state.json"),
68051
68219
  logger
68052
68220
  });
68053
68221
  const observer = new SessionObserver({
@@ -68107,13 +68275,13 @@ async function startDaemon(config) {
68107
68275
  screenIdleProbeLogger
68108
68276
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
68109
68277
  registerAdapter("claude", claudeAdapter);
68110
- const codexIsolatedHome = import_node_path74.default.join(config.dataDir, "codex-home");
68278
+ const codexIsolatedHome = import_node_path75.default.join(config.dataDir, "codex-home");
68111
68279
  try {
68112
- import_node_fs53.default.mkdirSync(codexIsolatedHome, { recursive: true });
68280
+ import_node_fs54.default.mkdirSync(codexIsolatedHome, { recursive: true });
68113
68281
  for (const f of ["auth.json", "config.toml"]) {
68114
- const link = import_node_path74.default.join(codexIsolatedHome, f);
68115
- const target = import_node_path74.default.join(import_node_os26.default.homedir(), ".codex", f);
68116
- if (!import_node_fs53.default.existsSync(link) && import_node_fs53.default.existsSync(target)) import_node_fs53.default.symlinkSync(target, link);
68282
+ const link = import_node_path75.default.join(codexIsolatedHome, f);
68283
+ const target = import_node_path75.default.join(import_node_os26.default.homedir(), ".codex", f);
68284
+ if (!import_node_fs54.default.existsSync(link) && import_node_fs54.default.existsSync(target)) import_node_fs54.default.symlinkSync(target, link);
68117
68285
  }
68118
68286
  } catch (err) {
68119
68287
  logger.warn("codex isolated home setup failed", { err: String(err) });
@@ -68126,11 +68294,11 @@ async function startDaemon(config) {
68126
68294
  // projectPaths 每次调用现取 —— 用户新建 project 后立即生效,不用重启 daemon
68127
68295
  codexHomeFor: (cwd) => resolveCodexHome({
68128
68296
  cwd,
68129
- personaRoot: import_node_path74.default.join(config.dataDir, "personas"),
68297
+ personaRoot: import_node_path75.default.join(config.dataDir, "personas"),
68130
68298
  // 必须用 listMerged(Codex registry ∪ 叠加层):只用本地 list() 的话,用户在
68131
68299
  // Codex 里建的 project 不在表里,在它下面开的会话会被误判成隔离 → Codex 侧看不到
68132
68300
  projectPaths: projectsStore.listMerged().map((x) => x.path),
68133
- sharedHome: import_node_path74.default.join(import_node_os26.default.homedir(), ".codex"),
68301
+ sharedHome: import_node_path75.default.join(import_node_os26.default.homedir(), ".codex"),
68134
68302
  isolatedHome: codexIsolatedHome
68135
68303
  })
68136
68304
  })
@@ -68312,6 +68480,7 @@ async function startDaemon(config) {
68312
68480
  error: err instanceof Error ? err.message : String(err)
68313
68481
  });
68314
68482
  });
68483
+ const appBuilderPublishService = createPublishService();
68315
68484
  const devServerSupervisor = new DevServerSupervisor();
68316
68485
  devServerSupervisor.setLogger(logger);
68317
68486
  const appBuilderPortRange = [];
@@ -68414,11 +68583,11 @@ async function startDaemon(config) {
68414
68583
  // 'persona/<pid>/owner',default 走 'default'。
68415
68584
  getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
68416
68585
  // guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
68417
- personaRoot: import_node_path74.default.join(config.dataDir, "personas"),
68586
+ personaRoot: import_node_path75.default.join(config.dataDir, "personas"),
68418
68587
  usersRoot
68419
68588
  },
68420
68589
  // workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
68421
- personaRoot: import_node_path74.default.join(config.dataDir, "personas"),
68590
+ personaRoot: import_node_path75.default.join(config.dataDir, "personas"),
68422
68591
  // v2 多人 persona 隔离:handler 派生 guest user-dir 放行
68423
68592
  usersRoot,
68424
68593
  // v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
@@ -68499,6 +68668,8 @@ async function startDaemon(config) {
68499
68668
  // app-builder
68500
68669
  appBuilderStore,
68501
68670
  getProjectStore,
68671
+ publishService: appBuilderPublishService,
68672
+ createProjectOnDisk,
68502
68673
  devServerLifecycle: devServerSupervisor,
68503
68674
  // 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
68504
68675
  // 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
@@ -68517,11 +68688,11 @@ async function startDaemon(config) {
68517
68688
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
68518
68689
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
68519
68690
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
68520
- appBuilderPersonaRoot: import_node_path74.default.join(config.dataDir, "personas", "persona-app-builder"),
68691
+ appBuilderPersonaRoot: import_node_path75.default.join(config.dataDir, "personas", "persona-app-builder"),
68521
68692
  // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
68522
- deployKitRoot: import_node_path74.default.join(config.dataDir, "deploy-kit"),
68693
+ deployKitRoot: import_node_path75.default.join(config.dataDir, "deploy-kit"),
68523
68694
  // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
68524
- resolvePersonaRoot: (personaId2) => import_node_path74.default.join(config.dataDir, "personas", personaId2),
68695
+ resolvePersonaRoot: (personaId2) => import_node_path75.default.join(config.dataDir, "personas", personaId2),
68525
68696
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
68526
68697
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
68527
68698
  // 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
@@ -68634,12 +68805,12 @@ async function startDaemon(config) {
68634
68805
  });
68635
68806
  Object.assign(handlers, shiftHandlers);
68636
68807
  const deployReceipts = createReceiptStore({
68637
- filePath: import_node_path74.default.join(config.dataDir, "deploy.json"),
68808
+ filePath: import_node_path75.default.join(config.dataDir, "deploy.json"),
68638
68809
  now: () => Date.now(),
68639
68810
  onFlushError: (err) => logger.error("deploy receipts flush failed", { error: String(err) })
68640
68811
  });
68641
68812
  const deploySecrets = createSecretStore({
68642
- filePath: import_node_path74.default.join(config.dataDir, "deploy-secrets.json"),
68813
+ filePath: import_node_path75.default.join(config.dataDir, "deploy-secrets.json"),
68643
68814
  now: () => Date.now(),
68644
68815
  onFlushError: (err) => logger.error("deploy secrets flush failed", { error: String(err) })
68645
68816
  });
@@ -68653,7 +68824,7 @@ async function startDaemon(config) {
68653
68824
  hasPersona: (personaId2) => personaStore.has(personaId2),
68654
68825
  startScan: (deployId, personaId2) => {
68655
68826
  const assetLines = SCANNABLE_ASSETS.map(
68656
- (name) => ` - ${name}\uFF1A${import_node_path74.default.join(config.dataDir, name)}`
68827
+ (name) => ` - ${name}\uFF1A${import_node_path75.default.join(config.dataDir, name)}`
68657
68828
  ).join("\n");
68658
68829
  const prompt = [
68659
68830
  `\u626B\u63CF persona\u300C${personaId2}\u300D\u7684\u90E8\u7F72\u73AF\u5883\uFF0CdeployId=${deployId}\u3002`,
@@ -68694,7 +68865,7 @@ async function startDaemon(config) {
68694
68865
  });
68695
68866
  shiftScheduler.start();
68696
68867
  logger.info("shift scheduler started", { shiftCount: shiftStore.list().length });
68697
- const larkBotStore = createLarkBotBindingStore(import_node_path74.default.join(config.dataDir, "personas"));
68868
+ const larkBotStore = createLarkBotBindingStore(import_node_path75.default.join(config.dataDir, "personas"));
68698
68869
  const larkBotCloud = createLarkBotCloudClient({
68699
68870
  api: config.clawosApi ?? DEFAULT_CLAWOS_API,
68700
68871
  apiKey: resolveClawdTicketsApiKey(),
@@ -68708,7 +68879,7 @@ async function startDaemon(config) {
68708
68879
  store: larkBotStore,
68709
68880
  fetchResource: (appId, messageId, fileKey, maxBytes, type) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes, type),
68710
68881
  // 图片落 guest userWorkDir 下(capId = lark-<chat_id>):CC 经 --add-dir + allowRead carve 可 Read
68711
- mediaRootFor: (chatId) => import_node_path74.default.join(deriveUserWorkDir(`lark-${chatId}`, usersRoot), "lark-media"),
68882
+ mediaRootFor: (chatId) => import_node_path75.default.join(deriveUserWorkDir(`lark-${chatId}`, usersRoot), "lark-media"),
68712
68883
  isShuttingDown: () => larkDraining,
68713
68884
  logger: {
68714
68885
  warn: (msg) => logger.warn(msg),
@@ -69013,8 +69184,8 @@ async function startDaemon(config) {
69013
69184
  const lines = [
69014
69185
  `Tunnel: ${r.url}`,
69015
69186
  ...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
69016
- `Frpc config: ${import_node_path74.default.join(config.dataDir, "frpc.toml")}`,
69017
- `Frpc log: ${import_node_path74.default.join(logDir, "frpc.log")}`
69187
+ `Frpc config: ${import_node_path75.default.join(config.dataDir, "frpc.toml")}`,
69188
+ `Frpc log: ${import_node_path75.default.join(logDir, "frpc.log")}`
69018
69189
  ];
69019
69190
  const width2 = Math.max(...lines.map((l) => l.length));
69020
69191
  const bar = "\u2550".repeat(width2 + 4);
@@ -69027,8 +69198,8 @@ ${bar}
69027
69198
 
69028
69199
  `);
69029
69200
  try {
69030
- const connectPath = import_node_path74.default.join(config.dataDir, "connect.txt");
69031
- import_node_fs53.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
69201
+ const connectPath = import_node_path75.default.join(config.dataDir, "connect.txt");
69202
+ import_node_fs54.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
69032
69203
  } catch {
69033
69204
  }
69034
69205
  } catch (err) {
@@ -69117,9 +69288,9 @@ ${bar}
69117
69288
  };
69118
69289
  }
69119
69290
  function migrateDropPersonsDir(dataDir) {
69120
- const dir = import_node_path74.default.join(dataDir, "persons");
69291
+ const dir = import_node_path75.default.join(dataDir, "persons");
69121
69292
  try {
69122
- import_node_fs53.default.rmSync(dir, { recursive: true, force: true });
69293
+ import_node_fs54.default.rmSync(dir, { recursive: true, force: true });
69123
69294
  } catch {
69124
69295
  }
69125
69296
  }