@clawos-dev/clawd 0.2.105-beta.200.ef901dc → 0.2.105-beta.201.e3d63e9

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +208 -132
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -5367,6 +5367,20 @@ var init_received_capability = __esm({
5367
5367
  });
5368
5368
 
5369
5369
  // ../protocol/src/extension.ts
5370
+ function isAllowedHostedUrl(raw) {
5371
+ let u;
5372
+ try {
5373
+ u = new URL(raw);
5374
+ } catch {
5375
+ return false;
5376
+ }
5377
+ if (u.protocol === "https:") return true;
5378
+ if (u.protocol === "http:" && LOCALHOST_HOSTS.has(u.hostname)) return true;
5379
+ return false;
5380
+ }
5381
+ function manifestMode(m2) {
5382
+ return m2.entry?.url ? "hosted" : "local";
5383
+ }
5370
5384
  function ownerNamespace(ownerPrincipalId) {
5371
5385
  let h = 2166136261;
5372
5386
  for (let i = 0; i < ownerPrincipalId.length; i++) {
@@ -5408,7 +5422,7 @@ function parseOrThrow(v2) {
5408
5422
  const [a, b2, c] = v2.split(".").map((p2) => parseInt(p2, 10));
5409
5423
  return [a, b2, c];
5410
5424
  }
5411
- var EXT_ID_REGEX, ExtensionStateSchema, ExtensionManifestSchema, ExtensionRecordSchema, PublishedSnapshotSchema, ChannelRefSchema, SourceRefSchema, PublishedExtensionRecordSchema, PublishCheckSchema, PublishStatusResultSchema;
5425
+ var EXT_ID_REGEX, ExtensionStateSchema, LOCALHOST_HOSTS, ExtensionManifestSchema, ExtensionTargetSchema, ExtensionRecordSchema, PublishedSnapshotSchema, ChannelRefSchema, SourceRefSchema, PublishedExtensionRecordSchema, PublishCheckSchema, PublishStatusResultSchema;
5412
5426
  var init_extension = __esm({
5413
5427
  "../protocol/src/extension.ts"() {
5414
5428
  "use strict";
@@ -5421,18 +5435,38 @@ var init_extension = __esm({
5421
5435
  "crashed",
5422
5436
  "invalid"
5423
5437
  ]);
5438
+ LOCALHOST_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1"]);
5424
5439
  ExtensionManifestSchema = external_exports.object({
5425
5440
  id: external_exports.string().regex(EXT_ID_REGEX),
5426
5441
  name: external_exports.string().min(1),
5427
5442
  version: external_exports.string().min(1),
5428
5443
  apiVersion: external_exports.literal("1"),
5444
+ runtime: external_exports.object({ startCommand: external_exports.string().min(1) }).passthrough().optional(),
5429
5445
  entry: external_exports.object({
5430
- ui: external_exports.string().min(1).optional()
5431
- }).strict().optional(),
5432
- runtime: external_exports.object({
5433
- startCommand: external_exports.string().min(1)
5434
- }).passthrough()
5435
- }).passthrough();
5446
+ url: external_exports.string().url().refine(isAllowedHostedUrl, {
5447
+ message: "INVALID_URL_SCHEME: only https or http://localhost is allowed"
5448
+ })
5449
+ }).strict().optional()
5450
+ }).passthrough().superRefine((m2, ctx) => {
5451
+ const hasRuntime = !!m2.runtime;
5452
+ const hasUrl = !!m2.entry?.url;
5453
+ if (hasRuntime && hasUrl) {
5454
+ ctx.addIssue({
5455
+ code: external_exports.ZodIssueCode.custom,
5456
+ message: "INVALID_MANIFEST: entry.url and runtime cannot coexist"
5457
+ });
5458
+ }
5459
+ if (!hasRuntime && !hasUrl) {
5460
+ ctx.addIssue({
5461
+ code: external_exports.ZodIssueCode.custom,
5462
+ message: "INVALID_MANIFEST: must have either entry.url or runtime"
5463
+ });
5464
+ }
5465
+ });
5466
+ ExtensionTargetSchema = external_exports.discriminatedUnion("kind", [
5467
+ external_exports.object({ kind: external_exports.literal("local"), port: external_exports.number().int().min(1).max(65535) }),
5468
+ external_exports.object({ kind: external_exports.literal("hosted"), url: external_exports.string().url() })
5469
+ ]);
5436
5470
  ExtensionRecordSchema = external_exports.discriminatedUnion("state", [
5437
5471
  external_exports.object({
5438
5472
  extId: external_exports.string(),
@@ -5448,7 +5482,7 @@ var init_extension = __esm({
5448
5482
  extId: external_exports.string(),
5449
5483
  manifest: ExtensionManifestSchema,
5450
5484
  state: external_exports.literal("running"),
5451
- port: external_exports.number().int().min(1).max(65535)
5485
+ target: ExtensionTargetSchema
5452
5486
  }),
5453
5487
  external_exports.object({
5454
5488
  extId: external_exports.string(),
@@ -32546,11 +32580,12 @@ var SessionManager = class {
32546
32580
  // 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
32547
32581
  // 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
32548
32582
  ptyReplaysByToolSid = /* @__PURE__ */ new Map();
32549
- // TUI 模式:toolSessionId → @xterm/headless PopupDetector。
32550
- // ClaudeTuiAdapter spawn 时通过 onDetectorRegister callback 写入;exit 时 onDetectorUnregister 清理。
32551
- // manager.resizePty 用此映射把 UI session:pty:resize 同步到 detector 内部 buffer,
32583
+ // TUI 模式:toolSessionId → @xterm/headless TerminalSurface(单一虚拟终端,popup / ready
32584
+ // observer 都从这一份 buffer 派生状态)。ClaudeTuiAdapter spawn 时通过 onSurfaceRegister
32585
+ // callback 写入;exit onSurfaceUnregister 清理。
32586
+ // manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 surface 内部 buffer,
32552
32587
  // 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
32553
- detectorsByToolSid = /* @__PURE__ */ new Map();
32588
+ surfacesByToolSid = /* @__PURE__ */ new Map();
32554
32589
  // 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
32555
32590
  // observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
32556
32591
  // 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
@@ -33824,18 +33859,18 @@ var SessionManager = class {
33824
33859
  if (!tsid) return void 0;
33825
33860
  return this.ptyTransports.get(tsid);
33826
33861
  }
33827
- /** ClaudeTuiAdapter spawn 时注册 detector ref;同一 tsid 重 spawn 会覆盖 */
33828
- registerDetector(toolSessionId, detector) {
33829
- this.detectorsByToolSid.set(toolSessionId, detector);
33862
+ /** ClaudeTuiAdapter spawn 时注册 surface ref;同一 tsid 重 spawn 会覆盖 */
33863
+ registerSurface(toolSessionId, surface) {
33864
+ this.surfacesByToolSid.set(toolSessionId, surface);
33830
33865
  }
33831
33866
  /** spawn 进程 exit 时清理 */
33832
- unregisterDetector(toolSessionId) {
33833
- this.detectorsByToolSid.delete(toolSessionId);
33867
+ unregisterSurface(toolSessionId) {
33868
+ this.surfacesByToolSid.delete(toolSessionId);
33834
33869
  }
33835
33870
  /**
33836
- * 同时 resize pty 子进程和 detector buffer:UI session:pty:resize 入口走这个。
33837
- * 顺序:pty.resize 在前,detector.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33838
- * detector 只是 daemon 端镜像,任一步失败都返 false。
33871
+ * 同时 resize pty 子进程和 surface buffer:UI session:pty:resize 入口走这个。
33872
+ * 顺序:pty.resize 在前,surface.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33873
+ * surface 只是 daemon 端镜像,任一步失败都返 false。
33839
33874
  * 找不到 runner / toolSessionId / pty 任一缺失 → 返 false,handler 静默给 UI 报 ok=false 不抛错。
33840
33875
  */
33841
33876
  resizePty(sessionId, cols, rows) {
@@ -33850,8 +33885,8 @@ var SessionManager = class {
33850
33885
  } catch {
33851
33886
  return false;
33852
33887
  }
33853
- const detector = this.detectorsByToolSid.get(tsid);
33854
- if (detector) detector.resize(cols, rows);
33888
+ const surface = this.surfacesByToolSid.get(tsid);
33889
+ if (surface) surface.resize(cols, rows);
33855
33890
  return true;
33856
33891
  }
33857
33892
  /**
@@ -35149,59 +35184,30 @@ var POPUP_FOOTER_PATTERNS = [
35149
35184
  { kind: "permission", regex: /Tab to amend|Do you want to proceed/i },
35150
35185
  { kind: "question", regex: /Enter to select|↑\/↓ to navigate/i }
35151
35186
  ];
35152
- function createPopupDetector(opts) {
35187
+ function createTerminalSurface(opts) {
35153
35188
  const term = new Terminal({
35154
- cols: opts.cols ?? 120,
35155
- rows: opts.rows ?? 40,
35156
- scrollback: 1e3,
35189
+ cols: opts?.cols ?? 120,
35190
+ rows: opts?.rows ?? 40,
35191
+ scrollback: opts?.scrollback ?? 1e3,
35157
35192
  allowProposedApi: true
35158
35193
  });
35159
35194
  const serializeAddon = new D();
35160
35195
  term.loadAddon(serializeAddon);
35161
- let visible = null;
35162
- let pendingClear = null;
35163
35196
  let parseSeq = 0;
35164
35197
  let pendingParse = 0;
35165
35198
  let drainWaiters = [];
35166
35199
  let disposed = false;
35167
- const scanFooter = () => {
35200
+ const observers = [];
35201
+ const collectLines = () => {
35168
35202
  const buf = term.buffer.active;
35203
+ const lines = new Array(buf.length);
35169
35204
  for (let i = 0; i < buf.length; i++) {
35170
35205
  const line = buf.getLine(i);
35171
- if (!line) continue;
35172
- const text = line.translateToString(true);
35173
- if (!text) continue;
35174
- for (const p2 of POPUP_FOOTER_PATTERNS) {
35175
- if (p2.regex.test(text)) return p2.kind;
35176
- }
35177
- }
35178
- return null;
35179
- };
35180
- const tick = () => {
35181
- const matched = scanFooter();
35182
- if (matched && visible === null) {
35183
- visible = matched;
35184
- opts.onTransition(matched, true);
35185
- } else if (matched && visible !== null) {
35186
- if (pendingClear) {
35187
- clearTimeout(pendingClear);
35188
- pendingClear = null;
35189
- }
35190
- } else if (!matched && visible !== null && !pendingClear) {
35191
- const toClear = visible;
35192
- pendingClear = setTimeout(() => {
35193
- pendingClear = null;
35194
- if (visible === toClear) {
35195
- visible = null;
35196
- opts.onTransition(toClear, false);
35197
- }
35198
- }, opts.clearQuietMs);
35206
+ lines[i] = line ? line.translateToString(true) : "";
35199
35207
  }
35208
+ return lines;
35200
35209
  };
35201
35210
  return {
35202
- get visibleKind() {
35203
- return visible;
35204
- },
35205
35211
  get parseSeq() {
35206
35212
  return parseSeq;
35207
35213
  },
@@ -35215,7 +35221,10 @@ function createPopupDetector(opts) {
35215
35221
  }
35216
35222
  if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
35217
35223
  pendingParse--;
35218
- tick();
35224
+ if (observers.length > 0) {
35225
+ const lines = collectLines();
35226
+ for (const cb of [...observers]) cb(lines);
35227
+ }
35219
35228
  if (pendingParse === 0 && drainWaiters.length > 0) {
35220
35229
  const waiters = drainWaiters;
35221
35230
  drainWaiters = [];
@@ -35237,42 +35246,93 @@ function createPopupDetector(opts) {
35237
35246
  } catch {
35238
35247
  }
35239
35248
  },
35249
+ serialize() {
35250
+ return serializeAddon.serialize({ scrollback: 1e3 });
35251
+ },
35240
35252
  dispose() {
35241
35253
  disposed = true;
35242
- if (pendingClear) {
35243
- clearTimeout(pendingClear);
35244
- pendingClear = null;
35245
- }
35254
+ pendingParse = 0;
35246
35255
  const waiters = drainWaiters;
35247
35256
  drainWaiters = [];
35248
35257
  for (const w2 of waiters) w2();
35258
+ observers.length = 0;
35249
35259
  serializeAddon.dispose();
35250
35260
  term.dispose();
35251
35261
  },
35252
- serialize() {
35253
- return serializeAddon.serialize({ scrollback: 1e3 });
35262
+ onTick(cb) {
35263
+ observers.push(cb);
35264
+ return () => {
35265
+ const i = observers.indexOf(cb);
35266
+ if (i >= 0) observers.splice(i, 1);
35267
+ };
35254
35268
  }
35255
35269
  };
35256
35270
  }
35257
- function createReadyDetector(opts) {
35258
- const term = new Terminal({
35259
- cols: opts.cols ?? 120,
35260
- rows: opts.rows ?? 40,
35261
- scrollback: 200,
35262
- allowProposedApi: true
35271
+ function observePopup(surface, opts) {
35272
+ let visible = null;
35273
+ let pendingClear = null;
35274
+ const scanFooter = (lines) => {
35275
+ for (const text of lines) {
35276
+ if (!text) continue;
35277
+ for (const p2 of POPUP_FOOTER_PATTERNS) {
35278
+ if (p2.regex.test(text)) return p2.kind;
35279
+ }
35280
+ }
35281
+ return null;
35282
+ };
35283
+ const unsubscribe = surface.onTick((lines) => {
35284
+ const matched = scanFooter(lines);
35285
+ const inputFrameVisible = opts.getInputFrameVisible();
35286
+ if (visible === null) {
35287
+ if (matched && !inputFrameVisible) {
35288
+ visible = matched;
35289
+ opts.onTransition(matched, true);
35290
+ }
35291
+ return;
35292
+ }
35293
+ if (!inputFrameVisible) {
35294
+ if (pendingClear) {
35295
+ clearTimeout(pendingClear);
35296
+ pendingClear = null;
35297
+ }
35298
+ return;
35299
+ }
35300
+ if (!pendingClear) {
35301
+ const toClear = visible;
35302
+ pendingClear = setTimeout(() => {
35303
+ pendingClear = null;
35304
+ if (visible === toClear) {
35305
+ visible = null;
35306
+ opts.onTransition(toClear, false);
35307
+ }
35308
+ }, opts.clearQuietMs);
35309
+ }
35263
35310
  });
35311
+ return {
35312
+ get visibleKind() {
35313
+ return visible;
35314
+ },
35315
+ dispose() {
35316
+ unsubscribe();
35317
+ if (pendingClear) {
35318
+ clearTimeout(pendingClear);
35319
+ pendingClear = null;
35320
+ }
35321
+ }
35322
+ };
35323
+ }
35324
+ function observeReady(surface, opts) {
35325
+ let prevReady = false;
35264
35326
  let quietTimer = null;
35265
- let disposed = false;
35266
- const scanReadyFrame = () => {
35267
- const buf = term.buffer.active;
35327
+ let unsubscribed = false;
35328
+ let latestLines = [];
35329
+ const scanReadyFrame = (lines) => {
35268
35330
  let prevHasDivider = false;
35269
- for (let i = 0; i < buf.length; i++) {
35270
- const line = buf.getLine(i);
35271
- if (!line) {
35331
+ for (const text of lines) {
35332
+ if (!text) {
35272
35333
  prevHasDivider = false;
35273
35334
  continue;
35274
35335
  }
35275
- const text = line.translateToString(true);
35276
35336
  const hasPrompt = /^❯/.test(text);
35277
35337
  const hasDivider = /─{20,}/.test(text);
35278
35338
  if (hasPrompt && prevHasDivider) return true;
@@ -35295,18 +35355,18 @@ function createReadyDetector(opts) {
35295
35355
  };
35296
35356
  const quietFire = () => {
35297
35357
  quietTimer = null;
35298
- if (disposed) return;
35299
- if (!scanReadyFrame()) {
35358
+ if (unsubscribed) return;
35359
+ if (!scanReadyFrame(latestLines)) {
35300
35360
  dbg({ event: "quiet-fire-but-not-ready-anymore" });
35301
35361
  return;
35302
35362
  }
35303
35363
  dbg({ event: "quiet-fire \u2192 onReady" });
35304
35364
  opts.onReady();
35305
35365
  };
35306
- let prevReady = false;
35307
- const tick = () => {
35308
- if (disposed) return;
35309
- const ready = scanReadyFrame();
35366
+ const unsubscribe = surface.onTick((lines) => {
35367
+ if (unsubscribed) return;
35368
+ latestLines = lines;
35369
+ const ready = scanReadyFrame(lines);
35310
35370
  if (!ready) {
35311
35371
  clearQuiet();
35312
35372
  if (prevReady) {
@@ -35321,19 +35381,15 @@ function createReadyDetector(opts) {
35321
35381
  }
35322
35382
  clearQuiet();
35323
35383
  quietTimer = setTimeout(quietFire, opts.quietMs);
35324
- };
35384
+ });
35325
35385
  return {
35326
- writeBytes(data) {
35327
- if (disposed) return;
35328
- term.write(data, () => {
35329
- if (disposed) return;
35330
- tick();
35331
- });
35386
+ get inputFrameVisible() {
35387
+ return prevReady;
35332
35388
  },
35333
35389
  dispose() {
35334
- disposed = true;
35390
+ unsubscribed = true;
35391
+ unsubscribe();
35335
35392
  clearQuiet();
35336
- term.dispose();
35337
35393
  }
35338
35394
  };
35339
35395
  }
@@ -35415,7 +35471,18 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35415
35471
  tag: ctx.toolSessionId ?? "no-tsid"
35416
35472
  });
35417
35473
  const bootGate = createBootGate(pty, this.tuiLogger);
35418
- const detector = createPopupDetector({
35474
+ const surface = createTerminalSurface();
35475
+ const readyObserver = observeReady(surface, {
35476
+ quietMs: 500,
35477
+ onReady: () => {
35478
+ if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35479
+ this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35480
+ this.tuiOpts.onReady(ctx.toolSessionId);
35481
+ },
35482
+ // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35483
+ logger: this.tuiLogger
35484
+ });
35485
+ const popupObserver = observePopup(surface, {
35419
35486
  clearQuietMs: 200,
35420
35487
  onTransition: (kind, visible) => {
35421
35488
  if (!ctx.toolSessionId || !this.tuiOpts.onPopupTransition) return;
@@ -35425,29 +35492,21 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35425
35492
  visible
35426
35493
  });
35427
35494
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
35428
- }
35429
- });
35430
- const readyDetector = createReadyDetector({
35431
- quietMs: 500,
35432
- onReady: () => {
35433
- if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35434
- this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35435
- this.tuiOpts.onReady(ctx.toolSessionId);
35436
35495
  },
35437
- // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35438
- logger: this.tuiLogger
35496
+ // popup_close 信号源:ready observer 扫到 + ─{20,} 输入框结构 → 输入框可见 → popup 已关
35497
+ getInputFrameVisible: () => readyObserver.inputFrameVisible
35439
35498
  });
35440
- if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
35441
- this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
35499
+ if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
35500
+ this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
35442
35501
  }
35443
35502
  let chunkSeq = 0;
35444
35503
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
35445
35504
  this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
35446
- await detector.drain();
35505
+ await surface.drain();
35447
35506
  return {
35448
- data: detector.serialize(),
35449
- popupKind: detector.visibleKind,
35450
- atSeq: detector.parseSeq
35507
+ data: surface.serialize(),
35508
+ popupKind: popupObserver.visibleKind,
35509
+ atSeq: surface.parseSeq
35451
35510
  };
35452
35511
  });
35453
35512
  }
@@ -35464,8 +35523,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35464
35523
  this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
35465
35524
  }
35466
35525
  bootGate.feedBytes(merged);
35467
- void detector.writeBytes(merged.toString("utf8"), seq);
35468
- readyDetector.writeBytes(merged.toString("utf8"));
35526
+ void surface.writeBytes(merged.toString("utf8"), seq);
35469
35527
  if (ctx.toolSessionId) {
35470
35528
  probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
35471
35529
  }
@@ -35483,14 +35541,15 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35483
35541
  flushCoalesced();
35484
35542
  }
35485
35543
  bootGate.dispose();
35486
- readyDetector.dispose();
35487
- if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
35488
- this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
35544
+ readyObserver.dispose();
35545
+ popupObserver.dispose();
35546
+ if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
35547
+ this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
35489
35548
  }
35490
35549
  if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
35491
35550
  this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
35492
35551
  }
35493
- detector.dispose();
35552
+ surface.dispose();
35494
35553
  });
35495
35554
  return ptyChild;
35496
35555
  }
@@ -37766,12 +37825,6 @@ async function importZip(buf, root) {
37766
37825
  } catch (e) {
37767
37826
  throw new ImportError("INVALID_MANIFEST", `manifest.json: ${e.message}`);
37768
37827
  }
37769
- if (manifestJson && typeof manifestJson === "object" && "entry" in manifestJson && manifestJson.entry?.url !== void 0) {
37770
- throw new ImportError(
37771
- "REMOTE_NOT_SUPPORTED",
37772
- "entry.url (remote mode) is not supported in v1"
37773
- );
37774
- }
37775
37828
  const result = ExtensionManifestSchema.safeParse(manifestJson);
37776
37829
  if (!result.success) {
37777
37830
  throw new ImportError(
@@ -37780,6 +37833,16 @@ async function importZip(buf, root) {
37780
37833
  );
37781
37834
  }
37782
37835
  const manifest = result.data;
37836
+ if (manifestMode(manifest) === "hosted") {
37837
+ const filenames = Object.keys(zip.files).filter((n) => !zip.files[n].dir);
37838
+ const extras = filenames.filter((n) => n !== "manifest.json");
37839
+ if (extras.length > 0) {
37840
+ throw new ImportError(
37841
+ "HOSTED_ZIP_EXTRA_FILES",
37842
+ `hosted extension may only contain manifest.json (extra: ${extras.join(", ")})`
37843
+ );
37844
+ }
37845
+ }
37783
37846
  const destDir = import_node_path19.default.join(root, manifest.id);
37784
37847
  let destExists = false;
37785
37848
  try {
@@ -40240,6 +40303,7 @@ function buildReadyFrame(deps, client) {
40240
40303
  fileSharing.httpBaseUrl = httpBaseUrl;
40241
40304
  if (deps.httpToken) fileSharing.httpToken = deps.httpToken;
40242
40305
  }
40306
+ const daemonSource = process.env.CLAWD_DAEMON_SOURCE === "ota" ? "ota" : "packaged";
40243
40307
  return {
40244
40308
  version,
40245
40309
  protocolVersion: PROTOCOL_VERSION,
@@ -40249,6 +40313,7 @@ function buildReadyFrame(deps, client) {
40249
40313
  runningSessions: info.runningSessions,
40250
40314
  tunnelUrl,
40251
40315
  mode: deps.mode,
40316
+ daemonSource,
40252
40317
  ...fileSharing
40253
40318
  };
40254
40319
  }
@@ -40941,10 +41006,17 @@ function pickExtId(frame) {
40941
41006
  }
40942
41007
  function composeState(rec, running, crashed, getPort) {
40943
41008
  if (rec.state === "invalid") return rec;
41009
+ if (manifestMode(rec.manifest) === "hosted") {
41010
+ return {
41011
+ ...rec,
41012
+ state: "running",
41013
+ target: { kind: "hosted", url: rec.manifest.entry.url }
41014
+ };
41015
+ }
40944
41016
  if (running.has(rec.extId)) {
40945
41017
  const port = getPort(rec.extId);
40946
41018
  if (port == null) return rec;
40947
- return { ...rec, state: "running", port };
41019
+ return { ...rec, state: "running", target: { kind: "local", port } };
40948
41020
  }
40949
41021
  if (crashed.has(rec.extId)) return { ...rec, state: "crashed" };
40950
41022
  return rec;
@@ -40966,8 +41038,8 @@ function buildExtensionHandlers(deps) {
40966
41038
  const open = async (frame, _client, ctx) => {
40967
41039
  assertOwner(ctx);
40968
41040
  const extId = pickExtId(frame);
40969
- const { port } = await deps.runtime.open(extId);
40970
- return { response: { port } };
41041
+ const target = await deps.runtime.open(extId);
41042
+ return { response: target };
40971
41043
  };
40972
41044
  const close = async (frame, _client, ctx) => {
40973
41045
  assertOwner(ctx);
@@ -41526,16 +41598,20 @@ var Runtime = class {
41526
41598
  }
41527
41599
  async open(extId) {
41528
41600
  const existing = this.handles.get(extId);
41529
- if (existing) return { port: existing.port };
41601
+ if (existing) return { kind: "local", port: existing.port };
41530
41602
  const records = await loadAll(this.root);
41531
41603
  const rec = records.find((r) => r.extId === extId);
41532
41604
  if (!rec) throw new RuntimeError("NOT_FOUND", `extension ${extId} not installed`);
41533
41605
  if (rec.state === "invalid")
41534
41606
  throw new RuntimeError("INVALID_MANIFEST", rec.invalidReason);
41607
+ if (manifestMode(rec.manifest) === "hosted") {
41608
+ return { kind: "hosted", url: rec.manifest.entry.url };
41609
+ }
41535
41610
  const port = await this.allocator.allocate().catch(() => {
41536
41611
  throw new RuntimeError("PORT_EXHAUSTED", "no free port in configured range");
41537
41612
  });
41538
- const cmd = rec.manifest.runtime.startCommand.replace(
41613
+ const startCommand = rec.manifest.runtime.startCommand;
41614
+ const cmd = startCommand.replace(
41539
41615
  /\$CLAWOS_EXT_PORT/g,
41540
41616
  String(port)
41541
41617
  );
@@ -41573,7 +41649,7 @@ var Runtime = class {
41573
41649
  try {
41574
41650
  await this.waitForHealthz(port, () => earlyExit, handle);
41575
41651
  this.crashed.delete(extId);
41576
- return { port };
41652
+ return { kind: "local", port };
41577
41653
  } catch (e) {
41578
41654
  if (this.handles.get(extId) === handle) {
41579
41655
  this.handles.delete(extId);
@@ -41988,8 +42064,8 @@ async function startDaemon(config) {
41988
42064
  onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
41989
42065
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
41990
42066
  onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
41991
- onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
41992
- onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid),
42067
+ onSurfaceRegister: (tsid, surface) => manager.registerSurface(tsid, surface),
42068
+ onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
41993
42069
  // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
41994
42070
  onReady: (tsid) => manager.dispatchReadyDetected(tsid)
41995
42071
  }) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.105-beta.200.ef901dc",
3
+ "version": "0.2.105-beta.201.e3d63e9",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",