@pushpalsdev/cli 1.0.9 → 1.0.10
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/pushpals-cli.js +371 -125
- package/monitor-ui/+not-found.html +1 -1
- package/monitor-ui/_expo/static/js/web/{entry-7e55666b7443eef13f7696e8d422e8cf.js → entry-1d5a07c47473852fe9754bfe3e6da301.js} +177 -176
- package/monitor-ui/_expo/static/js/web/{index-04194fb85c12ac46718492c2ff687e6c.js → index-5650b6cee60075cb63c1efc40dcf7683.js} +4 -4
- package/monitor-ui/_sitemap.html +1 -1
- package/monitor-ui/index.html +1 -1
- package/monitor-ui/modal.html +1 -1
- package/package.json +1 -1
- package/runtime/.env.example +4 -4
- package/runtime/configs/default.toml +5 -4
- package/runtime/configs/local.example.toml +6 -1
package/dist/pushpals-cli.js
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
appendFileSync,
|
|
7
7
|
chmodSync,
|
|
8
8
|
cpSync,
|
|
9
|
-
existsSync as
|
|
9
|
+
existsSync as existsSync4,
|
|
10
10
|
mkdirSync,
|
|
11
|
-
readFileSync as
|
|
11
|
+
readFileSync as readFileSync4,
|
|
12
12
|
writeFileSync
|
|
13
13
|
} from "fs";
|
|
14
|
-
import { dirname, extname, join as join2, resolve as
|
|
14
|
+
import { dirname, extname, join as join2, resolve as resolve4 } from "path";
|
|
15
15
|
import { createInterface } from "readline";
|
|
16
16
|
|
|
17
17
|
// ../shared/src/client_preflight.ts
|
|
@@ -21,12 +21,53 @@ import { relative, resolve as resolve2 } from "path";
|
|
|
21
21
|
// ../shared/src/config.ts
|
|
22
22
|
import { existsSync, readFileSync } from "fs";
|
|
23
23
|
import { join, resolve, isAbsolute } from "path";
|
|
24
|
+
|
|
25
|
+
// ../shared/src/local_network.ts
|
|
26
|
+
var DEFAULT_LOCAL_LOOPBACK_HOST = "127.0.0.1";
|
|
27
|
+
function isLoopbackHost(hostname) {
|
|
28
|
+
const normalized = String(hostname ?? "").trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
29
|
+
return normalized === "127.0.0.1" || normalized === "::1" || normalized === "localhost";
|
|
30
|
+
}
|
|
31
|
+
function normalizeLoopbackHost(hostname) {
|
|
32
|
+
const normalized = String(hostname ?? "").trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
33
|
+
if (isLoopbackHost(normalized))
|
|
34
|
+
return DEFAULT_LOCAL_LOOPBACK_HOST;
|
|
35
|
+
return DEFAULT_LOCAL_LOOPBACK_HOST;
|
|
36
|
+
}
|
|
37
|
+
function normalizeLoopbackHttpUrl(value, fallbackPort) {
|
|
38
|
+
const fallback = `http://${DEFAULT_LOCAL_LOOPBACK_HOST}:${Math.max(1, fallbackPort)}`;
|
|
39
|
+
const text = String(value ?? "").trim();
|
|
40
|
+
if (!text)
|
|
41
|
+
return fallback;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = new URL(text);
|
|
44
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
parsed.protocol = "http:";
|
|
48
|
+
parsed.username = "";
|
|
49
|
+
parsed.password = "";
|
|
50
|
+
parsed.hostname = normalizeLoopbackHost(parsed.hostname);
|
|
51
|
+
parsed.pathname = "/";
|
|
52
|
+
parsed.search = "";
|
|
53
|
+
parsed.hash = "";
|
|
54
|
+
if (!parsed.port) {
|
|
55
|
+
parsed.port = String(Math.max(1, fallbackPort));
|
|
56
|
+
}
|
|
57
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
58
|
+
} catch {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ../shared/src/config.ts
|
|
24
64
|
var PROJECT_ROOT = resolve(import.meta.dir, "..", "..", "..");
|
|
25
65
|
var DEFAULT_CONFIG_DIR = "configs";
|
|
26
66
|
var LEGACY_CONFIG_DIR = "config";
|
|
27
67
|
var TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
28
68
|
var FALSY = new Set(["0", "false", "no", "off"]);
|
|
29
69
|
var DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE = 8;
|
|
70
|
+
var DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS = 1;
|
|
30
71
|
var DEFAULT_WORKERPALS_FILE_MODIFYING_JOBS = ["task.execute"];
|
|
31
72
|
var DEFAULT_WORKERPALS_OUTPUT_MAX_CHARS = 192 * 1024;
|
|
32
73
|
var DEFAULT_WORKERPALS_OUTPUT_MAX_LINES = 600;
|
|
@@ -294,13 +335,14 @@ function loadPushPalsConfig(options = {}) {
|
|
|
294
335
|
const remotebuddyDbPath = resolvePathFromRoot(projectRoot, firstNonEmpty(process.env.REMOTEBUDDY_DB_PATH, asString(pathsNode.remotebuddy_db_path, join(dataDir, "remotebuddy-state.db"))));
|
|
295
336
|
const serverNode = getObject(merged, "server");
|
|
296
337
|
const serverPort = Math.max(1, asInt(parseIntEnv("PUSHPALS_PORT") ?? serverNode.port, 3001));
|
|
297
|
-
const serverUrl = firstNonEmpty(process.env.PUSHPALS_SERVER_URL, asString(serverNode.url, `http://
|
|
298
|
-
const serverHost = asString(serverNode.host, "
|
|
338
|
+
const serverUrl = normalizeLoopbackHttpUrl(firstNonEmpty(process.env.PUSHPALS_SERVER_URL, asString(serverNode.url, `http://127.0.0.1:${serverPort}`), `http://127.0.0.1:${serverPort}`), serverPort);
|
|
339
|
+
const serverHost = normalizeLoopbackHost(firstNonEmpty(process.env.PUSHPALS_HOST, asString(serverNode.host, "127.0.0.1")));
|
|
299
340
|
const debugHttp = parseBoolEnv("PUSHPALS_DEBUG_HTTP") ?? asBoolean(serverNode.debug_http, false);
|
|
300
341
|
const staleClaimTtlMs = Math.max(5000, asInt(parseIntEnv("PUSHPALS_STALE_CLAIM_TTL_MS") ?? serverNode.stale_claim_ttl_ms, 120000));
|
|
301
342
|
const staleClaimSweepIntervalMs = Math.max(1000, asInt(parseIntEnv("PUSHPALS_STALE_CLAIM_SWEEP_INTERVAL_MS") ?? serverNode.stale_claim_sweep_interval_ms, 5000));
|
|
302
343
|
const globalStatusHeartbeatMs = parseIntEnv("PUSHPALS_STATUS_HEARTBEAT_MS");
|
|
303
344
|
const localNode = getObject(merged, "localbuddy");
|
|
345
|
+
const localEnabled = parseBoolEnv("LOCALBUDDY_ENABLED") ?? asBoolean(localNode.enabled, false);
|
|
304
346
|
const localPort = Math.max(1, asInt(parseIntEnv("LOCAL_AGENT_PORT") ?? localNode.port, 3003));
|
|
305
347
|
const localStatusHeartbeatMs = Math.max(0, asInt(parseIntEnv("LOCALBUDDY_STATUS_HEARTBEAT_MS") ?? globalStatusHeartbeatMs ?? localNode.status_heartbeat_ms, 120000));
|
|
306
348
|
const localLlm = resolveLlmConfig(localNode, "LOCALBUDDY", {
|
|
@@ -377,7 +419,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
377
419
|
const workerMinisweTimeoutMs = Math.max(1e4, asInt(parseIntEnv("WORKERPALS_MINISWE_TIMEOUT_MS") ?? workerNode.miniswe_timeout_ms, 1800000));
|
|
378
420
|
const workerOpenAICodexPython = firstNonEmpty(process.env.PUSHPALS_OPENAI_CODEX_PYTHON, asString(workerNode.openai_codex_python, "python"), "python");
|
|
379
421
|
const workerOpenAICodexTimeoutMs = Math.max(1e4, asInt(workerNode.openai_codex_timeout_ms, 7200000));
|
|
380
|
-
const workerQualityMaxAutoRevisions = Math.max(0, Math.min(10, asInt(parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ?? workerNode.quality_max_auto_revisions,
|
|
422
|
+
const workerQualityMaxAutoRevisions = Math.max(0, Math.min(10, asInt(parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ?? workerNode.quality_max_auto_revisions, DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS)));
|
|
381
423
|
const workerFileModifyingJobs = (() => {
|
|
382
424
|
const envRaw = firstNonEmpty(process.env.WORKERPALS_FILE_MODIFYING_JOBS);
|
|
383
425
|
const parsed = envRaw ? envRaw.split(",").map((entry) => entry.trim()).filter(Boolean) : asStringArray(workerNode.file_modifying_jobs);
|
|
@@ -539,6 +581,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
539
581
|
staleClaimSweepIntervalMs
|
|
540
582
|
},
|
|
541
583
|
localbuddy: {
|
|
584
|
+
enabled: localEnabled,
|
|
542
585
|
port: localPort,
|
|
543
586
|
statusHeartbeatMs: localStatusHeartbeatMs,
|
|
544
587
|
llm: localLlm
|
|
@@ -742,7 +785,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
742
785
|
portConflictPolicy: startupPortConflictPolicy
|
|
743
786
|
},
|
|
744
787
|
client: {
|
|
745
|
-
localAgentUrl: firstNonEmpty(process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, asString(clientNode.local_agent_url, `http://
|
|
788
|
+
localAgentUrl: normalizeLoopbackHttpUrl(firstNonEmpty(process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, asString(clientNode.local_agent_url, `http://127.0.0.1:${localPort}`), `http://127.0.0.1:${localPort}`), localPort),
|
|
746
789
|
traceTailLines: Math.max(10, asInt(parseIntEnv("EXPO_PUBLIC_PUSHPALS_TRACE_TAIL_LINES") ?? clientNode.trace_tail_lines, 100))
|
|
747
790
|
}
|
|
748
791
|
};
|
|
@@ -1061,6 +1104,46 @@ function formatClientRuntimePreflightLines(result, prefix) {
|
|
|
1061
1104
|
return lines;
|
|
1062
1105
|
}
|
|
1063
1106
|
|
|
1107
|
+
// ../shared/src/repo.ts
|
|
1108
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
|
|
1109
|
+
import { resolve as resolve3 } from "path";
|
|
1110
|
+
function resolveDotGitEntry(repoRoot) {
|
|
1111
|
+
return resolve3(repoRoot, ".git");
|
|
1112
|
+
}
|
|
1113
|
+
function resolveGitMetadataDir(repoRoot) {
|
|
1114
|
+
const dotGitPath = resolveDotGitEntry(repoRoot);
|
|
1115
|
+
if (!existsSync3(dotGitPath))
|
|
1116
|
+
return null;
|
|
1117
|
+
try {
|
|
1118
|
+
const stat = statSync(dotGitPath);
|
|
1119
|
+
if (stat.isDirectory()) {
|
|
1120
|
+
return dotGitPath;
|
|
1121
|
+
}
|
|
1122
|
+
if (!stat.isFile()) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
} catch {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
try {
|
|
1129
|
+
const firstLine = readFileSync3(dotGitPath, "utf8").split(/\r?\n/, 1)[0] ?? "";
|
|
1130
|
+
const match = firstLine.match(/^gitdir:\s*(.+)\s*$/i);
|
|
1131
|
+
if (!match)
|
|
1132
|
+
return null;
|
|
1133
|
+
const gitDir = resolve3(repoRoot, match[1].trim());
|
|
1134
|
+
return existsSync3(gitDir) ? gitDir : null;
|
|
1135
|
+
} catch {
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function resolveGitStateFilePath(repoRoot, fileName) {
|
|
1140
|
+
const gitMetadataDir = resolveGitMetadataDir(repoRoot);
|
|
1141
|
+
const normalizedFileName = String(fileName ?? "").trim();
|
|
1142
|
+
if (!gitMetadataDir || !normalizedFileName)
|
|
1143
|
+
return null;
|
|
1144
|
+
return resolve3(gitMetadataDir, normalizedFileName);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1064
1147
|
// ../../scripts/pushpals-cli.ts
|
|
1065
1148
|
var DEFAULT_MONITOR_PORT = 8081;
|
|
1066
1149
|
var MONITOR_SCAN_PORTS = 32;
|
|
@@ -1081,6 +1164,29 @@ var GITHUB_HEADERS = {
|
|
|
1081
1164
|
"User-Agent": "pushpals-cli"
|
|
1082
1165
|
};
|
|
1083
1166
|
var stateVersion = 1;
|
|
1167
|
+
var cliTimestampedConsoleInstalled = false;
|
|
1168
|
+
function formatTimestampedCliLine(line, at = new Date) {
|
|
1169
|
+
const text = String(line ?? "");
|
|
1170
|
+
if (!text.startsWith("[pushpals]") && !text.startsWith("[localbuddy]")) {
|
|
1171
|
+
return text;
|
|
1172
|
+
}
|
|
1173
|
+
return `[${at.toISOString()}]${text}`;
|
|
1174
|
+
}
|
|
1175
|
+
function installTimestampedCliConsole() {
|
|
1176
|
+
if (cliTimestampedConsoleInstalled)
|
|
1177
|
+
return;
|
|
1178
|
+
cliTimestampedConsoleInstalled = true;
|
|
1179
|
+
const patch = (original) => (...args) => {
|
|
1180
|
+
if (args.length > 0 && typeof args[0] === "string") {
|
|
1181
|
+
args[0] = formatTimestampedCliLine(args[0]);
|
|
1182
|
+
}
|
|
1183
|
+
return original(...args);
|
|
1184
|
+
};
|
|
1185
|
+
console.log = patch(console.log.bind(console));
|
|
1186
|
+
console.warn = patch(console.warn.bind(console));
|
|
1187
|
+
console.error = patch(console.error.bind(console));
|
|
1188
|
+
}
|
|
1189
|
+
installTimestampedCliConsole();
|
|
1084
1190
|
function logCliInvocation(argv) {
|
|
1085
1191
|
const startedAt = new Date().toISOString();
|
|
1086
1192
|
const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
|
|
@@ -1106,6 +1212,7 @@ function printUsage() {
|
|
|
1106
1212
|
console.log(" --runtime-tag <tag> Override runtime release tag (e.g. v1.0.2)");
|
|
1107
1213
|
console.log(" --no-auto-start Disable runtime auto-start when LocalBuddy is down");
|
|
1108
1214
|
console.log(" --no-stream Disable live session event stream");
|
|
1215
|
+
console.log(" --runtime-only Start the local runtime and wait for shutdown without opening the interactive chat");
|
|
1109
1216
|
console.log(" -h, --help Show this help");
|
|
1110
1217
|
console.log("");
|
|
1111
1218
|
console.log("Chat commands:");
|
|
@@ -1120,7 +1227,7 @@ function printUsage() {
|
|
|
1120
1227
|
console.log(" - LocalBuddy must be attached to the same repo root.");
|
|
1121
1228
|
}
|
|
1122
1229
|
function parseArgs(argv) {
|
|
1123
|
-
const options = { noAutoStart: false, noStream: false };
|
|
1230
|
+
const options = { noAutoStart: false, noStream: false, runtimeOnly: false };
|
|
1124
1231
|
for (let i = 0;i < argv.length; i++) {
|
|
1125
1232
|
const arg = argv[i];
|
|
1126
1233
|
if (arg === "-h" || arg === "--help") {
|
|
@@ -1135,6 +1242,10 @@ function parseArgs(argv) {
|
|
|
1135
1242
|
options.noAutoStart = true;
|
|
1136
1243
|
continue;
|
|
1137
1244
|
}
|
|
1245
|
+
if (arg === "--runtime-only") {
|
|
1246
|
+
options.runtimeOnly = true;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1138
1249
|
if (arg === "--server-url") {
|
|
1139
1250
|
options.serverUrl = argv[++i];
|
|
1140
1251
|
continue;
|
|
@@ -1170,6 +1281,30 @@ function normalizeUrl(value, fallback = "") {
|
|
|
1170
1281
|
const selected = text || fallback;
|
|
1171
1282
|
return selected.replace(/\/+$/, "");
|
|
1172
1283
|
}
|
|
1284
|
+
function normalizeLoopbackUrl(value, fallback) {
|
|
1285
|
+
const selected = normalizeUrl(value, fallback);
|
|
1286
|
+
if (!selected)
|
|
1287
|
+
return "";
|
|
1288
|
+
try {
|
|
1289
|
+
const parsed = new URL(selected);
|
|
1290
|
+
parsed.protocol = "http:";
|
|
1291
|
+
parsed.username = "";
|
|
1292
|
+
parsed.password = "";
|
|
1293
|
+
parsed.hostname = "127.0.0.1";
|
|
1294
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
1295
|
+
} catch {
|
|
1296
|
+
return normalizeUrl(fallback);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function isLoopbackUrl(value) {
|
|
1300
|
+
try {
|
|
1301
|
+
const parsed = new URL(normalizeUrl(value));
|
|
1302
|
+
const hostname = String(parsed.hostname ?? "").trim().toLowerCase();
|
|
1303
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
1304
|
+
} catch {
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1173
1308
|
function parsePositiveInt(value, fallback) {
|
|
1174
1309
|
const parsed = Number.parseInt(String(value ?? "").trim(), 10);
|
|
1175
1310
|
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
@@ -1177,7 +1312,7 @@ function parsePositiveInt(value, fallback) {
|
|
|
1177
1312
|
return parsed;
|
|
1178
1313
|
}
|
|
1179
1314
|
function normalizePath(value) {
|
|
1180
|
-
const normalized =
|
|
1315
|
+
const normalized = resolve4(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1181
1316
|
if (process.platform === "win32")
|
|
1182
1317
|
return normalized.toLowerCase();
|
|
1183
1318
|
return normalized;
|
|
@@ -1205,11 +1340,11 @@ async function resolveCurrentGitRepoRoot(cwd) {
|
|
|
1205
1340
|
const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
1206
1341
|
if (!root.ok || !root.stdout)
|
|
1207
1342
|
return null;
|
|
1208
|
-
return
|
|
1343
|
+
return resolve4(root.stdout);
|
|
1209
1344
|
}
|
|
1210
1345
|
function resolveDefaultRuntimeRoot() {
|
|
1211
1346
|
const home = process.env.USERPROFILE || process.env.HOME || process.cwd();
|
|
1212
|
-
return
|
|
1347
|
+
return resolve4(home, ".pushpals", "runtime");
|
|
1213
1348
|
}
|
|
1214
1349
|
function buildRuntimeAssetSource(root, protocolSchemasDir) {
|
|
1215
1350
|
return {
|
|
@@ -1222,13 +1357,13 @@ function buildRuntimeAssetSource(root, protocolSchemasDir) {
|
|
|
1222
1357
|
};
|
|
1223
1358
|
}
|
|
1224
1359
|
function isCompleteRuntimeAssetSource(source) {
|
|
1225
|
-
return
|
|
1360
|
+
return existsSync4(source.envExamplePath) && existsSync4(source.visionExamplePath) && existsSync4(join2(source.configsDir, "default.toml")) && existsSync4(source.promptsDir) && existsSync4(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(source.protocolSchemasDir, "events.schema.json"));
|
|
1226
1361
|
}
|
|
1227
1362
|
function resolveBundledRuntimeAssetSource() {
|
|
1228
1363
|
const candidates = [
|
|
1229
|
-
buildRuntimeAssetSource(
|
|
1230
|
-
buildRuntimeAssetSource(
|
|
1231
|
-
buildRuntimeAssetSource(
|
|
1364
|
+
buildRuntimeAssetSource(resolve4(import.meta.dir, "..", "runtime"), resolve4(import.meta.dir, "..", "runtime", "protocol", "schemas")),
|
|
1365
|
+
buildRuntimeAssetSource(resolve4(import.meta.dir, ".."), resolve4(import.meta.dir, "..", "packages", "protocol", "src", "schemas")),
|
|
1366
|
+
buildRuntimeAssetSource(resolve4(import.meta.dir, "..", "packages", "cli", "runtime"), resolve4(import.meta.dir, "..", "packages", "cli", "runtime", "protocol", "schemas"))
|
|
1232
1367
|
];
|
|
1233
1368
|
for (const candidate of candidates) {
|
|
1234
1369
|
if (isCompleteRuntimeAssetSource(candidate))
|
|
@@ -1237,12 +1372,12 @@ function resolveBundledRuntimeAssetSource() {
|
|
|
1237
1372
|
return null;
|
|
1238
1373
|
}
|
|
1239
1374
|
function looksLikeMonitoringHubBuild(root) {
|
|
1240
|
-
return
|
|
1375
|
+
return existsSync4(join2(root, "index.html")) && existsSync4(join2(root, "_expo"));
|
|
1241
1376
|
}
|
|
1242
1377
|
function resolveBundledMonitoringHubRoot() {
|
|
1243
1378
|
const candidates = [
|
|
1244
|
-
|
|
1245
|
-
|
|
1379
|
+
resolve4(import.meta.dir, "..", "monitor-ui"),
|
|
1380
|
+
resolve4(import.meta.dir, "..", "packages", "cli", "monitor-ui")
|
|
1246
1381
|
];
|
|
1247
1382
|
for (const candidate of candidates) {
|
|
1248
1383
|
if (looksLikeMonitoringHubBuild(candidate))
|
|
@@ -1252,12 +1387,12 @@ function resolveBundledMonitoringHubRoot() {
|
|
|
1252
1387
|
}
|
|
1253
1388
|
function resolveCliSourceCheckoutRoot() {
|
|
1254
1389
|
const candidates = [
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1390
|
+
resolve4(import.meta.dir, ".."),
|
|
1391
|
+
resolve4(import.meta.dir, "..", ".."),
|
|
1392
|
+
resolve4(import.meta.dir, "..", "..", "..")
|
|
1258
1393
|
];
|
|
1259
1394
|
for (const candidate of candidates) {
|
|
1260
|
-
if (
|
|
1395
|
+
if (existsSync4(join2(candidate, "package.json")) && existsSync4(join2(candidate, "apps", "client", "app.json")) && existsSync4(join2(candidate, "scripts", "sync-cli-monitor-ui.ts"))) {
|
|
1261
1396
|
return candidate;
|
|
1262
1397
|
}
|
|
1263
1398
|
}
|
|
@@ -1287,7 +1422,7 @@ async function ensureBundledMonitoringHubRoot() {
|
|
|
1287
1422
|
return resolveBundledMonitoringHubRoot();
|
|
1288
1423
|
}
|
|
1289
1424
|
function repoLooksLikePushPalsSourceCheckout(repoRoot) {
|
|
1290
|
-
return
|
|
1425
|
+
return existsSync4(join2(repoRoot, "configs", "default.toml")) || existsSync4(join2(repoRoot, "config", "default.toml"));
|
|
1291
1426
|
}
|
|
1292
1427
|
function parseSemverFromPackageVersion(value) {
|
|
1293
1428
|
const raw = String(value ?? "").trim();
|
|
@@ -1325,6 +1460,7 @@ async function resolveRuntimeReleaseTag(explicitTag) {
|
|
|
1325
1460
|
if (fromEnv)
|
|
1326
1461
|
return fromEnv;
|
|
1327
1462
|
const packageVersion = parseSemverFromPackageVersion(process.env.PUSHPALS_CLI_PACKAGE_VERSION);
|
|
1463
|
+
console.log("[pushpals] Resolving embedded runtime release tag from GitHub...");
|
|
1328
1464
|
try {
|
|
1329
1465
|
return await fetchLatestReleaseTag();
|
|
1330
1466
|
} catch (err) {
|
|
@@ -1337,7 +1473,7 @@ async function resolveRuntimeReleaseTag(explicitTag) {
|
|
|
1337
1473
|
}
|
|
1338
1474
|
}
|
|
1339
1475
|
function writeTextFileIfMissing(pathValue, text) {
|
|
1340
|
-
if (
|
|
1476
|
+
if (existsSync4(pathValue))
|
|
1341
1477
|
return;
|
|
1342
1478
|
mkdirSync(dirname(pathValue), { recursive: true });
|
|
1343
1479
|
writeFileSync(pathValue, text, "utf8");
|
|
@@ -1380,8 +1516,8 @@ function seedRuntimePreflightAssets(runtimeRoot) {
|
|
|
1380
1516
|
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
1381
1517
|
`);
|
|
1382
1518
|
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
1383
|
-
if (
|
|
1384
|
-
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"),
|
|
1519
|
+
if (existsSync4(localExamplePath)) {
|
|
1520
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync4(localExamplePath, "utf8"));
|
|
1385
1521
|
} else {
|
|
1386
1522
|
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
|
|
1387
1523
|
`);
|
|
@@ -1395,6 +1531,7 @@ async function fetchTextFromUrl(url, timeoutMs = 20000) {
|
|
|
1395
1531
|
return await response.text();
|
|
1396
1532
|
}
|
|
1397
1533
|
async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
1534
|
+
console.log(`[pushpals] Downloading embedded runtime assets from source tag ${tag}...`);
|
|
1398
1535
|
const treeUrl = `${GITHUB_API_URL}/git/trees/${encodeURIComponent(tag)}?recursive=1`;
|
|
1399
1536
|
const treeResponse = await fetchWithTimeout(treeUrl, { headers: GITHUB_HEADERS }, 30000);
|
|
1400
1537
|
if (!treeResponse.ok) {
|
|
@@ -1415,16 +1552,19 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
|
1415
1552
|
}
|
|
1416
1553
|
}
|
|
1417
1554
|
async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
1555
|
+
console.log(`[pushpals] Preparing embedded runtime assets for ${runtimeTag}...`);
|
|
1418
1556
|
const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
|
|
1419
|
-
const currentTag =
|
|
1557
|
+
const currentTag = existsSync4(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
|
|
1420
1558
|
const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
|
|
1421
|
-
const hasProtocolSchemas =
|
|
1422
|
-
const hasAssets =
|
|
1559
|
+
const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
|
|
1560
|
+
const hasAssets = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
|
|
1423
1561
|
if (!hasAssets || currentTag !== runtimeTag) {
|
|
1562
|
+
console.log(`[pushpals] Embedded runtime assets ${hasAssets ? "are stale" : "are missing"}; refreshing bundle...`);
|
|
1424
1563
|
copyBundledRuntimeAssets(runtimeRoot);
|
|
1425
|
-
const hasProtocolSchemasAfterCopy =
|
|
1426
|
-
const hasAssetsAfterCopy =
|
|
1564
|
+
const hasProtocolSchemasAfterCopy = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
|
|
1565
|
+
const hasAssetsAfterCopy = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
|
|
1427
1566
|
if (!hasAssetsAfterCopy) {
|
|
1567
|
+
console.log("[pushpals] Bundled runtime assets are incomplete; falling back to release source downloads...");
|
|
1428
1568
|
await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
|
|
1429
1569
|
}
|
|
1430
1570
|
writeFileSync(markerPath, `${runtimeTag}
|
|
@@ -1433,18 +1573,19 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
|
1433
1573
|
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
1434
1574
|
`);
|
|
1435
1575
|
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
1436
|
-
if (
|
|
1437
|
-
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"),
|
|
1576
|
+
if (existsSync4(localExamplePath)) {
|
|
1577
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync4(localExamplePath, "utf8"));
|
|
1438
1578
|
} else {
|
|
1439
1579
|
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
|
|
1440
1580
|
`);
|
|
1441
1581
|
}
|
|
1582
|
+
console.log("[pushpals] Embedded runtime assets are ready.");
|
|
1442
1583
|
}
|
|
1443
1584
|
function resolveDeferredRuntimeTagHint(explicitTag) {
|
|
1444
1585
|
return String(explicitTag || process.env.PUSHPALS_RUNTIME_TAG || "").trim();
|
|
1445
1586
|
}
|
|
1446
1587
|
async function prepareCliRuntime(opts) {
|
|
1447
|
-
const runtimeRoot =
|
|
1588
|
+
const runtimeRoot = resolve4(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
|
|
1448
1589
|
if (repoLooksLikePushPalsSourceCheckout(opts.repoRoot)) {
|
|
1449
1590
|
return {
|
|
1450
1591
|
runtimeRoot,
|
|
@@ -1551,9 +1692,9 @@ function timestampFileToken() {
|
|
|
1551
1692
|
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
1552
1693
|
}
|
|
1553
1694
|
function readLogTail(logPath, maxLines = 40) {
|
|
1554
|
-
if (!
|
|
1695
|
+
if (!existsSync4(logPath))
|
|
1555
1696
|
return "";
|
|
1556
|
-
const raw =
|
|
1697
|
+
const raw = readFileSync4(logPath, "utf8");
|
|
1557
1698
|
const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
|
|
1558
1699
|
if (lines.length === 0)
|
|
1559
1700
|
return "";
|
|
@@ -1561,6 +1702,7 @@ function readLogTail(logPath, maxLines = 40) {
|
|
|
1561
1702
|
`);
|
|
1562
1703
|
}
|
|
1563
1704
|
async function downloadBinaryAsset(tag, assetName, outPath) {
|
|
1705
|
+
console.log(`[pushpals] Downloading embedded runtime binary ${assetName} from ${tag}...`);
|
|
1564
1706
|
const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
|
|
1565
1707
|
const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
|
|
1566
1708
|
if (!response.ok) {
|
|
@@ -1572,6 +1714,7 @@ async function downloadBinaryAsset(tag, assetName, outPath) {
|
|
|
1572
1714
|
}
|
|
1573
1715
|
async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
1574
1716
|
const platformKey = resolveRuntimePlatformKey();
|
|
1717
|
+
console.log(`[pushpals] Preparing embedded runtime binaries for ${runtimeTag} (${platformKey})...`);
|
|
1575
1718
|
const binDir = join2(runtimeRoot, "bin", `${runtimeTag}-${platformKey}`);
|
|
1576
1719
|
mkdirSync(binDir, { recursive: true });
|
|
1577
1720
|
const runtimeBinaries = {
|
|
@@ -1586,11 +1729,13 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
|
1586
1729
|
runtimeBinaries.remotebuddy,
|
|
1587
1730
|
runtimeBinaries.sourceControlManager
|
|
1588
1731
|
];
|
|
1732
|
+
let downloadedCount = 0;
|
|
1589
1733
|
for (const binaryPath of requiredAssets) {
|
|
1590
|
-
if (
|
|
1734
|
+
if (existsSync4(binaryPath))
|
|
1591
1735
|
continue;
|
|
1592
1736
|
const assetName = binaryPath.split(/[\\/]/).pop() || "";
|
|
1593
1737
|
await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
|
|
1738
|
+
downloadedCount++;
|
|
1594
1739
|
}
|
|
1595
1740
|
if (process.platform !== "win32") {
|
|
1596
1741
|
for (const binaryPath of requiredAssets) {
|
|
@@ -1599,6 +1744,12 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
|
1599
1744
|
} catch {}
|
|
1600
1745
|
}
|
|
1601
1746
|
}
|
|
1747
|
+
if (downloadedCount === 0) {
|
|
1748
|
+
console.log("[pushpals] Embedded runtime binaries are already present.");
|
|
1749
|
+
} else {
|
|
1750
|
+
console.log(`[pushpals] Embedded runtime binaries downloaded: ${downloadedCount}.`);
|
|
1751
|
+
}
|
|
1752
|
+
console.log("[pushpals] Embedded runtime binaries are ready.");
|
|
1602
1753
|
return runtimeBinaries;
|
|
1603
1754
|
}
|
|
1604
1755
|
function spawnRuntimeService(name, command, cwd, env, logPath) {
|
|
@@ -1738,18 +1889,34 @@ async function fetchJsonWithTimeout(url, init = {}, timeoutMs = HTTP_TIMEOUT_MS)
|
|
|
1738
1889
|
return null;
|
|
1739
1890
|
}
|
|
1740
1891
|
}
|
|
1741
|
-
function
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1892
|
+
function buildClientTransportQuery(cursor, client) {
|
|
1893
|
+
const params = new URLSearchParams;
|
|
1894
|
+
if (cursor > 0)
|
|
1895
|
+
params.set("after", String(cursor));
|
|
1896
|
+
params.set("clientId", client.clientId);
|
|
1897
|
+
params.set("clientKind", client.kind);
|
|
1898
|
+
params.set("clientLabel", client.label);
|
|
1899
|
+
params.set("clientVersion", client.version);
|
|
1900
|
+
params.set("clientPlatform", client.platform);
|
|
1901
|
+
params.set("clientRepoRoot", client.repoRoot);
|
|
1902
|
+
const query = params.toString();
|
|
1903
|
+
return query ? `?${query}` : "";
|
|
1904
|
+
}
|
|
1905
|
+
function createRuntimeClientId(prefix) {
|
|
1906
|
+
if (typeof crypto?.randomUUID === "function") {
|
|
1907
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
1908
|
+
}
|
|
1909
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
1745
1910
|
}
|
|
1746
|
-
async function probeLocalBuddy(localAgentUrl
|
|
1747
|
-
return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, {
|
|
1911
|
+
async function probeLocalBuddy(localAgentUrl) {
|
|
1912
|
+
return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, {}, LOCALBUDDY_TIMEOUT_MS);
|
|
1748
1913
|
}
|
|
1749
1914
|
async function autoStartRuntimeServices(opts) {
|
|
1750
1915
|
const { runtimePreflight } = opts.preparedRuntime;
|
|
1751
1916
|
const runtimeRoot = opts.preparedRuntime.runtimeRoot;
|
|
1752
1917
|
const runtimeTag = opts.preparedRuntime.runtimeTag || await resolveRuntimeReleaseTag(opts.requestedRuntimeTag);
|
|
1918
|
+
const localBuddyEnabled = Boolean(runtimePreflight.config.localbuddy.enabled);
|
|
1919
|
+
const requireLocalBuddy = opts.requireLocalBuddy ?? true;
|
|
1753
1920
|
console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
|
|
1754
1921
|
console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
|
|
1755
1922
|
console.log(`[pushpals] runtimeTag=${runtimeTag}`);
|
|
@@ -1805,10 +1972,14 @@ ${tail}` : ""}`);
|
|
|
1805
1972
|
} else {
|
|
1806
1973
|
console.log("[pushpals] Server already healthy; skipping embedded server start.");
|
|
1807
1974
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1975
|
+
if (localBuddyEnabled) {
|
|
1976
|
+
console.log("[pushpals] Starting embedded LocalBuddy...");
|
|
1977
|
+
const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, logPathFor("localbuddy"));
|
|
1978
|
+
services.push(localbuddyService);
|
|
1979
|
+
console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
|
|
1980
|
+
} else {
|
|
1981
|
+
console.log("[pushpals] Embedded LocalBuddy disabled by runtime config; skipping start.");
|
|
1982
|
+
}
|
|
1812
1983
|
console.log("[pushpals] Starting embedded RemoteBuddy...");
|
|
1813
1984
|
const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, logPathFor("remotebuddy"));
|
|
1814
1985
|
services.push(remotebuddyService);
|
|
@@ -1858,8 +2029,8 @@ ${tail2}`);
|
|
|
1858
2029
|
${tail}` : ""}`);
|
|
1859
2030
|
}
|
|
1860
2031
|
}
|
|
1861
|
-
const health = await probeLocalBuddy(opts.localAgentUrl
|
|
1862
|
-
if (health?.ok) {
|
|
2032
|
+
const health = localBuddyEnabled ? await probeLocalBuddy(opts.localAgentUrl) : null;
|
|
2033
|
+
if (!requireLocalBuddy || localBuddyEnabled && health?.ok) {
|
|
1863
2034
|
const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
|
|
1864
2035
|
while (Date.now() < stabilityDeadline) {
|
|
1865
2036
|
for (let i = services.length - 1;i >= 0; i--) {
|
|
@@ -1890,13 +2061,16 @@ ${tail}` : ""}`);
|
|
|
1890
2061
|
await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
|
|
1891
2062
|
}
|
|
1892
2063
|
stopRuntimeServices(services);
|
|
2064
|
+
if (!localBuddyEnabled) {
|
|
2065
|
+
throw new Error(`Timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
2066
|
+
}
|
|
1893
2067
|
throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
1894
2068
|
}
|
|
1895
2069
|
function readCliState(pathValue) {
|
|
1896
|
-
if (!
|
|
2070
|
+
if (!existsSync4(pathValue))
|
|
1897
2071
|
return {};
|
|
1898
2072
|
try {
|
|
1899
|
-
const raw =
|
|
2073
|
+
const raw = readFileSync4(pathValue, "utf8");
|
|
1900
2074
|
const parsed = JSON.parse(raw);
|
|
1901
2075
|
if (!parsed || typeof parsed !== "object")
|
|
1902
2076
|
return {};
|
|
@@ -1922,6 +2096,9 @@ function writeCliState(pathValue, state) {
|
|
|
1922
2096
|
writeFileSync(pathValue, `${JSON.stringify(payload, null, 2)}
|
|
1923
2097
|
`, "utf8");
|
|
1924
2098
|
}
|
|
2099
|
+
function resolveCliStatePath(repoRoot) {
|
|
2100
|
+
return resolveGitStateFilePath(repoRoot, "pushpals-cli-state.json");
|
|
2101
|
+
}
|
|
1925
2102
|
async function looksLikeMonitoringHub(url) {
|
|
1926
2103
|
try {
|
|
1927
2104
|
const response = await fetchWithTimeout(url, {}, 700);
|
|
@@ -1942,7 +2119,9 @@ function buildMonitoringHubRuntimeBootstrap(opts) {
|
|
|
1942
2119
|
serverUrl: opts.serverUrl,
|
|
1943
2120
|
localAgentUrl: opts.localAgentUrl,
|
|
1944
2121
|
sessionId: opts.sessionId,
|
|
1945
|
-
|
|
2122
|
+
clientId: `cli-monitor-${opts.sessionId}`,
|
|
2123
|
+
clientKind: "cli_monitor",
|
|
2124
|
+
clientLabel: "CLI Monitor"
|
|
1946
2125
|
};
|
|
1947
2126
|
}
|
|
1948
2127
|
function injectMonitoringHubBootstrap(html, bootstrap) {
|
|
@@ -1983,19 +2162,19 @@ function monitoringHubContentType(pathValue) {
|
|
|
1983
2162
|
}
|
|
1984
2163
|
}
|
|
1985
2164
|
function resolveMonitoringHubAssetPath(assetRoot, pathname) {
|
|
1986
|
-
const root =
|
|
2165
|
+
const root = resolve4(assetRoot);
|
|
1987
2166
|
const rootPrefix = `${root}${root.endsWith("\\") || root.endsWith("/") ? "" : process.platform === "win32" ? "\\" : "/"}`;
|
|
1988
2167
|
const decodedPath = decodeURIComponent(pathname);
|
|
1989
2168
|
const trimmedPath = decodedPath === "/" ? "/index.html" : decodedPath;
|
|
1990
2169
|
const relativePath = trimmedPath.replace(/^\/+/, "");
|
|
1991
|
-
const candidatePath =
|
|
2170
|
+
const candidatePath = resolve4(root, relativePath);
|
|
1992
2171
|
if (candidatePath !== root && !candidatePath.startsWith(rootPrefix))
|
|
1993
2172
|
return null;
|
|
1994
|
-
if (
|
|
2173
|
+
if (existsSync4(candidatePath))
|
|
1995
2174
|
return candidatePath;
|
|
1996
2175
|
if (!extname(relativePath)) {
|
|
1997
|
-
const nestedIndexPath =
|
|
1998
|
-
if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) &&
|
|
2176
|
+
const nestedIndexPath = resolve4(root, relativePath, "index.html");
|
|
2177
|
+
if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) && existsSync4(nestedIndexPath)) {
|
|
1999
2178
|
return nestedIndexPath;
|
|
2000
2179
|
}
|
|
2001
2180
|
return join2(root, "index.html");
|
|
@@ -2004,10 +2183,10 @@ function resolveMonitoringHubAssetPath(assetRoot, pathname) {
|
|
|
2004
2183
|
}
|
|
2005
2184
|
async function serveBundledMonitoringHub(assetRoot, pathname, bootstrap) {
|
|
2006
2185
|
const assetPath = resolveMonitoringHubAssetPath(assetRoot, pathname);
|
|
2007
|
-
if (!assetPath || !
|
|
2186
|
+
if (!assetPath || !existsSync4(assetPath))
|
|
2008
2187
|
return null;
|
|
2009
2188
|
if (assetPath.endsWith("index.html")) {
|
|
2010
|
-
const html = injectMonitoringHubBootstrap(
|
|
2189
|
+
const html = injectMonitoringHubBootstrap(readFileSync4(assetPath, "utf8"), bootstrap);
|
|
2011
2190
|
return new Response(html, {
|
|
2012
2191
|
headers: {
|
|
2013
2192
|
"content-type": "text/html; charset=utf-8",
|
|
@@ -2156,9 +2335,9 @@ function buildEmbeddedMonitoringHubHtml(opts) {
|
|
|
2156
2335
|
</body>
|
|
2157
2336
|
</html>`;
|
|
2158
2337
|
}
|
|
2159
|
-
async function proxyMonitoringHubRequest(serverUrl,
|
|
2338
|
+
async function proxyMonitoringHubRequest(serverUrl, pathValue) {
|
|
2160
2339
|
const target = `${serverUrl}${pathValue}`;
|
|
2161
|
-
const upstream = await fetchWithTimeout(target, {
|
|
2340
|
+
const upstream = await fetchWithTimeout(target, {}, 1e4);
|
|
2162
2341
|
const body = await upstream.text();
|
|
2163
2342
|
return new Response(body, {
|
|
2164
2343
|
status: upstream.status,
|
|
@@ -2177,14 +2356,14 @@ async function startEmbeddedMonitoringHub(opts) {
|
|
|
2177
2356
|
const bootstrap = buildMonitoringHubRuntimeBootstrap({
|
|
2178
2357
|
serverUrl: opts.serverUrl,
|
|
2179
2358
|
localAgentUrl: opts.localAgentUrl,
|
|
2180
|
-
sessionId: opts.sessionId
|
|
2181
|
-
authToken: opts.authToken
|
|
2359
|
+
sessionId: opts.sessionId
|
|
2182
2360
|
});
|
|
2183
2361
|
const candidatePorts = Array.from({ length: MONITOR_SCAN_PORTS }, (_, index) => opts.preferredPort + index).concat(0);
|
|
2184
2362
|
for (const port of candidatePorts) {
|
|
2185
2363
|
try {
|
|
2186
2364
|
const server = Bun.serve({
|
|
2187
2365
|
port,
|
|
2366
|
+
hostname: "127.0.0.1",
|
|
2188
2367
|
idleTimeout: 30,
|
|
2189
2368
|
fetch: async (req) => {
|
|
2190
2369
|
const url = new URL(req.url);
|
|
@@ -2197,16 +2376,16 @@ async function startEmbeddedMonitoringHub(opts) {
|
|
|
2197
2376
|
});
|
|
2198
2377
|
}
|
|
2199
2378
|
if (url.pathname === "/api/status") {
|
|
2200
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2379
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/system/status");
|
|
2201
2380
|
}
|
|
2202
2381
|
if (url.pathname === "/api/requests") {
|
|
2203
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2382
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/requests?status=all&limit=20");
|
|
2204
2383
|
}
|
|
2205
2384
|
if (url.pathname === "/api/jobs") {
|
|
2206
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2385
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/jobs?status=all&limit=20");
|
|
2207
2386
|
}
|
|
2208
2387
|
if (url.pathname === "/api/completions") {
|
|
2209
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2388
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/completions?status=all&limit=20");
|
|
2210
2389
|
}
|
|
2211
2390
|
const bundledResponse = await serveBundledMonitoringHub(monitoringHubAssetRoot, url.pathname, bootstrap);
|
|
2212
2391
|
if (bundledResponse)
|
|
@@ -2227,10 +2406,13 @@ async function startEmbeddedMonitoringHub(opts) {
|
|
|
2227
2406
|
async function resolveMonitoringHub(opts) {
|
|
2228
2407
|
const explicit = normalizeUrl(opts.preferredUrl);
|
|
2229
2408
|
if (explicit) {
|
|
2230
|
-
if (
|
|
2409
|
+
if (!isLoopbackUrl(explicit)) {
|
|
2410
|
+
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is not local; ignoring it and starting a local monitor instead.`);
|
|
2411
|
+
} else if (await looksLikeMonitoringHub(explicit)) {
|
|
2231
2412
|
return { url: explicit, port: 0, stop: () => {}, embedded: false };
|
|
2413
|
+
} else {
|
|
2414
|
+
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
|
|
2232
2415
|
}
|
|
2233
|
-
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
|
|
2234
2416
|
}
|
|
2235
2417
|
for (let port = opts.fallbackPort;port < opts.fallbackPort + MONITOR_SCAN_PORTS; port++) {
|
|
2236
2418
|
const candidate = `http://127.0.0.1:${port}`;
|
|
@@ -2350,12 +2532,11 @@ function formatSessionEventLine(event) {
|
|
|
2350
2532
|
}
|
|
2351
2533
|
return null;
|
|
2352
2534
|
}
|
|
2353
|
-
async function runSessionStream(serverUrl, sessionId,
|
|
2535
|
+
async function runSessionStream(serverUrl, sessionId, client, print, signal) {
|
|
2354
2536
|
let cursor = 0;
|
|
2355
2537
|
while (!signal.aborted) {
|
|
2356
|
-
const headers = authHeaders(authToken);
|
|
2357
2538
|
try {
|
|
2358
|
-
const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${cursor
|
|
2539
|
+
const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${buildClientTransportQuery(cursor, client)}`, {}, 15000);
|
|
2359
2540
|
if (!response.ok || !response.body) {
|
|
2360
2541
|
print(`[pushpals] Session stream unavailable: HTTP ${response.status}`);
|
|
2361
2542
|
await Bun.sleep(SSE_RECONNECT_MS);
|
|
@@ -2469,10 +2650,9 @@ async function main() {
|
|
|
2469
2650
|
process.exit(1);
|
|
2470
2651
|
}
|
|
2471
2652
|
const config = preparedRuntime.runtimePreflight.config;
|
|
2472
|
-
const serverUrl =
|
|
2473
|
-
const localAgentUrl =
|
|
2653
|
+
const serverUrl = normalizeLoopbackUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
|
|
2654
|
+
const localAgentUrl = normalizeLoopbackUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
|
|
2474
2655
|
const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
|
|
2475
|
-
const authToken = config.authToken;
|
|
2476
2656
|
let autoStartedServices = [];
|
|
2477
2657
|
const stopAutoStartedServices = () => {
|
|
2478
2658
|
if (autoStartedServices.length === 0)
|
|
@@ -2480,8 +2660,10 @@ async function main() {
|
|
|
2480
2660
|
stopRuntimeServices(autoStartedServices);
|
|
2481
2661
|
autoStartedServices = [];
|
|
2482
2662
|
};
|
|
2483
|
-
let
|
|
2484
|
-
|
|
2663
|
+
let serverHealthy = await probeServer(serverUrl);
|
|
2664
|
+
let health = await probeLocalBuddy(localAgentUrl);
|
|
2665
|
+
const runtimeNeedsAutoStart = parsed.runtimeOnly ? !serverHealthy : !health?.ok;
|
|
2666
|
+
if (runtimeNeedsAutoStart && !parsed.noAutoStart) {
|
|
2485
2667
|
try {
|
|
2486
2668
|
autoStartedServices = await autoStartRuntimeServices({
|
|
2487
2669
|
repoRoot,
|
|
@@ -2489,45 +2671,58 @@ async function main() {
|
|
|
2489
2671
|
localAgentUrl,
|
|
2490
2672
|
sourceControlManagerPort: config.sourceControlManager.port,
|
|
2491
2673
|
sourceControlManagerRemote: config.sourceControlManager.remote,
|
|
2492
|
-
authToken,
|
|
2493
2674
|
preparedRuntime,
|
|
2494
|
-
requestedRuntimeTag: parsed.runtimeTag
|
|
2675
|
+
requestedRuntimeTag: parsed.runtimeTag,
|
|
2676
|
+
requireLocalBuddy: !parsed.runtimeOnly
|
|
2495
2677
|
});
|
|
2496
|
-
|
|
2678
|
+
serverHealthy = await probeServer(serverUrl);
|
|
2679
|
+
health = await probeLocalBuddy(localAgentUrl);
|
|
2497
2680
|
} catch (err) {
|
|
2498
2681
|
console.error(`[pushpals] Auto-start failed: ${String(err)}`);
|
|
2499
2682
|
stopAutoStartedServices();
|
|
2500
2683
|
}
|
|
2501
2684
|
}
|
|
2502
|
-
if (!
|
|
2503
|
-
console.error(`[pushpals]
|
|
2685
|
+
if (parsed.runtimeOnly && !serverHealthy) {
|
|
2686
|
+
console.error(`[pushpals] Server is unavailable at ${serverUrl}.`);
|
|
2504
2687
|
if (parsed.noAutoStart) {
|
|
2505
2688
|
console.error("[pushpals] Auto-start is disabled (--no-auto-start).");
|
|
2506
2689
|
} else {
|
|
2507
|
-
console.error("[pushpals] Auto-start could not bring
|
|
2690
|
+
console.error("[pushpals] Auto-start could not bring the embedded runtime online.");
|
|
2508
2691
|
}
|
|
2509
2692
|
process.exit(1);
|
|
2510
2693
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
stopAutoStartedServices();
|
|
2519
|
-
console.error("[pushpals] Repo mismatch detected.");
|
|
2520
|
-
console.error(`[pushpals] currentRepo=${repoRoot}`);
|
|
2521
|
-
console.error(`[pushpals] localBuddyRepo=${localBuddyRepo}`);
|
|
2522
|
-
console.error("[pushpals] LocalBuddy must run against the same repo. Start PushPals from this repo and retry.");
|
|
2694
|
+
if (!parsed.runtimeOnly && !health?.ok) {
|
|
2695
|
+
console.error(`[pushpals] LocalBuddy is unavailable at ${localAgentUrl}.`);
|
|
2696
|
+
if (parsed.noAutoStart) {
|
|
2697
|
+
console.error("[pushpals] Auto-start is disabled (--no-auto-start).");
|
|
2698
|
+
} else {
|
|
2699
|
+
console.error("[pushpals] Auto-start could not bring LocalBuddy online.");
|
|
2700
|
+
}
|
|
2523
2701
|
process.exit(1);
|
|
2524
2702
|
}
|
|
2525
|
-
|
|
2526
|
-
if (
|
|
2527
|
-
|
|
2703
|
+
let localBuddySessionId = sessionId;
|
|
2704
|
+
if (!parsed.runtimeOnly) {
|
|
2705
|
+
const localBuddyRepo = health?.repo ? resolve4(health.repo) : "";
|
|
2706
|
+
if (!localBuddyRepo) {
|
|
2707
|
+
stopAutoStartedServices();
|
|
2708
|
+
console.error("[pushpals] LocalBuddy health response did not include repo path.");
|
|
2709
|
+
process.exit(1);
|
|
2710
|
+
}
|
|
2711
|
+
if (normalizePath(localBuddyRepo) !== normalizePath(repoRoot)) {
|
|
2712
|
+
stopAutoStartedServices();
|
|
2713
|
+
console.error("[pushpals] Repo mismatch detected.");
|
|
2714
|
+
console.error(`[pushpals] currentRepo=${repoRoot}`);
|
|
2715
|
+
console.error(`[pushpals] localBuddyRepo=${localBuddyRepo}`);
|
|
2716
|
+
console.error("[pushpals] LocalBuddy must run against the same repo. Start PushPals from this repo and retry.");
|
|
2717
|
+
process.exit(1);
|
|
2718
|
+
}
|
|
2719
|
+
localBuddySessionId = health?.sessionId && String(health.sessionId).trim() ? String(health.sessionId).trim() : sessionId;
|
|
2720
|
+
if (sessionId && sessionId !== localBuddySessionId) {
|
|
2721
|
+
console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
|
|
2722
|
+
}
|
|
2528
2723
|
}
|
|
2529
|
-
const statePath =
|
|
2530
|
-
const saved = readCliState(statePath);
|
|
2724
|
+
const statePath = resolveCliStatePath(repoRoot);
|
|
2725
|
+
const saved = statePath ? readCliState(statePath) : {};
|
|
2531
2726
|
const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
|
|
2532
2727
|
const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
|
|
2533
2728
|
const monitoringHub = await resolveMonitoringHub({
|
|
@@ -2535,32 +2730,48 @@ async function main() {
|
|
|
2535
2730
|
fallbackPort: monitorPort,
|
|
2536
2731
|
serverUrl,
|
|
2537
2732
|
localAgentUrl,
|
|
2538
|
-
sessionId: localBuddySessionId
|
|
2539
|
-
authToken
|
|
2733
|
+
sessionId: localBuddySessionId
|
|
2540
2734
|
});
|
|
2541
2735
|
const monitoringHubUrl = monitoringHub?.url ?? "";
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2736
|
+
if (statePath) {
|
|
2737
|
+
writeCliState(statePath, {
|
|
2738
|
+
monitoringHubUrl: monitoringHubUrl || undefined,
|
|
2739
|
+
serverUrl,
|
|
2740
|
+
localAgentUrl,
|
|
2741
|
+
sessionId: localBuddySessionId,
|
|
2742
|
+
repoRoot
|
|
2743
|
+
});
|
|
2744
|
+
} else {
|
|
2745
|
+
console.warn("[pushpals] Could not resolve git metadata dir; skipping CLI state persistence.");
|
|
2746
|
+
}
|
|
2549
2747
|
console.log("[pushpals] Connected.");
|
|
2550
2748
|
if (monitoringHubUrl) {
|
|
2551
|
-
console.log(`monitoringHubUrl=${monitoringHubUrl}`);
|
|
2749
|
+
console.log(`[pushpals] monitoringHubUrl=${monitoringHubUrl}`);
|
|
2552
2750
|
if (monitoringHub?.embedded) {
|
|
2553
2751
|
console.log("[pushpals] Embedded monitoring hub is running.");
|
|
2554
2752
|
}
|
|
2555
2753
|
} else {
|
|
2556
|
-
console.log("monitoringHubUrl=unavailable");
|
|
2557
|
-
}
|
|
2558
|
-
console.log(`serverUrl=${serverUrl}`);
|
|
2559
|
-
console.log(`localAgentUrl=${localAgentUrl}`);
|
|
2560
|
-
console.log(`sessionId=${localBuddySessionId}`);
|
|
2561
|
-
console.log(`repoRoot=${repoRoot}`);
|
|
2562
|
-
console.log(`cliStateFile=${statePath}`);
|
|
2563
|
-
|
|
2754
|
+
console.log("[pushpals] monitoringHubUrl=unavailable");
|
|
2755
|
+
}
|
|
2756
|
+
console.log(`[pushpals] serverUrl=${serverUrl}`);
|
|
2757
|
+
console.log(`[pushpals] localAgentUrl=${localAgentUrl}`);
|
|
2758
|
+
console.log(`[pushpals] sessionId=${localBuddySessionId}`);
|
|
2759
|
+
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
2760
|
+
console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
|
|
2761
|
+
if (parsed.runtimeOnly) {
|
|
2762
|
+
console.log("[pushpals] runtimeOnly=true");
|
|
2763
|
+
} else {
|
|
2764
|
+
console.log("[pushpals] Type a message and press Enter. Use /exit or exit to quit.");
|
|
2765
|
+
}
|
|
2766
|
+
const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
|
|
2767
|
+
const cliClient = {
|
|
2768
|
+
clientId: createRuntimeClientId("cli"),
|
|
2769
|
+
kind: "cli",
|
|
2770
|
+
label: "CLI",
|
|
2771
|
+
version: cliVersion,
|
|
2772
|
+
platform: `${process.platform}/${process.arch}`,
|
|
2773
|
+
repoRoot
|
|
2774
|
+
};
|
|
2564
2775
|
const streamAbort = new AbortController;
|
|
2565
2776
|
let rl = null;
|
|
2566
2777
|
const printIncoming = (line) => {
|
|
@@ -2575,7 +2786,7 @@ ${line}
|
|
|
2575
2786
|
}
|
|
2576
2787
|
console.log(line);
|
|
2577
2788
|
};
|
|
2578
|
-
const streamTask = parsed.noStream ? Promise.resolve() : runSessionStream(serverUrl, localBuddySessionId,
|
|
2789
|
+
const streamTask = parsed.noStream ? Promise.resolve() : parsed.runtimeOnly ? Promise.resolve() : runSessionStream(serverUrl, localBuddySessionId, cliClient, printIncoming, streamAbort.signal);
|
|
2579
2790
|
let shuttingDown = false;
|
|
2580
2791
|
const requestStop = () => {
|
|
2581
2792
|
if (shuttingDown)
|
|
@@ -2596,6 +2807,39 @@ ${line}
|
|
|
2596
2807
|
process.once("SIGINT", requestStop);
|
|
2597
2808
|
process.once("SIGTERM", requestStop);
|
|
2598
2809
|
process.once("exit", requestStop);
|
|
2810
|
+
if (parsed.runtimeOnly) {
|
|
2811
|
+
console.log("[pushpals] Runtime-only mode is active. Send `exit` on stdin or terminate the process to stop.");
|
|
2812
|
+
await new Promise((resolveStop) => {
|
|
2813
|
+
let resolved = false;
|
|
2814
|
+
const finish = () => {
|
|
2815
|
+
if (resolved)
|
|
2816
|
+
return;
|
|
2817
|
+
resolved = true;
|
|
2818
|
+
resolveStop();
|
|
2819
|
+
};
|
|
2820
|
+
process.once("SIGINT", finish);
|
|
2821
|
+
process.once("SIGTERM", finish);
|
|
2822
|
+
const runtimeOnlyInput = createInterface({
|
|
2823
|
+
input: process.stdin,
|
|
2824
|
+
output: process.stdout,
|
|
2825
|
+
terminal: false
|
|
2826
|
+
});
|
|
2827
|
+
runtimeOnlyInput.on("line", (line) => {
|
|
2828
|
+
if (!isCliExitCommand(line))
|
|
2829
|
+
return;
|
|
2830
|
+
requestStop();
|
|
2831
|
+
runtimeOnlyInput.close();
|
|
2832
|
+
finish();
|
|
2833
|
+
});
|
|
2834
|
+
runtimeOnlyInput.on("close", () => {
|
|
2835
|
+
requestStop();
|
|
2836
|
+
finish();
|
|
2837
|
+
});
|
|
2838
|
+
});
|
|
2839
|
+
requestStop();
|
|
2840
|
+
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2599
2843
|
rl = createInterface({
|
|
2600
2844
|
input: process.stdin,
|
|
2601
2845
|
output: process.stdout,
|
|
@@ -2614,16 +2858,16 @@ ${line}
|
|
|
2614
2858
|
break;
|
|
2615
2859
|
}
|
|
2616
2860
|
if (text === "/hub") {
|
|
2617
|
-
console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
|
|
2861
|
+
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
2618
2862
|
rl.prompt();
|
|
2619
2863
|
continue;
|
|
2620
2864
|
}
|
|
2621
2865
|
if (text === "/status") {
|
|
2622
|
-
console.log(`serverUrl=${serverUrl}`);
|
|
2623
|
-
console.log(`localAgentUrl=${localAgentUrl}`);
|
|
2624
|
-
console.log(`sessionId=${localBuddySessionId}`);
|
|
2625
|
-
console.log(`repoRoot=${repoRoot}`);
|
|
2626
|
-
console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
|
|
2866
|
+
console.log(`[pushpals] serverUrl=${serverUrl}`);
|
|
2867
|
+
console.log(`[pushpals] localAgentUrl=${localAgentUrl}`);
|
|
2868
|
+
console.log(`[pushpals] sessionId=${localBuddySessionId}`);
|
|
2869
|
+
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
2870
|
+
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
2627
2871
|
rl.prompt();
|
|
2628
2872
|
continue;
|
|
2629
2873
|
}
|
|
@@ -2656,12 +2900,14 @@ if (import.meta.main) {
|
|
|
2656
2900
|
export {
|
|
2657
2901
|
startEmbeddedMonitoringHub,
|
|
2658
2902
|
resolveCommandPath,
|
|
2903
|
+
resolveCliStatePath,
|
|
2659
2904
|
resolveBundledRuntimeAssetSource,
|
|
2660
2905
|
resolveBundledMonitoringHubRoot,
|
|
2661
2906
|
prepareCliRuntime,
|
|
2662
2907
|
normalizeChildProcessEnv,
|
|
2663
2908
|
isCliExitCommand,
|
|
2664
2909
|
injectMonitoringHubBootstrap,
|
|
2910
|
+
formatTimestampedCliLine,
|
|
2665
2911
|
buildServiceStopCommand,
|
|
2666
2912
|
buildOpenMonitoringHubCommand,
|
|
2667
2913
|
buildEmbeddedRuntimeEnv,
|