@haywoodmarx/agent-pager 0.1.3 → 0.2.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.
Files changed (77) hide show
  1. package/README.md +4 -4
  2. package/dist/src/broker/address.d.ts +7 -0
  3. package/dist/src/broker/address.js +9 -0
  4. package/dist/src/broker/broker.d.ts +112 -24
  5. package/dist/src/broker/broker.js +604 -132
  6. package/dist/src/broker/operator-projection.d.ts +22 -0
  7. package/dist/src/broker/operator-projection.js +43 -5
  8. package/dist/src/broker/protocol.d.ts +1 -1
  9. package/dist/src/broker/protocol.js +1 -1
  10. package/dist/src/broker/schema.d.ts +4 -0
  11. package/dist/src/broker/schema.js +20 -8
  12. package/dist/src/broker/snapshot.d.ts +4 -2
  13. package/dist/src/implementations/claude/gateway-cli.js +1 -2
  14. package/dist/src/implementations/claude/gateway.d.ts +11 -4
  15. package/dist/src/implementations/claude/gateway.js +44 -14
  16. package/dist/src/implementations/claude/instructions.js +1 -1
  17. package/dist/src/implementations/model.d.ts +6 -3
  18. package/dist/src/implementations/opencode/plugin.d.ts +7 -2
  19. package/dist/src/implementations/opencode/plugin.js +103 -7
  20. package/dist/src/implementations/pi/broker-client.d.ts +13 -4
  21. package/dist/src/implementations/pi/broker-client.js +71 -9
  22. package/dist/src/implementations/pi/extension.d.ts +7 -2
  23. package/dist/src/implementations/pi/extension.js +15 -3
  24. package/dist/src/index.d.ts +2 -2
  25. package/dist/src/index.js +2 -2
  26. package/dist/src/launch/broker-lock.d.ts +58 -4
  27. package/dist/src/launch/broker-lock.js +140 -7
  28. package/dist/src/launch/launch.d.ts +1 -1
  29. package/dist/src/launch/on-demand.d.ts +8 -8
  30. package/dist/src/launch/on-demand.js +54 -35
  31. package/dist/src/launch/runtime-composition.js +2 -0
  32. package/dist/src/launch/runtime.js +4 -14
  33. package/dist/src/launch/service.d.ts +1 -0
  34. package/dist/src/launch/service.js +23 -1
  35. package/dist/src/launch/workspace-branch.d.ts +3 -3
  36. package/dist/src/launch/workspace-branch.js +7 -15
  37. package/dist/src/launch/workspace-root.d.ts +13 -0
  38. package/dist/src/launch/workspace-root.js +27 -0
  39. package/dist/src/messaging/delivery-client.d.ts +17 -0
  40. package/dist/src/messaging/delivery-client.js +31 -1
  41. package/dist/src/messaging/identity-statement.d.ts +7 -0
  42. package/dist/src/messaging/identity-statement.js +8 -0
  43. package/dist/src/messaging/mcp.d.ts +8 -2
  44. package/dist/src/messaging/mcp.js +54 -1
  45. package/dist/src/messaging/notifications.d.ts +31 -3
  46. package/dist/src/messaging/notifications.js +25 -0
  47. package/dist/src/messaging/push-delivery.d.ts +2 -1
  48. package/dist/src/messaging/push-delivery.js +24 -7
  49. package/dist/src/messaging/tools.js +22 -2
  50. package/dist/src/messaging/wake.js +7 -0
  51. package/dist/src/operator/agent-projection.d.ts +8 -5
  52. package/dist/src/operator/agent-projection.js +29 -9
  53. package/dist/src/operator/commands.d.ts +2 -0
  54. package/dist/src/operator/commands.js +57 -21
  55. package/dist/src/operator/protocol.d.ts +34 -2
  56. package/dist/src/operator/protocol.js +9 -0
  57. package/dist/src/operator/repository-system.d.ts +29 -0
  58. package/dist/src/operator/repository-system.js +39 -12
  59. package/dist/src/operator/repository.d.ts +28 -11
  60. package/dist/src/operator/repository.js +37 -4
  61. package/dist/src/operator/transport.js +10 -5
  62. package/dist/src/operator/updates.js +7 -1
  63. package/dist/src/operator/validation.js +35 -1
  64. package/dist/src/shared/versions.d.ts +18 -0
  65. package/dist/src/shared/versions.js +26 -0
  66. package/dist/src/tui/agent-tree.d.ts +8 -5
  67. package/dist/src/tui/agent-tree.js +195 -22
  68. package/dist/src/tui/app.js +6 -2
  69. package/dist/src/tui/message-views.js +4 -3
  70. package/dist/src/tui/model.d.ts +25 -1
  71. package/dist/src/tui/model.js +33 -9
  72. package/dist/src/tui/presentation.d.ts +11 -2
  73. package/dist/src/tui/presentation.js +26 -4
  74. package/dist/src/version.d.ts +7 -0
  75. package/dist/src/version.js +14 -6
  76. package/package.json +1 -1
  77. package/skills/pager/SKILL.md +35 -45
