@portel/photon 1.19.0 → 1.20.1
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/auto-ui/beam/routes/api-browse.d.ts.map +1 -1
- package/dist/auto-ui/beam/routes/api-browse.js +16 -4
- package/dist/auto-ui/beam/routes/api-browse.js.map +1 -1
- package/dist/auto-ui/beam/routes/api-config.d.ts.map +1 -1
- package/dist/auto-ui/beam/routes/api-config.js +165 -24
- package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
- package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -1
- package/dist/auto-ui/beam/routes/api-marketplace.js +14 -1
- package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -1
- package/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +187 -77
- package/dist/auto-ui/beam.js.map +1 -1
- package/dist/auto-ui/bridge/index.d.ts.map +1 -1
- package/dist/auto-ui/bridge/index.js +17 -0
- package/dist/auto-ui/bridge/index.js.map +1 -1
- package/dist/auto-ui/bridge/renderers.d.ts.map +1 -1
- package/dist/auto-ui/bridge/renderers.js +12 -4
- package/dist/auto-ui/bridge/renderers.js.map +1 -1
- package/dist/auto-ui/streamable-http-transport.d.ts +1 -0
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
- package/dist/auto-ui/streamable-http-transport.js +179 -44
- package/dist/auto-ui/streamable-http-transport.js.map +1 -1
- package/dist/auto-ui/types.d.ts +12 -0
- package/dist/auto-ui/types.d.ts.map +1 -1
- package/dist/auto-ui/types.js.map +1 -1
- package/dist/beam-form.bundle.js +63 -185
- package/dist/beam-form.bundle.js.map +4 -4
- package/dist/beam.bundle.js +2115 -761
- package/dist/beam.bundle.js.map +4 -4
- package/dist/capability-negotiator.d.ts +67 -0
- package/dist/capability-negotiator.d.ts.map +1 -0
- package/dist/capability-negotiator.js +104 -0
- package/dist/capability-negotiator.js.map +1 -0
- package/dist/channel-manager.d.ts +122 -0
- package/dist/channel-manager.d.ts.map +1 -0
- package/dist/channel-manager.js +266 -0
- package/dist/channel-manager.js.map +1 -0
- package/dist/cli/commands/beam.d.ts.map +1 -1
- package/dist/cli/commands/beam.js +47 -30
- package/dist/cli/commands/beam.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +27 -2
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/daemon.js +12 -6
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +18 -6
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/package.d.ts.map +1 -1
- package/dist/cli/commands/package.js +25 -7
- package/dist/cli/commands/package.js.map +1 -1
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +14 -2
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli-alias.d.ts.map +1 -1
- package/dist/cli-alias.js +2 -3
- package/dist/cli-alias.js.map +1 -1
- package/dist/context-store.d.ts +4 -4
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +18 -15
- package/dist/context-store.js.map +1 -1
- package/dist/context.d.ts +25 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +69 -4
- package/dist/context.js.map +1 -1
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +16 -1
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/manager.d.ts +2 -0
- package/dist/daemon/manager.d.ts.map +1 -1
- package/dist/daemon/manager.js +40 -8
- package/dist/daemon/manager.js.map +1 -1
- package/dist/daemon/server.js +89 -64
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/worker-host.js +7 -0
- package/dist/daemon/worker-host.js.map +1 -1
- package/dist/daemon/worker-manager.d.ts.map +1 -1
- package/dist/daemon/worker-manager.js +79 -17
- package/dist/daemon/worker-manager.js.map +1 -1
- package/dist/daemon/worker-protocol.d.ts +3 -0
- package/dist/daemon/worker-protocol.d.ts.map +1 -1
- package/dist/deploy/cloudflare.d.ts.map +1 -1
- package/dist/deploy/cloudflare.js +2 -4
- package/dist/deploy/cloudflare.js.map +1 -1
- package/dist/loader.d.ts +11 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +129 -13
- package/dist/loader.js.map +1 -1
- package/dist/marketplace-manager.d.ts +7 -1
- package/dist/marketplace-manager.d.ts.map +1 -1
- package/dist/marketplace-manager.js +165 -61
- package/dist/marketplace-manager.js.map +1 -1
- package/dist/namespace-migration.d.ts +1 -0
- package/dist/namespace-migration.d.ts.map +1 -1
- package/dist/namespace-migration.js +86 -0
- package/dist/namespace-migration.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +40 -21
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +59 -15
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/resource-server.d.ts +105 -0
- package/dist/resource-server.d.ts.map +1 -0
- package/dist/resource-server.js +723 -0
- package/dist/resource-server.js.map +1 -0
- package/dist/serv/auth/jwt.d.ts +2 -0
- package/dist/serv/auth/jwt.d.ts.map +1 -1
- package/dist/serv/auth/jwt.js +11 -5
- package/dist/serv/auth/jwt.js.map +1 -1
- package/dist/serv/vault/token-vault.d.ts +2 -0
- package/dist/serv/vault/token-vault.d.ts.map +1 -1
- package/dist/serv/vault/token-vault.js +6 -0
- package/dist/serv/vault/token-vault.js.map +1 -1
- package/dist/server.d.ts +20 -149
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +246 -1233
- package/dist/server.js.map +1 -1
- package/dist/shared/audit.d.ts.map +1 -1
- package/dist/shared/audit.js +7 -0
- package/dist/shared/audit.js.map +1 -1
- package/dist/shared/security.d.ts +10 -0
- package/dist/shared/security.d.ts.map +1 -1
- package/dist/shared/security.js +27 -0
- package/dist/shared/security.js.map +1 -1
- package/dist/shared-utils.d.ts +4 -0
- package/dist/shared-utils.d.ts.map +1 -1
- package/dist/shared-utils.js +22 -0
- package/dist/shared-utils.js.map +1 -1
- package/dist/task-executor.d.ts +69 -0
- package/dist/task-executor.d.ts.map +1 -0
- package/dist/task-executor.js +182 -0
- package/dist/task-executor.js.map +1 -0
- package/dist/template-manager.d.ts.map +1 -1
- package/dist/template-manager.js +56 -234
- package/dist/template-manager.js.map +1 -1
- package/dist/types/photon-instance.d.ts +50 -0
- package/dist/types/photon-instance.d.ts.map +1 -0
- package/dist/types/photon-instance.js +9 -0
- package/dist/types/photon-instance.js.map +1 -0
- package/dist/types/server-types.d.ts +61 -0
- package/dist/types/server-types.d.ts.map +1 -0
- package/dist/types/server-types.js +8 -0
- package/dist/types/server-types.js.map +1 -0
- package/package.json +3 -3
package/dist/daemon/server.js
CHANGED
|
@@ -267,7 +267,7 @@ function cleanupStaleMaps() {
|
|
|
267
267
|
}
|
|
268
268
|
// Remove empty channel subscription sets and prune destroyed sockets
|
|
269
269
|
for (const [channel, subs] of channelSubscriptions.entries()) {
|
|
270
|
-
for (const socket of subs) {
|
|
270
|
+
for (const socket of [...subs]) {
|
|
271
271
|
if (socket.destroyed)
|
|
272
272
|
subs.delete(socket);
|
|
273
273
|
}
|
|
@@ -288,8 +288,10 @@ function cleanupStaleMaps() {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
|
-
setInterval(cleanupExpiredLocks, 10000);
|
|
292
|
-
setInterval(cleanupStaleMaps, 60000);
|
|
291
|
+
const lockCleanupInterval = setInterval(cleanupExpiredLocks, 10000);
|
|
292
|
+
const staleMapCleanupInterval = setInterval(cleanupStaleMaps, 60000);
|
|
293
|
+
lockCleanupInterval.unref();
|
|
294
|
+
staleMapCleanupInterval.unref();
|
|
293
295
|
// ════════════════════════════════════════════════════════════════════════════════
|
|
294
296
|
// SCHEDULED JOBS
|
|
295
297
|
// ════════════════════════════════════════════════════════════════════════════════
|
|
@@ -711,7 +713,7 @@ function publishToChannel(channel, message, excludeSocket) {
|
|
|
711
713
|
// Send to exact channel subscribers
|
|
712
714
|
const exactSubscribers = channelSubscriptions.get(channel);
|
|
713
715
|
if (exactSubscribers) {
|
|
714
|
-
for (const socket of exactSubscribers) {
|
|
716
|
+
for (const socket of [...exactSubscribers]) {
|
|
715
717
|
if (socket !== excludeSocket && !socket.destroyed && !sentSockets.has(socket)) {
|
|
716
718
|
try {
|
|
717
719
|
socket.write(payload);
|
|
@@ -730,7 +732,7 @@ function publishToChannel(channel, message, excludeSocket) {
|
|
|
730
732
|
const wildcardChannel = `${channelPrefix}:*`;
|
|
731
733
|
const wildcardSubscribers = channelSubscriptions.get(wildcardChannel);
|
|
732
734
|
if (wildcardSubscribers) {
|
|
733
|
-
for (const socket of wildcardSubscribers) {
|
|
735
|
+
for (const socket of [...wildcardSubscribers]) {
|
|
734
736
|
if (socket !== excludeSocket && !socket.destroyed && !sentSockets.has(socket)) {
|
|
735
737
|
try {
|
|
736
738
|
socket.write(payload);
|
|
@@ -803,15 +805,8 @@ async function getOrCreateSessionManager(photonName, photonPath, workingDir) {
|
|
|
803
805
|
// If @worker tagged, spawn in a worker thread instead of in-process
|
|
804
806
|
if (shouldRunInWorker(pathToUse) && !workerManager.has(key)) {
|
|
805
807
|
try {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
photonPaths.set(key, pathToUse);
|
|
809
|
-
if (!photonPaths.has(photonName))
|
|
810
|
-
photonPaths.set(photonName, pathToUse);
|
|
811
|
-
if (workingDir)
|
|
812
|
-
workingDirs.set(key, workingDir);
|
|
813
|
-
watchPhotonFile(photonName, pathToUse);
|
|
814
|
-
// Wire dep resolver to go through main thread's getOrCreateSessionManager
|
|
808
|
+
// Wire dep resolver BEFORE spawn so the worker can resolve @photon deps
|
|
809
|
+
// during initialization (loadFile triggers dep resolution before 'ready').
|
|
815
810
|
if (!workerManager.depResolver) {
|
|
816
811
|
workerManager.depResolver = async (depName, depPath, _consumerPhoton) => {
|
|
817
812
|
const depManager = await getOrCreateSessionManager(depName, depPath, workingDir);
|
|
@@ -830,6 +825,14 @@ async function getOrCreateSessionManager(photonName, photonPath, workingDir) {
|
|
|
830
825
|
return depManager.loader.executeTool(loaded, method, args);
|
|
831
826
|
};
|
|
832
827
|
}
|
|
828
|
+
logger.info('Spawning worker thread for @worker photon', { photonName, key });
|
|
829
|
+
const info = await workerManager.spawn(key, photonName, pathToUse, workingDir);
|
|
830
|
+
photonPaths.set(key, pathToUse);
|
|
831
|
+
if (!photonPaths.has(photonName))
|
|
832
|
+
photonPaths.set(photonName, pathToUse);
|
|
833
|
+
if (workingDir)
|
|
834
|
+
workingDirs.set(key, workingDir);
|
|
835
|
+
watchPhotonFile(photonName, pathToUse);
|
|
833
836
|
// Return null — caller should check workerManager.has(key) for routing
|
|
834
837
|
// We store a marker so callers know this is a worker photon
|
|
835
838
|
logger.info('Worker photon ready', { photonName, tools: info.tools.length });
|
|
@@ -938,39 +941,6 @@ async function getOrCreateSessionManager(photonName, photonPath, workingDir) {
|
|
|
938
941
|
// Auto-register scheduled jobs and webhook routes from tool metadata
|
|
939
942
|
// Do this lazily on first session creation
|
|
940
943
|
void autoRegisterFromMetadata(key, manager);
|
|
941
|
-
// Auto-symlink: if this photon was resolved from a @photon dependency path
|
|
942
|
-
// (e.g. ./chat.photon.ts relative to consumer), ensure it's accessible by bare
|
|
943
|
-
// name from ~/.photon/local/ so CLI can reach it without manual symlinking.
|
|
944
|
-
try {
|
|
945
|
-
const localDir = path.join(os.homedir(), '.photon', 'local');
|
|
946
|
-
const symlinkName = `${photonName}.photon.ts`;
|
|
947
|
-
const symlinkPath = path.join(localDir, symlinkName);
|
|
948
|
-
const resolvedSource = fs.realpathSync(pathToUse);
|
|
949
|
-
if (!fs.existsSync(symlinkPath)) {
|
|
950
|
-
fs.mkdirSync(localDir, { recursive: true });
|
|
951
|
-
fs.symlinkSync(resolvedSource, symlinkPath);
|
|
952
|
-
logger.info('Auto-created symlink for CLI access', {
|
|
953
|
-
photonName,
|
|
954
|
-
symlink: symlinkPath,
|
|
955
|
-
target: resolvedSource,
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
// Also symlink the UI directory if it exists alongside the photon
|
|
959
|
-
const photonDir = path.dirname(resolvedSource);
|
|
960
|
-
const uiDir = path.join(photonDir, photonName);
|
|
961
|
-
const symlinkUiDir = path.join(localDir, photonName);
|
|
962
|
-
if (fs.existsSync(uiDir) && !fs.existsSync(symlinkUiDir)) {
|
|
963
|
-
fs.symlinkSync(uiDir, symlinkUiDir);
|
|
964
|
-
logger.info('Auto-created UI directory symlink', {
|
|
965
|
-
photonName,
|
|
966
|
-
symlink: symlinkUiDir,
|
|
967
|
-
target: uiDir,
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
catch {
|
|
972
|
-
// Non-critical — CLI access via full path still works
|
|
973
|
-
}
|
|
974
944
|
return manager;
|
|
975
945
|
}
|
|
976
946
|
catch (error) {
|
|
@@ -1433,13 +1403,14 @@ async function handleRequest(request, socket) {
|
|
|
1433
1403
|
suggestion: 'Include photonName in the request payload',
|
|
1434
1404
|
};
|
|
1435
1405
|
}
|
|
1406
|
+
const existing = scheduledJobs.get(request.jobId);
|
|
1436
1407
|
const job = {
|
|
1437
1408
|
id: request.jobId,
|
|
1438
1409
|
method: request.method,
|
|
1439
1410
|
args: request.args,
|
|
1440
1411
|
cron: request.cron,
|
|
1441
|
-
runCount: 0,
|
|
1442
|
-
createdAt: Date.now(),
|
|
1412
|
+
runCount: existing?.runCount ?? 0,
|
|
1413
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
1443
1414
|
createdBy: request.sessionId,
|
|
1444
1415
|
photonName,
|
|
1445
1416
|
workingDir: request.workingDir,
|
|
@@ -1488,7 +1459,8 @@ async function handleRequest(request, socket) {
|
|
|
1488
1459
|
// not forwarded to worker threads (workers don't know about _use etc.)
|
|
1489
1460
|
const runtimeTools = ['_use', '_instances', '_undo', '_redo'];
|
|
1490
1461
|
const cmdKey = compositeKey(photonName, request.workingDir);
|
|
1491
|
-
|
|
1462
|
+
const readyWorker = workerManager.get(cmdKey);
|
|
1463
|
+
if (readyWorker?.ready && !runtimeTools.includes(request.method)) {
|
|
1492
1464
|
const startMs = Date.now();
|
|
1493
1465
|
const result = await workerManager.call(cmdKey, request.method, request.args || {}, request.sessionId || 'default', request.instanceName || '');
|
|
1494
1466
|
return {
|
|
@@ -1500,10 +1472,28 @@ async function handleRequest(request, socket) {
|
|
|
1500
1472
|
durationMs: result.durationMs ?? Date.now() - startMs,
|
|
1501
1473
|
};
|
|
1502
1474
|
}
|
|
1503
|
-
// Trigger worker spawn if needed (getOrCreateSessionManager returns null for workers)
|
|
1504
|
-
|
|
1475
|
+
// Trigger worker spawn if needed (getOrCreateSessionManager returns null for workers).
|
|
1476
|
+
// Bound the wait so a slow worker spawn (cascading deps) doesn't block the CLI indefinitely.
|
|
1477
|
+
let sessionManager = null;
|
|
1478
|
+
try {
|
|
1479
|
+
const initPromise = getOrCreateSessionManager(photonName, request.photonPath, request.workingDir);
|
|
1480
|
+
const INIT_TIMEOUT_MS = 60_000;
|
|
1481
|
+
sessionManager = await Promise.race([
|
|
1482
|
+
initPromise,
|
|
1483
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Photon ${photonName} initialization timed out (${INIT_TIMEOUT_MS / 1000}s)`)), INIT_TIMEOUT_MS)),
|
|
1484
|
+
]);
|
|
1485
|
+
}
|
|
1486
|
+
catch (initErr) {
|
|
1487
|
+
return {
|
|
1488
|
+
type: 'error',
|
|
1489
|
+
id: request.id,
|
|
1490
|
+
error: getErrorMessage(initErr),
|
|
1491
|
+
suggestion: `The photon may still be loading. Try again shortly, or check: cat ${getDefaultContext().logFile}`,
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1505
1494
|
// Re-check: might have spawned a worker
|
|
1506
|
-
|
|
1495
|
+
const readyWorkerAfterInit = workerManager.get(cmdKey);
|
|
1496
|
+
if (readyWorkerAfterInit?.ready && !runtimeTools.includes(request.method)) {
|
|
1507
1497
|
const startMs = Date.now();
|
|
1508
1498
|
const result = await workerManager.call(cmdKey, request.method, request.args || {}, request.sessionId || 'default', request.instanceName || '');
|
|
1509
1499
|
return {
|
|
@@ -2294,9 +2284,11 @@ function watchPhotonFile(photonName, photonPath) {
|
|
|
2294
2284
|
const existing = watchDebounce.get(watchPath);
|
|
2295
2285
|
if (existing)
|
|
2296
2286
|
clearTimeout(existing);
|
|
2297
|
-
|
|
2287
|
+
const timer = setTimeout(() => {
|
|
2298
2288
|
void (async () => {
|
|
2299
|
-
watchDebounce.
|
|
2289
|
+
const currentTimer = watchDebounce.get(watchPath);
|
|
2290
|
+
if (currentTimer === timer)
|
|
2291
|
+
watchDebounce.delete(watchPath);
|
|
2300
2292
|
// On macOS, editors like sed -i and some IDEs replace the file (new inode),
|
|
2301
2293
|
// which kills the watcher. Re-watch via original path (symlink) so we
|
|
2302
2294
|
// re-resolve to the new real path. Don't return — fall through to reload,
|
|
@@ -2345,7 +2337,8 @@ function watchPhotonFile(photonName, photonPath) {
|
|
|
2345
2337
|
});
|
|
2346
2338
|
}
|
|
2347
2339
|
})();
|
|
2348
|
-
}, 100)
|
|
2340
|
+
}, 100);
|
|
2341
|
+
watchDebounce.set(watchPath, timer);
|
|
2349
2342
|
});
|
|
2350
2343
|
if ('on' in watcher && typeof watcher.on === 'function') {
|
|
2351
2344
|
watcher.on('error', (err) => {
|
|
@@ -2451,9 +2444,11 @@ function watchWorkingDir(workingDir) {
|
|
|
2451
2444
|
const existing = watchDebounce.get(debounceKey);
|
|
2452
2445
|
if (existing)
|
|
2453
2446
|
clearTimeout(existing);
|
|
2454
|
-
|
|
2447
|
+
const timer = setTimeout(() => {
|
|
2455
2448
|
void (async () => {
|
|
2456
|
-
watchDebounce.
|
|
2449
|
+
const currentTimer = watchDebounce.get(debounceKey);
|
|
2450
|
+
if (currentTimer === timer)
|
|
2451
|
+
watchDebounce.delete(debounceKey);
|
|
2457
2452
|
if (fs.existsSync(workingDir)) {
|
|
2458
2453
|
// Still exists — record updated inode in case it was recreated
|
|
2459
2454
|
try {
|
|
@@ -2506,7 +2501,8 @@ function watchWorkingDir(workingDir) {
|
|
|
2506
2501
|
workingDirInodes.delete(workingDir);
|
|
2507
2502
|
}
|
|
2508
2503
|
})();
|
|
2509
|
-
}, 150)
|
|
2504
|
+
}, 150);
|
|
2505
|
+
watchDebounce.set(debounceKey, timer);
|
|
2510
2506
|
});
|
|
2511
2507
|
if ('on' in watcher && typeof watcher.on === 'function') {
|
|
2512
2508
|
watcher.on('error', (err) => {
|
|
@@ -2548,9 +2544,11 @@ function watchStateDir(workingDir) {
|
|
|
2548
2544
|
const existing = watchDebounce.get(debounceKey);
|
|
2549
2545
|
if (existing)
|
|
2550
2546
|
clearTimeout(existing);
|
|
2551
|
-
|
|
2547
|
+
const timer = setTimeout(() => {
|
|
2552
2548
|
void (async () => {
|
|
2553
|
-
watchDebounce.
|
|
2549
|
+
const currentTimer = watchDebounce.get(debounceKey);
|
|
2550
|
+
if (currentTimer === timer)
|
|
2551
|
+
watchDebounce.delete(debounceKey);
|
|
2554
2552
|
const photonStateDir = path.join(stateDir, filename);
|
|
2555
2553
|
if (fs.existsSync(photonStateDir))
|
|
2556
2554
|
return; // Still there — not a deletion
|
|
@@ -2565,7 +2563,8 @@ function watchStateDir(workingDir) {
|
|
|
2565
2563
|
await manager.clearInstances();
|
|
2566
2564
|
}
|
|
2567
2565
|
})();
|
|
2568
|
-
}, 150)
|
|
2566
|
+
}, 150);
|
|
2567
|
+
watchDebounce.set(debounceKey, timer);
|
|
2569
2568
|
});
|
|
2570
2569
|
if ('on' in watcher && typeof watcher.on === 'function') {
|
|
2571
2570
|
watcher.on('error', (err) => {
|
|
@@ -3014,6 +3013,8 @@ function shutdown() {
|
|
|
3014
3013
|
if (idleTimer) {
|
|
3015
3014
|
clearTimeout(idleTimer);
|
|
3016
3015
|
}
|
|
3016
|
+
clearInterval(lockCleanupInterval);
|
|
3017
|
+
clearInterval(staleMapCleanupInterval);
|
|
3017
3018
|
for (const timer of jobTimers.values()) {
|
|
3018
3019
|
clearTimeout(timer);
|
|
3019
3020
|
}
|
|
@@ -3049,12 +3050,36 @@ function shutdown() {
|
|
|
3049
3050
|
if (webhookServer) {
|
|
3050
3051
|
webhookServer.close();
|
|
3051
3052
|
}
|
|
3053
|
+
// Only delete the socket if we still own it.
|
|
3054
|
+
// After `daemon stop` + `daemon start`, a new daemon may have already created
|
|
3055
|
+
// a new socket at this path. Deleting it would orphan the new daemon's listener.
|
|
3052
3056
|
if (fs.existsSync(socketPath) && process.platform !== 'win32') {
|
|
3057
|
+
let weOwnSocket = true;
|
|
3053
3058
|
try {
|
|
3054
|
-
|
|
3059
|
+
const pidFile = getDefaultContext().pidFile;
|
|
3060
|
+
const pidContent = fs.readFileSync(pidFile, 'utf-8').trim();
|
|
3061
|
+
const filePid = parseInt(pidContent, 10);
|
|
3062
|
+
if (!isNaN(filePid) && filePid !== process.pid) {
|
|
3063
|
+
// PID file points to a different process — new daemon already started
|
|
3064
|
+
weOwnSocket = false;
|
|
3065
|
+
logger.info('Socket belongs to new daemon, skipping cleanup', {
|
|
3066
|
+
ourPid: process.pid,
|
|
3067
|
+
newPid: filePid,
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3055
3070
|
}
|
|
3056
3071
|
catch {
|
|
3057
|
-
//
|
|
3072
|
+
// PID file missing (deleted by stop) — another process may own the socket now.
|
|
3073
|
+
// If the socket still exists, it likely belongs to a new daemon. Don't delete.
|
|
3074
|
+
weOwnSocket = false;
|
|
3075
|
+
}
|
|
3076
|
+
if (weOwnSocket) {
|
|
3077
|
+
try {
|
|
3078
|
+
fs.unlinkSync(socketPath);
|
|
3079
|
+
}
|
|
3080
|
+
catch {
|
|
3081
|
+
// Ignore cleanup errors
|
|
3082
|
+
}
|
|
3058
3083
|
}
|
|
3059
3084
|
}
|
|
3060
3085
|
process.exit(0);
|