@haywoodmarx/agent-pager 0.1.2 → 0.1.3

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
@@ -62,7 +62,7 @@ Pager does not change a harness's permission settings. A session that has to wai
62
62
 
63
63
  ## Service operations
64
64
 
65
- The broker starts on demand: `pager` with no command, and every harness connecting over MCP, adopts the running broker or starts one. `pager status` reports whether the broker is running, its pid, its MCP endpoint, and the database it serves. `pager exit` stops it, and `pager restart` stops it and starts a fresh one. `pager serve` runs the broker in the foreground.
65
+ The broker starts on demand: `pager` with no command, and every harness connecting over MCP, adopts the running broker or starts one. `pager status` reports whether the broker is running, its pid, its MCP endpoint, the database it serves, the installed pager version, and the version of the running broker. `pager exit` stops it, and `pager restart` stops it and starts a fresh one. `pager serve` runs the broker in the foreground.
66
66
 
67
67
  Callers adopt a running service only when its database-adjacent lock and `/health` agree on the service identity; concurrent starters race for that lock and every caller adopts the winner. The adoption rules are declared in [`src/launch/on-demand.ts`](src/launch/on-demand.ts) and [`src/launch/broker-lock.ts`](src/launch/broker-lock.ts).
68
68
 
@@ -83,6 +83,12 @@ export declare function brokerLockPath(databasePath: string): string;
83
83
  * @returns - Valid parsed lock data when the file is well-formed.
84
84
  */
85
85
  export declare function readBrokerLock(lockPath: string): BrokerLock | undefined;
86
+ /**
87
+ * Name the package version of the build that wrote a lock record.
88
+ * @param lock - Lock record, possibly written by a foreign build.
89
+ * @returns - The version after the build id's last `@`, or the whole build id when it names no version.
90
+ */
91
+ export declare function brokerLockVersion(lock: Pick<BrokerLock, "buildId">): string;
86
92
  /** Bounds how many times acquisition re-attempts after losing a lock-file race. */
87
93
  export declare const BROKER_LOCK_ACQUISITION_ATTEMPTS = 5;
88
94
  /** Reports the start time of a process, or that this platform cannot supply one. */
@@ -117,6 +117,15 @@ export function readBrokerLock(lockPath) {
117
117
  return undefined;
118
118
  }
119
119
  }
120
+ /**
121
+ * Name the package version of the build that wrote a lock record.
122
+ * @param lock - Lock record, possibly written by a foreign build.
123
+ * @returns - The version after the build id's last `@`, or the whole build id when it names no version.
124
+ */
125
+ export function brokerLockVersion(lock) {
126
+ const separator = lock.buildId.lastIndexOf("@");
127
+ return separator > 0 ? lock.buildId.slice(separator + 1) : lock.buildId;
128
+ }
120
129
  /**
121
130
  * Structural gate, not a currency check: `serviceProtocolVersion`, `runtime`, and `protocolVersion`
122
131
  * must equal the current build's because a mismatch there means the rest of the shape cannot be
@@ -10,8 +10,9 @@ import { packagedSkills } from "./skill.js";
10
10
  import { createOperatorTuiClient, fileBackedTuiLayout, DEFAULT_OPERATOR_TUI_THEME, startOperatorTui, } from "../tui/production.js";
11
11
  import { assertSupportedBunRuntime } from "./runtime.js";
12
12
  import { BrokerService } from "./service.js";
13
+ import { PAGER_VERSION } from "../version.js";
13
14
  import { systemBrokerServiceFilesystem, systemBrokerServiceScheduler } from "./service-system.js";
14
- import { BrokerLockGuard, brokerLockPath, brokerLockHasCurrentIdentity, brokerLockIsLive, processIsAlive, readBrokerLock, readBrokerOwnerPid, } from "./broker-lock.js";
15
+ import { BrokerLockGuard, brokerLockPath, brokerLockHasCurrentIdentity, brokerLockIsLive, brokerLockVersion, processIsAlive, readBrokerLock, readBrokerOwnerPid, } from "./broker-lock.js";
15
16
  /**
16
17
  * Run the operational CLI.
17
18
  *
@@ -55,6 +56,8 @@ export async function main(argv = process.argv.slice(2)) {
55
56
  state: running ? "running" : starting ? "starting" : ownerAlive ? "incompatible" : "stopped",
56
57
  ...(ownerAlive ? { pid: ownerPid } : {}),
57
58
  ...(running && lock.mcpUrl ? { mcpUrl: lock.mcpUrl } : {}),
59
+ version: PAGER_VERSION,
60
+ ...(ownerAlive && lock ? { brokerVersion: brokerLockVersion(lock) } : {}),
58
61
  dbPath,
59
62
  }));
60
63
  break;
@@ -278,7 +281,7 @@ function usage(implementations) {
278
281
  "pager open the operator TUI",
279
282
  "pager <harness> [--session-name <name>] [-- <harness arguments>]",
280
283
  ` start a harness attached to the broker (installed: ${harnesses || "none"})`,
281
- "pager status broker state, pid, MCP endpoint and database",
284
+ "pager status broker state, pid, MCP endpoint, database and versions",
282
285
  "pager exit stop the broker",
283
286
  "pager restart stop the broker and start a fresh one",
284
287
  "pager serve run the broker in the foreground",
@@ -250,6 +250,8 @@ export type OperatorSnapshot = {
250
250
  protocolVersion: typeof OPERATOR_PROTOCOL_VERSION;
251
251
  /** Identifies the broker generation that produced the snapshot. */
