@a5c-ai/genty-runtime 5.1.1-staging.0007199a1cb2

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 (233) hide show
  1. package/README.md +69 -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 +20 -0
  6. package/dist/background/state.d.ts.map +1 -0
  7. package/dist/background/state.js +52 -0
  8. package/dist/backgroundProcessRegistry.d.ts +124 -0
  9. package/dist/backgroundProcessRegistry.d.ts.map +1 -0
  10. package/dist/backgroundProcessRegistry.js +427 -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 +245 -0
  35. package/dist/daemon/daemonLog.d.ts +30 -0
  36. package/dist/daemon/daemonLog.d.ts.map +1 -0
  37. package/dist/daemon/daemonLog.js +140 -0
  38. package/dist/daemon/durableQueue.d.ts +41 -0
  39. package/dist/daemon/durableQueue.d.ts.map +1 -0
  40. package/dist/daemon/durableQueue.js +183 -0
  41. package/dist/daemon/fileWatcher.d.ts +9 -0
  42. package/dist/daemon/fileWatcher.d.ts.map +1 -0
  43. package/dist/daemon/fileWatcher.js +144 -0
  44. package/dist/daemon/index.d.ts +15 -0
  45. package/dist/daemon/index.d.ts.map +1 -0
  46. package/dist/daemon/index.js +25 -0
  47. package/dist/daemon/lifecycle.d.ts +13 -0
  48. package/dist/daemon/lifecycle.d.ts.map +1 -0
  49. package/dist/daemon/lifecycle.js +320 -0
  50. package/dist/daemon/loop.d.ts +27 -0
  51. package/dist/daemon/loop.d.ts.map +1 -0
  52. package/dist/daemon/loop.js +387 -0
  53. package/dist/daemon/timerScheduler.d.ts +13 -0
  54. package/dist/daemon/timerScheduler.d.ts.map +1 -0
  55. package/dist/daemon/timerScheduler.js +212 -0
  56. package/dist/daemon/types.d.ts +122 -0
  57. package/dist/daemon/types.d.ts.map +1 -0
  58. package/dist/daemon/types.js +25 -0
  59. package/dist/daemon/webhookListener.d.ts +6 -0
  60. package/dist/daemon/webhookListener.d.ts.map +1 -0
  61. package/dist/daemon/webhookListener.js +132 -0
  62. package/dist/execution/index.d.ts +10 -0
  63. package/dist/execution/index.d.ts.map +1 -0
  64. package/dist/execution/index.js +20 -0
  65. package/dist/execution/modes/docker.d.ts +26 -0
  66. package/dist/execution/modes/docker.d.ts.map +1 -0
  67. package/dist/execution/modes/docker.js +183 -0
  68. package/dist/execution/modes/index.d.ts +10 -0
  69. package/dist/execution/modes/index.d.ts.map +1 -0
  70. package/dist/execution/modes/index.js +14 -0
  71. package/dist/execution/modes/kubernetes.d.ts +46 -0
  72. package/dist/execution/modes/kubernetes.d.ts.map +1 -0
  73. package/dist/execution/modes/kubernetes.js +334 -0
  74. package/dist/execution/modes/local.d.ts +23 -0
  75. package/dist/execution/modes/local.d.ts.map +1 -0
  76. package/dist/execution/modes/local.js +117 -0
  77. package/dist/execution/modes/ssh.d.ts +23 -0
  78. package/dist/execution/modes/ssh.d.ts.map +1 -0
  79. package/dist/execution/modes/ssh.js +144 -0
  80. package/dist/execution/policy.d.ts +15 -0
  81. package/dist/execution/policy.d.ts.map +1 -0
  82. package/dist/execution/policy.js +121 -0
  83. package/dist/execution/provider.d.ts +32 -0
  84. package/dist/execution/provider.d.ts.map +1 -0
  85. package/dist/execution/provider.js +90 -0
  86. package/dist/execution/types.d.ts +189 -0
  87. package/dist/execution/types.d.ts.map +1 -0
  88. package/dist/execution/types.js +9 -0
  89. package/dist/index.d.ts +12 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +44 -0
  92. package/dist/observability/diagnostics.d.ts +25 -0
  93. package/dist/observability/diagnostics.d.ts.map +1 -0
  94. package/dist/observability/diagnostics.js +98 -0
  95. package/dist/observability/health.d.ts +19 -0
  96. package/dist/observability/health.d.ts.map +1 -0
  97. package/dist/observability/health.js +145 -0
  98. package/dist/observability/index.d.ts +7 -0
  99. package/dist/observability/index.d.ts.map +1 -0
  100. package/dist/observability/index.js +25 -0
  101. package/dist/observability/runStatus.d.ts +44 -0
  102. package/dist/observability/runStatus.d.ts.map +1 -0
  103. package/dist/observability/runStatus.js +170 -0
  104. package/dist/observability/timeline.d.ts +11 -0
  105. package/dist/observability/timeline.d.ts.map +1 -0
  106. package/dist/observability/timeline.js +176 -0
  107. package/dist/observability/types.d.ts +65 -0
  108. package/dist/observability/types.d.ts.map +1 -0
  109. package/dist/observability/types.js +8 -0
  110. package/dist/observability/webhooks.d.ts +68 -0
  111. package/dist/observability/webhooks.d.ts.map +1 -0
  112. package/dist/observability/webhooks.js +132 -0
  113. package/dist/resources/budget-tracker.d.ts +56 -0
  114. package/dist/resources/budget-tracker.d.ts.map +1 -0
  115. package/dist/resources/budget-tracker.js +131 -0
  116. package/dist/resources/concurrency-guard.d.ts +55 -0
  117. package/dist/resources/concurrency-guard.d.ts.map +1 -0
  118. package/dist/resources/concurrency-guard.js +132 -0
  119. package/dist/resources/index.d.ts +12 -0
  120. package/dist/resources/index.d.ts.map +1 -0
  121. package/dist/resources/index.js +20 -0
  122. package/dist/resources/manager.d.ts +52 -0
  123. package/dist/resources/manager.d.ts.map +1 -0
  124. package/dist/resources/manager.js +150 -0
  125. package/dist/resources/timeout-cascade.d.ts +56 -0
  126. package/dist/resources/timeout-cascade.d.ts.map +1 -0
  127. package/dist/resources/timeout-cascade.js +145 -0
  128. package/dist/resources/types.d.ts +130 -0
  129. package/dist/resources/types.d.ts.map +1 -0
  130. package/dist/resources/types.js +9 -0
  131. package/dist/rpc/index.d.ts +5 -0
  132. package/dist/rpc/index.d.ts.map +1 -0
  133. package/dist/rpc/index.js +7 -0
  134. package/dist/rpc/server.d.ts +13 -0
  135. package/dist/rpc/server.d.ts.map +1 -0
  136. package/dist/rpc/server.js +64 -0
  137. package/dist/rpc/server.test.d.ts +2 -0
  138. package/dist/rpc/server.test.d.ts.map +1 -0
  139. package/dist/rpc/server.test.js +35 -0
  140. package/dist/rpc/types.d.ts +23 -0
  141. package/dist/rpc/types.d.ts.map +1 -0
  142. package/dist/rpc/types.js +20 -0
  143. package/dist/session/context.d.ts +22 -0
  144. package/dist/session/context.d.ts.map +1 -0
  145. package/dist/session/context.js +113 -0
  146. package/dist/session/continuityState.d.ts +39 -0
  147. package/dist/session/continuityState.d.ts.map +1 -0
  148. package/dist/session/continuityState.js +164 -0
  149. package/dist/session/cost.d.ts +63 -0
  150. package/dist/session/cost.d.ts.map +1 -0
  151. package/dist/session/cost.js +194 -0
  152. package/dist/session/discovery.d.ts +24 -0
  153. package/dist/session/discovery.d.ts.map +1 -0
  154. package/dist/session/discovery.js +43 -0
  155. package/dist/session/export.d.ts +4 -0
  156. package/dist/session/export.d.ts.map +1 -0
  157. package/dist/session/export.js +56 -0
  158. package/dist/session/export.test.d.ts +2 -0
  159. package/dist/session/export.test.d.ts.map +1 -0
  160. package/dist/session/export.test.js +42 -0
  161. package/dist/session/history.d.ts +30 -0
  162. package/dist/session/history.d.ts.map +1 -0
  163. package/dist/session/history.js +143 -0
  164. package/dist/session/index.d.ts +20 -0
  165. package/dist/session/index.d.ts.map +1 -0
  166. package/dist/session/index.js +78 -0
  167. package/dist/session/memoryExtraction.d.ts +65 -0
  168. package/dist/session/memoryExtraction.d.ts.map +1 -0
  169. package/dist/session/memoryExtraction.js +201 -0
  170. package/dist/session/parse.d.ts +45 -0
  171. package/dist/session/parse.d.ts.map +1 -0
  172. package/dist/session/parse.js +170 -0
  173. package/dist/session/persistence.d.ts +46 -0
  174. package/dist/session/persistence.d.ts.map +1 -0
  175. package/dist/session/persistence.js +180 -0
  176. package/dist/session/rewind.d.ts +45 -0
  177. package/dist/session/rewind.d.ts.map +1 -0
  178. package/dist/session/rewind.js +68 -0
  179. package/dist/session/rewind.test.d.ts +2 -0
  180. package/dist/session/rewind.test.d.ts.map +1 -0
  181. package/dist/session/rewind.test.js +96 -0
  182. package/dist/session/tree.d.ts +29 -0
  183. package/dist/session/tree.d.ts.map +1 -0
  184. package/dist/session/tree.js +115 -0
  185. package/dist/session/tree.test.d.ts +2 -0
  186. package/dist/session/tree.test.d.ts.map +1 -0
  187. package/dist/session/tree.test.js +75 -0
  188. package/dist/session/types.d.ts +267 -0
  189. package/dist/session/types.d.ts.map +1 -0
  190. package/dist/session/types.js +45 -0
  191. package/dist/session/write.d.ts +61 -0
  192. package/dist/session/write.d.ts.map +1 -0
  193. package/dist/session/write.js +213 -0
  194. package/dist/shellInvocation.d.ts +6 -0
  195. package/dist/shellInvocation.d.ts.map +1 -0
  196. package/dist/shellInvocation.js +8 -0
  197. package/dist/shellInvocation.test.d.ts +2 -0
  198. package/dist/shellInvocation.test.d.ts.map +1 -0
  199. package/dist/shellInvocation.test.js +18 -0
  200. package/dist/storage/journal.d.ts +17 -0
  201. package/dist/storage/journal.d.ts.map +1 -0
  202. package/dist/storage/journal.js +165 -0
  203. package/dist/storage/runFiles.d.ts +10 -0
  204. package/dist/storage/runFiles.d.ts.map +1 -0
  205. package/dist/storage/runFiles.js +54 -0
  206. package/dist/telemetry/audit-log.d.ts +56 -0
  207. package/dist/telemetry/audit-log.d.ts.map +1 -0
  208. package/dist/telemetry/audit-log.js +59 -0
  209. package/dist/telemetry/exporters.d.ts +35 -0
  210. package/dist/telemetry/exporters.d.ts.map +1 -0
  211. package/dist/telemetry/exporters.js +141 -0
  212. package/dist/telemetry/index.d.ts +12 -0
  213. package/dist/telemetry/index.d.ts.map +1 -0
  214. package/dist/telemetry/index.js +25 -0
  215. package/dist/telemetry/provider.d.ts +57 -0
  216. package/dist/telemetry/provider.d.ts.map +1 -0
  217. package/dist/telemetry/provider.js +261 -0
  218. package/dist/telemetry/span-tree.d.ts +46 -0
  219. package/dist/telemetry/span-tree.d.ts.map +1 -0
  220. package/dist/telemetry/span-tree.js +93 -0
  221. package/dist/telemetry/traceContext.d.ts +10 -0
  222. package/dist/telemetry/traceContext.d.ts.map +1 -0
  223. package/dist/telemetry/traceContext.js +43 -0
  224. package/dist/telemetry/types.d.ts +109 -0
  225. package/dist/telemetry/types.d.ts.map +1 -0
  226. package/dist/telemetry/types.js +21 -0
  227. package/dist/types/sdk.d.ts +66 -0
  228. package/dist/types/sdk.d.ts.map +1 -0
  229. package/dist/types/sdk.js +9 -0
  230. package/dist/utils/ulid.d.ts +11 -0
  231. package/dist/utils/ulid.d.ts.map +1 -0
  232. package/dist/utils/ulid.js +62 -0
  233. package/package.json +137 -0
