@clawos-dev/clawd 0.2.102 → 0.2.103-beta.198.c756b33

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 +181 -157
  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,12 +32580,11 @@ 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 TerminalSurface(单一虚拟终端,popup / ready
32550
- // observer 都从这一份 buffer 派生状态)。ClaudeTuiAdapter spawn 时通过 onSurfaceRegister
32551
- // callback 写入;exit onSurfaceUnregister 清理。
32552
- // manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 surface 内部 buffer,
32583
+ // TUI 模式:toolSessionId → @xterm/headless PopupDetector。
32584
+ // ClaudeTuiAdapter spawn 时通过 onDetectorRegister callback 写入;exit 时 onDetectorUnregister 清理。
32585
+ // manager.resizePty 用此映射把 UI session:pty:resize 同步到 detector 内部 buffer,
32553
32586
  // 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
32554
- surfacesByToolSid = /* @__PURE__ */ new Map();
32587
+ detectorsByToolSid = /* @__PURE__ */ new Map();
32555
32588
  // 仅 mode='tui' 需要:index.ts 装配阶段在 observer 构造后注入。
32556
32589
  // observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
32557
32590
  // 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
@@ -33825,18 +33858,18 @@ var SessionManager = class {
33825
33858
  if (!tsid) return void 0;
33826
33859
  return this.ptyTransports.get(tsid);
33827
33860
  }
33828
- /** ClaudeTuiAdapter spawn 时注册 surface ref;同一 tsid 重 spawn 会覆盖 */
33829
- registerSurface(toolSessionId, surface) {
33830
- this.surfacesByToolSid.set(toolSessionId, surface);
33861
+ /** ClaudeTuiAdapter spawn 时注册 detector ref;同一 tsid 重 spawn 会覆盖 */
33862
+ registerDetector(toolSessionId, detector) {
33863
+ this.detectorsByToolSid.set(toolSessionId, detector);
33831
33864
  }
33832
33865
  /** spawn 进程 exit 时清理 */
33833
- unregisterSurface(toolSessionId) {
33834
- this.surfacesByToolSid.delete(toolSessionId);
33866
+ unregisterDetector(toolSessionId) {
33867
+ this.detectorsByToolSid.delete(toolSessionId);
33835
33868
  }
33836
33869
  /**
33837
- * 同时 resize pty 子进程和 surface buffer:UI session:pty:resize 入口走这个。
33838
- * 顺序:pty.resize 在前,surface.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33839
- * surface 只是 daemon 端镜像,任一步失败都返 false。
33870
+ * 同时 resize pty 子进程和 detector buffer:UI session:pty:resize 入口走这个。
33871
+ * 顺序:pty.resize 在前,detector.resize 在后 —— pty 是真正的 CC 子进程视图,必须先成功;
33872
+ * detector 只是 daemon 端镜像,任一步失败都返 false。
33840
33873
  * 找不到 runner / toolSessionId / pty 任一缺失 → 返 false,handler 静默给 UI 报 ok=false 不抛错。
33841
33874
  */
33842
33875
  resizePty(sessionId, cols, rows) {
@@ -33851,8 +33884,8 @@ var SessionManager = class {
33851
33884
  } catch {
33852
33885
  return false;
33853
33886
  }
33854
- const surface = this.surfacesByToolSid.get(tsid);
33855
- if (surface) surface.resize(cols, rows);
33887
+ const detector = this.detectorsByToolSid.get(tsid);
33888
+ if (detector) detector.resize(cols, rows);
33856
33889
  return true;
33857
33890
  }
33858
33891
  /**
@@ -35150,30 +35183,59 @@ var POPUP_FOOTER_PATTERNS = [
35150
35183
  { kind: "permission", regex: /Tab to amend|Do you want to proceed/i },
35151
35184
  { kind: "question", regex: /Enter to select|↑\/↓ to navigate/i }
35152
35185
  ];
35153
- function createTerminalSurface(opts) {
35186
+ function createPopupDetector(opts) {
35154
35187
  const term = new Terminal({
35155
- cols: opts?.cols ?? 120,
35156
- rows: opts?.rows ?? 40,
35157
- scrollback: opts?.scrollback ?? 1e3,
35188
+ cols: opts.cols ?? 120,
35189
+ rows: opts.rows ?? 40,
35190
+ scrollback: 1e3,
35158
35191
  allowProposedApi: true
35159
35192
  });
35160
35193
  const serializeAddon = new D();
35161
35194
  term.loadAddon(serializeAddon);
35195
+ let visible = null;
35196
+ let pendingClear = null;
35162
35197
  let parseSeq = 0;
35163
35198
  let pendingParse = 0;
35164
35199
  let drainWaiters = [];
35165
35200
  let disposed = false;
35166
- const observers = [];
35167
- const collectLines = () => {
35201
+ const scanFooter = () => {
35168
35202
  const buf = term.buffer.active;
35169
- const lines = new Array(buf.length);
35170
35203
  for (let i = 0; i < buf.length; i++) {
35171
35204
  const line = buf.getLine(i);
35172
- lines[i] = line ? line.translateToString(true) : "";
35205
+ if (!line) continue;
35206
+ const text = line.translateToString(true);
35207
+ if (!text) continue;
35208
+ for (const p2 of POPUP_FOOTER_PATTERNS) {
35209
+ if (p2.regex.test(text)) return p2.kind;
35210
+ }
35211
+ }
35212
+ return null;
35213
+ };
35214
+ const tick = () => {
35215
+ const matched = scanFooter();
35216
+ if (matched && visible === null) {
35217
+ visible = matched;
35218
+ opts.onTransition(matched, true);
35219
+ } else if (matched && visible !== null) {
35220
+ if (pendingClear) {
35221
+ clearTimeout(pendingClear);
35222
+ pendingClear = null;
35223
+ }
35224
+ } else if (!matched && visible !== null && !pendingClear) {
35225
+ const toClear = visible;
35226
+ pendingClear = setTimeout(() => {
35227
+ pendingClear = null;
35228
+ if (visible === toClear) {
35229
+ visible = null;
35230
+ opts.onTransition(toClear, false);
35231
+ }
35232
+ }, opts.clearQuietMs);
35173
35233
  }
35174
- return lines;
35175
35234
  };
35176
35235
  return {
35236
+ get visibleKind() {
35237
+ return visible;
35238
+ },
35177
35239
  get parseSeq() {
35178
35240
  return parseSeq;
35179
35241
  },
@@ -35187,10 +35249,7 @@ function createTerminalSurface(opts) {
35187
35249
  }
35188
35250
  if (typeof seq === "number" && seq > parseSeq) parseSeq = seq;
35189
35251
  pendingParse--;
35190
- if (observers.length > 0) {
35191
- const lines = collectLines();
35192
- for (const cb of [...observers]) cb(lines);
35193
- }
35252
+ tick();
35194
35253
  if (pendingParse === 0 && drainWaiters.length > 0) {
35195
35254
  const waiters = drainWaiters;
35196
35255
  drainWaiters = [];
@@ -35212,93 +35271,42 @@ function createTerminalSurface(opts) {
35212
35271
  } catch {
35213
35272
  }
35214
35273
  },
35215
- serialize() {
35216
- return serializeAddon.serialize({ scrollback: 1e3 });
35217
- },
35218
35274
  dispose() {
35219
35275
  disposed = true;
35220
- pendingParse = 0;
35276
+ if (pendingClear) {
35277
+ clearTimeout(pendingClear);
35278
+ pendingClear = null;
35279
+ }
35221
35280
  const waiters = drainWaiters;
35222
35281
  drainWaiters = [];
35223
35282
  for (const w2 of waiters) w2();
35224
- observers.length = 0;
35225
35283
  serializeAddon.dispose();
35226
35284
  term.dispose();
35227
35285
  },
35228
- onTick(cb) {
35229
- observers.push(cb);
35230
- return () => {
35231
- const i = observers.indexOf(cb);
35232
- if (i >= 0) observers.splice(i, 1);
35233
- };
35286
+ serialize() {
35287
+ return serializeAddon.serialize({ scrollback: 1e3 });
35234
35288
  }
35235
35289
  };
35236
35290
  }
35237
- function observePopup(surface, opts) {
35238
- let visible = null;
35239
- let pendingClear = null;
35240
- const scanFooter = (lines) => {
35241
- for (const text of lines) {
35242
- if (!text) continue;
35243
- for (const p2 of POPUP_FOOTER_PATTERNS) {
35244
- if (p2.regex.test(text)) return p2.kind;
35245
- }
35246
- }
35247
- return null;
35248
- };
35249
- const unsubscribe = surface.onTick((lines) => {
35250
- const matched = scanFooter(lines);
35251
- const inputFrameVisible = opts.getInputFrameVisible();
35252
- if (visible === null) {
35253
- if (matched && !inputFrameVisible) {
35254
- visible = matched;
35255
- opts.onTransition(matched, true);
35256
- }
35257
- return;
35258
- }
35259
- if (!inputFrameVisible) {
35260
- if (pendingClear) {
35261
- clearTimeout(pendingClear);
35262
- pendingClear = null;
35263
- }
35264
- return;
35265
- }
35266
- if (!pendingClear) {
35267
- const toClear = visible;
35268
- pendingClear = setTimeout(() => {
35269
- pendingClear = null;
35270
- if (visible === toClear) {
35271
- visible = null;
35272
- opts.onTransition(toClear, false);
35273
- }
35274
- }, opts.clearQuietMs);
35275
- }
35291
+ function createReadyDetector(opts) {
35292
+ const term = new Terminal({
35293
+ cols: opts.cols ?? 120,
35294
+ rows: opts.rows ?? 40,
35295
+ scrollback: 200,
35296
+ allowProposedApi: true
35276
35297
  });
35277
- return {
35278
- get visibleKind() {
35279
- return visible;
35280
- },
35281
- dispose() {
35282
- unsubscribe();
35283
- if (pendingClear) {
35284
- clearTimeout(pendingClear);
35285
- pendingClear = null;
35286
- }
35287
- }
35288
- };
35289
- }
35290
- function observeReady(surface, opts) {
35291
- let prevReady = false;
35292
35298
  let quietTimer = null;
35293
- let unsubscribed = false;
35294
- let latestLines = [];
35295
- const scanReadyFrame = (lines) => {
35299
+ let disposed = false;
35300
+ const scanReadyFrame = () => {
35301
+ const buf = term.buffer.active;
35296
35302
  let prevHasDivider = false;
35297
- for (const text of lines) {
35298
- if (!text) {
35303
+ for (let i = 0; i < buf.length; i++) {
35304
+ const line = buf.getLine(i);
35305
+ if (!line) {
35299
35306
  prevHasDivider = false;
35300
35307
  continue;
35301
35308
  }
35309
+ const text = line.translateToString(true);
35302
35310
  const hasPrompt = /^❯/.test(text);
35303
35311
  const hasDivider = /─{20,}/.test(text);
35304
35312
  if (hasPrompt && prevHasDivider) return true;
@@ -35321,18 +35329,18 @@ function observeReady(surface, opts) {
35321
35329
  };
35322
35330
  const quietFire = () => {
35323
35331
  quietTimer = null;
35324
- if (unsubscribed) return;
35325
- if (!scanReadyFrame(latestLines)) {
35332
+ if (disposed) return;
35333
+ if (!scanReadyFrame()) {
35326
35334
  dbg({ event: "quiet-fire-but-not-ready-anymore" });
35327
35335
  return;
35328
35336
  }
35329
35337
  dbg({ event: "quiet-fire \u2192 onReady" });
35330
35338
  opts.onReady();
35331
35339
  };
35332
- const unsubscribe = surface.onTick((lines) => {
35333
- if (unsubscribed) return;
35334
- latestLines = lines;
35335
- const ready = scanReadyFrame(lines);
35340
+ let prevReady = false;
35341
+ const tick = () => {
35342
+ if (disposed) return;
35343
+ const ready = scanReadyFrame();
35336
35344
  if (!ready) {
35337
35345
  clearQuiet();
35338
35346
  if (prevReady) {
@@ -35347,15 +35355,19 @@ function observeReady(surface, opts) {
35347
35355
  }
35348
35356
  clearQuiet();
35349
35357
  quietTimer = setTimeout(quietFire, opts.quietMs);
35350
- });
35358
+ };
35351
35359
  return {
35352
- get inputFrameVisible() {
35353
- return prevReady;
35360
+ writeBytes(data) {
35361
+ if (disposed) return;
35362
+ term.write(data, () => {
35363
+ if (disposed) return;
35364
+ tick();
35365
+ });
35354
35366
  },
35355
35367
  dispose() {
35356
- unsubscribed = true;
35357
- unsubscribe();
35368
+ disposed = true;
35358
35369
  clearQuiet();
35370
+ term.dispose();
35359
35371
  }
35360
35372
  };
35361
35373
  }
@@ -35437,18 +35449,7 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35437
35449
  tag: ctx.toolSessionId ?? "no-tsid"
35438
35450
  });
35439
35451
  const bootGate = createBootGate(pty, this.tuiLogger);
35440
- const surface = createTerminalSurface();
35441
- const readyObserver = observeReady(surface, {
35442
- quietMs: 500,
35443
- onReady: () => {
35444
- if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35445
- this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35446
- this.tuiOpts.onReady(ctx.toolSessionId);
35447
- },
35448
- // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35449
- logger: this.tuiLogger
35450
- });
35451
- const popupObserver = observePopup(surface, {
35452
+ const detector = createPopupDetector({
35452
35453
  clearQuietMs: 200,
35453
35454
  onTransition: (kind, visible) => {
35454
35455
  if (!ctx.toolSessionId || !this.tuiOpts.onPopupTransition) return;
@@ -35458,21 +35459,29 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35458
35459
  visible
35459
35460
  });
35460
35461
  this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
35462
+ }
35463
+ });
35464
+ const readyDetector = createReadyDetector({
35465
+ quietMs: 500,
35466
+ onReady: () => {
35467
+ if (!ctx.toolSessionId || !this.tuiOpts.onReady) return;
35468
+ this.tuiLogger?.debug("ready-detected", { toolSessionId: ctx.toolSessionId });
35469
+ this.tuiOpts.onReady(ctx.toolSessionId);
35461
35470
  },
35462
- // popup_close 信号源:ready observer 扫到 + ─{20,} 输入框结构 → 输入框可见 → popup 已关
35463
- getInputFrameVisible: () => readyObserver.inputFrameVisible
35471
+ // [RG-DBG] 排查 ReadyGate 偶现卡输入用;定位完跟所有 [RG] 日志一起删
35472
+ logger: this.tuiLogger
35464
35473
  });
35465
- if (ctx.toolSessionId && this.tuiOpts.onSurfaceRegister) {
35466
- this.tuiOpts.onSurfaceRegister(ctx.toolSessionId, surface);
35474
+ if (ctx.toolSessionId && this.tuiOpts.onDetectorRegister) {
35475
+ this.tuiOpts.onDetectorRegister(ctx.toolSessionId, detector);
35467
35476
  }
35468
35477
  let chunkSeq = 0;
35469
35478
  if (ctx.toolSessionId && this.tuiOpts.onPtyReplayRegister) {
35470
35479
  this.tuiOpts.onPtyReplayRegister(ctx.toolSessionId, async () => {
35471
- await surface.drain();
35480
+ await detector.drain();
35472
35481
  return {
35473
- data: surface.serialize(),
35474
- popupKind: popupObserver.visibleKind,
35475
- atSeq: surface.parseSeq
35482
+ data: detector.serialize(),
35483
+ popupKind: detector.visibleKind,
35484
+ atSeq: detector.parseSeq
35476
35485
  };
35477
35486
  });
35478
35487
  }
@@ -35489,7 +35498,8 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35489
35498
  this.tuiOpts.onPtyData(ctx.toolSessionId, merged.toString("base64"), seq);
35490
35499
  }
35491
35500
  bootGate.feedBytes(merged);
35492
- void surface.writeBytes(merged.toString("utf8"), seq);
35501
+ void detector.writeBytes(merged.toString("utf8"), seq);
35502
+ readyDetector.writeBytes(merged.toString("utf8"));
35493
35503
  if (ctx.toolSessionId) {
35494
35504
  probePtyDataThrottled(ctx.toolSessionId, merged.length, merged);
35495
35505
  }
@@ -35507,15 +35517,14 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
35507
35517
  flushCoalesced();
35508
35518
  }
35509
35519
  bootGate.dispose();
35510
- readyObserver.dispose();
35511
- popupObserver.dispose();
35512
- if (ctx.toolSessionId && this.tuiOpts.onSurfaceUnregister) {
35513
- this.tuiOpts.onSurfaceUnregister(ctx.toolSessionId);
35520
+ readyDetector.dispose();
35521
+ if (ctx.toolSessionId && this.tuiOpts.onDetectorUnregister) {
35522
+ this.tuiOpts.onDetectorUnregister(ctx.toolSessionId);
35514
35523
  }
35515
35524
  if (ctx.toolSessionId && this.tuiOpts.onPtyUnregister) {
35516
35525
  this.tuiOpts.onPtyUnregister(ctx.toolSessionId);
35517
35526
  }
35518
- surface.dispose();
35527
+ detector.dispose();
35519
35528
  });
35520
35529
  return ptyChild;
35521
35530
  }
@@ -37791,12 +37800,6 @@ async function importZip(buf, root) {
37791
37800
  } catch (e) {
37792
37801
  throw new ImportError("INVALID_MANIFEST", `manifest.json: ${e.message}`);
37793
37802
  }
37794
- if (manifestJson && typeof manifestJson === "object" && "entry" in manifestJson && manifestJson.entry?.url !== void 0) {
37795
- throw new ImportError(
37796
- "REMOTE_NOT_SUPPORTED",
37797
- "entry.url (remote mode) is not supported in v1"
37798
- );
37799
- }
37800
37803
  const result = ExtensionManifestSchema.safeParse(manifestJson);
37801
37804
  if (!result.success) {
37802
37805
  throw new ImportError(
@@ -37805,6 +37808,16 @@ async function importZip(buf, root) {
37805
37808
  );
37806
37809
  }
37807
37810
  const manifest = result.data;
37811
+ if (manifestMode(manifest) === "hosted") {
37812
+ const filenames = Object.keys(zip.files).filter((n) => !zip.files[n].dir);
37813
+ const extras = filenames.filter((n) => n !== "manifest.json");
37814
+ if (extras.length > 0) {
37815
+ throw new ImportError(
37816
+ "HOSTED_ZIP_EXTRA_FILES",
37817
+ `hosted extension may only contain manifest.json (extra: ${extras.join(", ")})`
37818
+ );
37819
+ }
37820
+ }
37808
37821
  const destDir = import_node_path19.default.join(root, manifest.id);
37809
37822
  let destExists = false;
37810
37823
  try {
@@ -40963,10 +40976,17 @@ function pickExtId(frame) {
40963
40976
  }
40964
40977
  function composeState(rec, running, crashed, getPort) {
40965
40978
  if (rec.state === "invalid") return rec;
40979
+ if (manifestMode(rec.manifest) === "hosted") {
40980
+ return {
40981
+ ...rec,
40982
+ state: "running",
40983
+ target: { kind: "hosted", url: rec.manifest.entry.url }
40984
+ };
40985
+ }
40966
40986
  if (running.has(rec.extId)) {
40967
40987
  const port = getPort(rec.extId);
40968
40988
  if (port == null) return rec;
40969
- return { ...rec, state: "running", port };
40989
+ return { ...rec, state: "running", target: { kind: "local", port } };
40970
40990
  }
40971
40991
  if (crashed.has(rec.extId)) return { ...rec, state: "crashed" };
40972
40992
  return rec;
@@ -40988,8 +41008,8 @@ function buildExtensionHandlers(deps) {
40988
41008
  const open = async (frame, _client, ctx) => {
40989
41009
  assertOwner(ctx);
40990
41010
  const extId = pickExtId(frame);
40991
- const { port } = await deps.runtime.open(extId);
40992
- return { response: { port } };
41011
+ const target = await deps.runtime.open(extId);
41012
+ return { response: target };
40993
41013
  };
40994
41014
  const close = async (frame, _client, ctx) => {
40995
41015
  assertOwner(ctx);
@@ -41539,16 +41559,20 @@ var Runtime = class {
41539
41559
  }
41540
41560
  async open(extId) {
41541
41561
  const existing = this.handles.get(extId);
41542
- if (existing) return { port: existing.port };
41562
+ if (existing) return { kind: "local", port: existing.port };
41543
41563
  const records = await loadAll(this.root);
41544
41564
  const rec = records.find((r) => r.extId === extId);
41545
41565
  if (!rec) throw new RuntimeError("NOT_FOUND", `extension ${extId} not installed`);
41546
41566
  if (rec.state === "invalid")
41547
41567
  throw new RuntimeError("INVALID_MANIFEST", rec.invalidReason);
41568
+ if (manifestMode(rec.manifest) === "hosted") {
41569
+ return { kind: "hosted", url: rec.manifest.entry.url };
41570
+ }
41548
41571
  const port = await this.allocator.allocate().catch(() => {
41549
41572
  throw new RuntimeError("PORT_EXHAUSTED", "no free port in configured range");
41550
41573
  });
41551
- const cmd = rec.manifest.runtime.startCommand.replace(
41574
+ const startCommand = rec.manifest.runtime.startCommand;
41575
+ const cmd = startCommand.replace(
41552
41576
  /\$CLAWOS_EXT_PORT/g,
41553
41577
  String(port)
41554
41578
  );
@@ -41586,7 +41610,7 @@ var Runtime = class {
41586
41610
  try {
41587
41611
  await this.waitForHealthz(port, () => earlyExit, handle);
41588
41612
  this.crashed.delete(extId);
41589
- return { port };
41613
+ return { kind: "local", port };
41590
41614
  } catch (e) {
41591
41615
  if (this.handles.get(extId) === handle) {
41592
41616
  this.handles.delete(extId);
@@ -41968,8 +41992,8 @@ async function startDaemon(config) {
41968
41992
  onPtyData: (tsid, b64, seq) => manager.emitPtyChunk(tsid, b64, seq),
41969
41993
  onPopupTransition: (tsid, kind, visible) => manager.emitPopupTransition(tsid, kind, visible),
41970
41994
  onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
41971
- onSurfaceRegister: (tsid, surface) => manager.registerSurface(tsid, surface),
41972
- onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
41995
+ onDetectorRegister: (tsid, detector) => manager.registerDetector(tsid, detector),
41996
+ onDetectorUnregister: (tsid) => manager.unregisterDetector(tsid),
41973
41997
  // ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
41974
41998
  onReady: (tsid) => manager.dispatchReadyDetected(tsid)
41975
41999
  }) : 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.102",
3
+ "version": "0.2.103-beta.198.c756b33",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",