fifony 0.1.42 → 0.1.43

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.
Files changed (58) hide show
  1. package/app/dist/assets/{CommandPalette-DNR5umI1.js → CommandPalette-M4VAMxCU.js} +1 -1
  2. package/app/dist/assets/{KeyboardShortcutsHelp-Dpl19F20.js → KeyboardShortcutsHelp-DkvPUXQq.js} +1 -1
  3. package/app/dist/assets/OnboardingWizard-B7V9hoCR.js +1 -0
  4. package/app/dist/assets/analytics.lazy-zVJdF880.js +1 -0
  5. package/app/dist/assets/{api-ChEctgc5.js → api-CkVfYg_m.js} +1 -1
  6. package/app/dist/assets/{createLucideIcon-R47sXufx.js → createLucideIcon-Dfk_Hxud.js} +1 -1
  7. package/app/dist/assets/index-BpiCi7Ew.css +1 -0
  8. package/app/dist/assets/index-D2INW0zc.js +47 -0
  9. package/app/dist/assets/vendor-BEoYbFV1.js +9 -0
  10. package/app/dist/index.html +5 -5
  11. package/app/dist/service-worker.js +9 -4
  12. package/bin/fifony.js +3 -0
  13. package/dist/agent/pty-daemon.js +177 -0
  14. package/dist/agent/run-local.js +177 -43
  15. package/dist/{agent-NNGZEKZH.js → agent-RMQTTUEC.js} +37 -16
  16. package/dist/analytics-broadcaster-O6YBP66L.js +145 -0
  17. package/dist/chunk-3NE23NYW.js +82 -0
  18. package/dist/chunk-42AMQAJG.js +404 -0
  19. package/dist/{chunk-H5N7O5NP.js → chunk-AILXZ2TD.js} +79 -147
  20. package/dist/{chunk-I2UHVKHS.js → chunk-BRSR26VK.js} +2 -2
  21. package/dist/chunk-E2EWEYA4.js +1302 -0
  22. package/dist/chunk-ESWHDHH6.js +102 -0
  23. package/dist/{chunk-NB44PCD2.js → chunk-FJNH3G2Z.js} +1061 -1138
  24. package/dist/chunk-MVTGAKQK.js +493 -0
  25. package/dist/chunk-QQQLP3PL.js +155 -0
  26. package/dist/chunk-SOBLO4YZ.js +2016 -0
  27. package/dist/chunk-YRSH2CLW.js +13784 -0
  28. package/dist/cli.js +335 -44
  29. package/dist/{issue-state-machine-GPQNZYUZ.js → fsm-issue-YGGF7SIL.js} +9 -5
  30. package/dist/helpers-L7NYO5XS.js +53 -0
  31. package/dist/issue-log-broadcaster-WZAHISYB.js +84 -0
  32. package/dist/{issues-MZLRSXD6.js → issues-3QRR7KM6.js} +10 -8
  33. package/dist/log-analyzer-K7MXQB4T.js +287 -0
  34. package/dist/mcp/server.js +109 -137
  35. package/dist/parallel-executor-6INE6NDO.js +118 -0
  36. package/dist/pid-manager-UBWXVSMD.js +21 -0
  37. package/dist/queue-workers-XFZK3TT5.js +32 -0
  38. package/dist/replan-issue.command-4UCWYHGZ.js +15 -0
  39. package/dist/scheduler-ZP7GOZDW.js +26 -0
  40. package/dist/{settings-NGY33WQE.js → settings-ZAWDCFP2.js} +32 -8
  41. package/dist/settings.resource-5CW456AZ.js +24 -0
  42. package/dist/store-M6NCKMZY.js +97 -0
  43. package/dist/{web-push-CRVDJKWR.js → web-push-AX5IIK3P.js} +2 -2
  44. package/dist/{workspace-D3F3XGSI.js → workspace-CJTWFWTJ.js} +5 -4
  45. package/package.json +8 -7
  46. package/app/dist/assets/OnboardingWizard-CijMhJDW.js +0 -1
  47. package/app/dist/assets/analytics.lazy-Dq90a756.js +0 -1
  48. package/app/dist/assets/index-Dy_fM427.js +0 -54
  49. package/app/dist/assets/index-Q9jBP0Pz.css +0 -1
  50. package/app/dist/assets/vendor-DkWeBvNl.js +0 -9
  51. package/dist/chunk-2CVTK5F2.js +0 -288
  52. package/dist/chunk-37N5OFHM.js +0 -125
  53. package/dist/chunk-JTKUWIQD.js +0 -8406
  54. package/dist/chunk-RBDBGU2C.js +0 -303
  55. package/dist/issue-runner-CMZPSVC7.js +0 -16
  56. package/dist/queue-workers-XZ6DGH4W.js +0 -23
  57. package/dist/scheduler-NVE6L3P7.js +0 -22
  58. package/dist/store-4HCGBN4L.js +0 -65
