@nettee/beacon 0.2.9 → 0.3.0
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 +167 -7
- package/dist/config/event-credentials.js +67 -0
- package/dist/config/global.js +23 -0
- package/dist/config/profile.js +26 -0
- package/dist/dashboard/records.js +10 -1
- package/dist/dashboard/server.js +3 -0
- package/dist/doctor.js +3 -0
- package/dist/events/cloudevent.js +93 -0
- package/dist/events/pipeline.js +99 -0
- package/dist/events/server.js +157 -0
- package/dist/events/store.js +183 -0
- package/dist/outcome/instructions.js +1 -1
- package/dist/run/orchestrator.js +21 -2
- package/dist/run/queue.js +32 -7
- package/dist/runtime/system-prompt.js +12 -0
- package/dist/service.js +69 -15
- package/dist/state/trigger-store.js +41 -37
- package/examples/config.yaml +8 -0
- package/examples/event-credentials.json.example +10 -0
- package/examples/profiles/example/profile.yaml +8 -0
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { chmod, mkdir, open, readdir, readFile, rename, } from "node:fs/promises";
|
|
2
|
+
import { chmod, mkdir, open, readdir, readFile, rename, rm, } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { failureCodes, } from "../domain/types.js";
|
|
6
|
+
import { cloudEventSchema } from "../events/cloudevent.js";
|
|
6
7
|
import { feedbackRecordSchema, finalOutcomeContentSchema, parseFinalOutcomeContent, } from "../outcome/content.js";
|
|
7
8
|
const timestamp = z.string().datetime({ offset: true });
|
|
8
9
|
const failureSchema = z
|
|
@@ -23,6 +24,7 @@ const messageSchema = z
|
|
|
23
24
|
})
|
|
24
25
|
.strict();
|
|
25
26
|
const inputSchema = z.discriminatedUnion("kind", [
|
|
27
|
+
z.object({ kind: z.literal("event"), event: cloudEventSchema }).strict(),
|
|
26
28
|
z
|
|
27
29
|
.object({
|
|
28
30
|
kind: z.literal("feishu_message"),
|
|
@@ -202,21 +204,14 @@ export class TriggerStore {
|
|
|
202
204
|
});
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
|
-
async
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
? error.cause
|
|
213
|
-
: undefined;
|
|
214
|
-
if (cause?.code !== "ENOENT")
|
|
215
|
-
throw error;
|
|
216
|
-
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
217
|
-
}
|
|
207
|
+
async syncDirectory(directory) {
|
|
208
|
+
const handle = await open(directory, "r");
|
|
209
|
+
try {
|
|
210
|
+
await handle.sync();
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
await handle.close();
|
|
218
214
|
}
|
|
219
|
-
return this.read(triggerKey);
|
|
220
215
|
}
|
|
221
216
|
async write(triggerKey, record) {
|
|
222
217
|
const validated = validateRecord(record);
|
|
@@ -231,13 +226,7 @@ export class TriggerStore {
|
|
|
231
226
|
await handle.close();
|
|
232
227
|
}
|
|
233
228
|
await rename(temporary, this.recordPath(triggerKey));
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
await directoryHandle.sync();
|
|
237
|
-
}
|
|
238
|
-
finally {
|
|
239
|
-
await directoryHandle.close();
|
|
240
|
-
}
|
|
229
|
+
await this.syncDirectory(directory);
|
|
241
230
|
}
|
|
242
231
|
async claim(request) {
|
|
243
232
|
if (request.sourceKey.length < 2 ||
|
|
@@ -247,18 +236,6 @@ export class TriggerStore {
|
|
|
247
236
|
await this.prepare();
|
|
248
237
|
const triggerKey = keyFor(this.profileId, request.sourceKey);
|
|
249
238
|
const directory = join(this.root, triggerKey);
|
|
250
|
-
try {
|
|
251
|
-
await mkdir(directory, { mode: 0o700 });
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
if (error.code === "EEXIST") {
|
|
255
|
-
return {
|
|
256
|
-
created: false,
|
|
257
|
-
record: await this.readConcurrentClaim(triggerKey),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
throw new Error(`Cannot claim Trigger ${triggerKey}`, { cause: error });
|
|
261
|
-
}
|
|
262
239
|
const record = {
|
|
263
240
|
version: 1,
|
|
264
241
|
triggerKey,
|
|
@@ -268,10 +245,37 @@ export class TriggerStore {
|
|
|
268
245
|
acceptedAt: (request.acceptedAt ?? new Date()).toISOString(),
|
|
269
246
|
target: request.target,
|
|
270
247
|
...(request.notifyTarget ? { notifyTarget: request.notifyTarget } : {}),
|
|
248
|
+
...(request.input ? { input: request.input } : {}),
|
|
271
249
|
...(request.ingress ? { ingress: request.ingress } : {}),
|
|
272
250
|
};
|
|
273
|
-
|
|
274
|
-
|
|
251
|
+
const temporaryKey = `.claim-${randomUUID()}`;
|
|
252
|
+
const temporaryDirectory = join(this.root, temporaryKey);
|
|
253
|
+
await mkdir(temporaryDirectory, { mode: 0o700 });
|
|
254
|
+
try {
|
|
255
|
+
// Publish only after the complete record and its directory are durable.
|
|
256
|
+
// POSIX rename may replace a legacy empty claim directory, but cannot
|
|
257
|
+
// replace a populated winner from another concurrent claimant.
|
|
258
|
+
await this.write(temporaryKey, record);
|
|
259
|
+
try {
|
|
260
|
+
await rename(temporaryDirectory, directory);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
const code = error.code;
|
|
264
|
+
if (code !== "EEXIST" && code !== "ENOTEMPTY") {
|
|
265
|
+
throw new Error(`Cannot publish Trigger ${triggerKey}`, {
|
|
266
|
+
cause: error,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const existing = await this.read(triggerKey);
|
|
270
|
+
await this.syncDirectory(this.root);
|
|
271
|
+
return { created: false, record: existing };
|
|
272
|
+
}
|
|
273
|
+
await this.syncDirectory(this.root);
|
|
274
|
+
return { created: true, record };
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
278
|
+
}
|
|
275
279
|
}
|
|
276
280
|
async update(triggerKey, transform) {
|
|
277
281
|
const current = await this.read(triggerKey);
|
|
@@ -286,7 +290,7 @@ export class TriggerStore {
|
|
|
286
290
|
await this.prepare();
|
|
287
291
|
const entries = await readdir(this.root, { withFileTypes: true });
|
|
288
292
|
const records = await Promise.all(entries
|
|
289
|
-
.filter((entry) => entry.isDirectory())
|
|
293
|
+
.filter((entry) => entry.isDirectory() && !/^\.claim-[0-9a-f-]{36}$/.test(entry.name))
|
|
290
294
|
.map((entry) => this.read(entry.name)));
|
|
291
295
|
return records.sort((left, right) => left.acceptedAt.localeCompare(right.acceptedAt) ||
|
|
292
296
|
left.triggerId.localeCompare(right.triggerId));
|
package/examples/config.yaml
CHANGED
|
@@ -15,3 +15,11 @@ dashboard:
|
|
|
15
15
|
enabled: true
|
|
16
16
|
listen: 0.0.0.0
|
|
17
17
|
port: 46183
|
|
18
|
+
# Opt in after creating a private event-credentials.json; see README.
|
|
19
|
+
events:
|
|
20
|
+
enabled: false
|
|
21
|
+
listen: 127.0.0.1
|
|
22
|
+
port: 46184
|
|
23
|
+
max_body_bytes: 262144
|
|
24
|
+
max_pending: 1000
|
|
25
|
+
credentials_file: event-credentials.json
|
|
@@ -12,3 +12,11 @@ schedules:
|
|
|
12
12
|
input: Prepare the weekday brief.
|
|
13
13
|
notify:
|
|
14
14
|
chat_id: REPLACE_WITH_GROUP_CHAT_ID
|
|
15
|
+
# Optional external event subscription. Both lists use exact matches.
|
|
16
|
+
# listener:
|
|
17
|
+
# sources:
|
|
18
|
+
# - https://deploy.example.com/production
|
|
19
|
+
# types:
|
|
20
|
+
# - com.example.deployment.completed.v1
|
|
21
|
+
# notify:
|
|
22
|
+
# chat_id: REPLACE_WITH_GROUP_CHAT_ID
|