deskbuddy 0.2.0-beta.37 → 0.2.0-beta.38
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/cli.js +86 -52
- package/dist/gate.cjs +68 -3
- package/dist/launcher.cjs +106 -6
- package/hook/gate.cjs +68 -3
- package/hook/hook-debug.log +24 -0
- package/hook/launcher.cjs +106 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -469,9 +469,9 @@ var defaultExec2 = (file, args, opts) => new Promise((resolve8, reject) => {
|
|
|
469
469
|
} else resolve8();
|
|
470
470
|
});
|
|
471
471
|
});
|
|
472
|
-
function ptyCandidatesIn(dir2, platform = process.platform,
|
|
472
|
+
function ptyCandidatesIn(dir2, platform = process.platform, arch2 = process.arch) {
|
|
473
473
|
const pkg = join2(dir2, "node_modules", PACKAGE_NAME);
|
|
474
|
-
return [...hasVendoredPty(platform,
|
|
474
|
+
return [...hasVendoredPty(platform, arch2) ? [join2(pkg, "vendor", "node-pty")] : [], join2(dir2, "node_modules", "node-pty")];
|
|
475
475
|
}
|
|
476
476
|
function ptyLoadsIn(dir2, execPath = process.execPath) {
|
|
477
477
|
const script = "for(const p of process.argv.slice(1)){try{const m=require(p);if(typeof m.spawn==='function')process.exit(0)}catch{}}process.exit(3)";
|
|
@@ -797,7 +797,7 @@ var AutoEnabler = class {
|
|
|
797
797
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
798
798
|
import { existsSync as existsSync7, readdirSync as readdirSync11, readFileSync as readFileSync14, statSync as statSync13 } from "fs";
|
|
799
799
|
import { homedir as homedir8 } from "os";
|
|
800
|
-
import { basename as basename9, dirname as dirname9, join as
|
|
800
|
+
import { basename as basename9, dirname as dirname9, join as join28 } from "path";
|
|
801
801
|
import WebSocket2 from "ws";
|
|
802
802
|
|
|
803
803
|
// src/core/transcript/locate.ts
|
|
@@ -6924,10 +6924,10 @@ var SNAPSHOT = `(async () => {${PRELUDE2}
|
|
|
6924
6924
|
if (JSON.stringify(out).length > ${SNAPSHOT_MAX}) return { ok: false, why: 'too_big' };
|
|
6925
6925
|
return out;
|
|
6926
6926
|
})()`;
|
|
6927
|
-
function answerExpr2(sessionId, interruptId,
|
|
6928
|
-
const payload = { type, ...body.tool_use_id ? { tool_use_id: body.tool_use_id } : {}, answers: body.answers, state: body.state };
|
|
6927
|
+
function answerExpr2(sessionId, interruptId, type2, body) {
|
|
6928
|
+
const payload = { type: type2, ...body.tool_use_id ? { tool_use_id: body.tool_use_id } : {}, answers: body.answers, state: body.state };
|
|
6929
6929
|
return `(async () => {${PRELUDE2}
|
|
6930
|
-
const sid = ${JSON.stringify(sessionId)}, iid = ${JSON.stringify(interruptId)}, want = ${JSON.stringify(
|
|
6930
|
+
const sid = ${JSON.stringify(sessionId)}, iid = ${JSON.stringify(interruptId)}, want = ${JSON.stringify(type2)};
|
|
6931
6931
|
const payload = ${JSON.stringify(payload)};
|
|
6932
6932
|
const list = (S().agentInterruptCards || {})[sid] || [];
|
|
6933
6933
|
const c = list.find((x) => x && String(x.interruptId) === iid);
|
|
@@ -13108,11 +13108,11 @@ var CodexWatch = class {
|
|
|
13108
13108
|
}
|
|
13109
13109
|
/** 会话状态变了(广播 / 补查):active 订阅,其余退订 */
|
|
13110
13110
|
#applyStatus(id, status) {
|
|
13111
|
-
const
|
|
13112
|
-
if (typeof
|
|
13111
|
+
const type2 = status.type;
|
|
13112
|
+
if (typeof type2 !== "string") return;
|
|
13113
13113
|
const prev = this.#status.get(id);
|
|
13114
|
-
this.#status.set(id,
|
|
13115
|
-
if (
|
|
13114
|
+
this.#status.set(id, type2);
|
|
13115
|
+
if (type2 === "idle" && prev !== "idle") {
|
|
13116
13116
|
try {
|
|
13117
13117
|
this.#d.onIdle?.(id);
|
|
13118
13118
|
} catch {
|
|
@@ -13121,7 +13121,7 @@ var CodexWatch = class {
|
|
|
13121
13121
|
const flags = Array.isArray(status.activeFlags) ? status.activeFlags : [];
|
|
13122
13122
|
if (flags.includes("waitingOnApproval")) this.#waiting.add(id);
|
|
13123
13123
|
else this.#waiting.delete(id);
|
|
13124
|
-
if (
|
|
13124
|
+
if (type2 === "active") {
|
|
13125
13125
|
this.#idle.delete(id);
|
|
13126
13126
|
this.#subscribe(id);
|
|
13127
13127
|
} else {
|
|
@@ -13220,8 +13220,8 @@ var CodexWatch = class {
|
|
|
13220
13220
|
};
|
|
13221
13221
|
this.#own.set(tid, (this.#own.get(tid) ?? 0) + 1);
|
|
13222
13222
|
const prev = this.#chain.get(tid);
|
|
13223
|
-
let
|
|
13224
|
-
const mine = new Promise((r) =>
|
|
13223
|
+
let release2;
|
|
13224
|
+
const mine = new Promise((r) => release2 = r);
|
|
13225
13225
|
const tail4 = (prev ?? Promise.resolve()).then(() => mine);
|
|
13226
13226
|
this.#chain.set(tid, tail4);
|
|
13227
13227
|
try {
|
|
@@ -13234,7 +13234,7 @@ var CodexWatch = class {
|
|
|
13234
13234
|
this.#d.log(`Codex \u53D1\u4E00\u8F6E\u51FA\u9519\uFF1A${e.message}`);
|
|
13235
13235
|
return { sent: false, reason: "start_failed" };
|
|
13236
13236
|
} finally {
|
|
13237
|
-
|
|
13237
|
+
release2();
|
|
13238
13238
|
if (this.#chain.get(tid) === tail4) this.#chain.delete(tid);
|
|
13239
13239
|
const n = (this.#own.get(tid) ?? 1) - 1;
|
|
13240
13240
|
if (n > 0) this.#own.set(tid, n);
|
|
@@ -13425,11 +13425,11 @@ var CodexWatch = class {
|
|
|
13425
13425
|
case "thread/status/changed": {
|
|
13426
13426
|
const id = typeof p.threadId === "string" ? p.threadId : "";
|
|
13427
13427
|
const status = p.status ?? {};
|
|
13428
|
-
const
|
|
13428
|
+
const type2 = status.type;
|
|
13429
13429
|
if (!id) return;
|
|
13430
|
-
if (
|
|
13431
|
-
else if (typeof
|
|
13432
|
-
if (typeof
|
|
13430
|
+
if (type2 === "notLoaded") this.#setLoaded(id, false);
|
|
13431
|
+
else if (typeof type2 === "string") this.#setLoaded(id, true);
|
|
13432
|
+
if (typeof type2 === "string") this.#applyStatus(id, status);
|
|
13433
13433
|
else {
|
|
13434
13434
|
this.#idle.add(id);
|
|
13435
13435
|
this.#unsubscribe(id);
|
|
@@ -14286,9 +14286,37 @@ function describe2(r) {
|
|
|
14286
14286
|
return [r.status, r.n !== void 0 ? `n=${r.n}` : "", r.detail ?? ""].filter(Boolean).join(" ");
|
|
14287
14287
|
}
|
|
14288
14288
|
|
|
14289
|
+
// src/daemon/health.ts
|
|
14290
|
+
import { arch, release, type } from "os";
|
|
14291
|
+
import { join as join24 } from "path";
|
|
14292
|
+
var MEM_LOG_MS = 30 * 60 * 1e3;
|
|
14293
|
+
function envLine() {
|
|
14294
|
+
return `\u8FD0\u884C\u73AF\u5883\uFF1A${type()} ${release()} ${arch()}\uFF1BNode ${process.version}\uFF1BDeskBuddy ${packageVersion()}`;
|
|
14295
|
+
}
|
|
14296
|
+
var mb = (n) => Math.round(n / 1024 / 1024);
|
|
14297
|
+
function memLine() {
|
|
14298
|
+
const m = process.memoryUsage();
|
|
14299
|
+
let handles = -1;
|
|
14300
|
+
try {
|
|
14301
|
+
handles = process.getActiveResourcesInfo().length;
|
|
14302
|
+
} catch {
|
|
14303
|
+
}
|
|
14304
|
+
return `\u5185\u5B58\uFF1Arss ${mb(m.rss)}MB\uFF0C\u5806 ${mb(m.heapUsed)}/${mb(m.heapTotal)}MB\uFF0Cexternal ${mb(m.external)}MB\uFF0CarrayBuffers ${mb(m.arrayBuffers)}MB\uFF1B\u6D3B\u52A8\u53E5\u67C4 ${handles}`;
|
|
14305
|
+
}
|
|
14306
|
+
function enableFatalReport(home) {
|
|
14307
|
+
try {
|
|
14308
|
+
const r = process.report;
|
|
14309
|
+
if (!r) return;
|
|
14310
|
+
r.directory = join24(home, "reports");
|
|
14311
|
+
r.reportOnFatalError = true;
|
|
14312
|
+
r.compact = true;
|
|
14313
|
+
} catch {
|
|
14314
|
+
}
|
|
14315
|
+
}
|
|
14316
|
+
|
|
14289
14317
|
// src/daemon/dshVersion.ts
|
|
14290
14318
|
import { readFileSync as readFileSync11, realpathSync as realpathSync5 } from "fs";
|
|
14291
|
-
import { dirname as dirname8, join as
|
|
14319
|
+
import { dirname as dirname8, join as join25 } from "path";
|
|
14292
14320
|
var DSH_HEADLESS_MIN_VERSION = "0.0.1-rc.1";
|
|
14293
14321
|
var DSH_PKG = "@deepseek-ai/dsh";
|
|
14294
14322
|
var DSH_VERSION_UNSUPPORTED = "tool_version_unsupported";
|
|
@@ -14320,7 +14348,7 @@ function dshSupport(d = defaultDeps2) {
|
|
|
14320
14348
|
}
|
|
14321
14349
|
let dir2 = dirname8(real2);
|
|
14322
14350
|
for (let i = 0; i < 4; i++) {
|
|
14323
|
-
const raw = d.readFile(
|
|
14351
|
+
const raw = d.readFile(join25(dir2, "package.json"));
|
|
14324
14352
|
if (raw !== null) {
|
|
14325
14353
|
let pkg = {};
|
|
14326
14354
|
try {
|
|
@@ -14838,17 +14866,17 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
|
14838
14866
|
|
|
14839
14867
|
// src/core/appended.ts
|
|
14840
14868
|
import { chmodSync as chmodSync4, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync8, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
14841
|
-
import { join as
|
|
14869
|
+
import { join as join26 } from "path";
|
|
14842
14870
|
var APPENDED_MAX_ITEMS = 5;
|
|
14843
14871
|
var APPENDED_TEXT_MAX = 500;
|
|
14844
14872
|
var APPENDED_RESULT_MAX = 800;
|
|
14845
14873
|
var APPENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14846
14874
|
function appendedDir() {
|
|
14847
|
-
return
|
|
14875
|
+
return join26(deskbuddyHome(), "appended");
|
|
14848
14876
|
}
|
|
14849
14877
|
function appendedPath(sessionId) {
|
|
14850
14878
|
const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
14851
|
-
return safe2 ?
|
|
14879
|
+
return safe2 ? join26(appendedDir(), `${safe2}.json`) : null;
|
|
14852
14880
|
}
|
|
14853
14881
|
function readAppended(sessionId) {
|
|
14854
14882
|
const file = appendedPath(sessionId);
|
|
@@ -14878,13 +14906,13 @@ function writeAppended(sessionId, item) {
|
|
|
14878
14906
|
var RUNNING_TTL_MS = 60 * 60 * 1e3;
|
|
14879
14907
|
function runningPath(sessionId) {
|
|
14880
14908
|
const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
14881
|
-
return safe2 ?
|
|
14909
|
+
return safe2 ? join26(deskbuddyHome(), "running", `${safe2}.json`) : null;
|
|
14882
14910
|
}
|
|
14883
14911
|
function writeRunning(sessionId, now = Date.now()) {
|
|
14884
14912
|
const file = runningPath(sessionId);
|
|
14885
14913
|
if (!file) return;
|
|
14886
14914
|
try {
|
|
14887
|
-
mkdirSync11(
|
|
14915
|
+
mkdirSync11(join26(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
|
|
14888
14916
|
writeFileSync10(`${file}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
|
|
14889
14917
|
renameSync8(`${file}.tmp`, file);
|
|
14890
14918
|
} catch {
|
|
@@ -14932,7 +14960,7 @@ function resultSummary(tool, stdout6) {
|
|
|
14932
14960
|
|
|
14933
14961
|
// src/core/dshMailbox.ts
|
|
14934
14962
|
import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync12, readdirSync as readdirSync10, readFileSync as readFileSync13, renameSync as renameSync9, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
14935
|
-
import { join as
|
|
14963
|
+
import { join as join27 } from "path";
|
|
14936
14964
|
var DSH_HEARTBEAT_FRESH_MS = 5 * 1e3;
|
|
14937
14965
|
var DSH_ACK_TIMEOUT_MS = 8 * 1e3;
|
|
14938
14966
|
var ACK_POLL_MS = 200;
|
|
@@ -14943,10 +14971,10 @@ var REVOKE_RETRY_MS = 50;
|
|
|
14943
14971
|
var SID_RE2 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
14944
14972
|
var IID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
14945
14973
|
function dshMailboxDir(home = deskbuddyHome(), channel = "dsh") {
|
|
14946
|
-
return
|
|
14974
|
+
return join27(home, channel);
|
|
14947
14975
|
}
|
|
14948
14976
|
function sub(home, name, channel = "dsh") {
|
|
14949
|
-
return
|
|
14977
|
+
return join27(dshMailboxDir(home, channel), name);
|
|
14950
14978
|
}
|
|
14951
14979
|
function ensureDir2(p) {
|
|
14952
14980
|
mkdirSync12(p, { recursive: true, mode: 448 });
|
|
@@ -14986,7 +15014,7 @@ function liveHeartbeats(sessionId, d0 = {}) {
|
|
|
14986
15014
|
if (!/^\d+\.json$/.test(n)) continue;
|
|
14987
15015
|
let hb;
|
|
14988
15016
|
try {
|
|
14989
|
-
hb = JSON.parse(readFileSync13(
|
|
15017
|
+
hb = JSON.parse(readFileSync13(join27(dir2, n), "utf8"));
|
|
14990
15018
|
} catch {
|
|
14991
15019
|
continue;
|
|
14992
15020
|
}
|
|
@@ -15010,8 +15038,8 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
|
|
|
15010
15038
|
const ackDir = sub(d.home, "ack", d.channel);
|
|
15011
15039
|
ensureDir2(inbox);
|
|
15012
15040
|
ensureDir2(ackDir);
|
|
15013
|
-
const letter =
|
|
15014
|
-
const ackPath =
|
|
15041
|
+
const letter = join27(inbox, `${sessionId}.${msg.instructionId}.json`);
|
|
15042
|
+
const ackPath = join27(ackDir, `${msg.instructionId}.json`);
|
|
15015
15043
|
if (existsSync6(letter)) return { ok: false, reason: "inbox_busy" };
|
|
15016
15044
|
const writtenAt = d.now();
|
|
15017
15045
|
writeJson(letter, {
|
|
@@ -15080,7 +15108,7 @@ function dshClearInbox(home = deskbuddyHome(), channel = "dsh") {
|
|
|
15080
15108
|
var DSH_REPLY_WAIT_MS = 4 * 60 * 1e3;
|
|
15081
15109
|
function takeMailboxReply(instructionId, home = deskbuddyHome(), channel = "dsh") {
|
|
15082
15110
|
if (!IID_RE.test(instructionId)) return null;
|
|
15083
|
-
const p =
|
|
15111
|
+
const p = join27(sub(home, "reply", channel), `${instructionId}.json`);
|
|
15084
15112
|
let v = null;
|
|
15085
15113
|
try {
|
|
15086
15114
|
v = JSON.parse(readFileSync13(p, "utf8"));
|
|
@@ -15109,7 +15137,7 @@ function clearDir(inbox) {
|
|
|
15109
15137
|
let n = 0;
|
|
15110
15138
|
for (const f of names) {
|
|
15111
15139
|
try {
|
|
15112
|
-
rmSync10(
|
|
15140
|
+
rmSync10(join27(inbox, f), { force: true });
|
|
15113
15141
|
n++;
|
|
15114
15142
|
} catch {
|
|
15115
15143
|
}
|
|
@@ -15184,7 +15212,7 @@ function defaultCodexAppServers() {
|
|
|
15184
15212
|
}
|
|
15185
15213
|
function opencodeReady() {
|
|
15186
15214
|
const mod = ocPluginModule(installedHookPath());
|
|
15187
|
-
return isOcPluginInstalled(opencodePluginPath()) && existsSync7(mod) && existsSync7(
|
|
15215
|
+
return isOcPluginInstalled(opencodePluginPath()) && existsSync7(mod) && existsSync7(join28(dirname9(mod), "ocTranslate.mjs"));
|
|
15188
15216
|
}
|
|
15189
15217
|
var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
|
|
15190
15218
|
function orphanResumeText(summary2) {
|
|
@@ -15251,6 +15279,7 @@ var Daemon = class {
|
|
|
15251
15279
|
#ws = null;
|
|
15252
15280
|
#idleTimer = null;
|
|
15253
15281
|
#windowScan = null;
|
|
15282
|
+
#memLog = null;
|
|
15254
15283
|
#aliveReport = null;
|
|
15255
15284
|
#reportRetryTimer = null;
|
|
15256
15285
|
#reportRetry = new ReportRetry({
|
|
@@ -15604,6 +15633,10 @@ var Daemon = class {
|
|
|
15604
15633
|
async startGate() {
|
|
15605
15634
|
const port = await this.#gate.start();
|
|
15606
15635
|
log(`\u6388\u6743\u7AEF\u70B9\u5DF2\u5C31\u7EEA 127.0.0.1:${port}`);
|
|
15636
|
+
enableFatalReport(deskbuddyHome());
|
|
15637
|
+
log(`${envLine()}\uFF1B${memLine()}`);
|
|
15638
|
+
this.#memLog = setInterval(() => log(memLine()), MEM_LOG_MS);
|
|
15639
|
+
this.#memLog.unref();
|
|
15607
15640
|
this.#windowScan = setInterval(() => this.#coord.scanWindows(), 1e3);
|
|
15608
15641
|
this.#windowScan.unref();
|
|
15609
15642
|
this.#aliveReport = setInterval(() => void this.#reportAlive(), ALIVE_REPORT_MS);
|
|
@@ -16002,6 +16035,7 @@ var Daemon = class {
|
|
|
16002
16035
|
this.#clearIdle();
|
|
16003
16036
|
if (this.#windowScan) clearInterval(this.#windowScan);
|
|
16004
16037
|
if (this.#aliveReport) clearInterval(this.#aliveReport);
|
|
16038
|
+
if (this.#memLog) clearInterval(this.#memLog);
|
|
16005
16039
|
if (this.#reportRetryTimer) clearInterval(this.#reportRetryTimer);
|
|
16006
16040
|
if (this.#codexTimer) clearInterval(this.#codexTimer);
|
|
16007
16041
|
if (this.#wakeTimer) clearInterval(this.#wakeTimer);
|
|
@@ -16582,8 +16616,8 @@ var Daemon = class {
|
|
|
16582
16616
|
}
|
|
16583
16617
|
const sid = resolveAlias(cmd.sessionId);
|
|
16584
16618
|
for (let p = this.#opening.get(sid); p; p = this.#opening.get(sid)) await p;
|
|
16585
|
-
let
|
|
16586
|
-
this.#opening.set(sid, new Promise((r) =>
|
|
16619
|
+
let release2;
|
|
16620
|
+
this.#opening.set(sid, new Promise((r) => release2 = r));
|
|
16587
16621
|
try {
|
|
16588
16622
|
if (this.#queue.busy(sid)) await this.#queue.enqueue(sid, async () => {
|
|
16589
16623
|
});
|
|
@@ -16605,7 +16639,7 @@ var Daemon = class {
|
|
|
16605
16639
|
await this.#openWindowFor(cmd, sid, r.cwd);
|
|
16606
16640
|
} finally {
|
|
16607
16641
|
this.#opening.delete(sid);
|
|
16608
|
-
|
|
16642
|
+
release2();
|
|
16609
16643
|
}
|
|
16610
16644
|
}
|
|
16611
16645
|
#guiAvailable() {
|
|
@@ -17044,7 +17078,7 @@ var Daemon = class {
|
|
|
17044
17078
|
for (const n of names) {
|
|
17045
17079
|
if (!n.endsWith(".json")) continue;
|
|
17046
17080
|
try {
|
|
17047
|
-
const s2 = JSON.parse(readFileSync14(
|
|
17081
|
+
const s2 = JSON.parse(readFileSync14(join28(stateDir(), n), "utf8"));
|
|
17048
17082
|
if (typeof s2.sessionId === "string" && typeof s2.tool === "string" && enabled.has(s2.tool)) out.push({ tool: s2.tool, sessionId: s2.sessionId, state: typeof s2.state === "string" ? s2.state : "", cwd: typeof s2.cwd === "string" ? s2.cwd : "" });
|
|
17049
17083
|
} catch {
|
|
17050
17084
|
}
|
|
@@ -17078,12 +17112,12 @@ var Daemon = class {
|
|
|
17078
17112
|
}
|
|
17079
17113
|
case "deepseek_harness": {
|
|
17080
17114
|
if (!TRANSCRIPT_SID_RE.test(s2.sessionId)) return null;
|
|
17081
|
-
const p =
|
|
17115
|
+
const p = join28(deskbuddyHome(), "dsh", "transcript", `${s2.sessionId}.jsonl`);
|
|
17082
17116
|
return existsSync7(p) ? { kind: "file", path: p, format: "dsh" } : null;
|
|
17083
17117
|
}
|
|
17084
17118
|
case "opencode": {
|
|
17085
17119
|
if (!TRANSCRIPT_SID_RE.test(s2.sessionId)) return null;
|
|
17086
|
-
return snapshotSource(`opencode-snap:${s2.sessionId}`,
|
|
17120
|
+
return snapshotSource(`opencode-snap:${s2.sessionId}`, join28(deskbuddyHome(), "opencode", "transcript", `${s2.sessionId}.json`));
|
|
17087
17121
|
}
|
|
17088
17122
|
case "dumate":
|
|
17089
17123
|
return { kind: "http", key: "dumate", fetch: () => this.#dumate.transcriptMessages(s2.sessionId) };
|
|
@@ -18031,7 +18065,7 @@ async function runDisable(tools, opts = {}) {
|
|
|
18031
18065
|
|
|
18032
18066
|
// src/commands/install.ts
|
|
18033
18067
|
import { mkdirSync as mkdirSync13, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
18034
|
-
import { join as
|
|
18068
|
+
import { join as join29 } from "path";
|
|
18035
18069
|
|
|
18036
18070
|
// src/commands/setup.ts
|
|
18037
18071
|
import qrcode from "qrcode-terminal";
|
|
@@ -18248,7 +18282,7 @@ function inSandbox(env) {
|
|
|
18248
18282
|
}
|
|
18249
18283
|
function precheckWritable(dirs) {
|
|
18250
18284
|
for (const dir2 of dirs) {
|
|
18251
|
-
const probe =
|
|
18285
|
+
const probe = join29(dir2, `.deskbuddy-probe-${process.pid}`);
|
|
18252
18286
|
try {
|
|
18253
18287
|
mkdirSync13(dir2, { recursive: true });
|
|
18254
18288
|
writeFileSync13(probe, "");
|
|
@@ -18658,7 +18692,7 @@ async function runAutoSwitch(opts) {
|
|
|
18658
18692
|
// src/wrap/run.ts
|
|
18659
18693
|
import { randomBytes as randomBytes10 } from "crypto";
|
|
18660
18694
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync15, statSync as statSync16, truncateSync as truncateSync3 } from "fs";
|
|
18661
|
-
import { join as
|
|
18695
|
+
import { join as join32 } from "path";
|
|
18662
18696
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
18663
18697
|
|
|
18664
18698
|
// src/wrap/injector.ts
|
|
@@ -19158,7 +19192,7 @@ import { execFileSync as execFileSync4, spawn as spawn4 } from "child_process";
|
|
|
19158
19192
|
import { closeSync as closeSync9, mkdirSync as mkdirSync14, openSync as openSync9, readdirSync as readdirSync12, readFileSync as readFileSync16, rmSync as rmSync14, statSync as statSync15, truncateSync as truncateSync2, writeFileSync as writeFileSync14, renameSync as renameSync10 } from "fs";
|
|
19159
19193
|
import { request as request2 } from "http";
|
|
19160
19194
|
import { createServer as createServer5 } from "net";
|
|
19161
|
-
import { join as
|
|
19195
|
+
import { join as join30 } from "path";
|
|
19162
19196
|
var TAKEOVER_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "--config", "-m", "--model", "-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--enable", "--disable"]);
|
|
19163
19197
|
var SHORT_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "-m", "-s", "-a", "-C"]);
|
|
19164
19198
|
var TAKEOVER_FLAGS = /* @__PURE__ */ new Set(["--search", "--no-alt-screen", "--dangerously-bypass-approvals-and-sandbox"]);
|
|
@@ -19327,10 +19361,10 @@ async function runCodexWindow(real2, args, d) {
|
|
|
19327
19361
|
}
|
|
19328
19362
|
var MAX_LOG_BYTES2 = 1024 * 1024;
|
|
19329
19363
|
function appServerLogPath() {
|
|
19330
|
-
return
|
|
19364
|
+
return join30(deskbuddyHome(), "codex-app-server.log");
|
|
19331
19365
|
}
|
|
19332
19366
|
function windowsDir2() {
|
|
19333
|
-
return
|
|
19367
|
+
return join30(deskbuddyHome(), "codex-windows");
|
|
19334
19368
|
}
|
|
19335
19369
|
function pidAlive5(pid) {
|
|
19336
19370
|
try {
|
|
@@ -19420,7 +19454,7 @@ function killOwnServer(pid, url, args = freshArgs, kill = (p) => killServer(p))
|
|
|
19420
19454
|
if (a && isAppServerArgs(a, url)) kill(pid);
|
|
19421
19455
|
}
|
|
19422
19456
|
function recordPath(rec) {
|
|
19423
|
-
return
|
|
19457
|
+
return join30(windowsDir2(), `${rec.server}.json`);
|
|
19424
19458
|
}
|
|
19425
19459
|
function writeRecord(rec) {
|
|
19426
19460
|
try {
|
|
@@ -19445,7 +19479,7 @@ function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs,
|
|
|
19445
19479
|
return;
|
|
19446
19480
|
}
|
|
19447
19481
|
for (const n of names) {
|
|
19448
|
-
const p =
|
|
19482
|
+
const p = join30(dir2, n);
|
|
19449
19483
|
let rec;
|
|
19450
19484
|
try {
|
|
19451
19485
|
rec = JSON.parse(readFileSync16(p, "utf8"));
|
|
@@ -19468,7 +19502,7 @@ function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs,
|
|
|
19468
19502
|
function launchLog(line) {
|
|
19469
19503
|
try {
|
|
19470
19504
|
mkdirSync14(deskbuddyHome(), { recursive: true });
|
|
19471
|
-
writeFileSync14(
|
|
19505
|
+
writeFileSync14(join30(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
|
|
19472
19506
|
`, { flag: "a" });
|
|
19473
19507
|
} catch {
|
|
19474
19508
|
}
|
|
@@ -19493,7 +19527,7 @@ function defaultCodexWindowDeps(base) {
|
|
|
19493
19527
|
// src/wrap/promptLaunch.ts
|
|
19494
19528
|
import { randomBytes as randomBytes8, randomUUID as randomUUID3 } from "crypto";
|
|
19495
19529
|
import { homedir as homedir10 } from "os";
|
|
19496
|
-
import { join as
|
|
19530
|
+
import { join as join31 } from "path";
|
|
19497
19531
|
var FIRST_LINE_WAIT_MS2 = 2 * 60 * 1e3;
|
|
19498
19532
|
function promptArgs(text, tool = "") {
|
|
19499
19533
|
if (!text) return [];
|
|
@@ -19503,7 +19537,7 @@ function promptArgs(text, tool = "") {
|
|
|
19503
19537
|
function promptEnv(tool, env, launcherPid = process.pid, answerWindowId = "") {
|
|
19504
19538
|
const out = { ...env };
|
|
19505
19539
|
if (tool === "workbuddy") {
|
|
19506
|
-
if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR =
|
|
19540
|
+
if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join31(homedir10(), ".workbuddy");
|
|
19507
19541
|
out.DESKBUDDY_LAUNCHER_PID = String(launcherPid);
|
|
19508
19542
|
out.DESKBUDDY_LAUNCHED_TOOL = tool;
|
|
19509
19543
|
if (answerWindowId) out.DESKBUDDY_LAUNCHED_WINDOW = answerWindowId;
|
|
@@ -20087,7 +20121,7 @@ function childEnv(src, windowId, takeover = true) {
|
|
|
20087
20121
|
}
|
|
20088
20122
|
function wrapLog(line) {
|
|
20089
20123
|
try {
|
|
20090
|
-
const p =
|
|
20124
|
+
const p = join32(deskbuddyHome(), "wrap.log");
|
|
20091
20125
|
mkdirSync15(deskbuddyHome(), { recursive: true });
|
|
20092
20126
|
try {
|
|
20093
20127
|
if (statSync16(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
|
package/dist/gate.cjs
CHANGED
|
@@ -64,7 +64,7 @@ function readStdin() {
|
|
|
64
64
|
})
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function post(port, token, body) {
|
|
67
|
+
function post(port, token, body, info) {
|
|
68
68
|
return new Promise((resolve, reject) => {
|
|
69
69
|
const req = http.request(
|
|
70
70
|
{
|
|
@@ -87,10 +87,75 @@ function post(port, token, body) {
|
|
|
87
87
|
}
|
|
88
88
|
)
|
|
89
89
|
req.on('error', reject)
|
|
90
|
+
// 连上过(守护进程收下了请求)还是根本没连上:窗口模式据此决定要不要等守护进程重启(问题表 268)
|
|
91
|
+
if (info) req.on('socket', (sock) => (sock.connecting ? sock.once('connect', () => (info.connected = true)) : (info.connected = true)))
|
|
90
92
|
req.end(body)
|
|
91
93
|
})
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/** 守护进程等待中途没了(崩溃 / 被杀),launcher 10 秒后重拉:最多等这么久,等它换了新端口再提交一次 */
|
|
97
|
+
const RESTART_WAIT_MS = 3 * 60 * 1000
|
|
98
|
+
const RESTART_POLL_MS = 2000
|
|
99
|
+
|
|
100
|
+
/** 与 src/daemon/autostart.ts STOP_MARK_FILE / STOP_MARK_TTL_MS 一致:DeskBuddy 自己的停止流程留的标记 */
|
|
101
|
+
const STOP_MARK_FILE = 'daemon.stopping'
|
|
102
|
+
const STOP_MARK_TTL_MS = 2 * 60 * 1000
|
|
103
|
+
function stopMarkFresh(now) {
|
|
104
|
+
try {
|
|
105
|
+
const t = Number(fs.readFileSync(path.join(deskbuddyHome(), STOP_MARK_FILE), 'utf8').trim())
|
|
106
|
+
return Number.isFinite(t) && now - t >= 0 && now - t < STOP_MARK_TTL_MS
|
|
107
|
+
} catch (_) {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readGateConf() {
|
|
113
|
+
try {
|
|
114
|
+
const c = JSON.parse(fs.readFileSync(path.join(deskbuddyHome(), 'gate.json'), 'utf8'))
|
|
115
|
+
return c && Number.isInteger(c.port) && typeof c.token === 'string' && c.token ? c : null
|
|
116
|
+
} catch (_) {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 窗口模式的 POST:守护进程在等设备答复期间没了(连上过、之后连接断开),不放弃 ——
|
|
123
|
+
* 等它重启(gate.json 换了端口 / 令牌)后把同一个授权再提交一次,设备上重新出卡(问题表 268:
|
|
124
|
+
* 崩溃后窗口里那道授权被撤、再也推不到设备)。🔴 一开始就连不上(守护进程根本不在)照旧立刻失败,不卡窗口
|
|
125
|
+
*/
|
|
126
|
+
async function postSurviving(conf, body, deps) {
|
|
127
|
+
const d = deps || {}
|
|
128
|
+
const send = d.post || post
|
|
129
|
+
const now = d.now || Date.now
|
|
130
|
+
const sleep = d.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)))
|
|
131
|
+
const read = d.readConf || readGateConf
|
|
132
|
+
const stopping = d.stopping || stopMarkFresh
|
|
133
|
+
let cur = conf
|
|
134
|
+
let deadline = 0
|
|
135
|
+
for (;;) {
|
|
136
|
+
const info = { connected: false }
|
|
137
|
+
try {
|
|
138
|
+
return await send(cur.port, cur.token, body, info)
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// 连上过又断了 = 守护进程这次没了,重新计时(重提交后又崩一次也能再等);没连上且不在等重启 = 守护进程根本不在
|
|
141
|
+
if (info.connected) deadline = now() + RESTART_WAIT_MS
|
|
142
|
+
else if (!deadline) throw e
|
|
143
|
+
// 等新的守护进程起来:gate.json 换了才再提交(同一个端口还连不上就接着等)
|
|
144
|
+
for (;;) {
|
|
145
|
+
// 🔴 正常停止(disable / 升级 / 未授权退出)不等:停止流程先删 gate.json,Windows 结束进程时留停止标记(评审必修)
|
|
146
|
+
if (now() >= deadline || stopping(now())) throw e
|
|
147
|
+
await sleep(RESTART_POLL_MS)
|
|
148
|
+
const c = read()
|
|
149
|
+
if (!c || stopping(now())) throw e
|
|
150
|
+
if (c.port !== cur.port || c.token !== cur.token) {
|
|
151
|
+
cur = c
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
/**
|
|
95
160
|
* 这个 hook 是不是由【交互窗口】里的 Claude Code 触发的。
|
|
96
161
|
* 🔴 实测(2026-09-24,2.1.259):`claude -p` 碰到要授权的工具**也会**触发 PermissionRequest,
|
|
@@ -792,7 +857,7 @@ async function windowMain() {
|
|
|
792
857
|
...(lang ? { lang } : {}),
|
|
793
858
|
...(tool === 'claude_code' ? { ccAutoMode: ccAutoMode(hook) } : {}),
|
|
794
859
|
}
|
|
795
|
-
const res = await
|
|
860
|
+
const res = await postSurviving(conf, JSON.stringify({ mode: 'window', tool, hook, ...extra }))
|
|
796
861
|
if (res.status !== 200) return fail()
|
|
797
862
|
const out = JSON.parse(res.data)
|
|
798
863
|
if (!out || (out.decision !== 'allow' && out.decision !== 'deny')) return fail()
|
|
@@ -925,5 +990,5 @@ if (require.main === module) {
|
|
|
925
990
|
} else {
|
|
926
991
|
// 只给单测(智能体永远是 `node gate.cjs` 直接跑)。
|
|
927
992
|
// 🔴 禁止通过 require 调用本关卡:main 不跑,进程会 exit 0 = 放行
|
|
928
|
-
module.exports = { preToolOutput, PRETOOL_MAX_MS, traeNoticeHook, qoderCliMatches, qoderNodeCliMatches, isInteractiveAgentWin, claudeArgsInteractive, qoderArgsInteractive, isInteractiveQoder, qoderUi, ccAutoMode, CC_POLICY_DOMAIN, CC_REG_KEYS, codebuddyAgent, codebuddyCliArgs, codebuddyAgentWin, winScriptOf, findCodebuddy, codebuddyProduct }
|
|
993
|
+
module.exports = { postSurviving, RESTART_WAIT_MS, preToolOutput, PRETOOL_MAX_MS, traeNoticeHook, qoderCliMatches, qoderNodeCliMatches, isInteractiveAgentWin, claudeArgsInteractive, qoderArgsInteractive, isInteractiveQoder, qoderUi, ccAutoMode, CC_POLICY_DOMAIN, CC_REG_KEYS, codebuddyAgent, codebuddyCliArgs, codebuddyAgentWin, winScriptOf, findCodebuddy, codebuddyProduct }
|
|
929
994
|
}
|
package/dist/launcher.cjs
CHANGED
|
@@ -54,6 +54,77 @@ function paths(h) {
|
|
|
54
54
|
crashes: path.join(versions, 'crashes.json'),
|
|
55
55
|
log: path.join(h, 'daemon.log'),
|
|
56
56
|
pid: path.join(h, 'launcher.pid'),
|
|
57
|
+
stderr: path.join(h, 'daemon.stderr.log'),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 子进程 stderr 落盘(问题表 268):Windows 上 Node 的致命错误(V8 内存耗尽、libuv 断言、原生模块 abort)
|
|
63
|
+
* 退出码都是 0xC0000409,原因只打在 stderr 上;launcher 是隐藏启动的,stderr 没人接 → 日志里什么都没有。
|
|
64
|
+
* 超过 1MB 先转存成 .1(只留一份)。打不开返回 null(照旧 inherit)
|
|
65
|
+
*/
|
|
66
|
+
const STDERR_MAX_BYTES = 1024 * 1024
|
|
67
|
+
function openStderr(h) {
|
|
68
|
+
const p = paths(h).stderr
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(h, { recursive: true })
|
|
71
|
+
try {
|
|
72
|
+
if (fs.statSync(p).size > STDERR_MAX_BYTES) fs.renameSync(p, p + '.1')
|
|
73
|
+
} catch {
|
|
74
|
+
/* 还没有这个文件 */
|
|
75
|
+
}
|
|
76
|
+
const fd = fs.openSync(p, 'a')
|
|
77
|
+
return { fd, start: fs.fstatSync(fd).size }
|
|
78
|
+
} catch {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const FATAL_LINE_RE = /FATAL ERROR|Fatal error|Assertion failed|Check failed|Uncaught|Error:|Exception|out of memory|abort/i
|
|
84
|
+
|
|
85
|
+
/** 这次运行期间写进 stderr 文件的末尾(最多 1500 字符,去掉空行);没有返回 '' */
|
|
86
|
+
function stderrTail(h, start) {
|
|
87
|
+
try {
|
|
88
|
+
const p = paths(h).stderr
|
|
89
|
+
const size = fs.statSync(p).size
|
|
90
|
+
const from = Math.max(start, size - 16384)
|
|
91
|
+
if (size <= from) return ''
|
|
92
|
+
const buf = Buffer.alloc(size - from)
|
|
93
|
+
const fd = fs.openSync(p, 'r')
|
|
94
|
+
try {
|
|
95
|
+
fs.readSync(fd, buf, 0, buf.length, from)
|
|
96
|
+
} finally {
|
|
97
|
+
fs.closeSync(fd)
|
|
98
|
+
}
|
|
99
|
+
const lines = buf.toString('utf8').split(/\r?\n/).filter((l) => l.trim())
|
|
100
|
+
// 关键行(V8 / libuv / Node 的致命错误头、未捕获异常)放前面:原生调用栈在后面,只留末尾几行
|
|
101
|
+
const key = lines.filter((l) => FATAL_LINE_RE.test(l) && !/^\s*\d+: 0x/.test(l)).slice(0, 4) // 原生调用栈帧(` 1: 0x… node::Abort()`)不算
|
|
102
|
+
const tail = lines.filter((l) => !key.includes(l)).slice(-4)
|
|
103
|
+
return [...key, ...(tail.length ? ['…', ...tail] : [])].join(' | ').slice(0, 1500)
|
|
104
|
+
} catch {
|
|
105
|
+
return ''
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Node 致命错误时写诊断报告(JS / 原生调用栈、堆、句柄)的目录;只留最新 5 份 */
|
|
110
|
+
const REPORT_KEEP = 5
|
|
111
|
+
function reportDir(h) {
|
|
112
|
+
const d = path.join(h, 'reports')
|
|
113
|
+
try {
|
|
114
|
+
fs.mkdirSync(d, { recursive: true })
|
|
115
|
+
const old = fs.readdirSync(d).filter((n) => n.endsWith('.json')).sort()
|
|
116
|
+
for (const n of old.slice(0, Math.max(0, old.length - REPORT_KEEP))) fs.rmSync(path.join(d, n), { force: true })
|
|
117
|
+
} catch {
|
|
118
|
+
/* 清不了不影响启动 */
|
|
119
|
+
}
|
|
120
|
+
return d
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function newReports(dir, since) {
|
|
124
|
+
try {
|
|
125
|
+
return fs.readdirSync(dir).filter((n) => n.endsWith('.json') && fs.statSync(path.join(dir, n)).mtimeMs >= since)
|
|
126
|
+
} catch {
|
|
127
|
+
return []
|
|
57
128
|
}
|
|
58
129
|
}
|
|
59
130
|
|
|
@@ -255,7 +326,25 @@ const AFTER_STOP_POLL_MS = 1000
|
|
|
255
326
|
function runOnce(h, cur, args) {
|
|
256
327
|
return new Promise((resolve) => {
|
|
257
328
|
let stopping = false
|
|
258
|
-
const
|
|
329
|
+
const err = openStderr(h)
|
|
330
|
+
const reports = reportDir(h)
|
|
331
|
+
const startedAt = Date.now()
|
|
332
|
+
let child
|
|
333
|
+
try {
|
|
334
|
+
child = spawn(process.execPath, ['--report-on-fatalerror', '--report-compact', '--report-directory=' + reports, cur.cliPath, ...args], {
|
|
335
|
+
stdio: ['inherit', 'inherit', err ? err.fd : 'inherit'],
|
|
336
|
+
windowsHide: true,
|
|
337
|
+
env: childEnv(process.env),
|
|
338
|
+
})
|
|
339
|
+
} finally {
|
|
340
|
+
if (err) {
|
|
341
|
+
try {
|
|
342
|
+
fs.closeSync(err.fd) // 子进程已经拿到一份
|
|
343
|
+
} catch {
|
|
344
|
+
/* ignore */
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
259
348
|
const forward = (sig) => {
|
|
260
349
|
stopping = true
|
|
261
350
|
try {
|
|
@@ -269,9 +358,9 @@ function runOnce(h, cur, args) {
|
|
|
269
358
|
handlers[sig] = () => forward(sig)
|
|
270
359
|
process.on(sig, handlers[sig])
|
|
271
360
|
}
|
|
272
|
-
const done = (code, signal,
|
|
361
|
+
const done = (code, signal, e) => {
|
|
273
362
|
for (const sig of Object.keys(handlers)) process.removeListener(sig, handlers[sig])
|
|
274
|
-
resolve({ code, signal, stopping, err })
|
|
363
|
+
resolve({ code, signal, stopping, err: e, stderr: err ? stderrTail(h, err.start) : '', reports: newReports(reports, startedAt) })
|
|
275
364
|
}
|
|
276
365
|
child.on('error', (e) => done(null, null, e))
|
|
277
366
|
child.on('exit', (code, signal) => done(code, signal, null))
|
|
@@ -323,6 +412,10 @@ async function main(argv, deps) {
|
|
|
323
412
|
}
|
|
324
413
|
const startedAt = now()
|
|
325
414
|
const r = await run(cur)
|
|
415
|
+
// 🔴 退出后重读一次:跑着的时候 daemon 可能已把 verifying 改成 good(连满 3 分钟)。
|
|
416
|
+
// 用启动时的旧副本会把验证通过之后的崩溃也算成「验证期内」、不记退出码(问题表 268 的 4 次「验证期内异常退出」)
|
|
417
|
+
const latest = readCurrent(h)
|
|
418
|
+
const ran = latest && latest.version === cur.version ? latest : cur
|
|
326
419
|
const abnormal = !r.stopping && (r.err || r.signal || r.code !== 0)
|
|
327
420
|
if (r.err) log(h, '起不来版本 ' + cur.version + ':' + r.err.message)
|
|
328
421
|
if (!abnormal) return r.code == null ? 0 : r.code
|
|
@@ -340,16 +433,23 @@ async function main(argv, deps) {
|
|
|
340
433
|
log(h, '守护进程已退出(DeskBuddy 停止流程),launcher 不再重拉')
|
|
341
434
|
return 0
|
|
342
435
|
}
|
|
436
|
+
// 崩溃现场(问题表 268):退出码、stderr 末尾、诊断报告文件名记进 daemon.log
|
|
437
|
+
log(
|
|
438
|
+
h,
|
|
439
|
+
'守护进程退出码 ' + (r.code == null ? r.signal || '?' : r.code) +
|
|
440
|
+
(r.stderr ? ';stderr 末尾:' + r.stderr : ';stderr 没有输出') +
|
|
441
|
+
(r.reports && r.reports.length ? ';诊断报告:reports/' + r.reports.join(', reports/') : '')
|
|
442
|
+
)
|
|
343
443
|
const killed = terminated(r)
|
|
344
444
|
// 被外部 SIGTERM 不是这一版的毛病:不计崩溃、不触发回滚
|
|
345
|
-
const rolledBack = killed ? false : recordCrash(h,
|
|
445
|
+
const rolledBack = killed ? false : recordCrash(h, ran, now())
|
|
346
446
|
if (killed) log(h, '守护进程被外部结束(SIGTERM,不是 DeskBuddy 的停止流程),交给' + (platform === 'win32' ? ' launcher ' : '开机自启') + '重新拉起')
|
|
347
447
|
// 非 Windows:交给 launchd / systemd 按「非正常退出」重拉(它们会重新执行本 launcher,读到回滚后的 current.json)
|
|
348
448
|
if (platform !== 'win32') return r.code == null ? 1 : r.code || 1
|
|
349
449
|
// Windows:「启动」文件夹只在登录时跑一次,退了没人拉 —— 本进程自己重拉。
|
|
350
450
|
// 🔴 DeskBuddy 自己的停止流程先结束本 launcher 再结束 daemon(autostart.ts stopPidFileDaemon),并留了标记,不会走到这里
|
|
351
451
|
let wait = WIN_RESTART_MS
|
|
352
|
-
if (
|
|
452
|
+
if (ran.state !== 'verifying' && !rolledBack) {
|
|
353
453
|
winFails = now() - startedAt >= WIN_STABLE_MS ? 1 : winFails + 1
|
|
354
454
|
wait = Math.min(WIN_RESTART_MS * Math.pow(2, winFails - 1), WIN_RESTART_MAX_MS)
|
|
355
455
|
log(h, '守护进程异常退出(退出码 ' + (r.code == null ? r.signal || '?' : r.code) + '),' + Math.round(wait / 1000) + ' 秒后重拉')
|
|
@@ -358,7 +458,7 @@ async function main(argv, deps) {
|
|
|
358
458
|
}
|
|
359
459
|
}
|
|
360
460
|
|
|
361
|
-
module.exports = { main, childEnv, daemonLockName, lockOwnerAlive, intentionalStop, AFTER_STOP_ENV, SWITCH_ENV_KEYS, pickTarget, recordCrash, rollback, readCurrent, paths, CRASH_WINDOW_MS, CRASH_LIMIT, TERM_EXIT_CODE, UNBOUND_EXIT_CODE, STOP_MARK_FILE, STOP_MARK_TTL_MS, WIN_RESTART_MS, WIN_RESTART_MAX_MS }
|
|
461
|
+
module.exports = { main, runOnce, childEnv, daemonLockName, lockOwnerAlive, intentionalStop, AFTER_STOP_ENV, SWITCH_ENV_KEYS, pickTarget, recordCrash, rollback, readCurrent, paths, CRASH_WINDOW_MS, CRASH_LIMIT, TERM_EXIT_CODE, UNBOUND_EXIT_CODE, STOP_MARK_FILE, STOP_MARK_TTL_MS, WIN_RESTART_MS, WIN_RESTART_MAX_MS }
|
|
362
462
|
|
|
363
463
|
if (require.main === module) {
|
|
364
464
|
main(process.argv.slice(2)).then(
|
package/hook/gate.cjs
CHANGED
|
@@ -64,7 +64,7 @@ function readStdin() {
|
|
|
64
64
|
})
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function post(port, token, body) {
|
|
67
|
+
function post(port, token, body, info) {
|
|
68
68
|
return new Promise((resolve, reject) => {
|
|
69
69
|
const req = http.request(
|
|
70
70
|
{
|
|
@@ -87,10 +87,75 @@ function post(port, token, body) {
|
|
|
87
87
|
}
|
|
88
88
|
)
|
|
89
89
|
req.on('error', reject)
|
|
90
|
+
// 连上过(守护进程收下了请求)还是根本没连上:窗口模式据此决定要不要等守护进程重启(问题表 268)
|
|
91
|
+
if (info) req.on('socket', (sock) => (sock.connecting ? sock.once('connect', () => (info.connected = true)) : (info.connected = true)))
|
|
90
92
|
req.end(body)
|
|
91
93
|
})
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/** 守护进程等待中途没了(崩溃 / 被杀),launcher 10 秒后重拉:最多等这么久,等它换了新端口再提交一次 */
|
|
97
|
+
const RESTART_WAIT_MS = 3 * 60 * 1000
|
|
98
|
+
const RESTART_POLL_MS = 2000
|
|
99
|
+
|
|
100
|
+
/** 与 src/daemon/autostart.ts STOP_MARK_FILE / STOP_MARK_TTL_MS 一致:DeskBuddy 自己的停止流程留的标记 */
|
|
101
|
+
const STOP_MARK_FILE = 'daemon.stopping'
|
|
102
|
+
const STOP_MARK_TTL_MS = 2 * 60 * 1000
|
|
103
|
+
function stopMarkFresh(now) {
|
|
104
|
+
try {
|
|
105
|
+
const t = Number(fs.readFileSync(path.join(deskbuddyHome(), STOP_MARK_FILE), 'utf8').trim())
|
|
106
|
+
return Number.isFinite(t) && now - t >= 0 && now - t < STOP_MARK_TTL_MS
|
|
107
|
+
} catch (_) {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readGateConf() {
|
|
113
|
+
try {
|
|
114
|
+
const c = JSON.parse(fs.readFileSync(path.join(deskbuddyHome(), 'gate.json'), 'utf8'))
|
|
115
|
+
return c && Number.isInteger(c.port) && typeof c.token === 'string' && c.token ? c : null
|
|
116
|
+
} catch (_) {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 窗口模式的 POST:守护进程在等设备答复期间没了(连上过、之后连接断开),不放弃 ——
|
|
123
|
+
* 等它重启(gate.json 换了端口 / 令牌)后把同一个授权再提交一次,设备上重新出卡(问题表 268:
|
|
124
|
+
* 崩溃后窗口里那道授权被撤、再也推不到设备)。🔴 一开始就连不上(守护进程根本不在)照旧立刻失败,不卡窗口
|
|
125
|
+
*/
|
|
126
|
+
async function postSurviving(conf, body, deps) {
|
|
127
|
+
const d = deps || {}
|
|
128
|
+
const send = d.post || post
|
|
129
|
+
const now = d.now || Date.now
|
|
130
|
+
const sleep = d.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)))
|
|
131
|
+
const read = d.readConf || readGateConf
|
|
132
|
+
const stopping = d.stopping || stopMarkFresh
|
|
133
|
+
let cur = conf
|
|
134
|
+
let deadline = 0
|
|
135
|
+
for (;;) {
|
|
136
|
+
const info = { connected: false }
|
|
137
|
+
try {
|
|
138
|
+
return await send(cur.port, cur.token, body, info)
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// 连上过又断了 = 守护进程这次没了,重新计时(重提交后又崩一次也能再等);没连上且不在等重启 = 守护进程根本不在
|
|
141
|
+
if (info.connected) deadline = now() + RESTART_WAIT_MS
|
|
142
|
+
else if (!deadline) throw e
|
|
143
|
+
// 等新的守护进程起来:gate.json 换了才再提交(同一个端口还连不上就接着等)
|
|
144
|
+
for (;;) {
|
|
145
|
+
// 🔴 正常停止(disable / 升级 / 未授权退出)不等:停止流程先删 gate.json,Windows 结束进程时留停止标记(评审必修)
|
|
146
|
+
if (now() >= deadline || stopping(now())) throw e
|
|
147
|
+
await sleep(RESTART_POLL_MS)
|
|
148
|
+
const c = read()
|
|
149
|
+
if (!c || stopping(now())) throw e
|
|
150
|
+
if (c.port !== cur.port || c.token !== cur.token) {
|
|
151
|
+
cur = c
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
/**
|
|
95
160
|
* 这个 hook 是不是由【交互窗口】里的 Claude Code 触发的。
|
|
96
161
|
* 🔴 实测(2026-09-24,2.1.259):`claude -p` 碰到要授权的工具**也会**触发 PermissionRequest,
|
|
@@ -792,7 +857,7 @@ async function windowMain() {
|
|
|
792
857
|
...(lang ? { lang } : {}),
|
|
793
858
|
...(tool === 'claude_code' ? { ccAutoMode: ccAutoMode(hook) } : {}),
|
|
794
859
|
}
|
|
795
|
-
const res = await
|
|
860
|
+
const res = await postSurviving(conf, JSON.stringify({ mode: 'window', tool, hook, ...extra }))
|
|
796
861
|
if (res.status !== 200) return fail()
|
|
797
862
|
const out = JSON.parse(res.data)
|
|
798
863
|
if (!out || (out.decision !== 'allow' && out.decision !== 'deny')) return fail()
|
|
@@ -925,5 +990,5 @@ if (require.main === module) {
|
|
|
925
990
|
} else {
|
|
926
991
|
// 只给单测(智能体永远是 `node gate.cjs` 直接跑)。
|
|
927
992
|
// 🔴 禁止通过 require 调用本关卡:main 不跑,进程会 exit 0 = 放行
|
|
928
|
-
module.exports = { preToolOutput, PRETOOL_MAX_MS, traeNoticeHook, qoderCliMatches, qoderNodeCliMatches, isInteractiveAgentWin, claudeArgsInteractive, qoderArgsInteractive, isInteractiveQoder, qoderUi, ccAutoMode, CC_POLICY_DOMAIN, CC_REG_KEYS, codebuddyAgent, codebuddyCliArgs, codebuddyAgentWin, winScriptOf, findCodebuddy, codebuddyProduct }
|
|
993
|
+
module.exports = { postSurviving, RESTART_WAIT_MS, preToolOutput, PRETOOL_MAX_MS, traeNoticeHook, qoderCliMatches, qoderNodeCliMatches, isInteractiveAgentWin, claudeArgsInteractive, qoderArgsInteractive, isInteractiveQoder, qoderUi, ccAutoMode, CC_POLICY_DOMAIN, CC_REG_KEYS, codebuddyAgent, codebuddyCliArgs, codebuddyAgentWin, winScriptOf, findCodebuddy, codebuddyProduct }
|
|
929
994
|
}
|
package/hook/hook-debug.log
CHANGED
|
@@ -380,3 +380,27 @@
|
|
|
380
380
|
2026-10-12T11:00:13.614Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
381
381
|
2026-10-12T11:00:13.614Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
382
382
|
2026-10-12T11:00:15.993Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
|
383
|
+
2026-10-12T11:15:58.702Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
|
|
384
|
+
2026-10-12T11:16:03.652Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
|
|
385
|
+
2026-10-12T11:16:03.652Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
386
|
+
2026-10-12T11:16:03.752Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
387
|
+
2026-10-12T11:16:03.753Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
388
|
+
2026-10-12T11:16:06.045Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
|
389
|
+
2026-10-12T11:21:53.902Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
|
|
390
|
+
2026-10-12T11:21:58.896Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
|
|
391
|
+
2026-10-12T11:21:58.896Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
392
|
+
2026-10-12T11:21:58.995Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
393
|
+
2026-10-12T11:21:58.995Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
394
|
+
2026-10-12T11:22:01.397Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
|
395
|
+
2026-10-12T11:24:28.256Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
|
|
396
|
+
2026-10-12T11:24:33.247Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
|
|
397
|
+
2026-10-12T11:24:33.247Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
398
|
+
2026-10-12T11:24:33.342Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
399
|
+
2026-10-12T11:24:33.342Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
400
|
+
2026-10-12T11:24:35.695Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
|
401
|
+
2026-10-12T11:26:33.586Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
|
|
402
|
+
2026-10-12T11:26:38.587Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
|
|
403
|
+
2026-10-12T11:26:38.588Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
404
|
+
2026-10-12T11:26:38.709Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
405
|
+
2026-10-12T11:26:38.710Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
406
|
+
2026-10-12T11:26:41.188Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
package/hook/launcher.cjs
CHANGED
|
@@ -54,6 +54,77 @@ function paths(h) {
|
|
|
54
54
|
crashes: path.join(versions, 'crashes.json'),
|
|
55
55
|
log: path.join(h, 'daemon.log'),
|
|
56
56
|
pid: path.join(h, 'launcher.pid'),
|
|
57
|
+
stderr: path.join(h, 'daemon.stderr.log'),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 子进程 stderr 落盘(问题表 268):Windows 上 Node 的致命错误(V8 内存耗尽、libuv 断言、原生模块 abort)
|
|
63
|
+
* 退出码都是 0xC0000409,原因只打在 stderr 上;launcher 是隐藏启动的,stderr 没人接 → 日志里什么都没有。
|
|
64
|
+
* 超过 1MB 先转存成 .1(只留一份)。打不开返回 null(照旧 inherit)
|
|
65
|
+
*/
|
|
66
|
+
const STDERR_MAX_BYTES = 1024 * 1024
|
|
67
|
+
function openStderr(h) {
|
|
68
|
+
const p = paths(h).stderr
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(h, { recursive: true })
|
|
71
|
+
try {
|
|
72
|
+
if (fs.statSync(p).size > STDERR_MAX_BYTES) fs.renameSync(p, p + '.1')
|
|
73
|
+
} catch {
|
|
74
|
+
/* 还没有这个文件 */
|
|
75
|
+
}
|
|
76
|
+
const fd = fs.openSync(p, 'a')
|
|
77
|
+
return { fd, start: fs.fstatSync(fd).size }
|
|
78
|
+
} catch {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const FATAL_LINE_RE = /FATAL ERROR|Fatal error|Assertion failed|Check failed|Uncaught|Error:|Exception|out of memory|abort/i
|
|
84
|
+
|
|
85
|
+
/** 这次运行期间写进 stderr 文件的末尾(最多 1500 字符,去掉空行);没有返回 '' */
|
|
86
|
+
function stderrTail(h, start) {
|
|
87
|
+
try {
|
|
88
|
+
const p = paths(h).stderr
|
|
89
|
+
const size = fs.statSync(p).size
|
|
90
|
+
const from = Math.max(start, size - 16384)
|
|
91
|
+
if (size <= from) return ''
|
|
92
|
+
const buf = Buffer.alloc(size - from)
|
|
93
|
+
const fd = fs.openSync(p, 'r')
|
|
94
|
+
try {
|
|
95
|
+
fs.readSync(fd, buf, 0, buf.length, from)
|
|
96
|
+
} finally {
|
|
97
|
+
fs.closeSync(fd)
|
|
98
|
+
}
|
|
99
|
+
const lines = buf.toString('utf8').split(/\r?\n/).filter((l) => l.trim())
|
|
100
|
+
// 关键行(V8 / libuv / Node 的致命错误头、未捕获异常)放前面:原生调用栈在后面,只留末尾几行
|
|
101
|
+
const key = lines.filter((l) => FATAL_LINE_RE.test(l) && !/^\s*\d+: 0x/.test(l)).slice(0, 4) // 原生调用栈帧(` 1: 0x… node::Abort()`)不算
|
|
102
|
+
const tail = lines.filter((l) => !key.includes(l)).slice(-4)
|
|
103
|
+
return [...key, ...(tail.length ? ['…', ...tail] : [])].join(' | ').slice(0, 1500)
|
|
104
|
+
} catch {
|
|
105
|
+
return ''
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Node 致命错误时写诊断报告(JS / 原生调用栈、堆、句柄)的目录;只留最新 5 份 */
|
|
110
|
+
const REPORT_KEEP = 5
|
|
111
|
+
function reportDir(h) {
|
|
112
|
+
const d = path.join(h, 'reports')
|
|
113
|
+
try {
|
|
114
|
+
fs.mkdirSync(d, { recursive: true })
|
|
115
|
+
const old = fs.readdirSync(d).filter((n) => n.endsWith('.json')).sort()
|
|
116
|
+
for (const n of old.slice(0, Math.max(0, old.length - REPORT_KEEP))) fs.rmSync(path.join(d, n), { force: true })
|
|
117
|
+
} catch {
|
|
118
|
+
/* 清不了不影响启动 */
|
|
119
|
+
}
|
|
120
|
+
return d
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function newReports(dir, since) {
|
|
124
|
+
try {
|
|
125
|
+
return fs.readdirSync(dir).filter((n) => n.endsWith('.json') && fs.statSync(path.join(dir, n)).mtimeMs >= since)
|
|
126
|
+
} catch {
|
|
127
|
+
return []
|
|
57
128
|
}
|
|
58
129
|
}
|
|
59
130
|
|
|
@@ -255,7 +326,25 @@ const AFTER_STOP_POLL_MS = 1000
|
|
|
255
326
|
function runOnce(h, cur, args) {
|
|
256
327
|
return new Promise((resolve) => {
|
|
257
328
|
let stopping = false
|
|
258
|
-
const
|
|
329
|
+
const err = openStderr(h)
|
|
330
|
+
const reports = reportDir(h)
|
|
331
|
+
const startedAt = Date.now()
|
|
332
|
+
let child
|
|
333
|
+
try {
|
|
334
|
+
child = spawn(process.execPath, ['--report-on-fatalerror', '--report-compact', '--report-directory=' + reports, cur.cliPath, ...args], {
|
|
335
|
+
stdio: ['inherit', 'inherit', err ? err.fd : 'inherit'],
|
|
336
|
+
windowsHide: true,
|
|
337
|
+
env: childEnv(process.env),
|
|
338
|
+
})
|
|
339
|
+
} finally {
|
|
340
|
+
if (err) {
|
|
341
|
+
try {
|
|
342
|
+
fs.closeSync(err.fd) // 子进程已经拿到一份
|
|
343
|
+
} catch {
|
|
344
|
+
/* ignore */
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
259
348
|
const forward = (sig) => {
|
|
260
349
|
stopping = true
|
|
261
350
|
try {
|
|
@@ -269,9 +358,9 @@ function runOnce(h, cur, args) {
|
|
|
269
358
|
handlers[sig] = () => forward(sig)
|
|
270
359
|
process.on(sig, handlers[sig])
|
|
271
360
|
}
|
|
272
|
-
const done = (code, signal,
|
|
361
|
+
const done = (code, signal, e) => {
|
|
273
362
|
for (const sig of Object.keys(handlers)) process.removeListener(sig, handlers[sig])
|
|
274
|
-
resolve({ code, signal, stopping, err })
|
|
363
|
+
resolve({ code, signal, stopping, err: e, stderr: err ? stderrTail(h, err.start) : '', reports: newReports(reports, startedAt) })
|
|
275
364
|
}
|
|
276
365
|
child.on('error', (e) => done(null, null, e))
|
|
277
366
|
child.on('exit', (code, signal) => done(code, signal, null))
|
|
@@ -323,6 +412,10 @@ async function main(argv, deps) {
|
|
|
323
412
|
}
|
|
324
413
|
const startedAt = now()
|
|
325
414
|
const r = await run(cur)
|
|
415
|
+
// 🔴 退出后重读一次:跑着的时候 daemon 可能已把 verifying 改成 good(连满 3 分钟)。
|
|
416
|
+
// 用启动时的旧副本会把验证通过之后的崩溃也算成「验证期内」、不记退出码(问题表 268 的 4 次「验证期内异常退出」)
|
|
417
|
+
const latest = readCurrent(h)
|
|
418
|
+
const ran = latest && latest.version === cur.version ? latest : cur
|
|
326
419
|
const abnormal = !r.stopping && (r.err || r.signal || r.code !== 0)
|
|
327
420
|
if (r.err) log(h, '起不来版本 ' + cur.version + ':' + r.err.message)
|
|
328
421
|
if (!abnormal) return r.code == null ? 0 : r.code
|
|
@@ -340,16 +433,23 @@ async function main(argv, deps) {
|
|
|
340
433
|
log(h, '守护进程已退出(DeskBuddy 停止流程),launcher 不再重拉')
|
|
341
434
|
return 0
|
|
342
435
|
}
|
|
436
|
+
// 崩溃现场(问题表 268):退出码、stderr 末尾、诊断报告文件名记进 daemon.log
|
|
437
|
+
log(
|
|
438
|
+
h,
|
|
439
|
+
'守护进程退出码 ' + (r.code == null ? r.signal || '?' : r.code) +
|
|
440
|
+
(r.stderr ? ';stderr 末尾:' + r.stderr : ';stderr 没有输出') +
|
|
441
|
+
(r.reports && r.reports.length ? ';诊断报告:reports/' + r.reports.join(', reports/') : '')
|
|
442
|
+
)
|
|
343
443
|
const killed = terminated(r)
|
|
344
444
|
// 被外部 SIGTERM 不是这一版的毛病:不计崩溃、不触发回滚
|
|
345
|
-
const rolledBack = killed ? false : recordCrash(h,
|
|
445
|
+
const rolledBack = killed ? false : recordCrash(h, ran, now())
|
|
346
446
|
if (killed) log(h, '守护进程被外部结束(SIGTERM,不是 DeskBuddy 的停止流程),交给' + (platform === 'win32' ? ' launcher ' : '开机自启') + '重新拉起')
|
|
347
447
|
// 非 Windows:交给 launchd / systemd 按「非正常退出」重拉(它们会重新执行本 launcher,读到回滚后的 current.json)
|
|
348
448
|
if (platform !== 'win32') return r.code == null ? 1 : r.code || 1
|
|
349
449
|
// Windows:「启动」文件夹只在登录时跑一次,退了没人拉 —— 本进程自己重拉。
|
|
350
450
|
// 🔴 DeskBuddy 自己的停止流程先结束本 launcher 再结束 daemon(autostart.ts stopPidFileDaemon),并留了标记,不会走到这里
|
|
351
451
|
let wait = WIN_RESTART_MS
|
|
352
|
-
if (
|
|
452
|
+
if (ran.state !== 'verifying' && !rolledBack) {
|
|
353
453
|
winFails = now() - startedAt >= WIN_STABLE_MS ? 1 : winFails + 1
|
|
354
454
|
wait = Math.min(WIN_RESTART_MS * Math.pow(2, winFails - 1), WIN_RESTART_MAX_MS)
|
|
355
455
|
log(h, '守护进程异常退出(退出码 ' + (r.code == null ? r.signal || '?' : r.code) + '),' + Math.round(wait / 1000) + ' 秒后重拉')
|
|
@@ -358,7 +458,7 @@ async function main(argv, deps) {
|
|
|
358
458
|
}
|
|
359
459
|
}
|
|
360
460
|
|
|
361
|
-
module.exports = { main, childEnv, daemonLockName, lockOwnerAlive, intentionalStop, AFTER_STOP_ENV, SWITCH_ENV_KEYS, pickTarget, recordCrash, rollback, readCurrent, paths, CRASH_WINDOW_MS, CRASH_LIMIT, TERM_EXIT_CODE, UNBOUND_EXIT_CODE, STOP_MARK_FILE, STOP_MARK_TTL_MS, WIN_RESTART_MS, WIN_RESTART_MAX_MS }
|
|
461
|
+
module.exports = { main, runOnce, childEnv, daemonLockName, lockOwnerAlive, intentionalStop, AFTER_STOP_ENV, SWITCH_ENV_KEYS, pickTarget, recordCrash, rollback, readCurrent, paths, CRASH_WINDOW_MS, CRASH_LIMIT, TERM_EXIT_CODE, UNBOUND_EXIT_CODE, STOP_MARK_FILE, STOP_MARK_TTL_MS, WIN_RESTART_MS, WIN_RESTART_MAX_MS }
|
|
362
462
|
|
|
363
463
|
if (require.main === module) {
|
|
364
464
|
main(process.argv.slice(2)).then(
|