@@ -20,12 +20,12 @@
20
20
  <link rel="icon" href="/assets/icon-32.png" sizes="32x32" type="image/png" />
21
21
  <link rel="icon" href="/assets/icon-16.png" sizes="16x16" type="image/png" />
22
22
  <link rel="apple-touch-icon" href="/assets/apple-touch-icon.png" />
23
- <script type="module" crossorigin src="/assets/assets/index-Dy_fM427.js"></script>
23
+ <script type="module" crossorigin src="/assets/assets/index-D2INW0zc.js"></script>
24
24
  <link rel="modulepreload" crossorigin href="/assets/assets/rolldown-runtime-Dw2cE7zH.js">
25
- <link rel="modulepreload" crossorigin href="/assets/assets/api-ChEctgc5.js">
26
- <link rel="modulepreload" crossorigin href="/assets/assets/vendor-DkWeBvNl.js">
27
- <link rel="modulepreload" crossorigin href="/assets/assets/createLucideIcon-R47sXufx.js">
28
- <link rel="stylesheet" crossorigin href="/assets/assets/index-Q9jBP0Pz.css">
25
+ <link rel="modulepreload" crossorigin href="/assets/assets/api-CkVfYg_m.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/assets/vendor-BEoYbFV1.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/assets/createLucideIcon-Dfk_Hxud.js">
28
+ <link rel="stylesheet" crossorigin href="/assets/assets/index-BpiCi7Ew.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="root"></div>
@@ -1,19 +1,24 @@
1
- const CACHE_VERSION = "1774421145935";
1
+ const CACHE_VERSION = "1774647226934";
2
2
  const CORE_CACHE = `fifony-core-${CACHE_VERSION}`;
3
3
  const ASSET_CACHE = `fifony-assets-${CACHE_VERSION}`;
4
4
  const APP_SHELL_ROUTES = [
5
5
  "/onboarding",
6
6
  "/kanban",
7
+ "/milestones",
7
8
  "/issues",
8
9
  "/analytics",
9
10
  "/agents",
11
+ "/services",
10
12
  "/settings",
11
13
  "/settings/project",
12
- "/settings/general",
14
+ "/settings/system",
13
15
  "/settings/agents",
14
16
  "/settings/notifications",
15
- "/settings/workflow",
16
- "/settings/hotkeys",
17
+ "/settings/execution",
18
+ "/settings/quality",
19
+ "/settings/pipeline",
20
+ "/settings/services",
21
+ "/settings/appearance",
17
22
  "/settings/providers",
18
23
  ];
19
24
  const APP_SHELL_FILES = ["/offline.html", "/manifest.webmanifest", "/favicon.png", "/icon-192.png", "/icon-512.png"];
package/bin/fifony.js CHANGED
@@ -9,6 +9,9 @@ const __dirname = dirname(__filename);
9
9
  const packageRoot = resolve(__dirname, "..");
10
10
  const workspaceRoot = env.FIFONY_WORKSPACE_ROOT ?? cwd();
11
11
 
12
+ // Make the package root available to all child processes (used by pty-daemon path resolution)
13
+ process.env.FIFONY_PKG_ROOT = packageRoot;
14
+
12
15
  const distCli = resolve(packageRoot, "dist", "cli.js");
13
16
  const srcCli = resolve(packageRoot, "src", "cli.ts");