252
252
  epoch: string;
253
+ /** Names the published package version of the broker that produced the snapshot. */
254
+ brokerVersion: string;
253
255
  /** Identifies the last event already reflected by this snapshot. */
254
256
  cursor: OperatorCursor;
255
257
  /** Records when the projection was generated. */
@@ -2,6 +2,7 @@ import { projectOperatorAgents } from "./agent-projection.js";
2
2
  import { OPERATOR_PROTOCOL_VERSION, } from "./protocol.js";
3
3
  import { operatorDiagnostic } from "./diagnostics.js";
4
4
  import { projectOperatorTraffic } from "./traffic-projection.js";
5
+ import { PAGER_VERSION } from "../version.js";
5
6
  /** Bounds retained stream-integrity diagnostics. */
6
7
  const UPDATE_DIAGNOSTIC_LIMIT = 64;
7
8
  /** Broker tables whose rows feed the repository and agent halves of an operator snapshot. */
@@ -181,6 +182,7 @@ export class OperatorUpdateService {
181
182
  return {
182
183
  protocolVersion: OPERATOR_PROTOCOL_VERSION,
183
184
  epoch: cursor.epoch,
185
+ brokerVersion: PAGER_VERSION,
184
186
  cursor,
185
187
  generatedAt: this.#now(),
186
188
  repositories: projected.repositories,
@@ -173,7 +173,16 @@ export function validateOperatorSnapshot(value) {
173
173
  const record = readRecord(value, "snapshot");
174
174
  exactFields({
175
175
  record,
176
- required: ["protocolVersion", "epoch", "cursor", "generatedAt", "repositories", "agents", "traffic"],
176
+ required: [
177
+ "protocolVersion",
178
+ "epoch",
179
+ "brokerVersion",
180
+ "cursor",
181
+ "generatedAt",
182
+ "repositories",
183
+ "agents",
184
+ "traffic",
185
+ ],
177
186
  optional: [],
178
187
  path: "snapshot",
179
188
  });
@@ -207,6 +216,7 @@ export function validateOperatorSnapshot(value) {
207
216
  const snapshot = {
208
217
  protocolVersion,
209
218
  epoch,
219
+ brokerVersion: readNonemptyString(record.brokerVersion, "snapshot.brokerVersion"),
210
220
  cursor,
211
221
  generatedAt: readTimestamp(record.generatedAt, "snapshot.generatedAt"),
212
222
  repositories,
@@ -8,6 +8,7 @@ import { Line, Overlay, PaneBar, Rule, VERTICAL_SCROLL_STYLE, useTuiModel } from
8
8
  import { AgentTrafficLog, MessageDetail, MessageOverlays, selectNewestTuiTraffic, stepTuiTrafficSelection, } from "./message-views.js";
9
9
  import { AGENT_TREE_COLUMNS, sidePaneWidths, MESSAGE_LOG_COLUMNS, PORTRAIT_BREAKPOINT_COLUMNS, agentColor, activeAgentCount, agentLogScope, brokerIndicator, COLLAPSED_LOG_ROWS, describeCommandOutcome, diagnosticColor, fitBarLeading, formatClock, formatDiagnostic, selectedMessage, selectMessageBodyRows, selectTuiTraffic, } from "./presentation.js";
10
10
  import { dimTuiTheme } from "./theme.js";
11
+ import { PAGER_VERSION } from "../version.js";
11
12
  /* eslint-enable jsdoc/require-jsdoc -- The collaborator declarations end here. */
12
13
  /** Rows the wide layout spends on fixed chrome rather than pane content. */
13
14
  const WIDE_PANE_CHROME_ROWS = 7;
@@ -274,11 +275,11 @@ function FollowFooter({ state, theme }) {
274
275
  }
275
276
  function StatusBar({ state, theme }) {
276
277
  const broker = brokerIndicator(state.connection, theme);
277
- return (_jsxs("box", { height: 1, flexShrink: 0, flexDirection: "row", paddingLeft: 1, paddingRight: 1, justifyContent: "space-between", children: [_jsx("text", { fg: theme.structure.emphasis, children: "pager" }), _jsx("box", { flexDirection: "row", children: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), " broker ", `${String(state.unreadMessageIds.size)} unread ${String(activeAgentCount(state))} agents`] }) })] }));
278
+ return (_jsxs("box", { height: 1, flexShrink: 0, flexDirection: "row", paddingLeft: 1, paddingRight: 1, justifyContent: "space-between", children: [_jsx("text", { fg: theme.structure.emphasis, children: "pager" }), _jsx("box", { flexDirection: "row", children: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), " broker ", `${String(state.unreadMessageIds.size)} unread ${String(activeAgentCount(state))} agents v${PAGER_VERSION}`] }) })] }));
278
279
  }
