@kybernesis/create 0.26.2 → 0.26.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/dist/buzz.js CHANGED
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
2
2
  import { tmpdir, userInfo } from "node:os";
3
3
  import { basename, join, resolve } from "node:path";
4
4
  import { upsertEnv } from "./envfile.js";
5
+ import { buzzBridgeUnitsFor } from "./systemd.js";
5
6
  import { bold, capture, dim, green, linePackage, red, run, yellow } from "./util.js";
6
7
  /**
7
8
  * `kyb add buzz` — get an agent ready for its owner's Buzz community.
@@ -32,7 +33,7 @@ export function buzzKeyPath(appDir) {
32
33
  return join(appDir, ".buzz", "agent-key.json");
33
34
  }
34
35
  export function buzzBridgeUnitName(agent) {
35
- return `${agent}-buzz-bridge`;
36
+ return `${agent.toLowerCase()}-buzz-bridge`;
36
37
  }
37
38
  /** The bridge's systemd unit. Pure, so the test pins what every host gets. */
38
39
  export function buzzBridgeUnit(input) {
@@ -119,6 +120,15 @@ export function addBuzz(cwd = resolve(process.cwd())) {
119
120
  console.log(yellow(" ! no docker here, so the Buzz CLI was not built; the agent can talk in Buzz but not act in it."));
120
121
  }
121
122
  }
123
+ // A bridge already serving this folder is this agent's bridge, whatever it is called.
124
+ // Installing another beside it would answer every message twice.
125
+ const existingUnits = buzzBridgeUnitsFor(cwd);
126
+ if (existingUnits.length > 0) {
127
+ console.log(dim(` bridge: ${existingUnits.join(", ")} already serves this agent`));
128
+ console.log(bold("\n Next:"));
129
+ console.log(` · The owner connects it from the Channels page: their community's address, then an admin adds ${npub} as a member.`);
130
+ return;
131
+ }
122
132
  const unitName = buzzBridgeUnitName(agent);
123
133
  const dir = mkdtempSync(join(tmpdir(), "kyb-buzz-"));