14
17
  const forceSource = argv.includes("--dev") || env.NODE_ENV === "development";
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/agents/pty-daemon.ts
4
+ import { appendFileSync, readFileSync, rmSync, writeFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { createServer } from "net";
7
+ function appendTail(current, text, maxChars) {
8
+ const combined = current + text;
9
+ return combined.length > maxChars ? combined.slice(combined.length - maxChars) : combined;
10
+ }
11
+ async function main() {
12
+ const raw = process.argv[2];
13
+ if (!raw) {
14
+ process.stderr.write("[pty-daemon] Missing options argument\n");
15
+ process.exit(1);
16
+ }
17
+ let opts;
18
+ try {
19
+ opts = JSON.parse(raw);
20
+ } catch {
21
+ process.stderr.write("[pty-daemon] Failed to parse options JSON\n");
22
+ process.exit(1);
23
+ }
24
+ const { command, workspacePath, issueId, startedAt, commandSlice } = opts;
25
+ const liveLogFile = join(workspacePath, "live-output.log");
26
+ const socketPath = join(workspacePath, "agent.sock");
27
+ const agentPidFile = join(workspacePath, "agent.pid");
28
+ const daemonPidFile = join(workspacePath, "daemon.pid");
29
+ const daemonExitFile = join(workspacePath, "daemon.exit.json");
30
+ writeFileSync(daemonPidFile, String(process.pid), "utf8");
31
+ writeFileSync(liveLogFile, "", "utf8");
32
+ const cleanupFiles = () => {
33
+ try {
34
+ rmSync(socketPath, { force: true });
35
+ } catch {
36
+ }
37
+ try {
38
+ rmSync(daemonPidFile, { force: true });
39
+ } catch {
40
+ }
41
+ };
42
+ process.on("exit", cleanupFiles);
43
+ process.on("SIGTERM", () => {
44
+ cleanupFiles();
45
+ process.exit(0);
46
+ });
47
+ process.on("SIGINT", () => {
48
+ cleanupFiles();
49
+ process.exit(0);
50
+ });
51
+ const clients = /* @__PURE__ */ new Set();
52
+ let outputTail = "";
53
+ const OUTPUT_RING_CHARS = 3e5;
54
+ const MAX_LOG_BYTES = 10 * 1024 * 1024;
55
+ const TARGET_LOG_BYTES = 5 * 1024 * 1024;
56
+ let bytesWritten = 0;
57
+ const server = createServer((socket) => {
58
+ clients.add(socket);
59
+ socket.on("close", () => clients.delete(socket));
60
+ socket.on("error", () => clients.delete(socket));
61
+ let buf = "";
62
+ socket.on("data", (chunk) => {
63
+ buf += chunk.toString();
64
+ const lines = buf.split("\n");
65
+ buf = lines.pop() ?? "";
66
+ for (const line of lines) {
67
+ if (!line.trim()) continue;
68
+ try {
69
+ const msg = JSON.parse(line);
70
+ if (msg.t === "cancel") {
71
+ try {
72
+ ptyProcess.kill();
73
+ } catch {
74
+ }
75
+ } else if (msg.t === "tail") {
76
+ const reply = JSON.stringify({ t: "tail", v: outputTail }) + "\n";
77
+ try {
78
+ socket.write(reply);
79
+ } catch {
80
+ }
81
+ } else if (msg.t === "write" && typeof msg.v === "string") {
82
+ try {
83
+ ptyProcess.write(msg.v);
84
+ } catch {
85
+ }
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ });
91
+ });
92
+ try {
93
+ rmSync(socketPath, { force: true });
94
+ } catch {
95
+ }
96
+ server.listen(socketPath);
97
+ const nodePty = await import("node-pty");
98
+ const ptyProcess = nodePty.spawn("sh", ["-c", command], {
99
+ name: "xterm-256color",
100
+ cols: 220,
101
+ rows: 50,
102
+ cwd: workspacePath,
103
+ env: process.env
104
+ });
105
+ const agentPid = ptyProcess.pid;
106
+ if (agentPid) {
107
+ writeFileSync(agentPidFile, JSON.stringify({
108
+ pid: agentPid,
109
+ issueId,
110
+ startedAt,
111
+ command: commandSlice
112
+ }), "utf8");
113
+ }
114
+ const broadcast = (msg) => {
115
+ for (const client of clients) {
116
+ try {
117
+ client.write(msg);
118
+ } catch {
119
+ clients.delete(client);
120
+ }
121
+ }
122
+ };
123
+ ptyProcess.onData((data) => {
124
+ outputTail = appendTail(outputTail, data, OUTPUT_RING_CHARS);
125
+ const encoded = Buffer.from(data);
126
+ try {
127
+ appendFileSync(liveLogFile, encoded);
128
+ } catch {
129
+ }
130
+ bytesWritten += encoded.length;
131
+ if (bytesWritten >= MAX_LOG_BYTES) {
132
+ try {
133
+ const content = readFileSync(liveLogFile);
134
+ if (content.length > TARGET_LOG_BYTES) {
135
+ writeFileSync(liveLogFile, content.slice(content.length - TARGET_LOG_BYTES));
136
+ }
137
+ } catch {
138
+ }
139
+ bytesWritten = 0;
140
+ }
141
+ broadcast(JSON.stringify({ t: "d", v: data }) + "\n");
142
+ });
143
+ ptyProcess.onExit(({ exitCode }) => {
144
+ const success = exitCode === 0;
145
+ const exitRecord = {
146
+ success,
147
+ code: exitCode ?? null,
148
+ outputPath: liveLogFile,
149
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
150
+ };
151
+ try {
152
+ writeFileSync(daemonExitFile, JSON.stringify(exitRecord, null, 2), "utf8");
153
+ } catch {
154
+ }
155
+ broadcast(JSON.stringify({ t: "x", c: exitCode ?? null, s: success }) + "\n");
156
+ try {
157
+ rmSync(agentPidFile, { force: true });
158
+ } catch {
159
+ }
160
+ setTimeout(() => {
161
+ server.close();
162
+ for (const client of clients) {
163
+ try {
164
+ client.destroy();
165
+ } catch {
166
+ }
167
+ }
168
+ process.exit(0);
169
+ }, 2e3);
170
+ });
171
+ }
172
+ main().catch((err) => {
173
+ process.stderr.write(`[pty-daemon] Fatal: ${String(err)}
174
+ `);
175
+ process.exit(1);
176
+ });
177
+ //# sourceMappingURL=pty-daemon.js.map
@@ -1,73 +1,92 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- addEvent,
4
3
  applyPersistedSettings,
5
- applyWorkflowConfig,
6
- buildQueueTitle,
7
- buildRuntimeState,
4
+ broadcastToWebSocketClients,
5
+ cleanTerminalWorkspaces,
8
6
  closeStateStore,
9
7
  createContainer,
10
- deriveConfig,
11
- detectProjectName,
12
8
  hasTerminalQueue,
13
- hydrate,
9
+ initManagedServiceWatcher,
10
+ initQueueWorkers,
14
11
  initStateStore,
15
12
  installGracefulShutdown,
13
+ listServiceStatuses,
14
+ loadLegacyPersistedServices,
15
+ loadPersistedMilestones,
16
+ loadPersistedServices,
16
17
  loadPersistedState,
18
+ loadPersistedVariables,
17
19
  loadRuntimeSettings,
18
20
  persistDetectedProvidersSetting,
19
21
  persistSetting,
20
22
  persistState,
21
23
  persistStateFull,
24
+ reconcileAgentStateTransitions,
25
+ reconcileManagedServiceStates,
26
+ recoverOrphans,
22
27
  recoverPlanningSession,
23
- resolveProjectMetadata,
28
+ recoverState,
29
+ replaceAllServices,
24
30
  startApiServer,
31
+ startAutoConfiguredServices,
32
+ startManagedAgentWatcher,
33
+ startServiceLogBroadcasting,
34
+ stopQueueWorkers,
35
+ stopServiceLogBroadcasting,
25
36
  syncRuntimeConfigSettings,
26
- validateConfig
27
- } from "../chunk-JTKUWIQD.js";
28
- import {
29
- cleanTerminalWorkspaces,
30
- initQueueWorkers,
31
- recoverOrphans,
32
- recoverState,
33
- stopQueueWorkers
34
- } from "../chunk-RBDBGU2C.js";
37
+ upsertPersistedVariable
38
+ } from "../chunk-YRSH2CLW.js";
35
39
  import {
36
40
  initWebPush
37
- } from "../chunk-I2UHVKHS.js";
41
+ } from "../chunk-BRSR26VK.js";
42
+ import "../chunk-QQQLP3PL.js";
43
+ import "../chunk-AILXZ2TD.js";
38
44
  import {
39
- computeMetrics
40
- } from "../chunk-H5N7O5NP.js";
41
- import {
42
- detectAvailableProviders,
43
45
  detectDefaultBranch,
44
46
  getGitRepoStatus,
45
- getProviderDefaultCommand,
46
- resolveDefaultProvider,
47
47
  setSkipSource
48
- } from "../chunk-NB44PCD2.js";
48
+ } from "../chunk-SOBLO4YZ.js";
49
49
  import {
50
- debugBoot,
51
- fail,
52
- now,
53
- parseIntArg
54
- } from "../chunk-2CVTK5F2.js";
50
+ addEvent,
51
+ applyWorkflowConfig,
52
+ buildQueueTitle,
53
+ buildRuntimeState,
54
+ deriveConfig,
55
+ detectProjectName,
56
+ hydrate,
57
+ resolveProjectMetadata,
58
+ validateConfig
59
+ } from "../chunk-E2EWEYA4.js";
55
60
  import {
56
- CLI_ARGS,
57
- PACKAGE_ROOT,
58
- STATE_ROOT,
59
- TARGET_ROOT
60
- } from "../chunk-37N5OFHM.js";
61
+ computeMetrics
62
+ } from "../chunk-MVTGAKQK.js";
63
+ import {
64
+ detectAvailableProviders,
65
+ getProviderDefaultCommand,
66
+ resolveDefaultProvider
67
+ } from "../chunk-FJNH3G2Z.js";
61
68
  import {
62
69
  initLogger,
63
70
  logger
64
71
  } from "../chunk-DVU3CXWA.js";
