@nettee/beacon 0.3.2 → 0.3.4

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 CHANGED
@@ -111,16 +111,16 @@ per-Run directories with mode `0700` when Pi starts.
111
111
 
112
112
  Configuration is strict: unknown YAML/JSON fields, YAML aliases or warnings, missing paths, duplicate Schedule IDs, invalid timezones/cron expressions, a missing complete `persona.md` + `task.md` pair, escaped Profile markdown, permissive secret or runtime-environment permissions, missing Profile credentials, and unknown Feishu channel names all fail startup. Beacon validates all Profiles before opening a Feishu connection.
113
113
 
114
- Feishu delivery destinations are registered globally in `~/.beacon/channels.yaml`
115
- (beside `config.yaml`), then referenced by semantic `name` from each Profile:
114
+ Group notify destinations are registered globally in `~/.beacon/channels.yaml`
115
+ (beside `config.yaml`), then referenced by semantic `name` from each Profile's
116
+ `notify`. Admin DMs are **not** channel entries: each Profile keeps a raw
117
+ `admin.chat_id` (one bot×person private chat; putting those in the registry
118
+ would explode as bots and people grow).
116
119
 
117
120
  ```yaml
118
- # ~/.beacon/channels.yaml
121
+ # ~/.beacon/channels.yaml — group chats only
119
122
  version: 1
120
123
  channels:
121
- - name: admin-dm
122
- description: Example admin direct chat
123
- chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
124
124
  - name: weekday-brief-group
125
125
  description: Example weekday brief notify group
126
126
  chat_id: REPLACE_WITH_GROUP_CHAT_ID
@@ -129,7 +129,7 @@ channels:
129
129
  ```yaml
130
130
  # profile.yaml destinations
131
131
  admin:
132
- name: admin-dm
132
+ chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
133
133
  schedules:
134
134
  - id: weekday-brief
135
135
  cron: "0 9 * * 1-5"
@@ -140,11 +140,12 @@ schedules:
140
140
  ```
141
141
 
142
142
  Each channel has `name` (stable slug), `description` (human-readable Chinese or
143
- English label), and `chat_id` (the real Feishu chat id). Profile destinations
144
- bind `name` and may optionally override `description` for local readability;
143
+ English label), and `chat_id` (the real Feishu group chat id). Profile `notify`
144
+ binds `name` and may optionally override `description` for local readability;
145
145
  Beacon resolves `name` → `chat_id` at load time. Unknown names fail startup.
146
- Legacy `chat_id` directly under `admin` / `notify` is still accepted so existing
147
- hosts can migrate one Profile at a time; new Profiles should use `name`.
146
+ `admin` must use bare `chat_id` (not a channel `name`). Legacy bare `chat_id`
147
+ under `notify` still loads so hosts can migrate one Profile at a time; new
148
+ `notify` entries should use `name`.
148
149
 
149
150
  Each Schedule uses a five-field cron expression and an IANA timezone. Profile
150
151
  `admin` is the private chat used for schedule `reply_text` /
@@ -216,7 +217,7 @@ Subscribe in the desired `profile.yaml`:
216
217
 
217
218
  ```yaml
218
219
  admin:
219
- name: admin-dm
220
+ chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
220
221
  listener:
221
222
  sources:
222
223
  - https://deploy.example.com/production
@@ -10,6 +10,11 @@ const profileIdPattern = /^[a-z0-9](?:[a-z0-9_-]{0,62})$/;
10
10
  export const profilePersonaFile = "persona.md";
11
11
  export const profileTaskFile = "task.md";
12
12
  export const workspaceProfileDirectoryName = ".beacon-profile";
13
+ /** Admin DM: raw Feishu chat_id only (bot×person DMs are not channel registry entries). */
14
+ const adminDestinationSchema = z
15
+ .object({ chat_id: z.string().trim().min(1) })
16
+ .strict();
17
+ /** Group notify: prefer semantic name from ~/.beacon/channels.yaml; bare chat_id still loads. */
13
18
  const namedDestinationSchema = z
