fifony 0.1.47 → 0.1.48
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/README.md +78 -0
- package/app/dist/assets/CommandPalette-CZDG20HW.js +1 -0
- package/app/dist/assets/{KeyboardShortcutsHelp-CqEFfGcE.js → KeyboardShortcutsHelp-TYhQc4aA.js} +1 -1
- package/app/dist/assets/OnboardingWizard-CQ9YmVIT.js +1 -0
- package/app/dist/assets/agents.lazy-CgakDm_P.js +1 -0
- package/app/dist/assets/analytics.lazy-C0rw3sov.js +1 -0
- package/app/dist/assets/{createLucideIcon-luywpIq4.js → createLucideIcon-B3bah5lk.js} +1 -1
- package/app/dist/assets/hooks-CNPue7d7.js +1 -0
- package/app/dist/assets/index-B8XCmr0-.css +1 -0
- package/app/dist/assets/index-Dfn02uW3.js +47 -0
- package/app/dist/assets/index.lazy-JdqhBwPd.js +44 -0
- package/app/dist/assets/services-CHpVij2M.css +1 -0
- package/app/dist/assets/services.lazy-BShKUCOt.js +12 -0
- package/app/dist/assets/vendor-X6HTElZW.js +9 -0
- package/app/dist/assets/viz-Dsh_q2DK.js +4 -0
- package/app/dist/index.html +5 -4
- package/app/dist/service-worker.js +32 -1
- package/dist/agent/run-local.js +89 -76
- package/dist/{agent-DFSFG6DG.js → agent-DJ4SCNBZ.js} +22 -17
- package/dist/{analytics-broadcaster-O4AE3RUK.js → analytics-broadcaster-INNYWHDJ.js} +25 -20
- package/dist/approve-plan.command-WE2CO3H2.js +21 -0
- package/dist/{chunk-HOIOVUHI.js → chunk-5M7PBFMZ.js} +8 -6
- package/dist/chunk-7R7XFXJM.js +1247 -0
- package/dist/{chunk-2PRRKBG6.js → chunk-A4P2MYJF.js} +22 -9
- package/dist/chunk-AFOV3ZAF.js +722 -0
- package/dist/chunk-AFP36N23.js +134 -0
- package/dist/{chunk-AAZKYWOY.js → chunk-AFYKGVSP.js} +103 -8
- package/dist/chunk-APJOZXRP.js +737 -0
- package/dist/chunk-DLSPRIQL.js +241 -0
- package/dist/{chunk-5AMWD66T.js → chunk-EDIPHR5B.js} +6 -4
- package/dist/{chunk-K36BWMUV.js → chunk-JU3MF3MW.js} +2526 -736
- package/dist/{chunk-7TXZYZR5.js → chunk-N5HCNY4O.js} +7 -5
- package/dist/{chunk-JRLWLZOD.js → chunk-NKMZYPIS.js} +31 -23
- package/dist/{chunk-PI7Y77R3.js → chunk-OFIVTM2E.js} +17 -7
- package/dist/{chunk-QH6VCTET.js → chunk-RCSJFMQG.js} +909 -98
- package/dist/{chunk-AAVROEQC.js → chunk-UR7T7IA6.js} +253 -349
- package/dist/{chunk-QHISYRXJ.js → chunk-VOYLU3MI.js} +57 -3
- package/dist/{chunk-EBCSQFPR.js → chunk-W5IULOWV.js} +2 -3
- package/dist/chunk-X37RNTWU.js +193 -0
- package/dist/{chunk-PACI3T4I.js → chunk-XY2APMDE.js} +13 -5
- package/dist/chunk-Z6ZWNWWR.js +34 -0
- package/dist/cli.js +45 -17
- package/dist/constants-AAP7ZGCX.js +124 -0
- package/dist/create-issue.command-SX3AXXIC.js +29 -0
- package/dist/fsm-agent-JGV22WK4.js +59 -0
- package/dist/{fsm-issue-EHTSKMFN.js → fsm-issue-LHIJM5VB.js} +12 -8
- package/dist/{fsm-service-7O4AJG2R.js → fsm-service-GGDKUTWS.js} +13 -4
- package/dist/{helpers-ON2S7UEF.js → helpers-AENVYEZJ.js} +6 -2
- package/dist/{issue-log-broadcaster-FZGVEEIX.js → issue-log-broadcaster-QQWM7LOV.js} +29 -18
- package/dist/{issues-3YNNTB4U.js → issues-RXFKKSXB.js} +10 -7
- package/dist/{log-analyzer-EIX6R6PP.js → log-analyzer-4LNXQISY.js} +30 -20
- package/dist/{logger-IFLXTQPS.js → logger-4F6ATWNA.js} +2 -1
- package/dist/mcp/server.js +6 -2
- package/dist/merge-workspace.command-ZNGIZC4O.js +29 -0
- package/dist/{parallel-executor-DWESCNX3.js → parallel-executor-OL5CB33L.js} +78 -19
- package/dist/{pid-manager-UBWXVSMD.js → pid-manager-EDT4DHAU.js} +2 -1
- package/dist/queue-workers-NSKIIMQ2.js +43 -0
- package/dist/replan-issue.command-73PETERX.js +21 -0
- package/dist/retry-issue.command-DIDP4OCS.js +21 -0
- package/dist/reverse-proxy-server-QSS3H4UH.js +97 -0
- package/dist/scheduler-5YORYECF.js +37 -0
- package/dist/service-log-broadcaster-JIUP2L3D.js +21 -0
- package/dist/{settings-SOTIS6ZD.js → settings-ZNDXYL46.js} +34 -23
- package/dist/settings.resource-OKUHXICJ.js +35 -0
- package/dist/{store-S3NAYZ3S.js → store-P3ACO6YA.js} +22 -17
- package/dist/telemetry-KVUFHDQS.js +828 -0
- package/dist/template-variants-HEPLYKMP.js +24 -0
- package/dist/trace-bundle-IJOV7IWH.js +41 -0
- package/dist/{web-push-QCTLS7EJ.js → web-push-X2LLMQ4M.js} +2 -1
- package/dist/websocket-Q2TUCIC2.js +103 -0
- package/dist/{workspace-OS7GPMCN.js → workspace-TDX3NJCX.js} +10 -6
- package/package.json +12 -9
- package/app/dist/assets/CommandPalette-CL8p78lG.js +0 -1
- package/app/dist/assets/OnboardingWizard-BmI50ZUv.js +0 -1
- package/app/dist/assets/analytics.lazy-CXGjZabc.js +0 -1
- package/app/dist/assets/index-CEaccpYh.js +0 -96
- package/app/dist/assets/index-CzzWGzux.css +0 -1
- package/app/dist/assets/vendor-uqBx3VSC.js +0 -9
- package/dist/approve-plan.command-QGQZZXTQ.js +0 -17
- package/dist/chunk-N4KFNX2G.js +0 -370
- package/dist/chunk-VM5QAYP5.js +0 -404
- package/dist/create-issue.command-VAKYRECC.js +0 -24
- package/dist/merge-workspace.command-T2NIGR4M.js +0 -24
- package/dist/queue-workers-V57BYXAY.js +0 -38
- package/dist/replan-issue.command-2GQ3QXCR.js +0 -17
- package/dist/retry-issue.command-GJBUUYDJ.js +0 -17
- package/dist/scheduler-KYILMWLD.js +0 -32
- package/dist/settings.resource-JMD3JQOS.js +0 -30
- package/dist/websocket-T2Y3BY4B.js +0 -61
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
buildProxyEnvVars,
|
|
3
|
+
getMeshRuntimePortSnapshot
|
|
4
|
+
} from "./chunk-7R7XFXJM.js";
|
|
5
|
+
import {
|
|
6
|
+
isProcessAlive
|
|
7
|
+
} from "./chunk-3NE23NYW.js";
|
|
8
|
+
import {
|
|
9
|
+
init_helpers,
|
|
3
10
|
now
|
|
4
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DLSPRIQL.js";
|
|
12
|
+
import {
|
|
13
|
+
S3DB_SERVICES_RESOURCE,
|
|
14
|
+
init_constants
|
|
15
|
+
} from "./chunk-X37RNTWU.js";
|
|
5
16
|
import {
|
|
6
17
|
logger
|
|
7
18
|
} from "./chunk-PXTIWKLQ.js";
|
|
8
|
-
import {
|
|
9
|
-
isProcessAlive
|
|
10
|
-
} from "./chunk-3NE23NYW.js";
|
|
11
19
|
|
|
12
20
|
// src/persistence/plugins/fsm-service.ts
|
|
13
21
|
import {
|
|
@@ -16,12 +24,14 @@ import {
|
|
|
16
24
|
openSync,
|
|
17
25
|
readFileSync,
|
|
18
26
|
readSync,
|
|
27
|
+
renameSync,
|
|
19
28
|
rmSync,
|
|
20
29
|
statSync,
|
|
21
30
|
writeFileSync
|
|
22
31
|
} from "fs";
|
|
23
32
|
import { join, resolve } from "path";
|
|
24
|
-
import { spawn } from "child_process";
|
|
33
|
+
import { spawn, execSync } from "child_process";
|
|
34
|
+
init_helpers();
|
|
25
35
|
|
|
26
36
|
// src/domains/service-env.ts
|
|
27
37
|
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -57,285 +67,54 @@ function mergeServiceEnvironment(globalEnv, serviceEnv) {
|
|
|
57
67
|
function shellQuoteEnvValue(value) {
|
|
58
68
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
59
69
|
}
|
|
60
|
-
function buildServiceCommand(command, globalEnv, serviceEnv) {
|
|
70
|
+
function buildServiceCommand(command, globalEnv, serviceEnv, enforcedEnv) {
|
|
61
71
|
const baseCommand = command.trim();
|
|
62
72
|
if (!baseCommand) return "";
|
|
63
|
-
const env =
|
|
73
|
+
const env = {
|
|
74
|
+
...mergeServiceEnvironment(globalEnv, serviceEnv),
|
|
75
|
+
...enforcedEnv ?? {}
|
|
76
|
+
};
|
|
64
77
|
const assignments = Object.entries(env).map(([key, value]) => `${key}=${shellQuoteEnvValue(value)}`);
|
|
65
78
|
return assignments.length > 0 ? `${assignments.join(" ")} ${baseCommand}` : baseCommand;
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
// src/persistence/plugins/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
var TrafficRingBuffer = class {
|
|
76
|
-
constructor(capacity = 1e3) {
|
|
77
|
-
this.capacity = capacity;
|
|
78
|
-
this.buf = new Array(capacity);
|
|
79
|
-
}
|
|
80
|
-
buf;
|
|
81
|
-
head = 0;
|
|
82
|
-
count = 0;
|
|
83
|
-
push(entry) {
|
|
84
|
-
this.buf[this.head] = entry;
|
|
85
|
-
this.head = (this.head + 1) % this.capacity;
|
|
86
|
-
if (this.count < this.capacity) this.count++;
|
|
87
|
-
}
|
|
88
|
-
getAll() {
|
|
89
|
-
if (this.count === 0) return [];
|
|
90
|
-
if (this.count < this.capacity) return this.buf.slice(0, this.count);
|
|
91
|
-
return [...this.buf.slice(this.head), ...this.buf.slice(0, this.head)];
|
|
92
|
-
}
|
|
93
|
-
getRecent(n) {
|
|
94
|
-
const all = this.getAll();
|
|
95
|
-
return n >= all.length ? all : all.slice(-n);
|
|
96
|
-
}
|
|
97
|
-
clear() {
|
|
98
|
-
this.head = 0;
|
|
99
|
-
this.count = 0;
|
|
100
|
-
}
|
|
101
|
-
get size() {
|
|
102
|
-
return this.count;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
function edgeKey(source, target) {
|
|
106
|
-
return `${source}\0${target}`;
|
|
81
|
+
// src/persistence/plugins/fsm-service.ts
|
|
82
|
+
init_constants();
|
|
83
|
+
var STARTING_GRACE_MS = 3e3;
|
|
84
|
+
var STOPPING_KILL_MS = 5e3;
|
|
85
|
+
var SERVICE_WATCHER_INTERVAL_MS = 5e3;
|
|
86
|
+
function pidPath(fifonyDir, id) {
|
|
87
|
+
return join(fifonyDir, `service-${id}.pid`);
|
|
107
88
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const key = edgeKey(src, tgt);
|
|
118
|
-
this.totalRequests++;
|
|
119
|
-
let edge = this.edges.get(key);
|
|
120
|
-
if (!edge) {
|
|
121
|
-
edge = {
|
|
122
|
-
source: src,
|
|
123
|
-
target: tgt,
|
|
124
|
-
requestCount: 0,
|
|
125
|
-
errorCount: 0,
|
|
126
|
-
latency: { sum: 0, count: 0, values: [] },
|
|
127
|
-
lastSeenAt: entry.startedAt,
|
|
128
|
-
pathCounts: /* @__PURE__ */ new Map()
|
|
129
|
-
};
|
|
130
|
-
this.edges.set(key, edge);
|
|
131
|
-
}
|
|
132
|
-
edge.requestCount++;
|
|
133
|
-
if (entry.statusCode >= 400 || entry.error) edge.errorCount++;
|
|
134
|
-
edge.latency.sum += entry.durationMs;
|
|
135
|
-
edge.latency.count++;
|
|
136
|
-
if (edge.latency.values.length >= MAX_P95_SAMPLES) edge.latency.values.shift();
|
|
137
|
-
edge.latency.values.push(entry.durationMs);
|
|
138
|
-
edge.lastSeenAt = entry.startedAt;
|
|
139
|
-
edge.pathCounts.set(entry.path, (edge.pathCounts.get(entry.path) ?? 0) + 1);
|
|
140
|
-
}
|
|
141
|
-
getGraph(services) {
|
|
142
|
-
const nodes = services.map((s) => ({
|
|
143
|
-
id: s.id,
|
|
144
|
-
name: s.name,
|
|
145
|
-
state: s.state,
|
|
146
|
-
port: s.port
|
|
147
|
-
}));
|
|
148
|
-
const edges = [];
|
|
149
|
-
for (const e of this.edges.values()) {
|
|
150
|
-
const sorted = [...e.latency.values].sort((a, b) => a - b);
|
|
151
|
-
const pct = (p) => sorted.length > 0 ? sorted[Math.min(Math.floor(sorted.length * p), sorted.length - 1)] : 0;
|
|
152
|
-
const topPaths = [...e.pathCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, MAX_TOP_PATHS).map(([path, count]) => ({ path, count }));
|
|
153
|
-
edges.push({
|
|
154
|
-
source: e.source,
|
|
155
|
-
target: e.target,
|
|
156
|
-
requestCount: e.requestCount,
|
|
157
|
-
errorCount: e.errorCount,
|
|
158
|
-
avgLatencyMs: e.latency.count > 0 ? Math.round(e.latency.sum / e.latency.count) : 0,
|
|
159
|
-
p50LatencyMs: pct(0.5),
|
|
160
|
-
p90LatencyMs: pct(0.9),
|
|
161
|
-
p95LatencyMs: pct(0.95),
|
|
162
|
-
p99LatencyMs: pct(0.99),
|
|
163
|
-
lastSeenAt: e.lastSeenAt,
|
|
164
|
-
topPaths
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
nodes,
|
|
169
|
-
edges,
|
|
170
|
-
capturedSince: this.capturedSince,
|
|
171
|
-
totalRequests: this.totalRequests
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
reset() {
|
|
175
|
-
this.edges.clear();
|
|
176
|
-
this.totalRequests = 0;
|
|
177
|
-
this.capturedSince = (/* @__PURE__ */ new Date()).toISOString();
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
function extractServiceIdFromProxyAuth(headers) {
|
|
181
|
-
const auth = headers["proxy-authorization"];
|
|
182
|
-
if (!auth) return null;
|
|
183
|
-
const match = auth.match(/^Basic\s+(.+)$/i);
|
|
184
|
-
if (!match) return null;
|
|
89
|
+
function serviceLogPath(fifonyDir, id) {
|
|
90
|
+
return join(fifonyDir, `service-${id}.log`);
|
|
91
|
+
}
|
|
92
|
+
function serviceLogGenerationPath(fifonyDir, id, generation) {
|
|
93
|
+
const base = serviceLogPath(fifonyDir, id);
|
|
94
|
+
return generation === 0 ? base : `${base}.${generation}`;
|
|
95
|
+
}
|
|
96
|
+
function rotateServiceLogs(fifonyDir, id) {
|
|
97
|
+
const base = serviceLogPath(fifonyDir, id);
|
|
185
98
|
try {
|
|
186
|
-
|
|
187
|
-
const colon = decoded.indexOf(":");
|
|
188
|
-
return colon > 0 ? decoded.slice(0, colon) : decoded;
|
|
99
|
+
if (!existsSync(base) || statSync(base).size === 0) return;
|
|
189
100
|
} catch {
|
|
190
|
-
return
|
|
101
|
+
return;
|
|
191
102
|
}
|
|
192
|
-
}
|
|
193
|
-
function resolveTargetService(url, services) {
|
|
194
103
|
try {
|
|
195
|
-
|
|
196
|
-
const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
|
|
197
|
-
const match = services.find((s) => s.port === port);
|
|
198
|
-
return match?.id ?? null;
|
|
104
|
+
if (existsSync(`${base}.2`)) rmSync(`${base}.2`);
|
|
199
105
|
} catch {
|
|
200
|
-
return null;
|
|
201
106
|
}
|
|
202
|
-
}
|
|
203
|
-
function buildProxyEnvVars(proxyPort, serviceId, dashboardPort, extraNoPorts = []) {
|
|
204
|
-
const proxyUrl = `http://${encodeURIComponent(serviceId)}:fifony@localhost:${proxyPort}`;
|
|
205
|
-
const noProxyList = [`localhost:${dashboardPort}`, ...extraNoPorts.map((p) => `localhost:${p}`)].join(",");
|
|
206
|
-
return {
|
|
207
|
-
HTTP_PROXY: proxyUrl,
|
|
208
|
-
http_proxy: proxyUrl,
|
|
209
|
-
NO_PROXY: noProxyList,
|
|
210
|
-
no_proxy: noProxyList
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
var entrySeq = 0;
|
|
214
|
-
function buildTrafficEntry(method, url, requestSize, statusCode, responseSize, sourceServiceId, targetServiceId, startTime, error) {
|
|
215
|
-
let path;
|
|
216
107
|
try {
|
|
217
|
-
|
|
108
|
+
if (existsSync(`${base}.1`)) renameSync(`${base}.1`, `${base}.2`);
|
|
218
109
|
} catch {
|
|
219
|
-
path = url;
|
|
220
110
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
targetServiceId,
|
|
225
|
-
method,
|
|
226
|
-
url,
|
|
227
|
-
path,
|
|
228
|
-
statusCode,
|
|
229
|
-
requestSize,
|
|
230
|
-
responseSize,
|
|
231
|
-
startedAt: new Date(startTime).toISOString(),
|
|
232
|
-
durationMs: Date.now() - startTime,
|
|
233
|
-
error
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// src/persistence/plugins/traffic-proxy-server.ts
|
|
238
|
-
var server = null;
|
|
239
|
-
var proxy = null;
|
|
240
|
-
var buffer = null;
|
|
241
|
-
var graph = null;
|
|
242
|
-
var boundPort = null;
|
|
243
|
-
var onEntryCallback = null;
|
|
244
|
-
var servicesAccessor = null;
|
|
245
|
-
function setServicesAccessor(fn) {
|
|
246
|
-
servicesAccessor = fn;
|
|
247
|
-
}
|
|
248
|
-
async function startTrafficProxy(options = {}) {
|
|
249
|
-
if (server) {
|
|
250
|
-
logger.warn("Traffic proxy already running, skipping start");
|
|
251
|
-
return boundPort;
|
|
111
|
+
try {
|
|
112
|
+
renameSync(base, `${base}.1`);
|
|
113
|
+
} catch {
|
|
252
114
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
graph = new ServiceGraphAccumulator();
|
|
257
|
-
onEntryCallback = options.onEntry ?? null;
|
|
258
|
-
proxy = createHttpForwardProxy({
|
|
259
|
-
timeout: 3e4,
|
|
260
|
-
maxBodySize: 10 * 1024 * 1024
|
|
261
|
-
});
|
|
262
|
-
server = createServer((req, res) => {
|
|
263
|
-
const startTime = Date.now();
|
|
264
|
-
const method = req.method ?? "GET";
|
|
265
|
-
const url = req.url ?? "";
|
|
266
|
-
const sourceId = extractServiceIdFromProxyAuth(
|
|
267
|
-
req.headers
|
|
268
|
-
);
|
|
269
|
-
const services = servicesAccessor?.() ?? [];
|
|
270
|
-
const targetId = resolveTargetService(url, services);
|
|
271
|
-
const requestSize = Number(req.headers["content-length"] ?? 0);
|
|
272
|
-
res.on("finish", () => {
|
|
273
|
-
const entry = buildTrafficEntry(
|
|
274
|
-
method,
|
|
275
|
-
url,
|
|
276
|
-
requestSize,
|
|
277
|
-
res.statusCode,
|
|
278
|
-
Number(res.getHeader("content-length") ?? 0),
|
|
279
|
-
sourceId,
|
|
280
|
-
targetId,
|
|
281
|
-
startTime
|
|
282
|
-
);
|
|
283
|
-
buffer?.push(entry);
|
|
284
|
-
graph?.record(entry);
|
|
285
|
-
onEntryCallback?.(entry);
|
|
286
|
-
});
|
|
287
|
-
proxy.requestHandler(req, res);
|
|
288
|
-
});
|
|
289
|
-
return new Promise((resolve2, reject) => {
|
|
290
|
-
server.listen(port, "127.0.0.1", () => {
|
|
291
|
-
const addr = server.address();
|
|
292
|
-
boundPort = typeof addr === "object" && addr ? addr.port : port;
|
|
293
|
-
logger.info({ port: boundPort }, "Mesh traffic proxy started");
|
|
294
|
-
resolve2(boundPort);
|
|
295
|
-
});
|
|
296
|
-
server.on("error", (err) => {
|
|
297
|
-
logger.error({ err }, "Mesh traffic proxy failed to start");
|
|
298
|
-
reject(err);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
async function stopTrafficProxy() {
|
|
303
|
-
if (!server) return;
|
|
304
|
-
return new Promise((resolve2) => {
|
|
305
|
-
server.close(() => {
|
|
306
|
-
logger.info("Mesh traffic proxy stopped");
|
|
307
|
-
server = null;
|
|
308
|
-
proxy = null;
|
|
309
|
-
boundPort = null;
|
|
310
|
-
resolve2();
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
function getTrafficProxyPort() {
|
|
315
|
-
return boundPort;
|
|
316
|
-
}
|
|
317
|
-
function isTrafficProxyRunning() {
|
|
318
|
-
return server !== null;
|
|
319
|
-
}
|
|
320
|
-
function getTrafficBuffer() {
|
|
321
|
-
return buffer;
|
|
322
|
-
}
|
|
323
|
-
function getServiceGraph() {
|
|
324
|
-
return graph;
|
|
325
|
-
}
|
|
326
|
-
function getTrafficProxyStats() {
|
|
327
|
-
return proxy?.stats ?? null;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// src/persistence/plugins/fsm-service.ts
|
|
331
|
-
var STARTING_GRACE_MS = 3e3;
|
|
332
|
-
var STOPPING_KILL_MS = 5e3;
|
|
333
|
-
var SERVICE_WATCHER_INTERVAL_MS = 5e3;
|
|
334
|
-
function pidPath(fifonyDir, id) {
|
|
335
|
-
return join(fifonyDir, `service-${id}.pid`);
|
|
336
|
-
}
|
|
337
|
-
function serviceLogPath(fifonyDir, id) {
|
|
338
|
-
return join(fifonyDir, `service-${id}.log`);
|
|
115
|
+
errorCountCache.delete(base);
|
|
116
|
+
errorCountCache.delete(`${base}.1`);
|
|
117
|
+
errorCountCache.delete(`${base}.2`);
|
|
339
118
|
}
|
|
340
119
|
var ERROR_PATTERN = /\b(ERROR|Exception|FATAL|FAIL)\b/gi;
|
|
341
120
|
var errorCountCache = /* @__PURE__ */ new Map();
|
|
@@ -397,12 +176,16 @@ function spawnProcess(entry, targetRoot, fifonyDir, globalEnv) {
|
|
|
397
176
|
const cwd = entry.cwd ? resolve(targetRoot, entry.cwd) : targetRoot;
|
|
398
177
|
const log = serviceLogPath(fifonyDir, entry.id);
|
|
399
178
|
let mergedGlobalEnv = globalEnv;
|
|
400
|
-
const proxyPort =
|
|
179
|
+
const proxyPort = getMeshRuntimePortSnapshot();
|
|
401
180
|
if (proxyPort) {
|
|
402
181
|
const dashPort = Number(process.env.FIFONY_PORT ?? 4e3);
|
|
403
182
|
mergedGlobalEnv = { ...globalEnv ?? {}, ...buildProxyEnvVars(proxyPort, entry.id, dashPort) };
|
|
404
183
|
}
|
|
405
|
-
const
|
|
184
|
+
const enforcedEnv = entry.port ? {
|
|
185
|
+
PORT: String(entry.port)
|
|
186
|
+
} : {};
|
|
187
|
+
const command = buildServiceCommand(entry.command, mergedGlobalEnv, entry.env, enforcedEnv);
|
|
188
|
+
rotateServiceLogs(fifonyDir, entry.id);
|
|
406
189
|
try {
|
|
407
190
|
writeFileSync(log, "");
|
|
408
191
|
} catch {
|
|
@@ -424,6 +207,65 @@ function spawnProcess(entry, targetRoot, fifonyDir, globalEnv) {
|
|
|
424
207
|
}
|
|
425
208
|
return { pid: child.pid, command };
|
|
426
209
|
}
|
|
210
|
+
function killProcessTree(pid) {
|
|
211
|
+
try {
|
|
212
|
+
const children = execSync(`pgrep -P ${pid} 2>/dev/null || true`, { encoding: "utf8" }).trim();
|
|
213
|
+
if (children) {
|
|
214
|
+
for (const childStr of children.split("\n")) {
|
|
215
|
+
const childPid = parseInt(childStr.trim(), 10);
|
|
216
|
+
if (!isNaN(childPid) && childPid > 0) {
|
|
217
|
+
killProcessTree(childPid);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
process.kill(pid, "SIGKILL");
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function killProcessesOnPort(port) {
|
|
229
|
+
try {
|
|
230
|
+
const pids = execSync(`lsof -ti tcp:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
|
|
231
|
+
if (!pids) return;
|
|
232
|
+
for (const pidStr of pids.split("\n")) {
|
|
233
|
+
const pid = parseInt(pidStr.trim(), 10);
|
|
234
|
+
if (!isNaN(pid) && pid > 0) {
|
|
235
|
+
try {
|
|
236
|
+
process.kill(pid, "SIGKILL");
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
logger.debug({ port, pids }, "[ServiceFSM] Killed orphaned processes on port");
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function cleanupServiceProcesses(pid, port) {
|
|
246
|
+
if (pid && pid > 0) {
|
|
247
|
+
let allPorts = [];
|
|
248
|
+
try {
|
|
249
|
+
const lsofOut = execSync(`lsof -aPi tcp -sTCP:LISTEN -p ${pid} -Fn 2>/dev/null || true`, { encoding: "utf8" });
|
|
250
|
+
for (const line of lsofOut.split("\n")) {
|
|
251
|
+
if (line.startsWith("n")) {
|
|
252
|
+
const match = line.match(/:(\d+)$/);
|
|
253
|
+
if (match) allPorts.push(parseInt(match[1], 10));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
process.kill(-pid, "SIGKILL");
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
killProcessTree(pid);
|
|
263
|
+
for (const p of allPorts) {
|
|
264
|
+
killProcessesOnPort(p);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (port) killProcessesOnPort(port);
|
|
268
|
+
}
|
|
427
269
|
function autoRestartBackoffMs(crashCount) {
|
|
428
270
|
return Math.min(Math.pow(2, crashCount) * 1e3, 6e4);
|
|
429
271
|
}
|
|
@@ -475,13 +317,12 @@ function getServiceStatus(entry, fifonyDir) {
|
|
|
475
317
|
function getAllServiceStatuses(entries, fifonyDir) {
|
|
476
318
|
return entries.map((e) => getServiceStatus(e, fifonyDir));
|
|
477
319
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
if (!existsSync(log)) return "";
|
|
320
|
+
function readLogFileTail(filePath, bytes) {
|
|
321
|
+
if (!existsSync(filePath)) return "";
|
|
481
322
|
try {
|
|
482
|
-
const size = statSync(
|
|
323
|
+
const size = statSync(filePath).size;
|
|
483
324
|
const readSize = Math.min(size, bytes);
|
|
484
|
-
const fd = openSync(
|
|
325
|
+
const fd = openSync(filePath, "r");
|
|
485
326
|
const buf = Buffer.alloc(readSize);
|
|
486
327
|
readSync(fd, buf, 0, readSize, Math.max(0, size - readSize));
|
|
487
328
|
closeSync(fd);
|
|
@@ -490,12 +331,27 @@ function readServiceLogTail(id, fifonyDir, bytes = 8192) {
|
|
|
490
331
|
return "";
|
|
491
332
|
}
|
|
492
333
|
}
|
|
334
|
+
function readServiceLogTail(id, fifonyDir, bytes = 8192) {
|
|
335
|
+
return readLogFileTail(serviceLogPath(fifonyDir, id), bytes);
|
|
336
|
+
}
|
|
337
|
+
function readServiceLogGenerationTail(id, fifonyDir, generation, bytes = 16384) {
|
|
338
|
+
return readLogFileTail(serviceLogGenerationPath(fifonyDir, id, generation), bytes);
|
|
339
|
+
}
|
|
340
|
+
function listServiceLogGenerations(fifonyDir, id) {
|
|
341
|
+
const base = serviceLogPath(fifonyDir, id);
|
|
342
|
+
const generations = [];
|
|
343
|
+
if (existsSync(base)) generations.push(0);
|
|
344
|
+
if (existsSync(`${base}.1`)) generations.push(1);
|
|
345
|
+
if (existsSync(`${base}.2`)) generations.push(2);
|
|
346
|
+
return generations;
|
|
347
|
+
}
|
|
493
348
|
function reconcileServiceStates(entries, fifonyDir) {
|
|
494
349
|
for (const entry of entries) {
|
|
495
350
|
const info = readPidInfo(fifonyDir, entry.id);
|
|
496
351
|
if (!info) continue;
|
|
497
352
|
if (info.state === "stopped") continue;
|
|
498
353
|
if (!isProcessAlive(info.pid)) {
|
|
354
|
+
cleanupServiceProcesses(info.pid, entry.port);
|
|
499
355
|
const crashCount = (info.crashCount ?? 0) + 1;
|
|
500
356
|
writePidInfo(fifonyDir, entry.id, {
|
|
501
357
|
...info,
|
|
@@ -503,7 +359,7 @@ function reconcileServiceStates(entries, fifonyDir) {
|
|
|
503
359
|
crashCount,
|
|
504
360
|
lastCrashAt: now()
|
|
505
361
|
});
|
|
506
|
-
logger.info({ id: entry.id, crashCount }, "[Service] Boot: process dead \u2192 crashed");
|
|
362
|
+
logger.info({ id: entry.id, crashCount }, "[Service] Boot: process dead \u2192 crashed (port cleaned)");
|
|
507
363
|
}
|
|
508
364
|
}
|
|
509
365
|
}
|
|
@@ -521,19 +377,69 @@ function setServiceResourceStateApi(api) {
|
|
|
521
377
|
serviceResourceStateApi = api;
|
|
522
378
|
}
|
|
523
379
|
async function sendServiceEvent(entityId, event, context = {}) {
|
|
524
|
-
if (!
|
|
525
|
-
|
|
380
|
+
if (!serviceRuntime) throw new Error("Service runtime not initialized");
|
|
381
|
+
const { fifonyDir, targetRoot, getEntries, getGlobalEnv, onTransition } = serviceRuntime;
|
|
382
|
+
const entry = (getEntries() ?? []).find((e) => e.id === entityId);
|
|
383
|
+
if (event === "START") {
|
|
384
|
+
const existing = readPidInfo(fifonyDir, entityId);
|
|
385
|
+
cleanupServiceProcesses(existing?.pid ?? null, entry?.port);
|
|
386
|
+
if (!entry) throw new Error(`Service entry not found: ${entityId}`);
|
|
387
|
+
const globalEnv = getGlobalEnv() ?? {};
|
|
388
|
+
const serviceVaulterEnv = serviceRuntime.getServiceEnv?.(entityId) ?? {};
|
|
389
|
+
const effectiveEntry = Object.keys(serviceVaulterEnv).length > 0 ? { ...entry, env: { ...serviceVaulterEnv, ...entry.env ?? {} } } : entry;
|
|
390
|
+
const spawned = spawnProcess(effectiveEntry, targetRoot, fifonyDir, globalEnv);
|
|
391
|
+
writePidInfo(fifonyDir, entityId, {
|
|
392
|
+
pid: spawned.pid,
|
|
393
|
+
command: spawned.command,
|
|
394
|
+
startedAt: now(),
|
|
395
|
+
state: "starting",
|
|
396
|
+
crashCount: 0
|
|
397
|
+
});
|
|
398
|
+
const transition = {
|
|
399
|
+
id: entityId,
|
|
400
|
+
from: "stopped",
|
|
401
|
+
to: "starting",
|
|
402
|
+
reason: "manual start",
|
|
403
|
+
pid: spawned.pid
|
|
404
|
+
};
|
|
405
|
+
onTransition?.(transition);
|
|
406
|
+
logger.info({ id: entityId, pid: spawned.pid }, "[ServiceFSM] START \u2192 starting");
|
|
407
|
+
return { pid: spawned.pid, state: "starting" };
|
|
526
408
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
409
|
+
if (event === "STOP") {
|
|
410
|
+
const info = readPidInfo(fifonyDir, entityId);
|
|
411
|
+
if (!info || info.state === "stopped") return { state: "stopped" };
|
|
412
|
+
if (entry?.stopCommand) {
|
|
413
|
+
try {
|
|
414
|
+
const cwd = entry.cwd ? join(targetRoot, entry.cwd) : targetRoot;
|
|
415
|
+
execSync(entry.stopCommand, { cwd, stdio: "pipe", timeout: 15e3 });
|
|
416
|
+
logger.info({ id: entityId, stopCommand: entry.stopCommand }, "[ServiceFSM] stopCommand executed");
|
|
417
|
+
} catch (err) {
|
|
418
|
+
logger.warn({ err, id: entityId }, "[ServiceFSM] stopCommand failed, falling back to SIGTERM");
|
|
419
|
+
if (isProcessAlive(info.pid)) {
|
|
420
|
+
try {
|
|
421
|
+
process.kill(-info.pid, "SIGTERM");
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} else if (isProcessAlive(info.pid)) {
|
|
427
|
+
try {
|
|
428
|
+
process.kill(-info.pid, "SIGTERM");
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
process.kill(info.pid, "SIGTERM");
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
535
435
|
}
|
|
436
|
+
writePidInfo(fifonyDir, entityId, { ...info, state: "stopping", stoppingAt: now() });
|
|
437
|
+
const transition = { id: entityId, from: info.state, to: "stopping", reason: "manual stop", pid: info.pid };
|
|
438
|
+
onTransition?.(transition);
|
|
439
|
+
logger.info({ id: entityId, pid: info.pid }, "[ServiceFSM] STOP \u2192 stopping");
|
|
440
|
+
return { state: "stopping" };
|
|
536
441
|
}
|
|
442
|
+
throw new Error(`Unknown service event: ${event}`);
|
|
537
443
|
}
|
|
538
444
|
function resolveServiceEntry(entityId) {
|
|
539
445
|
if (!serviceRuntime) return null;
|
|
@@ -552,10 +458,13 @@ var serviceStateMachineConfig = {
|
|
|
552
458
|
stateField: "state",
|
|
553
459
|
initialState: "stopped",
|
|
554
460
|
autoCleanup: false,
|
|
461
|
+
hooks: {
|
|
462
|
+
afterTransition: "onServiceTransition"
|
|
463
|
+
},
|
|
555
464
|
states: {
|
|
556
465
|
stopped: {
|
|
557
466
|
on: { START: "starting" },
|
|
558
|
-
|
|
467
|
+
afterEnter: "onEnterStopped"
|
|
559
468
|
},
|
|
560
469
|
starting: {
|
|
561
470
|
on: {
|
|
@@ -563,7 +472,7 @@ var serviceStateMachineConfig = {
|
|
|
563
472
|
PROCESS_DIED: "crashed",
|
|
564
473
|
STOP: "stopping"
|
|
565
474
|
},
|
|
566
|
-
|
|
475
|
+
afterEnter: "spawnService",
|
|
567
476
|
triggers: [{
|
|
568
477
|
type: "function",
|
|
569
478
|
interval: TRIGGER_INTERVAL_MS,
|
|
@@ -592,7 +501,7 @@ var serviceStateMachineConfig = {
|
|
|
592
501
|
PROCESS_DIED: "crashed",
|
|
593
502
|
STOP: "stopping"
|
|
594
503
|
},
|
|
595
|
-
|
|
504
|
+
afterEnter: "onEnterRunning",
|
|
596
505
|
triggers: [{
|
|
597
506
|
type: "function",
|
|
598
507
|
interval: TRIGGER_INTERVAL_MS,
|
|
@@ -610,7 +519,7 @@ var serviceStateMachineConfig = {
|
|
|
610
519
|
PROCESS_EXITED: "stopped",
|
|
611
520
|
KILL_TIMEOUT: "stopped"
|
|
612
521
|
},
|
|
613
|
-
|
|
522
|
+
afterEnter: "sendSigterm",
|
|
614
523
|
triggers: [{
|
|
615
524
|
type: "function",
|
|
616
525
|
interval: TRIGGER_INTERVAL_MS,
|
|
@@ -640,7 +549,7 @@ var serviceStateMachineConfig = {
|
|
|
640
549
|
START: "starting",
|
|
641
550
|
STOP: "stopping"
|
|
642
551
|
},
|
|
643
|
-
|
|
552
|
+
afterEnter: "recordCrash",
|
|
644
553
|
triggers: [{
|
|
645
554
|
type: "function",
|
|
646
555
|
interval: TRIGGER_INTERVAL_MS,
|
|
@@ -665,6 +574,30 @@ var serviceStateMachineConfig = {
|
|
|
665
574
|
// ── Actions ────────────────────────────────────────────────────────────────
|
|
666
575
|
// (context, event, machine) — context is the payload from send()
|
|
667
576
|
actions: {
|
|
577
|
+
// ── Machine-level afterTransition hook — consolidates WS broadcast + log broadcaster ──
|
|
578
|
+
onServiceTransition: async (context, _event, machine) => {
|
|
579
|
+
if (!serviceRuntime) return;
|
|
580
|
+
const { fifonyDir } = serviceRuntime;
|
|
581
|
+
const info = readPidInfo(fifonyDir, machine.entityId);
|
|
582
|
+
const currentState = info?.state ?? "stopped";
|
|
583
|
+
const pid = info?.pid ?? null;
|
|
584
|
+
logger.info({ id: machine.entityId, event: _event, state: currentState }, "[ServiceFSM] Transition");
|
|
585
|
+
serviceRuntime.onTransition?.({
|
|
586
|
+
id: machine.entityId,
|
|
587
|
+
from: context.previousState ?? "none",
|
|
588
|
+
to: currentState,
|
|
589
|
+
pid,
|
|
590
|
+
reason: _event
|
|
591
|
+
});
|
|
592
|
+
if (currentState === "starting") {
|
|
593
|
+
const { startServiceLogBroadcasting, stopServiceLogBroadcasting } = await import("./service-log-broadcaster-JIUP2L3D.js");
|
|
594
|
+
stopServiceLogBroadcasting(machine.entityId);
|
|
595
|
+
startServiceLogBroadcasting(machine.entityId, fifonyDir);
|
|
596
|
+
} else if (currentState === "stopped" || currentState === "crashed") {
|
|
597
|
+
const { stopServiceLogBroadcasting } = await import("./service-log-broadcaster-JIUP2L3D.js");
|
|
598
|
+
stopServiceLogBroadcasting(machine.entityId);
|
|
599
|
+
}
|
|
600
|
+
},
|
|
668
601
|
spawnService: async (context, _event, machine) => {
|
|
669
602
|
if (!serviceRuntime) {
|
|
670
603
|
logger.warn({ entityId: machine.entityId }, "[ServiceFSM] spawnService called but runtime not set");
|
|
@@ -677,18 +610,11 @@ var serviceStateMachineConfig = {
|
|
|
677
610
|
}
|
|
678
611
|
const { fifonyDir, targetRoot } = serviceRuntime;
|
|
679
612
|
const globalEnv = serviceRuntime.getGlobalEnv();
|
|
613
|
+
const serviceVaulterEnv = serviceRuntime.getServiceEnv?.(entry.id) ?? {};
|
|
614
|
+
const effectiveEntry = Object.keys(serviceVaulterEnv).length > 0 ? { ...entry, env: { ...serviceVaulterEnv, ...entry.env ?? {} } } : entry;
|
|
680
615
|
const existing = readPidInfo(fifonyDir, entry.id);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
process.kill(-existing.pid, "SIGKILL");
|
|
684
|
-
} catch {
|
|
685
|
-
}
|
|
686
|
-
try {
|
|
687
|
-
process.kill(existing.pid, "SIGKILL");
|
|
688
|
-
} catch {
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
const spawned = spawnProcess(entry, targetRoot, fifonyDir, globalEnv);
|
|
616
|
+
cleanupServiceProcesses(existing?.pid ?? null, entry.port);
|
|
617
|
+
const spawned = spawnProcess(effectiveEntry, targetRoot, fifonyDir, globalEnv);
|
|
692
618
|
const isAutoRestart = _event === "AUTO_RESTART";
|
|
693
619
|
const prevCrashCount = existing?.crashCount ?? 0;
|
|
694
620
|
writePidInfo(fifonyDir, entry.id, {
|
|
@@ -702,20 +628,28 @@ var serviceStateMachineConfig = {
|
|
|
702
628
|
{ id: entry.id, pid: spawned.pid, event: _event },
|
|
703
629
|
`[ServiceFSM] spawnService \u2192 starting (${isAutoRestart ? "auto-restart" : "manual"})`
|
|
704
630
|
);
|
|
705
|
-
serviceRuntime.onTransition?.({
|
|
706
|
-
id: entry.id,
|
|
707
|
-
from: context.previousState ?? "none",
|
|
708
|
-
to: "starting",
|
|
709
|
-
pid: spawned.pid,
|
|
710
|
-
reason: isAutoRestart ? `auto-restart (crash #${prevCrashCount})` : "manual start"
|
|
711
|
-
});
|
|
712
631
|
},
|
|
713
632
|
sendSigterm: async (context, _event, machine) => {
|
|
714
633
|
if (!serviceRuntime) return;
|
|
715
|
-
const { fifonyDir } = serviceRuntime;
|
|
634
|
+
const { fifonyDir, targetRoot } = serviceRuntime;
|
|
635
|
+
const entry = resolveServiceEntry(machine.entityId);
|
|
716
636
|
const info = readPidInfo(fifonyDir, machine.entityId);
|
|
717
637
|
if (!info) return;
|
|
718
|
-
if (
|
|
638
|
+
if (entry?.stopCommand) {
|
|
639
|
+
try {
|
|
640
|
+
const cwd = entry.cwd ? join(targetRoot, entry.cwd) : targetRoot;
|
|
641
|
+
execSync(entry.stopCommand, { cwd, stdio: "pipe", timeout: 15e3 });
|
|
642
|
+
logger.info({ id: machine.entityId, stopCommand: entry.stopCommand }, "[ServiceFSM] stopCommand executed");
|
|
643
|
+
} catch (err) {
|
|
644
|
+
logger.warn({ err, id: machine.entityId }, "[ServiceFSM] stopCommand failed, falling back to SIGTERM");
|
|
645
|
+
if (isProcessAlive(info.pid)) {
|
|
646
|
+
try {
|
|
647
|
+
process.kill(-info.pid, "SIGTERM");
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} else if (isProcessAlive(info.pid)) {
|
|
719
653
|
try {
|
|
720
654
|
process.kill(-info.pid, "SIGTERM");
|
|
721
655
|
} catch {
|
|
@@ -726,14 +660,7 @@ var serviceStateMachineConfig = {
|
|
|
726
660
|
state: "stopping",
|
|
727
661
|
stoppingAt: now()
|
|
728
662
|
});
|
|
729
|
-
logger.info({ id: machine.entityId, pid: info.pid }, "[ServiceFSM] sendSigterm \u2192 stopping");
|
|
730
|
-
serviceRuntime.onTransition?.({
|
|
731
|
-
id: machine.entityId,
|
|
732
|
-
from: context.previousState ?? "running",
|
|
733
|
-
to: "stopping",
|
|
734
|
-
pid: info.pid,
|
|
735
|
-
reason: "manual stop"
|
|
736
|
-
});
|
|
663
|
+
logger.info({ id: machine.entityId, pid: info.pid, hasStopCommand: !!entry?.stopCommand }, "[ServiceFSM] sendSigterm \u2192 stopping");
|
|
737
664
|
},
|
|
738
665
|
recordCrash: async (context, _event, machine) => {
|
|
739
666
|
if (!serviceRuntime) return;
|
|
@@ -756,13 +683,6 @@ var serviceStateMachineConfig = {
|
|
|
756
683
|
{ id: machine.entityId, crashCount, nextRetryAt },
|
|
757
684
|
"[ServiceFSM] recordCrash \u2192 crashed"
|
|
758
685
|
);
|
|
759
|
-
serviceRuntime.onTransition?.({
|
|
760
|
-
id: machine.entityId,
|
|
761
|
-
from: context.previousState ?? "running",
|
|
762
|
-
to: "crashed",
|
|
763
|
-
pid: null,
|
|
764
|
-
reason: `process died (crash #${crashCount})`
|
|
765
|
-
});
|
|
766
686
|
},
|
|
767
687
|
onEnterStopped: async (context, _event, machine) => {
|
|
768
688
|
if (!serviceRuntime) return;
|
|
@@ -781,15 +701,11 @@ var serviceStateMachineConfig = {
|
|
|
781
701
|
}
|
|
782
702
|
logger.info({ id: machine.entityId }, "[ServiceFSM] onEnterStopped \u2014 SIGKILL after stop timeout");
|
|
783
703
|
}
|
|
704
|
+
const entry = resolveServiceEntry(machine.entityId);
|
|
705
|
+
const info2 = readPidInfo(fifonyDir, machine.entityId);
|
|
706
|
+
cleanupServiceProcesses(info2?.pid ?? null, entry?.port);
|
|
784
707
|
removePidInfo(fifonyDir, machine.entityId);
|
|
785
708
|
logger.info({ id: machine.entityId, event: _event }, "[ServiceFSM] onEnterStopped \u2014 pid file removed");
|
|
786
|
-
serviceRuntime.onTransition?.({
|
|
787
|
-
id: machine.entityId,
|
|
788
|
-
from: context.previousState ?? "stopping",
|
|
789
|
-
to: "stopped",
|
|
790
|
-
pid: null,
|
|
791
|
-
reason: _event === "KILL_TIMEOUT" ? "SIGKILL after stop timeout" : "process exited"
|
|
792
|
-
});
|
|
793
709
|
},
|
|
794
710
|
onEnterRunning: async (context, _event, machine) => {
|
|
795
711
|
if (!serviceRuntime) return;
|
|
@@ -799,13 +715,6 @@ var serviceStateMachineConfig = {
|
|
|
799
715
|
writePidInfo(fifonyDir, machine.entityId, { ...info, state: "running" });
|
|
800
716
|
}
|
|
801
717
|
logger.info({ id: machine.entityId, pid: info?.pid }, "[ServiceFSM] onEnterRunning \u2014 grace period elapsed");
|
|
802
|
-
serviceRuntime.onTransition?.({
|
|
803
|
-
id: machine.entityId,
|
|
804
|
-
from: "starting",
|
|
805
|
-
to: "running",
|
|
806
|
-
pid: info?.pid ?? null,
|
|
807
|
-
reason: "startup grace period elapsed"
|
|
808
|
-
});
|
|
809
718
|
}
|
|
810
719
|
},
|
|
811
720
|
// ── Guards ─────────────────────────────────────────────────────────────────
|
|
@@ -835,19 +744,14 @@ var serviceStateMachineConfig = {
|
|
|
835
744
|
|
|
836
745
|
export {
|
|
837
746
|
normalizeServiceEnvironment,
|
|
838
|
-
setServicesAccessor,
|
|
839
|
-
startTrafficProxy,
|
|
840
|
-
stopTrafficProxy,
|
|
841
|
-
getTrafficProxyPort,
|
|
842
|
-
isTrafficProxyRunning,
|
|
843
|
-
getTrafficBuffer,
|
|
844
|
-
getServiceGraph,
|
|
845
|
-
getTrafficProxyStats,
|
|
846
747
|
SERVICE_WATCHER_INTERVAL_MS,
|
|
847
748
|
serviceLogPath,
|
|
749
|
+
serviceLogGenerationPath,
|
|
848
750
|
getServiceStatus,
|
|
849
751
|
getAllServiceStatuses,
|
|
850
752
|
readServiceLogTail,
|
|
753
|
+
readServiceLogGenerationTail,
|
|
754
|
+
listServiceLogGenerations,
|
|
851
755
|
reconcileServiceStates,
|
|
852
756
|
SERVICE_STATE_MACHINE_ID,
|
|
853
757
|
setServiceRuntime,
|
|
@@ -856,4 +760,4 @@ export {
|
|
|
856
760
|
sendServiceEvent,
|
|
857
761
|
serviceStateMachineConfig
|
|
858
762
|
};
|
|
859
|
-
//# sourceMappingURL=chunk-
|
|
763
|
+
//# sourceMappingURL=chunk-UR7T7IA6.js.map
|