72
+ import "../chunk-ESWHDHH6.js";
73
+ import {
74
+ CLI_ARGS,
75
+ PACKAGE_ROOT,
76
+ STATE_ROOT,
77
+ TARGET_ROOT,
78
+ debugBoot,
79
+ fail,
80
+ now,
81
+ parseIntArg
82
+ } from "../chunk-42AMQAJG.js";
83
+ import "../chunk-3NE23NYW.js";
65
84
 
66
85
  // src/boot.ts
67
86
  import { mkdirSync, readFileSync } from "fs";
68
87
  import { env, exit, argv } from "process";
69
88
 
70
- // src/persistence/plugins/dev-server.ts
89
+ // src/persistence/plugins/dev-frontend.ts
71
90
  import { resolve } from "path";
72
91
  async function startDevFrontend(apiPort, devPort, options) {
73
92
  const VITE_CONFIG_PATH = resolve(PACKAGE_ROOT, "app/vite.config.js");
@@ -143,7 +162,7 @@ async function startDevFrontend(apiPort, devPort, options) {
143
162
  await server.listen();
144
163
  logger.info(`Dev frontend available at http://localhost:${devPort}`);
145
164
  } catch (error) {
146
- logger.warn(`Failed to start Vite dev server: ${String(error)}`);
165
+ logger.warn(`Failed to start Vite service: ${String(error)}`);
147
166
  }
148
167
  }
