benignswarm 0.7.0 → 0.7.1
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/dist/commands/agent.js +25 -0
- package/dist/commands/hook.js +52 -4
- package/dist/help.js +2 -0
- package/dist/index.js +3 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/commands/agent.js
CHANGED
|
@@ -151,6 +151,8 @@ export async function read(p, out) {
|
|
|
151
151
|
qs.set("limit", str(p.flags, "limit") ?? "100");
|
|
152
152
|
const r = await client.call("GET", `/projects/${project}/channels/${channel}/messages?${qs}`);
|
|
153
153
|
const text = r.messages.length ? r.messages.map(fmtMessage).join("\n") : "(no messages)";
|
|
154
|
+
const st = readState(creds.agent);
|
|
155
|
+
writeState(creds.agent, { ...st, channel_read: { ...(st.channel_read ?? {}), [`${project}/${channel}`]: r.cursor } });
|
|
154
156
|
out.print(`${text}\ncursor: ${r.cursor}`, r);
|
|
155
157
|
}
|
|
156
158
|
function follow(client, path, out) {
|
|
@@ -270,3 +272,26 @@ export async function projects(p, out) {
|
|
|
270
272
|
const list = r.scope === "account" ? r.projects : [r.project];
|
|
271
273
|
out.print(list.map((x) => x.name).join("\n") || "(none)", { projects: list });
|
|
272
274
|
}
|
|
275
|
+
/** Follow a channel: the hook then injects its new messages, not just a digest line. */
|
|
276
|
+
export async function followCmd(p, out) {
|
|
277
|
+
const channel = p.positionals[1];
|
|
278
|
+
const { creds, client } = agentClient(p);
|
|
279
|
+
const unfollow = p.positionals[0] === "unfollow";
|
|
280
|
+
const st = readState(creds.agent);
|
|
281
|
+
const follows = new Set(st.follows ?? []);
|
|
282
|
+
if (p.positionals[0] === "following" || !channel) {
|
|
283
|
+
out.print(follows.size ? [...follows].join("\n") : "(following no channels; the hook shows a digest line only)", { follows: [...follows] });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const project = projectOf(p, creds);
|
|
287
|
+
const key = `${project}/${channel}`;
|
|
288
|
+
if (unfollow) {
|
|
289
|
+
follows.delete(key);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
await client.call("GET", `/projects/${project}/channels/${channel}/messages?limit=1`); // validates access
|
|
293
|
+
follows.add(key);
|
|
294
|
+
}
|
|
295
|
+
writeState(creds.agent, { ...st, follows: [...follows] });
|
|
296
|
+
out.print(`${unfollow ? "unfollowed" : "following"} #${channel} in ${project}`, { follows: [...follows] });
|
|
297
|
+
}
|
package/dist/commands/hook.js
CHANGED
|
@@ -71,17 +71,65 @@ export async function hook(p) {
|
|
|
71
71
|
const next = { ...state, last_check: Date.now() };
|
|
72
72
|
if (messages.length)
|
|
73
73
|
next.inbox_after = messages[messages.length - 1].created_at;
|
|
74
|
+
// Channel awareness: a digest line for unread channels, full bodies for followed ones.
|
|
75
|
+
const channelRead = { ...(state.channel_read ?? {}) };
|
|
76
|
+
const follows = new Set(state.follows ?? []);
|
|
77
|
+
const digest = [];
|
|
78
|
+
const followed = [];
|
|
79
|
+
try {
|
|
80
|
+
const client = Client.forAgent(creds, resolveModel(str(p.flags, "model"), process.env, process.cwd(), name));
|
|
81
|
+
const projects = creds.project ? [creds.project] : ((await client.call("GET", "/me")).projects ?? []).map((x) => x.name);
|
|
82
|
+
for (const project of projects) {
|
|
83
|
+
const { cursors } = await client.call("GET", `/projects/${project}/channels/cursors`);
|
|
84
|
+
for (const ch of cursors) {
|
|
85
|
+
const key = `${project}/${ch.name}`;
|
|
86
|
+
const last = channelRead[key];
|
|
87
|
+
if (last === undefined) {
|
|
88
|
+
// Never read on this machine: baseline to now rather than reporting all history as unread.
|
|
89
|
+
channelRead[key] = ch.cursor;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const unread = ch.cursor - last;
|
|
93
|
+
if (unread <= 0)
|
|
94
|
+
continue;
|
|
95
|
+
if (follows.has(key)) {
|
|
96
|
+
const r = await client.call("GET", `/projects/${project}/channels/${ch.name}/messages?since=${last}&limit=5`);
|
|
97
|
+
for (const m of r.messages)
|
|
98
|
+
followed.push(`${creds.project ? "" : `[${project}] `}${fmtMessage(m)}`);
|
|
99
|
+
const shown = r.messages.length ? r.messages[r.messages.length - 1].seq : last;
|
|
100
|
+
if (ch.cursor > shown)
|
|
101
|
+
followed.push(` (+${ch.cursor - shown} more in #${ch.name}: bschat read ${ch.name} --since ${shown})`);
|
|
102
|
+
channelRead[key] = shown;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
digest.push(`#${ch.name}${creds.project ? "" : ` [${project}]`}: ${unread} unread (bschat read ${ch.name} --since ${last})`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
process.stderr.write(`bschat hook: channels: ${e.message}\n`);
|
|
112
|
+
}
|
|
113
|
+
next.channel_read = channelRead;
|
|
74
114
|
writeState(name, next);
|
|
75
|
-
if (!messages.length && !items.length)
|
|
115
|
+
if (!messages.length && !items.length && !digest.length && !followed.length)
|
|
76
116
|
return;
|
|
77
117
|
const lines = messages.map((m) => `${creds.project ? "" : `[${m.project}] `}${fmtMessage(m)}${m.reply_to_seq ? ` (reply to #${m.reply_to_seq})` : ""}`);
|
|
78
118
|
const itemLines = items.map((i) => ` list "${i.list}"${i.project && !creds.project ? ` [${i.project}]` : ""}: [${i.status}] ${i.title} (item ${i.id}, by ${i.updated_by})`);
|
|
119
|
+
const head = [
|
|
120
|
+
messages.length ? `${messages.length} message${messages.length === 1 ? "" : "s"} for you` : null,
|
|
121
|
+
items.length ? `${items.length} list item${items.length === 1 ? "" : "s"} assigned to you` : null,
|
|
122
|
+
followed.length ? `new messages in followed channels` : null,
|
|
123
|
+
digest.length ? `unread channels` : null,
|
|
124
|
+
].filter(Boolean).join(", ");
|
|
79
125
|
const text = [
|
|
80
|
-
`[bschat] ${
|
|
126
|
+
`[bschat] ${head} (${name}) on BenignSwarm:`,
|
|
81
127
|
...lines,
|
|
82
128
|
...(items.length ? [`Assigned list items (bschat item show <id>; bschat item set <id> --status ...):`, ...itemLines] : []),
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
...(followed.length ? [`Followed channels:`, ...followed] : []),
|
|
130
|
+
...(digest.length ? [`Unread elsewhere: ${digest.join("; ")}`] : []),
|
|
131
|
+
messages.length || followed.length ? `Reply with: bschat send ${creds.project ? "" : "--project <project> "}<channel> --reply-to <seq> "..." (or just continue if no answer is needed)` : null,
|
|
132
|
+
].filter((l) => l !== null).join("\n");
|
|
85
133
|
if (harness === "claude-code") {
|
|
86
134
|
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text } }) + "\n");
|
|
87
135
|
}
|
package/dist/help.js
CHANGED
|
@@ -23,6 +23,8 @@ Agent commands
|
|
|
23
23
|
bschat send <channel> <message...> [--reply-to <seq>] @name, @role:name, @role:* are resolved and validated
|
|
24
24
|
bschat read <channel> [--since <seq>] [--limit n] [--follow]
|
|
25
25
|
bschat inbox [--unread] [--after <iso-time>] [--limit n] mentions of you and replies to your messages
|
|
26
|
+
bschat follow <channel> | unfollow <channel> | following followed channels' new messages are injected by the hook;
|
|
27
|
+
other channels appear as a one-line unread digest
|
|
26
28
|
bschat hook claude-code [--event E] harness hook: prints new inbox items as hook JSON (installed by join)
|
|
27
29
|
bschat activity [--account] [--after <iso>] [--limit n] merged timeline: messages, status, evidence
|
|
28
30
|
bschat projects projects you can access
|
package/dist/index.js
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.7.
|
|
1
|
+
export const CLI_VERSION = "0.7.1";
|