@openagents-org/agent-launcher 0.2.142 → 0.2.145
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/package.json +1 -1
- package/registry.json +274 -66
- package/src/adapters/aider.js +634 -0
- package/src/adapters/amp.js +477 -0
- package/src/adapters/base.js +108 -7
- package/src/adapters/claude.js +50 -34
- package/src/adapters/cline-stream.js +632 -0
- package/src/adapters/cline.js +914 -0
- package/src/adapters/codex.js +14 -16
- package/src/adapters/copilot-stream-parser.js +380 -0
- package/src/adapters/copilot.js +796 -0
- package/src/adapters/cursor.js +9 -15
- package/src/adapters/gemini.js +10 -12
- package/src/adapters/goose-stream.js +268 -0
- package/src/adapters/goose.js +649 -0
- package/src/adapters/health-status.js +166 -0
- package/src/adapters/hermes.js +8 -17
- package/src/adapters/index.js +16 -1
- package/src/adapters/nanoclaw-bridge.js +264 -0
- package/src/adapters/nanoclaw-channel-install.js +411 -0
- package/src/adapters/nanoclaw-control.js +491 -0
- package/src/adapters/nanoclaw-delivery-store.js +0 -0
- package/src/adapters/nanoclaw-protocol.js +349 -0
- package/src/adapters/nanoclaw.js +661 -9
- package/src/adapters/opencode.js +665 -79
- package/src/agent-rows.js +13 -1
- package/src/auth-guidance.js +98 -0
- package/src/cli.js +29 -7
- package/src/config.js +8 -2
- package/src/daemon.js +108 -6
- package/src/installer.js +509 -10
- package/src/paths.js +125 -2
- package/src/tui.js +29 -2
package/src/agent-rows.js
CHANGED
|
@@ -40,7 +40,16 @@ function loadAgentRows(connector) {
|
|
|
40
40
|
let health = null;
|
|
41
41
|
try {
|
|
42
42
|
health = connector.healthCheck(agent.type || 'openclaw');
|
|
43
|
-
if (health && !health.ready)
|
|
43
|
+
if (health && !health.ready) {
|
|
44
|
+
// "Not installed" only when the executable is genuinely missing; an
|
|
45
|
+
// installed-but-signed-out agent shows its login/config message, never
|
|
46
|
+
// "Not installed". Mirrors the launcher's formatHealthLabel rule.
|
|
47
|
+
const notInstalled =
|
|
48
|
+
health.reason === 'not_installed' || health.installed === false;
|
|
49
|
+
notReadyMsg = notInstalled
|
|
50
|
+
? 'Not installed'
|
|
51
|
+
: health.message || 'Not configured';
|
|
52
|
+
}
|
|
44
53
|
} catch {}
|
|
45
54
|
|
|
46
55
|
return {
|
|
@@ -55,6 +64,9 @@ function loadAgentRows(connector) {
|
|
|
55
64
|
path: agent.path || '',
|
|
56
65
|
network: agent.network || '',
|
|
57
66
|
lastError: info.last_error || '',
|
|
67
|
+
// Structured daemon failure reason (spawn_failed / workspace_join_failed /
|
|
68
|
+
// heartbeat_failed / runtime_missing / …) paired with lastError.
|
|
69
|
+
errorReason: info.error_reason || '',
|
|
58
70
|
notReadyMsg,
|
|
59
71
|
health,
|
|
60
72
|
configured: true,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure, content-free helpers that turn an agent's registry `check_ready` plus a
|
|
5
|
+
* health-check result into human-readable authentication guidance.
|
|
6
|
+
*
|
|
7
|
+
* Used by the connector TUI (`showConfigureScreen`) and CLI (`agn env`) so an
|
|
8
|
+
* agent whose sign-in is an OAuth/credential FILE (e.g. Gemini's
|
|
9
|
+
* ~/.gemini/oauth_creds.json) is guided to authenticate instead of being
|
|
10
|
+
* mislabeled "No configuration required for this agent type".
|
|
11
|
+
*
|
|
12
|
+
* These helpers NEVER read, parse, copy, or print credential CONTENTS. They use
|
|
13
|
+
* only the agent's DECLARED metadata — the login command and env-var NAMES from
|
|
14
|
+
* `check_ready`, and the already-computed (non-sensitive) `message` / status
|
|
15
|
+
* fields the installer's health check returns. No token, key, email, or file
|
|
16
|
+
* path is ever emitted.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True when an agent declares any credential / sign-in capability in
|
|
21
|
+
* `check_ready` — an OAuth/credential file, API-key env vars, a service-account
|
|
22
|
+
* path env, or a login command. This is the OPT-IN gate: agents WITHOUT any of
|
|
23
|
+
* these are left exactly as before (the caller keeps showing "No configuration
|
|
24
|
+
* required"), so other agents are never polluted by Gemini's guidance.
|
|
25
|
+
*
|
|
26
|
+
* @param {object|null|undefined} checkReady
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function hasCredentialMetadata(checkReady) {
|
|
30
|
+
if (!checkReady || typeof checkReady !== 'object') return false;
|
|
31
|
+
return !!(
|
|
32
|
+
checkReady.creds_file ||
|
|
33
|
+
(Array.isArray(checkReady.env_vars) && checkReady.env_vars.length) ||
|
|
34
|
+
(Array.isArray(checkReady.creds_path_env) && checkReady.creds_path_env.length) ||
|
|
35
|
+
checkReady.login_command
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A short, NON-sensitive label for a ready auth_mode. Registry-overridable via
|
|
41
|
+
* `check_ready.auth_detected_labels` (e.g. Gemini maps cli_login → "Google
|
|
42
|
+
* account sign-in detected"). Falls back to a generic, agent-agnostic phrase so
|
|
43
|
+
* any future credential-file agent reads sensibly without extra config.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} checkReady
|
|
46
|
+
* @param {string|null|undefined} authMode 'cli_login' | 'api_key' | other
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function readyLabel(checkReady, authMode) {
|
|
50
|
+
const labels = (checkReady && checkReady.auth_detected_labels) || {};
|
|
51
|
+
if (authMode === 'cli_login') return labels.cli_login || 'CLI sign-in detected';
|
|
52
|
+
if (authMode === 'api_key') return labels.api_key || 'API key detected';
|
|
53
|
+
return labels[authMode] || 'authenticated';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build display lines (plain text, no markup, no secrets) describing an agent's
|
|
58
|
+
* authentication state and the concrete next step. The caller is responsible
|
|
59
|
+
* for any terminal styling.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} entry Registry catalog entry (has .label/.name/.check_ready)
|
|
62
|
+
* @param {object|null} health Result of installer.healthCheck(type)
|
|
63
|
+
* @returns {{ ready: boolean, status: string, lines: string[] }}
|
|
64
|
+
*/
|
|
65
|
+
function formatAuthGuidance(entry, health) {
|
|
66
|
+
const checkReady = (entry && entry.check_ready) || {};
|
|
67
|
+
const label = (entry && (entry.label || entry.name)) || 'This agent';
|
|
68
|
+
const h = health || {};
|
|
69
|
+
const lines = [];
|
|
70
|
+
|
|
71
|
+
if (h.installed === false) {
|
|
72
|
+
lines.push(`${label} is not installed yet.`);
|
|
73
|
+
return { ready: false, status: 'not_installed', lines };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (h.ready) {
|
|
77
|
+
lines.push(`Ready — ${readyLabel(checkReady, h.auth_mode)}.`);
|
|
78
|
+
return { ready: true, status: h.auth_status || 'ready', lines };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Not ready: lead with the registry's own (non-sensitive) reason, then the
|
|
82
|
+
// concrete next steps derived from the DECLARED login command + env-var names.
|
|
83
|
+
if (h.message) lines.push(h.message);
|
|
84
|
+
const loginCmd = checkReady.login_command;
|
|
85
|
+
if (loginCmd) {
|
|
86
|
+
lines.push(
|
|
87
|
+
`Run \`${loginCmd}\` in your terminal and complete sign-in, then return here and refresh the agent status.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const envVars = Array.isArray(checkReady.env_vars) ? checkReady.env_vars : [];
|
|
91
|
+
if (envVars.length) {
|
|
92
|
+
lines.push(`Alternatively, configure ${envVars.join(' or ')}.`);
|
|
93
|
+
}
|
|
94
|
+
if (!lines.length) lines.push(`${label} requires authentication before use.`);
|
|
95
|
+
return { ready: false, status: h.auth_status || 'no_credentials', lines };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { hasCredentialMetadata, readyLabel, formatAuthGuidance };
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { AgentConnector, Daemon } = require('./index');
|
|
4
|
+
const { hasCredentialMetadata, formatAuthGuidance } = require('./auth-guidance');
|
|
4
5
|
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
// Arg parsing
|
|
@@ -41,6 +42,9 @@ function parseArgs(argv) {
|
|
|
41
42
|
function getConnector(flags) {
|
|
42
43
|
const opts = {};
|
|
43
44
|
if (flags.config) opts.configDir = flags.config;
|
|
45
|
+
if (flags.endpoint || process.env.OPENAGENTS_ENDPOINT) {
|
|
46
|
+
opts.workspaceEndpoint = flags.endpoint || process.env.OPENAGENTS_ENDPOINT;
|
|
47
|
+
}
|
|
44
48
|
return new AgentConnector(opts);
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -358,6 +362,8 @@ async function cmdConnect(connector, flags, positional) {
|
|
|
358
362
|
if (pid) {
|
|
359
363
|
connector.sendDaemonCommand(`restart:${name}`);
|
|
360
364
|
print('Daemon notified');
|
|
365
|
+
} else {
|
|
366
|
+
print('Daemon is not running. Run `agn up` to bring this agent online.');
|
|
361
367
|
}
|
|
362
368
|
} catch (e) {
|
|
363
369
|
print(`Error: ${e.message}`);
|
|
@@ -478,14 +484,30 @@ async function cmdEnv(connector, flags, positional) {
|
|
|
478
484
|
const display = field.password && val ? '***' : (val || '(not set)');
|
|
479
485
|
print(` ${field.name}: ${display} ${field.required ? '(required)' : ''}`);
|
|
480
486
|
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// No env_config, but the agent may still require a sign-in/credential (e.g.
|
|
491
|
+
// Gemini's OAuth login). Surface readiness + login guidance from the registry
|
|
492
|
+
// check_ready rather than a misleading "No env vars configured". Gated on
|
|
493
|
+
// credential metadata so plain no-config agents keep their existing output.
|
|
494
|
+
const entry = connector.registry.getEntry(type);
|
|
495
|
+
if (hasCredentialMetadata(entry && entry.check_ready)) {
|
|
496
|
+
let health = null;
|
|
497
|
+
try { health = connector.healthCheck(type); } catch {}
|
|
498
|
+
const g = formatAuthGuidance(entry, health);
|
|
499
|
+
print(`${entry.label || type} authentication status: ${g.ready ? 'Ready' : 'Not ready'}`);
|
|
500
|
+
print('');
|
|
501
|
+
for (const line of g.lines) print(line);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const entries = Object.entries(env);
|
|
506
|
+
if (entries.length === 0) {
|
|
507
|
+
print(`No env vars configured for ${type}`);
|
|
481
508
|
} else {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
print(`No env vars configured for ${type}`);
|
|
485
|
-
} else {
|
|
486
|
-
for (const [k, v] of entries) {
|
|
487
|
-
print(` ${k}: ${v}`);
|
|
488
|
-
}
|
|
509
|
+
for (const [k, v] of entries) {
|
|
510
|
+
print(` ${k}: ${v}`);
|
|
489
511
|
}
|
|
490
512
|
}
|
|
491
513
|
}
|
package/src/config.js
CHANGED
|
@@ -426,8 +426,14 @@ function parseYamlValue(val) {
|
|
|
426
426
|
if (val === 'true') return true;
|
|
427
427
|
if (val === 'false') return false;
|
|
428
428
|
if (/^\d+$/.test(val)) return parseInt(val, 10);
|
|
429
|
-
if (
|
|
430
|
-
|
|
429
|
+
if (val.startsWith('"') && val.endsWith('"')) {
|
|
430
|
+
// Reverse serializeYamlValue's escaping (\\ → \, \" → "). Without this a
|
|
431
|
+
// double-quoted Windows path like "D:\重要资料" (quoted because of the drive
|
|
432
|
+
// colon) round-trips to D:\\重要资料 and accumulates an extra backslash on
|
|
433
|
+
// every save/load cycle, corrupting the agent's working dir.
|
|
434
|
+
return val.slice(1, -1).replace(/\\(["\\])/g, '$1');
|
|
435
|
+
}
|
|
436
|
+
if (val.startsWith("'") && val.endsWith("'")) {
|
|
431
437
|
return val.slice(1, -1);
|
|
432
438
|
}
|
|
433
439
|
if (val.startsWith('{') || val.startsWith('[')) {
|
package/src/daemon.js
CHANGED
|
@@ -210,6 +210,7 @@ class Daemon {
|
|
|
210
210
|
restarts: info.restarts,
|
|
211
211
|
started_at: info.startedAt || null,
|
|
212
212
|
last_error: info.lastError || null,
|
|
213
|
+
error_reason: info.errorReason || null,
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
return result;
|
|
@@ -327,7 +328,17 @@ class Daemon {
|
|
|
327
328
|
* Read daemon PID, returning null if not running.
|
|
328
329
|
*/
|
|
329
330
|
static readDaemonPid(configDir) {
|
|
330
|
-
|
|
331
|
+
const pidFile = path.join(configDir, 'daemon.pid');
|
|
332
|
+
const statusFile = path.join(configDir, 'daemon.status.json');
|
|
333
|
+
const pid = Daemon._readPid(pidFile);
|
|
334
|
+
if (!pid) return null;
|
|
335
|
+
|
|
336
|
+
if (!Daemon._isAlive(pid)) {
|
|
337
|
+
Daemon._cleanupStaleDaemonFiles(pidFile, statusFile);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return pid;
|
|
331
342
|
}
|
|
332
343
|
|
|
333
344
|
// ---------------------------------------------------------------------------
|
|
@@ -353,6 +364,9 @@ class Daemon {
|
|
|
353
364
|
restarts: 0,
|
|
354
365
|
startedAt: null,
|
|
355
366
|
lastError: null,
|
|
367
|
+
// Structured failure reason (health-status REASON) paired with lastError,
|
|
368
|
+
// so the UI/TUI can classify "why" without parsing the message.
|
|
369
|
+
errorReason: null,
|
|
356
370
|
proc: null,
|
|
357
371
|
_backoff: 2,
|
|
358
372
|
};
|
|
@@ -462,9 +476,33 @@ class Daemon {
|
|
|
462
476
|
// Internal — adapter loop (workspace-connected agents)
|
|
463
477
|
// ---------------------------------------------------------------------------
|
|
464
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Apply a live status update reported by a running adapter (join/heartbeat
|
|
481
|
+
* health). A genuine failure reason → state 'error' + redacted last_error so
|
|
482
|
+
* the Agents list / TUI show the real cause; a null reason → recovered, clear
|
|
483
|
+
* the error. Ignored once the agent is stopping (a user stop must win).
|
|
484
|
+
*/
|
|
485
|
+
_applyAdapterStatus(name, info, update) {
|
|
486
|
+
if (!update || this._shuttingDown || this._stoppedAgents.has(name)) return;
|
|
487
|
+
const { isErrorReason, redactDiagnostic } = require('./adapters/health-status');
|
|
488
|
+
const reason = update.reason || null;
|
|
489
|
+
if (reason && isErrorReason(reason)) {
|
|
490
|
+
info.state = 'error';
|
|
491
|
+
info.errorReason = reason;
|
|
492
|
+
info.lastError = redactDiagnostic(update.message || reason);
|
|
493
|
+
} else {
|
|
494
|
+
// Recovered / healthy again — clear any prior connectivity error.
|
|
495
|
+
if (info.state === 'error') info.state = 'running';
|
|
496
|
+
info.errorReason = null;
|
|
497
|
+
info.lastError = null;
|
|
498
|
+
}
|
|
499
|
+
this._writeStatus();
|
|
500
|
+
}
|
|
501
|
+
|
|
465
502
|
async _adapterLoop(name, agentCfg, info, network) {
|
|
466
503
|
const { createAdapter } = require('./adapters');
|
|
467
504
|
const { skillsToDisabledModules } = require('./skill-catalog');
|
|
505
|
+
const { redactDiagnostic } = require('./adapters/health-status');
|
|
468
506
|
const agentType = agentCfg.type || 'openclaw';
|
|
469
507
|
const endpoint = network.endpoint || 'https://workspace-endpoint.openagents.org';
|
|
470
508
|
|
|
@@ -491,20 +529,46 @@ class Daemon {
|
|
|
491
529
|
// .claude/skills there failed with EPERM. Root it under ~/.openagents.
|
|
492
530
|
workingDir: agentCfg.path || defaultAgentWorkdir(name),
|
|
493
531
|
toolMode: agentCfg.tool_mode || 'skills',
|
|
532
|
+
// Live runtime/connectivity status → daemon.status.json (Agents list/TUI).
|
|
533
|
+
onStatus: (update) => this._applyAdapterStatus(name, info, update),
|
|
494
534
|
});
|
|
495
535
|
} catch (e) {
|
|
496
|
-
|
|
536
|
+
// A construction failure (e.g. unknown agent type in this core) is a hard
|
|
537
|
+
// error with a real cause — surface it (redacted), do not pretend stopped.
|
|
497
538
|
info.state = 'error';
|
|
498
|
-
info.
|
|
539
|
+
info.errorReason = 'adapter_crashed';
|
|
540
|
+
info.lastError = redactDiagnostic(e.message || String(e));
|
|
541
|
+
this._log(`${name} failed to create ${agentType} adapter: ${info.lastError}`);
|
|
499
542
|
this._writeStatus();
|
|
500
543
|
return;
|
|
501
544
|
}
|
|
502
545
|
|
|
546
|
+
// Preflight: when the agent's runtime binary is genuinely missing, surface a
|
|
547
|
+
// precise reason ('runtime_missing') and DO NOT join the workspace — there is
|
|
548
|
+
// no point spinning a join/poll loop that can never run the CLI. Adapters
|
|
549
|
+
// that don't override preflight() always pass.
|
|
550
|
+
try {
|
|
551
|
+
const pf =
|
|
552
|
+
typeof adapter.preflight === 'function' ? adapter.preflight() : null;
|
|
553
|
+
if (pf && pf.ok === false) {
|
|
554
|
+
info.state = 'error';
|
|
555
|
+
info.errorReason = pf.reason || 'runtime_missing';
|
|
556
|
+
info.lastError = redactDiagnostic(pf.message || 'runtime not available');
|
|
557
|
+
this._writeStatus();
|
|
558
|
+
this._log(`${name} preflight failed (${info.errorReason}): ${info.lastError}`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
} catch (e) {
|
|
562
|
+
this._log(`${name} preflight error (ignored, continuing): ${e.message}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
503
565
|
// Store adapter reference for stop and duplicate detection
|
|
504
566
|
this._adapters[name] = adapter;
|
|
505
567
|
|
|
506
568
|
info.state = 'running';
|
|
507
569
|
info.startedAt = new Date().toISOString();
|
|
570
|
+
info.lastError = null;
|
|
571
|
+
info.errorReason = null;
|
|
508
572
|
this._writeStatus();
|
|
509
573
|
this._log(`${name} adapter online → ${network.slug} (type: ${agentType})`);
|
|
510
574
|
|
|
@@ -521,14 +585,41 @@ class Daemon {
|
|
|
521
585
|
await adapter.run();
|
|
522
586
|
clearInterval(checkStop);
|
|
523
587
|
} catch (e) {
|
|
524
|
-
info.
|
|
588
|
+
info.errorReason = info.errorReason || 'adapter_crashed';
|
|
589
|
+
info.lastError = redactDiagnostic((e.message || String(e)).slice(0, 200));
|
|
525
590
|
this._log(`${name} adapter error: ${info.lastError}`);
|
|
526
591
|
}
|
|
527
592
|
|
|
528
593
|
delete this._adapters[name];
|
|
529
|
-
|
|
594
|
+
|
|
595
|
+
// Final state: a clean stop (user stop / daemon shutdown) is NEVER an error.
|
|
596
|
+
// A terminal failure recorded by the adapter (join/heartbeat/session-revoked)
|
|
597
|
+
// or a crash surfaces as 'error' with its classified, redacted reason.
|
|
598
|
+
const userStopped =
|
|
599
|
+
this._shuttingDown ||
|
|
600
|
+
this._stoppedAgents.has(name) ||
|
|
601
|
+
(typeof adapter.wasStopRequested === 'function' && adapter.wasStopRequested());
|
|
602
|
+
if (userStopped) {
|
|
603
|
+
info.state = 'stopped';
|
|
604
|
+
info.lastError = null;
|
|
605
|
+
info.errorReason = null;
|
|
606
|
+
} else {
|
|
607
|
+
const exit =
|
|
608
|
+
(typeof adapter.getExitInfo === 'function' && adapter.getExitInfo()) ||
|
|
609
|
+
null;
|
|
610
|
+
if (exit && exit.reason) {
|
|
611
|
+
info.state = 'error';
|
|
612
|
+
info.errorReason = exit.reason;
|
|
613
|
+
info.lastError = redactDiagnostic(exit.message || exit.reason);
|
|
614
|
+
} else if (info.errorReason) {
|
|
615
|
+
// A crash, or a live error report that never recovered — keep it visible.
|
|
616
|
+
info.state = 'error';
|
|
617
|
+
} else {
|
|
618
|
+
info.state = 'stopped';
|
|
619
|
+
}
|
|
620
|
+
}
|
|
530
621
|
this._writeStatus();
|
|
531
|
-
this._log(`${name} adapter stopped`);
|
|
622
|
+
this._log(`${name} adapter stopped (state: ${info.state})`);
|
|
532
623
|
}
|
|
533
624
|
|
|
534
625
|
// NOTE: Adapter-specific message handling (openclaw, claude, codex)
|
|
@@ -873,6 +964,17 @@ class Daemon {
|
|
|
873
964
|
}
|
|
874
965
|
}
|
|
875
966
|
|
|
967
|
+
static _cleanupStaleDaemonFiles(pidFile, statusFile) {
|
|
968
|
+
Daemon._unlinkIfExists(pidFile);
|
|
969
|
+
Daemon._unlinkIfExists(statusFile);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
static _unlinkIfExists(filePath) {
|
|
973
|
+
if (fs.existsSync(filePath)) {
|
|
974
|
+
fs.unlinkSync(filePath);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
876
978
|
static _isAlive(pid) {
|
|
877
979
|
try {
|
|
878
980
|
process.kill(pid, 0);
|