pubblue 0.6.9 → 0.7.2

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.
@@ -33,6 +33,59 @@ function toCliFailure(error) {
33
33
  };
34
34
  }
35
35
 
36
+ // ../shared/protocol-runtime-core.ts
37
+ function readRecord(input) {
38
+ return input && typeof input === "object" && !Array.isArray(input) ? input : null;
39
+ }
40
+ function readString(input) {
41
+ return typeof input === "string" ? input : void 0;
42
+ }
43
+ function readNonEmptyString(input) {
44
+ return typeof input === "string" && input.trim().length > 0 ? input : void 0;
45
+ }
46
+ function readFiniteNumber(input) {
47
+ return typeof input === "number" && Number.isFinite(input) ? input : void 0;
48
+ }
49
+ function readBoolean(input) {
50
+ return typeof input === "boolean" ? input : void 0;
51
+ }
52
+ function readStringArray(input) {
53
+ if (!Array.isArray(input)) return void 0;
54
+ const values = input.filter((entry) => typeof entry === "string");
55
+ return values.length === input.length ? values : void 0;
56
+ }
57
+ function readStringRecord(input) {
58
+ const record = readRecord(input);
59
+ if (!record) return void 0;
60
+ const entries = Object.entries(record);
61
+ const values = entries.filter((entry) => typeof entry[1] === "string");
62
+ if (values.length !== entries.length) return void 0;
63
+ return Object.fromEntries(values);
64
+ }
65
+
66
+ // ../shared/live-api-core.ts
67
+ function parseLiveInfo(input) {
68
+ if (input === null || input === void 0) return null;
69
+ const record = readRecord(input);
70
+ if (!record) return null;
71
+ const slug = readNonEmptyString(record.slug);
72
+ const browserCandidates = readStringArray(record.browserCandidates);
73
+ const agentCandidates = readStringArray(record.agentCandidates);
74
+ const createdAt = readFiniteNumber(record.createdAt);
75
+ if (!slug || !browserCandidates || !agentCandidates || createdAt === void 0) {
76
+ return null;
77
+ }
78
+ return {
79
+ slug,
80
+ status: readString(record.status),
81
+ browserOffer: readString(record.browserOffer),
82
+ agentAnswer: readString(record.agentAnswer),
83
+ browserCandidates,
84
+ agentCandidates,
85
+ createdAt
86
+ };
87
+ }
88
+
36
89
  // src/lib/api.ts
