@a5c-ai/agent-runtime 5.0.1-staging.04ca6ab00d21

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 (170) hide show
  1. package/README.md +23 -0
  2. package/dist/apiResult.d.ts +19 -0
  3. package/dist/apiResult.d.ts.map +1 -0
  4. package/dist/apiResult.js +16 -0
  5. package/dist/background/state.d.ts +14 -0
  6. package/dist/background/state.d.ts.map +1 -0
  7. package/dist/background/state.js +25 -0
  8. package/dist/backgroundProcessRegistry.d.ts +66 -0
  9. package/dist/backgroundProcessRegistry.d.ts.map +1 -0
  10. package/dist/backgroundProcessRegistry.js +202 -0
  11. package/dist/cost/claudeCodeParser.d.ts +81 -0
  12. package/dist/cost/claudeCodeParser.d.ts.map +1 -0
  13. package/dist/cost/claudeCodeParser.js +232 -0
  14. package/dist/cost/collector.d.ts +42 -0
  15. package/dist/cost/collector.d.ts.map +1 -0
  16. package/dist/cost/collector.js +105 -0
  17. package/dist/cost/effectCost.d.ts +23 -0
  18. package/dist/cost/effectCost.d.ts.map +1 -0
  19. package/dist/cost/effectCost.js +26 -0
  20. package/dist/cost/index.d.ts +19 -0
  21. package/dist/cost/index.d.ts.map +1 -0
  22. package/dist/cost/index.js +39 -0
  23. package/dist/cost/journal.d.ts +40 -0
  24. package/dist/cost/journal.d.ts.map +1 -0
  25. package/dist/cost/journal.js +137 -0
  26. package/dist/cost/types.d.ts +164 -0
  27. package/dist/cost/types.d.ts.map +1 -0
  28. package/dist/cost/types.js +228 -0
  29. package/dist/daemon/automationExecutor.d.ts +16 -0
  30. package/dist/daemon/automationExecutor.d.ts.map +1 -0
  31. package/dist/daemon/automationExecutor.js +222 -0
  32. package/dist/daemon/config.d.ts +8 -0
  33. package/dist/daemon/config.d.ts.map +1 -0
  34. package/dist/daemon/config.js +209 -0
  35. package/dist/daemon/daemonLog.d.ts +13 -0
  36. package/dist/daemon/daemonLog.d.ts.map +1 -0
  37. package/dist/daemon/daemonLog.js +64 -0
  38. package/dist/daemon/fileWatcher.d.ts +9 -0
  39. package/dist/daemon/fileWatcher.d.ts.map +1 -0
  40. package/dist/daemon/fileWatcher.js +141 -0
  41. package/dist/daemon/index.d.ts +13 -0
  42. package/dist/daemon/index.d.ts.map +1 -0
  43. package/dist/daemon/index.js +22 -0
  44. package/dist/daemon/lifecycle.d.ts +12 -0
  45. package/dist/daemon/lifecycle.d.ts.map +1 -0
  46. package/dist/daemon/lifecycle.js +257 -0
  47. package/dist/daemon/loop.d.ts +21 -0
  48. package/dist/daemon/loop.d.ts.map +1 -0
  49. package/dist/daemon/loop.js +196 -0
  50. package/dist/daemon/timerScheduler.d.ts +13 -0
  51. package/dist/daemon/timerScheduler.d.ts.map +1 -0
  52. package/dist/daemon/timerScheduler.js +122 -0
  53. package/dist/daemon/types.d.ts +93 -0
  54. package/dist/daemon/types.d.ts.map +1 -0
  55. package/dist/daemon/types.js +25 -0
  56. package/dist/daemon/webhookListener.d.ts +6 -0
  57. package/dist/daemon/webhookListener.d.ts.map +1 -0
  58. package/dist/daemon/webhookListener.js +110 -0
  59. package/dist/execution/index.d.ts +8 -0
  60. package/dist/execution/index.d.ts.map +1 -0
  61. package/dist/execution/index.js +12 -0
  62. package/dist/execution/modes/docker.d.ts +21 -0
  63. package/dist/execution/modes/docker.d.ts.map +1 -0
  64. package/dist/execution/modes/docker.js +125 -0
  65. package/dist/execution/modes/index.d.ts +10 -0
  66. package/dist/execution/modes/index.d.ts.map +1 -0
  67. package/dist/execution/modes/index.js +14 -0
  68. package/dist/execution/modes/kubernetes.d.ts +29 -0
  69. package/dist/execution/modes/kubernetes.d.ts.map +1 -0
  70. package/dist/execution/modes/kubernetes.js +121 -0
  71. package/dist/execution/modes/local.d.ts +23 -0
  72. package/dist/execution/modes/local.d.ts.map +1 -0
  73. package/dist/execution/modes/local.js +102 -0
  74. package/dist/execution/modes/ssh.d.ts +23 -0
  75. package/dist/execution/modes/ssh.d.ts.map +1 -0
  76. package/dist/execution/modes/ssh.js +134 -0
  77. package/dist/execution/provider.d.ts +32 -0
  78. package/dist/execution/provider.d.ts.map +1 -0
  79. package/dist/execution/provider.js +90 -0
  80. package/dist/execution/types.d.ts +105 -0
  81. package/dist/execution/types.d.ts.map +1 -0
  82. package/dist/execution/types.js +9 -0
  83. package/dist/index.d.ts +11 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +42 -0
  86. package/dist/observability/health.d.ts +19 -0
  87. package/dist/observability/health.d.ts.map +1 -0
  88. package/dist/observability/health.js +129 -0
  89. package/dist/observability/index.d.ts +6 -0
  90. package/dist/observability/index.d.ts.map +1 -0
  91. package/dist/observability/index.js +20 -0
  92. package/dist/observability/runStatus.d.ts +44 -0
  93. package/dist/observability/runStatus.d.ts.map +1 -0
  94. package/dist/observability/runStatus.js +169 -0
  95. package/dist/observability/timeline.d.ts +11 -0
  96. package/dist/observability/timeline.d.ts.map +1 -0
  97. package/dist/observability/timeline.js +176 -0
  98. package/dist/observability/types.d.ts +62 -0
  99. package/dist/observability/types.d.ts.map +1 -0
  100. package/dist/observability/types.js +8 -0
  101. package/dist/observability/webhooks.d.ts +68 -0
  102. package/dist/observability/webhooks.d.ts.map +1 -0
  103. package/dist/observability/webhooks.js +132 -0
  104. package/dist/resources/budget-tracker.d.ts +56 -0
  105. package/dist/resources/budget-tracker.d.ts.map +1 -0
  106. package/dist/resources/budget-tracker.js +131 -0
  107. package/dist/resources/concurrency-guard.d.ts +55 -0
  108. package/dist/resources/concurrency-guard.d.ts.map +1 -0
  109. package/dist/resources/concurrency-guard.js +132 -0
  110. package/dist/resources/index.d.ts +12 -0
  111. package/dist/resources/index.d.ts.map +1 -0
  112. package/dist/resources/index.js +20 -0
  113. package/dist/resources/manager.d.ts +49 -0
  114. package/dist/resources/manager.d.ts.map +1 -0
  115. package/dist/resources/manager.js +111 -0
  116. package/dist/resources/timeout-cascade.d.ts +56 -0
  117. package/dist/resources/timeout-cascade.d.ts.map +1 -0
  118. package/dist/resources/timeout-cascade.js +145 -0
  119. package/dist/resources/types.d.ts +108 -0
  120. package/dist/resources/types.d.ts.map +1 -0
  121. package/dist/resources/types.js +9 -0
  122. package/dist/session/context.d.ts +22 -0
  123. package/dist/session/context.d.ts.map +1 -0
  124. package/dist/session/context.js +113 -0
  125. package/dist/session/continuityState.d.ts +39 -0
  126. package/dist/session/continuityState.d.ts.map +1 -0
  127. package/dist/session/continuityState.js +164 -0
  128. package/dist/session/cost.d.ts +63 -0
  129. package/dist/session/cost.d.ts.map +1 -0
  130. package/dist/session/cost.js +194 -0
  131. package/dist/session/discovery.d.ts +22 -0
  132. package/dist/session/discovery.d.ts.map +1 -0
  133. package/dist/session/discovery.js +35 -0
  134. package/dist/session/history.d.ts +30 -0
  135. package/dist/session/history.d.ts.map +1 -0
  136. package/dist/session/history.js +143 -0
  137. package/dist/session/index.d.ts +20 -0
  138. package/dist/session/index.d.ts.map +1 -0
  139. package/dist/session/index.js +78 -0
  140. package/dist/session/memoryExtraction.d.ts +65 -0
  141. package/dist/session/memoryExtraction.d.ts.map +1 -0
  142. package/dist/session/memoryExtraction.js +201 -0
  143. package/dist/session/parse.d.ts +45 -0
  144. package/dist/session/parse.d.ts.map +1 -0
  145. package/dist/session/parse.js +170 -0
  146. package/dist/session/persistence.d.ts +46 -0
  147. package/dist/session/persistence.d.ts.map +1 -0
  148. package/dist/session/persistence.js +180 -0
  149. package/dist/session/types.d.ts +267 -0
  150. package/dist/session/types.d.ts.map +1 -0
  151. package/dist/session/types.js +45 -0
  152. package/dist/session/write.d.ts +61 -0
  153. package/dist/session/write.d.ts.map +1 -0
  154. package/dist/session/write.js +213 -0
  155. package/dist/telemetry/audit-log.d.ts +56 -0
  156. package/dist/telemetry/audit-log.d.ts.map +1 -0
  157. package/dist/telemetry/audit-log.js +59 -0
  158. package/dist/telemetry/index.d.ts +9 -0
  159. package/dist/telemetry/index.d.ts.map +1 -0
  160. package/dist/telemetry/index.js +15 -0
  161. package/dist/telemetry/provider.d.ts +39 -0
  162. package/dist/telemetry/provider.d.ts.map +1 -0
  163. package/dist/telemetry/provider.js +91 -0
  164. package/dist/telemetry/span-tree.d.ts +46 -0
  165. package/dist/telemetry/span-tree.d.ts.map +1 -0
  166. package/dist/telemetry/span-tree.js +93 -0
  167. package/dist/telemetry/types.d.ts +85 -0
  168. package/dist/telemetry/types.d.ts.map +1 -0
  169. package/dist/telemetry/types.js +21 -0
  170. package/package.json +90 -0
