@haywoodmarx/agent-pager 0.1.3 → 0.2.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.
Files changed (63) 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 +91 -23
  5. package/dist/src/broker/broker.js +505 -123
  6. package/dist/src/broker/operator-projection.js +3 -5
  7. package/dist/src/broker/protocol.d.ts +1 -1
  8. package/dist/src/broker/protocol.js +1 -1
  9. package/dist/src/broker/schema.d.ts +4 -0
  10. package/dist/src/broker/schema.js +9 -6
  11. package/dist/src/broker/snapshot.d.ts +4 -2
  12. package/dist/src/implementations/claude/gateway-cli.js +1 -2
  13. package/dist/src/implementations/claude/gateway.d.ts +11 -4
  14. package/dist/src/implementations/claude/gateway.js +44 -14
  15. package/dist/src/implementations/claude/instructions.js +1 -1
  16. package/dist/src/implementations/model.d.ts +6 -3
  17. package/dist/src/implementations/opencode/plugin.d.ts +7 -2
  18. package/dist/src/implementations/opencode/plugin.js +103 -7
  19. package/dist/src/implementations/pi/broker-client.d.ts +13 -4
  20. package/dist/src/implementations/pi/broker-client.js +71 -9
  21. package/dist/src/implementations/pi/extension.d.ts +7 -2
  22. package/dist/src/implementations/pi/extension.js +15 -3
  23. package/dist/src/index.d.ts +1 -1
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/launch/broker-lock.d.ts +58 -4
  26. package/dist/src/launch/broker-lock.js +140 -7
  27. package/dist/src/launch/launch.d.ts +1 -1
  28. package/dist/src/launch/on-demand.d.ts +8 -8
  29. package/dist/src/launch/on-demand.js +54 -35
  30. package/dist/src/launch/runtime-composition.js +2 -0
  31. package/dist/src/launch/runtime.js +4 -14
  32. package/dist/src/launch/service.d.ts +1 -0
  33. package/dist/src/launch/service.js +23 -1
  34. package/dist/src/launch/workspace-branch.d.ts +3 -3
  35. package/dist/src/launch/workspace-branch.js +7 -15
  36. package/dist/src/launch/workspace-root.d.ts +13 -0
  37. package/dist/src/launch/workspace-root.js +27 -0
  38. package/dist/src/messaging/delivery-client.d.ts +17 -0
  39. package/dist/src/messaging/delivery-client.js +31 -1
  40. package/dist/src/messaging/identity-statement.d.ts +7 -0
  41. package/dist/src/messaging/identity-statement.js +8 -0
  42. package/dist/src/messaging/mcp.d.ts +8 -2
  43. package/dist/src/messaging/mcp.js +54 -1
  44. package/dist/src/messaging/notifications.d.ts +31 -3
  45. package/dist/src/messaging/notifications.js +25 -0
  46. package/dist/src/messaging/push-delivery.d.ts +2 -1
  47. package/dist/src/messaging/push-delivery.js +24 -7
  48. package/dist/src/messaging/tools.js +22 -2
  49. package/dist/src/messaging/wake.js +7 -0
  50. package/dist/src/operator/agent-projection.d.ts +8 -5
  51. package/dist/src/operator/agent-projection.js +29 -9
  52. package/dist/src/operator/protocol.d.ts +2 -2
  53. package/dist/src/operator/repository-system.d.ts +29 -0
  54. package/dist/src/operator/repository-system.js +39 -12
  55. package/dist/src/operator/repository.d.ts +28 -11
  56. package/dist/src/operator/repository.js +37 -4
  57. package/dist/src/shared/versions.d.ts +18 -0
  58. package/dist/src/shared/versions.js +26 -0
  59. package/dist/src/tui/agent-tree.js +2 -2
  60. package/dist/src/version.d.ts +7 -0
  61. package/dist/src/version.js +14 -6
  62. package/package.json +1 -1
  63. package/skills/pager/SKILL.md +35 -45
package/README.md CHANGED
@@ -52,9 +52,9 @@ pager status
52
52
  pager <implementation-id> [--session-name <name>] -- <implementation-arguments>