@@ -119,12 +119,21 @@ export declare function formatDiagnostic(diagnostic: OperatorDiagnostic, width:
119
119
  * An answer is admission, not the resulting state change, so an accepted command is worded as
120
120
  * accepted. A command that got no answer at all is worded as unknown, because the broker may have
121
121
  * applied it.
122
- * @param state - Current projection, which names the agent a command targeted.
122
+ * @param target - What the command acted on, as `describeCommandTarget` named it when the command was submitted.
123
123
  * @param command - Command the operator submitted.
124
124
  * @param result - The broker's admission answer, or null when none came back.
125
125
  * @returns Severity and content-free summary for the log row.
126
126
  */
127
- export declare function describeCommandOutcome(state: TuiOperatorModelState, command: OperatorCommand, result: OperatorCommandResult | null): Pick<OperatorDiagnostic, "severity" | "summary">;
127
+ export declare function describeCommandOutcome(target: string, command: OperatorCommand, result: OperatorCommandResult | null): Pick<OperatorDiagnostic, "severity" | "summary">;
128
+ /**
129
+ * Name what one operator command acts on, in the words the diagnostics log uses.
130
+ *
131
+ * Read at submission: a purge removes its agents from the projection before its answer arrives.
132
+ * @param state - Current projection, which names the agents a command targets while they remain in it.
133
+ * @param command - Command the operator is submitting.
134
+ * @returns - The message id, the agent handle, or the number of agents a multi-agent purge names.
135
+ */
136
+ export declare function describeCommandTarget(state: TuiOperatorModelState, command: OperatorCommand): string;
128
137
  /**
129
138
  * Select the colour role one diagnostic severity carries.
130
139
  * @param severity - Attention the condition asks for.
@@ -135,20 +135,42 @@ export function formatDiagnostic(diagnostic, width) {
135
135
  * An answer is admission, not the resulting state change, so an accepted command is worded as
136
136
  * accepted. A command that got no answer at all is worded as unknown, because the broker may have
137
137
  * applied it.
138
- * @param state - Current projection, which names the agent a command targeted.
138
+ * @param target - What the command acted on, as `describeCommandTarget` named it when the command was submitted.
139
139
  * @param command - Command the operator submitted.
140
140
  * @param result - The broker's admission answer, or null when none came back.
141
141
  * @returns Severity and content-free summary for the log row.
142
142
  */
143
- export function describeCommandOutcome(state, command, result) {
144
- const verb = { ping_agent: "ping", archive_agent: "archive", expire_message: "expire" }[command.type];
143
+ export function describeCommandOutcome(target, command, result) {
144
+ const verb = { ping_agent: "ping", archive_agent: "archive", expire_message: "expire", purge_agents: "purge" }[command.type];
145
145
  if (result === null)
146
146
  return { severity: "error", summary: `${verb} outcome unknown: no answer came back from the broker` };
147
147
  if (result.status === "rejected")
148
148
  return { severity: "warning", summary: `${verb} refused: ${result.reason}` };
149
- const target = command.type === "expire_message" ? command.messageId : agentHandle(state, command.targetAgentId);
150
149
  return { severity: "info", summary: `${verb} accepted for ${target}` };
151
150
  }
151
+ /**
152
+ * Name what one operator command acts on, in the words the diagnostics log uses.
153
+ *
154
+ * Read at submission: a purge removes its agents from the projection before its answer arrives.
155
+ * @param state - Current projection, which names the agents a command targets while they remain in it.
156
+ * @param command - Command the operator is submitting.
157
+ * @returns - The message id, the agent handle, or the number of agents a multi-agent purge names.
158
+ */
159
+ export function describeCommandTarget(state, command) {
160
+ switch (command.type) {
161
+ case "expire_message":
162
+ return command.messageId;
163
+ case "ping_agent":
164
+ case "archive_agent":
165
+ return agentHandle(state, command.targetAgentId);
166
+ case "purge_agents": {
167
+ const [only] = command.targetAgentIds;
168
+ return command.targetAgentIds.length === 1 && only !== undefined
169
+ ? agentHandle(state, only)
170
+ : `${String(command.targetAgentIds.length)} agents`;
171
+ }
172
+ }
173
+ }
152
174
  /**
153
175
  * Select the colour role one diagnostic severity carries.
154
176
  * @param severity - Attention the condition asks for.
@@ -9,3 +9,10 @@
9
9
  export declare function manifestVersion(contents: string, source: string): string;
10
10
  /** Names the published package version, read once from the nearest `package.json`. */
11
11
  export declare const PAGER_VERSION: string;
12
+ /**
13
+ * Reports whether this code runs from a git checkout, detected by a `.git` entry beside
14
+ * `package.json`, which a published tarball never carries.
15
+ *
16
+ * A checkout's version names its last release, not the unreleased code it runs.
17
+ */
18
+ export declare const PAGER_SOURCE_CHECKOUT: boolean;
@@ -1,4 +1,4 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { isPlainObject } from "./shared/guards.js";
@@ -24,19 +24,19 @@ export function manifestVersion(contents, source) {
24
24
  return parsed.version;
25
25
  }
26
26
  /**
27
- * Read the published package version from the nearest `package.json` above this file.
27
+ * Find the nearest `package.json` above this file and read the version it declares.
28
28
  *
29
29
  * Walks up rather than using one fixed relative path: this module runs both directly from `src/`
30
30
  * (Bun executing TypeScript unbuilt) and from the compiled `dist/src/` tree, one directory deeper, so
31
31
  * no single relative path reaches `package.json` in both.
32
- * @returns - The version field from the nearest ancestor `package.json`.
32
+ * @returns - The directory holding that `package.json`, and its version field.
33
33
  */
34
- function readPackageVersion() {
34
+ function readPackage() {
35
35
  let directory = dirname(fileURLToPath(import.meta.url));
36
36
  for (;;) {
37
37
  try {
38
38
  const path = join(directory, "package.json");
39
- return manifestVersion(readFileSync(path, "utf8"), path);
39
+ return { root: directory, version: manifestVersion(readFileSync(path, "utf8"), path) };
40
40
  }
41
41
  catch (error) {
42
42
  if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"))
@@ -48,5 +48,13 @@ function readPackageVersion() {
48
48
  directory = parent;
49
49
  }
50
50
  }
51
+ const PACKAGE = readPackage();
51
52
  /** Names the published package version, read once from the nearest `package.json`. */
52
- export const PAGER_VERSION = readPackageVersion();
53
+ export const PAGER_VERSION = PACKAGE.version;
54
+ /**
55
+ * Reports whether this code runs from a git checkout, detected by a `.git` entry beside
56
+ * `package.json`, which a published tarball never carries.
57
+ *
58
+ * A checkout's version names its last release, not the unreleased code it runs.
59
+ */
60
+ export const PAGER_SOURCE_CHECKOUT = existsSync(join(PACKAGE.root, ".git"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haywoodmarx/agent-pager",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Durable local messaging between agent implementations.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -5,18 +5,17 @@ description: Send and receive durable messages between agents over the Pager MCP
5
5
 
6
6
  # Pager
7
7
 
8
+ This skill covers receiving and acknowledging messages, your identity, sending, and what each error code asks you to do.
9
+
8
10
  ## When a message arrives
9
11
 
10
- Pager delivers to your session. Something arrives in one of two forms:
12
+ Pager delivers to your session in one of two forms:
11
13
 
12
- ```
13
- Something arrived — which form?
14
- ├─ a delivery whose first line is "pager push", carrying messages already claimed for you,
15
- │ each with its claimToken
16
- │ └─ ack, then work
17
- └─ a body-free line beginning "pager-inbox" with reason "operator_ping"
18
- └─ a human pinged you. Nothing to fetch
19
- ```
14
+ - A delivery whose first line is `pager push` carries messages already claimed for you, each with its `claimToken`. Acknowledge them, then do the work.
15
+ - A body-free line beginning `pager-inbox` with reason `operator_ping` means a human pinged you. There is nothing to fetch.
16
+ - A body-free line beginning `pager-inbox` with reason `session_claimed` means another session took your alias over and ended your session. Your next call runs as a fresh identity. There is nothing to fetch, and messages for the old alias now go to the new holder.
17
+
18
+ The calls:
20
19
 
21
20
  ```jsonc
22
21
  // only to recover a claim_expired message: fetch_agent_messages { "limit": 50 } →
@@ -40,34 +39,27 @@ A token in `rejected` acknowledged nothing. Its `reason` says what to do:
40
39
  | `foreign_token` | The token belongs to another agent. Ack only tokens delivered to you |
41
40
  | `unknown_token` | No such token. Check that it was copied whole from the claim |
42
41
 
43
- Ack as soon as you hold a message, within 5 minutes, or it is redelivered. Ack means received and
44
- yours to act on, not finished. Redelivery backs off and gives up after 24 hours from that message's
45
- arrival: one still unclaimed past that budget is dead-lettered, with no wake to the recipient.
42
+ Ack as soon as you hold a message, within 5 minutes, or the broker redelivers it. An ack means the message is received and yours to act on, not that the work is finished. The broker retries redelivery with growing delays for up to 24 hours from the message's arrival. It dead-letters a message still unclaimed after that, without waking the recipient.
46
43
 
47
- A message carrying `to: *@workspace` (`broadcastAddress` in a fetch) is one copy of a broadcast to
48
- that workspace's agents. A reply to its `fromAddress` reaches the sender alone.
44
+ A message carrying `to: *@workspace`, shown as `broadcastAddress` in a fetch, is one copy of a broadcast to that workspace's agents. A reply to its `fromAddress` reaches the sender alone.
49
45
 
50
- ## Identity
46
+ ## Your identity and alias
51
47
 
52
- `whoami` returns your agent id, current alias, and prior aliases.
48
+ Call `whoami` for the address an agent in another workspace uses to reach you.
53
49
 
54
- `update_agent_alias` replaces your alias and durable session name; it fails with `alias_conflict` if
55
- the alias is taken. Prior names stay reserved, so a stale sender gets `recipient_alias_changed` rather
56
- than a silent redirect.
50
+ `update_agent_alias` replaces your alias and durable session name. `move_to_workspace` moves you into another workspace, named as `list_agents` reports it or as an absolute directory, with an optional new alias. Both fail with `alias_conflict` naming the holder when the alias is taken there. Pass `force` only when that holder is your own earlier session. You take its identity over, it is told, and its session ends, so its next call runs as a fresh unnamed agent. Every name and address you held stays reserved, so a stale sender gets `recipient_alias_changed` rather than a silent redirect.
57
51
 
58
- ## Sending
52
+ ## Sending a message
59
53
 
60
- An address is either a bare alias (an agent in your own workspace) or `alias@workspace`, naming the
61
- workspace explicitly — your own or another's.
54
+ Your workspace is your Git repository, so agents in its other worktrees are in it too. Choose the address by who you are addressing:
62
55
 
63
- ```
64
- Who are you addressing?
65
- ├─ answering a message → its `from` or `fromAddress`. There is no reply tool
66
- ├─ an id or address a listing gave you → send it back verbatim
67
- ├─ an agent in your own workspace → its alias
68
- ├─ an agent anywhere else → alias@workspace
69
- └─ every agent in a workspace → broadcast_agent_message
70
- ```
56
+ - Answering a message: its `from` or `fromAddress`. There is no reply tool.
57
+ - An id or address a listing gave you: send it back verbatim.
58
+ - An agent in your own workspace: its alias.
59
+ - An agent in another workspace: `alias@workspace`.
60
+ - Every agent in a workspace: `broadcast_agent_message`.
61
+
62
+ The calls:
71
63
 
72
64
  ```jsonc
73
65
  // send_agent_message
@@ -82,31 +74,29 @@ Who are you addressing?
82
74
  // → { "address": "*@web-app", "sent": [{ "messageId": "msg_…", "to": "pgr://agent/…" }], "refused": [] }
83
75
  ```
84
76
 
85
- A broadcast skips archived agents and ended ephemeral sessions. Each entry in `refused` is a
86
- recipient a rate or inbox limit turned away, with the error `code` the table below explains; the rest
87
- still received it.
77
+ A broadcast skips archived agents and ended ephemeral sessions. Each entry in `refused` is a recipient that a rate or inbox limit turned away, with an error `code` from the error code table. Every other recipient received the message.
88
78
 
89
- - `list_team_members` lists your workspace; `list_agents` lists everyone with the `address` to reach them.
90
- - `subject` is required and capped at 40 characters. Write it like a commit summary — the operator scans a column of them, so put the detail in `body`.
91
- - `type` is required free text capped at 8 characters, for a coarse category like `"brief"`, `"idle"`, or `"status"`. Validated the same way as `subject`; carried through the claim and shown in the operator's views. The broker enforces no fixed vocabulary.
92
- - The broker treats `body` as opaque; it does not define an envelope shape. Send the detail itself — a plain sentence, not an envelope.
93
- - Cap 12 KB. Send a path or an id, not a file.
79
+ - `list_team_members` lists your workspace. `list_agents` lists every agent with the `address` that reaches it.
80
+ - `subject` is required and capped at 40 characters. Write it like a commit summary, because the operator scans a column of them, and put the detail in `body`.
81
+ - `type` is required free text capped at 8 characters, naming a coarse category such as `"brief"`, `"idle"` or `"status"`. The broker enforces no fixed vocabulary. The recipient sees it in the claim, and the operator sees it in their views.
82
+ - The broker treats `body` as opaque text. Write the detail itself in plain sentences, not a structured envelope.
83
+ - `body` is capped at 12 KB. Send a path or an id, not a file's contents.
94
84
  - Add `idempotencyKey` on any send you might retry.
95
- - `get_message_status { messageId }` for something you sent.
85
+ - Call `get_message_status { messageId }` to check a message you sent.
96
86
 
97
- ## On error
87
+ ## Error codes
98
88
 
99
89
  | Code | Do |
100
90
  | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
101
91
  | `unknown_recipient` | `list_agents`, then use a listed `address`. Do not retry the same one |
102
- | `recipient_alias_changed` | Resend to the alias named in the error |
103
- | `alias_conflict` | Requested alias is taken in this workspace, or is a previous name reserved for its old holder. Pick another |
92
+ | `recipient_alias_changed` | Resend to the address named in the error |
93
+ | `alias_conflict` | Requested alias is taken in that workspace, or is a previous name reserved for its old holder. Pick another, or `force` if it is your own |
104
94
  | `conflict` | Rate (60/min per pair) or inbox (1000) limit: back off, do not loop. Any other cause: the request cannot succeed as sent, so read the message |
105
95
  | `oversized_content` | The named field (body, subject, or type) exceeds its limit; shorten that field |
106
96
  | `validation_failed` | The error message names the cause (a field, an unknown id, a malformed request); read it, do not retry unchanged |
107
97
 
108
- ## Never
98
+ ## Never do these
109
99
 
110
- - Poll `fetch_agent_messages` on a timer — messages are delivered to you.
100
+ - Poll `fetch_agent_messages` on a timer. The broker delivers messages to you.
111
101
  - Ack a message you have not read.
112
- - Put tokens, credentials, or reservations in a body. Bodies are shown in operator tools.
102
+ - Put tokens, credentials, or reservations in a body. Operator tools show bodies.