149
168
 
@@ -173,7 +192,7 @@ Options:
173
192
  --attempts <n> Maximum attempts per issue
174
193
  --poll <ms> Scheduler interval in ms
175
194
  --timeout <ms> Agent command timeout in ms (default: 1800000)
176
- --dev Start Vite dev server alongside API (HMR on port+1)
195
+ --dev Start Vite dev frontend alongside API (HMR on port+1)
177
196
  --no-tls Disable HTTPS (use plain HTTP)
178
197
  --once Process once and exit
179
198
  --skip-source Skip source snapshot copy
@@ -184,6 +203,8 @@ Options:
184
203
  );
185
204
  }
186
205
  async function main() {
206
+ let serviceWatcher = null;
207
+ let agentWatcher = null;
187
208
  debugBoot("main:start");
188
209
  const args = CLI_ARGS;
189
210
  if (args.includes("--help") || args.includes("-h")) {
@@ -236,37 +257,79 @@ async function main() {
236
257
  sourceRepoUrl: TARGET_ROOT,
237
258
  sourceRef: "workspace",
238
259
  config,
260
+ milestones: [],
239
261
  issues: [],
240
262
  events: [],
241
263
  metrics: { total: 0, planning: 0, queued: 0, inProgress: 0, blocked: 0, done: 0, merged: 0, cancelled: 0, activeWorkers: 0 },
242
264
  notes: [],
265
+ variables: [],
243
266
  booting: true
244
267
  };
245
268
  let apiState = earlyState;
246
269
  createContainer(apiState);
247
270
  debugBoot("main:container-early-init");
248
271
  if (dashboardPort) {
249
- await startApiServer(apiState, dashboardPort);
272
+ const devPort = devMode ? dashboardPort + 1 : void 0;
273
+ await startApiServer(apiState, dashboardPort, { devPort });
250
274
  debugBoot("main:api-server-early-start");
251
- if (devMode) {
252
- const devPort = dashboardPort + 1;
275
+ if (devMode && devPort) {
253
276
  await startDevFrontend(dashboardPort, devPort);
254
277
  }
255
278
  }
279
+ const extractLegacyServicesFromRuntimeState = (value) => {
280
+ if (!value || typeof value !== "object") return [];
281
+ const configValue = value.config;
282
+ if (!configValue || typeof configValue !== "object") return [];
283
+ const legacyServices = configValue.devServers;
284
+ if (!Array.isArray(legacyServices)) return [];
285
+ return legacyServices.filter(
286
+ (entry) => Boolean(entry && typeof entry === "object" && typeof entry.id === "string" && typeof entry.command === "string")
287
+ );
288
+ };
256
289
  debugBoot("main:phase-c-start");
257
290
  logger.debug("[Boot] Loading persisted state, settings, and recovering sessions");
258
- const [previous, persistedSettings] = await Promise.all([
291
+ const [previous, persistedSettings, persistedServices, legacyPersistedServices, persistedMilestones, persistedVariables] = await Promise.all([
259
292
  loadPersistedState(),
260
293
  loadRuntimeSettings(),
294
+ loadPersistedServices(),
295
+ loadLegacyPersistedServices(),
296
+ loadPersistedMilestones(),
297
+ loadPersistedVariables(),
261
298
  persistDetectedProvidersSetting(detectedProviders),
262
299
  recoverPlanningSession()
263
300
  ]);
264
301
  logger.info({ hadPreviousState: previous !== null, issueCount: previous?.issues?.length ?? 0, settingsCount: persistedSettings.length }, "[Boot] State loaded from persistence");
265
302
  debugBoot("main:state-loaded");
303
+ const runtimeLegacyServices = extractLegacyServicesFromRuntimeState(previous);
304
+ const migratedServices = persistedServices.length > 0 ? persistedServices : legacyPersistedServices.length > 0 ? legacyPersistedServices : runtimeLegacyServices;
266
305
  config = applyPersistedSettings(config, persistedSettings);
306
+ if (migratedServices.length > 0) {
307
+ config = { ...config, services: migratedServices };
308
+ if (persistedServices.length === 0) {
309
+ await replaceAllServices(migratedServices);
310
+ }
311
+ }
267
312
  await syncRuntimeConfigSettings(config, persistedSettings);
268
313
  const projectMetadata = resolveProjectMetadata(persistedSettings, TARGET_ROOT);
269
- const state = buildRuntimeState(previous, config, projectMetadata);
314
+ const state = buildRuntimeState(previous, config, projectMetadata, persistedMilestones);
315
+ state.variables = persistedVariables;
316
+ if (state.variables.length === 0) {
317
+ const migrated = [];
318
+ const globalEnv = config.serviceEnv ?? {};
319
+ for (const [key, value] of Object.entries(globalEnv)) {
320
+ migrated.push({ id: `global:${key}`, key, value: String(value ?? ""), scope: "global", updatedAt: now() });
321
+ }
322
+ for (const svc of config.services ?? []) {
323
+ for (const [key, value] of Object.entries(svc.env ?? {})) {
324
+ migrated.push({ id: `${svc.id}:${key}`, key, value: String(value ?? ""), scope: svc.id, updatedAt: now() });
325
+ }
326
+ }
327
+ if (migrated.length > 0) {
328
+ await Promise.all(migrated.map((v) => upsertPersistedVariable(v)));
329
+ state.variables = migrated;
330
+ logger.info({ count: migrated.length }, "[Boot] Migrated legacy env vars to variables resource");
331
+ }
332
+ }
270
333
  debugBoot("main:state-merged");
271
334
  state.config.dashboardPort = dashboardPort ? String(dashboardPort) : void 0;
272
335
  state.updatedAt = now();
@@ -306,6 +369,32 @@ async function main() {
306
369
  }
307
370
  cleanTerminalWorkspaces();
308
371
  if (!skipRecovery) await recoverOrphans();
372
+ try {
373
+ const agentTransitions = reconcileAgentStateTransitions(state.issues, STATE_ROOT);
374
+ if (agentTransitions.length > 0) {
375
+ logger.info({ count: agentTransitions.length }, "[Boot] Agent states reconciled");
376
+ }
377
+ } catch (err) {
378
+ logger.warn({ err }, "[Boot] Agent state reconciliation failed \u2014 continuing");
379
+ }
380
+ try {
381
+ const services = state.config.services ?? [];
382
+ reconcileManagedServiceStates(services, STATE_ROOT);
383
+ const autoStartTransitions = startAutoConfiguredServices(
384
+ services,
385
+ TARGET_ROOT,
386
+ STATE_ROOT,
387
+ state.config.serviceEnv
388
+ );
389
+ for (const t of autoStartTransitions) {
390
+ logger.info({ id: t.id, command: t.to }, "[Boot] Service auto-started");
391
+ }
392
+ for (const status of listServiceStatuses(services, STATE_ROOT)) {
393
+ if (status.running) startServiceLogBroadcasting(status.id, STATE_ROOT);
394
+ }
395
+ } catch (err) {
396
+ logger.warn({ err }, "[Boot] Service init failed \u2014 continuing");
397
+ }
309
398
  state.metrics = computeMetrics(state.issues);
310
399
  if (dashboardPort) {
311
400
  Object.assign(apiState, state);
@@ -319,6 +408,43 @@ async function main() {
319
408
  } catch (error) {
320
409
  logger.warn({ err: error }, "[Boot] Queue workers failed to initialize \u2014 continuing without queue-based dispatch");
321
410
  }
411
+ serviceWatcher = initManagedServiceWatcher(
412
+ () => apiState.config.services ?? [],
413
+ () => apiState.config.serviceEnv ?? {},
414
+ STATE_ROOT,
415
+ TARGET_ROOT,
416
+ (t) => {
417
+ logger.info({ id: t.id, from: t.from, to: t.to, reason: t.reason }, "[Service] FSM transition");
418
+ broadcastToWebSocketClients({
419
+ type: "service",
420
+ id: t.id,
421
+ state: t.to,
422
+ running: t.to === "starting" || t.to === "running",
423
+ pid: t.pid ?? null
424
+ });
425
+ if (t.to === "starting") {
426
+ startServiceLogBroadcasting(t.id, STATE_ROOT);
427
+ } else if (t.to === "stopped" || t.to === "crashed") {
428
+ stopServiceLogBroadcasting(t.id);
429
+ }
430
+ }
431
+ );
432
+ agentWatcher = startManagedAgentWatcher(
433
+ () => apiState.issues,
434
+ STATE_ROOT,
435
+ (t) => {
436
+ logger.info({ issueId: t.issueId, identifier: t.identifier, from: t.from, to: t.to, reason: t.reason }, "[AgentFSM] Transition");
437
+ broadcastToWebSocketClients({
438
+ type: "agent-fsm",
439
+ issueId: t.issueId,
440
+ identifier: t.identifier,
441
+ operation: t.operation,
442
+ state: t.to,
443
+ running: t.to === "running" || t.to === "preparing",
444
+ pid: t.pid ?? null
445
+ });
446
+ }
447
+ );
322
448
  installGracefulShutdown(state);
323
449
  try {
324
450
  const settings = await loadRuntimeSettings();
@@ -362,6 +488,14 @@ async function main() {
362
488
  state.updatedAt = now();
363
489
  state.metrics = computeMetrics(state.issues);
364
490
  await persistStateFull(state);
491
+ try {
492
+ serviceWatcher?.stop();
493
+ } catch {
494
+ }
495
+ try {
496
+ agentWatcher?.stop();
497
+ } catch {
498
+ }
365
499
  try {
366
500
  await stopQueueWorkers();
367
501
  } catch {
@@ -1,25 +1,24 @@
1
1
  import {
2
2
  addTokenUsage,
3
- cleanStalePidFile,
3
+ canDispatchManagedAgent,
4
4
  extractTokenUsage,
5
- isAgentStillRunning,
6
- isProcessAlive,
7
5
  issueHasResumableSession,
8
6
  loadAgentPipelineSnapshotForIssue,
9
7
  loadAgentPipelineState,
10
8
  loadAgentSessionSnapshotsForIssue,
11
9
  readAgentDirective,
12
- readAgentPid,
13
10
  runAgentPipeline,
14
11
  runAgentSession,
15
- runIssueOnce,
12
+ runManagedExecuteJob,
13
+ runManagedReviewJob,
16
14
  runPlanningJob,
17
15
  tryParseJsonOutput
18
- } from "./chunk-JTKUWIQD.js";
19
- import "./chunk-RBDBGU2C.js";
20
- import "./chunk-I2UHVKHS.js";
21
- import "./chunk-H5N7O5NP.js";
16
+ } from "./chunk-YRSH2CLW.js";
17
+ import "./chunk-BRSR26VK.js";
18
+ import "./chunk-QQQLP3PL.js";
19
+ import "./chunk-AILXZ2TD.js";
22
20
  import {
21
+ attachToDaemon,
23
22
  buildPrompt,
24
23
  buildProviderBasePrompt,
25
24
  buildTurnPrompt,
@@ -34,16 +33,32 @@ import {
34
33
  prepareWorkspace,
35
34
  runCommandWithTimeout,
36
35
  runHook,
37
- shouldSkipMergePath
38
- } from "./chunk-NB44PCD2.js";
39
- import "./chunk-2CVTK5F2.js";
40
- import "./chunk-37N5OFHM.js";
36
+ shouldSkipMergePath,
37
+ writeToDaemon
38
+ } from "./chunk-SOBLO4YZ.js";
39
+ import "./chunk-E2EWEYA4.js";
40
+ import "./chunk-MVTGAKQK.js";
41
+ import "./chunk-FJNH3G2Z.js";
41
42
  import "./chunk-DVU3CXWA.js";
43
+ import "./chunk-ESWHDHH6.js";
44
+ import "./chunk-42AMQAJG.js";
45
+ import {
46
+ cleanStalePidFile,
47
+ isAgentStillRunning,
48
+ isDaemonAlive,
49
+ isDaemonSocketReady,
50
+ isProcessAlive,
51
+ readAgentPid,
52
+ readDaemonExit,
53
+ readDaemonPid
54
+ } from "./chunk-3NE23NYW.js";
42
55
  export {
43
56
  addTokenUsage,
57
+ attachToDaemon,
44
58
  buildPrompt,
45
59
  buildProviderBasePrompt,
46
60
  buildTurnPrompt,
61
+ canDispatchManagedAgent as canDispatchAgent,
47
62
  cleanStalePidFile,
48
63
  cleanWorkspace,
49
64
  computeDiffStats,
@@ -53,6 +68,8 @@ export {
53
68
  hydrateIssuePathsFromWorkspace,
54
69
  inferChangedWorkspacePaths,
55
70
  isAgentStillRunning,
71
+ isDaemonAlive,
72
+ isDaemonSocketReady,
56
73
  isProcessAlive,
57
74
  issueHasResumableSession,
58
75
  loadAgentPipelineSnapshotForIssue,
@@ -63,13 +80,17 @@ export {
63
80
  prepareWorkspace,
64
81
  readAgentDirective,
65
82
  readAgentPid,
83
+ readDaemonExit,
84
+ readDaemonPid,
66
85
  runAgentPipeline,
67
86
  runAgentSession,
68
87
  runCommandWithTimeout,
88
+ runManagedExecuteJob as runExecutePhase,
69
89
  runHook,
70
- runIssueOnce,
71
90
  runPlanningJob,
91
+ runManagedReviewJob as runReviewPhase,
72
92
  shouldSkipMergePath,
73
- tryParseJsonOutput
93
+ tryParseJsonOutput,
94
+ writeToDaemon
74
95
  };
75
- //# sourceMappingURL=agent-NNGZEKZH.js.map
96
+ //# sourceMappingURL=agent-RMQTTUEC.js.map