bosun 0.36.2 → 0.36.3
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/analyze-agent-work-helpers.mjs +308 -0
- package/analyze-agent-work.mjs +926 -0
- package/autofix.mjs +2 -0
- package/codex-shell.mjs +85 -10
- package/git-editor-fix.mjs +273 -0
- package/mcp-registry.mjs +579 -0
- package/meeting-workflow-service.mjs +631 -0
- package/monitor.mjs +18 -103
- package/package.json +13 -2
- package/primary-agent.mjs +32 -12
- package/session-tracker.mjs +68 -0
- package/stream-resilience.mjs +17 -7
- package/ui/app.js +19 -4
- package/ui/components/chat-view.js +108 -5
- package/ui/components/session-list.js +1 -1
- package/ui/components/shared.js +188 -15
- package/ui/modules/icons.js +13 -0
- package/ui/modules/utils.js +44 -0
- package/ui/modules/voice.js +15 -6
- package/ui/styles/components.css +99 -3
- package/ui/styles/sessions.css +84 -12
- package/ui/tabs/chat.js +5 -1
- package/ui/tabs/control.js +16 -22
- package/ui/tabs/dashboard.js +85 -8
- package/ui/tabs/library.js +113 -17
- package/ui/tabs/settings.js +116 -2
- package/ui/tabs/tasks.js +388 -39
- package/ui/tabs/telemetry.js +0 -1
- package/ui/tabs/workflows.js +4 -0
- package/ui-server.mjs +193 -19
- package/update-check.mjs +41 -13
- package/voice-relay.mjs +816 -0
- package/voice-tools.mjs +679 -0
- package/workflow-templates/agents.mjs +6 -2
- package/workflow-templates/github.mjs +154 -12
- package/workflow-templates.mjs +3 -0
- package/github-reconciler.mjs +0 -506
- package/merge-strategy.mjs +0 -1210
- package/pr-cleanup-daemon.mjs +0 -992
- package/workspace-reaper.mjs +0 -405
package/ui-server.mjs
CHANGED
|
@@ -1253,6 +1253,85 @@ async function applySharedStateToTasks(tasks) {
|
|
|
1253
1253
|
});
|
|
1254
1254
|
}
|
|
1255
1255
|
|
|
1256
|
+
function normalizeCandidatePath(input) {
|
|
1257
|
+
if (!input) return "";
|
|
1258
|
+
const raw = String(input).trim();
|
|
1259
|
+
if (!raw) return "";
|
|
1260
|
+
try {
|
|
1261
|
+
return resolve(raw);
|
|
1262
|
+
} catch {
|
|
1263
|
+
return "";
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function pickWorkspaceRepoDir(workspace) {
|
|
1268
|
+
if (!workspace || typeof workspace !== "object") return "";
|
|
1269
|
+
const repos = Array.isArray(workspace.repos) ? workspace.repos : [];
|
|
1270
|
+
const activeRepoName = String(workspace.activeRepo || "").trim();
|
|
1271
|
+
const selectedRepo =
|
|
1272
|
+
(activeRepoName
|
|
1273
|
+
? repos.find((repo) => String(repo?.name || "").trim() === activeRepoName)
|
|
1274
|
+
: null) ||
|
|
1275
|
+
repos.find((repo) => repo?.primary) ||
|
|
1276
|
+
repos[0] ||
|
|
1277
|
+
null;
|
|
1278
|
+
|
|
1279
|
+
const candidates = [];
|
|
1280
|
+
const selectedRepoPath = normalizeCandidatePath(selectedRepo?.path);
|
|
1281
|
+
if (selectedRepoPath) candidates.push(selectedRepoPath);
|
|
1282
|
+
const workspacePath = normalizeCandidatePath(workspace.path);
|
|
1283
|
+
if (workspacePath && selectedRepo?.name) {
|
|
1284
|
+
const joined = normalizeCandidatePath(resolve(workspacePath, String(selectedRepo.name)));
|
|
1285
|
+
if (joined) candidates.push(joined);
|
|
1286
|
+
}
|
|
1287
|
+
if (workspacePath) candidates.push(workspacePath);
|
|
1288
|
+
|
|
1289
|
+
for (const candidate of candidates) {
|
|
1290
|
+
if (!candidate || !existsSync(candidate)) continue;
|
|
1291
|
+
if (existsSync(resolve(candidate, ".git"))) return candidate;
|
|
1292
|
+
}
|
|
1293
|
+
for (const candidate of candidates) {
|
|
1294
|
+
if (candidate && existsSync(candidate)) return candidate;
|
|
1295
|
+
}
|
|
1296
|
+
return "";
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function resolveActiveWorkspaceExecutionContext() {
|
|
1300
|
+
const fallback = { workspaceId: "", workspaceDir: repoRoot };
|
|
1301
|
+
const configDir = resolveUiConfigDir();
|
|
1302
|
+
if (!configDir) return fallback;
|
|
1303
|
+
|
|
1304
|
+
const listed = listManagedWorkspaces(configDir, { repoRoot });
|
|
1305
|
+
const active = getActiveManagedWorkspace(configDir);
|
|
1306
|
+
const activeId = String(active?.id || "").trim();
|
|
1307
|
+
const workspace =
|
|
1308
|
+
(activeId
|
|
1309
|
+
? listed.find((entry) => String(entry?.id || "") === activeId)
|
|
1310
|
+
: null) ||
|
|
1311
|
+
active ||
|
|
1312
|
+
listed[0] ||
|
|
1313
|
+
null;
|
|
1314
|
+
if (!workspace) return fallback;
|
|
1315
|
+
|
|
1316
|
+
const workspaceId = String(workspace.id || "").trim();
|
|
1317
|
+
const workspaceDir = pickWorkspaceRepoDir(workspace) || fallback.workspaceDir;
|
|
1318
|
+
return {
|
|
1319
|
+
workspaceId,
|
|
1320
|
+
workspaceDir,
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function resolveSessionWorkspaceDir(session = null) {
|
|
1325
|
+
const metadata =
|
|
1326
|
+
session && typeof session.metadata === "object" && session.metadata
|
|
1327
|
+
? session.metadata
|
|
1328
|
+
: null;
|
|
1329
|
+
const explicit = normalizeCandidatePath(metadata?.workspaceDir);
|
|
1330
|
+
if (explicit && existsSync(explicit)) return explicit;
|
|
1331
|
+
const context = resolveActiveWorkspaceExecutionContext();
|
|
1332
|
+
return context.workspaceDir || repoRoot;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1256
1335
|
function normalizeWorktreePath(input) {
|
|
1257
1336
|
if (!input) return "";
|
|
1258
1337
|
try {
|
|
@@ -2359,7 +2438,11 @@ const SETTINGS_SENSITIVE_KEYS = new Set([
|
|
|
2359
2438
|
"CLOUDFLARE_TUNNEL_CREDENTIALS", "CLOUDFLARE_API_TOKEN",
|
|
2360
2439
|
]);
|
|
2361
2440
|
|
|
2362
|
-
const
|
|
2441
|
+
const SETTINGS_SCHEMA_KEYS = SETTINGS_SCHEMA
|
|
2442
|
+
.map((def) => String(def?.key || "").trim())
|
|
2443
|
+
.filter((key) => key && !key.startsWith("_"));
|
|
2444
|
+
const SETTINGS_KNOWN_SET = new Set([...SETTINGS_KNOWN_KEYS, ...SETTINGS_SCHEMA_KEYS]);
|
|
2445
|
+
const SETTINGS_EFFECTIVE_KEYS = Array.from(SETTINGS_KNOWN_SET);
|
|
2363
2446
|
let _settingsLastUpdateTime = 0;
|
|
2364
2447
|
const ASYNC_UI_COMMAND_BASES = new Set(["/plan"]);
|
|
2365
2448
|
|
|
@@ -2411,7 +2494,7 @@ function buildSettingsResponseData() {
|
|
|
2411
2494
|
);
|
|
2412
2495
|
const { configData } = readConfigDocument();
|
|
2413
2496
|
|
|
2414
|
-
for (const key of
|
|
2497
|
+
for (const key of SETTINGS_EFFECTIVE_KEYS) {
|
|
2415
2498
|
const def = defsByKey.get(key);
|
|
2416
2499
|
let rawValue = process.env[key];
|
|
2417
2500
|
let source = hasSettingValue(rawValue) ? "env" : "unset";
|
|
@@ -2447,7 +2530,7 @@ function buildSettingsResponseData() {
|
|
|
2447
2530
|
}
|
|
2448
2531
|
|
|
2449
2532
|
const displayValue = toSettingsDisplayValue(def, rawValue);
|
|
2450
|
-
if (SETTINGS_SENSITIVE_KEYS.has(key)) {
|
|
2533
|
+
if (SETTINGS_SENSITIVE_KEYS.has(key) || def?.sensitive) {
|
|
2451
2534
|
data[key] = displayValue ? "••••••" : "";
|
|
2452
2535
|
} else {
|
|
2453
2536
|
data[key] = displayValue;
|
|
@@ -8490,9 +8573,28 @@ async function handleApi(req, res, url) {
|
|
|
8490
8573
|
}
|
|
8491
8574
|
const args = (body?.args || "").trim();
|
|
8492
8575
|
const adapter = (body?.adapter || "").trim() || undefined;
|
|
8493
|
-
const
|
|
8576
|
+
const requestedSessionId = String(body?.sessionId || "").trim();
|
|
8577
|
+
const tracker = getSessionTracker();
|
|
8578
|
+
const commandSession = requestedSessionId
|
|
8579
|
+
? tracker.getSessionById(requestedSessionId)
|
|
8580
|
+
: null;
|
|
8581
|
+
const commandCwd = resolveSessionWorkspaceDir(commandSession);
|
|
8582
|
+
const runSdkCommand =
|
|
8583
|
+
typeof uiDeps.execSdkCommand === "function"
|
|
8584
|
+
? uiDeps.execSdkCommand
|
|
8585
|
+
: execSdkCommand;
|
|
8586
|
+
const result = await runSdkCommand(command, args, adapter, {
|
|
8587
|
+
cwd: commandCwd,
|
|
8588
|
+
sessionId: requestedSessionId || undefined,
|
|
8589
|
+
});
|
|
8494
8590
|
const parsed = typeof result === "string" ? result : JSON.stringify(result);
|
|
8495
|
-
jsonResponse(res, 200, {
|
|
8591
|
+
jsonResponse(res, 200, {
|
|
8592
|
+
ok: true,
|
|
8593
|
+
result: parsed,
|
|
8594
|
+
command,
|
|
8595
|
+
adapter: adapter || getPrimaryAgentName(),
|
|
8596
|
+
sessionId: requestedSessionId || null,
|
|
8597
|
+
});
|
|
8496
8598
|
broadcastUiEvent(["agents", "sessions"], "invalidate", {
|
|
8497
8599
|
reason: "sdk-command-executed",
|
|
8498
8600
|
command,
|
|
@@ -8659,6 +8761,13 @@ async function handleApi(req, res, url) {
|
|
|
8659
8761
|
const body = await readJsonBody(req);
|
|
8660
8762
|
const type = body?.type || "manual";
|
|
8661
8763
|
const id = `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8764
|
+
const workspaceContext = resolveActiveWorkspaceExecutionContext();
|
|
8765
|
+
const requestedWorkspaceId = String(body?.workspaceId || "").trim();
|
|
8766
|
+
const requestedWorkspaceDir = normalizeCandidatePath(body?.workspaceDir);
|
|
8767
|
+
const resolvedWorkspaceId =
|
|
8768
|
+
requestedWorkspaceId || workspaceContext.workspaceId;
|
|
8769
|
+
const resolvedWorkspaceDir =
|
|
8770
|
+
requestedWorkspaceDir || workspaceContext.workspaceDir || repoRoot;
|
|
8662
8771
|
const tracker = getSessionTracker();
|
|
8663
8772
|
const session = tracker.createSession({
|
|
8664
8773
|
id,
|
|
@@ -8668,6 +8777,8 @@ async function handleApi(req, res, url) {
|
|
|
8668
8777
|
agent: body?.agent || getPrimaryAgentName(),
|
|
8669
8778
|
mode: body?.mode || getAgentMode(),
|
|
8670
8779
|
model: body?.model || undefined,
|
|
8780
|
+
...(resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {}),
|
|
8781
|
+
...(resolvedWorkspaceDir ? { workspaceDir: resolvedWorkspaceDir } : {}),
|
|
8671
8782
|
},
|
|
8672
8783
|
});
|
|
8673
8784
|
jsonResponse(res, 200, { ok: true, session: { id: session.id, type: session.type, status: session.status, metadata: session.metadata } });
|
|
@@ -8813,6 +8924,7 @@ async function handleApi(req, res, url) {
|
|
|
8813
8924
|
exec = await resolveExecPrimaryPrompt();
|
|
8814
8925
|
}
|
|
8815
8926
|
if (exec) {
|
|
8927
|
+
const sessionWorkspaceDir = resolveSessionWorkspaceDir(session);
|
|
8816
8928
|
// Don't record user event here — execPrimaryPrompt records it
|
|
8817
8929
|
// Respond immediately so the UI doesn't block on agent execution
|
|
8818
8930
|
jsonResponse(res, 200, { ok: true, messageId });
|
|
@@ -8853,6 +8965,7 @@ async function handleApi(req, res, url) {
|
|
|
8853
8965
|
sessionType: "primary",
|
|
8854
8966
|
mode: messageMode,
|
|
8855
8967
|
model: messageModel,
|
|
8968
|
+
cwd: sessionWorkspaceDir,
|
|
8856
8969
|
persistent: true,
|
|
8857
8970
|
sendRawEvents: true,
|
|
8858
8971
|
attachments,
|
|
@@ -8893,6 +9006,44 @@ async function handleApi(req, res, url) {
|
|
|
8893
9006
|
return;
|
|
8894
9007
|
}
|
|
8895
9008
|
|
|
9009
|
+
if (action === "message/edit" && req.method === "POST") {
|
|
9010
|
+
try {
|
|
9011
|
+
const tracker = getSessionTracker();
|
|
9012
|
+
const session = tracker.getSessionById(sessionId);
|
|
9013
|
+
if (!session) {
|
|
9014
|
+
jsonResponse(res, 404, { ok: false, error: "Session not found" });
|
|
9015
|
+
return;
|
|
9016
|
+
}
|
|
9017
|
+
const body = await readJsonBody(req);
|
|
9018
|
+
const content = String(body?.content || "").trim();
|
|
9019
|
+
if (!content) {
|
|
9020
|
+
jsonResponse(res, 400, { ok: false, error: "content is required" });
|
|
9021
|
+
return;
|
|
9022
|
+
}
|
|
9023
|
+
|
|
9024
|
+
const edited = tracker.editUserMessage(sessionId, {
|
|
9025
|
+
messageId: body?.messageId,
|
|
9026
|
+
timestamp: body?.timestamp,
|
|
9027
|
+
previousContent: body?.previousContent,
|
|
9028
|
+
content,
|
|
9029
|
+
});
|
|
9030
|
+
if (!edited?.ok) {
|
|
9031
|
+
const status = edited?.error === "Message not found" ? 404 : 400;
|
|
9032
|
+
jsonResponse(res, status, { ok: false, error: edited?.error || "edit failed" });
|
|
9033
|
+
return;
|
|
9034
|
+
}
|
|
9035
|
+
|
|
9036
|
+
jsonResponse(res, 200, { ok: true, message: edited.message });
|
|
9037
|
+
broadcastUiEvent(["sessions"], "invalidate", {
|
|
9038
|
+
reason: "session-message-edited",
|
|
9039
|
+
sessionId,
|
|
9040
|
+
});
|
|
9041
|
+
} catch (err) {
|
|
9042
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
9043
|
+
}
|
|
9044
|
+
return;
|
|
9045
|
+
}
|
|
9046
|
+
|
|
8896
9047
|
if (action === "archive" && req.method === "POST") {
|
|
8897
9048
|
try {
|
|
8898
9049
|
const tracker = getSessionTracker();
|
|
@@ -9421,14 +9572,18 @@ export async function startTelegramUiServer(options = {}) {
|
|
|
9421
9572
|
shouldReusePersistedPort
|
|
9422
9573
|
? persistedPort
|
|
9423
9574
|
: configuredPort;
|
|
9575
|
+
const hasExplicitEnvPort =
|
|
9576
|
+
Number.isFinite(Number(process.env.TELEGRAM_UI_PORT || "")) &&
|
|
9577
|
+
Number(process.env.TELEGRAM_UI_PORT || "") > 0;
|
|
9424
9578
|
const portSource =
|
|
9425
9579
|
shouldReusePersistedPort
|
|
9426
9580
|
? "cache.ui-last-port"
|
|
9427
9581
|
: options.port != null
|
|
9428
9582
|
? "options.port"
|
|
9429
|
-
:
|
|
9583
|
+
: hasExplicitEnvPort
|
|
9430
9584
|
? "env.TELEGRAM_UI_PORT"
|
|
9431
9585
|
: `default(${DEFAULT_TELEGRAM_UI_PORT})`;
|
|
9586
|
+
const usingFallbackDefaultPort = portSource.startsWith("default(");
|
|
9432
9587
|
|
|
9433
9588
|
if (!Number.isFinite(port) || port < 0) {
|
|
9434
9589
|
console.warn(
|
|
@@ -9745,7 +9900,8 @@ export async function startTelegramUiServer(options = {}) {
|
|
|
9745
9900
|
// Reuse a recent session token when possible so browser sessions survive restarts.
|
|
9746
9901
|
ensureSessionToken();
|
|
9747
9902
|
|
|
9748
|
-
const
|
|
9903
|
+
const host = options.host || DEFAULT_HOST;
|
|
9904
|
+
const maxPortFallbackAttempts = usingFallbackDefaultPort ? 20 : 0;
|
|
9749
9905
|
const listenOnce = (targetPort) =>
|
|
9750
9906
|
new Promise((resolveReady, rejectReady) => {
|
|
9751
9907
|
const onError = (err) => {
|
|
@@ -9758,20 +9914,38 @@ export async function startTelegramUiServer(options = {}) {
|
|
|
9758
9914
|
};
|
|
9759
9915
|
uiServer.once("error", onError);
|
|
9760
9916
|
uiServer.once("listening", onListening);
|
|
9761
|
-
uiServer.listen(targetPort,
|
|
9917
|
+
uiServer.listen(targetPort, host);
|
|
9762
9918
|
});
|
|
9919
|
+
let listenPort = port;
|
|
9920
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
9921
|
+
try {
|
|
9922
|
+
await listenOnce(listenPort);
|
|
9923
|
+
break;
|
|
9924
|
+
} catch (err) {
|
|
9925
|
+
const isAddrInUse = err?.code === "EADDRINUSE";
|
|
9926
|
+
const canRetryPortIncrement =
|
|
9927
|
+
isAddrInUse &&
|
|
9928
|
+
attempt < maxPortFallbackAttempts &&
|
|
9929
|
+
listenPort > 0;
|
|
9930
|
+
if (canRetryPortIncrement) {
|
|
9931
|
+
const nextPort = listenPort + 1;
|
|
9932
|
+
console.warn(
|
|
9933
|
+
`[telegram-ui] port ${listenPort} in use; retrying on ${nextPort} (attempt ${attempt + 1}/${maxPortFallbackAttempts})`,
|
|
9934
|
+
);
|
|
9935
|
+
listenPort = nextPort;
|
|
9936
|
+
continue;
|
|
9937
|
+
}
|
|
9763
9938
|
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
await listenOnce(0);
|
|
9939
|
+
const code = String(err?.code || "").toUpperCase();
|
|
9940
|
+
const canRetryWithEphemeral =
|
|
9941
|
+
allowEphemeralPort && listenPort > 0 && (code === "EADDRINUSE" || code === "EACCES");
|
|
9942
|
+
if (!canRetryWithEphemeral) throw err;
|
|
9943
|
+
console.warn(
|
|
9944
|
+
`[telegram-ui] failed to bind ${host}:${listenPort} (${code || "unknown"}); retrying with ephemeral port`,
|
|
9945
|
+
);
|
|
9946
|
+
await listenOnce(0);
|
|
9947
|
+
break;
|
|
9948
|
+
}
|
|
9775
9949
|
}
|
|
9776
9950
|
} catch (err) {
|
|
9777
9951
|
releaseUiInstanceLock();
|
package/update-check.mjs
CHANGED
|
@@ -529,6 +529,12 @@ let parentPid = null;
|
|
|
529
529
|
let parentCheckInterval = null;
|
|
530
530
|
let cleanupHandlersRegistered = false;
|
|
531
531
|
|
|
532
|
+
function isSuppressedStreamNoiseError(err) {
|
|
533
|
+
const msg = String(err?.message || err || "");
|
|
534
|
+
if (!msg) return false;
|
|
535
|
+
return msg.includes("setRawMode EIO") || msg.includes("read EIO");
|
|
536
|
+
}
|
|
537
|
+
|
|
532
538
|
/**
|
|
533
539
|
* Start a background polling loop that checks for updates every `intervalMs`
|
|
534
540
|
* (default 10 min). When a newer version is found, it:
|
|
@@ -800,17 +806,17 @@ function registerCleanupHandlers() {
|
|
|
800
806
|
stopAutoUpdateLoop();
|
|
801
807
|
});
|
|
802
808
|
|
|
803
|
-
// Handle uncaught exceptions (last resort)
|
|
804
|
-
|
|
809
|
+
// Handle uncaught exceptions (last resort). Do not manually re-emit previous
|
|
810
|
+
// listeners: Node invokes all uncaughtException handlers automatically.
|
|
805
811
|
process.on("uncaughtException", (err) => {
|
|
812
|
+
if (isSuppressedStreamNoiseError(err)) {
|
|
813
|
+
console.log(
|
|
814
|
+
`[auto-update] suppressed stream noise (uncaughtException): ${err?.message || err}`,
|
|
815
|
+
);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
806
818
|
console.error(`[auto-update] Uncaught exception, cleaning up:`, err);
|
|
807
819
|
stopAutoUpdateLoop();
|
|
808
|
-
// Re-emit for other handlers
|
|
809
|
-
if (originalUncaughtException.length > 0) {
|
|
810
|
-
for (const handler of originalUncaughtException) {
|
|
811
|
-
handler(err);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
820
|
});
|
|
815
821
|
}
|
|
816
822
|
|
|
@@ -829,22 +835,44 @@ function printUpdateNotice(current, latest) {
|
|
|
829
835
|
|
|
830
836
|
function promptConfirm(question) {
|
|
831
837
|
return new Promise((res) => {
|
|
838
|
+
let settled = false;
|
|
839
|
+
const settle = (value) => {
|
|
840
|
+
if (settled) return;
|
|
841
|
+
settled = true;
|
|
842
|
+
res(Boolean(value));
|
|
843
|
+
};
|
|
832
844
|
const rl = createInterface({
|
|
833
845
|
input: process.stdin,
|
|
834
846
|
output: process.stdout,
|
|
835
|
-
|
|
847
|
+
// Keep readline out of raw-mode transitions; they can throw EIO during
|
|
848
|
+
// terminal teardown in daemonized/non-interactive contexts.
|
|
849
|
+
terminal: false,
|
|
850
|
+
});
|
|
851
|
+
rl.on("error", (err) => {
|
|
852
|
+
if (isSuppressedStreamNoiseError(err)) {
|
|
853
|
+
console.log(
|
|
854
|
+
`[auto-update] suppressed stream noise (readline): ${err?.message || err}`,
|
|
855
|
+
);
|
|
856
|
+
settle(false);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
console.warn(`[auto-update] prompt failed: ${err?.message || err}`);
|
|
860
|
+
settle(false);
|
|
836
861
|
});
|
|
837
862
|
rl.question(question, (answer) => {
|
|
838
863
|
try {
|
|
839
864
|
rl.close();
|
|
840
865
|
} catch (err) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
866
|
+
if (isSuppressedStreamNoiseError(err)) {
|
|
867
|
+
console.log(
|
|
868
|
+
`[auto-update] suppressed stream noise (readline close): ${err?.message || err}`,
|
|
869
|
+
);
|
|
870
|
+
} else {
|
|
871
|
+
console.warn(`[auto-update] prompt close failed: ${err?.message || err}`);
|
|
844
872
|
}
|
|
845
873
|
}
|
|
846
874
|
const a = answer.trim().toLowerCase();
|
|
847
|
-
|
|
875
|
+
settle(!a || a === "y" || a === "yes");
|
|
848
876
|
});
|
|
849
877
|
});
|
|
850
878
|
}
|