37
90
  var PubApiError = class extends Error {
38
91
  constructor(message, status, retryAfterSeconds) {
@@ -56,8 +109,8 @@ var PubApiClient = class {
56
109
  getApiKey() {
57
110
  return this.apiKey;
58
111
  }
59
- async request(path4, options = {}) {
60
- const url = new URL(path4, this.baseUrl);
112
+ async request(path5, options = {}) {
113
+ const url = new URL(path5, this.baseUrl);
61
114
  const res = await fetch(url, {
62
115
  ...options,
63
116
  headers: {
@@ -163,15 +216,19 @@ var PubApiClient = class {
163
216
  });
164
217
  }
165
218
  // -- Agent live management ------------------------------------------------
166
- async getPendingLive(daemonSessionId) {
219
+ async getLive(daemonSessionId) {
167
220
  const params = new URLSearchParams();
168
221
  if (daemonSessionId) {
169
222
  params.set("daemonSessionId", daemonSessionId);
170
223
  }
171
224
  const query = params.toString();
172
- const path4 = query ? `/api/v1/agent/live?${query}` : "/api/v1/agent/live";
173
- const data = await this.request(path4);
174
- return data.live;
225
+ const path5 = query ? `/api/v1/agent/live?${query}` : "/api/v1/agent/live";
226
+ const data = await this.request(path5);
227
+ const live = parseLiveInfo(data.live);
228
+ if (data.live !== null && data.live !== void 0 && live === null) {
229
+ throw new PubApiError("Invalid live snapshot response from server.", 502);
230
+ }
231
+ return live;
175
232
  }
176
233
  async signalAnswer(opts) {
177
234
  await this.request("/api/v1/agent/live/signal", {
@@ -185,8 +242,8 @@ var PubApiClient = class {
185
242
  params.set("daemonSessionId", daemonSessionId);
186
243
  }
187
244
  const query = params.toString();
188
- const path4 = query ? `/api/v1/agent/live?${query}` : "/api/v1/agent/live";
189
- await this.request(path4, { method: "DELETE" });
245
+ const path5 = query ? `/api/v1/agent/live?${query}` : "/api/v1/agent/live";
246
+ await this.request(path5, { method: "DELETE" });
190
247
  }
191
248
  // -- Telegram bot token ---------------------------------------------------
192
249
  async uploadBotToken(opts) {
@@ -198,20 +255,21 @@ var PubApiClient = class {
198
255
  async deleteBotToken() {
199
256
  await this.request("/api/v1/agent/telegram-bot", { method: "DELETE" });
200
257
  }
201
- // -- Per-slug live info ---------------------------------------------------
202
- async getLive(slug) {
203
- const data = await this.request(
204
- `/api/v1/pubs/${encodeURIComponent(slug)}/live`
205
- );
206
- return data.live;
207
- }
208
258
  };
209
259
 
210
260
  // ../shared/bridge-protocol-core.ts
261
+ var BRIDGE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
262
+ "text",
263
+ "html",
264
+ "binary",
265
+ "stream-start",
266
+ "stream-data",
267
+ "stream-end",
268
+ "event"
269
+ ]);
211
270
  var CONTROL_CHANNEL = "_control";
212
271
  var CHANNELS = {
213
272
  CHAT: "chat",
214
- CANVAS: "canvas",
215
273
  RENDER_ERROR: "render-error",
216
274
  AUDIO: "audio",
217
275
  MEDIA: "media",
@@ -228,13 +286,51 @@ function generateMessageId() {
228
286
  function encodeMessage(msg) {
229
287
  return JSON.stringify(msg);
230
288
  }
289
+ function parseBridgeMessageMeta(input) {
290
+ if (input === void 0) return void 0;
291
+ const record = readRecord(input);
292
+ if (!record) return null;
293
+ const meta = { ...record };
294
+ const knownStringKeys = ["mime", "filename", "title"];
295
+ for (const key of knownStringKeys) {
296
+ if (record[key] === void 0) continue;
297
+ const value = readString(record[key]);
298
+ if (value === void 0) return null;
299
+ meta[key] = value;
300
+ }
301
+ const knownNumberKeys = ["sampleRate", "width", "height", "size"];
302
+ for (const key of knownNumberKeys) {
303
+ if (record[key] === void 0) continue;
304
+ const value = readFiniteNumber(record[key]);
305
+ if (value === void 0) return null;
306
+ meta[key] = value;
307
+ }
308
+ return meta;
309
+ }
310
+ function parseBridgeMessage(input) {
311
+ const record = readRecord(input);
312
+ if (!record) return null;
313
+ const id = readNonEmptyString(record.id);
314
+ const type = readString(record.type);
315
+ if (!id || !type || !BRIDGE_MESSAGE_TYPES.has(type)) {
316
+ return null;
317
+ }
318
+ const data = record.data === void 0 ? void 0 : readString(record.data);
319
+ if (record.data !== void 0 && data === void 0) {
320
+ return null;
321
+ }
322
+ const meta = parseBridgeMessageMeta(record.meta);
323
+ if (meta === null) return null;
324
+ return {
325
+ id,
326
+ type,
327
+ data,
328
+ meta
329
+ };
330
+ }
231
331
  function decodeMessage(raw) {
232
332
  try {
233
- const parsed = JSON.parse(raw);
234
- if (parsed && typeof parsed.id === "string" && typeof parsed.type === "string") {
235
- return parsed;
236
- }
237
- return null;
333
+ return parseBridgeMessage(JSON.parse(raw));
238
334
  } catch (_error) {
239
335
  return null;
240
336
  }
@@ -327,7 +423,6 @@ function buildSessionBriefing(slug, ctx, instructions) {
327
423
  "## Pub Context"
328
424
  ];
329
425
  if (ctx.title) lines.push(`- Title: ${ctx.title}`);
330
- if (ctx.contentType) lines.push(`- Content type: ${ctx.contentType}`);
331
426
  if (ctx.isPublic !== void 0)
332
427
  lines.push(`- Visibility: ${ctx.isPublic ? "public" : "private"}`);
333
428
  if (ctx.canvasContentFilePath) {
@@ -809,25 +904,269 @@ async function createClaudeCodeBridgeRunner(config, abortSignal) {
809
904
  };
810
905
  }
811
906
 
907
+ // src/lib/live-bridge-claude-sdk.ts
908
+ import * as fs2 from "fs";
909
+ import * as os2 from "os";
910
+ import * as path2 from "path";
911
+ async function tryImportSdk() {
912
+ try {
913
+ return await import("./sdk-IV5ZYS3G.js");
914
+ } catch {
915
+ return null;
916
+ }
917
+ }
918
+ function isClaudeSdkAvailableInEnv(env) {
919
+ return isClaudeCodeAvailableInEnv(env);
920
+ }
921
+ async function isClaudeSdkImportable() {
922
+ return await tryImportSdk() !== null;
923
+ }
924
+ function buildSdkSessionOptions(env = process.env) {
925
+ const model = env.CLAUDE_CODE_MODEL?.trim() || "claude-sonnet-4-6";
926
+ const claudePath = resolveClaudeCodePath(env);
927
+ const allowedToolsRaw = env.CLAUDE_CODE_ALLOWED_TOOLS?.trim();
928
+ const allowedTools = allowedToolsRaw ? allowedToolsRaw.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
929
+ const sdkEnv = { ...env };
930
+ delete sdkEnv.CLAUDECODE;
931
+ return { model, claudePath, allowedTools, sdkEnv };
932
+ }
933
+ function buildAppendSystemPrompt(bridgeSystemPrompt, env = process.env) {
934
+ const userSystemPrompt = env.CLAUDE_CODE_APPEND_SYSTEM_PROMPT?.trim();
935
+ const effective = [bridgeSystemPrompt, userSystemPrompt].filter(Boolean).join("\n\n");
936
+ return effective.length > 0 ? effective : void 0;
937
+ }
938
+ async function runClaudeSdkBridgeStartupProbe(env = process.env) {
939
+ const { model, claudePath, allowedTools } = buildSdkSessionOptions(env);
940
+ const cwd = env.CLAUDE_CODE_CWD?.trim() || env.PUBBLUE_PROJECT_ROOT || void 0;
941
+ const sdk = await tryImportSdk();
942
+ if (!sdk) {
943
+ throw new Error(
944
+ "Claude Agent SDK (@anthropic-ai/claude-agent-sdk) is not importable. Install it and retry."
945
+ );
946
+ }
947
+ await runAgentWritePongProbe({
948
+ label: "Claude SDK",
949
+ baseEnv: env,
950
+ execute: async (probeEnv) => {
951
+ const probeEnvClean = { ...probeEnv };
952
+ delete probeEnvClean.CLAUDECODE;
953
+ const socketPath = probeEnv.PUBBLUE_AGENT_SOCKET ?? "";
954
+ const logPath = path2.join(os2.tmpdir(), "pubblue-sdk-probe.log");
955
+ const appendLog = (line) => {
956
+ try {
957
+ fs2.appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
958
+ `);
959
+ } catch (error) {
960
+ if (process.env.PUBBLUE_DEBUG === "1") {
961
+ console.warn(`Warning: failed to append SDK probe log: ${errorMessage(error)}`);
962
+ }
963
+ }
964
+ };
965
+ appendLog(`probe start socket=${socketPath}`);
966
+ const prompt = [
967
+ "This is a startup connectivity probe.",
968
+ "Run this exact shell command now:",
969
+ `PUBBLUE_AGENT_SOCKET=${socketPath} pubblue write "pong"`,
970
+ "Do not explain. Just execute it."
971
+ ].join("\n");
972
+ const q = sdk.query({
973
+ prompt,
974
+ options: {
975
+ model,
976
+ pathToClaudeCodeExecutable: claudePath,
977
+ env: probeEnvClean,
978
+ allowedTools,
979
+ cwd: os2.tmpdir(),
980
+ maxTurns: 2,
981
+ persistSession: false,
982
+ canUseTool: async (toolName, input) => {
983
+ appendLog(`canUseTool: tool=${toolName}`);
984
+ return { behavior: "allow", updatedInput: input };
985
+ }
986
+ }
987
+ });
988
+ for await (const msg of q) {
989
+ appendLog(`msg: type=${msg.type} ${JSON.stringify(msg).slice(0, 300)}`);
990
+ }
991
+ appendLog("probe stream completed");
992
+ }
993
+ });
994
+ return { claudePath, cwd };
995
+ }
996
+ var MAX_SESSION_RECREATIONS = 2;
997
+ async function createClaudeSdkBridgeRunner(config, abortSignal) {
998
+ const { slug, sendMessage, debugLog, sessionBriefing } = config;
999
+ const env = process.env;
1000
+ const sdk = await tryImportSdk();
1001
+ if (!sdk) {
1002
+ throw new Error("Claude Agent SDK is not importable.");
1003
+ }
1004
+ const loadedSdk = sdk;
1005
+ const { model, claudePath, allowedTools, sdkEnv } = buildSdkSessionOptions(env);
1006
+ const appendSystemPrompt = buildAppendSystemPrompt(config.instructions.systemPrompt, env);
1007
+ let sessionId;
1008
+ let forwardedMessageCount = 0;
1009
+ let lastError;
1010
+ let stopped = abortSignal?.aborted ?? false;
1011
+ let sessionRecreations = 0;
1012
+ let activeSession = null;
1013
+ if (abortSignal) {
1014
+ abortSignal.addEventListener(
1015
+ "abort",
1016
+ () => {
1017
+ stopped = true;
1018
+ activeSession?.close();
1019
+ },
1020
+ { once: true }
1021
+ );
1022
+ }
1023
+ const canvasReminderEvery = resolveCanvasReminderEvery();
1024
+ function createSession() {
1025
+ const session2 = loadedSdk.unstable_v2_createSession({
1026
+ model,
1027
+ pathToClaudeCodeExecutable: claudePath,
1028
+ env: {
1029
+ ...sdkEnv,
1030
+ ...appendSystemPrompt ? { CLAUDE_CODE_APPEND_SYSTEM_PROMPT: appendSystemPrompt } : {}
1031
+ },
1032
+ allowedTools,
1033
+ canUseTool: async (_tool, input) => ({ behavior: "allow", updatedInput: input })
1034
+ });
1035
+ activeSession = session2;
1036
+ return session2;
1037
+ }
1038
+ async function consumeStream(session2) {
1039
+ for await (const msg of session2.stream()) {
1040
+ if (stopped) break;
1041
+ if (msg.type === "assistant") {
1042
+ debugLog("sdk assistant message received");
1043
+ } else if (msg.type === "result") {
1044
+ if ("session_id" in msg && typeof msg.session_id === "string") {
1045
+ sessionId = msg.session_id;
1046
+ debugLog(`captured session_id: ${sessionId}`);
1047
+ }
1048
+ if (msg.subtype !== "success") {
1049
+ throw new Error(`Claude SDK result error: ${msg.subtype}`);
1050
+ }
1051
+ }
1052
+ }
1053
+ }
1054
+ async function sendAndStream(session2, prompt) {
1055
+ await session2.send(prompt);
1056
+ await consumeStream(session2);
1057
+ }
1058
+ async function deliverWithRecovery(prompt) {
1059
+ if (stopped) return;
1060
+ try {
1061
+ if (!activeSession) throw new Error("session not initialized");
1062
+ await sendAndStream(activeSession, prompt);
1063
+ } catch (error) {
1064
+ const msg = errorMessage(error);
1065
+ debugLog(`session error: ${msg}`, error);
1066
+ if (stopped || sessionRecreations >= MAX_SESSION_RECREATIONS) {
1067
+ throw error;
1068
+ }
1069
+ debugLog(`recreating session (attempt ${sessionRecreations + 1}/${MAX_SESSION_RECREATIONS})`);
1070
+ sessionRecreations += 1;
1071
+ try {
1072
+ activeSession?.close();
1073
+ } catch (error2) {
1074
+ debugLog(`failed to close previous SDK session: ${errorMessage(error2)}`, error2);
1075
+ }
1076
+ const newSession = createSession();
1077
+ await sendAndStream(newSession, sessionBriefing);
1078
+ debugLog("session briefing re-delivered after recovery");
1079
+ await sendAndStream(newSession, prompt);
1080
+ }
1081
+ }
1082
+ const session = createSession();
1083
+ await sendAndStream(session, sessionBriefing);
1084
+ debugLog("session briefing delivered via SDK");
1085
+ const queue = createBridgeEntryQueue({
1086
+ onEntry: async (entry) => {
1087
+ const includeCanvasReminder = shouldIncludeCanvasPolicyReminder(
1088
+ forwardedMessageCount + 1,
1089
+ canvasReminderEvery
1090
+ );
1091
+ const chat = readTextChatMessage(entry);
1092
+ if (chat) {
1093
+ const prompt = buildInboundPrompt(slug, chat, includeCanvasReminder, config.instructions);
1094
+ await deliverWithRecovery(prompt);
1095
+ forwardedMessageCount += 1;
1096
+ config.onDeliveryUpdate?.({
1097
+ channel: entry.channel,
1098
+ messageId: entry.msg.id,
1099
+ stage: "confirmed"
1100
+ });
1101
+ return;
1102
+ }
1103
+ const renderError = readRenderErrorMessage(entry);
1104
+ if (renderError) {
1105
+ const prompt = buildRenderErrorPrompt(slug, renderError, config.instructions);
1106
+ await deliverWithRecovery(prompt);
1107
+ forwardedMessageCount += 1;
1108
+ config.onDeliveryUpdate?.({
1109
+ channel: entry.channel,
1110
+ messageId: entry.msg.id,
1111
+ stage: "confirmed"
1112
+ });
1113
+ }
1114
+ },
1115
+ onError: (error, entry) => {
1116
+ const message = errorMessage(error);
1117
+ lastError = message;
1118
+ debugLog(`bridge entry processing failed: ${message}`, error);
1119
+ config.onDeliveryUpdate?.({
1120
+ channel: entry.channel,
1121
+ messageId: entry.msg.id,
1122
+ stage: "failed",
1123
+ error: message
1124
+ });
1125
+ void sendMessage(CHANNELS.CHAT, {
1126
+ id: generateMessageId(),
1127
+ type: "text",
1128
+ data: `Bridge error: ${message}`
1129
+ });
1130
+ }
1131
+ });
1132
+ return {
1133
+ enqueue: (entries) => queue.enqueue(entries),
1134
+ async stop() {
1135
+ if (stopped) return;
1136
+ stopped = true;
1137
+ await queue.stop();
1138
+ activeSession?.close();
1139
+ },
1140
+ status() {
1141
+ return {
1142
+ running: !stopped,
1143
+ sessionId,
1144
+ lastError,
1145
+ forwardedMessages: forwardedMessageCount
1146
+ };
1147
+ }
1148
+ };
1149
+ }
1150
+
812
1151
  // src/lib/live-bridge-openclaw.ts
813
1152
  import { execFile } from "child_process";
814
1153
  import { existsSync as existsSync5 } from "fs";
815
- import { join as join5 } from "path";
1154
+ import { join as join6 } from "path";
816
1155
  import { promisify } from "util";
817
1156
 
818
1157
  // src/lib/live-bridge-openclaw-attachments.ts
819
1158
  import { createHash } from "crypto";
820
1159
  import { mkdirSync, renameSync, unlinkSync as unlinkSync2, writeFileSync } from "fs";
821
- import { basename, extname, join as join4 } from "path";
1160
+ import { basename, extname, join as join5 } from "path";
822
1161
 
823
1162
  // src/lib/live-bridge-openclaw-session.ts
824
1163
  import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
825
- import { join as join3 } from "path";
1164
+ import { join as join4 } from "path";
826
1165
 
827
1166
  // src/lib/openclaw-paths.ts
828
1167
  import { existsSync as existsSync3, readFileSync } from "fs";
829
1168
  import { homedir } from "os";
830
- import { dirname, isAbsolute, join as join2, resolve } from "path";
1169
+ import { dirname, isAbsolute, join as join3, resolve } from "path";
831
1170
  function trimToUndefined(value) {
832
1171
  const trimmed = value?.trim();
833
1172
  return trimmed ? trimmed : void 0;
@@ -846,7 +1185,7 @@ function resolveBaseHome(env) {
846
1185
  function expandHomePrefix(input, home) {
847
1186
  if (input === "~") return home;
848
1187
  if (input.startsWith("~/") || input.startsWith("~\\")) {
849
- return join2(home, input.slice(2));
1188
+ return join3(home, input.slice(2));
850
1189
  }
851
1190
  return input;
852
1191
  }
@@ -864,12 +1203,12 @@ function resolveOpenClawHome(env = process.env) {
864
1203
  function resolveOpenClawStateDir(env = process.env) {
865
1204
  const configured = trimToUndefined(env.OPENCLAW_STATE_DIR);
866
1205
  if (configured) return resolvePathFromInput(configured, env);
867
- return join2(resolveOpenClawHome(env), ".openclaw");
1206
+ return join3(resolveOpenClawHome(env), ".openclaw");
868
1207
  }
869
1208
  function resolveOpenClawConfigPath(env = process.env) {
870
1209
  const configured = trimToUndefined(env.OPENCLAW_CONFIG_PATH);
871
1210
  if (configured) return resolvePathFromInput(configured, env);
872
- return join2(resolveOpenClawStateDir(env), "openclaw.json");
1211
+ return join3(resolveOpenClawStateDir(env), "openclaw.json");
873
1212
  }
874
1213
  function readWorkspaceFromOpenClawConfig(configPath) {
875
1214
  if (!existsSync3(configPath)) return null;
@@ -899,13 +1238,13 @@ function resolveOpenClawWorkspaceDir(env = process.env) {
899
1238
  const configPath = resolveOpenClawConfigPath(env);
900
1239
  const fromConfig = readWorkspaceFromOpenClawConfig(configPath);
901
1240
  if (fromConfig) return resolveWorkspacePath(fromConfig, configPath, env);
902
- return join2(resolveOpenClawStateDir(env), "workspace");
1241
+ return join3(resolveOpenClawStateDir(env), "workspace");
903
1242
  }
904
1243
 
905
1244
  // src/lib/live-bridge-openclaw-session.ts
906
1245
  var OPENCLAW_MAIN_SESSION_KEY = "agent:main:main";
907
1246
  function resolveOpenClawSessionsPath(env = process.env) {
908
- return join3(resolveOpenClawStateDir(env), "agents", "main", "sessions", "sessions.json");
1247
+ return join4(resolveOpenClawStateDir(env), "agents", "main", "sessions", "sessions.json");
909
1248
  }
910
1249
  function buildThreadCandidateKeys(threadId) {
911
1250
  const trimmed = threadId?.trim();
@@ -981,7 +1320,7 @@ var DEFAULT_ATTACHMENT_MAX_BYTES = 5 * 1024 * 1024;
981
1320
  function resolveAttachmentRootDir() {
982
1321
  const configured = process.env.OPENCLAW_ATTACHMENT_DIR?.trim();
983
1322
  if (configured) return configured;
984
- return join4(resolveOpenClawStateDir(), "pubblue-inbox");
1323
+ return join5(resolveOpenClawStateDir(), "pubblue-inbox");
985
1324
  }
986
1325
  function resolveAttachmentMaxBytes() {
987
1326
  const raw = Number.parseInt(process.env.OPENCLAW_ATTACHMENT_MAX_BYTES ?? "", 10);
@@ -1022,12 +1361,12 @@ function resolveAttachmentFilename(params) {
1022
1361
  }
1023
1362
  function ensureDirectoryWritable(dirPath) {
1024
1363
  mkdirSync(dirPath, { recursive: true });
1025
- const probe = join4(dirPath, `.bridge-writecheck-${process.pid}-${Date.now()}`);
1364
+ const probe = join5(dirPath, `.bridge-writecheck-${process.pid}-${Date.now()}`);
1026
1365
  writeFileSync(probe, "ok\n", { mode: 384 });
1027
1366
  unlinkSync2(probe);
1028
1367
  }
1029
1368
  function stageAttachment(params) {
1030
- const slugDir = join4(params.attachmentRoot, sanitizeFilename(params.slug));
1369
+ const slugDir = join5(params.attachmentRoot, sanitizeFilename(params.slug));
1031
1370
  ensureDirectoryWritable(slugDir);
1032
1371
  const mime = (params.mime || "application/octet-stream").trim();
1033
1372
  const resolvedName = resolveAttachmentFilename({
@@ -1037,7 +1376,7 @@ function stageAttachment(params) {
1037
1376
  mime
1038
1377
  });
1039
1378
  const collisionSafeName = `${Date.now()}-${sanitizeFilename(params.messageId)}-${resolvedName}`;
1040
- const targetPath = join4(slugDir, collisionSafeName);
1379
+ const targetPath = join5(slugDir, collisionSafeName);
1041
1380
  const tempPath = `${targetPath}.tmp-${process.pid}`;
1042
1381
  writeFileSync(tempPath, params.bytes, { mode: 384 });
1043
1382
  renameSync(tempPath, targetPath);
@@ -1219,9 +1558,9 @@ function getOpenClawDiscoveryPaths(env = process.env) {
1219
1558
  return [
1220
1559
  .../* @__PURE__ */ new Set([
1221
1560
  "/app/dist/index.js",
1222
- join5(home, "openclaw", "dist", "index.js"),
1223
- join5(stateDir, "openclaw"),
1224
- join5(home, ".openclaw", "openclaw"),
1561
+ join6(home, "openclaw", "dist", "index.js"),
1562
+ join6(stateDir, "openclaw"),
1563
+ join6(home, ".openclaw", "openclaw"),
1225
1564
  "/usr/local/bin/openclaw",
1226
1565
  "/opt/homebrew/bin/openclaw"
1227
1566
  ])
@@ -1258,7 +1597,7 @@ function resolveOpenClawPath(env = process.env) {
1258
1597
  "OpenClaw executable was not found.",
1259
1598
  "Configure it with: pubblue configure --set openclaw.path=/absolute/path/to/openclaw",
1260
1599
  "Or set OPENCLAW_PATH in environment.",
1261
- "Checked: " + discoveryPaths.join(", ")
1600
+ `Checked: ${discoveryPaths.join(", ")}`
1262
1601
  ].join(" ")
1263
1602
  );
1264
1603
  }
@@ -1486,35 +1825,35 @@ async function createOpenClawBridgeRunner(config) {
1486
1825
  }
1487
1826
 
1488
1827
  // src/lib/live-runtime/daemon-files.ts
1489
- import * as fs3 from "fs";
1490
- import * as path3 from "path";
1828
+ import * as fs4 from "fs";
1829
+ import * as path4 from "path";
1491
1830
 
1492
1831
  // src/lib/config.ts
1493
- import * as fs2 from "fs";
1494
- import * as path2 from "path";
1832
+ import * as fs3 from "fs";
1833
+ import * as path3 from "path";
1495
1834
  var DEFAULT_BASE_URL = "https://silent-guanaco-514.convex.site";
1496
1835
  function getConfigDir(homeDir) {
1497
1836
  const explicit = process.env.PUBBLUE_CONFIG_DIR?.trim();
1498
1837
  if (explicit) return explicit;
1499
1838
  if (homeDir) {
1500
- return path2.join(path2.resolve(homeDir), ".openclaw", "pubblue");
1839
+ return path3.join(path3.resolve(homeDir), ".openclaw", "pubblue");
1501
1840
  }
1502
- return path2.join(resolveOpenClawStateDir(), "pubblue");
1841
+ return path3.join(resolveOpenClawStateDir(), "pubblue");
1503
1842
  }
1504
1843
  function getConfigPath(homeDir) {
1505
1844
  const dir = getConfigDir(homeDir);
1506
- fs2.mkdirSync(dir, { recursive: true, mode: 448 });
1507
- return path2.join(dir, "config.json");
1845
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
1846
+ return path3.join(dir, "config.json");
1508
1847
  }
1509
1848
  function readConfig(homeDir) {
1510
1849
  const configPath = getConfigPath(homeDir);
1511
- if (!fs2.existsSync(configPath)) return null;
1512
- const raw = fs2.readFileSync(configPath, "utf-8");
1850
+ if (!fs3.existsSync(configPath)) return null;
1851
+ const raw = fs3.readFileSync(configPath, "utf-8");
1513
1852
  return JSON.parse(raw);
1514
1853
  }
1515
1854
  function saveConfig(config, homeDir) {
1516
1855
  const configPath = getConfigPath(homeDir);
1517
- fs2.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1856
+ fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1518
1857
  `, {
1519
1858
  mode: 384
1520
1859
  });
@@ -1546,39 +1885,32 @@ function getTelegramMiniAppUrl(slug) {
1546
1885
 
1547
1886
  // src/lib/live-runtime/daemon-files.ts
1548
1887
  function liveInfoDir() {
1549
- const dir = path3.join(getConfigDir(), "lives");
1550
- fs3.mkdirSync(dir, { recursive: true });
1888
+ const dir = path4.join(getConfigDir(), "lives");
1889
+ fs4.mkdirSync(dir, { recursive: true });
1551
1890
  return dir;
1552
1891
  }
1553
1892
  function liveInfoPath(slug) {
1554
- return path3.join(liveInfoDir(), `${slug}.json`);
1893
+ return path4.join(liveInfoDir(), `${slug}.json`);
1555
1894
  }
1556
1895
  function liveLogPath(slug) {
1557
- return path3.join(liveInfoDir(), `${slug}.log`);
1896
+ return path4.join(liveInfoDir(), `${slug}.log`);
1558
1897
  }
1559
1898
  function sanitizeSlugForFilename(slug) {
1560
1899
  const sanitized = slug.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
1561
1900
  return sanitized.length > 0 ? sanitized : "live";
1562
1901
  }
1563
- function resolveSessionContentExtension(contentType) {
1564
- if (contentType === "html") return "html";
1565
- if (contentType === "markdown") return "md";
1566
- if (contentType === "text") return "txt";
1567
- return "txt";
1568
- }
1569
- function liveSessionContentPath(slug, contentType, rootDir) {
1902
+ function liveSessionContentPath(slug, rootDir) {
1570
1903
  const safeSlug = sanitizeSlugForFilename(slug);
1571
- const ext = resolveSessionContentExtension(contentType);
1572
- return path3.join(rootDir ?? liveInfoDir(), `${safeSlug}.session-content.${ext}`);
1904
+ return path4.join(rootDir ?? liveInfoDir(), `${safeSlug}.session-content.html`);
1573
1905
  }
1574
1906
  function writeLiveSessionContentFile(params) {
1575
- const filePath = liveSessionContentPath(params.slug, params.contentType, params.rootDir);
1576
- fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
1577
- fs3.writeFileSync(filePath, params.content, "utf-8");
1907
+ const filePath = liveSessionContentPath(params.slug, params.rootDir);
1908
+ fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
1909
+ fs4.writeFileSync(filePath, params.content, "utf-8");
1578
1910
  return filePath;
1579
1911
  }
1580
1912
  function latestCliVersionPath() {
1581
- return path3.join(liveInfoDir(), "cli-version.txt");
1913
+ return path4.join(liveInfoDir(), "cli-version.txt");
1582
1914
  }
1583
1915
  function isMissingPathError(error) {
1584
1916
  if (typeof error !== "object" || error === null || !("code" in error)) return false;
@@ -1588,7 +1920,7 @@ function isMissingPathError(error) {
1588
1920
  function readLatestCliVersion(versionPath) {
1589
1921
  const resolved = versionPath || latestCliVersionPath();
1590
1922
  try {
1591
- const value = fs3.readFileSync(resolved, "utf-8").trim();
1923
+ const value = fs4.readFileSync(resolved, "utf-8").trim();
1592
1924
  return value.length === 0 ? null : value;
1593
1925
  } catch (error) {
1594
1926
  if (isMissingPathError(error)) return null;
@@ -1599,13 +1931,13 @@ function writeLatestCliVersion(version, versionPath) {
1599
1931
  const trimmed = version.trim();
1600
1932
  if (trimmed.length === 0) return;
1601
1933
  const resolved = versionPath || latestCliVersionPath();
1602
- const dir = path3.dirname(resolved);
1603
- fs3.mkdirSync(dir, { recursive: true });
1604
- fs3.writeFileSync(resolved, trimmed, "utf-8");
1934
+ const dir = path4.dirname(resolved);
1935
+ fs4.mkdirSync(dir, { recursive: true });
1936
+ fs4.writeFileSync(resolved, trimmed, "utf-8");
1605
1937
  }
1606
1938
  function readLogTail(logPath, maxChars = 4e3) {
1607
1939
  try {
1608
- const content = fs3.readFileSync(logPath, "utf-8");
1940
+ const content = fs4.readFileSync(logPath, "utf-8");
1609
1941
  if (content.length <= maxChars) return content;
1610
1942
  return content.slice(-maxChars);
1611
1943
  } catch (error) {
@@ -1614,10 +1946,152 @@ function readLogTail(logPath, maxChars = 4e3) {
1614
1946
  }
1615
1947
  }
1616
1948
 
1949
+ // src/lib/live-ipc-protocol.ts
1950
+ function parseBufferedBridgeMessage(input) {
1951
+ const record = readRecord(input);
1952
+ if (!record) return null;
1953
+ const channel = readString(record.channel);
1954
+ const msg = parseBridgeMessage(record.msg);
1955
+ if (!channel || !msg) return null;
1956
+ return {
1957
+ channel,
1958
+ msg,
1959
+ timestamp: readFiniteNumber(record.timestamp)
1960
+ };
1961
+ }
1962
+ function parseBridgeStatus(input) {
1963
+ if (input === null) return null;
1964
+ const record = readRecord(input);
1965
+ if (!record) return null;
1966
+ const running = readBoolean(record.running);
1967
+ const forwardedMessages = readFiniteNumber(record.forwardedMessages);
1968
+ if (running === void 0 || forwardedMessages === void 0) return null;
1969
+ const sessionSourceRaw = record.sessionSource === void 0 ? void 0 : readString(record.sessionSource);
1970
+ const sessionSource = sessionSourceRaw === void 0 || sessionSourceRaw === "env" || sessionSourceRaw === "thread-canonical" || sessionSourceRaw === "thread-legacy" || sessionSourceRaw === "main-fallback" ? sessionSourceRaw : null;
1971
+ if (sessionSource === null) return null;
1972
+ return {
1973
+ running,
1974
+ sessionId: readString(record.sessionId),
1975
+ sessionKey: readString(record.sessionKey),
1976
+ sessionSource,
1977
+ lastError: readString(record.lastError),
1978
+ forwardedMessages
1979
+ };
1980
+ }
1981
+ function parseIpcRequest(input) {
1982
+ const record = readRecord(input);
1983
+ if (!record) return null;
1984
+ const method = readString(record.method);
1985
+ const params = readRecord(record.params);
1986
+ if (!method || !params) return null;
1987
+ if (method === "write") {
1988
+ const msg = parseBridgeMessage(params.msg);
1989
+ if (!msg) return null;
1990
+ const channel = params.channel === void 0 ? void 0 : readString(params.channel);
1991
+ const binaryBase64 = params.binaryBase64 === void 0 ? void 0 : readString(params.binaryBase64);
1992
+ if (params.channel !== void 0 && channel === void 0) return null;
1993
+ if (params.binaryBase64 !== void 0 && binaryBase64 === void 0) return null;
1994
+ return {
1995
+ method,
1996
+ params: {
1997
+ channel,
1998
+ msg,
1999
+ binaryBase64
2000
+ }
2001
+ };
2002
+ }
2003
+ if (method === "read") {
2004
+ const channel = params.channel === void 0 ? void 0 : readString(params.channel);
2005
+ if (params.channel !== void 0 && channel === void 0) return null;
2006
+ return { method, params: { channel } };
2007
+ }
2008
+ if (method === "channels" || method === "status" || method === "active-slug" || method === "close") {
2009
+ return { method, params: {} };
2010
+ }
2011
+ return null;
2012
+ }
2013
+ function parseIpcResponse(method, input) {
2014
+ const record = readRecord(input);
2015
+ if (!record) return null;
2016
+ const ok = readBoolean(record.ok);
2017
+ if (ok === void 0) return null;
2018
+ const error = record.error === void 0 ? void 0 : readString(record.error);
2019
+ if (record.error !== void 0 && error === void 0) return null;
2020
+ if (!ok) return { ok, error };
2021
+ if (method === "write") {
2022
+ const delivered = record.delivered === void 0 ? void 0 : readBoolean(record.delivered);
2023
+ if (record.delivered !== void 0 && delivered === void 0) return null;
2024
+ return { ok, delivered, error };
2025
+ }
2026
+ if (method === "read") {
2027
+ if (record.messages === void 0) return { ok, error };
2028
+ if (!Array.isArray(record.messages)) return null;
2029
+ const messages = record.messages.map((entry) => parseBufferedBridgeMessage(entry)).filter((entry) => entry !== null);
2030
+ if (messages.length !== record.messages.length) return null;
2031
+ return { ok, messages, error };
2032
+ }
2033
+ if (method === "channels") {
2034
+ if (record.channels === void 0) return { ok, error };
2035
+ if (!Array.isArray(record.channels)) return null;
2036
+ const channels = record.channels.map((entry) => {
2037
+ const channelRecord = readRecord(entry);
2038
+ if (!channelRecord) return null;
2039
+ const name = readString(channelRecord.name);
2040
+ const direction = readString(channelRecord.direction);
2041
+ if (!name || !direction) return null;
2042
+ return { name, direction };
2043
+ }).filter((entry) => entry !== null);
2044
+ if (channels.length !== record.channels.length) return null;
2045
+ return { ok, channels, error };
2046
+ }
2047
+ if (method === "status") {
2048
+ const connected = readBoolean(record.connected);
2049
+ const signalingConnected = record.signalingConnected === null ? null : record.signalingConnected === void 0 ? void 0 : readBoolean(record.signalingConnected);
2050
+ const activeSlug = record.activeSlug === null ? null : record.activeSlug === void 0 ? void 0 : readString(record.activeSlug);
2051
+ const uptime = readFiniteNumber(record.uptime);
2052
+ const bufferedMessages = readFiniteNumber(record.bufferedMessages);
2053
+ const lastError = record.lastError === null ? null : record.lastError === void 0 ? void 0 : readString(record.lastError);
2054
+ const bridgeMode = record.bridgeMode === null ? null : record.bridgeMode === void 0 ? void 0 : readString(record.bridgeMode);
2055
+ if (connected === void 0 || signalingConnected === void 0 || activeSlug === void 0 || uptime === void 0 || !Array.isArray(record.channels) || bufferedMessages === void 0 || lastError === void 0 || bridgeMode === void 0) {
2056
+ return null;
2057
+ }
2058
+ const channels = record.channels.filter((entry) => typeof entry === "string");
2059
+ if (channels.length !== record.channels.length) return null;
2060
+ const bridge = record.bridge === void 0 ? null : parseBridgeStatus(record.bridge);
2061
+ if (record.bridge !== void 0 && bridge === null && record.bridge !== null) return null;
2062
+ return {
2063
+ ok,
2064
+ connected,
2065
+ signalingConnected,
2066
+ activeSlug,
2067
+ uptime,
2068
+ channels,
2069
+ bufferedMessages,
2070
+ lastError,
2071
+ bridgeMode,
2072
+ bridge,
2073
+ error
2074
+ };
2075
+ }
2076
+ if (method === "active-slug") {
2077
+ const slug = record.slug === null ? null : record.slug === void 0 ? void 0 : readString(record.slug);
2078
+ if (record.slug !== void 0 && slug === void 0) return null;
2079
+ return { ok, slug, error };
2080
+ }
2081
+ return { ok, error };
2082
+ }
2083
+
1617
2084
  export {
1618
2085
  failCli,
1619
2086
  errorMessage,
1620
2087
  toCliFailure,
2088
+ readRecord,
2089
+ readString,
2090
+ readNonEmptyString,
2091
+ readFiniteNumber,
2092
+ readStringArray,
2093
+ readStringRecord,
2094
+ parseLiveInfo,
1621
2095
  PubApiError,
1622
2096
  PubApiClient,
1623
2097
  resolveOpenClawHome,
@@ -1638,12 +2112,18 @@ export {
1638
2112
  makeDeliveryReceiptMessage,
1639
2113
  parseAckMessage,
1640
2114
  shouldAcknowledgeMessage,
2115
+ parseIpcRequest,
2116
+ parseIpcResponse,
1641
2117
  buildSessionBriefing,
1642
2118
  isClaudeCodeAvailableInEnv,
1643
2119
  resolveClaudeCodePath,
1644
2120
  buildClaudeArgs,
1645
2121
  runClaudeCodeBridgeStartupProbe,
1646
2122
  createClaudeCodeBridgeRunner,
2123
+ isClaudeSdkAvailableInEnv,
2124
+ isClaudeSdkImportable,
2125
+ runClaudeSdkBridgeStartupProbe,
2126
+ createClaudeSdkBridgeRunner,
1647
2127
  isOpenClawAvailable,
1648
2128
  resolveOpenClawRuntime,
1649
2129
  runOpenClawBridgeStartupProbe,