viber-channel 0.5.2 → 0.6.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.
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * viber-codex-supervisor (#280 step-12, slice 4).
4
+ *
5
+ * Runs ONE supervisor process that launches and supervises many Codex agents, each
6
+ * in its own `viber-codex-bridge` child process. This is the local entry point;
7
+ * remote (server-driven) control of agents is a later slice.
8
+ *
9
+ * USAGE
10
+ * viber-codex-supervisor --config <path.json>
11
+ * VIBER_SUPERVISOR_CONFIG=<path.json> viber-codex-supervisor
12
+ *
13
+ * Config: see lib/supervisor_config.ts. Each agent defaults to `--await-invite`, so
14
+ * the agents come up idle and the web control plane adds them to conversations.
15
+ *
16
+ * Ctrl-C / SIGTERM stops every agent and exits.
17
+ */
18
+
19
+ import { readFileSync } from "node:fs";
20
+ import { createBridgeSpawn } from "./lib/bridge_spawn.ts";
21
+ import { parseSupervisorConfig } from "./lib/supervisor_config.ts";
22
+ import { type AgentSpec, Supervisor } from "./lib/supervisor.ts";
23
+
24
+ export function configPathFromArgv(argv: string[]): string | undefined {
25
+ const eq = argv.find((a) => a.startsWith("--config="));
26
+ if (eq) return eq.slice("--config=".length);
27
+ const i = argv.indexOf("--config");
28
+ if (i !== -1) {
29
+ // `--config` was given explicitly: it MUST be followed by a real path. A
30
+ // missing value or a following flag is a usage error — fail loudly rather
31
+ // than silently falling back to the env var (CLI precedence wins).
32
+ const val = argv[i + 1];
33
+ if (!val || val.startsWith("--")) {
34
+ throw new Error("--config requires a path argument");
35
+ }
36
+ return val;
37
+ }
38
+ return process.env.VIBER_SUPERVISOR_CONFIG;
39
+ }
40
+
41
+ export function loadConfig(argv: string[]): AgentSpec[] {
42
+ const path = configPathFromArgv(argv);
43
+ if (!path) {
44
+ throw new Error(
45
+ "no config: pass --config <path.json> or set VIBER_SUPERVISOR_CONFIG",
46
+ );
47
+ }
48
+ let raw: unknown;
49
+ try {
50
+ raw = JSON.parse(readFileSync(path, "utf-8"));
51
+ } catch (err) {
52
+ const msg = err instanceof Error ? err.message : String(err);
53
+ throw new Error(`cannot read/parse config ${path}: ${msg}`);
54
+ }
55
+ try {
56
+ return parseSupervisorConfig(raw);
57
+ } catch (err) {
58
+ // Name the file so a validation error points at the offending config.
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ throw new Error(`invalid config ${path}: ${msg}`);
61
+ }
62
+ }
63
+
64
+ async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
65
+ const specs = loadConfig(argv);
66
+ // Launcher is configurable so the supervisor works both with the installed
67
+ // `viber-codex-bridge` bin (default) and from source (`bun viber-codex-bridge.ts`).
68
+ const command = process.env.VIBER_BRIDGE_COMMAND ?? "viber-codex-bridge";
69
+ const baseArgs = (process.env.VIBER_BRIDGE_BASE_ARGS ?? "").split(" ").filter(Boolean);
70
+ const supervisor = new Supervisor({ spawn: createBridgeSpawn({ command, baseArgs }) });
71
+
72
+ process.stderr.write(`[supervisor] launching ${specs.length} agent(s)\n`);
73
+ for (const spec of specs) supervisor.start(spec);
74
+
75
+ let shuttingDown = false;
76
+ const shutdown = (sig: NodeJS.Signals): void => {
77
+ if (shuttingDown) return;
78
+ shuttingDown = true;
79
+ process.stderr.write(`[supervisor] ${sig} — stopping all agents\n`);
80
+ supervisor.stopAll();
81
+ // Give children a moment to exit on SIGTERM, then leave.
82
+ setTimeout(() => process.exit(0), 500);
83
+ };
84
+ process.on("SIGINT", () => shutdown("SIGINT"));
85
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
86
+
87
+ // Keep the process alive; the supervisor drives everything via callbacks.
88
+ await new Promise<void>(() => {});
89
+ }
90
+
91
+ if (import.meta.main) {
92
+ main().catch((err) => {
93
+ process.stderr.write(`[supervisor] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
94
+ process.exit(1);
95
+ });
96
+ }