@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.
- package/README.md +4 -4
- package/dist/src/broker/address.d.ts +7 -0
- package/dist/src/broker/address.js +9 -0
- package/dist/src/broker/broker.d.ts +91 -23
- package/dist/src/broker/broker.js +505 -123
- package/dist/src/broker/operator-projection.js +3 -5
- package/dist/src/broker/protocol.d.ts +1 -1
- package/dist/src/broker/protocol.js +1 -1
- package/dist/src/broker/schema.d.ts +4 -0
- package/dist/src/broker/schema.js +9 -6
- package/dist/src/broker/snapshot.d.ts +4 -2
- package/dist/src/implementations/claude/gateway-cli.js +1 -2
- package/dist/src/implementations/claude/gateway.d.ts +11 -4
- package/dist/src/implementations/claude/gateway.js +44 -14
- package/dist/src/implementations/claude/instructions.js +1 -1
- package/dist/src/implementations/model.d.ts +6 -3
- package/dist/src/implementations/opencode/plugin.d.ts +7 -2
- package/dist/src/implementations/opencode/plugin.js +103 -7
- package/dist/src/implementations/pi/broker-client.d.ts +13 -4
- package/dist/src/implementations/pi/broker-client.js +71 -9
- package/dist/src/implementations/pi/extension.d.ts +7 -2
- package/dist/src/implementations/pi/extension.js +15 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/launch/broker-lock.d.ts +58 -4
- package/dist/src/launch/broker-lock.js +140 -7
- package/dist/src/launch/launch.d.ts +1 -1
- package/dist/src/launch/on-demand.d.ts +8 -8
- package/dist/src/launch/on-demand.js +54 -35
- package/dist/src/launch/runtime-composition.js +2 -0
- package/dist/src/launch/runtime.js +4 -14
- package/dist/src/launch/service.d.ts +1 -0
- package/dist/src/launch/service.js +23 -1
- package/dist/src/launch/workspace-branch.d.ts +3 -3
- package/dist/src/launch/workspace-branch.js +7 -15
- package/dist/src/launch/workspace-root.d.ts +13 -0
- package/dist/src/launch/workspace-root.js +27 -0
- package/dist/src/messaging/delivery-client.d.ts +17 -0
- package/dist/src/messaging/delivery-client.js +31 -1
- package/dist/src/messaging/identity-statement.d.ts +7 -0
- package/dist/src/messaging/identity-statement.js +8 -0
- package/dist/src/messaging/mcp.d.ts +8 -2
- package/dist/src/messaging/mcp.js +54 -1
- package/dist/src/messaging/notifications.d.ts +31 -3
- package/dist/src/messaging/notifications.js +25 -0
- package/dist/src/messaging/push-delivery.d.ts +2 -1
- package/dist/src/messaging/push-delivery.js +24 -7
- package/dist/src/messaging/tools.js +22 -2
- package/dist/src/messaging/wake.js +7 -0
- package/dist/src/operator/agent-projection.d.ts +8 -5
- package/dist/src/operator/agent-projection.js +29 -9
- package/dist/src/operator/protocol.d.ts +2 -2
- package/dist/src/operator/repository-system.d.ts +29 -0
- package/dist/src/operator/repository-system.js +39 -12
- package/dist/src/operator/repository.d.ts +28 -11
- package/dist/src/operator/repository.js +37 -4
- package/dist/src/shared/versions.d.ts +18 -0
- package/dist/src/shared/versions.js +26 -0
- package/dist/src/tui/agent-tree.js +2 -2
- package/dist/src/version.d.ts +7 -0
- package/dist/src/version.js +14 -6
- package/package.json +1 -1
- package/skills/pager/SKILL.md +35 -45
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a Git port so each path is inspected at most once per expiry window.
|
|
4
|
+
*
|
|
5
|
+
* A thrown inspection is remembered like an answered one, and rethrown for the rest of its window.
|
|
6
|
+
* @param git - Port that performs the inspection when nothing current is remembered.
|
|
7
|
+
* @param options - Clock and expiry window.
|
|
8
|
+
* @returns A port answering repeated requests for one path from the last inspection within the window.
|
|
9
|
+
*/
|
|
10
|
+
export function rememberRepositoryInspections(git, options) {
|
|
11
|
+
const remembered = new Map();
|
|
12
|
+
const inspectAndRemember = (path, now) => {
|
|
13
|
+
let outcome;
|
|
14
|
+
try {
|
|
15
|
+
outcome = { kind: "answered", inspection: git.inspect(path) };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
outcome = { kind: "threw", error };
|
|
19
|
+
}
|
|
20
|
+
remembered.set(path, { outcome, expiresAt: now + options.ttlMs });
|
|
21
|
+
return outcome;
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
inspect: (path) => {
|
|
25
|
+
const now = options.now();
|
|
26
|
+
const current = remembered.get(path);
|
|
27
|
+
const outcome = current && current.expiresAt > now ? current.outcome : inspectAndRemember(path, now);
|
|
28
|
+
if (outcome.kind === "threw")
|
|
29
|
+
throw outcome.error;
|
|
30
|
+
return outcome.inspection;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
2
34
|
/** Reports invalid or unavailable repository projection inputs. */
|
|
3
35
|
class RepositoryProjectionError extends Error {
|
|
4
36
|
}
|
|
@@ -17,14 +49,15 @@ export class RepositoryDiscoveryError extends RepositoryProjectionError {
|
|
|
17
49
|
}
|
|
18
50
|
}
|
|
19
51
|
/**
|
|
20
|
-
* Project one broker workspace
|
|
52
|
+
* Project one directory, a broker workspace or an agent's working directory, into repository
|
|
53
|
+
* grouping and worktree detail.
|
|
21
54
|
*
|
|
22
|
-
* A
|
|
55
|
+
* A directory whose path no longer resolves is projected from its recorded path alone. Neither
|
|
23
56
|
* canonicalization nor Git discovery can run against a removed directory, and both throw rather
|
|
24
57
|
* than reporting absence, so the removal is detected before either is attempted.
|
|
25
|
-
* @param workspacePath - Absolute broker workspace path to inspect.
|
|
58
|
+
* @param workspacePath - Absolute broker workspace path, or agent working directory, to inspect.
|
|
26
59
|
* @param dependencies - Explicit filesystem and Git discovery collaborators.
|
|
27
|
-
* @returns Presentation-only repository and worktree values, and whether the
|
|
60
|
+
* @returns Presentation-only repository and worktree values, and whether the directory still resolves.
|
|
28
61
|
*/
|
|
29
62
|
export function projectRepositoryLocation(workspacePath, dependencies) {
|
|
30
63
|
if (!dependencies.fileSystem.exists(workspacePath)) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** A `major.minor.patch` version as three numbers. */
|
|
2
|
+
export type NumericVersion = readonly [number, number, number];
|
|
3
|
+
/**
|
|
4
|
+
* Read the `major.minor.patch` numbers of a version string.
|
|
5
|
+
* @param version - Version text, such as a runtime's reported version or a package release.
|
|
6
|
+
* @param extent - `prefix` accepts trailing text such as a prerelease or build suffix and ignores it;
|
|
7
|
+
* `exact` rejects any version carrying one.
|
|
8
|
+
* @returns - The three numbers, or undefined when the text does not start with, or is not exactly, a
|
|
9
|
+
* numeric version.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseNumericVersion(version: string, extent: "prefix" | "exact"): NumericVersion | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Order two numeric versions.
|
|
14
|
+
* @param left - First version.
|
|
15
|
+
* @param right - Second version.
|
|
16
|
+
* @returns - Negative when `left` is lower, positive when it is higher, zero when they are equal.
|
|
17
|
+
*/
|
|
18
|
+
export declare function compareNumericVersions(left: NumericVersion, right: NumericVersion): number;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the `major.minor.patch` numbers of a version string.
|
|
3
|
+
* @param version - Version text, such as a runtime's reported version or a package release.
|
|
4
|
+
* @param extent - `prefix` accepts trailing text such as a prerelease or build suffix and ignores it;
|
|
5
|
+
* `exact` rejects any version carrying one.
|
|
6
|
+
* @returns - The three numbers, or undefined when the text does not start with, or is not exactly, a
|
|
7
|
+
* numeric version.
|
|
8
|
+
*/
|
|
9
|
+
export function parseNumericVersion(version, extent) {
|
|
10
|
+
const match = (extent === "exact" ? /^(\d+)\.(\d+)\.(\d+)$/u : /^(\d+)\.(\d+)\.(\d+)/u).exec(version);
|
|
11
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Order two numeric versions.
|
|
15
|
+
* @param left - First version.
|
|
16
|
+
* @param right - Second version.
|
|
17
|
+
* @returns - Negative when `left` is lower, positive when it is higher, zero when they are equal.
|
|
18
|
+
*/
|
|
19
|
+
export function compareNumericVersions(left, right) {
|
|
20
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
21
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
22
|
+
if (difference !== 0)
|
|
23
|
+
return difference;
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
@@ -611,8 +611,8 @@ function archiveStatusLabel(agent) {
|
|
|
611
611
|
return "archived by operator";
|
|
612
612
|
case "lifecycle":
|
|
613
613
|
return agent.lifecycle;
|
|
614
|
-
case "
|
|
615
|
-
return "
|
|
614
|
+
case "directory_removed":
|
|
615
|
+
return "directory removed";
|
|
616
616
|
}
|
|
617
617
|
}
|
|
618
618
|
const TREE_STATE_COLUMN = 2;
|
package/dist/src/version.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/version.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
32
|
+
* @returns - The directory holding that `package.json`, and its version field.
|
|
33
33
|
*/
|
|
34
|
-
function
|
|
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 =
|
|
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
package/skills/pager/SKILL.md
CHANGED
|
@@ -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
|
|
12
|
+
Pager delivers to your session in one of two forms:
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
46
|
+
## Your identity and alias
|
|
51
47
|
|
|
52
|
-
`whoami`
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
90
|
-
- `subject` is required and capped at 40 characters. Write it like a commit summary
|
|
91
|
-
- `type` is required free text capped at 8 characters,
|
|
92
|
-
- The broker treats `body` as opaque
|
|
93
|
-
-
|
|
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 }`
|
|
85
|
+
- Call `get_message_status { messageId }` to check a message you sent.
|
|
96
86
|
|
|
97
|
-
##
|
|
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
|
|
103
|
-
| `alias_conflict` | Requested alias is taken in
|
|
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
|
|
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.
|
|
102
|
+
- Put tokens, credentials, or reservations in a body. Operator tools show bodies.
|