@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.
- package/README.md +23 -0
- package/dist/apiResult.d.ts +19 -0
- package/dist/apiResult.d.ts.map +1 -0
- package/dist/apiResult.js +16 -0
- package/dist/background/state.d.ts +14 -0
- package/dist/background/state.d.ts.map +1 -0
- package/dist/background/state.js +25 -0
- package/dist/backgroundProcessRegistry.d.ts +66 -0
- package/dist/backgroundProcessRegistry.d.ts.map +1 -0
- package/dist/backgroundProcessRegistry.js +202 -0
- package/dist/cost/claudeCodeParser.d.ts +81 -0
- package/dist/cost/claudeCodeParser.d.ts.map +1 -0
- package/dist/cost/claudeCodeParser.js +232 -0
- package/dist/cost/collector.d.ts +42 -0
- package/dist/cost/collector.d.ts.map +1 -0
- package/dist/cost/collector.js +105 -0
- package/dist/cost/effectCost.d.ts +23 -0
- package/dist/cost/effectCost.d.ts.map +1 -0
- package/dist/cost/effectCost.js +26 -0
- package/dist/cost/index.d.ts +19 -0
- package/dist/cost/index.d.ts.map +1 -0
- package/dist/cost/index.js +39 -0
- package/dist/cost/journal.d.ts +40 -0
- package/dist/cost/journal.d.ts.map +1 -0
- package/dist/cost/journal.js +137 -0
- package/dist/cost/types.d.ts +164 -0
- package/dist/cost/types.d.ts.map +1 -0
- package/dist/cost/types.js +228 -0
- package/dist/daemon/automationExecutor.d.ts +16 -0
- package/dist/daemon/automationExecutor.d.ts.map +1 -0
- package/dist/daemon/automationExecutor.js +222 -0
- package/dist/daemon/config.d.ts +8 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +209 -0
- package/dist/daemon/daemonLog.d.ts +13 -0
- package/dist/daemon/daemonLog.d.ts.map +1 -0
- package/dist/daemon/daemonLog.js +64 -0
- package/dist/daemon/fileWatcher.d.ts +9 -0
- package/dist/daemon/fileWatcher.d.ts.map +1 -0
- package/dist/daemon/fileWatcher.js +141 -0
- package/dist/daemon/index.d.ts +13 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +22 -0
- package/dist/daemon/lifecycle.d.ts +12 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +257 -0
- package/dist/daemon/loop.d.ts +21 -0
- package/dist/daemon/loop.d.ts.map +1 -0
- package/dist/daemon/loop.js +196 -0
- package/dist/daemon/timerScheduler.d.ts +13 -0
- package/dist/daemon/timerScheduler.d.ts.map +1 -0
- package/dist/daemon/timerScheduler.js +122 -0
- package/dist/daemon/types.d.ts +93 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +25 -0
- package/dist/daemon/webhookListener.d.ts +6 -0
- package/dist/daemon/webhookListener.d.ts.map +1 -0
- package/dist/daemon/webhookListener.js +110 -0
- package/dist/execution/index.d.ts +8 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +12 -0
- package/dist/execution/modes/docker.d.ts +21 -0
- package/dist/execution/modes/docker.d.ts.map +1 -0
- package/dist/execution/modes/docker.js +125 -0
- package/dist/execution/modes/index.d.ts +10 -0
- package/dist/execution/modes/index.d.ts.map +1 -0
- package/dist/execution/modes/index.js +14 -0
- package/dist/execution/modes/kubernetes.d.ts +29 -0
- package/dist/execution/modes/kubernetes.d.ts.map +1 -0
- package/dist/execution/modes/kubernetes.js +121 -0
- package/dist/execution/modes/local.d.ts +23 -0
- package/dist/execution/modes/local.d.ts.map +1 -0
- package/dist/execution/modes/local.js +102 -0
- package/dist/execution/modes/ssh.d.ts +23 -0
- package/dist/execution/modes/ssh.d.ts.map +1 -0
- package/dist/execution/modes/ssh.js +134 -0
- package/dist/execution/provider.d.ts +32 -0
- package/dist/execution/provider.d.ts.map +1 -0
- package/dist/execution/provider.js +90 -0
- package/dist/execution/types.d.ts +105 -0
- package/dist/execution/types.d.ts.map +1 -0
- package/dist/execution/types.js +9 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/observability/health.d.ts +19 -0
- package/dist/observability/health.d.ts.map +1 -0
- package/dist/observability/health.js +129 -0
- package/dist/observability/index.d.ts +6 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +20 -0
- package/dist/observability/runStatus.d.ts +44 -0
- package/dist/observability/runStatus.d.ts.map +1 -0
- package/dist/observability/runStatus.js +169 -0
- package/dist/observability/timeline.d.ts +11 -0
- package/dist/observability/timeline.d.ts.map +1 -0
- package/dist/observability/timeline.js +176 -0
- package/dist/observability/types.d.ts +62 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +8 -0
- package/dist/observability/webhooks.d.ts +68 -0
- package/dist/observability/webhooks.d.ts.map +1 -0
- package/dist/observability/webhooks.js +132 -0
- package/dist/resources/budget-tracker.d.ts +56 -0
- package/dist/resources/budget-tracker.d.ts.map +1 -0
- package/dist/resources/budget-tracker.js +131 -0
- package/dist/resources/concurrency-guard.d.ts +55 -0
- package/dist/resources/concurrency-guard.d.ts.map +1 -0
- package/dist/resources/concurrency-guard.js +132 -0
- package/dist/resources/index.d.ts +12 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +20 -0
- package/dist/resources/manager.d.ts +49 -0
- package/dist/resources/manager.d.ts.map +1 -0
- package/dist/resources/manager.js +111 -0
- package/dist/resources/timeout-cascade.d.ts +56 -0
- package/dist/resources/timeout-cascade.d.ts.map +1 -0
- package/dist/resources/timeout-cascade.js +145 -0
- package/dist/resources/types.d.ts +108 -0
- package/dist/resources/types.d.ts.map +1 -0
- package/dist/resources/types.js +9 -0
- package/dist/session/context.d.ts +22 -0
- package/dist/session/context.d.ts.map +1 -0
- package/dist/session/context.js +113 -0
- package/dist/session/continuityState.d.ts +39 -0
- package/dist/session/continuityState.d.ts.map +1 -0
- package/dist/session/continuityState.js +164 -0
- package/dist/session/cost.d.ts +63 -0
- package/dist/session/cost.d.ts.map +1 -0
- package/dist/session/cost.js +194 -0
- package/dist/session/discovery.d.ts +22 -0
- package/dist/session/discovery.d.ts.map +1 -0
- package/dist/session/discovery.js +35 -0
- package/dist/session/history.d.ts +30 -0
- package/dist/session/history.d.ts.map +1 -0
- package/dist/session/history.js +143 -0
- package/dist/session/index.d.ts +20 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +78 -0
- package/dist/session/memoryExtraction.d.ts +65 -0
- package/dist/session/memoryExtraction.d.ts.map +1 -0
- package/dist/session/memoryExtraction.js +201 -0
- package/dist/session/parse.d.ts +45 -0
- package/dist/session/parse.d.ts.map +1 -0
- package/dist/session/parse.js +170 -0
- package/dist/session/persistence.d.ts +46 -0
- package/dist/session/persistence.d.ts.map +1 -0
- package/dist/session/persistence.js +180 -0
- package/dist/session/types.d.ts +267 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +45 -0
- package/dist/session/write.d.ts +61 -0
- package/dist/session/write.d.ts.map +1 -0
- package/dist/session/write.js +213 -0
- package/dist/telemetry/audit-log.d.ts +56 -0
- package/dist/telemetry/audit-log.d.ts.map +1 -0
- package/dist/telemetry/audit-log.js +59 -0
- package/dist/telemetry/index.d.ts +9 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +15 -0
- package/dist/telemetry/provider.d.ts +39 -0
- package/dist/telemetry/provider.d.ts.map +1 -0
- package/dist/telemetry/provider.js +91 -0
- package/dist/telemetry/span-tree.d.ts +46 -0
- package/dist/telemetry/span-tree.d.ts.map +1 -0
- package/dist/telemetry/span-tree.js +93 -0
- package/dist/telemetry/types.d.ts +85 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +21 -0
- package/package.json +90 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared automation-rule execution path for daemon timer and webhook triggers.
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.executeAutomationTrigger = executeAutomationTrigger;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
14
|
+
const DEFAULT_BACKLOG_FILE_PATH = process.env.KANBAN_BACKLOG_FILE
|
|
15
|
+
?? node_path_1.default.join(node_os_1.default.homedir(), ".a5c", "kanban-backlog.json");
|
|
16
|
+
function cloneRecord(value) {
|
|
17
|
+
return value ? { ...value } : undefined;
|
|
18
|
+
}
|
|
19
|
+
async function readAutomationStorage(backlogFilePath) {
|
|
20
|
+
try {
|
|
21
|
+
const raw = await node_fs_1.promises.readFile(backlogFilePath, "utf8");
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const errno = error;
|
|
26
|
+
if (errno.code === "ENOENT") {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function writeAutomationStorage(backlogFilePath, payload) {
|
|
33
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(backlogFilePath), { recursive: true });
|
|
34
|
+
await node_fs_1.promises.writeFile(backlogFilePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
35
|
+
}
|
|
36
|
+
function createTriggerMetadata(rule) {
|
|
37
|
+
if (rule.trigger.type === "timer") {
|
|
38
|
+
return {
|
|
39
|
+
triggerType: "timer",
|
|
40
|
+
cron: rule.trigger.cron,
|
|
41
|
+
timezone: rule.trigger.timezone,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
triggerType: "webhook",
|
|
46
|
+
port: rule.trigger.port,
|
|
47
|
+
path: rule.trigger.path ?? "/trigger",
|
|
48
|
+
method: rule.trigger.method ?? "POST",
|
|
49
|
+
sourceEvent: rule.trigger.sourceEvent,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function nextIssueNumber(project, issues) {
|
|
53
|
+
const prefix = `${project.key}-AUTO-`;
|
|
54
|
+
return issues.reduce((max, issue) => {
|
|
55
|
+
if (!issue.key.startsWith(prefix)) {
|
|
56
|
+
return max;
|
|
57
|
+
}
|
|
58
|
+
const rawNumber = issue.key.slice(prefix.length);
|
|
59
|
+
const parsed = Number.parseInt(rawNumber, 10);
|
|
60
|
+
return Number.isFinite(parsed) ? Math.max(max, parsed) : max;
|
|
61
|
+
}, 0) + 1;
|
|
62
|
+
}
|
|
63
|
+
function resolveProjectCollections(project, template) {
|
|
64
|
+
const labelsById = new Map(project.labels.map((label) => [label.id, label]));
|
|
65
|
+
const assigneesById = new Map(project.assignees.map((assignee) => [assignee.id, assignee]));
|
|
66
|
+
return {
|
|
67
|
+
labels: (template.labelIds ?? [])
|
|
68
|
+
.map((labelId) => labelsById.get(labelId))
|
|
69
|
+
.filter((label) => Boolean(label)),
|
|
70
|
+
assignees: (template.assigneeIds ?? [])
|
|
71
|
+
.map((assigneeId) => assigneesById.get(assigneeId))
|
|
72
|
+
.filter((assignee) => Boolean(assignee)),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function resolveIssueSource(rule) {
|
|
76
|
+
if (rule.template.issueSource) {
|
|
77
|
+
return { ...rule.template.issueSource };
|
|
78
|
+
}
|
|
79
|
+
if (rule.source.kind === "config-file") {
|
|
80
|
+
return {
|
|
81
|
+
kind: "file",
|
|
82
|
+
path: rule.source.path,
|
|
83
|
+
externalId: rule.source.externalId ?? rule.id,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
kind: "run-derived",
|
|
88
|
+
externalId: rule.source.externalId ?? rule.id,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createAcceptanceCriteria(issueId, criteria) {
|
|
92
|
+
return (criteria ?? []).map((title, index) => ({
|
|
93
|
+
id: `${issueId}-ac-${index + 1}`,
|
|
94
|
+
title,
|
|
95
|
+
satisfied: false,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
function createDecomposition(issueId, items) {
|
|
99
|
+
return (items ?? []).map((item, index) => ({
|
|
100
|
+
id: `${issueId}-decomp-${index + 1}`,
|
|
101
|
+
title: item.title,
|
|
102
|
+
kind: item.kind,
|
|
103
|
+
status: item.status,
|
|
104
|
+
issueId: undefined,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
function createAutomationIssue(rule, project, issues, now) {
|
|
108
|
+
const sequence = nextIssueNumber(project, issues);
|
|
109
|
+
const issueKey = `${project.key}-AUTO-${String(sequence).padStart(3, "0")}`;
|
|
110
|
+
const issueId = issueKey;
|
|
111
|
+
const { labels, assignees } = resolveProjectCollections(project, rule.template);
|
|
112
|
+
return {
|
|
113
|
+
id: issueId,
|
|
114
|
+
key: issueKey,
|
|
115
|
+
projectId: rule.target.projectId,
|
|
116
|
+
title: rule.template.title,
|
|
117
|
+
summary: rule.template.summary,
|
|
118
|
+
description: rule.template.description,
|
|
119
|
+
status: rule.template.status ?? "backlog",
|
|
120
|
+
priority: rule.template.priority ?? "medium",
|
|
121
|
+
labels,
|
|
122
|
+
assignees,
|
|
123
|
+
dependencies: [],
|
|
124
|
+
acceptanceCriteria: createAcceptanceCriteria(issueId, rule.template.acceptanceCriteria),
|
|
125
|
+
decomposition: createDecomposition(issueId, rule.template.decomposition),
|
|
126
|
+
childIssueIds: [],
|
|
127
|
+
createdAt: now,
|
|
128
|
+
updatedAt: now,
|
|
129
|
+
source: resolveIssueSource(rule),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function createExecutionRecord(rule, event, now, overrides) {
|
|
133
|
+
return {
|
|
134
|
+
id: `automation-exec-${(0, node_crypto_1.randomUUID)().toLowerCase()}`,
|
|
135
|
+
ruleId: rule.id,
|
|
136
|
+
ruleName: rule.name,
|
|
137
|
+
triggerType: rule.trigger.type,
|
|
138
|
+
status: overrides.status,
|
|
139
|
+
triggeredAt: now,
|
|
140
|
+
triggeredBy: `daemon-${rule.trigger.type}`,
|
|
141
|
+
source: {
|
|
142
|
+
...rule.source,
|
|
143
|
+
metadata: cloneRecord(rule.source.metadata),
|
|
144
|
+
},
|
|
145
|
+
projectId: rule.target.projectId,
|
|
146
|
+
boardProjectId: rule.target.boardProjectId,
|
|
147
|
+
issueId: overrides.issue?.id,
|
|
148
|
+
issueKey: overrides.issue?.key,
|
|
149
|
+
issueSource: overrides.issue?.source,
|
|
150
|
+
stateAtExecution: rule.state,
|
|
151
|
+
skipReason: overrides.skipReason,
|
|
152
|
+
inputs: event.inputs ? { ...event.inputs } : undefined,
|
|
153
|
+
metadata: createTriggerMetadata(rule),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function executeAutomationTrigger(event, options = {}) {
|
|
157
|
+
const backlogFilePath = options.backlogFilePath ?? DEFAULT_BACKLOG_FILE_PATH;
|
|
158
|
+
const now = options.now?.() ?? new Date().toISOString();
|
|
159
|
+
const storage = await readAutomationStorage(backlogFilePath);
|
|
160
|
+
const rules = [...(storage.automationRules ?? [])];
|
|
161
|
+
const rule = rules.find((candidate) => candidate.id === event.rule.id) ?? event.rule;
|
|
162
|
+
if (rule.state !== "active") {
|
|
163
|
+
const execution = createExecutionRecord(rule, event, now, {
|
|
164
|
+
status: "skipped",
|
|
165
|
+
skipReason: `Rule ${rule.id} is ${rule.state}.`,
|
|
166
|
+
});
|
|
167
|
+
await writeAutomationStorage(backlogFilePath, {
|
|
168
|
+
...storage,
|
|
169
|
+
automationExecutions: [...(storage.automationExecutions ?? []), execution],
|
|
170
|
+
});
|
|
171
|
+
return execution;
|
|
172
|
+
}
|
|
173
|
+
const projects = [...(storage.projects ?? [])];
|
|
174
|
+
const issueProject = projects.find((candidate) => candidate.id === rule.target.projectId);
|
|
175
|
+
const boardProject = projects.find((candidate) => candidate.id === rule.target.boardProjectId);
|
|
176
|
+
if (!issueProject || !boardProject) {
|
|
177
|
+
const missingTarget = !issueProject
|
|
178
|
+
? `Project ${rule.target.projectId}`
|
|
179
|
+
: `Board project ${rule.target.boardProjectId}`;
|
|
180
|
+
const execution = createExecutionRecord(rule, event, now, {
|
|
181
|
+
status: "skipped",
|
|
182
|
+
skipReason: `${missingTarget} not found.`,
|
|
183
|
+
});
|
|
184
|
+
await writeAutomationStorage(backlogFilePath, {
|
|
185
|
+
...storage,
|
|
186
|
+
automationExecutions: [...(storage.automationExecutions ?? []), execution],
|
|
187
|
+
});
|
|
188
|
+
return execution;
|
|
189
|
+
}
|
|
190
|
+
const issues = [...(storage.issues ?? [])];
|
|
191
|
+
const issue = createAutomationIssue(rule, issueProject, issues, now);
|
|
192
|
+
const execution = createExecutionRecord(rule, event, now, {
|
|
193
|
+
status: "created",
|
|
194
|
+
issue,
|
|
195
|
+
});
|
|
196
|
+
const nextRules = rules.length > 0
|
|
197
|
+
? rules.map((candidate) => (candidate.id === rule.id
|
|
198
|
+
? {
|
|
199
|
+
...candidate,
|
|
200
|
+
audit: {
|
|
201
|
+
...candidate.audit,
|
|
202
|
+
lastTriggeredAt: now,
|
|
203
|
+
lastTriggeredBy: execution.triggeredBy,
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
: candidate))
|
|
207
|
+
: storage.automationRules;
|
|
208
|
+
const nextProjects = projects.map((candidate) => (candidate.id === issueProject.id || candidate.id === boardProject.id
|
|
209
|
+
? {
|
|
210
|
+
...candidate,
|
|
211
|
+
issueIds: Array.from(new Set([...candidate.issueIds, issue.id])),
|
|
212
|
+
}
|
|
213
|
+
: candidate));
|
|
214
|
+
await writeAutomationStorage(backlogFilePath, {
|
|
215
|
+
...storage,
|
|
216
|
+
projects: nextProjects,
|
|
217
|
+
issues: [...issues, issue],
|
|
218
|
+
automationRules: nextRules,
|
|
219
|
+
automationExecutions: [...(storage.automationExecutions ?? []), execution],
|
|
220
|
+
});
|
|
221
|
+
return execution;
|
|
222
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAP-REMOTE-001: Daemon Config — load/validate/write daemon configuration.
|
|
3
|
+
*/
|
|
4
|
+
import type { ApiResult } from "../apiResult";
|
|
5
|
+
import type { DaemonConfig } from "./types";
|
|
6
|
+
export declare function loadDaemonConfig(configPath: string): Promise<ApiResult<DaemonConfig>>;
|
|
7
|
+
export declare function writeDaemonConfig(config: DaemonConfig, configPath: string): Promise<ApiResult<void>>;
|
|
8
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/daemon/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAkJ5C,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CA0BlC;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAgB1B"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GAP-REMOTE-001: Daemon Config — load/validate/write daemon configuration.
|
|
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.loadDaemonConfig = loadDaemonConfig;
|
|
40
|
+
exports.writeDaemonConfig = writeDaemonConfig;
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const apiResult_1 = require("../apiResult");
|
|
44
|
+
const VALID_TRIGGER_TYPES = new Set(["file", "webhook", "timer"]);
|
|
45
|
+
const VALID_AUTOMATION_STATES = new Set(["draft", "active", "paused", "disabled", "archived"]);
|
|
46
|
+
const DEFAULT_MAX_CONCURRENT_RUNS = 4;
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
function isUnknownArray(value) {
|
|
51
|
+
return Array.isArray(value);
|
|
52
|
+
}
|
|
53
|
+
function validateFileTrigger(raw, index) {
|
|
54
|
+
if (!isRecord(raw)) {
|
|
55
|
+
return `Trigger at index ${index} must be an object`;
|
|
56
|
+
}
|
|
57
|
+
if (raw.type !== "file") {
|
|
58
|
+
return `Invalid trigger type at index ${index}: ${String(raw.type)}. Must be one of: file, webhook, timer`;
|
|
59
|
+
}
|
|
60
|
+
if (typeof raw.processId !== "string" || !raw.processId) {
|
|
61
|
+
return `Missing processId at trigger index ${index}`;
|
|
62
|
+
}
|
|
63
|
+
if (typeof raw.entrypoint !== "string" || !raw.entrypoint) {
|
|
64
|
+
return `Missing entrypoint at trigger index ${index}`;
|
|
65
|
+
}
|
|
66
|
+
if (typeof raw.pattern !== "string" || !raw.pattern) {
|
|
67
|
+
return `Missing pattern at trigger index ${index}`;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function validateAutomationRule(raw, index) {
|
|
72
|
+
if (!isRecord(raw)) {
|
|
73
|
+
return `Trigger at index ${index} must be an object`;
|
|
74
|
+
}
|
|
75
|
+
if (typeof raw.id !== "string" || !raw.id) {
|
|
76
|
+
return `Missing automation rule id at trigger index ${index}`;
|
|
77
|
+
}
|
|
78
|
+
if (typeof raw.name !== "string" || !raw.name) {
|
|
79
|
+
return `Missing automation rule name at trigger index ${index}`;
|
|
80
|
+
}
|
|
81
|
+
if (typeof raw.state !== "string" || !VALID_AUTOMATION_STATES.has(raw.state)) {
|
|
82
|
+
return `Invalid automation state at trigger index ${index}: ${String(raw.state)}`;
|
|
83
|
+
}
|
|
84
|
+
const trigger = raw.trigger;
|
|
85
|
+
if (!isRecord(trigger) || typeof trigger.type !== "string" || !VALID_TRIGGER_TYPES.has(trigger.type)) {
|
|
86
|
+
return `Invalid automation trigger type at index ${index}: ${String(isRecord(trigger) ? trigger.type : trigger)}`;
|
|
87
|
+
}
|
|
88
|
+
if (trigger.type === "file") {
|
|
89
|
+
return `Automation rules do not support file triggers at index ${index}`;
|
|
90
|
+
}
|
|
91
|
+
if (trigger.type === "timer" && (typeof trigger.cron !== "string" || !trigger.cron)) {
|
|
92
|
+
return `Missing cron for timer automation at trigger index ${index}`;
|
|
93
|
+
}
|
|
94
|
+
if (trigger.type === "webhook") {
|
|
95
|
+
if (typeof trigger.port !== "number" || trigger.port <= 0) {
|
|
96
|
+
return `Missing port for webhook automation at trigger index ${index}`;
|
|
97
|
+
}
|
|
98
|
+
if (trigger.path !== undefined && (typeof trigger.path !== "string" || !trigger.path)) {
|
|
99
|
+
return `Invalid webhook path at trigger index ${index}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const target = raw.target;
|
|
103
|
+
if (!isRecord(target) || typeof target.projectId !== "string" || !target.projectId) {
|
|
104
|
+
return `Missing target.projectId at trigger index ${index}`;
|
|
105
|
+
}
|
|
106
|
+
if (typeof target.boardProjectId !== "string" || !target.boardProjectId) {
|
|
107
|
+
return `Missing target.boardProjectId at trigger index ${index}`;
|
|
108
|
+
}
|
|
109
|
+
const template = raw.template;
|
|
110
|
+
if (!isRecord(template) || typeof template.title !== "string" || !template.title) {
|
|
111
|
+
return `Missing template.title at trigger index ${index}`;
|
|
112
|
+
}
|
|
113
|
+
const routing = raw.routing;
|
|
114
|
+
if (!isRecord(routing)) {
|
|
115
|
+
return `Missing routing block at trigger index ${index}`;
|
|
116
|
+
}
|
|
117
|
+
if (!isRecord(routing.issue) || routing.issue.action !== "canonical-issue-create") {
|
|
118
|
+
return `Invalid routing.issue action at trigger index ${index}`;
|
|
119
|
+
}
|
|
120
|
+
if (!isRecord(routing.board) || routing.board.action !== "shared-board-derive") {
|
|
121
|
+
return `Invalid routing.board action at trigger index ${index}`;
|
|
122
|
+
}
|
|
123
|
+
if (routing.mutateBoardDirectly !== false) {
|
|
124
|
+
return `Automation routing must disable direct board mutation at trigger index ${index}`;
|
|
125
|
+
}
|
|
126
|
+
const source = raw.source;
|
|
127
|
+
if (!isRecord(source) || typeof source.kind !== "string" || !source.kind) {
|
|
128
|
+
return `Missing source.kind at trigger index ${index}`;
|
|
129
|
+
}
|
|
130
|
+
const audit = raw.audit;
|
|
131
|
+
if (!isRecord(audit) || typeof audit.createdAt !== "string" || !audit.createdAt) {
|
|
132
|
+
return `Missing audit.createdAt at trigger index ${index}`;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function validateConfig(raw) {
|
|
137
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
138
|
+
return { valid: false, error: "Config must be a JSON object" };
|
|
139
|
+
}
|
|
140
|
+
const obj = raw;
|
|
141
|
+
if (typeof obj.workspace !== "string" || !obj.workspace) {
|
|
142
|
+
return { valid: false, error: "Missing required field: workspace" };
|
|
143
|
+
}
|
|
144
|
+
if (!isUnknownArray(obj.triggers)) {
|
|
145
|
+
return { valid: false, error: "Missing required field: triggers (must be an array)" };
|
|
146
|
+
}
|
|
147
|
+
for (let i = 0; i < obj.triggers.length; i++) {
|
|
148
|
+
const trigger = obj.triggers[i];
|
|
149
|
+
const error = isRecord(trigger) && trigger.type === "file"
|
|
150
|
+
? validateFileTrigger(trigger, i)
|
|
151
|
+
: validateAutomationRule(trigger, i);
|
|
152
|
+
if (error) {
|
|
153
|
+
return { valid: false, error };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const maxConcurrent = typeof obj.maxConcurrentRuns === "number"
|
|
157
|
+
? obj.maxConcurrentRuns
|
|
158
|
+
: DEFAULT_MAX_CONCURRENT_RUNS;
|
|
159
|
+
return {
|
|
160
|
+
valid: true,
|
|
161
|
+
config: {
|
|
162
|
+
workspace: obj.workspace,
|
|
163
|
+
triggers: obj.triggers,
|
|
164
|
+
maxConcurrentRuns: maxConcurrent,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function loadDaemonConfig(configPath) {
|
|
169
|
+
try {
|
|
170
|
+
let raw;
|
|
171
|
+
try {
|
|
172
|
+
raw = await node_fs_1.promises.readFile(configPath, "utf-8");
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return (0, apiResult_1.fail)("CONFIG_NOT_FOUND", `Config file not found: ${configPath}`);
|
|
176
|
+
}
|
|
177
|
+
let parsed;
|
|
178
|
+
try {
|
|
179
|
+
parsed = JSON.parse(raw);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return (0, apiResult_1.fail)("INVALID_CONFIG", `Failed to parse config as JSON: ${configPath}`);
|
|
183
|
+
}
|
|
184
|
+
const result = validateConfig(parsed);
|
|
185
|
+
if (!result.valid) {
|
|
186
|
+
return (0, apiResult_1.fail)("INVALID_CONFIG", result.error);
|
|
187
|
+
}
|
|
188
|
+
return (0, apiResult_1.ok)(result.config);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
192
|
+
return (0, apiResult_1.fail)("INTERNAL_ERROR", msg);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function writeDaemonConfig(config, configPath) {
|
|
196
|
+
try {
|
|
197
|
+
const dir = path.dirname(configPath);
|
|
198
|
+
await node_fs_1.promises.mkdir(dir, { recursive: true });
|
|
199
|
+
const content = JSON.stringify(config, null, 2);
|
|
200
|
+
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
201
|
+
await node_fs_1.promises.writeFile(tmpPath, content, "utf-8");
|
|
202
|
+
await node_fs_1.promises.rename(tmpPath, configPath);
|
|
203
|
+
return (0, apiResult_1.ok)(undefined);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
207
|
+
return (0, apiResult_1.fail)("INTERNAL_ERROR", msg);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAP-REMOTE-001: Daemon JSONL logging.
|
|
3
|
+
*
|
|
4
|
+
* Appends structured JSON lines to a daemon activation log file.
|
|
5
|
+
*/
|
|
6
|
+
export interface DaemonLogEntry {
|
|
7
|
+
timestamp: string;
|
|
8
|
+
event: string;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function appendDaemonLog(logDir: string, entry: DaemonLogEntry): Promise<void>;
|
|
12
|
+
export declare function readDaemonLog(logDir: string): Promise<DaemonLogEntry[]>;
|
|
13
|
+
//# sourceMappingURL=daemonLog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemonLog.d.ts","sourceRoot":"","sources":["../../src/daemon/daemonLog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,EAAE,CAAC,CAY3B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GAP-REMOTE-001: Daemon JSONL logging.
|
|
4
|
+
*
|
|
5
|
+
* Appends structured JSON lines to a daemon activation log file.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.appendDaemonLog = appendDaemonLog;
|
|
42
|
+
exports.readDaemonLog = readDaemonLog;
|
|
43
|
+
const node_fs_1 = require("node:fs");
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
async function appendDaemonLog(logDir, entry) {
|
|
46
|
+
await node_fs_1.promises.mkdir(logDir, { recursive: true });
|
|
47
|
+
const logPath = path.join(logDir, "daemon.jsonl");
|
|
48
|
+
const line = JSON.stringify(entry) + "\n";
|
|
49
|
+
await node_fs_1.promises.appendFile(logPath, line, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
async function readDaemonLog(logDir) {
|
|
52
|
+
const logPath = path.join(logDir, "daemon.jsonl");
|
|
53
|
+
try {
|
|
54
|
+
const content = await node_fs_1.promises.readFile(logPath, "utf-8");
|
|
55
|
+
return content
|
|
56
|
+
.trim()
|
|
57
|
+
.split("\n")
|
|
58
|
+
.filter((line) => line.trim())
|
|
59
|
+
.map((line) => JSON.parse(line));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAP-REMOTE-001: File Watcher — debounced fs.watch trigger.
|
|
3
|
+
*
|
|
4
|
+
* Watches directories for file changes matching glob patterns,
|
|
5
|
+
* firing trigger callbacks with configurable debounce.
|
|
6
|
+
*/
|
|
7
|
+
import type { FileTriggerConfig, FileWatcherHandle, TriggerCallback } from "./types";
|
|
8
|
+
export declare function createFileWatcher(triggers: FileTriggerConfig[], onTrigger: TriggerCallback): FileWatcherHandle;
|
|
9
|
+
//# sourceMappingURL=fileWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileWatcher.d.ts","sourceRoot":"","sources":["../../src/daemon/fileWatcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmDrF,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,SAAS,EAAE,eAAe,GACzB,iBAAiB,CA6DnB"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GAP-REMOTE-001: File Watcher — debounced fs.watch trigger.
|
|
4
|
+
*
|
|
5
|
+
* Watches directories for file changes matching glob patterns,
|
|
6
|
+
* firing trigger callbacks with configurable debounce.
|
|
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.createFileWatcher = createFileWatcher;
|
|
43
|
+
const fsSync = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const DEFAULT_DEBOUNCE_MS = 500;
|
|
46
|
+
/**
|
|
47
|
+
* Extract the extension pattern from a glob (e.g., "**\/*.ts" -> ".ts").
|
|
48
|
+
* Returns null if no extension can be extracted.
|
|
49
|
+
*/
|
|
50
|
+
function extractExtension(pattern) {
|
|
51
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
52
|
+
// Match patterns like **/*.ts, **/*.test.ts, *.js
|
|
53
|
+
const match = normalized.match(/\*(\.[a-zA-Z0-9.]+)$/);
|
|
54
|
+
return match ? match[1] : null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if a filename matches the pattern's file extension filter.
|
|
58
|
+
* This is a simplified matcher that works with common glob patterns like:
|
|
59
|
+
* - path/to/**\/*.ts -> matches any .ts file under path/to/
|
|
60
|
+
* - path/to/**\/*.test.ts -> matches any .test.ts file under path/to/
|
|
61
|
+
*/
|
|
62
|
+
function matchesExtension(filename, pattern) {
|
|
63
|
+
const ext = extractExtension(pattern);
|
|
64
|
+
if (!ext)
|
|
65
|
+
return true; // No extension filter = match all
|
|
66
|
+
return filename.endsWith(ext);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract the base directory from a glob pattern (everything before the first glob segment).
|
|
70
|
+
*/
|
|
71
|
+
function getWatchDir(pattern) {
|
|
72
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
73
|
+
const parts = normalized.split("/");
|
|
74
|
+
const dirParts = [];
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (part.includes("*") || part.includes("?") || part.includes("{")) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
dirParts.push(part);
|
|
80
|
+
}
|
|
81
|
+
return dirParts.join(path.sep) || ".";
|
|
82
|
+
}
|
|
83
|
+
function createFileWatcher(triggers, onTrigger) {
|
|
84
|
+
const entries = [];
|
|
85
|
+
let disposed = false;
|
|
86
|
+
for (const trigger of triggers) {
|
|
87
|
+
const watchDir = getWatchDir(trigger.pattern);
|
|
88
|
+
const debounceMs = trigger.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
89
|
+
const entry = {
|
|
90
|
+
trigger,
|
|
91
|
+
watcher: null,
|
|
92
|
+
debounceTimer: null,
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
entry.watcher = fsSync.watch(watchDir, { recursive: true }, (_eventType, filename) => {
|
|
96
|
+
if (disposed || !filename)
|
|
97
|
+
return;
|
|
98
|
+
if (!matchesExtension(filename, trigger.pattern))
|
|
99
|
+
return;
|
|
100
|
+
// Debounce: clear previous timer and set a new one
|
|
101
|
+
if (entry.debounceTimer) {
|
|
102
|
+
clearTimeout(entry.debounceTimer);
|
|
103
|
+
}
|
|
104
|
+
entry.debounceTimer = setTimeout(() => {
|
|
105
|
+
if (disposed)
|
|
106
|
+
return;
|
|
107
|
+
void onTrigger({
|
|
108
|
+
type: "file",
|
|
109
|
+
processId: trigger.processId,
|
|
110
|
+
entrypoint: trigger.entrypoint,
|
|
111
|
+
});
|
|
112
|
+
}, debounceMs);
|
|
113
|
+
});
|
|
114
|
+
entry.watcher.on("error", () => {
|
|
115
|
+
if (entry.watcher) {
|
|
116
|
+
entry.watcher.close();
|
|
117
|
+
entry.watcher = null;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Watch dir doesn't exist yet — skip
|
|
123
|
+
}
|
|
124
|
+
entries.push(entry);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
dispose() {
|
|
128
|
+
disposed = true;
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
if (entry.debounceTimer) {
|
|
131
|
+
clearTimeout(entry.debounceTimer);
|
|
132
|
+
entry.debounceTimer = null;
|
|
133
|
+
}
|
|
134
|
+
if (entry.watcher) {
|
|
135
|
+
entry.watcher.close();
|
|
136
|
+
entry.watcher = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|