@kmmao/happy-agent 0.5.0 → 0.5.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/index.cjs +171 -1
- package/dist/index.mjs +173 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17,8 +17,9 @@ var crypto = require('crypto');
|
|
|
17
17
|
var path = require('path');
|
|
18
18
|
var fs = require('fs');
|
|
19
19
|
var os = require('os');
|
|
20
|
+
var http = require('http');
|
|
20
21
|
|
|
21
|
-
var version = "0.5.
|
|
22
|
+
var version = "0.5.1";
|
|
22
23
|
|
|
23
24
|
function loadConfig() {
|
|
24
25
|
const serverUrl = (process.env.HAPPY_SERVER_URL ?? "https://happyserve.xycloud.info").replace(/\/+$/, "");
|
|
@@ -1838,12 +1839,15 @@ function isNotFound(err) {
|
|
|
1838
1839
|
}
|
|
1839
1840
|
|
|
1840
1841
|
const pidToSession = /* @__PURE__ */ new Map();
|
|
1842
|
+
let persistPath = null;
|
|
1841
1843
|
function trackSession(session) {
|
|
1842
1844
|
pidToSession.set(session.pid, session);
|
|
1845
|
+
flush();
|
|
1843
1846
|
}
|
|
1844
1847
|
function untrackSession(pid) {
|
|
1845
1848
|
const session = pidToSession.get(pid);
|
|
1846
1849
|
pidToSession.delete(pid);
|
|
1850
|
+
flush();
|
|
1847
1851
|
return session;
|
|
1848
1852
|
}
|
|
1849
1853
|
function getTrackedSession(pid) {
|
|
@@ -1855,9 +1859,59 @@ function getAllTrackedSessions() {
|
|
|
1855
1859
|
function getTrackedSessionCount() {
|
|
1856
1860
|
return pidToSession.size;
|
|
1857
1861
|
}
|
|
1862
|
+
function enablePersistence(filePath) {
|
|
1863
|
+
persistPath = filePath;
|
|
1864
|
+
load();
|
|
1865
|
+
}
|
|
1866
|
+
function load() {
|
|
1867
|
+
if (!persistPath) return;
|
|
1868
|
+
try {
|
|
1869
|
+
const raw = fs.readFileSync(persistPath, "utf-8");
|
|
1870
|
+
const entries = JSON.parse(raw);
|
|
1871
|
+
let recovered = 0;
|
|
1872
|
+
for (const entry of entries) {
|
|
1873
|
+
try {
|
|
1874
|
+
process.kill(entry.pid, 0);
|
|
1875
|
+
} catch {
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
pidToSession.set(entry.pid, {
|
|
1879
|
+
pid: entry.pid,
|
|
1880
|
+
directory: entry.directory,
|
|
1881
|
+
startedAt: entry.startedAt,
|
|
1882
|
+
happySessionId: entry.happySessionId,
|
|
1883
|
+
lastActivityAt: entry.lastActivityAt,
|
|
1884
|
+
automationContext: entry.automationContext
|
|
1885
|
+
});
|
|
1886
|
+
recovered++;
|
|
1887
|
+
}
|
|
1888
|
+
if (recovered > 0) {
|
|
1889
|
+
logger.debug(`[TRACKED] Recovered ${recovered} sessions from ${persistPath}`);
|
|
1890
|
+
}
|
|
1891
|
+
} catch {
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function flush() {
|
|
1895
|
+
if (!persistPath) return;
|
|
1896
|
+
try {
|
|
1897
|
+
const entries = [...pidToSession.values()].map((s) => ({
|
|
1898
|
+
pid: s.pid,
|
|
1899
|
+
directory: s.directory,
|
|
1900
|
+
startedAt: s.startedAt,
|
|
1901
|
+
happySessionId: s.happySessionId,
|
|
1902
|
+
lastActivityAt: s.lastActivityAt,
|
|
1903
|
+
automationContext: s.automationContext
|
|
1904
|
+
}));
|
|
1905
|
+
fs.mkdirSync(path.dirname(persistPath), { recursive: true });
|
|
1906
|
+
fs.writeFileSync(persistPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
logger.debug(`[TRACKED] Failed to persist: ${err}`);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1858
1911
|
|
|
1859
1912
|
var trackedSessions = /*#__PURE__*/Object.freeze({
|
|
1860
1913
|
__proto__: null,
|
|
1914
|
+
enablePersistence: enablePersistence,
|
|
1861
1915
|
getAllTrackedSessions: getAllTrackedSessions,
|
|
1862
1916
|
getTrackedSession: getTrackedSession,
|
|
1863
1917
|
getTrackedSessionCount: getTrackedSessionCount,
|
|
@@ -3125,6 +3179,101 @@ class AutomationAuditStore {
|
|
|
3125
3179
|
}
|
|
3126
3180
|
}
|
|
3127
3181
|
|
|
3182
|
+
class WebhookServer {
|
|
3183
|
+
server = null;
|
|
3184
|
+
port = 0;
|
|
3185
|
+
onSessionStarted = null;
|
|
3186
|
+
/**
|
|
3187
|
+
* Start the HTTP server on a random available port.
|
|
3188
|
+
*/
|
|
3189
|
+
async start() {
|
|
3190
|
+
return new Promise((resolve, reject) => {
|
|
3191
|
+
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
3192
|
+
this.server.on("error", (err) => {
|
|
3193
|
+
logger.debug(`[WEBHOOK-SERVER] Error: ${err.message}`);
|
|
3194
|
+
reject(err);
|
|
3195
|
+
});
|
|
3196
|
+
this.server.listen(0, "127.0.0.1", () => {
|
|
3197
|
+
const addr = this.server.address();
|
|
3198
|
+
if (addr && typeof addr === "object") {
|
|
3199
|
+
this.port = addr.port;
|
|
3200
|
+
logger.debug(`[WEBHOOK-SERVER] Listening on 127.0.0.1:${this.port}`);
|
|
3201
|
+
resolve(this.port);
|
|
3202
|
+
} else {
|
|
3203
|
+
reject(new Error("Failed to get server address"));
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
/**
|
|
3209
|
+
* Set the callback for session-started events.
|
|
3210
|
+
*/
|
|
3211
|
+
setSessionStartedHandler(handler) {
|
|
3212
|
+
this.onSessionStarted = handler;
|
|
3213
|
+
}
|
|
3214
|
+
/**
|
|
3215
|
+
* Get the port the server is listening on.
|
|
3216
|
+
*/
|
|
3217
|
+
getPort() {
|
|
3218
|
+
return this.port;
|
|
3219
|
+
}
|
|
3220
|
+
/**
|
|
3221
|
+
* Stop the server.
|
|
3222
|
+
*/
|
|
3223
|
+
shutdown() {
|
|
3224
|
+
if (this.server) {
|
|
3225
|
+
this.server.close();
|
|
3226
|
+
this.server = null;
|
|
3227
|
+
logger.debug("[WEBHOOK-SERVER] Shutdown");
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
// -----------------------------------------------------------------------
|
|
3231
|
+
// Internal
|
|
3232
|
+
// -----------------------------------------------------------------------
|
|
3233
|
+
handleRequest(req, res) {
|
|
3234
|
+
if (req.method === "POST" && req.url === "/session-started") {
|
|
3235
|
+
this.handleSessionStarted(req, res);
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/health")) {
|
|
3239
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3240
|
+
res.end(JSON.stringify({ status: "ok", port: this.port }));
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
res.writeHead(404);
|
|
3244
|
+
res.end("Not Found");
|
|
3245
|
+
}
|
|
3246
|
+
handleSessionStarted(req, res) {
|
|
3247
|
+
let body = "";
|
|
3248
|
+
req.on("data", (chunk) => {
|
|
3249
|
+
body += chunk.toString();
|
|
3250
|
+
});
|
|
3251
|
+
req.on("end", () => {
|
|
3252
|
+
try {
|
|
3253
|
+
const parsed = JSON.parse(body);
|
|
3254
|
+
if (!parsed.sessionId || typeof parsed.sessionId !== "string") {
|
|
3255
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3256
|
+
res.end(JSON.stringify({ error: "sessionId is required" }));
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
const hostPid = typeof parsed.metadata?.hostPid === "number" ? parsed.metadata.hostPid : void 0;
|
|
3260
|
+
logger.debug(`[WEBHOOK-SERVER] Session started: ${parsed.sessionId} (hostPid=${hostPid})`);
|
|
3261
|
+
this.onSessionStarted?.(
|
|
3262
|
+
parsed.sessionId,
|
|
3263
|
+
parsed.metadata ?? {},
|
|
3264
|
+
hostPid
|
|
3265
|
+
);
|
|
3266
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3267
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
3268
|
+
} catch (err) {
|
|
3269
|
+
logger.debug(`[WEBHOOK-SERVER] Parse error: ${err}`);
|
|
3270
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3271
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3272
|
+
}
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3128
3277
|
function pidFilePath(homeDir) {
|
|
3129
3278
|
return node_path.join(homeDir, "agent-daemon.pid");
|
|
3130
3279
|
}
|
|
@@ -3199,6 +3348,26 @@ async function startDaemon(options) {
|
|
|
3199
3348
|
});
|
|
3200
3349
|
const guardian = new GuardianSessionRegistry();
|
|
3201
3350
|
const loopCoordinator = new AgentLoopCoordinator(scheduler, config.serverUrl, creds.token, guardian);
|
|
3351
|
+
enablePersistence(node_path.join(config.homeDir, "agent-tracked-sessions.json"));
|
|
3352
|
+
const webhookServer = new WebhookServer();
|
|
3353
|
+
const webhookPort = await webhookServer.start();
|
|
3354
|
+
webhookServer.setSessionStartedHandler((sessionId, _metadata, hostPid) => {
|
|
3355
|
+
if (hostPid) {
|
|
3356
|
+
const tracked = getTrackedSession(hostPid);
|
|
3357
|
+
if (tracked) {
|
|
3358
|
+
tracked.happySessionId = sessionId;
|
|
3359
|
+
logger.debug(`[DAEMON] Session ${sessionId} linked to PID ${hostPid}`);
|
|
3360
|
+
if (tracked.automationContext?.kind === "agent_loop") {
|
|
3361
|
+
guardian.remember(sessionId, {
|
|
3362
|
+
loopId: tracked.automationContext.trigger?.split(":")[1],
|
|
3363
|
+
projectId: tracked.automationContext.projectId
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
});
|
|
3369
|
+
process.env.HAPPY_DAEMON_HTTP_PORT = String(webhookPort);
|
|
3370
|
+
console.log(`Webhook server: 127.0.0.1:${webhookPort}`);
|
|
3202
3371
|
client.setTailscaleInfo(fullTailscale);
|
|
3203
3372
|
client.enableAutomation(config.serverUrl, creds.token, scheduler, loopCoordinator, auditStore);
|
|
3204
3373
|
loopCoordinator.start();
|
|
@@ -3212,6 +3381,7 @@ async function startDaemon(options) {
|
|
|
3212
3381
|
logger.debug(`[DAEMON] Received ${signal}, shutting down...`);
|
|
3213
3382
|
console.log(`
|
|
3214
3383
|
Received ${signal}, shutting down...`);
|
|
3384
|
+
webhookServer.shutdown();
|
|
3215
3385
|
loopCoordinator.shutdown();
|
|
3216
3386
|
scheduler.shutdown();
|
|
3217
3387
|
client.shutdown();
|
package/dist/index.mjs
CHANGED
|
@@ -12,11 +12,12 @@ import { exec, execFile, spawn } from 'child_process';
|
|
|
12
12
|
import { promisify } from 'util';
|
|
13
13
|
import { readFile, mkdir, writeFile, readdir, stat } from 'fs/promises';
|
|
14
14
|
import { createHash as createHash$1, randomUUID } from 'crypto';
|
|
15
|
-
import { join as join$1, resolve } from 'path';
|
|
16
|
-
import { realpathSync } from 'fs';
|
|
15
|
+
import { join as join$1, resolve, dirname as dirname$1 } from 'path';
|
|
16
|
+
import { realpathSync, readFileSync as readFileSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1 } from 'fs';
|
|
17
17
|
import { tmpdir } from 'os';
|
|
18
|
+
import { createServer } from 'http';
|
|
18
19
|
|
|
19
|
-
var version = "0.5.
|
|
20
|
+
var version = "0.5.1";
|
|
20
21
|
|
|
21
22
|
function loadConfig() {
|
|
22
23
|
const serverUrl = (process.env.HAPPY_SERVER_URL ?? "https://happyserve.xycloud.info").replace(/\/+$/, "");
|
|
@@ -1836,12 +1837,15 @@ function isNotFound(err) {
|
|
|
1836
1837
|
}
|
|
1837
1838
|
|
|
1838
1839
|
const pidToSession = /* @__PURE__ */ new Map();
|
|
1840
|
+
let persistPath = null;
|
|
1839
1841
|
function trackSession(session) {
|
|
1840
1842
|
pidToSession.set(session.pid, session);
|
|
1843
|
+
flush();
|
|
1841
1844
|
}
|
|
1842
1845
|
function untrackSession(pid) {
|
|
1843
1846
|
const session = pidToSession.get(pid);
|
|
1844
1847
|
pidToSession.delete(pid);
|
|
1848
|
+
flush();
|
|
1845
1849
|
return session;
|
|
1846
1850
|
}
|
|
1847
1851
|
function getTrackedSession(pid) {
|
|
@@ -1853,9 +1857,59 @@ function getAllTrackedSessions() {
|
|
|
1853
1857
|
function getTrackedSessionCount() {
|
|
1854
1858
|
return pidToSession.size;
|
|
1855
1859
|
}
|
|
1860
|
+
function enablePersistence(filePath) {
|
|
1861
|
+
persistPath = filePath;
|
|
1862
|
+
load();
|
|
1863
|
+
}
|
|
1864
|
+
function load() {
|
|
1865
|
+
if (!persistPath) return;
|
|
1866
|
+
try {
|
|
1867
|
+
const raw = readFileSync$1(persistPath, "utf-8");
|
|
1868
|
+
const entries = JSON.parse(raw);
|
|
1869
|
+
let recovered = 0;
|
|
1870
|
+
for (const entry of entries) {
|
|
1871
|
+
try {
|
|
1872
|
+
process.kill(entry.pid, 0);
|
|
1873
|
+
} catch {
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
pidToSession.set(entry.pid, {
|
|
1877
|
+
pid: entry.pid,
|
|
1878
|
+
directory: entry.directory,
|
|
1879
|
+
startedAt: entry.startedAt,
|
|
1880
|
+
happySessionId: entry.happySessionId,
|
|
1881
|
+
lastActivityAt: entry.lastActivityAt,
|
|
1882
|
+
automationContext: entry.automationContext
|
|
1883
|
+
});
|
|
1884
|
+
recovered++;
|
|
1885
|
+
}
|
|
1886
|
+
if (recovered > 0) {
|
|
1887
|
+
logger.debug(`[TRACKED] Recovered ${recovered} sessions from ${persistPath}`);
|
|
1888
|
+
}
|
|
1889
|
+
} catch {
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
function flush() {
|
|
1893
|
+
if (!persistPath) return;
|
|
1894
|
+
try {
|
|
1895
|
+
const entries = [...pidToSession.values()].map((s) => ({
|
|
1896
|
+
pid: s.pid,
|
|
1897
|
+
directory: s.directory,
|
|
1898
|
+
startedAt: s.startedAt,
|
|
1899
|
+
happySessionId: s.happySessionId,
|
|
1900
|
+
lastActivityAt: s.lastActivityAt,
|
|
1901
|
+
automationContext: s.automationContext
|
|
1902
|
+
}));
|
|
1903
|
+
mkdirSync$1(dirname$1(persistPath), { recursive: true });
|
|
1904
|
+
writeFileSync$1(persistPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
1905
|
+
} catch (err) {
|
|
1906
|
+
logger.debug(`[TRACKED] Failed to persist: ${err}`);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1856
1909
|
|
|
1857
1910
|
var trackedSessions = /*#__PURE__*/Object.freeze({
|
|
1858
1911
|
__proto__: null,
|
|
1912
|
+
enablePersistence: enablePersistence,
|
|
1859
1913
|
getAllTrackedSessions: getAllTrackedSessions,
|
|
1860
1914
|
getTrackedSession: getTrackedSession,
|
|
1861
1915
|
getTrackedSessionCount: getTrackedSessionCount,
|
|
@@ -3123,6 +3177,101 @@ class AutomationAuditStore {
|
|
|
3123
3177
|
}
|
|
3124
3178
|
}
|
|
3125
3179
|
|
|
3180
|
+
class WebhookServer {
|
|
3181
|
+
server = null;
|
|
3182
|
+
port = 0;
|
|
3183
|
+
onSessionStarted = null;
|
|
3184
|
+
/**
|
|
3185
|
+
* Start the HTTP server on a random available port.
|
|
3186
|
+
*/
|
|
3187
|
+
async start() {
|
|
3188
|
+
return new Promise((resolve, reject) => {
|
|
3189
|
+
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
3190
|
+
this.server.on("error", (err) => {
|
|
3191
|
+
logger.debug(`[WEBHOOK-SERVER] Error: ${err.message}`);
|
|
3192
|
+
reject(err);
|
|
3193
|
+
});
|
|
3194
|
+
this.server.listen(0, "127.0.0.1", () => {
|
|
3195
|
+
const addr = this.server.address();
|
|
3196
|
+
if (addr && typeof addr === "object") {
|
|
3197
|
+
this.port = addr.port;
|
|
3198
|
+
logger.debug(`[WEBHOOK-SERVER] Listening on 127.0.0.1:${this.port}`);
|
|
3199
|
+
resolve(this.port);
|
|
3200
|
+
} else {
|
|
3201
|
+
reject(new Error("Failed to get server address"));
|
|
3202
|
+
}
|
|
3203
|
+
});
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Set the callback for session-started events.
|
|
3208
|
+
*/
|
|
3209
|
+
setSessionStartedHandler(handler) {
|
|
3210
|
+
this.onSessionStarted = handler;
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Get the port the server is listening on.
|
|
3214
|
+
*/
|
|
3215
|
+
getPort() {
|
|
3216
|
+
return this.port;
|
|
3217
|
+
}
|
|
3218
|
+
/**
|
|
3219
|
+
* Stop the server.
|
|
3220
|
+
*/
|
|
3221
|
+
shutdown() {
|
|
3222
|
+
if (this.server) {
|
|
3223
|
+
this.server.close();
|
|
3224
|
+
this.server = null;
|
|
3225
|
+
logger.debug("[WEBHOOK-SERVER] Shutdown");
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
// -----------------------------------------------------------------------
|
|
3229
|
+
// Internal
|
|
3230
|
+
// -----------------------------------------------------------------------
|
|
3231
|
+
handleRequest(req, res) {
|
|
3232
|
+
if (req.method === "POST" && req.url === "/session-started") {
|
|
3233
|
+
this.handleSessionStarted(req, res);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/health")) {
|
|
3237
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3238
|
+
res.end(JSON.stringify({ status: "ok", port: this.port }));
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
res.writeHead(404);
|
|
3242
|
+
res.end("Not Found");
|
|
3243
|
+
}
|
|
3244
|
+
handleSessionStarted(req, res) {
|
|
3245
|
+
let body = "";
|
|
3246
|
+
req.on("data", (chunk) => {
|
|
3247
|
+
body += chunk.toString();
|
|
3248
|
+
});
|
|
3249
|
+
req.on("end", () => {
|
|
3250
|
+
try {
|
|
3251
|
+
const parsed = JSON.parse(body);
|
|
3252
|
+
if (!parsed.sessionId || typeof parsed.sessionId !== "string") {
|
|
3253
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3254
|
+
res.end(JSON.stringify({ error: "sessionId is required" }));
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
const hostPid = typeof parsed.metadata?.hostPid === "number" ? parsed.metadata.hostPid : void 0;
|
|
3258
|
+
logger.debug(`[WEBHOOK-SERVER] Session started: ${parsed.sessionId} (hostPid=${hostPid})`);
|
|
3259
|
+
this.onSessionStarted?.(
|
|
3260
|
+
parsed.sessionId,
|
|
3261
|
+
parsed.metadata ?? {},
|
|
3262
|
+
hostPid
|
|
3263
|
+
);
|
|
3264
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3265
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
3266
|
+
} catch (err) {
|
|
3267
|
+
logger.debug(`[WEBHOOK-SERVER] Parse error: ${err}`);
|
|
3268
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3269
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3270
|
+
}
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3126
3275
|
function pidFilePath(homeDir) {
|
|
3127
3276
|
return join(homeDir, "agent-daemon.pid");
|
|
3128
3277
|
}
|
|
@@ -3197,6 +3346,26 @@ async function startDaemon(options) {
|
|
|
3197
3346
|
});
|
|
3198
3347
|
const guardian = new GuardianSessionRegistry();
|
|
3199
3348
|
const loopCoordinator = new AgentLoopCoordinator(scheduler, config.serverUrl, creds.token, guardian);
|
|
3349
|
+
enablePersistence(join(config.homeDir, "agent-tracked-sessions.json"));
|
|
3350
|
+
const webhookServer = new WebhookServer();
|
|
3351
|
+
const webhookPort = await webhookServer.start();
|
|
3352
|
+
webhookServer.setSessionStartedHandler((sessionId, _metadata, hostPid) => {
|
|
3353
|
+
if (hostPid) {
|
|
3354
|
+
const tracked = getTrackedSession(hostPid);
|
|
3355
|
+
if (tracked) {
|
|
3356
|
+
tracked.happySessionId = sessionId;
|
|
3357
|
+
logger.debug(`[DAEMON] Session ${sessionId} linked to PID ${hostPid}`);
|
|
3358
|
+
if (tracked.automationContext?.kind === "agent_loop") {
|
|
3359
|
+
guardian.remember(sessionId, {
|
|
3360
|
+
loopId: tracked.automationContext.trigger?.split(":")[1],
|
|
3361
|
+
projectId: tracked.automationContext.projectId
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
});
|
|
3367
|
+
process.env.HAPPY_DAEMON_HTTP_PORT = String(webhookPort);
|
|
3368
|
+
console.log(`Webhook server: 127.0.0.1:${webhookPort}`);
|
|
3200
3369
|
client.setTailscaleInfo(fullTailscale);
|
|
3201
3370
|
client.enableAutomation(config.serverUrl, creds.token, scheduler, loopCoordinator, auditStore);
|
|
3202
3371
|
loopCoordinator.start();
|
|
@@ -3210,6 +3379,7 @@ async function startDaemon(options) {
|
|
|
3210
3379
|
logger.debug(`[DAEMON] Received ${signal}, shutting down...`);
|
|
3211
3380
|
console.log(`
|
|
3212
3381
|
Received ${signal}, shutting down...`);
|
|
3382
|
+
webhookServer.shutdown();
|
|
3213
3383
|
loopCoordinator.shutdown();
|
|
3214
3384
|
scheduler.shutdown();
|
|
3215
3385
|
client.shutdown();
|