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/{chunk-JSX5KHV3.js → chunk-5ODXW2EM.js} +315 -47
- package/dist/index.js +50 -19
- package/dist/live-daemon-entry.js +81 -129
- package/dist/sdk-IV5ZYS3G.js +12299 -0
- package/package.json +4 -1
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-
|
|
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(
|
|
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.
|
|
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)").
|
|
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}${
|
|
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-
|
|
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
|
-
|
|
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
|
|
508
|
+
for (const entry of functions) {
|
|
509
509
|
const normalized = normalizeFunctionSpec(entry);
|
|
510
510
|
if (!normalized.executor) {
|
|
511
|
-
params.debugLog(`
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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(`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|