14
19
  .object({
15
20
  name: z
@@ -21,7 +26,7 @@ const namedDestinationSchema = z
21
26
  const legacyChatDestinationSchema = z
22
27
  .object({ chat_id: z.string().trim().min(1) })
23
28
  .strict();
24
- const destinationSchema = z.union([
29
+ const notifyDestinationSchema = z.union([
25
30
  namedDestinationSchema,
26
31
  legacyChatDestinationSchema,
27
32
  ]);
@@ -47,7 +52,7 @@ const scheduleSchema = z
47
52
  }
48
53
  }, "Schedule timezone must be a valid IANA timezone"),
49
54
  input: z.string().trim().min(1),
50
- notify: destinationSchema.optional(),
55
+ notify: notifyDestinationSchema.optional(),
51
56
  })
52
57
  .strict();
53
58
  const profileDocumentSchema = z
@@ -60,13 +65,13 @@ const profileDocumentSchema = z
60
65
  id: z.string().min(1),
61
66
  })
62
67
  .strict(),
63
- admin: destinationSchema.optional(),
68
+ admin: adminDestinationSchema.optional(),
64
69
  schedules: z.array(scheduleSchema).default([]),
65
70
  listener: z
66
71
  .object({
67
72
  sources: z.array(eventSourceSchema).min(1),
68
73
  types: z.array(z.string().min(1)).min(1),
69
- notify: destinationSchema.optional(),
74
+ notify: notifyDestinationSchema.optional(),
70
75
  })
71
76
  .strict()
72
77
  .optional(),
@@ -163,7 +168,7 @@ async function loadPersonaAndTask(profileId, workspace, profileDirectory) {
163
168
  ];
164
169
  throw new Error(`Profile ${profileId} has no complete ${profilePersonaFile} + ${profileTaskFile} pair. Missing: ${missing.join(", ")}`);
165
170
  }
166
- function resolveDestination(destination, channels, channelsPath) {
171
+ function resolveNotifyDestination(destination, channels, channelsPath) {
167
172
  if ("chat_id" in destination) {
168
173
  return { chatId: destination.chat_id };
169
174
  }
@@ -202,9 +207,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
202
207
  if (config.listener && !config.admin) {
203
208
  throw new Error(`Profile ${profileId} with listener must declare admin`);
204
209
  }
205
- const admin = config.admin
206
- ? resolveDestination(config.admin, channels, channelsPath)
207
- : undefined;
210
+ const admin = config.admin ? { chatId: config.admin.chat_id } : undefined;
208
211
  const canonicalProfileDirectory = await realpath(profileDirectory);
209
212
  const workspace = isAbsolute(config.workspace)
210
213
  ? config.workspace
@@ -238,7 +241,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
238
241
  types: config.listener.types,
239
242
  ...(config.listener.notify
240
243
  ? {
241
- notify: resolveDestination(config.listener.notify, channels, channelsPath),
244
+ notify: resolveNotifyDestination(config.listener.notify, channels, channelsPath),
242
245
  }
243
246
  : {}),
244
247
  },
@@ -251,7 +254,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
251
254
  input: schedule.input,
252
255
  ...(schedule.notify
253
256
  ? {
254
- notify: resolveDestination(schedule.notify, channels, channelsPath),
257
+ notify: resolveNotifyDestination(schedule.notify, channels, channelsPath),
255
258
  }
256
259
  : {}),
257
260
  })),
@@ -1,8 +1,7 @@
1
1
  version: 1
2
2
  channels:
3
- - name: admin-dm
4
- description: Example admin direct chat
5
- chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
3
+ # Group chats only (notify_card). Admin DMs stay on profile admin.chat_id —
4
+ # one bot×person pair each, so they do not belong in this registry.
6
5
  - name: weekday-brief-group
7
6
  description: Example weekday brief notify group
8
7
  chat_id: REPLACE_WITH_GROUP_CHAT_ID
@@ -4,7 +4,7 @@ model:
4
4
  provider: REPLACE_WITH_PI_PROVIDER
5
5
  id: REPLACE_WITH_PI_MODEL
6
6
  admin:
7
- name: admin-dm
7
+ chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
8
8
  schedules:
9
9
  - id: weekday-brief
10
10
  cron: "0 9 * * 1-5"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nettee/beacon",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "A single-host bridge from messaging and schedules to isolated agent runs",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {