pubblue 0.6.9 → 0.7.0

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/index.js CHANGED
@@ -12,6 +12,8 @@ import {
12
12
  getConfigDir,
13
13
  getTelegramMiniAppUrl,
14
14
  isClaudeCodeAvailableInEnv,
15
+ isClaudeSdkAvailableInEnv,
16
+ isClaudeSdkImportable,
15
17
  isOpenClawAvailable,
16
18
  liveInfoDir,
17
19
  liveInfoPath,
@@ -21,11 +23,12 @@ import {
21
23
  resolveOpenClawHome,
22
24
  resolveOpenClawWorkspaceDir,
23
25
  runClaudeCodeBridgeStartupProbe,
26
+ runClaudeSdkBridgeStartupProbe,
24
27
  runOpenClawBridgeStartupProbe,
25
28
  saveConfig,
26
29
  toCliFailure,
27
30
  writeLatestCliVersion
28
- } from "./chunk-JSX5KHV3.js";
31
+ } from "./chunk-5ODXW2EM.js";
29
32
 
30
33
  // src/program.ts
31
34
  import { Command } from "commander";
@@ -507,10 +510,10 @@ async function ensureNodeDatachannelAvailable() {
507
510
  }
508
511
  function parseBridgeMode(raw) {
509
512
  const normalized = raw.trim().toLowerCase();
510
- if (normalized === "openclaw" || normalized === "claude-code") {
513
+ if (normalized === "openclaw" || normalized === "claude-code" || normalized === "claude-sdk") {
511
514
  return normalized;
512
515
  }
513
- throw new Error(`--bridge must be one of: openclaw, claude-code. Received: ${raw}`);
516
+ throw new Error(`--bridge must be one of: openclaw, claude-code, claude-sdk. Received: ${raw}`);
514
517
  }
515
518
  function describeConfiguredPath(key, env) {
516
519
  const configured = env[key]?.trim();
@@ -542,6 +545,39 @@ var BRIDGE_PROVIDERS = [
542
545
  ];
543
546
  }
544
547
  },
548
+ {
549
+ mode: "claude-sdk",
550
+ priority: 75,
551
+ detect(env) {
552
+ const cliAvailable = isClaudeSdkAvailableInEnv(env);
553
+ if (!cliAvailable) {
554
+ return {
555
+ available: false,
556
+ detail: `Claude CLI not detected (${describeConfiguredPath("CLAUDE_CODE_PATH", env)})`
557
+ };
558
+ }
559
+ return {
560
+ available: true,
561
+ detail: `Claude CLI detected; SDK import checked at startup (${describeConfiguredPath("CLAUDE_CODE_PATH", env)})`
562
+ };
563
+ },
564
+ async startupProbe(env) {
565
+ const sdkAvailable = await isClaudeSdkImportable();
566
+ if (!sdkAvailable) {
567
+ throw new Error(
568
+ "Claude Agent SDK (@anthropic-ai/claude-agent-sdk) is not importable. Install it or use --bridge claude-code."
569
+ );
570
+ }
571
+ const runtime = await runClaudeSdkBridgeStartupProbe(env);
572
+ const cwd = runtime.cwd || env.PUBBLUE_PROJECT_ROOT || process.cwd();
573
+ return [
574
+ `Claude executable: ${runtime.claudePath}`,
575
+ `Claude SDK: available`,
576
+ `Claude cwd: ${cwd}`,
577
+ 'Claude SDK communication via `pubblue write "pong"`: OK'
578
+ ];
579
+ }
580
+ },
545
581
  {
546
582
  mode: "claude-code",
547
583
  priority: 50,
@@ -955,7 +991,9 @@ function formatPreflightError(params) {
955
991
  }
956
992
  lines.push("", "Debug tips:");
957
993
  lines.push("- Run `pubblue configure` to inspect saved CLI configuration.");
958
- lines.push("- Use `pubblue start --bridge openclaw|claude-code` to force a bridge mode.");
994
+ lines.push(
995
+ "- Use `pubblue start --bridge openclaw|claude-code|claude-sdk` to force a bridge mode."
996
+ );
959
997
  return lines.join("\n");
960
998
  }
961
999
  async function runStartPreflight(opts) {
@@ -1059,7 +1097,7 @@ async function runStartPreflight(opts) {
1059
1097
  // package.json
1060
1098
  var package_default = {
1061
1099
  name: "pubblue",
1062
- version: "0.6.9",
1100
+ version: "0.7.0",
1063
1101
  description: "CLI for publishing and visualizing AI-agent output via pub.blue",
1064
1102
  type: "module",
1065
1103
  bin: {
@@ -1077,6 +1115,9 @@ var package_default = {
1077
1115
  convex: "^1.19.0",
1078
1116
  "node-datachannel": "^0.32.0"
1079
1117
  },
1118
+ optionalDependencies: {
1119
+ "@anthropic-ai/claude-agent-sdk": "^0.2.0"
1120
+ },
1080
1121
  devDependencies: {
1081
1122
  "@types/node": "22.10.2",
1082
1123
  tsup: "^8.3.6",
@@ -1136,7 +1177,7 @@ function printLocalRuntimeSummary() {
1136
1177
  }
1137
1178
  }
1138
1179
  function registerStartCommand(program2) {
1139
- program2.command("start").description("Start the agent daemon (registers presence, awaits live requests)").requiredOption("--agent-name <name>", "Agent display name shown to the browser user").option("--bridge <mode>", "Bridge mode: openclaw|claude-code").action(async (opts) => {
1180
+ program2.command("start").description("Start the agent daemon (registers presence, awaits live requests)").requiredOption("--agent-name <name>", "Agent display name shown to the browser user").option("--bridge <mode>", "Bridge mode: openclaw|claude-code|claude-sdk").action(async (opts) => {
1140
1181
  writeLatestCliVersion(CLI_VERSION);
1141
1182
  const preflight = await runStartPreflight({ bridge: opts.bridge });
1142
1183
  const { runtimeConfig, bridgeMode, bridgeProcessEnv } = preflight;
@@ -1423,9 +1464,6 @@ function registerDoctorCommand(program2) {
1423
1464
  if (live.status !== "active") {
1424
1465
  fail(`API reports live is not active (status: ${live.status})`);
1425
1466
  }
1426
- if (live.expiresAt <= Date.now()) {
1427
- fail("API reports live is expired.");
1428
- }
1429
1467
  if (typeof live.browserOffer !== "string" || live.browserOffer.length === 0) {
1430
1468
  fail("browser offer was not published.");
1431
1469
  }
@@ -1499,7 +1537,7 @@ function registerDoctorCommand(program2) {
1499
1537
 
1500
1538
  // src/commands/pubs.ts
1501
1539
  function registerPubCommands(program2) {
1502
- program2.command("create").description("Create a new pub").argument("[file]", "Path to the file (reads stdin if omitted)").option("--slug <slug>", "Custom slug for the URL").option("--title <title>", "Title for the pub").option("--public", "Make the pub public").option("--private", "Make the pub private (default)").option("--expires <duration>", "Auto-delete after duration (e.g. 1h, 24h, 7d)").action(
1540
+ program2.command("create").description("Create a new pub").argument("[file]", "Path to the file (reads stdin if omitted)").option("--slug <slug>", "Custom slug for the URL").option("--title <title>", "Title for the pub").option("--public", "Make the pub public").option("--private", "Make the pub private (default)").action(
1503
1541
  async (fileArg, opts) => {
1504
1542
  const client = createClient();
1505
1543
  let content;
@@ -1521,15 +1559,11 @@ function registerPubCommands(program2) {
1521
1559
  filename,
1522
1560
  title: opts.title,
1523
1561
  slug: opts.slug,
1524
- isPublic: resolvedVisibility ?? false,
1525
- expiresIn: opts.expires
1562
+ isPublic: resolvedVisibility ?? false
1526
1563
  });
1527
1564
  console.log(`Created: ${result.url}`);
1528
1565
  const tmaUrl = getTelegramMiniAppUrl(result.slug);
1529
1566
  if (tmaUrl) console.log(`Telegram: ${tmaUrl}`);
1530
- if (result.expiresAt) {
1531
- console.log(` Expires: ${new Date(result.expiresAt).toISOString()}`);
1532
- }
1533
1567
  }
1534
1568
  );
1535
1569
  program2.command("get").description("Get details of a pub").argument("<slug>", "Slug of the pub").option("--content", "Output raw content to stdout (no metadata, pipeable)").action(async (slug, opts) => {
@@ -1543,14 +1577,12 @@ function registerPubCommands(program2) {
1543
1577
  if (pub.contentType) console.log(` Type: ${pub.contentType}`);
1544
1578
  if (pub.title) console.log(` Title: ${pub.title}`);
1545
1579
  console.log(` Status: ${formatVisibility(pub.isPublic)}`);
1546
- if (pub.expiresAt) console.log(` Expires: ${new Date(pub.expiresAt).toISOString()}`);
1547
1580
  console.log(` Created: ${new Date(pub.createdAt).toLocaleDateString()}`);
1548
1581
  console.log(` Updated: ${new Date(pub.updatedAt).toLocaleDateString()}`);
1549
1582
  if (pub.content) console.log(` Size: ${pub.content.length} bytes`);
1550
1583
  if (pub.live) {
1551
1584
  console.log(` Live: ${pub.live.status}`);
1552
1585
  console.log(` Connected: ${pub.live.hasConnection ? "yes" : "no"}`);
1553
- console.log(` Expires: ${new Date(pub.live.expiresAt).toISOString()}`);
1554
1586
  }
1555
1587
  });
1556
1588
  program2.command("update").description("Update a pub's content and/or metadata").argument("<slug>", "Slug of the pub to update").option("--file <file>", "New content from file").option("--title <title>", "New title").option("--public", "Make the pub public").option("--private", "Make the pub private").option("--slug <newSlug>", "Rename the slug").action(
@@ -1590,11 +1622,10 @@ function registerPubCommands(program2) {
1590
1622
  }
1591
1623
  for (const pub of pubs) {
1592
1624
  const date = new Date(pub.createdAt).toLocaleDateString();
1593
- const expires = pub.expiresAt ? ` expires:${new Date(pub.expiresAt).toISOString()}` : "";
1594
1625
  const contentLabel = pub.contentType ? `[${pub.contentType}]` : "[no content]";
1595
1626
  const sessionLabel = pub.live?.status === "active" ? " [live]" : "";
1596
1627
  console.log(
1597
- ` ${pub.slug} ${contentLabel} ${formatVisibility(pub.isPublic)} ${date}${expires}${sessionLabel}`
1628
+ ` ${pub.slug} ${contentLabel} ${formatVisibility(pub.isPublic)} ${date}${sessionLabel}`
1598
1629
  );
1599
1630
  }
1600
1631
  });
@@ -5,6 +5,7 @@ import {
5
5
  buildClaudeArgs,
6
6
  buildSessionBriefing,
7
7
  createClaudeCodeBridgeRunner,
8
+ createClaudeSdkBridgeRunner,
8
9
  createOpenClawBridgeRunner,
9
10
  decodeMessage,
10
11
  encodeMessage,
@@ -19,7 +20,7 @@ import {
19
20
  resolveOpenClawRuntime,
20
21
  shouldAcknowledgeMessage,
21
22
  writeLiveSessionContentFile
22
- } from "./chunk-JSX5KHV3.js";
23
+ } from "./chunk-5ODXW2EM.js";
23
24
 
24
25
  // src/lib/live-daemon.ts
25
26
  import { randomUUID } from "crypto";
@@ -39,9 +40,6 @@ import { spawn } from "child_process";
39
40
  // ../shared/command-protocol-core.ts
40
41
  var COMMAND_PROTOCOL_VERSION = 1;
41
42
  var COMMAND_MANIFEST_MAX_FUNCTIONS = 64;
42
- function makeCommandBindResultMessage(payload) {
43
- return makeEventMessage("command.bind.result", payload);
44
- }
45
43
  function makeCommandResultMessage(payload) {
46
44
  return makeEventMessage("command.result", payload);
47
45
  }
@@ -143,18 +141,6 @@ function parseFunctionList(input) {
143
141
  function parseMetaRecord(msg) {
144
142
  return msg.type === "event" && msg.meta ? readRecord(msg.meta) : null;
145
143
  }
146
- function parseCommandBindMessage(msg) {
147
- if (msg.type !== "event" || msg.data !== "command.bind") return null;
148
- const meta = parseMetaRecord(msg);
149
- if (!meta) return null;
150
- const manifestId = readString(meta.manifestId);
151
- if (!manifestId) return null;
152
- return {
153
- v: readFiniteNumber(meta.v) ?? COMMAND_PROTOCOL_VERSION,
154
- manifestId,
155
- functions: parseFunctionList(meta.functions)
156
- };
157
- }
158
144
  function parseCommandInvokeMessage(msg) {
159
145
  if (msg.type !== "event" || msg.data !== "command.invoke") return null;
160
146
  const meta = parseMetaRecord(msg);
@@ -182,6 +168,28 @@ function parseCommandCancelMessage(msg) {
182
168
  reason: readString(meta.reason)
183
169
  };
184
170
  }
171
+ var MANIFEST_SCRIPT_RE = /<script\s[^>]*type\s*=\s*["']application\/pubblue-command-manifest\+json["'][^>]*>([\s\S]*?)<\/script>/i;
172
+ function extractManifestFromHtml(html) {
173
+ const match = MANIFEST_SCRIPT_RE.exec(html);
174
+ if (!match?.[1]) return null;
175
+ const raw = match[1].trim();
176
+ if (raw.length === 0) return null;
177
+ let parsed;
178
+ try {
179
+ parsed = JSON.parse(raw);
180
+ } catch {
181
+ return null;
182
+ }
183
+ if (!parsed || typeof parsed !== "object") return null;
184
+ const record = parsed;
185
+ const manifestId = typeof record.manifestId === "string" && record.manifestId.length > 0 ? record.manifestId : `manifest-${Date.now().toString(36)}`;
186
+ const functions = parseFunctionList(record.functions);
187
+ return {
188
+ v: typeof record.version === "number" ? record.version : 1,
189
+ manifestId,
190
+ functions
191
+ };
192
+ }
185
193
 
186
194
  // src/lib/live-command-handler.ts
187
195
  var DEFAULT_RECENT_RESULT_TTL_MS = 12e4;
@@ -433,9 +441,6 @@ function createLiveCommandHandler(params) {
433
441
  });
434
442
  await params.sendCommandMessage(makeCommandResultMessage(payload));
435
443
  }
436
- async function sendBindResult(payload) {
437
- await params.sendCommandMessage(makeCommandBindResultMessage(payload));
438
- }
439
444
  async function executeFunction(spec, args, abortSignal) {
440
445
  const executor = spec.executor;
441
446
  if (!executor) {
@@ -498,39 +503,27 @@ function createLiveCommandHandler(params) {
498
503
  signal: abortSignal
499
504
  });
500
505
  }
501
- async function handleBind(message) {
502
- params.debugLog(
503
- `command:bind manifestId=${message.manifestId} functions=[${message.functions.map((f) => f.name).join(", ")}]`
504
- );
505
- const accepted = [];
506
- const rejected = [];
506
+ function bindFunctions(functions) {
507
507
  boundFunctions.clear();
508
- for (const entry of message.functions) {
508
+ for (const entry of functions) {
509
509
  const normalized = normalizeFunctionSpec(entry);
510
510
  if (!normalized.executor) {
511
- params.debugLog(`command:bind rejected "${normalized.name}" \u2014 missing executor`);
512
- rejected.push({
513
- name: normalized.name,
514
- code: "INVALID_FUNCTION",
515
- message: `Function "${normalized.name}" is missing executor definition.`
516
- });
511
+ params.debugLog(`commands skipped "${normalized.name}" \u2014 missing executor`);
517
512
  continue;
518
513
  }
519
514
  boundFunctions.set(normalized.name, normalized);
520
- accepted.push({
521
- name: normalized.name,
522
- returns: normalized.returns ?? "void"
523
- });
524
515
  }
525
- params.debugLog(
526
- `command:bind result accepted=[${accepted.map((a) => a.name).join(", ")}] rejected=[${rejected.map((r) => r.name).join(", ")}]`
527
- );
528
- await sendBindResult({
529
- v: COMMAND_PROTOCOL_VERSION,
530
- manifestId: message.manifestId,
531
- accepted,
532
- rejected
533
- });
516
+ params.debugLog(`commands bound=[${[...boundFunctions.keys()].join(", ")}]`);
517
+ }
518
+ function bindFromHtml(html) {
519
+ const manifest = extractManifestFromHtml(html);
520
+ if (!manifest) {
521
+ boundFunctions.clear();
522
+ params.debugLog("commands no manifest found in HTML, cleared bindings");
523
+ return;
524
+ }
525
+ params.debugLog(`commands manifestId=${manifest.manifestId}`);
526
+ bindFunctions(manifest.functions);
534
527
  }
535
528
  async function handleInvoke(message) {
536
529
  if (!message) return;
@@ -552,7 +545,7 @@ function createLiveCommandHandler(params) {
552
545
  }
553
546
  const spec = getSpec(message.name);
554
547
  if (!spec) {
555
- params.debugLog(`command:invoke COMMAND_NOT_FOUND "${message.name}"`);
548
+ params.debugLog(`commands invoke COMMAND_NOT_FOUND "${message.name}"`);
556
549
  await sendResult({
557
550
  v: COMMAND_PROTOCOL_VERSION,
558
551
  callId: message.callId,
@@ -566,7 +559,7 @@ function createLiveCommandHandler(params) {
566
559
  return;
567
560
  }
568
561
  params.debugLog(
569
- `command:invoke "${message.name}" callId=${message.callId} args=${JSON.stringify(message.args ?? {}).slice(0, 200)}`
562
+ `commands invoke "${message.name}" callId=${message.callId} args=${JSON.stringify(message.args ?? {}).slice(0, 200)}`
570
563
  );
571
564
  const abort = new AbortController();
572
565
  const startedAt = Date.now();
@@ -576,14 +569,14 @@ function createLiveCommandHandler(params) {
576
569
  const active = running.get(message.callId);
577
570
  if (abort.signal.aborted || active?.cancelled) {
578
571
  params.debugLog(
579
- `command:invoke "${message.name}" cancelled after ${Date.now() - startedAt}ms`
572
+ `commands invoke "${message.name}" cancelled after ${Date.now() - startedAt}ms`
580
573
  );
581
574
  await sendResult(buildCancelledResult(message.callId, startedAt));
582
575
  return;
583
576
  }
584
577
  const durationMs = Date.now() - startedAt;
585
578
  params.debugLog(
586
- `command:invoke "${message.name}" ok=${true} duration=${durationMs}ms value=${JSON.stringify(value).slice(0, 200)}`
579
+ `commands invoke "${message.name}" ok=${true} duration=${durationMs}ms value=${JSON.stringify(value).slice(0, 200)}`
587
580
  );
588
581
  await sendResult({
589
582
  v: COMMAND_PROTOCOL_VERSION,
@@ -600,7 +593,7 @@ function createLiveCommandHandler(params) {
600
593
  }
601
594
  const durationMs = Date.now() - startedAt;
602
595
  params.debugLog(
603
- `command:invoke "${message.name}" FAILED duration=${durationMs}ms error=${detail.slice(0, 300)}`
596
+ `commands invoke "${message.name}" FAILED duration=${durationMs}ms error=${detail.slice(0, 300)}`
604
597
  );
605
598
  await sendResult({
606
599
  v: COMMAND_PROTOCOL_VERSION,
@@ -623,18 +616,13 @@ function createLiveCommandHandler(params) {
623
616
  async function handleBridgeMessage(message) {
624
617
  if (message.type !== "event") return;
625
618
  params.debugLog(
626
- `command:message type=${message.type} data=${typeof message.data === "string" ? message.data.slice(0, 120) : "?"}`
619
+ `commands message type=${message.type} data=${typeof message.data === "string" ? message.data.slice(0, 120) : "?"}`
627
620
  );
628
621
  for (const [callId, result] of recentResults) {
629
622
  if (result.expiresAt <= Date.now()) {
630
623
  recentResults.delete(callId);
631
624
  }
632
625
  }
633
- const bind = parseCommandBindMessage(message);
634
- if (bind) {
635
- await handleBind(bind);
636
- return;
637
- }
638
626
  const invoke = parseCommandInvokeMessage(message);
639
627
  if (invoke) {
640
628
  await handleInvoke(invoke);
@@ -646,6 +634,9 @@ function createLiveCommandHandler(params) {
646
634
  }
647
635
  }
648
636
  return {
637
+ bindFromHtml(html) {
638
+ bindFromHtml(html);
639
+ },
649
640
  stop() {
650
641
  for (const [callId, active] of running) {
651
642
  active.abort.abort();
@@ -757,9 +748,26 @@ function createDaemonIpcHandler(params) {
757
748
  switch (req.method) {
758
749
  case "write": {
759
750
  const channel = req.params.channel || "chat";
751
+ const msg = req.params.msg;
752
+ if (channel === CHANNELS.CANVAS && msg.type === "html" && typeof msg.data === "string") {
753
+ const slug = params.getActiveSlug();
754
+ if (!slug) return { ok: false, error: "No active live session." };
755
+ try {
756
+ await params.apiClient.update({
757
+ slug,
758
+ content: msg.data,
759
+ filename: "live-canvas.html"
760
+ });
761
+ params.bindCanvasCommands(msg.data);
762
+ return { ok: true, delivered: true };
763
+ } catch (error) {
764
+ const errMsg = error instanceof Error ? error.message : String(error);
765
+ params.markError(`failed to persist canvas HTML for "${slug}"`, error);
766
+ return { ok: false, error: `Canvas update failed: ${errMsg}` };
767
+ }
768
+ }
760
769
  const readinessError = params.getWriteReadinessError();
761
770
  if (readinessError) return { ok: false, error: readinessError };
762
- const msg = req.params.msg;
763
771
  const binaryBase64 = typeof req.params.binaryBase64 === "string" ? req.params.binaryBase64 : void 0;
764
772
  const binaryPayload = msg.type === "binary" && binaryBase64 ? Buffer.from(binaryBase64, "base64") : void 0;
765
773
  const maxAttempts = Math.max(1, params.writeAckMaxAttempts);
@@ -809,7 +817,6 @@ function createDaemonIpcHandler(params) {
809
817
  continue;
810
818
  }
811
819
  }
812
- params.trackOutboundMessage(channel, msg);
813
820
  return { ok: true, delivered: true };
814
821
  }
815
822
  return {
@@ -937,7 +944,7 @@ var CANVAS_COMMAND_PROTOCOL_GUIDE_MARKDOWN = [
937
944
 
938
945
  // src/lib/live-daemon-shared.ts
939
946
  function buildBridgeInstructions(mode) {
940
- if (mode === "claude-code") {
947
+ if (mode === "claude-code" || mode === "claude-sdk") {
941
948
  return {
942
949
  replyHint: 'Reply command: pubblue write "<your reply>"',
943
950
  canvasHint: "Canvas command: pubblue write -c canvas -f /path/to/file.html",
@@ -974,13 +981,6 @@ function shouldRecoverForBrowserOfferChange(params) {
974
981
  if (!lastAppliedBrowserOffer) return false;
975
982
  return incomingBrowserOffer !== lastAppliedBrowserOffer;
976
983
  }
977
- function readCanvasHtmlFromOutbound(params) {
978
- if (params.channel !== CHANNELS.CANVAS) return null;
979
- if (params.msg.type !== "html") return null;
980
- if (typeof params.msg.data !== "string") return null;
981
- if (params.msg.data.length === 0) return null;
982
- return params.msg.data;
983
- }
984
984
 
985
985
  // src/lib/live-daemon-signaling.ts
986
986
  import { ConvexClient } from "convex/browser";
@@ -1034,7 +1034,7 @@ function parseLiveSnapshot(result) {
1034
1034
  if (!Array.isArray(live.agentCandidates)) {
1035
1035
  throw new Error("Invalid signaling snapshot: missing agentCandidates");
1036
1036
  }
1037
- if (typeof live.createdAt !== "number" || typeof live.expiresAt !== "number") {
1037
+ if (typeof live.createdAt !== "number") {
1038
1038
  throw new Error("Invalid signaling snapshot: missing timestamps");
1039
1039
  }
1040
1040
  return {
@@ -1044,8 +1044,7 @@ function parseLiveSnapshot(result) {
1044
1044
  agentAnswer: live.agentAnswer,
1045
1045
  browserCandidates: live.browserCandidates,
1046
1046
  agentCandidates: live.agentCandidates,
1047
- createdAt: live.createdAt,
1048
- expiresAt: live.expiresAt
1047
+ createdAt: live.createdAt
1049
1048
  };
1050
1049
  }
1051
1050
  function createSignalingController(params) {
@@ -1203,9 +1202,7 @@ async function startDaemon(config) {
1203
1202
  let pendingInboundBinaryMeta = /* @__PURE__ */ new Map();
1204
1203
  let inboundStreams = /* @__PURE__ */ new Map();
1205
1204
  let seenInboundMessageKeys = /* @__PURE__ */ new Set();
1206
- let lastCanvasSnapshot = null;
1207
- let lastPersistedCanvasSnapshot = null;
1208
- let persistCanvasQueue = Promise.resolve();
1205
+ let commandProcessingQueue = Promise.resolve();
1209
1206
  let heartbeatTimer = null;
1210
1207
  let localCandidateInterval = null;
1211
1208
  let localCandidateStopTimer = null;
@@ -1222,14 +1219,10 @@ async function startDaemon(config) {
1222
1219
  markError,
1223
1220
  sendCommandMessage: async (msg) => {
1224
1221
  if (!isLiveConnected()) return false;
1225
- const sent = await sendOutboundMessageWithAck(CHANNELS.COMMAND, msg, {
1222
+ return sendOutboundMessageWithAck(CHANNELS.COMMAND, msg, {
1226
1223
  context: 'command outbound on "command"',
1227
1224
  maxAttempts: OUTBOUND_SEND_MAX_ATTEMPTS
1228
1225
  });
1229
- if (sent) {
1230
- trackOutboundMessage(CHANNELS.COMMAND, msg);
1231
- }
1232
- return sent;
1233
1226
  }
1234
1227
  });
1235
1228
  function debugLog(message, error) {
@@ -1324,46 +1317,8 @@ async function startDaemon(config) {
1324
1317
  buffer.messages.splice(0, buffer.messages.length - MAX_BUFFERED_MESSAGES);
1325
1318
  }
1326
1319
  }
1327
- function getActiveCanvasSnapshot() {
1328
- if (!activeSlug || !lastCanvasSnapshot) return null;
1329
- if (lastCanvasSnapshot.slug !== activeSlug) return null;
1330
- return lastCanvasSnapshot;
1331
- }
1332
- function isSameCanvasSnapshot(a, b) {
1333
- if (!a || !b) return false;
1334
- return a.slug === b.slug && a.html === b.html;
1335
- }
1336
- function queuePersistCanvasSnapshot(snapshot, reason) {
1337
- if (!snapshot) return Promise.resolve();
1338
- if (isSameCanvasSnapshot(lastPersistedCanvasSnapshot, snapshot)) return Promise.resolve();
1339
- persistCanvasQueue = persistCanvasQueue.then(async () => {
1340
- if (isSameCanvasSnapshot(lastPersistedCanvasSnapshot, snapshot)) return;
1341
- try {
1342
- await apiClient2.update({
1343
- slug: snapshot.slug,
1344
- content: snapshot.html,
1345
- filename: "live-canvas.html"
1346
- });
1347
- lastPersistedCanvasSnapshot = snapshot;
1348
- debugLog(`persisted latest canvas for "${snapshot.slug}" (${reason})`);
1349
- } catch (error) {
1350
- markError(`failed to persist latest canvas for "${snapshot.slug}" (${reason})`, error);
1351
- if (!debugEnabled) {
1352
- console.error(
1353
- `[pubblue-agent] failed to persist latest canvas for "${snapshot.slug}" (${reason}): ${errorMessage(error)}`
1354
- );
1355
- }
1356
- }
1357
- });
1358
- return persistCanvasQueue;
1359
- }
1360
- function trackOutboundMessage(channel, msg) {
1361
- const html = readCanvasHtmlFromOutbound({ channel, msg });
1362
- if (!html || !activeSlug) return;
1363
- lastCanvasSnapshot = { slug: activeSlug, html };
1364
- }
1365
1320
  function handleConnectionClosed(reason) {
1366
- void queuePersistCanvasSnapshot(getActiveCanvasSnapshot(), reason);
1321
+ debugLog(`connection closed: ${reason}`);
1367
1322
  const hadConnection = browserConnected || bridgePrimed;
1368
1323
  browserConnected = false;
1369
1324
  bridgePrimed = false;
@@ -1466,7 +1421,9 @@ async function startDaemon(config) {
1466
1421
  queueAck(msg.id, name);
1467
1422
  }
1468
1423
  if (name === CHANNELS.COMMAND) {
1469
- void commandHandler.onMessage(msg);
1424
+ commandProcessingQueue = commandProcessingQueue.then(() => commandHandler.onMessage(msg)).catch((error) => {
1425
+ markError("command message processing failed", error);
1426
+ });
1470
1427
  return;
1471
1428
  }
1472
1429
  appendBufferedMessage({ channel: name, msg, timestamp: Date.now() });
@@ -1756,10 +1713,6 @@ async function startDaemon(config) {
1756
1713
  if (recovering) return;
1757
1714
  recovering = true;
1758
1715
  try {
1759
- const previousCanvasSnapshot = getActiveCanvasSnapshot();
1760
- if (previousCanvasSnapshot && previousCanvasSnapshot.slug !== slug) {
1761
- void queuePersistCanvasSnapshot(previousCanvasSnapshot, `session-switch:${slug}`);
1762
- }
1763
1716
  await stopBridge();
1764
1717
  closeCurrentPeer();
1765
1718
  createPeer();
@@ -1834,6 +1787,8 @@ async function startDaemon(config) {
1834
1787
  }
1835
1788
  }, HEARTBEAT_INTERVAL_MS);
1836
1789
  const handleIpcRequest = createDaemonIpcHandler({
1790
+ apiClient: apiClient2,
1791
+ bindCanvasCommands: (html) => commandHandler.bindFromHtml(html),
1837
1792
  getConnected: () => isLiveConnected(),
1838
1793
  getSignalingConnected: () => {
1839
1794
  const state = signaling.status();
@@ -1854,7 +1809,6 @@ async function startDaemon(config) {
1854
1809
  waitForChannelOpen,
1855
1810
  waitForDeliveryAck,
1856
1811
  settlePendingAck,
1857
- trackOutboundMessage,
1858
1812
  markError,
1859
1813
  shutdown: () => {
1860
1814
  void shutdown();
@@ -1874,18 +1828,17 @@ async function startDaemon(config) {
1874
1828
  signaling.start();
1875
1829
  async function sendOnChannel(channel, msg) {
1876
1830
  if (stopped || !isLiveConnected()) return false;
1877
- const sent = await sendOutboundMessageWithAck(channel, msg, {
1831
+ return sendOutboundMessageWithAck(channel, msg, {
1878
1832
  context: `bridge outbound on "${channel}"`,
1879
1833
  maxAttempts: OUTBOUND_SEND_MAX_ATTEMPTS
1880
1834
  });
1881
- if (sent) {
1882
- trackOutboundMessage(channel, msg);
1883
- }
1884
- return sent;
1885
1835
  }
1886
1836
  async function buildInitialSessionBriefing(params) {
1887
1837
  const pub = await apiClient2.get(params.slug);
1888
1838
  const content = typeof pub.content === "string" ? pub.content : "";
1839
+ if (content.length > 0) {
1840
+ commandHandler.bindFromHtml(content);
1841
+ }
1889
1842
  const canvasContentFilePath = content.length > 0 ? writeLiveSessionContentFile({
1890
1843
  slug: params.slug,
1891
1844
  contentType: pub.contentType,
@@ -1928,7 +1881,7 @@ async function startDaemon(config) {
1928
1881
  debugLog,
1929
1882
  instructions
1930
1883
  };
1931
- const runner = config.bridgeMode === "claude-code" ? await createClaudeCodeBridgeRunner(bridgeConfig, abort.signal) : await createOpenClawBridgeRunner(bridgeConfig);
1884
+ const runner = config.bridgeMode === "claude-sdk" ? await createClaudeSdkBridgeRunner(bridgeConfig, abort.signal) : config.bridgeMode === "claude-code" ? await createClaudeCodeBridgeRunner(bridgeConfig, abort.signal) : await createOpenClawBridgeRunner(bridgeConfig);
1932
1885
  if (stopped || activeSlug !== slug || abort.signal.aborted) {
1933
1886
  await runner.stop();
1934
1887
  return;
@@ -1974,7 +1927,6 @@ async function startDaemon(config) {
1974
1927
  clearHeartbeatTimer();
1975
1928
  stopPingPong();
1976
1929
  await signaling.stop();
1977
- await queuePersistCanvasSnapshot(getActiveCanvasSnapshot(), "daemon-shutdown");
1978
1930
  try {
1979
1931
  await apiClient2.goOffline({ daemonSessionId });
1980
1932
  } catch (error) {