@raysonmeng/agentbridge 0.1.5 → 0.1.6
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +2 -3
- package/README.zh-CN.md +2 -3
- package/dist/cli.js +324 -88
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +121 -117
- package/plugins/agentbridge/server/daemon.js +336 -124
- package/scripts/postinstall.cjs +44 -4
|
@@ -13663,6 +13663,60 @@ class StdioServerTransport {
|
|
|
13663
13663
|
import { EventEmitter } from "events";
|
|
13664
13664
|
import { randomUUID } from "crypto";
|
|
13665
13665
|
import { appendFileSync } from "fs";
|
|
13666
|
+
|
|
13667
|
+
// src/state-dir.ts
|
|
13668
|
+
import { mkdirSync, existsSync } from "fs";
|
|
13669
|
+
import { join } from "path";
|
|
13670
|
+
import { homedir, platform } from "os";
|
|
13671
|
+
|
|
13672
|
+
class StateDirResolver {
|
|
13673
|
+
stateDir;
|
|
13674
|
+
constructor(envOverride) {
|
|
13675
|
+
const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
|
|
13676
|
+
if (override) {
|
|
13677
|
+
this.stateDir = override;
|
|
13678
|
+
} else if (platform() === "darwin") {
|
|
13679
|
+
this.stateDir = join(homedir(), "Library", "Application Support", "AgentBridge");
|
|
13680
|
+
} else {
|
|
13681
|
+
const xdgState = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
|
|
13682
|
+
this.stateDir = join(xdgState, "agentbridge");
|
|
13683
|
+
}
|
|
13684
|
+
}
|
|
13685
|
+
ensure() {
|
|
13686
|
+
if (!existsSync(this.stateDir)) {
|
|
13687
|
+
mkdirSync(this.stateDir, { recursive: true });
|
|
13688
|
+
}
|
|
13689
|
+
}
|
|
13690
|
+
get dir() {
|
|
13691
|
+
return this.stateDir;
|
|
13692
|
+
}
|
|
13693
|
+
get pidFile() {
|
|
13694
|
+
return join(this.stateDir, "daemon.pid");
|
|
13695
|
+
}
|
|
13696
|
+
get tuiPidFile() {
|
|
13697
|
+
return join(this.stateDir, "codex-tui.pid");
|
|
13698
|
+
}
|
|
13699
|
+
get lockFile() {
|
|
13700
|
+
return join(this.stateDir, "daemon.lock");
|
|
13701
|
+
}
|
|
13702
|
+
get statusFile() {
|
|
13703
|
+
return join(this.stateDir, "status.json");
|
|
13704
|
+
}
|
|
13705
|
+
get portsFile() {
|
|
13706
|
+
return join(this.stateDir, "ports.json");
|
|
13707
|
+
}
|
|
13708
|
+
get logFile() {
|
|
13709
|
+
return join(this.stateDir, "agentbridge.log");
|
|
13710
|
+
}
|
|
13711
|
+
get codexWrapperLogFile() {
|
|
13712
|
+
return join(this.stateDir, "codex-wrapper.log");
|
|
13713
|
+
}
|
|
13714
|
+
get killedFile() {
|
|
13715
|
+
return join(this.stateDir, "killed");
|
|
13716
|
+
}
|
|
13717
|
+
}
|
|
13718
|
+
|
|
13719
|
+
// src/claude-adapter.ts
|
|
13666
13720
|
var CLAUDE_INSTRUCTIONS = [
|
|
13667
13721
|
"Codex is an AI coding agent (OpenAI) running in a separate session on the same machine.",
|
|
13668
13722
|
"",
|
|
@@ -13697,23 +13751,27 @@ var CLAUDE_INSTRUCTIONS = [
|
|
|
13697
13751
|
"- If the reply tool returns a busy error, Codex is still executing \u2014 wait and try again later."
|
|
13698
13752
|
].join(`
|
|
13699
13753
|
`);
|
|
13700
|
-
var LOG_FILE = "/tmp/agentbridge.log";
|
|
13701
13754
|
|
|
13702
13755
|
class ClaudeAdapter extends EventEmitter {
|
|
13703
13756
|
server;
|
|
13704
13757
|
notificationSeq = 0;
|
|
13705
13758
|
sessionId;
|
|
13706
13759
|
notificationIdPrefix;
|
|
13760
|
+
instanceId;
|
|
13707
13761
|
replySender = null;
|
|
13762
|
+
logFile;
|
|
13708
13763
|
configuredMode;
|
|
13709
13764
|
resolvedMode = null;
|
|
13710
13765
|
pendingMessages = [];
|
|
13711
13766
|
maxBufferedMessages;
|
|
13712
13767
|
droppedMessageCount = 0;
|
|
13713
|
-
constructor() {
|
|
13768
|
+
constructor(logFile = new StateDirResolver().logFile) {
|
|
13714
13769
|
super();
|
|
13770
|
+
this.logFile = logFile;
|
|
13771
|
+
this.instanceId = randomUUID().slice(0, 8);
|
|
13715
13772
|
this.sessionId = `codex_${Date.now()}`;
|
|
13716
13773
|
this.notificationIdPrefix = randomUUID().replace(/-/g, "").slice(0, 12);
|
|
13774
|
+
this.log(`ClaudeAdapter created (instance=${this.instanceId})`);
|
|
13717
13775
|
const envMode = process.env.AGENTBRIDGE_MODE;
|
|
13718
13776
|
this.configuredMode = envMode && ["push", "pull", "auto"].includes(envMode) ? envMode : "auto";
|
|
13719
13777
|
this.maxBufferedMessages = parseInt(process.env.AGENTBRIDGE_MAX_BUFFERED_MESSAGES ?? "100", 10);
|
|
@@ -13749,11 +13807,12 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
13749
13807
|
this.resolvedMode = this.configuredMode;
|
|
13750
13808
|
this.log(`Delivery mode set by AGENTBRIDGE_MODE: ${this.resolvedMode}`);
|
|
13751
13809
|
} else {
|
|
13752
|
-
this.resolvedMode = "
|
|
13753
|
-
this.log("Delivery mode defaulting to
|
|
13810
|
+
this.resolvedMode = "push";
|
|
13811
|
+
this.log("Delivery mode defaulting to push (set AGENTBRIDGE_MODE=pull to use polling instead)");
|
|
13754
13812
|
}
|
|
13755
13813
|
}
|
|
13756
13814
|
async pushNotification(message) {
|
|
13815
|
+
this.log(`pushNotification (instance=${this.instanceId}, mode=${this.resolvedMode}, msgId=${message.id}, len=${message.content.length})`);
|
|
13757
13816
|
if (this.resolvedMode === "push") {
|
|
13758
13817
|
await this.pushViaChannel(message);
|
|
13759
13818
|
} else {
|
|
@@ -13791,9 +13850,10 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
13791
13850
|
this.log(`Message queue full, dropped oldest message (total dropped: ${this.droppedMessageCount})`);
|
|
13792
13851
|
}
|
|
13793
13852
|
this.pendingMessages.push(message);
|
|
13794
|
-
this.log(`Queued message for pull (${this.pendingMessages.length} pending)`);
|
|
13853
|
+
this.log(`Queued message for pull (${this.pendingMessages.length} pending, instance=${this.instanceId})`);
|
|
13795
13854
|
}
|
|
13796
13855
|
drainMessages() {
|
|
13856
|
+
this.log(`get_messages called (instance=${this.instanceId}, pending=${this.pendingMessages.length}, dropped=${this.droppedMessageCount})`);
|
|
13797
13857
|
if (this.pendingMessages.length === 0 && this.droppedMessageCount === 0) {
|
|
13798
13858
|
return {
|
|
13799
13859
|
content: [{ type: "text", text: "No new messages from Codex." }]
|
|
@@ -13818,6 +13878,7 @@ Codex: ${msg.content}`;
|
|
|
13818
13878
|
}).join(`
|
|
13819
13879
|
|
|
13820
13880
|
`);
|
|
13881
|
+
this.log(`get_messages returning ${count} message(s) (instance=${this.instanceId}, dropped=${dropped})`);
|
|
13821
13882
|
return {
|
|
13822
13883
|
content: [
|
|
13823
13884
|
{
|
|
@@ -13923,7 +13984,7 @@ ${formatted}`
|
|
|
13923
13984
|
`;
|
|
13924
13985
|
process.stderr.write(line);
|
|
13925
13986
|
try {
|
|
13926
|
-
appendFileSync(
|
|
13987
|
+
appendFileSync(this.logFile, line);
|
|
13927
13988
|
} catch {}
|
|
13928
13989
|
}
|
|
13929
13990
|
}
|
|
@@ -14083,7 +14144,7 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14083
14144
|
|
|
14084
14145
|
// src/daemon-lifecycle.ts
|
|
14085
14146
|
import { spawn, execFileSync } from "child_process";
|
|
14086
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
14147
|
+
import { existsSync as existsSync2, readFileSync, unlinkSync, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
14087
14148
|
import { fileURLToPath } from "url";
|
|
14088
14149
|
var DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY ?? "./daemon.ts";
|
|
14089
14150
|
var DAEMON_PATH = fileURLToPath(new URL(DAEMON_ENTRY, import.meta.url));
|
|
@@ -14221,7 +14282,7 @@ class DaemonLifecycle {
|
|
|
14221
14282
|
} catch {}
|
|
14222
14283
|
}
|
|
14223
14284
|
wasKilled() {
|
|
14224
|
-
return
|
|
14285
|
+
return existsSync2(this.stateDir.killedFile);
|
|
14225
14286
|
}
|
|
14226
14287
|
launch() {
|
|
14227
14288
|
this.stateDir.ensure();
|
|
@@ -14342,116 +14403,62 @@ function isProcessAlive(pid) {
|
|
|
14342
14403
|
}
|
|
14343
14404
|
}
|
|
14344
14405
|
|
|
14345
|
-
// src/state-dir.ts
|
|
14346
|
-
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
14347
|
-
import { join } from "path";
|
|
14348
|
-
import { homedir, platform } from "os";
|
|
14349
|
-
|
|
14350
|
-
class StateDirResolver {
|
|
14351
|
-
stateDir;
|
|
14352
|
-
constructor(envOverride) {
|
|
14353
|
-
const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
|
|
14354
|
-
if (override) {
|
|
14355
|
-
this.stateDir = override;
|
|
14356
|
-
} else if (platform() === "darwin") {
|
|
14357
|
-
this.stateDir = join(homedir(), "Library", "Application Support", "AgentBridge");
|
|
14358
|
-
} else {
|
|
14359
|
-
const xdgState = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
|
|
14360
|
-
this.stateDir = join(xdgState, "agentbridge");
|
|
14361
|
-
}
|
|
14362
|
-
}
|
|
14363
|
-
ensure() {
|
|
14364
|
-
if (!existsSync2(this.stateDir)) {
|
|
14365
|
-
mkdirSync(this.stateDir, { recursive: true });
|
|
14366
|
-
}
|
|
14367
|
-
}
|
|
14368
|
-
get dir() {
|
|
14369
|
-
return this.stateDir;
|
|
14370
|
-
}
|
|
14371
|
-
get pidFile() {
|
|
14372
|
-
return join(this.stateDir, "daemon.pid");
|
|
14373
|
-
}
|
|
14374
|
-
get tuiPidFile() {
|
|
14375
|
-
return join(this.stateDir, "codex-tui.pid");
|
|
14376
|
-
}
|
|
14377
|
-
get lockFile() {
|
|
14378
|
-
return join(this.stateDir, "daemon.lock");
|
|
14379
|
-
}
|
|
14380
|
-
get statusFile() {
|
|
14381
|
-
return join(this.stateDir, "status.json");
|
|
14382
|
-
}
|
|
14383
|
-
get portsFile() {
|
|
14384
|
-
return join(this.stateDir, "ports.json");
|
|
14385
|
-
}
|
|
14386
|
-
get logFile() {
|
|
14387
|
-
return join(this.stateDir, "agentbridge.log");
|
|
14388
|
-
}
|
|
14389
|
-
get killedFile() {
|
|
14390
|
-
return join(this.stateDir, "killed");
|
|
14391
|
-
}
|
|
14392
|
-
}
|
|
14393
|
-
|
|
14394
14406
|
// src/config-service.ts
|
|
14395
14407
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
14396
14408
|
import { join as join2 } from "path";
|
|
14397
14409
|
var DEFAULT_CONFIG = {
|
|
14398
14410
|
version: "1.0",
|
|
14399
|
-
|
|
14400
|
-
|
|
14411
|
+
codex: {
|
|
14412
|
+
appPort: 4500,
|
|
14401
14413
|
proxyPort: 4501
|
|
14402
14414
|
},
|
|
14403
|
-
agents: {
|
|
14404
|
-
claude: {
|
|
14405
|
-
role: "Reviewer, Planner",
|
|
14406
|
-
mode: "push"
|
|
14407
|
-
},
|
|
14408
|
-
codex: {
|
|
14409
|
-
role: "Implementer, Executor"
|
|
14410
|
-
}
|
|
14411
|
-
},
|
|
14412
|
-
markers: ["IMPORTANT", "STATUS", "FYI"],
|
|
14413
14415
|
turnCoordination: {
|
|
14414
|
-
attentionWindowSeconds: 15
|
|
14415
|
-
busyGuard: true
|
|
14416
|
+
attentionWindowSeconds: 15
|
|
14416
14417
|
},
|
|
14417
14418
|
idleShutdownSeconds: 30
|
|
14418
14419
|
};
|
|
14419
|
-
var DEFAULT_COLLABORATION_MD = `# Collaboration Rules
|
|
14420
|
-
|
|
14421
|
-
## Roles
|
|
14422
|
-
- Claude: Reviewer, Planner, Hypothesis Challenger
|
|
14423
|
-
- Codex: Implementer, Executor, Reproducer/Verifier
|
|
14424
|
-
|
|
14425
|
-
## Thinking Patterns
|
|
14426
|
-
- Analytical/review tasks: Independent Analysis & Convergence
|
|
14427
|
-
- Implementation tasks: Architect -> Builder -> Critic
|
|
14428
|
-
- Debugging tasks: Hypothesis -> Experiment -> Interpretation
|
|
14429
|
-
|
|
14430
|
-
## Communication
|
|
14431
|
-
- Use explicit phrases: "My independent view is:", "I agree on:", "I disagree on:", "Current consensus:"
|
|
14432
|
-
- Tag messages with [IMPORTANT], [STATUS], or [FYI]
|
|
14433
|
-
|
|
14434
|
-
## Review Process
|
|
14435
|
-
- Cross-review: author never reviews their own code
|
|
14436
|
-
- All changes go through feature/fix branches + PR
|
|
14437
|
-
- Merge via squash merge
|
|
14438
|
-
|
|
14439
|
-
## Custom Rules
|
|
14440
|
-
<!-- Add your project-specific collaboration rules here -->
|
|
14441
|
-
`;
|
|
14442
14420
|
var CONFIG_DIR = ".agentbridge";
|
|
14443
14421
|
var CONFIG_FILE = "config.json";
|
|
14444
|
-
|
|
14422
|
+
function isRecord(value) {
|
|
14423
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14424
|
+
}
|
|
14425
|
+
function normalizeInteger(value, fallback) {
|
|
14426
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
14427
|
+
return value;
|
|
14428
|
+
if (typeof value === "string") {
|
|
14429
|
+
const parsed = Number(value);
|
|
14430
|
+
if (Number.isFinite(parsed))
|
|
14431
|
+
return parsed;
|
|
14432
|
+
}
|
|
14433
|
+
return fallback;
|
|
14434
|
+
}
|
|
14435
|
+
function normalizeConfig(raw) {
|
|
14436
|
+
if (!isRecord(raw))
|
|
14437
|
+
return null;
|
|
14438
|
+
const config2 = raw;
|
|
14439
|
+
const codex = isRecord(config2.codex) ? config2.codex : {};
|
|
14440
|
+
const daemon = isRecord(config2.daemon) ? config2.daemon : {};
|
|
14441
|
+
const turnCoordination = isRecord(config2.turnCoordination) ? config2.turnCoordination : {};
|
|
14442
|
+
return {
|
|
14443
|
+
version: typeof config2.version === "string" ? config2.version : DEFAULT_CONFIG.version,
|
|
14444
|
+
codex: {
|
|
14445
|
+
appPort: normalizeInteger(codex.appPort ?? daemon.port, DEFAULT_CONFIG.codex.appPort),
|
|
14446
|
+
proxyPort: normalizeInteger(codex.proxyPort ?? daemon.proxyPort, DEFAULT_CONFIG.codex.proxyPort)
|
|
14447
|
+
},
|
|
14448
|
+
turnCoordination: {
|
|
14449
|
+
attentionWindowSeconds: normalizeInteger(turnCoordination.attentionWindowSeconds, DEFAULT_CONFIG.turnCoordination.attentionWindowSeconds)
|
|
14450
|
+
},
|
|
14451
|
+
idleShutdownSeconds: normalizeInteger(config2.idleShutdownSeconds, DEFAULT_CONFIG.idleShutdownSeconds)
|
|
14452
|
+
};
|
|
14453
|
+
}
|
|
14445
14454
|
|
|
14446
14455
|
class ConfigService {
|
|
14447
14456
|
configDir;
|
|
14448
14457
|
configPath;
|
|
14449
|
-
collaborationPath;
|
|
14450
14458
|
constructor(projectRoot) {
|
|
14451
14459
|
const root = projectRoot ?? process.cwd();
|
|
14452
14460
|
this.configDir = join2(root, CONFIG_DIR);
|
|
14453
14461
|
this.configPath = join2(this.configDir, CONFIG_FILE);
|
|
14454
|
-
this.collaborationPath = join2(this.configDir, COLLABORATION_FILE);
|
|
14455
14462
|
}
|
|
14456
14463
|
hasConfig() {
|
|
14457
14464
|
return existsSync3(this.configPath);
|
|
@@ -14459,7 +14466,7 @@ class ConfigService {
|
|
|
14459
14466
|
load() {
|
|
14460
14467
|
try {
|
|
14461
14468
|
const raw = readFileSync2(this.configPath, "utf-8");
|
|
14462
|
-
return JSON.parse(raw);
|
|
14469
|
+
return normalizeConfig(JSON.parse(raw));
|
|
14463
14470
|
} catch {
|
|
14464
14471
|
return null;
|
|
14465
14472
|
}
|
|
@@ -14472,17 +14479,6 @@ class ConfigService {
|
|
|
14472
14479
|
writeFileSync2(this.configPath, JSON.stringify(config2, null, 2) + `
|
|
14473
14480
|
`, "utf-8");
|
|
14474
14481
|
}
|
|
14475
|
-
loadCollaboration() {
|
|
14476
|
-
try {
|
|
14477
|
-
return readFileSync2(this.collaborationPath, "utf-8");
|
|
14478
|
-
} catch {
|
|
14479
|
-
return null;
|
|
14480
|
-
}
|
|
14481
|
-
}
|
|
14482
|
-
saveCollaboration(content) {
|
|
14483
|
-
this.ensureConfigDir();
|
|
14484
|
-
writeFileSync2(this.collaborationPath, content, "utf-8");
|
|
14485
|
-
}
|
|
14486
14482
|
initDefaults() {
|
|
14487
14483
|
this.ensureConfigDir();
|
|
14488
14484
|
const created = [];
|
|
@@ -14490,18 +14486,11 @@ class ConfigService {
|
|
|
14490
14486
|
this.save(DEFAULT_CONFIG);
|
|
14491
14487
|
created.push(this.configPath);
|
|
14492
14488
|
}
|
|
14493
|
-
if (!existsSync3(this.collaborationPath)) {
|
|
14494
|
-
this.saveCollaboration(DEFAULT_COLLABORATION_MD);
|
|
14495
|
-
created.push(this.collaborationPath);
|
|
14496
|
-
}
|
|
14497
14489
|
return created;
|
|
14498
14490
|
}
|
|
14499
14491
|
get configFilePath() {
|
|
14500
14492
|
return this.configPath;
|
|
14501
14493
|
}
|
|
14502
|
-
get collaborationFilePath() {
|
|
14503
|
-
return this.collaborationPath;
|
|
14504
|
-
}
|
|
14505
14494
|
ensureConfigDir() {
|
|
14506
14495
|
if (!existsSync3(this.configDir)) {
|
|
14507
14496
|
mkdirSync2(this.configDir, { recursive: true });
|
|
@@ -14521,16 +14510,19 @@ function disabledReplyError(reason) {
|
|
|
14521
14510
|
|
|
14522
14511
|
// src/bridge.ts
|
|
14523
14512
|
var stateDir = new StateDirResolver;
|
|
14513
|
+
stateDir.ensure();
|
|
14524
14514
|
var configService = new ConfigService;
|
|
14525
14515
|
var config2 = configService.loadOrDefault();
|
|
14526
14516
|
var CONTROL_PORT = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
|
|
14527
14517
|
var daemonLifecycle = new DaemonLifecycle({ stateDir, controlPort: CONTROL_PORT, log });
|
|
14528
14518
|
var CONTROL_WS_URL = daemonLifecycle.controlWsUrl;
|
|
14529
|
-
var claude = new ClaudeAdapter;
|
|
14519
|
+
var claude = new ClaudeAdapter(stateDir.logFile);
|
|
14530
14520
|
var daemonClient = new DaemonClient(CONTROL_WS_URL);
|
|
14531
14521
|
var shuttingDown = false;
|
|
14532
14522
|
var daemonDisabled = false;
|
|
14533
14523
|
var daemonDisabledReason = null;
|
|
14524
|
+
var hasSeenTuiConnect = false;
|
|
14525
|
+
var previousTuiConnected = false;
|
|
14534
14526
|
var RECONNECT_NOTIFY_COOLDOWN_MS = 30000;
|
|
14535
14527
|
var DISABLED_RECOVERY_INTERVAL_MS = 5000;
|
|
14536
14528
|
var lastDisconnectNotifyTs = 0;
|
|
@@ -14555,6 +14547,18 @@ daemonClient.on("codexMessage", (message) => {
|
|
|
14555
14547
|
});
|
|
14556
14548
|
daemonClient.on("status", (status) => {
|
|
14557
14549
|
log(`Daemon status: ready=${status.bridgeReady} tui=${status.tuiConnected} thread=${status.threadId ?? "none"} queued=${status.queuedMessageCount}`);
|
|
14550
|
+
if (!hasSeenTuiConnect && status.tuiConnected && !previousTuiConnected) {
|
|
14551
|
+
hasSeenTuiConnect = true;
|
|
14552
|
+
log("First TUI connect detected \u2014 sending kickoff message to Claude");
|
|
14553
|
+
claude.pushNotification(systemMessage("system_tui_kickoff", [
|
|
14554
|
+
"\uD83E\uDD1D Codex has connected via AgentBridge.",
|
|
14555
|
+
"You are now in a multi-agent collaboration session.",
|
|
14556
|
+
"When you receive a complex task, propose a division of labor to Codex.",
|
|
14557
|
+
"Use `reply` to send messages and `get_messages` to check for responses."
|
|
14558
|
+
].join(`
|
|
14559
|
+
`)));
|
|
14560
|
+
}
|
|
14561
|
+
previousTuiConnected = status.tuiConnected;
|
|
14558
14562
|
});
|
|
14559
14563
|
daemonClient.on("disconnect", () => {
|
|
14560
14564
|
if (shuttingDown || daemonDisabled)
|