53
53
  ```
54
54
 
55
- The bare implementation form starts or adopts the loopback service automatically. A command already running in a hosted terminal keeps the implementation in that terminal with inherited stdio. Otherwise, it prefers an available discovered terminal provider and falls back to inherited stdio; implementation exit status is propagated to the invoking shell. A session name is also the agent's routable alias and is unique within the working directory across implementations. Supplying it again rejoins that broker identity once its holder's harness has shut down gracefully and its connector has said so, its attachment heartbeat lease has lapsed, or the holder was archived; while the holder is attached, the launch exits non-zero naming the holder and the harness does not start. Omitting it creates an ephemeral identity that is reaped once its attachment heartbeat lease lapses, or, when a conversation key makes it resumable, once it has been detached for the resumable retention window, shortened to the unused window while it has never sent a message or received one addressed to it alone. An ephemeral identity that never attached is reaped once its attach reservations have lapsed. Archiving an identity from the operator TUI ends its attachments and hides it, while messages sent to it wait; attaching again un-archives it. A named identity detached for the named archive window is archived too, and comes back when its conversation attaches or its holder resumes its attachment. Otherwise an archived identity, ephemeral or named, is reaped once the archive retention window has passed since its first archive, and reaping dead-letters its unsettled mail. The first sweep after an upgrade reaps every identity already archived for longer than that window.
55
+ The bare implementation form starts or adopts the loopback service automatically. A command already running in a hosted terminal keeps the implementation in that terminal with inherited stdio. Otherwise, it prefers an available discovered terminal provider and falls back to inherited stdio; implementation exit status is propagated to the invoking shell. A session name is also the agent's routable alias and is unique within its workspace across implementations. A session's workspace is the Git repository its working directory belongs to, so the main worktree, every linked worktree, and every subdirectory of them share one; a directory outside any repository is a workspace of its own. Agents in one workspace address each other by bare alias. Supplying it again rejoins that broker identity once its holder's harness has shut down gracefully and its connector has said so, its attachment heartbeat lease has lapsed, or the holder was archived; while the holder is attached, the launch exits non-zero naming the holder and the harness does not start. Omitting it creates an ephemeral identity that is reaped once its attachment heartbeat lease lapses, or, when a conversation key makes it resumable, once it has been detached for the resumable retention window, shortened to the unused window while it has never sent a message or received one addressed to it alone. An ephemeral identity that never attached is reaped once its attach reservations have lapsed. Archiving an identity from the operator TUI ends its attachments and hides it, while messages sent to it wait; attaching again un-archives it. A named identity detached for the named archive window is archived too, and comes back when its conversation attaches or its holder resumes its attachment. Otherwise an archived identity, ephemeral or named, is reaped once the archive retention window has passed since its first archive, and reaping dead-letters its unsettled mail. The first sweep after an upgrade reaps every identity already archived for longer than that window.
56
56
 
57
- The authenticated `whoami` MCP tool returns the agent id, current alias, and previous aliases. `update_agent_alias` atomically replaces the current alias and durable session name; assigning one to an unnamed agent makes that identity durable. Prior names remain reserved. Sending to one is rejected with `recipient_alias_changed` and the current alias rather than silently redirecting the message.
57
+ The authenticated `whoami` MCP tool returns the caller's identity and the address that reaches it from any workspace. [`src/messaging/tools.ts`](src/messaging/tools.ts) describes what each tool returns. `update_agent_alias` atomically replaces the current alias and durable session name; assigning one to an unnamed agent makes that identity durable. `move_to_workspace` moves an agent into another workspace, keeping its identity and inbox. Prior names and addresses remain reserved. Sending to one is rejected with `recipient_alias_changed` and the current address rather than silently redirecting the message. An alias another agent holds is taken over only with `force`, which hands that agent's identity to the caller, tells its session and ends it, so that session's next call runs as a fresh unnamed agent.
58
58
 
59
59
  The package also exposes its Pi integration as a Pi package. `pi install npm:@haywoodmarx/agent-pager` enables directly started Pi sessions to discover or start the broker and register themselves; `pager pi -- <pi-arguments>` loads the same extension explicitly for a wrapper-managed session.
60
60
 
@@ -64,8 +64,8 @@ Pager does not change a harness's permission settings. A session that has to wai
64
64
 
65
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
- 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).
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, and which running broker from an older release a starting one replaces, 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
 
69
- Back up the database before an upgrade if its current state matters. Schema changes are destructive: a database whose schema marker or required columns do not match the running build is recreated, discarding every identity and message it held, and the recreation is reported with the counts it deleted on the standard error of the command that started the service, whether that is `pager serve` or an on-demand start. The marker is part of the service adoption identity, so a build that would recreate the database cannot displace a running service that owns it. Deleting the database and starting the service also creates an empty broker.
69
+ Back up the database before an upgrade if its current state matters. Schema changes are destructive: a database whose schema marker or required columns do not match the running build is recreated, discarding every identity and message it held, and the recreation is reported with the counts it deleted on the standard error of the command that started the service, whether that is `pager serve` or an on-demand start. The marker is part of the service adoption identity, so no build adopts a running service whose schema differs from its own, and a newer release that replaces an older one recreates the database when their schemas differ. Deleting the database and starting the service also creates an empty broker.
70
70
 
71
71
  Per-implementation installation and conformance documentation is indexed under [`docs/implementations/acceptance/`](docs/implementations/acceptance/README.md).
@@ -7,6 +7,13 @@ export declare function parseAgentAddress(value: string): {
7
7
  sessionName: string;
8
8
  workspace: string | null;
9
9
  };
10
+ /**
11
+ * Join an alias and a workspace name into the address that reaches the agent from any workspace.
12
+ * @param alias - Session name with or without its leading at-sign.
13
+ * @param workspace - Workspace name as {@link uniquePathNames} assigns it.
14
+ * @returns - Address in the form {@link parseAgentAddress} reads.
15
+ */
16
+ export declare function formatAgentAddress(alias: string, workspace: string): string;
10
17
  /**
11
18
  * Conform a caller-supplied workspace name to the form {@link uniquePathNames} names a workspace by.
12
19
  * @param value - Untrusted workspace name, as it appears after the at-sign of an address.
@@ -14,6 +14,15 @@ export function parseAgentAddress(value) {
14
14
  const workspace = parseWorkspaceName(withoutPrefix.slice(separator + 1));
15
15
  return { sessionName: validateRegistrationSessionName(withoutPrefix.slice(0, separator)), workspace };
16
16
  }
17
+ /**
18
+ * Join an alias and a workspace name into the address that reaches the agent from any workspace.
19
+ * @param alias - Session name with or without its leading at-sign.
20
+ * @param workspace - Workspace name as {@link uniquePathNames} assigns it.
21
+ * @returns - Address in the form {@link parseAgentAddress} reads.
22
+ */
23
+ export function formatAgentAddress(alias, workspace) {
24
+ return `${normalizeAlias(alias)}@${workspace}`;
25
+ }
17
26
  /**
18
27
  * Conform a caller-supplied workspace name to the form {@link uniquePathNames} names a workspace by.
19
28
  * @param value - Untrusted workspace name, as it appears after the at-sign of an address.
@@ -84,6 +84,52 @@ export type BroadcastResult = {
84
84
  message: string;
85
85
  }[];
86
86
  };
87
+ /** Identity of one agent, as reported to that agent. */
88
+ export type AgentIdentity = {
89
+ /** Stable broker identity. */
90
+ id: AgentId;
91
+ /** Current alias, with its leading at-sign. */
92
+ alias: string;
93
+ /** Name of the workspace the alias is unique within, as an address carries it. */
94
+ workspace: string;
95
+ /** Address that reaches the agent from any workspace. */
96
+ address: string;
97
+ /** Aliases the agent held before its current one. */
98
+ previousAliases: string[];
99
+ };
100
+ /** Options for {@link Broker.relocateAgent}. */
101
+ export type AgentRelocationRequest = {
102
+ /**
103
+ * Workspace to move into: a name as {@link Broker.listAgents} reports it, or an absolute directory,
104
+ * which names the workspace a registration from it would join. Absent keeps the current workspace.
105
+ */
106
+ workspace?: string;
107
+ /** Alias to hold in the target workspace, with an optional leading at-sign. Absent keeps the current alias. */
108
+ alias?: string;
109
+ /**
110
+ * Whether to take over the identity that holds or reserves the alias in the target workspace, rather
111
+ * than fail with `alias_conflict`.
112
+ */
113
+ force?: boolean;
114
+ };
115
+ /** Records the takeover a forced relocation performed. */
116
+ export type AliasClaim = {
117
+ /** Address taken, as it reached the identity's previous holder. */
118
+ address: string;
119
+ /** Identity taken over, which the caller now acts as. */
120
+ agentId: AgentId;
121
+ /** Identity the caller gave up; a send to its address is redirected to `agentId`. */
122
+ retiredId: AgentId;
123
+ /** Address the caller held before the claim. */
124
+ retiredAddress: string;
125
+ };
126
+ /** Outcome of {@link Broker.relocateAgent}. */
127
+ export type AgentRelocation = {
128
+ /** Identity the caller now acts as, as reported to it. */
129
+ identity: AgentIdentity;
130
+ /** Takeover performed, or null when the caller kept its own identity. */
131
+ claim: AliasClaim | null;
132
+ };
87
133
  /** Options for {@link Broker.fetch}. */
88
134
  export type BrokerFetchRequest = {
89
135
  /** Agent whose queue is fetched from. */
@@ -103,13 +149,19 @@ export type BrokerOptions = {
103
149
  */
104
150
  now: () => number;
105
151
  /**
106
- * Reports the branch checked out at a workspace path, or null when it is not a repository.
152
+ * Reports the branch checked out at an agent's working directory, or null when it is not a repository.
107
153
  *
108
- * A port rather than a direct call because the real one is a `git` subprocess: it must run before
109
- * the write transaction opens, and a test must be able to assert the persisted row without
110
- * shelling out to whatever branch the developer's own checkout happens to be on.
154
+ * Called before a registration's write transaction opens, so it may block on a subprocess.
111
155
  */
112
156
  workspaceBranch: (path: string) => string | null;
157
+ /**
158
+ * Reports the directory naming the workspace an agent's working directory belongs to: the same
159
+ * directory for every working directory whose agents address each other by bare alias.
160
+ *
161
+ * Called before a registration's write transaction opens, so it may block on a subprocess. A
162
+ * throw is reported through `report`, and the working directory names its own workspace.
163
+ */
164
+ workspaceRoot: (workingDirectory: string) => string;
113
165
  /** Sets the lifetime of an unacknowledged message claim. */
114
166
  visibilityTimeoutMs?: number;
115
167
  /** Sets the delay before the first redelivery wake for an inbox whose messages went unclaimed. */
@@ -187,7 +239,7 @@ export declare class Broker {
187
239
  /**
188
240
  * Opens a durable broker database with the supplied timing and routing policies.
189
241
  * @param path - SQLite database location, or memory sentinel, for durable state.
190
- * @param options - Clock, workspace-branch lookup, and diagnostics sink are required; see {@link BrokerOptions} for its optional fields.
242
+ * @param options - Clock, workspace-branch and workspace-root lookups, and diagnostics sink are required; see {@link BrokerOptions} for its optional fields.
191
243
  */
192
244
  constructor(path: string, options: BrokerOptions);
193
245
  /** Close the broker database connection. */
@@ -222,7 +274,8 @@ export declare class Broker {
222
274
  subscribeCommits(listener: BrokerCommitListener): () => void;
223
275
  /**
224
276
  * Register a concrete agent under one workspace-unique session name.
225
- * @param args - Caller-supplied identity, topology, and implementation values.
277
+ * @param args - Caller-supplied identity, topology, and implementation values. `workspacePath` is
278
+ * the directory the agent works in, which registers it in the workspace that directory belongs to.
226
279
  * @returns - Stable agent identity registered in the requested team.
227
280
  */
228
281
  registerAgent(args: {
@@ -237,15 +290,17 @@ export declare class Broker {
237
290
  /**
238
291
  * Register or rejoin an implementation identity without a prior launch reservation.
239
292
  *
240
- * A `sessionKey` makes the identity resumable: the same harness conversation returning after a
241
- * restart resolves to the identity it already owns, keeping its alias, inbox, and traffic, whatever
242
- * the caller passes as `sessionName`. Without one, rejoining still falls back to matching the alias.
293
+ * A `sessionKey` makes the identity resumable: the same harness conversation returning to the same
294
+ * working directory after a restart resolves to the identity it already owns, keeping its alias,
295
+ * inbox, and traffic, whatever the caller passes as `sessionName`. Without one, rejoining still
296
+ * falls back to matching the alias.
243
297
  *
244
298
  * A `continuesSessionKey` is the key a conversation was known by before it continued under
245
299
  * `sessionKey`. When `sessionKey` owns no identity yet, the identity the continued key owns moves
246
300
  * to `sessionKey`, its holder displaced, so the alias, inbox, and traffic follow the conversation.
247
- * @param args - Validated workspace, implementation, optional alias, optional resumable key, and
248
- * the key of the conversation that one continues, which requires the resumable key.
301
+ * @param args - Directory the agent works in, which registers it in the workspace that directory
302
+ * belongs to; implementation; optional alias; optional resumable key; and the key of the
303
+ * conversation that one continues, which requires the resumable key.
249
304
  * @returns - Stable agent identity, its current alias, and a fresh single-use attachment reservation.
250
305
  */
251
306
  registerAgentWithoutReservation(args: {
@@ -267,24 +322,37 @@ export declare class Broker {
267
322
  * @param requestedAlias - Workspace-unique name, with an optional leading at-sign.
268
323
  * @returns - Current identity and alias history after the replacement.
269
324
  */
270
- updateAgentAlias(agentId: AgentId, requestedAlias: string): {
271
- id: AgentId;
272
- alias: string;
273
- previousAliases: string[];
274
- };
325
+ updateAgentAlias(agentId: AgentId, requestedAlias: string): AgentIdentity;
326
+ /**
327
+ * Move an agent to another workspace, rename it, or both, in one step.
328
+ *
329
+ * The alias must be free in the target workspace: held by nobody else and reserved as nobody
330
+ * else's retired name. Without `force` a taken alias fails with `alias_conflict` naming its holder
331
+ * and changes nothing. With `force` the caller takes the holder's identity over: its id, alias,
332
+ * inbox and alias history become the caller's, and the identity joins the target workspace, which
333
+ * is the caller's own when the request names none; the caller's own connections and
334
+ * conversation key move onto it, and the caller's previous identity is retired with its messages
335
+ * moved across and its aliases reserved as redirects to the claimed identity. The holder's own
336
+ * sessions and unconsumed attach reservations end, so a session or resume token it presents is
337
+ * refused as expired, and a claim token it held acknowledges nothing.
338
+ *
339
+ * A move keeps every alias the agent held reserved in the workspace it leaves, redirecting to its
340
+ * current address, and carries its alias history into the workspace it joins.
341
+ * @param agentId - Authenticated identity asking to move or rename.
342
+ * @param request - Target workspace, alias, and whether to take the alias over from its holder.
343
+ * @returns - The identity the caller now acts as, and the takeover when one was performed.
344
+ */
345
+ relocateAgent(agentId: AgentId, request: AgentRelocationRequest): AgentRelocation;
275
346
  /**
276
- * Return the authenticated identity, current alias, and prior routable names.
347
+ * Return an agent's identity as reported to that agent.
277
348
  * @param agentId - Stable identity to inspect.
278
349
  * @returns - Identity details safe to return to the owning agent.
279
350
  */
280
- agentIdentity(agentId: AgentId): {
281
- id: AgentId;
282
- alias: string;
283
- previousAliases: string[];
284
- };
351
+ agentIdentity(agentId: AgentId): AgentIdentity;
285
352
  /**
286
353
  * Reserve an identity and single-use attach reservation for a future process.
287
- * @param args - Registration name, team, and workspace the reservation is created under.
354
+ * @param args - Registration name, team, and the directory the future process works in, which
355
+ * creates the reservation in the workspace that directory belongs to.
288
356
  * @returns - Reservation identity and one-time reservation for the future agent.
289
357
  */
290
358
  reserveAgent(args: {