@clawos-dev/clawd 0.2.30 → 0.2.31

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 +125 -163
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -8583,7 +8583,7 @@ var init_zod = __esm({
8583
8583
  });
8584
8584
 
8585
8585
  // ../protocol/src/persona-schemas.ts
8586
- var PersonaTokenEntrySchema, PersonaFileSchema, PersonaHelloFrameSchema, PersonaSendFrameSchema, PersonaResetFrameSchema, PersonaPingFrameSchema, PersonaReadyFrameSchema, PersonaHistoryEventFrameSchema, PersonaHistoryDoneFrameSchema, PersonaEventFrameSchema, PersonaErrorFrameSchema, PersonaPongFrameSchema, PersonaHistoryPageRequestFrameSchema, PersonaHistoryPageFrameSchema, PersonaClientFrameSchema, PersonaServerFrameSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema;
8586
+ var PersonaTokenEntrySchema, PersonaFileSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema;
8587
8587
  var init_persona_schemas = __esm({
8588
8588
  "../protocol/src/persona-schemas.ts"() {
8589
8589
  "use strict";
@@ -8602,83 +8602,6 @@ var init_persona_schemas = __esm({
8602
8602
  createdAt: external_exports.number(),
8603
8603
  updatedAt: external_exports.number()
8604
8604
  }).strict();
8605
- PersonaHelloFrameSchema = external_exports.object({
8606
- kind: external_exports.literal("persona-hello"),
8607
- token: external_exports.string().min(1)
8608
- });
8609
- PersonaSendFrameSchema = external_exports.object({
8610
- kind: external_exports.literal("send"),
8611
- text: external_exports.string()
8612
- });
8613
- PersonaResetFrameSchema = external_exports.object({
8614
- kind: external_exports.literal("reset")
8615
- });
8616
- PersonaPingFrameSchema = external_exports.object({
8617
- kind: external_exports.literal("ping")
8618
- });
8619
- PersonaReadyFrameSchema = external_exports.object({
8620
- kind: external_exports.literal("persona-ready"),
8621
- subSessionId: external_exports.string(),
8622
- personaLabel: external_exports.string(),
8623
- cwd: external_exports.string(),
8624
- model: external_exports.string().optional()
8625
- });
8626
- PersonaHistoryEventFrameSchema = external_exports.object({
8627
- kind: external_exports.literal("history-event"),
8628
- event: external_exports.unknown()
8629
- });
8630
- PersonaHistoryDoneFrameSchema = external_exports.object({
8631
- kind: external_exports.literal("history-done"),
8632
- offset: external_exports.number().int().nonnegative(),
8633
- total: external_exports.number().int().nonnegative()
8634
- });
8635
- PersonaEventFrameSchema = external_exports.object({
8636
- kind: external_exports.literal("event"),
8637
- event: external_exports.unknown()
8638
- });
8639
- PersonaErrorFrameSchema = external_exports.object({
8640
- kind: external_exports.literal("error"),
8641
- code: external_exports.enum([
8642
- "TOKEN_INVALID",
8643
- "TOKEN_REVOKED",
8644
- "PERSONA_DELETED",
8645
- "PERSONA_NOT_PUBLIC",
8646
- "INTERNAL"
8647
- ]),
8648
- message: external_exports.string()
8649
- });
8650
- PersonaPongFrameSchema = external_exports.object({
8651
- kind: external_exports.literal("pong")
8652
- });
8653
- PersonaHistoryPageRequestFrameSchema = external_exports.object({
8654
- kind: external_exports.literal("history-page-request"),
8655
- requestId: external_exports.string().min(1),
8656
- limit: external_exports.number().int().positive(),
8657
- offset: external_exports.number().int().nonnegative()
8658
- });
8659
- PersonaHistoryPageFrameSchema = external_exports.object({
8660
- kind: external_exports.literal("history-page"),
8661
- requestId: external_exports.string().min(1),
8662
- events: external_exports.array(external_exports.unknown()),
8663
- offset: external_exports.number().int().nonnegative(),
8664
- total: external_exports.number().int().nonnegative()
8665
- });
8666
- PersonaClientFrameSchema = external_exports.discriminatedUnion("kind", [
8667
- PersonaHelloFrameSchema,
8668
- PersonaSendFrameSchema,
8669
- PersonaResetFrameSchema,
8670
- PersonaPingFrameSchema,
8671
- PersonaHistoryPageRequestFrameSchema
8672
- ]);
8673
- PersonaServerFrameSchema = external_exports.discriminatedUnion("kind", [
8674
- PersonaReadyFrameSchema,
8675
- PersonaHistoryEventFrameSchema,
8676
- PersonaHistoryDoneFrameSchema,
8677
- PersonaEventFrameSchema,
8678
- PersonaErrorFrameSchema,
8679
- PersonaPongFrameSchema,
8680
- PersonaHistoryPageFrameSchema
8681
- ]);
8682
8605
  PersonaCreateArgsSchema = external_exports.object({
8683
8606
  label: external_exports.string().min(1),
8684
8607
  personality: external_exports.string(),
@@ -16661,8 +16584,9 @@ var SessionManager = class {
16661
16584
  // 不进 SessionFile schema(避免破坏现有 zod parse),仅运行时缓存
16662
16585
  subSessionMetaBySid = /* @__PURE__ */ new Map();
16663
16586
  // persona-bound transport 订阅器:sessionId → Set<listener>。
16664
- // 仅推 reducer 产出的 'session:event' 帧里的 ParsedEvent,其他帧(status / info / cleared)
16665
- // 由调用方按需自行处理(persona-bound 简化协议只暴露 event 流,不需要这些)
16587
+ // 透传 owner 路径白名单 EventFrame(routeFromRunner 决定哪些 type 进入),listener 端按
16588
+ // `frame.type` narrow 出 'session:event'(ParsedEvent)/ 'session:status'(procAlive 同步)等。
16589
+ // 这样 owner / listener 共用同一组事件字面量,新增跨端信号时只需在 fan-out 白名单里加 type。
16666
16590
  eventSubscribers = /* @__PURE__ */ new Map();
16667
16591
  // 按 agentId 拿对应的 SessionStore;persona-* agentId 使用 personaRoot 路径。
16668
16592
  // 'default' 直接复用 deps.store;其它 agentId 第一次访问时按需创建并缓存。
@@ -16717,15 +16641,14 @@ var SessionManager = class {
16717
16641
  routeFromRunner(frame, target) {
16718
16642
  const compressed = compressFrameForWire(frame);
16719
16643
  if (!compressed) return;
16720
- if (compressed.type === "session:event") {
16644
+ if (compressed.type === "session:event" || compressed.type === "session:status") {
16721
16645
  const sid = compressed.sessionId;
16722
- const ev = compressed.event;
16723
- if (sid && ev) {
16646
+ if (sid) {
16724
16647
  const subs = this.eventSubscribers.get(sid);
16725
16648
  if (subs && subs.size > 0) {
16726
16649
  for (const fn of subs) {
16727
16650
  try {
16728
- fn(ev);
16651
+ fn(compressed);
16729
16652
  } catch {
16730
16653
  }
16731
16654
  }
@@ -17170,6 +17093,20 @@ var SessionManager = class {
17170
17093
  getActive(sessionId) {
17171
17094
  return this.runners.get(sessionId);
17172
17095
  }
17096
+ /**
17097
+ * 拿当前 sub-session 的 wire 形态 status(已 compress:内部 reducer 'spawning' / 'running-idle' /
17098
+ * 'stopping' 等都压平)。无 runner 返回 'idle'(没 spawn 过)。
17099
+ *
17100
+ * 用途:listener 端 subscribe 时补推一次当前 status —— sessionManager.subscribe 注册的
17101
+ * callback 只接未来 emit,不重放当前 status;不补推则 listener 进入时如果 sub-session
17102
+ * 已 running,永远拿不到 procAlive 信号。owner 路径在 LocalWsServer.onSubscribe hook
17103
+ * 重放 pendingQuestions(同一思路)。
17104
+ */
17105
+ getCurrentStatus(sessionId) {
17106
+ const runner = this.runners.get(sessionId);
17107
+ if (!runner) return "idle";
17108
+ return compressStatus(runner.getState().status);
17109
+ }
17173
17110
  // 给 observer 用:保证有 runner 但不 spawn(仅创建 state 容器)
17174
17111
  ensureSession(file) {
17175
17112
  let r = this.runners.get(file.sessionId);
@@ -17282,7 +17219,9 @@ var SessionManager = class {
17282
17219
  return { events: all.slice(start, end), offset, total };
17283
17220
  }
17284
17221
  /**
17285
- * persona-bound transport 用:订阅 sub-session 实时 ParsedEvent。
17222
+ * persona-bound transport 用:订阅 sub-session owner-side EventFrame(白名单透传)。
17223
+ * 与 owner 路径共享同一组字面量,listener callback 拿到的 frame.type 当前可能是
17224
+ * 'session:event'(ParsedEvent 流)或 'session:status'(procAlive)。
17286
17225
  * 返回 unsubscribe;不破坏现有 wire 广播路径——routeFromRunner 同时 fan-out 到
17287
17226
  * eventSubscribers 和 deps.broadcastFrame
17288
17227
  */
@@ -18453,6 +18392,11 @@ var PersonaBoundHandler = class {
18453
18392
  this.deps.logger.warn(`persona ws send failed: ${err.message}`);
18454
18393
  }
18455
18394
  };
18395
+ const sendError = (requestId, code, message) => {
18396
+ const errFrame = { type: "error", code, message };
18397
+ if (requestId) errFrame.requestId = requestId;
18398
+ send(errFrame);
18399
+ };
18456
18400
  ws.on("message", (raw) => {
18457
18401
  let parsedRaw;
18458
18402
  try {
@@ -18461,18 +18405,19 @@ var PersonaBoundHandler = class {
18461
18405
  ws.close(4400, "PROTOCOL_VIOLATION");
18462
18406
  return;
18463
18407
  }
18464
- const parseResult = PersonaClientFrameSchema.safeParse(parsedRaw);
18465
- if (!parseResult.success) {
18408
+ if (typeof parsedRaw !== "object" || parsedRaw === null) {
18466
18409
  ws.close(4400, "PROTOCOL_VIOLATION");
18467
18410
  return;
18468
18411
  }
18469
- const frame = parseResult.data;
18412
+ const frame = parsedRaw;
18470
18413
  if (!scope) {
18471
- if (frame.kind !== "persona-hello") {
18414
+ const authParse = AuthRequestFrameSchema.safeParse(frame);
18415
+ if (!authParse.success) {
18472
18416
  ws.close(4400, "PROTOCOL_VIOLATION");
18473
18417
  return;
18474
18418
  }
18475
- const verify = this.deps.registry.verifyToken(personaId, frame.token);
18419
+ const { token } = authParse.data;
18420
+ const verify = this.deps.registry.verifyToken(personaId, token);
18476
18421
  if (!verify.ok) {
18477
18422
  const code = verify.code === "PERSONA_DELETED" ? 4404 : verify.code === "PERSONA_NOT_PUBLIC" ? 4403 : 4401;
18478
18423
  ws.close(code, verify.code);
@@ -18487,7 +18432,7 @@ var PersonaBoundHandler = class {
18487
18432
  try {
18488
18433
  const { sessionFile } = this.deps.personaManager.getOrCreateSubSession(
18489
18434
  personaId,
18490
- frame.token
18435
+ token
18491
18436
  );
18492
18437
  subSessionFile = sessionFile;
18493
18438
  } catch (err) {
@@ -18498,111 +18443,128 @@ var PersonaBoundHandler = class {
18498
18443
  return;
18499
18444
  }
18500
18445
  scope = { personaId, subSessionId: subSessionFile.sessionId };
18446
+ send({ type: "auth:ok" });
18501
18447
  send({
18502
- kind: "persona-ready",
18503
- subSessionId: subSessionFile.sessionId,
18504
- personaLabel: persona.label,
18448
+ type: "session:info",
18449
+ sessionId: subSessionFile.sessionId,
18450
+ label: persona.label,
18505
18451
  cwd: this.deps.personaStore.personaDirPath(personaId),
18506
- model: persona.model
18507
- });
18508
- unsubscribe = this.deps.sessionManager.subscribe(
18509
- subSessionFile.sessionId,
18510
- personaId,
18511
- (event) => {
18512
- send({ kind: "event", event });
18513
- }
18514
- );
18515
- const FIRST_PAGE_SIZE = 50;
18516
- let firstPageOffset = 0;
18517
- let firstPageTotal = 0;
18518
- try {
18519
- const page = this.deps.sessionManager.readHistoryPage(
18520
- subSessionFile.sessionId,
18521
- personaId,
18522
- FIRST_PAGE_SIZE,
18523
- 0
18524
- );
18525
- for (const event of page.events) {
18526
- send({ kind: "history-event", event });
18527
- }
18528
- firstPageOffset = page.events.length;
18529
- firstPageTotal = page.total;
18530
- } catch (err) {
18531
- this.deps.logger.warn(
18532
- `persona history read failed: ${err.message}`
18533
- );
18534
- }
18535
- send({
18536
- kind: "history-done",
18537
- offset: firstPageOffset,
18538
- total: firstPageTotal
18452
+ ...persona.model ? { model: persona.model } : {}
18539
18453
  });
18540
18454
  return;
18541
18455
  }
18542
- switch (frame.kind) {
18543
- case "persona-hello":
18544
- ws.close(4400, "PROTOCOL_VIOLATION");
18456
+ const type = frame.type;
18457
+ const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
18458
+ if (type === "auth") {
18459
+ ws.close(4400, "PROTOCOL_VIOLATION");
18460
+ return;
18461
+ }
18462
+ if (type === "ping") {
18463
+ send({ type: "pong", at: Date.now() });
18464
+ return;
18465
+ }
18466
+ const requireScopedSession = () => {
18467
+ if (frame.sessionId !== scope.subSessionId) {
18468
+ sendError(requestId, "FORBIDDEN", "sessionId out of scope");
18469
+ return false;
18470
+ }
18471
+ return true;
18472
+ };
18473
+ switch (type) {
18474
+ case "session:subscribe": {
18475
+ if (!requireScopedSession()) return;
18476
+ if (unsubscribe) unsubscribe();
18477
+ unsubscribe = this.deps.sessionManager.subscribe(
18478
+ scope.subSessionId,
18479
+ scope.personaId,
18480
+ (eventFrame) => send(eventFrame)
18481
+ );
18482
+ if (requestId)
18483
+ send({ type: "subscribed", requestId, sessionId: scope.subSessionId });
18484
+ const currentStatus = this.deps.sessionManager.getCurrentStatus(scope.subSessionId);
18485
+ send({
18486
+ type: "session:status",
18487
+ sessionId: scope.subSessionId,
18488
+ status: currentStatus
18489
+ });
18545
18490
  return;
18546
- case "send": {
18491
+ }
18492
+ case "session:unsubscribe": {
18493
+ if (!requireScopedSession()) return;
18494
+ if (unsubscribe) {
18495
+ unsubscribe();
18496
+ unsubscribe = null;
18497
+ }
18498
+ if (requestId) send({ type: "unsubscribed", requestId, sessionId: scope.subSessionId });
18499
+ return;
18500
+ }
18501
+ case "session:send": {
18502
+ if (!requireScopedSession()) return;
18503
+ const text = frame.text;
18504
+ if (typeof text !== "string") {
18505
+ sendError(requestId, "VALIDATION_ERROR", "text must be string");
18506
+ return;
18507
+ }
18547
18508
  try {
18548
18509
  this.deps.sessionManager.sendForAgent({
18549
18510
  sessionId: scope.subSessionId,
18550
18511
  agentId: scope.personaId,
18551
- text: frame.text
18512
+ text
18552
18513
  });
18514
+ if (requestId) send({ type: "session:send", ok: true, requestId });
18553
18515
  } catch (err) {
18554
- this.deps.logger.warn(
18555
- `persona sendForAgent failed: ${err.message}`
18556
- );
18516
+ const e = err;
18517
+ sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
18557
18518
  }
18558
18519
  return;
18559
18520
  }
18560
- case "reset": {
18521
+ case "session:new": {
18522
+ if (!requireScopedSession()) return;
18561
18523
  try {
18562
18524
  this.deps.sessionManager.resetForAgent({
18563
18525
  sessionId: scope.subSessionId,
18564
18526
  agentId: scope.personaId
18565
18527
  });
18528
+ if (requestId) send({ type: "session:info", sessionId: scope.subSessionId, requestId });
18566
18529
  } catch (err) {
18567
- this.deps.logger.warn(
18568
- `persona resetForAgent failed: ${err.message}`
18569
- );
18530
+ const e = err;
18531
+ sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
18570
18532
  }
18571
18533
  return;
18572
18534
  }
18573
- case "ping":
18574
- send({ kind: "pong" });
18575
- return;
18576
- case "history-page-request": {
18535
+ case "history:read": {
18536
+ if (!requireScopedSession()) return;
18537
+ const limit = typeof frame.limit === "number" && frame.limit > 0 ? frame.limit : 50;
18538
+ const offset = typeof frame.offset === "number" && frame.offset >= 0 ? frame.offset : 0;
18577
18539
  try {
18578
18540
  const page = this.deps.sessionManager.readHistoryPage(
18579
18541
  scope.subSessionId,
18580
18542
  scope.personaId,
18581
- frame.limit,
18582
- frame.offset
18543
+ limit,
18544
+ offset
18583
18545
  );
18584
- send({
18585
- kind: "history-page",
18586
- requestId: frame.requestId,
18587
- events: page.events,
18588
- offset: frame.offset,
18589
- // 请求值原样回传,便于 client 识别越界
18590
- total: page.total
18591
- });
18546
+ if (requestId) {
18547
+ send({
18548
+ type: "history:read",
18549
+ requestId,
18550
+ events: page.events,
18551
+ offset,
18552
+ total: page.total
18553
+ });
18554
+ }
18592
18555
  } catch (err) {
18593
- this.deps.logger.warn(
18594
- `persona history-page-request failed: ${err.message}`
18595
- );
18596
- send({
18597
- kind: "history-page",
18598
- requestId: frame.requestId,
18599
- events: [],
18600
- offset: frame.offset,
18601
- total: 0
18602
- });
18556
+ const e = err;
18557
+ sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
18603
18558
  }
18604
18559
  return;
18605
18560
  }
18561
+ default:
18562
+ sendError(
18563
+ requestId,
18564
+ "METHOD_NOT_ALLOWED",
18565
+ `${typeof type === "string" ? type : "unknown"} not allowed for listener`
18566
+ );
18567
+ return;
18606
18568
  }
18607
18569
  });
18608
18570
  ws.on("close", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",