279
280
  function PortraitStatusBar({ state, theme }) {
280
281
  const broker = brokerIndicator(state.connection, theme);
281
- return (_jsx(PaneBar, { left: _jsx("text", { fg: theme.structure.emphasis, children: "pager" }), right: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), ` ${String(state.unreadMessageIds.size)} unread · ${String(activeAgentCount(state))} agents`] }) }));
282
+ return (_jsx(PaneBar, { left: _jsx("text", { fg: theme.structure.emphasis, children: "pager" }), right: _jsxs("text", { fg: theme.structure.rulesAndLabels, children: [_jsx("span", { fg: broker.color, children: broker.glyph }), ` ${String(state.unreadMessageIds.size)} unread · ${String(activeAgentCount(state))} agents · v${PAGER_VERSION}`] }) }));
282
283
  }
283
284
  const HELP_KEYS = [
284
285
  ["↑↓", "move within the focused pane"],
@@ -349,7 +349,7 @@ export class TuiOperatorModel {
349
349
  if (!this.#active(generation))
350
350
  return;
351
351
  if (this.#state.connection !== "healthy")
352
- this.reportDiagnostic("info", "connection", `connected: snapshot at sequence ${String(snapshot.cursor.sequence)}`);
352
+ this.reportDiagnostic("info", "connection", `connected: broker ${snapshot.brokerVersion}, snapshot at sequence ${String(snapshot.cursor.sequence)}`);
353
353
  // A fetched snapshot is installed as a fresh start rather than a replacement, so unread identities
354
354
  // begin empty from it.
355
355
  this.observeSnapshot(structuredClone(snapshot), false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haywoodmarx/agent-pager",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Durable local messaging between agent implementations.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",