@@ -0,0 +1,387 @@
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
+ const durableQueue_1 = require("./durableQueue");
49
+ async function runDaemonLoop(config, options) {
50
+ const maxConcurrent = config.maxConcurrentRuns ?? 4;
51
+ const handles = [];
52
+ const activeRuns = new Set();
53
+ const queue = [];
54
+ const admission = createAdmissionController(config.triggerAdmission);
55
+ const retryTimers = new Set();
56
+ const durableQueue = options?.logDir
57
+ ? await durableQueue_1.DurableTriggerQueue.open(options.logDir, options.queue)
58
+ : null;
59
+ let statusWriteChain = Promise.resolve();
60
+ function scheduleStatusWrite() {
61
+ statusWriteChain = statusWriteChain.then(() => writeLoopStatus()).catch(() => {
62
+ // Status persistence is best-effort and must not surface as an
63
+ // unhandled rejection during trigger dispatch or shutdown races.
64
+ });
65
+ }
66
+ function scheduleRetryDrain(delayMs) {
67
+ const timer = setTimeout(() => {
68
+ retryTimers.delete(timer);
69
+ void drainQueue();
70
+ }, Math.max(0, delayMs));
71
+ retryTimers.add(timer);
72
+ }
73
+ async function drainQueue() {
74
+ if (durableQueue) {
75
+ while (activeRuns.size < maxConcurrent) {
76
+ const [next] = await durableQueue.claimDue(1);
77
+ if (!next)
78
+ break;
79
+ dispatchDurableTrigger(next);
80
+ }
81
+ scheduleStatusWrite();
82
+ return;
83
+ }
84
+ while (queue.length > 0 && activeRuns.size < maxConcurrent) {
85
+ const next = queue.shift();
86
+ dispatchTrigger(next);
87
+ }
88
+ scheduleStatusWrite();
89
+ }
90
+ function dispatchTrigger(trigger) {
91
+ if (options?.onTrigger) {
92
+ return Promise.resolve(options.onTrigger(trigger)).then(() => { });
93
+ }
94
+ }
95
+ function dispatchDurableTrigger(record) {
96
+ const promise = Promise.resolve()
97
+ .then(() => dispatchTrigger(record.trigger))
98
+ .then(async () => {
99
+ await durableQueue?.ack(record.id);
100
+ }, async (error) => {
101
+ await durableQueue?.fail(record.id, error);
102
+ await (0, daemonLog_1.appendDaemonLog)(options.logDir, {
103
+ timestamp: new Date().toISOString(),
104
+ event: "TRIGGER_FAILED",
105
+ data: { eventId: record.id, error: error instanceof Error ? error.message : String(error) },
106
+ }).catch(() => { });
107
+ const snapshot = (await durableQueue?.snapshot()) ?? [];
108
+ const failed = snapshot.find((event) => event.id === record.id && event.nextAttemptAt);
109
+ if (failed?.nextAttemptAt) {
110
+ scheduleRetryDrain(Date.parse(failed.nextAttemptAt) - Date.now());
111
+ }
112
+ })
113
+ .finally(() => {
114
+ activeRuns.delete(promise);
115
+ void drainQueue();
116
+ });
117
+ activeRuns.add(promise);
118
+ }
119
+ function dispatchMemoryTrigger(trigger) {
120
+ const promise = Promise.resolve()
121
+ .then(() => dispatchTrigger(trigger))
122
+ .catch((error) => {
123
+ if (options?.logDir) {
124
+ void (0, daemonLog_1.appendDaemonLog)(options.logDir, {
125
+ timestamp: new Date().toISOString(),
126
+ event: "TRIGGER_FAILED",
127
+ data: { error: error instanceof Error ? error.message : String(error) },
128
+ }).catch(() => { });
129
+ }
130
+ })
131
+ .finally(() => {
132
+ activeRuns.delete(promise);
133
+ void drainQueue();
134
+ });
135
+ activeRuns.add(promise);
136
+ }
137
+ async function writeLoopStatus() {
138
+ if (!options?.logDir)
139
+ return;
140
+ const statusPath = path.join(options.logDir, "daemon.status.json");
141
+ const status = {
142
+ activeRuns: durableQueue?.counts().active ?? activeRuns.size,
143
+ pendingRuns: durableQueue?.counts().pending ?? queue.length,
144
+ deadLetterRuns: durableQueue?.counts().deadLetter,
145
+ rejectedTriggers: admission.stats.rejected,
146
+ duplicateTriggers: admission.stats.duplicates,
147
+ rateLimitedTriggers: admission.stats.rateLimited,
148
+ updatedAt: new Date().toISOString(),
149
+ };
150
+ const tmpPath = `${statusPath}.tmp-${process.pid}-${Date.now()}`;
151
+ await node_fs_1.promises.writeFile(tmpPath, JSON.stringify(status), "utf-8");
152
+ await node_fs_1.promises.rename(tmpPath, statusPath);
153
+ }
154
+ const triggerCallback = async (trigger) => {
155
+ // Log the activation
156
+ if (options?.logDir) {
157
+ const data = (0, types_1.isAutomationTriggerEvent)(trigger)
158
+ ? {
159
+ type: "automation",
160
+ ruleId: trigger.rule.id,
161
+ triggerType: trigger.rule.trigger.type,
162
+ projectId: trigger.rule.target.projectId,
163
+ boardProjectId: trigger.rule.target.boardProjectId,
164
+ }
165
+ : {
166
+ type: trigger.type,
167
+ processId: trigger.processId,
168
+ entrypoint: trigger.entrypoint,
169
+ };
170
+ void (0, daemonLog_1.appendDaemonLog)(options.logDir, {
171
+ timestamp: new Date().toISOString(),
172
+ event: "TRIGGER_ACTIVATED",
173
+ data,
174
+ }).catch(() => {
175
+ // Activation logging is best-effort and should never interrupt the loop.
176
+ });
177
+ }
178
+ const admissionResult = admission.evaluate(trigger, {
179
+ activeRuns: activeRuns.size,
180
+ pendingRuns: durableQueue?.counts().pending ?? queue.length,
181
+ maxConcurrent,
182
+ });
183
+ if (admissionResult.status === "rejected" || admissionResult.status === "duplicate") {
184
+ if (options?.logDir) {
185
+ void (0, daemonLog_1.appendDaemonLog)(options.logDir, {
186
+ timestamp: new Date().toISOString(),
187
+ event: admissionResult.status === "duplicate" ? "TRIGGER_DUPLICATE" : "TRIGGER_REJECTED",
188
+ data: {
189
+ reason: admissionResult.reason,
190
+ retryAfterMs: admissionResult.retryAfterMs,
191
+ queueDepth: admissionResult.queueDepth,
192
+ fingerprint: admissionResult.fingerprint,
193
+ },
194
+ }).catch(() => { });
195
+ }
196
+ scheduleStatusWrite();
197
+ return admissionResult;
198
+ }
199
+ if (durableQueue) {
200
+ await durableQueue.enqueue(trigger).then(() => drainQueue()).catch((error) => {
201
+ void (0, daemonLog_1.appendDaemonLog)(options.logDir, {
202
+ timestamp: new Date().toISOString(),
203
+ event: "TRIGGER_QUEUE_ERROR",
204
+ data: { error: error instanceof Error ? error.message : String(error) },
205
+ }).catch(() => { });
206
+ });
207
+ scheduleStatusWrite();
208
+ return admissionResult;
209
+ }
210
+ if (activeRuns.size >= maxConcurrent) {
211
+ queue.push(trigger);
212
+ scheduleStatusWrite();
213
+ return admissionResult.status === "accepted"
214
+ ? { ...admissionResult, status: "deferred", queueDepth: queue.length }
215
+ : admissionResult;
216
+ }
217
+ dispatchMemoryTrigger(trigger);
218
+ scheduleStatusWrite();
219
+ return admissionResult;
220
+ };
221
+ // Set up file triggers
222
+ const fileTriggers = config.triggers
223
+ .filter(types_1.isFileTriggerConfig);
224
+ if (fileTriggers.length > 0) {
225
+ const handle = (0, fileWatcher_1.createFileWatcher)(fileTriggers, triggerCallback);
226
+ handles.push(handle);
227
+ }
228
+ // Set up webhook triggers
229
+ const webhookTriggers = config.triggers.filter(types_1.isWebhookAutomationRule);
230
+ for (const rule of webhookTriggers) {
231
+ try {
232
+ const handle = await (0, webhookListener_1.createWebhookListener)({
233
+ rule,
234
+ onTrigger: triggerCallback,
235
+ });
236
+ handles.push(handle);
237
+ }
238
+ catch {
239
+ // Port in use or other error — skip
240
+ }
241
+ }
242
+ // Set up timer/cron triggers
243
+ const timerTriggers = config.triggers.filter(types_1.isTimerAutomationRule);
244
+ if (timerTriggers.length > 0) {
245
+ const handle = (0, timerScheduler_1.createTimerScheduler)(timerTriggers, triggerCallback);
246
+ handles.push(handle);
247
+ }
248
+ await drainQueue();
249
+ // Wait for abort signal
250
+ if (options?.signal) {
251
+ await new Promise((resolve) => {
252
+ if (options.signal.aborted) {
253
+ resolve();
254
+ return;
255
+ }
256
+ options.signal.addEventListener("abort", () => resolve(), { once: true });
257
+ });
258
+ }
259
+ // Cleanup all handles
260
+ for (const handle of handles) {
261
+ if (handle.dispose)
262
+ handle.dispose();
263
+ if (handle.close)
264
+ await handle.close();
265
+ }
266
+ for (const timer of retryTimers) {
267
+ clearTimeout(timer);
268
+ }
269
+ // Wait for active runs to finish
270
+ if (activeRuns.size > 0) {
271
+ await Promise.allSettled([...activeRuns]);
272
+ }
273
+ // Wait for any in-flight status writes then write final status
274
+ await statusWriteChain;
275
+ try {
276
+ await writeLoopStatus();
277
+ }
278
+ catch { /* directory may be gone in tests */ }
279
+ }
280
+ function createAdmissionController(config) {
281
+ const seen = new Map();
282
+ const rateWindow = [];
283
+ const stats = {
284
+ rejected: 0,
285
+ duplicates: 0,
286
+ rateLimited: 0,
287
+ };
288
+ return {
289
+ stats,
290
+ evaluate(trigger, state) {
291
+ const now = Date.now();
292
+ const fingerprint = fingerprintTrigger(trigger);
293
+ const dedupeWindowMs = config?.dedupeWindowMs ?? 0;
294
+ if (dedupeWindowMs > 0) {
295
+ for (const [key, expiresAt] of seen) {
296
+ if (expiresAt <= now)
297
+ seen.delete(key);
298
+ }
299
+ const existing = seen.get(fingerprint);
300
+ if (existing && existing > now) {
301
+ stats.duplicates += 1;
302
+ return {
303
+ status: "duplicate",
304
+ reason: "dedupe-window",
305
+ retryAfterMs: existing - now,
306
+ queueDepth: state.pendingRuns,
307
+ fingerprint,
308
+ };
309
+ }
310
+ }
311
+ const rateLimit = config?.rateLimit;
312
+ if (rateLimit) {
313
+ while (rateWindow.length > 0 && rateWindow[0] <= now - rateLimit.windowMs) {
314
+ rateWindow.shift();
315
+ }
316
+ if (rateWindow.length >= rateLimit.maxTriggers) {
317
+ stats.rejected += 1;
318
+ stats.rateLimited += 1;
319
+ return {
320
+ status: "rejected",
321
+ reason: "rate-limit",
322
+ retryAfterMs: Math.max(0, rateWindow[0] + rateLimit.windowMs - now),
323
+ queueDepth: state.pendingRuns,
324
+ fingerprint,
325
+ };
326
+ }
327
+ }
328
+ const maxPendingRuns = config?.maxPendingRuns;
329
+ if (maxPendingRuns !== undefined && state.pendingRuns >= maxPendingRuns && state.activeRuns >= state.maxConcurrent) {
330
+ stats.rejected += 1;
331
+ return {
332
+ status: "rejected",
333
+ reason: "queue-full",
334
+ queueDepth: state.pendingRuns,
335
+ fingerprint,
336
+ };
337
+ }
338
+ if (dedupeWindowMs > 0) {
339
+ seen.set(fingerprint, now + dedupeWindowMs);
340
+ }
341
+ if (rateLimit) {
342
+ rateWindow.push(now);
343
+ }
344
+ return {
345
+ status: state.activeRuns >= state.maxConcurrent ? "deferred" : "accepted",
346
+ queueDepth: state.pendingRuns,
347
+ fingerprint,
348
+ };
349
+ },
350
+ };
351
+ }
352
+ function fingerprintTrigger(trigger) {
353
+ if (trigger.type === "file") {
354
+ const path = typeof trigger.inputs?.path === "string" ? trigger.inputs.path : "";
355
+ return path ? `file:path:${path}` : `file:${trigger.processId}:${trigger.entrypoint}`;
356
+ }
357
+ const deliveryId = typeof trigger.inputs?.deliveryId === "string" ? trigger.inputs.deliveryId : "";
358
+ const sourceEvent = trigger.rule.trigger.type === "webhook" ? trigger.rule.trigger.sourceEvent ?? "" : "";
359
+ const inputs = stableStringify(trigger.inputs ?? {});
360
+ return `automation:${trigger.rule.trigger.type}:${trigger.rule.id}:${deliveryId}:${sourceEvent}:${inputs}`;
361
+ }
362
+ function stableStringify(value) {
363
+ if (value === null || typeof value !== "object") {
364
+ return JSON.stringify(value);
365
+ }
366
+ if (Array.isArray(value)) {
367
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
368
+ }
369
+ return `{${Object.entries(value)
370
+ .sort(([left], [right]) => left.localeCompare(right))
371
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
372
+ .join(",")}}`;
373
+ }
374
+ /**
375
+ * Read the daemon loop's runtime status from its status file.
376
+ * Returns null if the file doesn't exist or is unreadable.
377
+ */
378
+ async function readDaemonLoopStatus(daemonDir) {
379
+ try {
380
+ const statusPath = path.join(daemonDir, "daemon.status.json");
381
+ const content = await node_fs_1.promises.readFile(statusPath, "utf-8");
382
+ return JSON.parse(content);
383
+ }
384
+ catch {
385
+ return null;
386
+ }
387
+ }
@@ -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/comm-adapter";
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,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,WAAW,oBAAoB;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AAkLD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,SAAS,EAAE,eAAe,GACzB,oBAAoB,CA0CtB"}
@@ -0,0 +1,212 @@
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
+ const CRON_MACROS = {
11
+ "@yearly": "0 0 1 1 *",
12
+ "@annually": "0 0 1 1 *",
13
+ "@monthly": "0 0 1 * *",
14
+ "@weekly": "0 0 * * 0",
15
+ "@daily": "0 0 * * *",
16
+ "@midnight": "0 0 * * *",
17
+ "@hourly": "0 * * * *",
18
+ };
19
+ const MONTH_NAMES = {
20
+ JAN: 1,
21
+ FEB: 2,
22
+ MAR: 3,
23
+ APR: 4,
24
+ MAY: 5,
25
+ JUN: 6,
26
+ JUL: 7,
27
+ AUG: 8,
28
+ SEP: 9,
29
+ OCT: 10,
30
+ NOV: 11,
31
+ DEC: 12,
32
+ };
33
+ const DAY_NAMES = {
34
+ SUN: 0,
35
+ MON: 1,
36
+ TUE: 2,
37
+ WED: 3,
38
+ THU: 4,
39
+ FRI: 5,
40
+ SAT: 6,
41
+ };
42
+ function normalizeCronField(field, names) {
43
+ if (!names)
44
+ return field;
45
+ return field.replace(/[A-Za-z]{3}/g, (name) => String(names[name.toUpperCase()] ?? name));
46
+ }
47
+ function parseCronField(field, min, max, names) {
48
+ field = normalizeCronField(field, names);
49
+ if (field === "*")
50
+ return null; // matches all
51
+ const values = [];
52
+ for (const part of field.split(",")) {
53
+ const trimmed = part.trim();
54
+ // Step syntax: */N or M-N/S
55
+ if (trimmed.includes("/")) {
56
+ const [rangeStr, stepStr] = trimmed.split("/");
57
+ const step = parseInt(stepStr, 10);
58
+ if (isNaN(step) || step <= 0)
59
+ return [];
60
+ let start = min;
61
+ let end = max;
62
+ if (rangeStr !== "*") {
63
+ if (rangeStr.includes("-")) {
64
+ const [rMin, rMax] = rangeStr.split("-").map((s) => parseInt(s, 10));
65
+ if (isNaN(rMin) || isNaN(rMax))
66
+ return [];
67
+ start = rMin;
68
+ end = rMax;
69
+ }
70
+ else {
71
+ start = parseInt(rangeStr, 10);
72
+ if (isNaN(start))
73
+ return [];
74
+ }
75
+ }
76
+ if (start < min || end > max || start > end)
77
+ return [];
78
+ for (let i = start; i <= end; i += step) {
79
+ values.push(max === 6 && i === 7 ? 0 : i);
80
+ }
81
+ }
82
+ // Range syntax: M-N
83
+ else if (trimmed.includes("-")) {
84
+ const [rMin, rMax] = trimmed.split("-").map((s) => parseInt(s, 10));
85
+ if (isNaN(rMin) || isNaN(rMax) || rMin < min || rMax > max || rMin > rMax)
86
+ return [];
87
+ for (let i = rMin; i <= rMax; i++) {
88
+ values.push(max === 6 && i === 7 ? 0 : i);
89
+ }
90
+ }
91
+ // Simple number
92
+ else {
93
+ const num = parseInt(trimmed, 10);
94
+ const normalized = max === 6 && num === 7 ? 0 : num;
95
+ if (isNaN(num) || normalized < min || normalized > max)
96
+ return [];
97
+ values.push(normalized);
98
+ }
99
+ }
100
+ return values.length > 0 ? [...new Set(values)] : [];
101
+ }
102
+ function parseCron(expression, timezone) {
103
+ expression = expression.trim();
104
+ if (expression === "@reboot") {
105
+ return {
106
+ macro: "reboot",
107
+ minutes: [],
108
+ hours: [],
109
+ daysOfMonth: [],
110
+ months: [],
111
+ daysOfWeek: [],
112
+ timezone,
113
+ };
114
+ }
115
+ expression = CRON_MACROS[expression] ?? expression;
116
+ const fields = expression.trim().split(/\s+/);
117
+ if (fields.length !== 5)
118
+ return null;
119
+ return {
120
+ minutes: parseCronField(fields[0], 0, 59),
121
+ hours: parseCronField(fields[1], 0, 23),
122
+ daysOfMonth: parseCronField(fields[2], 1, 31),
123
+ months: parseCronField(fields[3], 1, 12, MONTH_NAMES),
124
+ daysOfWeek: parseCronField(fields[4], 0, 7, DAY_NAMES)?.map((day) => day === 7 ? 0 : day) ?? null,
125
+ timezone,
126
+ };
127
+ }
128
+ function getCronDateParts(date, timezone) {
129
+ if (!timezone) {
130
+ return {
131
+ minute: date.getMinutes(),
132
+ hour: date.getHours(),
133
+ dayOfMonth: date.getDate(),
134
+ month: date.getMonth() + 1,
135
+ dayOfWeek: date.getDay(),
136
+ };
137
+ }
138
+ const parts = new Intl.DateTimeFormat("en-US", {
139
+ timeZone: timezone,
140
+ year: "numeric",
141
+ month: "numeric",
142
+ day: "numeric",
143
+ hour: "numeric",
144
+ minute: "numeric",
145
+ hourCycle: "h23",
146
+ }).formatToParts(date);
147
+ const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
148
+ const year = Number(values.year);
149
+ const month = Number(values.month);
150
+ const dayOfMonth = Number(values.day);
151
+ return {
152
+ minute: Number(values.minute),
153
+ hour: Number(values.hour),
154
+ dayOfMonth,
155
+ month,
156
+ dayOfWeek: new Date(Date.UTC(year, month - 1, dayOfMonth)).getUTCDay(),
157
+ };
158
+ }
159
+ function matchesCron(parsed, date) {
160
+ const { minute, hour, dayOfMonth, month, dayOfWeek } = getCronDateParts(date, parsed.timezone);
161
+ if (parsed.minutes && !parsed.minutes.includes(minute))
162
+ return false;
163
+ if (parsed.hours && !parsed.hours.includes(hour))
164
+ return false;
165
+ if (parsed.daysOfMonth && !parsed.daysOfMonth.includes(dayOfMonth))
166
+ return false;
167
+ if (parsed.months && !parsed.months.includes(month))
168
+ return false;
169
+ if (parsed.daysOfWeek && !parsed.daysOfWeek.includes(dayOfWeek))
170
+ return false;
171
+ return true;
172
+ }
173
+ function createTimerScheduler(triggers, onTrigger) {
174
+ const parsedTriggers = triggers
175
+ .map((t) => ({ trigger: t, parsed: parseCron(t.trigger.cron, t.trigger.timezone) }))
176
+ .filter((t) => t.parsed !== null);
177
+ for (const { trigger, parsed } of parsedTriggers) {
178
+ if (parsed.macro === "reboot") {
179
+ queueMicrotask(() => {
180
+ void onTrigger({
181
+ type: "automation",
182
+ rule: trigger,
183
+ inputs: { scheduledBy: "@reboot" },
184
+ });
185
+ });
186
+ }
187
+ }
188
+ let lastMinute = -1;
189
+ const interval = setInterval(() => {
190
+ const now = new Date();
191
+ const currentMinute = now.getMinutes() + now.getHours() * 60;
192
+ // Only check once per minute
193
+ if (currentMinute === lastMinute)
194
+ return;
195
+ lastMinute = currentMinute;
196
+ for (const { trigger, parsed } of parsedTriggers) {
197
+ if (parsed.macro === "reboot")
198
+ continue;
199
+ if (matchesCron(parsed, now)) {
200
+ void onTrigger({
201
+ type: "automation",
202
+ rule: trigger,
203
+ });
204
+ }
205
+ }
206
+ }, 30_000); // Check every 30 seconds
207
+ return {
208
+ dispose() {
209
+ clearInterval(interval);
210
+ },
211
+ };
212
+ }