@volter/teams 0.2.20 → 0.2.22

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.
Files changed (2) hide show
  1. package/board-owner.mjs +80 -0
  2. package/package.json +3 -2
@@ -0,0 +1,80 @@
1
+ // A home's board owner (`supercode workflow serve`, one per install: D152) answers every card verb on that home's boards
2
+ // over one local socket. The CLI and the machine daemon's board door hold nothing: each sends the verb here and renders
3
+ // the answer. The socket is the home's own (`<home>/board.sock`, beside the owner's lock `<home>/board.lock`), by the
4
+ // home's real path, so every process naming the home reaches the one owner whatever its environment.
5
+ //
6
+ // request: one JSON line { v: 1, argv | tool, board, caller, session, facts, cwd, stdin }
7
+ // argv the verb and its arguments, without --root or --board
8
+ // tool a dispatched worker's board tool instead of a verb: { name, args, env } (its HERMES_KANBAN_*
9
+ // task, run and board, and HERMES_PROFILE); answered with the tool's JSON as stdout
10
+ // board the board's slug
11
+ // caller the calling session's address when its side named it (`sc:<machine>:<harness>:<id>`), or null
12
+ // session { id, harness } when the caller is a session on the owner's machine that the CLI named
13
+ // facts what the caller's environment says of it (SUPERCODE_AGENT_ID, SUPERCODE_AUTOMATION,
14
+ // SUPERCODE_CONTEXT, HERMES_PROFILE, USER, SUPERCODE_CALLER, and a launched worker's
15
+ // SUPERCODE_RUN_LAUNCH: the owner's secret for its run, which only run-session reads)
16
+ // cwd the caller's working directory, against which a path it names is read
17
+ // stdin the caller's standard input, when it names `-` as a file (else null)
18
+ // answer: one JSON line { code, stdout, stderr }
19
+ import { realpathSync } from 'node:fs';
20
+ import { connect } from 'node:net';
21
+ import { join, resolve } from 'node:path';
22
+
23
+ /** A Unix socket's path is at most 104 bytes on macOS (108 on Linux), terminator included. */
24
+ const SOCKET_PATH_MAX = 103;
25
+
26
+ /** The home's real path. */
27
+ export function boardHome(root) {
28
+ try { return realpathSync(resolve(root)); } catch { return resolve(root); }
29
+ }
30
+
31
+ /** The owner's socket for the home at `root`: the home's own, by its real path. */
32
+ export function boardOwnerSocket(root) {
33
+ const path = join(boardHome(root), 'board.sock');
34
+ if (Buffer.byteLength(path) > SOCKET_PATH_MAX) throw new Error(`the home's board socket ${path} is longer than a Unix socket path may be (${SOCKET_PATH_MAX} bytes); keep the home at a shorter path`);
35
+ return path;
36
+ }
37
+
38
+ /**
39
+ * The verbs that are a board's own operation, run on its machine as acts of their own and never sent to its owner or
40
+ * through the board door: the owner itself and its one-shot round, init, adoption, the board list, housekeeping and
41
+ * repair, migration, the dispatching switch, and the verbs that hold the call open (watch, tail, attach, events).
42
+ */
43
+ export const BOARD_OPERATION_VERBS = new Set(['serve', 'daemon', 'dispatch', 'dispatching', 'init', 'boards', 'gc', 'repair', 'migrate', 'watch', 'tail', 'events']);
44
+
45
+ /** The facts of a caller's environment the owner reads, and nothing else of it. */
46
+ export const CALLER_FACTS = ['SUPERCODE_AGENT_ID', 'SUPERCODE_AUTOMATION', 'SUPERCODE_CONTEXT', 'HERMES_PROFILE', 'USER', 'SUPERCODE_CALLER', 'SUPERCODE_RUN_LAUNCH'];
47
+ export const callerFacts = (env = process.env) => Object.fromEntries(CALLER_FACTS.filter((key) => env[key]).map((key) => [key, env[key]]));
48
+
49
+ /**
50
+ * Send one verb to the owner of the home at `root`: answers `{ code, stdout, stderr }`. A home whose owner is not
51
+ * running answers that, with what starts it; nothing else runs the verb.
52
+ */
53
+ export function askBoardOwner(root, request) {
54
+ let path;
55
+ try { path = boardOwnerSocket(root); } catch (error) { return Promise.resolve({ code: 1, stdout: '', stderr: `${error.message}\n` }); }
56
+ return new Promise((done) => {
57
+ let answer = '';
58
+ let settled = false;
59
+ const finish = (value) => { if (!settled) { settled = true; done(value); } };
60
+ const socket = connect(path);
61
+ socket.setEncoding('utf8');
62
+ // The request is one line, and the connection stays open both ways until the answer's line has come: an answer that
63
+ // takes its time (a Teams lookup, a reread home) is never lost to a closed side.
64
+ socket.on('connect', () => socket.write(`${JSON.stringify({ v: 1, ...request })}\n`));
65
+ socket.on('data', (chunk) => {
66
+ answer += chunk;
67
+ const end = answer.indexOf('\n');
68
+ if (end < 0) return;
69
+ socket.end();
70
+ try { finish(JSON.parse(answer.slice(0, end))); } catch { finish({ code: 1, stdout: '', stderr: `the board owner of ${root} answered nothing readable\n` }); }
71
+ });
72
+ socket.on('close', () => finish({ code: 1, stdout: '', stderr: `the board owner of ${root} closed the call before answering\n` }));
73
+ socket.on('error', (error) => {
74
+ const down = ['ENOENT', 'ECONNREFUSED'].includes(error.code);
75
+ finish({ code: 1, stdout: '', stderr: down
76
+ ? `the boards of ${root} have no running owner: \`supercode workflow serve --root ${root}\` is it (an install's keeper runs it)\n`
77
+ : `the board owner of ${root} could not be reached: ${error.message}\n` });
78
+ });
79
+ });
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",
@@ -54,7 +54,8 @@
54
54
  "default": "./redact.mjs"
55
55
  },
56
56
  "./checkout": "./checkout.mjs",
57
- "./board-name": "./board-name.mjs"
57
+ "./board-name": "./board-name.mjs",
58
+ "./board-owner": "./board-owner.mjs"
58
59
  },
59
60
  "files": [
60
61
  "LICENSE-MIT",