@@ -0,0 +1,13 @@
1
+ export { startDaemon, stopDaemon, getDaemonStatus } from "./lifecycle";
2
+ export { loadDaemonConfig, writeDaemonConfig } from "./config";
3
+ export { createFileWatcher } from "./fileWatcher";
4
+ export { createWebhookListener } from "./webhookListener";
5
+ export { createTimerScheduler } from "./timerScheduler";
6
+ export { runDaemonLoop, readDaemonLoopStatus } from "./loop";
7
+ export { appendDaemonLog, readDaemonLog } from "./daemonLog";
8
+ export type { DaemonConfig, DaemonStartOptions, DaemonStartOutput, DaemonStopOptions, DaemonStopOutput, DaemonStatusOptions, DaemonStatusOutput, DaemonMetadata, TriggerConfig, TriggerEvent, FileTriggerEvent, AutomationTriggerEvent, FileTriggerConfig, FileWatcherHandle, WebhookListenerOptions, WebhookListenerHandle, TriggerCallback, } from "./types";
9
+ export type { TimerSchedulerHandle } from "./timerScheduler";
10
+ export type { DaemonLoopOptions, DaemonLoopStatus } from "./loop";
11
+ export type { DaemonLogEntry } from "./daemonLog";
12
+ export type { ApiResult } from "../apiResult";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAClE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readDaemonLog = exports.appendDaemonLog = exports.readDaemonLoopStatus = exports.runDaemonLoop = exports.createTimerScheduler = exports.createWebhookListener = exports.createFileWatcher = exports.writeDaemonConfig = exports.loadDaemonConfig = exports.getDaemonStatus = exports.stopDaemon = exports.startDaemon = void 0;
4
+ var lifecycle_1 = require("./lifecycle");
5
+ Object.defineProperty(exports, "startDaemon", { enumerable: true, get: function () { return lifecycle_1.startDaemon; } });
6
+ Object.defineProperty(exports, "stopDaemon", { enumerable: true, get: function () { return lifecycle_1.stopDaemon; } });
7
+ Object.defineProperty(exports, "getDaemonStatus", { enumerable: true, get: function () { return lifecycle_1.getDaemonStatus; } });
8
+ var config_1 = require("./config");
9
+ Object.defineProperty(exports, "loadDaemonConfig", { enumerable: true, get: function () { return config_1.loadDaemonConfig; } });
10
+ Object.defineProperty(exports, "writeDaemonConfig", { enumerable: true, get: function () { return config_1.writeDaemonConfig; } });
11
+ var fileWatcher_1 = require("./fileWatcher");
12
+ Object.defineProperty(exports, "createFileWatcher", { enumerable: true, get: function () { return fileWatcher_1.createFileWatcher; } });
13
+ var webhookListener_1 = require("./webhookListener");
14
+ Object.defineProperty(exports, "createWebhookListener", { enumerable: true, get: function () { return webhookListener_1.createWebhookListener; } });
15
+ var timerScheduler_1 = require("./timerScheduler");
16
+ Object.defineProperty(exports, "createTimerScheduler", { enumerable: true, get: function () { return timerScheduler_1.createTimerScheduler; } });
17
+ var loop_1 = require("./loop");
18
+ Object.defineProperty(exports, "runDaemonLoop", { enumerable: true, get: function () { return loop_1.runDaemonLoop; } });
19
+ Object.defineProperty(exports, "readDaemonLoopStatus", { enumerable: true, get: function () { return loop_1.readDaemonLoopStatus; } });
20
+ var daemonLog_1 = require("./daemonLog");
21
+ Object.defineProperty(exports, "appendDaemonLog", { enumerable: true, get: function () { return daemonLog_1.appendDaemonLog; } });
22
+ Object.defineProperty(exports, "readDaemonLog", { enumerable: true, get: function () { return daemonLog_1.readDaemonLog; } });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * GAP-REMOTE-001: Daemon Lifecycle — start/stop/status management.
3
+ *
4
+ * In foreground mode, the daemon runs in the current process (for dev/debug).
5
+ * In background mode, it spawns a detached child process.
6
+ */
7
+ import type { ApiResult } from "../apiResult";
8
+ import type { DaemonStartOptions, DaemonStartOutput, DaemonStopOptions, DaemonStopOutput, DaemonStatusOptions, DaemonStatusOutput } from "./types";
9
+ export declare function startDaemon(options: DaemonStartOptions): Promise<ApiResult<DaemonStartOutput>>;
10
+ export declare function stopDaemon(options: DaemonStopOptions): Promise<ApiResult<DaemonStopOutput>>;
11
+ export declare function getDaemonStatus(options: DaemonStatusOptions): Promise<ApiResult<DaemonStatusOutput>>;
12
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/daemon/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,SAAS,CAAC;AAwDjB,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAoEvC;AAID,wBAAsB,UAAU,CAC9B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAwEtC;AAID,wBAAsB,eAAe,CACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC,CAmCxC"}
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ /**
3
+ * GAP-REMOTE-001: Daemon Lifecycle — start/stop/status management.
4
+ *
5
+ * In foreground mode, the daemon runs in the current process (for dev/debug).
6
+ * In background mode, it spawns a detached child process.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.startDaemon = startDaemon;
43
+ exports.stopDaemon = stopDaemon;
44
+ exports.getDaemonStatus = getDaemonStatus;
45
+ const node_fs_1 = require("node:fs");
46
+ const node_child_process_1 = require("node:child_process");
47
+ const path = __importStar(require("node:path"));
48
+ const apiResult_1 = require("../apiResult");
49
+ const loop_1 = require("./loop");
50
+ const daemonLog_1 = require("./daemonLog");
51
+ const DEFAULT_GRACE_PERIOD_MS = 10_000;
52
+ // ── Atomic write helper ─────────────────────────────────────────────────────
53
+ async function atomicWrite(filePath, content) {
54
+ const dir = path.dirname(filePath);
55
+ await node_fs_1.promises.mkdir(dir, { recursive: true });
56
+ const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
57
+ await node_fs_1.promises.writeFile(tmpPath, content, "utf-8");
58
+ await node_fs_1.promises.rename(tmpPath, filePath);
59
+ }
60
+ // ── PID checking ────────────────────────────────────────────────────────────
61
+ function isProcessAlive(pid) {
62
+ try {
63
+ process.kill(pid, 0);
64
+ return true;
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ async function readPidFile(daemonDir) {
71
+ try {
72
+ const content = await node_fs_1.promises.readFile(path.join(daemonDir, "daemon.pid"), "utf-8");
73
+ const pid = parseInt(content.trim(), 10);
74
+ return isNaN(pid) ? null : pid;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ async function readDaemonMetadata(daemonDir) {
81
+ try {
82
+ const content = await node_fs_1.promises.readFile(path.join(daemonDir, "daemon.json"), "utf-8");
83
+ return JSON.parse(content);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ async function cleanupDaemonFiles(daemonDir) {
90
+ await node_fs_1.promises.unlink(path.join(daemonDir, "daemon.pid")).catch(() => { });
91
+ await node_fs_1.promises.unlink(path.join(daemonDir, "daemon.json")).catch(() => { });
92
+ }
93
+ function sleep(ms) {
94
+ return new Promise((resolve) => setTimeout(resolve, ms));
95
+ }
96
+ // ── startDaemon ─────────────────────────────────────────────────────────────
97
+ async function startDaemon(options) {
98
+ try {
99
+ const { daemonDir, workspace, foreground = false, config } = options;
100
+ // Check for existing daemon
101
+ const existingPid = await readPidFile(daemonDir);
102
+ if (existingPid !== null) {
103
+ if (isProcessAlive(existingPid)) {
104
+ return (0, apiResult_1.fail)("DAEMON_RUNNING", `Daemon already running with PID ${existingPid}`);
105
+ }
106
+ // Stale PID — clean up both files
107
+ await cleanupDaemonFiles(daemonDir);
108
+ }
109
+ await node_fs_1.promises.mkdir(daemonDir, { recursive: true });
110
+ let pid;
111
+ const startedAt = new Date().toISOString();
112
+ const maxConcurrentRuns = config?.maxConcurrentRuns ?? 4;
113
+ if (foreground) {
114
+ pid = process.pid;
115
+ }
116
+ else {
117
+ // Spawn detached child process running daemon:run
118
+ const child = (0, node_child_process_1.spawn)(process.execPath, [process.argv[1], "daemon:run", "--daemon-dir", daemonDir], {
119
+ detached: true,
120
+ stdio: "ignore",
121
+ cwd: workspace,
122
+ });
123
+ child.unref();
124
+ if (child.pid == null) {
125
+ return (0, apiResult_1.fail)("SPAWN_FAILED", "Failed to spawn daemon process — no PID returned");
126
+ }
127
+ pid = child.pid;
128
+ }
129
+ // Write PID file atomically
130
+ await atomicWrite(path.join(daemonDir, "daemon.pid"), String(pid));
131
+ // Write daemon.json metadata atomically
132
+ const metadata = {
133
+ workspace,
134
+ startedAt,
135
+ triggers: config?.triggers ?? [],
136
+ maxConcurrentRuns,
137
+ pid,
138
+ };
139
+ await atomicWrite(path.join(daemonDir, "daemon.json"), JSON.stringify(metadata, null, 2));
140
+ // Log daemon start
141
+ await (0, daemonLog_1.appendDaemonLog)(daemonDir, {
142
+ timestamp: startedAt,
143
+ event: "DAEMON_STARTED",
144
+ data: { pid, workspace, foreground },
145
+ });
146
+ return (0, apiResult_1.ok)({
147
+ pid,
148
+ daemonDir,
149
+ startedAt,
150
+ });
151
+ }
152
+ catch (error) {
153
+ const msg = error instanceof Error ? error.message : String(error);
154
+ return (0, apiResult_1.fail)("INTERNAL_ERROR", msg);
155
+ }
156
+ }
157
+ // ── stopDaemon ──────────────────────────────────────────────────────────────
158
+ async function stopDaemon(options) {
159
+ try {
160
+ const { daemonDir, gracePeriodMs = DEFAULT_GRACE_PERIOD_MS } = options;
161
+ const pid = await readPidFile(daemonDir);
162
+ if (pid === null) {
163
+ return (0, apiResult_1.fail)("DAEMON_NOT_RUNNING", "No daemon PID file found");
164
+ }
165
+ const stoppedAt = new Date().toISOString();
166
+ if (!isProcessAlive(pid)) {
167
+ // PID exists but process is dead — clean up stale files
168
+ await cleanupDaemonFiles(daemonDir);
169
+ return (0, apiResult_1.ok)({ pid, stoppedAt });
170
+ }
171
+ // For foreground mode (same process), just clean up files
172
+ if (pid === process.pid) {
173
+ await cleanupDaemonFiles(daemonDir);
174
+ await (0, daemonLog_1.appendDaemonLog)(daemonDir, {
175
+ timestamp: stoppedAt,
176
+ event: "DAEMON_STOPPED",
177
+ data: { pid, reason: "foreground-stop" },
178
+ }).catch(() => { });
179
+ return (0, apiResult_1.ok)({ pid, stoppedAt });
180
+ }
181
+ // Send SIGTERM first
182
+ try {
183
+ process.kill(pid, "SIGTERM");
184
+ }
185
+ catch {
186
+ // Process may have exited between check and kill
187
+ await cleanupDaemonFiles(daemonDir);
188
+ return (0, apiResult_1.ok)({ pid, stoppedAt });
189
+ }
190
+ // Wait for graceful shutdown with polling
191
+ const pollInterval = 100;
192
+ const maxPolls = Math.ceil(gracePeriodMs / pollInterval);
193
+ for (let i = 0; i < maxPolls; i++) {
194
+ await sleep(pollInterval);
195
+ if (!isProcessAlive(pid)) {
196
+ await cleanupDaemonFiles(daemonDir);
197
+ await (0, daemonLog_1.appendDaemonLog)(daemonDir, {
198
+ timestamp: new Date().toISOString(),
199
+ event: "DAEMON_STOPPED",
200
+ data: { pid, reason: "sigterm" },
201
+ }).catch(() => { });
202
+ return (0, apiResult_1.ok)({ pid, stoppedAt: new Date().toISOString() });
203
+ }
204
+ }
205
+ // Escalate to SIGKILL after grace period
206
+ try {
207
+ process.kill(pid, "SIGKILL");
208
+ }
209
+ catch {
210
+ // Already exited
211
+ }
212
+ await cleanupDaemonFiles(daemonDir);
213
+ await (0, daemonLog_1.appendDaemonLog)(daemonDir, {
214
+ timestamp: new Date().toISOString(),
215
+ event: "DAEMON_KILLED",
216
+ data: { pid, reason: "sigkill-after-grace-period", gracePeriodMs },
217
+ }).catch(() => { });
218
+ return (0, apiResult_1.ok)({ pid, stoppedAt: new Date().toISOString() });
219
+ }
220
+ catch (error) {
221
+ const msg = error instanceof Error ? error.message : String(error);
222
+ return (0, apiResult_1.fail)("INTERNAL_ERROR", msg);
223
+ }
224
+ }
225
+ // ── getDaemonStatus ─────────────────────────────────────────────────────────
226
+ async function getDaemonStatus(options) {
227
+ try {
228
+ const { daemonDir } = options;
229
+ const pid = await readPidFile(daemonDir);
230
+ if (pid === null) {
231
+ return (0, apiResult_1.ok)({ running: false });
232
+ }
233
+ if (!isProcessAlive(pid)) {
234
+ // Stale PID — clean up
235
+ await node_fs_1.promises.unlink(path.join(daemonDir, "daemon.pid")).catch(() => { });
236
+ return (0, apiResult_1.ok)({ running: false });
237
+ }
238
+ const metadata = await readDaemonMetadata(daemonDir);
239
+ const startedAt = metadata?.startedAt;
240
+ const uptime = startedAt
241
+ ? Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000)
242
+ : 0;
243
+ const loopStatus = await (0, loop_1.readDaemonLoopStatus)(daemonDir);
244
+ return (0, apiResult_1.ok)({
245
+ running: true,
246
+ pid,
247
+ uptime,
248
+ startedAt,
249
+ activeTriggers: metadata?.triggers?.length ?? 0,
250
+ pendingRuns: loopStatus?.pendingRuns ?? 0,
251
+ });
252
+ }
253
+ catch (error) {
254
+ const msg = error instanceof Error ? error.message : String(error);
255
+ return (0, apiResult_1.fail)("INTERNAL_ERROR", msg);
256
+ }
257
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * GAP-REMOTE-001: Daemon Loop — main event loop with concurrent run pool.
3
+ */
4
+ import type { DaemonConfig, TriggerCallback } from "./types";
5
+ export interface DaemonLoopOptions {
6
+ onTrigger?: TriggerCallback;
7
+ signal?: AbortSignal;
8
+ logDir?: string;
9
+ }
10
+ export interface DaemonLoopStatus {
11
+ activeRuns: number;
12
+ pendingRuns: number;
13
+ updatedAt: string;
14
+ }
15
+ export declare function runDaemonLoop(config: DaemonConfig, options?: DaemonLoopOptions): Promise<void>;
16
+ /**
17
+ * Read the daemon loop's runtime status from its status file.
18
+ * Returns null if the file doesn't exist or is unreadable.
19
+ */
20
+ export declare function readDaemonLoopStatus(daemonDir: string): Promise<DaemonLoopStatus | null>;
21
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/daemon/loop.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAY3E,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA+If;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQ9F"}
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * GAP-REMOTE-001: Daemon Loop — main event loop with concurrent run pool.
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runDaemonLoop = runDaemonLoop;
40
+ exports.readDaemonLoopStatus = readDaemonLoopStatus;
41
+ const node_fs_1 = require("node:fs");
42
+ const path = __importStar(require("node:path"));
43
+ const types_1 = require("./types");
44
+ const fileWatcher_1 = require("./fileWatcher");
45
+ const webhookListener_1 = require("./webhookListener");
46
+ const timerScheduler_1 = require("./timerScheduler");
47
+ const daemonLog_1 = require("./daemonLog");
48
+ async function runDaemonLoop(config, options) {
49
+ const maxConcurrent = config.maxConcurrentRuns ?? 4;
50
+ const handles = [];
51
+ const activeRuns = new Set();
52
+ const queue = [];
53
+ let statusWriteChain = Promise.resolve();
54
+ function scheduleStatusWrite() {
55
+ statusWriteChain = statusWriteChain.then(() => writeLoopStatus()).catch(() => {
56
+ // Status persistence is best-effort and must not surface as an
57
+ // unhandled rejection during trigger dispatch or shutdown races.
58
+ });
59
+ }
60
+ function drainQueue() {
61
+ while (queue.length > 0 && activeRuns.size < maxConcurrent) {
62
+ const next = queue.shift();
63
+ dispatchTrigger(next);
64
+ }
65
+ scheduleStatusWrite();
66
+ }
67
+ function dispatchTrigger(trigger) {
68
+ if (options?.onTrigger) {
69
+ const result = options.onTrigger(trigger);
70
+ // If onTrigger returns a Promise, track it for concurrency
71
+ if (result != null && typeof result.then === "function") {
72
+ const promise = result.then(() => {
73
+ activeRuns.delete(promise);
74
+ drainQueue();
75
+ }, () => {
76
+ activeRuns.delete(promise);
77
+ drainQueue();
78
+ });
79
+ activeRuns.add(promise);
80
+ }
81
+ }
82
+ }
83
+ async function writeLoopStatus() {
84
+ if (!options?.logDir)
85
+ return;
86
+ const statusPath = path.join(options.logDir, "daemon.status.json");
87
+ const status = {
88
+ activeRuns: activeRuns.size,
89
+ pendingRuns: queue.length,
90
+ updatedAt: new Date().toISOString(),
91
+ };
92
+ const tmpPath = `${statusPath}.tmp-${process.pid}-${Date.now()}`;
93
+ await node_fs_1.promises.writeFile(tmpPath, JSON.stringify(status), "utf-8");
94
+ await node_fs_1.promises.rename(tmpPath, statusPath);
95
+ }
96
+ const triggerCallback = (trigger) => {
97
+ // Log the activation
98
+ if (options?.logDir) {
99
+ const data = (0, types_1.isAutomationTriggerEvent)(trigger)
100
+ ? {
101
+ type: "automation",
102
+ ruleId: trigger.rule.id,
103
+ triggerType: trigger.rule.trigger.type,
104
+ projectId: trigger.rule.target.projectId,
105
+ boardProjectId: trigger.rule.target.boardProjectId,
106
+ }
107
+ : {
108
+ type: trigger.type,
109
+ processId: trigger.processId,
110
+ entrypoint: trigger.entrypoint,
111
+ };
112
+ void (0, daemonLog_1.appendDaemonLog)(options.logDir, {
113
+ timestamp: new Date().toISOString(),
114
+ event: "TRIGGER_ACTIVATED",
115
+ data,
116
+ }).catch(() => {
117
+ // Activation logging is best-effort and should never interrupt the loop.
118
+ });
119
+ }
120
+ if (activeRuns.size >= maxConcurrent) {
121
+ queue.push(trigger);
122
+ scheduleStatusWrite();
123
+ return;
124
+ }
125
+ dispatchTrigger(trigger);
126
+ scheduleStatusWrite();
127
+ };
128
+ // Set up file triggers
129
+ const fileTriggers = config.triggers
130
+ .filter(types_1.isFileTriggerConfig);
131
+ if (fileTriggers.length > 0) {
132
+ const handle = (0, fileWatcher_1.createFileWatcher)(fileTriggers, triggerCallback);
133
+ handles.push(handle);
134
+ }
135
+ // Set up webhook triggers
136
+ const webhookTriggers = config.triggers.filter(types_1.isWebhookAutomationRule);
137
+ for (const rule of webhookTriggers) {
138
+ try {
139
+ const handle = await (0, webhookListener_1.createWebhookListener)({
140
+ rule,
141
+ onTrigger: triggerCallback,
142
+ });
143
+ handles.push(handle);
144
+ }
145
+ catch {
146
+ // Port in use or other error — skip
147
+ }
148
+ }
149
+ // Set up timer/cron triggers
150
+ const timerTriggers = config.triggers.filter(types_1.isTimerAutomationRule);
151
+ if (timerTriggers.length > 0) {
152
+ const handle = (0, timerScheduler_1.createTimerScheduler)(timerTriggers, triggerCallback);
153
+ handles.push(handle);
154
+ }
155
+ // Wait for abort signal
156
+ if (options?.signal) {
157
+ await new Promise((resolve) => {
158
+ if (options.signal.aborted) {
159
+ resolve();
160
+ return;
161
+ }
162
+ options.signal.addEventListener("abort", () => resolve(), { once: true });
163
+ });
164
+ }
165
+ // Cleanup all handles
166
+ for (const handle of handles) {
167
+ if (handle.dispose)
168
+ handle.dispose();
169
+ if (handle.close)
170
+ await handle.close();
171
+ }
172
+ // Wait for active runs to finish
173
+ if (activeRuns.size > 0) {
174
+ await Promise.allSettled([...activeRuns]);
175
+ }
176
+ // Wait for any in-flight status writes then write final status
177
+ await statusWriteChain;
178
+ try {
179
+ await writeLoopStatus();
180
+ }
181
+ catch { /* directory may be gone in tests */ }
182
+ }
183
+ /**
184
+ * Read the daemon loop's runtime status from its status file.
185
+ * Returns null if the file doesn't exist or is unreadable.
186
+ */
187
+ async function readDaemonLoopStatus(daemonDir) {
188
+ try {
189
+ const statusPath = path.join(daemonDir, "daemon.status.json");
190
+ const content = await node_fs_1.promises.readFile(statusPath, "utf-8");
191
+ return JSON.parse(content);
192
+ }
193
+ catch {
194
+ return null;
195
+ }
196
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * GAP-REMOTE-001: Timer Scheduler — cron-expression-based trigger scheduling.
3
+ *
4
+ * Supports a subset of cron syntax: minute, hour, day-of-month, month, day-of-week.
5
+ * Uses setInterval to check the cron expression against current time.
6
+ */
7
+ import type { TimerAutomationRule } from "@a5c-ai/agent-comm-mux";
8
+ import type { TriggerCallback } from "./types";
9
+ export interface TimerSchedulerHandle {
10
+ dispose(): void;
11
+ }
12
+ export declare function createTimerScheduler(triggers: TimerAutomationRule[], onTrigger: TriggerCallback): TimerSchedulerHandle;
13
+ //# sourceMappingURL=timerScheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timerScheduler.d.ts","sourceRoot":"","sources":["../../src/daemon/timerScheduler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,WAAW,oBAAoB;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AAyFD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,SAAS,EAAE,eAAe,GACzB,oBAAoB,CA6BtB"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * GAP-REMOTE-001: Timer Scheduler — cron-expression-based trigger scheduling.
4
+ *
5
+ * Supports a subset of cron syntax: minute, hour, day-of-month, month, day-of-week.
6
+ * Uses setInterval to check the cron expression against current time.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createTimerScheduler = createTimerScheduler;
10
+ /**
11
+ * Parse a simple 5-field cron expression into match arrays.
12
+ * Fields: minute hour day-of-month month day-of-week
13
+ * Supports: *, specific numbers, comma-separated values.
14
+ */
15
+ function parseCronField(field, min, max) {
16
+ if (field === "*")
17
+ return null; // matches all
18
+ const values = [];
19
+ for (const part of field.split(",")) {
20
+ const trimmed = part.trim();
21
+ // Step syntax: */N or M-N/S
22
+ if (trimmed.includes("/")) {
23
+ const [rangeStr, stepStr] = trimmed.split("/");
24
+ const step = parseInt(stepStr, 10);
25
+ if (isNaN(step) || step <= 0)
26
+ return [];
27
+ let start = min;
28
+ let end = max;
29
+ if (rangeStr !== "*") {
30
+ if (rangeStr.includes("-")) {
31
+ const [rMin, rMax] = rangeStr.split("-").map((s) => parseInt(s, 10));
32
+ if (isNaN(rMin) || isNaN(rMax))
33
+ return [];
34
+ start = rMin;
35
+ end = rMax;
36
+ }
37
+ else {
38
+ start = parseInt(rangeStr, 10);
39
+ if (isNaN(start))
40
+ return [];
41
+ }
42
+ }
43
+ for (let i = start; i <= end; i += step) {
44
+ values.push(i);
45
+ }
46
+ }
47
+ // Range syntax: M-N
48
+ else if (trimmed.includes("-")) {
49
+ const [rMin, rMax] = trimmed.split("-").map((s) => parseInt(s, 10));
50
+ if (isNaN(rMin) || isNaN(rMax) || rMin < min || rMax > max)
51
+ return [];
52
+ for (let i = rMin; i <= rMax; i++) {
53
+ values.push(i);
54
+ }
55
+ }
56
+ // Simple number
57
+ else {
58
+ const num = parseInt(trimmed, 10);
59
+ if (isNaN(num) || num < min || num > max)
60
+ return [];
61
+ values.push(num);
62
+ }
63
+ }
64
+ return values.length > 0 ? values : [];
65
+ }
66
+ function parseCron(expression) {
67
+ const fields = expression.trim().split(/\s+/);
68
+ if (fields.length !== 5)
69
+ return null;
70
+ return {
71
+ minutes: parseCronField(fields[0], 0, 59),
72
+ hours: parseCronField(fields[1], 0, 23),
73
+ daysOfMonth: parseCronField(fields[2], 1, 31),
74
+ months: parseCronField(fields[3], 1, 12),
75
+ daysOfWeek: parseCronField(fields[4], 0, 6),
76
+ };
77
+ }
78
+ function matchesCron(parsed, date) {
79
+ const minute = date.getMinutes();
80
+ const hour = date.getHours();
81
+ const dayOfMonth = date.getDate();
82
+ const month = date.getMonth() + 1;
83
+ const dayOfWeek = date.getDay();
84
+ if (parsed.minutes && !parsed.minutes.includes(minute))
85
+ return false;
86
+ if (parsed.hours && !parsed.hours.includes(hour))
87
+ return false;
88
+ if (parsed.daysOfMonth && !parsed.daysOfMonth.includes(dayOfMonth))
89
+ return false;
90
+ if (parsed.months && !parsed.months.includes(month))
91
+ return false;
92
+ if (parsed.daysOfWeek && !parsed.daysOfWeek.includes(dayOfWeek))
93
+ return false;
94
+ return true;
95
+ }
96
+ function createTimerScheduler(triggers, onTrigger) {
97
+ const parsedTriggers = triggers
98
+ .map((t) => ({ trigger: t, parsed: parseCron(t.trigger.cron) }))
99
+ .filter((t) => t.parsed !== null);
100
+ let lastMinute = -1;
101
+ const interval = setInterval(() => {
102
+ const now = new Date();
103
+ const currentMinute = now.getMinutes() + now.getHours() * 60;
104
+ // Only check once per minute
105
+ if (currentMinute === lastMinute)
106
+ return;
107
+ lastMinute = currentMinute;
108
+ for (const { trigger, parsed } of parsedTriggers) {
109
+ if (matchesCron(parsed, now)) {
110
+ void onTrigger({
111
+ type: "automation",
112
+ rule: trigger,
113
+ });
114
+ }
115
+ }
116
+ }, 30_000); // Check every 30 seconds
117
+ return {
118
+ dispose() {
119
+ clearInterval(interval);
120
+ },
121
+ };
122
+ }