u-foo 2.3.11 → 2.3.12
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/src/agent/activityStateWriter.js +14 -3
- package/src/agent/internalRunner.js +13 -3
- package/src/agent/notifier.js +15 -4
- package/src/bus/index.js +2 -1
- package/src/bus/store.js +17 -5
- package/src/bus/subscriber.js +57 -1
- package/src/bus/utils.js +6 -0
- package/src/ufoo/agentRegistryDiagnostics.js +91 -0
- package/src/ufoo/agentsStore.js +38 -2
package/package.json
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
3
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Centralized helper for writing activity_state to all-agents.json.
|
|
@@ -12,8 +14,17 @@ function writeActivityState(agentsFilePath, subscriber, state, options = {}) {
|
|
|
12
14
|
const { since, force = false } = options;
|
|
13
15
|
try {
|
|
14
16
|
if (!agentsFilePath || !fs.existsSync(agentsFilePath)) return false;
|
|
15
|
-
const data =
|
|
16
|
-
if (!data
|
|
17
|
+
const data = readJSON(agentsFilePath, null);
|
|
18
|
+
if (!data) return false;
|
|
19
|
+
if (!data.agents || !data.agents[subscriber]) {
|
|
20
|
+
appendAgentRegistryDiagnostic(agentsFilePath, "activity_state_subscriber_missing", {
|
|
21
|
+
source: "agent.activityStateWriter.writeActivityState",
|
|
22
|
+
subscriber,
|
|
23
|
+
state,
|
|
24
|
+
known_ids: Object.keys(data.agents || {}).sort(),
|
|
25
|
+
});
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
17
28
|
|
|
18
29
|
const current = data.agents[subscriber].activity_state;
|
|
19
30
|
|
|
@@ -30,7 +41,7 @@ function writeActivityState(agentsFilePath, subscriber, state, options = {}) {
|
|
|
30
41
|
data.agents[subscriber].activity_since = since
|
|
31
42
|
? new Date(since).toISOString()
|
|
32
43
|
: new Date().toISOString();
|
|
33
|
-
|
|
44
|
+
writeJSON(agentsFilePath, data);
|
|
34
45
|
return true;
|
|
35
46
|
} catch {
|
|
36
47
|
return false;
|
|
@@ -3,6 +3,7 @@ const path = require("path");
|
|
|
3
3
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
5
|
const EventBus = require("../bus");
|
|
6
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
6
7
|
const { runCliAgent } = require("./cliRunner");
|
|
7
8
|
const { normalizeCliOutput } = require("./normalizeOutput");
|
|
8
9
|
const { createActivityStatePublisher } = require("./activityStatePublisher");
|
|
@@ -15,6 +16,7 @@ const { listToolsForCallerTier, CALLER_TIERS } = require("../tools");
|
|
|
15
16
|
const { redactToolCallPayload, redactSecrets } = require("../providerapi/redactor");
|
|
16
17
|
const { buildCachedMemoryPrefix } = require("../memory");
|
|
17
18
|
const { shouldForwardStreamToPublisher } = require("./publisherRouting");
|
|
19
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
18
20
|
|
|
19
21
|
function sleep(ms) {
|
|
20
22
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -493,14 +495,22 @@ function persistProviderSessionId(projectRoot, subscriber, providerSessionId) {
|
|
|
493
495
|
try {
|
|
494
496
|
const agentsFile = getUfooPaths(projectRoot).agentsFile;
|
|
495
497
|
const parsed = fs.existsSync(agentsFile)
|
|
496
|
-
?
|
|
498
|
+
? readJSON(agentsFile, null)
|
|
497
499
|
: {};
|
|
500
|
+
if (!parsed) return false;
|
|
498
501
|
if (!parsed.agents || typeof parsed.agents !== "object") return false;
|
|
499
|
-
if (!parsed.agents[subscriber] || typeof parsed.agents[subscriber] !== "object")
|
|
502
|
+
if (!parsed.agents[subscriber] || typeof parsed.agents[subscriber] !== "object") {
|
|
503
|
+
appendAgentRegistryDiagnostic(agentsFile, "provider_session_subscriber_missing", {
|
|
504
|
+
source: "agent.internalRunner.persistProviderSessionId",
|
|
505
|
+
subscriber,
|
|
506
|
+
known_ids: Object.keys(parsed.agents || {}).sort(),
|
|
507
|
+
});
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
500
510
|
if (parsed.agents[subscriber].provider_session_id === id) return false;
|
|
501
511
|
parsed.agents[subscriber].provider_session_id = id;
|
|
502
512
|
parsed.agents[subscriber].provider_session_updated_at = new Date().toISOString();
|
|
503
|
-
|
|
513
|
+
writeJSON(agentsFile, parsed);
|
|
504
514
|
return true;
|
|
505
515
|
} catch {
|
|
506
516
|
return false;
|
package/src/agent/notifier.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const EventBus = require("../bus");
|
|
4
|
+
const { readJSON, writeJSON } = require("../bus/utils");
|
|
4
5
|
const Injector = require("../bus/inject");
|
|
5
6
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
6
8
|
const { shakeTerminalByTty } = require("../bus/shake");
|
|
7
9
|
const { isITerm2 } = require("../terminal/detect");
|
|
8
10
|
const iterm2 = require("../terminal/iterm2");
|
|
@@ -62,7 +64,8 @@ class AgentNotifier {
|
|
|
62
64
|
getNickname() {
|
|
63
65
|
try {
|
|
64
66
|
if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return "";
|
|
65
|
-
const data =
|
|
67
|
+
const data = readJSON(this.agentsFile, null);
|
|
68
|
+
if (!data) return "";
|
|
66
69
|
const meta = data.agents && data.agents[this.subscriber];
|
|
67
70
|
return (meta && meta.nickname) ? String(meta.nickname) : "";
|
|
68
71
|
} catch {
|
|
@@ -109,11 +112,18 @@ class AgentNotifier {
|
|
|
109
112
|
updateHeartbeat() {
|
|
110
113
|
try {
|
|
111
114
|
if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return;
|
|
112
|
-
const data =
|
|
115
|
+
const data = readJSON(this.agentsFile, null);
|
|
116
|
+
if (!data) return;
|
|
113
117
|
if (data.agents && data.agents[this.subscriber]) {
|
|
114
118
|
data.agents[this.subscriber].last_seen = new Date().toISOString();
|
|
115
|
-
|
|
119
|
+
writeJSON(this.agentsFile, data);
|
|
120
|
+
return;
|
|
116
121
|
}
|
|
122
|
+
appendAgentRegistryDiagnostic(this.agentsFile, "heartbeat_subscriber_missing", {
|
|
123
|
+
source: "agent.notifier.updateHeartbeat",
|
|
124
|
+
subscriber: this.subscriber,
|
|
125
|
+
known_ids: Object.keys(data.agents || {}).sort(),
|
|
126
|
+
});
|
|
117
127
|
} catch {
|
|
118
128
|
// 心跳更新失败时静默忽略
|
|
119
129
|
}
|
|
@@ -132,7 +142,8 @@ class AgentNotifier {
|
|
|
132
142
|
getCurrentActivityState() {
|
|
133
143
|
try {
|
|
134
144
|
if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return "";
|
|
135
|
-
const data =
|
|
145
|
+
const data = readJSON(this.agentsFile, null);
|
|
146
|
+
if (!data) return "";
|
|
136
147
|
const meta = data.agents && data.agents[this.subscriber];
|
|
137
148
|
return meta && typeof meta.activity_state === "string"
|
|
138
149
|
? String(meta.activity_state).trim().toLowerCase()
|
package/src/bus/index.js
CHANGED
|
@@ -60,7 +60,8 @@ class EventBus {
|
|
|
60
60
|
this.queueManager = new QueueManager(this.busDir);
|
|
61
61
|
this.subscriberManager = new SubscriberManager(
|
|
62
62
|
this.busData,
|
|
63
|
-
this.queueManager
|
|
63
|
+
this.queueManager,
|
|
64
|
+
{ agentsFile: this.agentsFile }
|
|
64
65
|
);
|
|
65
66
|
this.messageManager = new MessageManager(
|
|
66
67
|
this.busDir,
|
package/src/bus/store.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require("path");
|
|
|
5
5
|
const { getTimestamp, ensureDir, safeNameToSubscriber, getTtyProcessInfo } = require("./utils");
|
|
6
6
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
7
|
const { loadAgentsData, saveAgentsData } = require("../ufoo/agentsStore");
|
|
8
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
8
9
|
|
|
9
10
|
function readQueueTty(queueDir) {
|
|
10
11
|
try {
|
|
@@ -25,7 +26,7 @@ function buildUsedNicknameSet(agents = {}) {
|
|
|
25
26
|
return set;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
function recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) {
|
|
29
|
+
function recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now, agentsFile) {
|
|
29
30
|
if (!subscriber || data.agents[subscriber]) return false;
|
|
30
31
|
|
|
31
32
|
if (subscriber === "ufoo-agent") {
|
|
@@ -45,6 +46,17 @@ function recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) {
|
|
|
45
46
|
};
|
|
46
47
|
return true;
|
|
47
48
|
}
|
|
49
|
+
appendAgentRegistryDiagnostic(
|
|
50
|
+
agentsFile,
|
|
51
|
+
"queue_entry_not_recovered",
|
|
52
|
+
{
|
|
53
|
+
source: "bus.store.recoverQueueEntry",
|
|
54
|
+
subscriber,
|
|
55
|
+
queue_dir: queueDir,
|
|
56
|
+
reason: "non_controller_queue_without_registry_entry",
|
|
57
|
+
used_nicknames: Array.from(usedNicknames || []).sort(),
|
|
58
|
+
}
|
|
59
|
+
);
|
|
48
60
|
return false;
|
|
49
61
|
}
|
|
50
62
|
|
|
@@ -112,20 +124,20 @@ class BusStore {
|
|
|
112
124
|
if (!stat.isDirectory()) continue;
|
|
113
125
|
|
|
114
126
|
const subscriber = safeNameToSubscriber(entry);
|
|
115
|
-
recovered = recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now) || recovered;
|
|
127
|
+
recovered = recoverQueueEntry(data, subscriber, queueDir, usedNicknames, now, this.agentsFile) || recovered;
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
recovered = reconcileReservedControllerAliases(data, now) || recovered;
|
|
119
131
|
|
|
120
132
|
if (recovered) {
|
|
121
|
-
saveAgentsData(this.agentsFile, data);
|
|
133
|
+
saveAgentsData(this.agentsFile, data, { source: "bus.store.load.recoverQueueEntry", trace: true });
|
|
122
134
|
}
|
|
123
135
|
return data;
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
save(busData) {
|
|
127
139
|
if (busData) {
|
|
128
|
-
saveAgentsData(this.agentsFile, busData);
|
|
140
|
+
saveAgentsData(this.agentsFile, busData, { source: "bus.store.save" });
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
|
|
@@ -144,7 +156,7 @@ class BusStore {
|
|
|
144
156
|
created_at: getTimestamp(),
|
|
145
157
|
agents: {},
|
|
146
158
|
};
|
|
147
|
-
saveAgentsData(this.agentsFile, busData);
|
|
159
|
+
saveAgentsData(this.agentsFile, busData, { source: "bus.store.init", trace: true });
|
|
148
160
|
}
|
|
149
161
|
}
|
|
150
162
|
|
package/src/bus/subscriber.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require("fs");
|
|
|
2
2
|
const { getTimestamp, isAgentPidAlive, isMetaActive, isValidTty, getTtyProcessInfo } = require("./utils");
|
|
3
3
|
const NicknameManager = require("./nickname");
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
5
6
|
|
|
6
7
|
function detectTerminalAppFromEnv() {
|
|
7
8
|
const termProgram = process.env.TERM_PROGRAM || "";
|
|
@@ -102,9 +103,14 @@ function hasProviderSession(meta) {
|
|
|
102
103
|
* 订阅者管理
|
|
103
104
|
*/
|
|
104
105
|
class SubscriberManager {
|
|
105
|
-
constructor(busData, queueManager) {
|
|
106
|
+
constructor(busData, queueManager, options = {}) {
|
|
106
107
|
this.busData = busData;
|
|
107
108
|
this.queueManager = queueManager;
|
|
109
|
+
this.agentsFile = options.agentsFile || "";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logRegistry(event, payload = {}) {
|
|
113
|
+
appendAgentRegistryDiagnostic(this.agentsFile, event, payload);
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
cleanupSubscriberArtifacts(subscriber) {
|
|
@@ -152,6 +158,15 @@ class SubscriberManager {
|
|
|
152
158
|
inheritedNickname = meta.nickname;
|
|
153
159
|
}
|
|
154
160
|
// Remove stale subscriber using same tty
|
|
161
|
+
this.logRegistry("cleanup_duplicate_tty", {
|
|
162
|
+
source: "bus.subscriber.cleanupDuplicateTty",
|
|
163
|
+
subscriber: id,
|
|
164
|
+
replacement: currentSubscriber,
|
|
165
|
+
tty: ttyPath,
|
|
166
|
+
same_agent_type: sameAgentType,
|
|
167
|
+
status: meta?.status || "",
|
|
168
|
+
nickname: meta?.nickname || "",
|
|
169
|
+
});
|
|
155
170
|
delete this.busData.agents[id];
|
|
156
171
|
try {
|
|
157
172
|
const queueDir = this.queueManager.getQueueDir(id);
|
|
@@ -420,6 +435,16 @@ class SubscriberManager {
|
|
|
420
435
|
const recoverable = hasProviderSession(meta);
|
|
421
436
|
if (meta.status === "inactive") {
|
|
422
437
|
if (!recoverable) {
|
|
438
|
+
this.logRegistry("cleanup_inactive_delete", {
|
|
439
|
+
source: "bus.subscriber.cleanupInactive",
|
|
440
|
+
subscriber: id,
|
|
441
|
+
reason: "internal_already_inactive_without_provider_session",
|
|
442
|
+
status: meta.status || "",
|
|
443
|
+
launch_mode: meta.launch_mode || "",
|
|
444
|
+
pid: meta.pid || 0,
|
|
445
|
+
tty: meta.tty || "",
|
|
446
|
+
last_seen: meta.last_seen || "",
|
|
447
|
+
});
|
|
423
448
|
delete this.busData.agents[id];
|
|
424
449
|
this.cleanupSubscriberArtifacts(id);
|
|
425
450
|
}
|
|
@@ -427,11 +452,31 @@ class SubscriberManager {
|
|
|
427
452
|
}
|
|
428
453
|
if (!isMetaActive(meta)) {
|
|
429
454
|
if (recoverable) {
|
|
455
|
+
this.logRegistry("cleanup_inactive_mark", {
|
|
456
|
+
source: "bus.subscriber.cleanupInactive",
|
|
457
|
+
subscriber: id,
|
|
458
|
+
reason: "internal_inactive_but_recoverable_provider_session",
|
|
459
|
+
status: meta.status || "",
|
|
460
|
+
launch_mode: meta.launch_mode || "",
|
|
461
|
+
pid: meta.pid || 0,
|
|
462
|
+
tty: meta.tty || "",
|
|
463
|
+
last_seen: meta.last_seen || "",
|
|
464
|
+
});
|
|
430
465
|
meta.status = "inactive";
|
|
431
466
|
meta.activity_state = "";
|
|
432
467
|
meta.last_seen = getTimestamp();
|
|
433
468
|
this.cleanupSubscriberArtifacts(id);
|
|
434
469
|
} else {
|
|
470
|
+
this.logRegistry("cleanup_inactive_delete", {
|
|
471
|
+
source: "bus.subscriber.cleanupInactive",
|
|
472
|
+
subscriber: id,
|
|
473
|
+
reason: "internal_inactive_without_provider_session",
|
|
474
|
+
status: meta.status || "",
|
|
475
|
+
launch_mode: meta.launch_mode || "",
|
|
476
|
+
pid: meta.pid || 0,
|
|
477
|
+
tty: meta.tty || "",
|
|
478
|
+
last_seen: meta.last_seen || "",
|
|
479
|
+
});
|
|
435
480
|
delete this.busData.agents[id];
|
|
436
481
|
this.cleanupSubscriberArtifacts(id);
|
|
437
482
|
}
|
|
@@ -439,6 +484,17 @@ class SubscriberManager {
|
|
|
439
484
|
continue;
|
|
440
485
|
}
|
|
441
486
|
if (meta.status === "active" && !isMetaActive(meta)) {
|
|
487
|
+
this.logRegistry("cleanup_inactive_mark", {
|
|
488
|
+
source: "bus.subscriber.cleanupInactive",
|
|
489
|
+
subscriber: id,
|
|
490
|
+
reason: "active_meta_failed_liveness",
|
|
491
|
+
status: meta.status || "",
|
|
492
|
+
launch_mode: meta.launch_mode || "",
|
|
493
|
+
pid: meta.pid || 0,
|
|
494
|
+
tty: meta.tty || "",
|
|
495
|
+
tty_shell_pid: meta.tty_shell_pid || 0,
|
|
496
|
+
last_seen: meta.last_seen || "",
|
|
497
|
+
});
|
|
442
498
|
meta.status = "inactive";
|
|
443
499
|
meta.activity_state = "";
|
|
444
500
|
meta.last_seen = getTimestamp();
|
package/src/bus/utils.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require("fs");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
5
|
const { redactSecrets } = require("../providerapi/redactor");
|
|
6
|
+
const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 获取当前 UTC 时间戳(ISO 8601 格式)
|
|
@@ -200,6 +201,11 @@ function readJSON(filePath, defaultValue = null) {
|
|
|
200
201
|
const content = fs.readFileSync(filePath, "utf8");
|
|
201
202
|
return JSON.parse(content);
|
|
202
203
|
} catch (err) {
|
|
204
|
+
appendAgentRegistryDiagnostic(filePath, "read_json_failed", {
|
|
205
|
+
source: "bus.utils.readJSON",
|
|
206
|
+
error: err && err.message ? err.message : String(err || "unknown"),
|
|
207
|
+
default_returned: defaultValue === null ? "null" : typeof defaultValue,
|
|
208
|
+
});
|
|
203
209
|
return defaultValue;
|
|
204
210
|
}
|
|
205
211
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function isAgentsFile(filePath) {
|
|
5
|
+
return path.basename(filePath || "") === "all-agents.json"
|
|
6
|
+
&& path.basename(path.dirname(filePath || "")) === "agent";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getRegistryLogPath(agentsFilePath) {
|
|
10
|
+
const ufooRoot = path.dirname(path.dirname(agentsFilePath));
|
|
11
|
+
return path.join(ufooRoot, "run", "agent-registry-diagnostics.log");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function summarizeFile(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
const stat = fs.statSync(filePath);
|
|
17
|
+
return {
|
|
18
|
+
exists: true,
|
|
19
|
+
size: stat.size,
|
|
20
|
+
mtime: stat.mtime.toISOString(),
|
|
21
|
+
};
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return {
|
|
24
|
+
exists: false,
|
|
25
|
+
error: err && err.code ? err.code : String(err || "unknown"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function summarizeAgents(data) {
|
|
31
|
+
const agents = data && typeof data === "object" && data.agents && typeof data.agents === "object"
|
|
32
|
+
? data.agents
|
|
33
|
+
: {};
|
|
34
|
+
const ids = Object.keys(agents).sort();
|
|
35
|
+
const statuses = {};
|
|
36
|
+
const nicknames = {};
|
|
37
|
+
for (const id of ids) {
|
|
38
|
+
const meta = agents[id] || {};
|
|
39
|
+
const status = typeof meta.status === "string" && meta.status ? meta.status : "unknown";
|
|
40
|
+
statuses[status] = (statuses[status] || 0) + 1;
|
|
41
|
+
if (typeof meta.nickname === "string" && meta.nickname) {
|
|
42
|
+
nicknames[id] = meta.nickname;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
count: ids.length,
|
|
47
|
+
ids,
|
|
48
|
+
statuses,
|
|
49
|
+
nicknames,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function safePayload(payload = {}) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [key, value] of Object.entries(payload || {})) {
|
|
56
|
+
if (/token|secret|password|credential|auth/i.test(key)) {
|
|
57
|
+
out[key] = "[REDACTED]";
|
|
58
|
+
} else {
|
|
59
|
+
out[key] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function appendAgentRegistryDiagnostic(agentsFilePath, event, payload = {}) {
|
|
66
|
+
if (!agentsFilePath || !isAgentsFile(agentsFilePath)) return;
|
|
67
|
+
try {
|
|
68
|
+
const logPath = getRegistryLogPath(agentsFilePath);
|
|
69
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
70
|
+
const line = JSON.stringify({
|
|
71
|
+
ts: new Date().toISOString(),
|
|
72
|
+
pid: process.pid,
|
|
73
|
+
ppid: process.ppid,
|
|
74
|
+
event,
|
|
75
|
+
agents_file: agentsFilePath,
|
|
76
|
+
file: summarizeFile(agentsFilePath),
|
|
77
|
+
...safePayload(payload),
|
|
78
|
+
});
|
|
79
|
+
fs.appendFileSync(logPath, `${line}\n`, "utf8");
|
|
80
|
+
} catch {
|
|
81
|
+
// Diagnostics must never affect agent liveness paths.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
appendAgentRegistryDiagnostic,
|
|
87
|
+
summarizeAgents,
|
|
88
|
+
summarizeFile,
|
|
89
|
+
isAgentsFile,
|
|
90
|
+
getRegistryLogPath,
|
|
91
|
+
};
|
package/src/ufoo/agentsStore.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { getTimestamp, readJSON, writeJSON } = require("../bus/utils");
|
|
2
|
+
const { appendAgentRegistryDiagnostic, summarizeAgents } = require("./agentRegistryDiagnostics");
|
|
2
3
|
|
|
3
4
|
const AGENTS_SCHEMA_VERSION = 1;
|
|
4
5
|
|
|
@@ -89,9 +90,23 @@ function normalizeAgentsData(data) {
|
|
|
89
90
|
function loadAgentsData(filePath) {
|
|
90
91
|
const data = readJSON(filePath, null);
|
|
91
92
|
if (!data) {
|
|
93
|
+
appendAgentRegistryDiagnostic(filePath, "load_agents_empty", {
|
|
94
|
+
source: "ufoo.agentsStore.loadAgentsData",
|
|
95
|
+
reason: "missing_or_unreadable_registry",
|
|
96
|
+
});
|
|
92
97
|
return normalizeAgentsData({});
|
|
93
98
|
}
|
|
94
|
-
|
|
99
|
+
const normalized = normalizeAgentsData(data);
|
|
100
|
+
const beforeSummary = summarizeAgents(data);
|
|
101
|
+
const afterSummary = summarizeAgents(normalized);
|
|
102
|
+
if (JSON.stringify(beforeSummary) !== JSON.stringify(afterSummary)) {
|
|
103
|
+
appendAgentRegistryDiagnostic(filePath, "load_agents_normalized", {
|
|
104
|
+
source: "ufoo.agentsStore.loadAgentsData",
|
|
105
|
+
before: beforeSummary,
|
|
106
|
+
after: afterSummary,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
function parseTimestampMs(value) {
|
|
@@ -125,13 +140,27 @@ function mergeExternalActivityFields(targetMeta, diskMeta) {
|
|
|
125
140
|
}
|
|
126
141
|
}
|
|
127
142
|
|
|
128
|
-
function saveAgentsData(filePath, data) {
|
|
143
|
+
function saveAgentsData(filePath, data, options = {}) {
|
|
144
|
+
const source = typeof options.source === "string" && options.source
|
|
145
|
+
? options.source
|
|
146
|
+
: "ufoo.agentsStore.saveAgentsData";
|
|
129
147
|
const normalized = normalizeAgentsData(data);
|
|
130
148
|
|
|
131
149
|
// Merge externally-managed fields from disk to avoid daemon in-memory writes
|
|
132
150
|
// overwriting fresher runner/notifier state updates.
|
|
133
151
|
const disk = readJSON(filePath, null);
|
|
134
152
|
if (disk && disk.agents && normalized.agents) {
|
|
153
|
+
const droppedIds = Object.keys(disk.agents)
|
|
154
|
+
.filter((id) => !Object.prototype.hasOwnProperty.call(normalized.agents, id))
|
|
155
|
+
.sort();
|
|
156
|
+
if (droppedIds.length > 0) {
|
|
157
|
+
appendAgentRegistryDiagnostic(filePath, "save_agents_dropping_disk_entries", {
|
|
158
|
+
source,
|
|
159
|
+
dropped_ids: droppedIds,
|
|
160
|
+
disk: summarizeAgents(disk),
|
|
161
|
+
next: summarizeAgents(normalized),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
135
164
|
for (const [id, diskMeta] of Object.entries(disk.agents)) {
|
|
136
165
|
if (!diskMeta || typeof diskMeta !== "object") continue;
|
|
137
166
|
const targetMeta = normalized.agents[id];
|
|
@@ -140,6 +169,13 @@ function saveAgentsData(filePath, data) {
|
|
|
140
169
|
}
|
|
141
170
|
}
|
|
142
171
|
|
|
172
|
+
const nextSummary = summarizeAgents(normalized);
|
|
173
|
+
if (nextSummary.count === 0 || options.trace === true) {
|
|
174
|
+
appendAgentRegistryDiagnostic(filePath, "save_agents_data", {
|
|
175
|
+
source,
|
|
176
|
+
next: nextSummary,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
143
179
|
writeJSON(filePath, normalized);
|
|
144
180
|
}
|
|
145
181
|
|