124
134
  try {
package/dist/doctor.js CHANGED
@@ -3,7 +3,7 @@ import { TEMPLATE_CONTAINER_PROBE, parseTemplateContainers, templateContainerDoc
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { EVE_VERSION, REGISTRY_URL, bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
6
- import { diagnoseManageRestart, findMatchingAgentServiceUnit } from "./systemd.js";
6
+ import { buzzBridgeUnitsFor, diagnoseManageRestart, findMatchingAgentServiceUnit } from "./systemd.js";
7
7
  import { inspectEveAgent } from "./agent-limits.js";
8
8
  import { classifyModelReach } from "./model-reach.js";
9
9
  import { countAbandonedRuns } from "./run-store.js";
@@ -195,6 +195,11 @@ export async function doctor() {
195
195
  * two changes signed off as deployed were live nowhere. Nothing looked
196
196
  * broken — the old code simply kept doing what it did.
197
197
  */
198
+ // One bridge per agent: two answer every Buzz message twice.
199
+ const bridges = buzzBridgeUnitsFor(cwd);
200
+ if (bridges.length > 1) {
201
+ add("fail", `${bridges.length} Buzz bridges run for this agent (${bridges.join(", ")})`, `Each answers every message, so people get every answer twice. Keep the one deploy.sh restarts and remove the rest: sudo systemctl disable --now <unit> && sudo rm /etc/systemd/system/<unit>.service && sudo systemctl daemon-reload`);
202
+ }
198
203
  const packageChange = newestPackageChange(cwd);
199
204
  const started = new Map();
200
205
  for (const unit of listAgentUnits()) {
package/dist/systemd.d.ts CHANGED
@@ -21,6 +21,14 @@ export interface InstalledAgentService {
21
21
  values: UnitValues;
22
22
  mode: number;
23
23
  }
24
+ /**
25
+ * The Buzz bridge units that run in this agent's folder, whatever they are called.
26
+ *
27
+ * A bridge belongs to an agent by the folder it runs in, not by its name: a
28
+ * second unit with another name ("Kyber-buzz-bridge" beside "kyber-buzz-bridge")
29
+ * is a second bridge on the same key, and every message is answered twice.
30
+ */
31
+ export declare function buzzBridgeUnitsFor(cwd: string, systemdDir?: string): string[];
24
32
  export declare function findMatchingAgentServiceUnit(cwd: string, systemdDir?: string): InstalledAgentService | null;
25
33
  /**
26
34
  * Refresh an existing package-generated unit. Deliberately never creates a
package/dist/systemd.js CHANGED
@@ -83,6 +83,28 @@ function dropIns(dir) {
83
83
  return [];
84
84
  }
85
85
  }
86
+ /**
87
+ * The Buzz bridge units that run in this agent's folder, whatever they are called.
88
+ *
89
+ * A bridge belongs to an agent by the folder it runs in, not by its name: a
90
+ * second unit with another name ("Kyber-buzz-bridge" beside "kyber-buzz-bridge")
91
+ * is a second bridge on the same key, and every message is answered twice.
92
+ */
93
+ export function buzzBridgeUnitsFor(cwd, systemdDir = "/etc/systemd/system") {
94
+ try {
95
+ return readdirSync(systemdDir)
96
+ .filter((entry) => /buzz-bridge.*\.service$/.test(entry))
97
+ .filter((file) => {
98
+ const dir = readFileSync(join(systemdDir, file), "utf8").match(/^WorkingDirectory=(.+)$/m)?.[1]?.trim();
99
+ return dir !== undefined && resolve(dir) === resolve(cwd);
100
+ })
101
+ .map((file) => file.replace(/\.service$/, ""))
102
+ .sort();
103
+ }
104
+ catch {
105
+ return [];
106
+ }
107
+ }
86
108
  export function findMatchingAgentServiceUnit(cwd, systemdDir = "/etc/systemd/system") {
87
109
  try {
88
110
  for (const file of readdirSync(systemdDir).filter((entry) => entry.endsWith("-agent.service"))) {
package/dist/templates.js CHANGED
@@ -306,7 +306,8 @@ export default multiplayerSlackChannel({
306
306
  return onExe
307
307
  ? {
308
308
  file: "linq.ts",
309
- content: `import { channelIdentity } from "@kybernesis/enterprise";
309
+ content: `import { progressEvents } from "@kybernesis/cards";
310
+ import { channelIdentity } from "@kybernesis/enterprise";
310
311
  import { linqEnvCredentials, linqSenderIdentity } from "@kybernesis/exe/linq";
311
312
  import { linqChannel } from "eve/channels/linq";
312
313
 
@@ -317,6 +318,10 @@ import { linqChannel } from "eve/channels/linq";
317
318
  //
318
319
  // A sender is a control-plane person, never a phone number: an unlinked number
319
320
  // gets one text with a claim link and nothing else.
321
+ //
322
+ // A text thread shows nothing between a request and its answer, so when the
323
+ // agent starts a long job it sends one "on it" line first (\`working_on_it\`,
324
+ // from @kybernesis/notify) and \`progressEvents\` posts it here as it happens.
320
325
  const issuer = process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai";
321
326
  const credential = process.env.KYBERNESIS_AGENT_CREDENTIAL;
322
327
  const agentName = process.env.KYBERNESIS_AGENT;
@@ -324,6 +329,7 @@ const agentName = process.env.KYBERNESIS_AGENT;
324
329
  export default linqChannel({
325
330
  credentials: linqEnvCredentials(),
326
331
  ...(agentName ? { userName: agentName } : {}),
332
+ events: progressEvents(),
327
333
  onMessage: linqSenderIdentity({
328
334
  issuer,
329
335
  agentName,
@@ -331,7 +337,7 @@ export default linqChannel({
331
337
  }),
332
338
  });
333
339
  `,
334
- deps: ["@kybernesis/exe", "@kybernesis/enterprise"],
340
+ deps: ["@kybernesis/exe", "@kybernesis/enterprise", "@kybernesis/cards"],
335
341
  registryItems: [],
336
342
  env: [],
337
343
  steps: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent \u2014 plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",