@haywoodmarx/agent-pager 0.1.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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/src/broker/address.d.ts +49 -0
  4. package/dist/src/broker/address.js +110 -0
  5. package/dist/src/broker/broker.d.ts +665 -0
  6. package/dist/src/broker/broker.js +2270 -0
  7. package/dist/src/broker/database.d.ts +81 -0
  8. package/dist/src/broker/database.js +69 -0
  9. package/dist/src/broker/operator-projection.d.ts +291 -0
  10. package/dist/src/broker/operator-projection.js +722 -0
  11. package/dist/src/broker/protocol.d.ts +237 -0
  12. package/dist/src/broker/protocol.js +311 -0
  13. package/dist/src/broker/schema.d.ts +94 -0
  14. package/dist/src/broker/schema.js +454 -0
  15. package/dist/src/broker/snapshot.d.ts +171 -0
  16. package/dist/src/broker/snapshot.js +78 -0
  17. package/dist/src/configuration-defaults.d.ts +64 -0
  18. package/dist/src/configuration-defaults.js +48 -0
  19. package/dist/src/configuration.d.ts +76 -0
  20. package/dist/src/configuration.js +156 -0
  21. package/dist/src/implementations/claude/environment.d.ts +8 -0
  22. package/dist/src/implementations/claude/environment.js +9 -0
  23. package/dist/src/implementations/claude/gateway-cli.d.ts +2 -0
  24. package/dist/src/implementations/claude/gateway-cli.js +136 -0
  25. package/dist/src/implementations/claude/gateway-identity.d.ts +134 -0
  26. package/dist/src/implementations/claude/gateway-identity.js +103 -0
  27. package/dist/src/implementations/claude/gateway.d.ts +148 -0
  28. package/dist/src/implementations/claude/gateway.js +236 -0
  29. package/dist/src/implementations/claude/instructions.d.ts +33 -0
  30. package/dist/src/implementations/claude/instructions.js +64 -0
  31. package/dist/src/implementations/claude/manifest.d.ts +33 -0
  32. package/dist/src/implementations/claude/manifest.js +108 -0
  33. package/dist/src/implementations/claude/plugin-directory.d.ts +80 -0
  34. package/dist/src/implementations/claude/plugin-directory.js +137 -0
  35. package/dist/src/implementations/claude/session-continuation.d.ts +25 -0
  36. package/dist/src/implementations/claude/session-continuation.js +125 -0
  37. package/dist/src/implementations/claude/session-identity.d.ts +25 -0
  38. package/dist/src/implementations/claude/session-identity.js +31 -0
  39. package/dist/src/implementations/claude/session-start-cli.d.ts +2 -0
  40. package/dist/src/implementations/claude/session-start-cli.js +16 -0
  41. package/dist/src/implementations/claude/socket-delivery.d.ts +52 -0
  42. package/dist/src/implementations/claude/socket-delivery.js +159 -0
  43. package/dist/src/implementations/model.d.ts +162 -0
  44. package/dist/src/implementations/model.js +1 -0
  45. package/dist/src/implementations/opencode/adapter.d.ts +71 -0
  46. package/dist/src/implementations/opencode/adapter.js +94 -0
  47. package/dist/src/implementations/opencode/environment.d.ts +8 -0
  48. package/dist/src/implementations/opencode/environment.js +9 -0
  49. package/dist/src/implementations/opencode/manifest.d.ts +30 -0
  50. package/dist/src/implementations/opencode/manifest.js +93 -0
  51. package/dist/src/implementations/opencode/plugin-api.d.ts +120 -0
  52. package/dist/src/implementations/opencode/plugin-api.js +1 -0
  53. package/dist/src/implementations/opencode/plugin.d.ts +38 -0
  54. package/dist/src/implementations/opencode/plugin.js +303 -0
  55. package/dist/src/implementations/opencode/self-close-tool.d.ts +9 -0
  56. package/dist/src/implementations/opencode/self-close-tool.js +31 -0
  57. package/dist/src/implementations/orca/terminal.d.ts +48 -0
  58. package/dist/src/implementations/orca/terminal.js +154 -0
  59. package/dist/src/implementations/pi/broker-client.d.ts +56 -0
  60. package/dist/src/implementations/pi/broker-client.js +156 -0
  61. package/dist/src/implementations/pi/environment.d.ts +8 -0
  62. package/dist/src/implementations/pi/environment.js +9 -0
  63. package/dist/src/implementations/pi/extension.d.ts +57 -0
  64. package/dist/src/implementations/pi/extension.js +253 -0
  65. package/dist/src/implementations/pi/host-slots.d.ts +18 -0
  66. package/dist/src/implementations/pi/host-slots.js +27 -0
  67. package/dist/src/implementations/pi/manifest.d.ts +11 -0
  68. package/dist/src/implementations/pi/manifest.js +61 -0
  69. package/dist/src/implementations/pi/self-close-tool.d.ts +7 -0
  70. package/dist/src/implementations/pi/self-close-tool.js +27 -0
  71. package/dist/src/implementations/registry.d.ts +47 -0
  72. package/dist/src/implementations/registry.js +157 -0
  73. package/dist/src/implementations/session-key.d.ts +13 -0
  74. package/dist/src/implementations/session-key.js +19 -0
  75. package/dist/src/index.d.ts +27 -0
  76. package/dist/src/index.js +26 -0
  77. package/dist/src/launch/broker-lock.d.ts +257 -0
  78. package/dist/src/launch/broker-lock.js +644 -0
  79. package/dist/src/launch/cli.d.ts +37 -0
  80. package/dist/src/launch/cli.js +317 -0
  81. package/dist/src/launch/environment.d.ts +45 -0
  82. package/dist/src/launch/environment.js +48 -0
  83. package/dist/src/launch/launch.d.ts +235 -0
  84. package/dist/src/launch/launch.js +223 -0
  85. package/dist/src/launch/on-demand.d.ts +202 -0
  86. package/dist/src/launch/on-demand.js +435 -0
  87. package/dist/src/launch/runtime-composition.d.ts +88 -0
  88. package/dist/src/launch/runtime-composition.js +253 -0
  89. package/dist/src/launch/runtime.d.ts +79 -0
  90. package/dist/src/launch/runtime.js +196 -0
  91. package/dist/src/launch/service-system.d.ts +11 -0
  92. package/dist/src/launch/service-system.js +39 -0
  93. package/dist/src/launch/service.d.ts +173 -0
  94. package/dist/src/launch/service.js +492 -0
  95. package/dist/src/launch/skill.d.ts +57 -0
  96. package/dist/src/launch/skill.js +81 -0
  97. package/dist/src/launch/terminal.d.ts +26 -0
  98. package/dist/src/launch/terminal.js +43 -0
  99. package/dist/src/launch/workspace-branch.d.ts +10 -0
  100. package/dist/src/launch/workspace-branch.js +29 -0
  101. package/dist/src/messaging/delivery-client.d.ts +144 -0
  102. package/dist/src/messaging/delivery-client.js +261 -0
  103. package/dist/src/messaging/delivery.d.ts +75 -0
  104. package/dist/src/messaging/delivery.js +22 -0
  105. package/dist/src/messaging/inbox-socket.d.ts +98 -0
  106. package/dist/src/messaging/inbox-socket.js +278 -0
  107. package/dist/src/messaging/mcp.d.ts +53 -0
  108. package/dist/src/messaging/mcp.js +501 -0
  109. package/dist/src/messaging/notifications.d.ts +231 -0
  110. package/dist/src/messaging/notifications.js +489 -0
  111. package/dist/src/messaging/push-delivery.d.ts +77 -0
  112. package/dist/src/messaging/push-delivery.js +187 -0
  113. package/dist/src/messaging/tools.d.ts +11 -0
  114. package/dist/src/messaging/tools.js +102 -0
  115. package/dist/src/messaging/wake.d.ts +23 -0
  116. package/dist/src/messaging/wake.js +39 -0
  117. package/dist/src/operator/agent-projection.d.ts +102 -0
  118. package/dist/src/operator/agent-projection.js +249 -0
  119. package/dist/src/operator/commands.d.ts +50 -0
  120. package/dist/src/operator/commands.js +199 -0
  121. package/dist/src/operator/diagnostics.d.ts +21 -0
  122. package/dist/src/operator/diagnostics.js +22 -0
  123. package/dist/src/operator/protocol.d.ts +511 -0
  124. package/dist/src/operator/protocol.js +49 -0
  125. package/dist/src/operator/repository-system.d.ts +14 -0
  126. package/dist/src/operator/repository-system.js +150 -0
  127. package/dist/src/operator/repository.d.ts +79 -0
  128. package/dist/src/operator/repository.js +72 -0
  129. package/dist/src/operator/traffic-projection.d.ts +19 -0
  130. package/dist/src/operator/traffic-projection.js +229 -0
  131. package/dist/src/operator/transport.d.ts +87 -0
  132. package/dist/src/operator/transport.js +549 -0
  133. package/dist/src/operator/update-store.d.ts +53 -0
  134. package/dist/src/operator/update-store.js +162 -0
  135. package/dist/src/operator/updates.d.ts +94 -0
  136. package/dist/src/operator/updates.js +280 -0
  137. package/dist/src/operator/validation.d.ts +114 -0
  138. package/dist/src/operator/validation.js +916 -0
  139. package/dist/src/shared/guards.d.ts +59 -0
  140. package/dist/src/shared/guards.js +119 -0
  141. package/dist/src/shared/http.d.ts +30 -0
  142. package/dist/src/shared/http.js +50 -0
  143. package/dist/src/shared/row.d.ts +2 -0
  144. package/dist/src/shared/row.js +1 -0
  145. package/dist/src/shared/shell.d.ts +6 -0
  146. package/dist/src/shared/shell.js +8 -0
  147. package/dist/src/tui/actions.d.ts +166 -0
  148. package/dist/src/tui/actions.js +253 -0
  149. package/dist/src/tui/agent-tree.d.ts +125 -0
  150. package/dist/src/tui/agent-tree.js +663 -0
  151. package/dist/src/tui/app.d.ts +38 -0
  152. package/dist/src/tui/app.js +378 -0
  153. package/dist/src/tui/chrome.d.ts +86 -0
  154. package/dist/src/tui/chrome.js +85 -0
  155. package/dist/src/tui/client.d.ts +41 -0
  156. package/dist/src/tui/client.js +279 -0
  157. package/dist/src/tui/composition.d.ts +41 -0
  158. package/dist/src/tui/composition.js +146 -0
  159. package/dist/src/tui/message-views.d.ts +94 -0
  160. package/dist/src/tui/message-views.js +515 -0
  161. package/dist/src/tui/model.d.ts +414 -0
  162. package/dist/src/tui/model.js +758 -0
  163. package/dist/src/tui/presentation.d.ts +350 -0
  164. package/dist/src/tui/presentation.js +541 -0
  165. package/dist/src/tui/production.d.ts +79 -0
  166. package/dist/src/tui/production.js +253 -0
  167. package/dist/src/tui/run-terminal.d.ts +32 -0
  168. package/dist/src/tui/run-terminal.js +54 -0
  169. package/dist/src/tui/runtime.d.ts +113 -0
  170. package/dist/src/tui/runtime.js +189 -0
  171. package/dist/src/tui/theme.d.ts +49 -0
  172. package/dist/src/tui/theme.js +55 -0
  173. package/dist/src/tui/transport.d.ts +31 -0
  174. package/dist/src/tui/transport.js +255 -0
  175. package/dist/src/version.d.ts +11 -0
  176. package/dist/src/version.js +52 -0
  177. package/package.json +142 -0
  178. package/skills/pager/SKILL.md +112 -0
@@ -0,0 +1,57 @@
1
+ /** Names the skill directory this package ships. */
2
+ export declare const packagedSkillName = "pager";
3
+ /** Names where the packaged skill is installed from. */
4
+ export type SkillSource = {
5
+ /** Installs a directory shipped inside this package. */
6
+ kind: "local";
7
+ /** Directory the skill is installed from. */
8
+ path: string;
9
+ };
10
+ /** Describes the child process that performs the install. */
11
+ export type SkillInstallCommand = {
12
+ /** Executable to run. */
13
+ command: string;
14
+ /** Arguments passed to the executable. */
15
+ args: string[];
16
+ };
17
+ /**
18
+ * Locate the skill directory shipped inside this package.
19
+ *
20
+ * Ancestors are walked rather than resolved at a fixed depth because the compiled entrypoint and the
21
+ * TypeScript source sit at different distances from the package root.
22
+ * @param from - Directory the upward search starts in.
23
+ * @returns - Absolute path to the packaged skill directory.
24
+ * @throws {Error} When no ancestor holds the packaged skill.
25
+ */
26
+ export declare function packagedSkillPath(from?: string): string;
27
+ /** One skill this package can install, and where it is installed from. */
28
+ export type PackagedSkill = {
29
+ /** Directory the skill installs as, and the name an operator sees. */
30
+ readonly name: string;
31
+ /** Where the skill is installed from. */
32
+ readonly source: SkillSource;
33
+ };
34
+ /**
35
+ * List the skills this build offers to install, in the order an operator is shown them.
36
+ *
37
+ * The one place the catalogue is decided. A further skill is an entry here; nothing that consumes
38
+ * this names a particular one. A build whose skill directory is absent offers nothing rather than
39
+ * throwing, so the operator screen that lists the catalogue still opens without the entry.
40
+ * @returns - Every installable skill this package ships.
41
+ */
42
+ export declare function packagedSkills(): readonly PackagedSkill[];
43
+ /**
44
+ * Report where this build installs its skill from.
45
+ *
46
+ * The one place the source is decided.
47
+ * @param path - Local skill directory, resolved from this package by default.
48
+ * @returns - Source the install command is built against.
49
+ */
50
+ export declare function packagedSkillSource(path?: string): SkillSource;
51
+ /**
52
+ * Build the `skills` CLI invocation that installs the skill.
53
+ * @param passthrough - Arguments forwarded to the CLI unaltered.
54
+ * @param source - Source to install, defaulting to the one this build ships.
55
+ * @returns - Executable and arguments for the install child process.
56
+ */
57
+ export declare function skillInstallCommand(passthrough?: readonly string[], source?: SkillSource): SkillInstallCommand;
@@ -0,0 +1,81 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /** Names the skill directory this package ships. */
5
+ export const packagedSkillName = "pager";
6
+ /**
7
+ * Pins the `skills` CLI to the major line whose `add` flags this command was written against.
8
+ * @see https://github.com/vercel-labs/skills
9
+ */
10
+ const skillsCliSpecifier = "skills@^1.5";
11
+ /**
12
+ * Locate the skill directory shipped inside this package.
13
+ *
14
+ * Ancestors are walked rather than resolved at a fixed depth because the compiled entrypoint and the
15
+ * TypeScript source sit at different distances from the package root.
16
+ * @param from - Directory the upward search starts in.
17
+ * @returns - Absolute path to the packaged skill directory.
18
+ * @throws {Error} When no ancestor holds the packaged skill.
19
+ */
20
+ export function packagedSkillPath(from = dirname(fileURLToPath(import.meta.url))) {
21
+ let directory = from;
22
+ for (;;) {
23
+ const candidate = join(directory, "skills", packagedSkillName);
24
+ if (existsSync(join(candidate, "SKILL.md")))
25
+ return candidate;
26
+ const parent = dirname(directory);
27
+ if (parent === directory)
28
+ throw new Error(`Packaged skill ${packagedSkillName} was not found above ${from}`);
29
+ directory = parent;
30
+ }
31
+ }
32
+ /** Resolved catalogue, kept because locating it walks the filesystem and it cannot move mid-process. */
33
+ let catalogue;
34
+ /**
35
+ * List the skills this build offers to install, in the order an operator is shown them.
36
+ *
37
+ * The one place the catalogue is decided. A further skill is an entry here; nothing that consumes
38
+ * this names a particular one. A build whose skill directory is absent offers nothing rather than
39
+ * throwing, so the operator screen that lists the catalogue still opens without the entry.
40
+ * @returns - Every installable skill this package ships.
41
+ */
42
+ export function packagedSkills() {
43
+ catalogue ??= resolveCatalogue(packagedSkillPath);
44
+ return catalogue;
45
+ }
46
+ /**
47
+ * List the catalogue from an injected skill locator, bypassing the cache.
48
+ * @internal
49
+ * @param locate - Resolves the packaged skill directory.
50
+ * @returns - Every installable skill this package ships.
51
+ */
52
+ export function packagedSkillsAt(locate) {
53
+ return resolveCatalogue(locate);
54
+ }
55
+ function resolveCatalogue(locate) {
56
+ try {
57
+ return [{ name: packagedSkillName, source: packagedSkillSource(locate()) }];
58
+ }
59
+ catch {
60
+ return [];
61
+ }
62
+ }
63
+ /**
64
+ * Report where this build installs its skill from.
65
+ *
66
+ * The one place the source is decided.
67
+ * @param path - Local skill directory, resolved from this package by default.
68
+ * @returns - Source the install command is built against.
69
+ */
70
+ export function packagedSkillSource(path = packagedSkillPath()) {
71
+ return { kind: "local", path };
72
+ }
73
+ /**
74
+ * Build the `skills` CLI invocation that installs the skill.
75
+ * @param passthrough - Arguments forwarded to the CLI unaltered.
76
+ * @param source - Source to install, defaulting to the one this build ships.
77
+ * @returns - Executable and arguments for the install child process.
78
+ */
79
+ export function skillInstallCommand(passthrough = [], source = packagedSkillSource()) {
80
+ return { command: "bunx", args: ["--bun", skillsCliSpecifier, "add", source.path, ...passthrough] };
81
+ }
@@ -0,0 +1,26 @@
1
+ import type { ForegroundProcessSpawner, TerminalLaunch } from "./launch.js";
2
+ /**
3
+ * Spawn a foreground child process inheriting this process's terminal.
4
+ * @returns - Process id and a one-shot listener registration, matching {@link ForegroundProcessSpawner}.
5
+ */
6
+ export declare function spawnForegroundProcess(command: string, args: string[], options: {
7
+ cwd: string;
8
+ env: NodeJS.ProcessEnv;
9
+ stdio: "inherit";
10
+ detached: false;
11
+ }): ReturnType<ForegroundProcessSpawner>;
12
+ /**
13
+ * Spawn a foreground child inheriting the caller's terminal, and settle once it exits.
14
+ * @param spawnProcess - Foreground process boundary used to start the child.
15
+ * @param request - Neutral terminal launch request supplying the command and environment layers.
16
+ * @returns - The spawned child and a promise that settles with its exit disposition.
17
+ */
18
+ export declare function spawnInheritedSession(spawnProcess: ForegroundProcessSpawner, request: TerminalLaunch): {
19
+ /** Spawned child process handle. */
20
+ child: ReturnType<ForegroundProcessSpawner>;
21
+ /** Settles with the child's exit disposition. */
22
+ exit: Promise<{
23
+ code: number | null;
24
+ signal: NodeJS.Signals | null;
25
+ }>;
26
+ };
@@ -0,0 +1,43 @@
1
+ import { spawn } from "node:child_process";
2
+ // eslint-disable-next-line jsdoc/require-param -- forwarded to spawn unmodified; options is the closed shape ForegroundProcessSpawner's contract already states.
3
+ /**
4
+ * Spawn a foreground child process inheriting this process's terminal.
5
+ * @returns - Process id and a one-shot listener registration, matching {@link ForegroundProcessSpawner}.
6
+ */
7
+ export function spawnForegroundProcess(command, args, options) {
8
+ const child = spawn(command, args, options);
9
+ return {
10
+ pid: child.pid,
11
+ once: (event, listener) => {
12
+ if (event === "error")
13
+ child.once("error", listener);
14
+ else
15
+ child.once("exit", listener);
16
+ },
17
+ };
18
+ }
19
+ /**
20
+ * Spawn a foreground child inheriting the caller's terminal, and settle once it exits.
21
+ * @param spawnProcess - Foreground process boundary used to start the child.
22
+ * @param request - Neutral terminal launch request supplying the command and environment layers.
23
+ * @returns - The spawned child and a promise that settles with its exit disposition.
24
+ */
25
+ export function spawnInheritedSession(spawnProcess, request) {
26
+ const child = spawnProcess(request.plan.command, [...request.plan.args], {
27
+ cwd: request.plan.workspacePath,
28
+ env: {
29
+ ...request.baseEnvironment,
30
+ ...request.plan.environment,
31
+ ...request.plan.sensitiveEnvironment,
32
+ },
33
+ stdio: "inherit",
34
+ detached: false,
35
+ });
36
+ const exit = new Promise((resolve, reject) => {
37
+ child.once("error", reject);
38
+ child.once("exit", (code, signal) => {
39
+ resolve({ code, signal });
40
+ });
41
+ });
42
+ return { child, exit };
43
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Report the branch checked out at a workspace path.
3
+ *
4
+ * A standalone module so a caller composing only the broker itself avoids pulling in the whole
5
+ * `BrokerService` module graph for one `git` lookup.
6
+ * @param path - Absolute workspace path to inspect.
7
+ * @returns - Current branch name, or null when the path is not a repository, is detached, or the
8
+ * lookup did not answer within {@link WORKSPACE_BRANCH_TIMEOUT_MS}.
9
+ */
10
+ export declare function systemWorkspaceBranch(path: string): string | null;
@@ -0,0 +1,29 @@
1
+ import { execFileSync } from "node:child_process";
2
+ /**
3
+ * Bounds the branch lookup, which runs a subprocess against a path this process does not control.
4
+ *
5
+ * The branch is a label on a workspace row; nothing waits on it, so a slow answer is discarded
6
+ * rather than waited for.
7
+ */
8
+ const WORKSPACE_BRANCH_TIMEOUT_MS = 2_000;
9
+ /**
10
+ * Report the branch checked out at a workspace path.
11
+ *
12
+ * A standalone module so a caller composing only the broker itself avoids pulling in the whole
13
+ * `BrokerService` module graph for one `git` lookup.
14
+ * @param path - Absolute workspace path to inspect.
15
+ * @returns - Current branch name, or null when the path is not a repository, is detached, or the
16
+ * lookup did not answer within {@link WORKSPACE_BRANCH_TIMEOUT_MS}.
17
+ */
18
+ export function systemWorkspaceBranch(path) {
19
+ try {
20
+ return (execFileSync("git", ["-C", path, "branch", "--show-current"], {
21
+ encoding: "utf8",
22
+ stdio: ["ignore", "pipe", "ignore"],
23
+ timeout: WORKSPACE_BRANCH_TIMEOUT_MS,
24
+ }).trim() || null);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
@@ -0,0 +1,144 @@
1
+ import { type AgentId } from "../broker/protocol.js";
2
+ /** Performs one broker HTTP request on behalf of an out-of-process implementation adapter. */
3
+ export type BrokerRequest = (url: string, init: RequestInit) => Promise<Response>;
4
+ /** Holds one fenced broker attachment established by an adapter process. */
5
+ export type AttachedSession = {
6
+ /** Identifies the attached broker session. */
7
+ sessionId: string;
8
+ /** Fences the attachment against a superseded broker epoch. */
9
+ epoch: string;
10
+ /** Presented back to the broker so it can rotate a lapsed or superseded session in place. */
11
+ resumeToken?: string;
12
+ /** Carries the identity the broker resolved, which a conversation key may redirect. */
13
+ agentId?: AgentId;
14
+ };
15
+ /** Carries the single-use attachments a directly started implementation process receives. */
16
+ export type BrokerIdentity = {
17
+ /** Identifies the registered broker agent. */
18
+ agentId: AgentId;
19
+ /** Attaches the model-callable messaging channel. */
20
+ messagingReservation: string;
21
+ /** Attaches the delivery channel, over which the session presents its inbox address. */
22
+ deliveryReservation: string;
23
+ };
24
+ /**
25
+ * Register a directly started implementation session through the unauthenticated broker boundary.
26
+ * @param request - HTTP operation used to reach the loopback broker.
27
+ * @param brokerUrl - Shared broker MCP endpoint.
28
+ * @param registration - Workspace and implementation identity, an optional stable session name the
29
+ * broker adopts as the alias instead of assigning one, the optional conversation key that rejoins
30
+ * an existing identity instead of forking a new one, and the optional key of the conversation that
31
+ * one continues.
32
+ * @returns - Registered identity with separate messaging and delivery attachments.
33
+ */
34
+ export declare function registerAgent(request: BrokerRequest, brokerUrl: string, registration: {
35
+ workspacePath: string;
36
+ implementationId: string;
37
+ sessionName?: string;
38
+ sessionKey?: string;
39
+ continuesSessionKey?: string;
40
+ }): Promise<BrokerIdentity>;
41
+ /** Options for {@link BrokerDeliveryClient.attach}. */
42
+ export type AttachDeliveryClientOptions = {
43
+ /** Shared broker MCP endpoint. */
44
+ brokerUrl: string;
45
+ /** Agent whose wakes this attachment may carry. */
46
+ agentId: AgentId;
47
+ /** Single-use delivery attachment credential. */
48
+ reservation: string;
49
+ /** HTTP operation used to reach the loopback broker. */
50
+ request: BrokerRequest;
51
+ /** Conversation key the broker may resolve to an existing identity. */
52
+ sessionKey?: string | undefined;
53
+ };
54
+ /**
55
+ * Holds one fenced delivery attachment: it keeps the identity's lease alive, presents the session's
56
+ * inbox address, and ends the identity when the harness leaves.
57
+ */
58
+ export declare class BrokerDeliveryClient {
59
+ #private;
60
+ private constructor();
61
+ /**
62
+ * Consume a delivery reservation and hold the resulting fenced attachment.
63
+ * @param options - Endpoint, agent identity, the attachment credential, the request seam, and an
64
+ * optional resumable conversation key.
65
+ * @returns - Connected delivery client bound to the identity the broker resolved.
66
+ */
67
+ static attach({ brokerUrl, agentId, reservation, request, sessionKey, }: AttachDeliveryClientOptions): Promise<BrokerDeliveryClient>;
68
+ /**
69
+ * Return the identity this attachment resolved to.
70
+ * @returns - Agent id the broker bound to this attachment.
71
+ */
72
+ agentId(): AgentId;
73
+ /**
74
+ * Present an inbox address on every later request, so the broker pushes this identity's wakes to it.
75
+ *
76
+ * Sent with each request rather than once, so a broker that restarted, or that dropped the address
77
+ * after a failed delivery, learns it again on the next heartbeat.
78
+ * @param address - Address text the session's inbox produced.
79
+ */
80
+ presentDeliveryAddress(address: string): void;
81
+ /** Renew the delivery attachment lease, presenting the inbox address when one is held. */
82
+ heartbeat(): Promise<void>;
83
+ /** Tell the broker the harness is shutting down, ending every attachment of this identity, this one included. */
84
+ detach(): Promise<void>;
85
+ /**
86
+ * Invoke one delivery-attached broker tool.
87
+ * @param tool - Delivery-channel tool name.
88
+ * @param args - Validated tool arguments.
89
+ * @returns - Decoded broker result payload.
90
+ */
91
+ call(tool: string, args: Record<string, unknown>): Promise<unknown>;
92
+ }
93
+ /**
94
+ * Build one compatibility-path tool call, without session headers.
95
+ * @param tool - Tool the broker dispatches on; connector-only tools are accepted on this path.
96
+ * @param args - Tool arguments as the broker validates them.
97
+ * @returns - Request the caller sends as is for a pre-attach tool, or with its session headers merged
98
+ * into `headers` for an authenticated one.
99
+ */
100
+ export declare function compatibilityToolRequest(tool: string, args: Record<string, unknown>): {
101
+ method: "POST";
102
+ headers: Record<string, string>;
103
+ body: string;
104
+ };
105
+ /**
106
+ * Take up the session generation a response announces, so a resumed attachment stays usable.
107
+ *
108
+ * A broker that rotates the session mid-request answers with the replacement credentials; keeping the
109
+ * spent ones would resume once and then fence every later call.
110
+ * @param current - Session the request was made with.
111
+ * @param response - Broker response, carrying replacement headers only when the session rotated.
112
+ * @returns - The announced session, or the current one unchanged.
113
+ */
114
+ export declare function adoptRotation(current: AttachedSession, response: Response): AttachedSession;
115
+ /**
116
+ * Decode a broker compatibility envelope, raising the broker's own message on failure.
117
+ * @param response - Broker response carrying an `{ ok, result }` envelope.
118
+ * @param operation - Operation named in the raised diagnostic.
119
+ * @returns - Envelope result payload.
120
+ */
121
+ export declare function compatibilityPayload(response: Response, operation: string): Promise<unknown>;
122
+ /**
123
+ * Decode a JSON-RPC envelope, raising the broker's own message on failure.
124
+ * @param response - Broker response carrying a JSON-RPC result.
125
+ * @param operation - Operation named in the raised diagnostic.
126
+ * @returns - JSON-RPC result payload.
127
+ */
128
+ export declare function rpcEnvelope(response: Response, operation: string): Promise<{
129
+ result: unknown;
130
+ }>;
131
+ /**
132
+ * Accept an object carrying only permitted fields, rejecting anything additional.
133
+ * @param value - Untrusted decoded payload.
134
+ * @param label - Subject named in the raised diagnostic.
135
+ * @param permitted - Field names the payload may carry.
136
+ * @returns - The payload as a plain record.
137
+ */
138
+ export declare function exactRecord(value: unknown, label: string, permitted: readonly string[]): Record<string, unknown>;
139
+ /**
140
+ * Narrow an untrusted value to a plain record.
141
+ * @param value - Untrusted decoded payload.
142
+ * @returns - The value as a plain record, or undefined when it is not one.
143
+ */
144
+ export declare function asRecord(value: unknown): Record<string, unknown> | undefined;
@@ -0,0 +1,261 @@
1
+ import { ATTACH_HEADER, isAgentId, PROTOCOL_VERSION } from "../broker/protocol.js";
2
+ import { exactFields as guardExactFields, isPlainObject } from "../shared/guards.js";
3
+ import { DELIVERY_ADDRESS_HEADER } from "./mcp.js";
4
+ /**
5
+ * Register a directly started implementation session through the unauthenticated broker boundary.
6
+ * @param request - HTTP operation used to reach the loopback broker.
7
+ * @param brokerUrl - Shared broker MCP endpoint.
8
+ * @param registration - Workspace and implementation identity, an optional stable session name the
9
+ * broker adopts as the alias instead of assigning one, the optional conversation key that rejoins
10
+ * an existing identity instead of forking a new one, and the optional key of the conversation that
11
+ * one continues.
12
+ * @returns - Registered identity with separate messaging and delivery attachments.
13
+ */
14
+ export async function registerAgent(request, brokerUrl, registration) {
15
+ const response = await request(brokerUrl, compatibilityToolRequest("register_agent", registration));
16
+ const payload = await compatibilityPayload(response, "Broker registration");
17
+ const result = exactRecord(payload, "Broker registration result", [
18
+ "agentId",
19
+ "reservation",
20
+ "deliveryReservation",
21
+ "sessionName",
22
+ "ephemeral",
23
+ "resumed",
24
+ ]);
25
+ if (!isAgentId(result.agentId))
26
+ throw new Error("Broker registration returned an invalid agent identity");
27
+ if (typeof result.reservation !== "string" || typeof result.deliveryReservation !== "string") {
28
+ throw new Error("Broker registration omitted attachment reservations");
29
+ }
30
+ return {
31
+ agentId: result.agentId,
32
+ messagingReservation: result.reservation,
33
+ deliveryReservation: result.deliveryReservation,
34
+ };
35
+ }
36
+ /**
37
+ * Holds one fenced delivery attachment: it keeps the identity's lease alive, presents the session's
38
+ * inbox address, and ends the identity when the harness leaves.
39
+ */
40
+ export class BrokerDeliveryClient {
41
+ #brokerUrl;
42
+ #agentId;
43
+ #request;
44
+ #delivery;
45
+ #deliveryAddress;
46
+ constructor(brokerUrl, agentId, request, delivery) {
47
+ this.#brokerUrl = brokerUrl;
48
+ this.#agentId = agentId;
49
+ this.#request = request;
50
+ this.#delivery = delivery;
51
+ }
52
+ /**
53
+ * Consume a delivery reservation and hold the resulting fenced attachment.
54
+ * @param options - Endpoint, agent identity, the attachment credential, the request seam, and an
55
+ * optional resumable conversation key.
56
+ * @returns - Connected delivery client bound to the identity the broker resolved.
57
+ */
58
+ static async attach({ brokerUrl, agentId, reservation, request, sessionKey, }) {
59
+ const attach = compatibilityToolRequest("attach", {});
60
+ const response = await request(brokerUrl, {
61
+ ...attach,
62
+ headers: {
63
+ ...attach.headers,
64
+ [ATTACH_HEADER]: reservation,
65
+ ...(sessionKey ? { "x-pager-session-key": sessionKey } : {}),
66
+ },
67
+ });
68
+ await compatibilityPayload(response, "Broker delivery attachment");
69
+ const session = deliverySession(response);
70
+ return new BrokerDeliveryClient(brokerUrl, session.agentId ?? agentId, request, session);
71
+ }
72
+ /**
73
+ * Return the identity this attachment resolved to.
74
+ * @returns - Agent id the broker bound to this attachment.
75
+ */
76
+ agentId() {
77
+ return this.#agentId;
78
+ }
79
+ /**
80
+ * Present an inbox address on every later request, so the broker pushes this identity's wakes to it.
81
+ *
82
+ * Sent with each request rather than once, so a broker that restarted, or that dropped the address
83
+ * after a failed delivery, learns it again on the next heartbeat.
84
+ * @param address - Address text the session's inbox produced.
85
+ */
86
+ presentDeliveryAddress(address) {
87
+ this.#deliveryAddress = address;
88
+ }
89
+ /** Renew the delivery attachment lease, presenting the inbox address when one is held. */
90
+ async heartbeat() {
91
+ await this.call("whoami", {});
92
+ }
93
+ /** Tell the broker the harness is shutting down, ending every attachment of this identity, this one included. */
94
+ async detach() {
95
+ await this.call("detach", {});
96
+ }
97
+ /**
98
+ * Invoke one delivery-attached broker tool.
99
+ * @param tool - Delivery-channel tool name.
100
+ * @param args - Validated tool arguments.
101
+ * @returns - Decoded broker result payload.
102
+ */
103
+ async call(tool, args) {
104
+ const call = compatibilityToolRequest(tool, args);
105
+ const response = await this.#request(this.#brokerUrl, {
106
+ ...call,
107
+ headers: {
108
+ ...call.headers,
109
+ "mcp-session-id": this.#delivery.sessionId,
110
+ "x-pager-epoch": this.#delivery.epoch,
111
+ // The attach reservation is single-use, so this token is the only way back after the broker
112
+ // fences the session.
113
+ ...(this.#delivery.resumeToken ? { "x-pager-resume-token": this.#delivery.resumeToken } : {}),
114
+ ...(this.#deliveryAddress ? { [DELIVERY_ADDRESS_HEADER]: this.#deliveryAddress } : {}),
115
+ },
116
+ });
117
+ this.#delivery = adoptRotation(this.#delivery, response);
118
+ return compatibilityPayload(response, `Broker ${tool}`);
119
+ }
120
+ }
121
+ /**
122
+ * Build one compatibility-path tool call, without session headers.
123
+ * @param tool - Tool the broker dispatches on; connector-only tools are accepted on this path.
124
+ * @param args - Tool arguments as the broker validates them.
125
+ * @returns - Request the caller sends as is for a pre-attach tool, or with its session headers merged
126
+ * into `headers` for an authenticated one.
127
+ */
128
+ export function compatibilityToolRequest(tool, args) {
129
+ return {
130
+ method: "POST",
131
+ headers: { "content-type": "application/json" },
132
+ body: JSON.stringify({ version: PROTOCOL_VERSION, tool, arguments: args }),
133
+ };
134
+ }
135
+ /**
136
+ * Take up the session generation a response announces, so a resumed attachment stays usable.
137
+ *
138
+ * A broker that rotates the session mid-request answers with the replacement credentials; keeping the
139
+ * spent ones would resume once and then fence every later call.
140
+ * @param current - Session the request was made with.
141
+ * @param response - Broker response, carrying replacement headers only when the session rotated.
142
+ * @returns - The announced session, or the current one unchanged.
143
+ */
144
+ export function adoptRotation(current, response) {
145
+ const sessionId = response.headers.get("mcp-session-id");
146
+ const epoch = response.headers.get("x-pager-epoch");
147
+ const resumeToken = response.headers.get("x-pager-resume-token");
148
+ if (!sessionId || !epoch)
149
+ return current;
150
+ return {
151
+ ...current,
152
+ sessionId,
153
+ epoch,
154
+ ...(resumeToken ? { resumeToken } : {}),
155
+ };
156
+ }
157
+ /**
158
+ * Read the fenced session headers a delivery attachment response must carry.
159
+ * @param response - Broker response opening a delivery attachment.
160
+ * @returns - Session identity and epoch, plus the resume token and resolved agent id when the response carried them.
161
+ */
162
+ function deliverySession(response) {
163
+ const sessionId = response.headers.get("mcp-session-id");
164
+ const epoch = response.headers.get("x-pager-epoch");
165
+ const resumeToken = response.headers.get("x-pager-resume-token");
166
+ const agentId = response.headers.get("x-pager-agent-id");
167
+ if (!sessionId || !epoch)
168
+ throw new Error("Broker attachment omitted session headers");
169
+ return {
170
+ sessionId,
171
+ epoch,
172
+ ...(resumeToken ? { resumeToken } : {}),
173
+ ...(isAgentId(agentId) ? { agentId } : {}),
174
+ };
175
+ }
176
+ /**
177
+ * Decode a broker compatibility envelope, raising the broker's own message on failure.
178
+ * @param response - Broker response carrying an `{ ok, result }` envelope.
179
+ * @param operation - Operation named in the raised diagnostic.
180
+ * @returns - Envelope result payload.
181
+ */
182
+ export async function compatibilityPayload(response, operation) {
183
+ const payload = await parseEnvelopeBody(response, operation);
184
+ if (!response.ok || payload?.ok !== true || !("result" in payload)) {
185
+ throw envelopeFailure(response, payload, operation);
186
+ }
187
+ return payload.result;
188
+ }
189
+ /**
190
+ * Decode a JSON-RPC envelope, raising the broker's own message on failure.
191
+ * @param response - Broker response carrying a JSON-RPC result.
192
+ * @param operation - Operation named in the raised diagnostic.
193
+ * @returns - JSON-RPC result payload.
194
+ */
195
+ export async function rpcEnvelope(response, operation) {
196
+ const payload = await parseEnvelopeBody(response, operation);
197
+ if (!response.ok || !payload || payload.jsonrpc !== "2.0" || !("result" in payload)) {
198
+ throw envelopeFailure(response, payload, operation);
199
+ }
200
+ return { result: payload.result };
201
+ }
202
+ /**
203
+ * Decode a response body as JSON, keeping the response's status in the raised diagnostic when a
204
+ * failing response's body isn't JSON at all (an ok response with an unparseable body still rejects
205
+ * with the raw parse error, since there is no failing status to fold it into).
206
+ * @param response - Broker response whose body is decoded.
207
+ * @param operation - Operation named in the raised diagnostic.
208
+ * @returns - Decoded body as a plain record, or undefined when it decoded to something else.
209
+ */
210
+ async function parseEnvelopeBody(response, operation) {
211
+ try {
212
+ return asRecord(await response.json());
213
+ }
214
+ catch (error) {
215
+ if (response.ok)
216
+ throw error;
217
+ throw new Error(`${operation} failed (${String(response.status)})`, { cause: error });
218
+ }
219
+ }
220
+ /**
221
+ * Build the error for a broker response that failed to carry a usable envelope.
222
+ * @param response - Broker response that failed to decode into a successful envelope.
223
+ * @param payload - Decoded response body, when it was valid JSON.
224
+ * @param operation - Operation named in the raised diagnostic.
225
+ * @returns - Error describing the failure.
226
+ */
227
+ function envelopeFailure(response, payload, operation) {
228
+ const error = asRecord(payload?.error);
229
+ const message = typeof error?.message === "string" ? error.message : `${operation} failed (${String(response.status)})`;
230
+ return new Error(message);
231
+ }
232
+ /**
233
+ * Accept an object carrying only permitted fields, rejecting anything additional.
234
+ * @param value - Untrusted decoded payload.
235
+ * @param label - Subject named in the raised diagnostic.
236
+ * @param permitted - Field names the payload may carry.
237
+ * @returns - The payload as a plain record.
238
+ */
239
+ export function exactRecord(value, label, permitted) {
240
+ const output = asRecord(value);
241
+ if (!output)
242
+ throw new Error(`${label} must be an object`);
243
+ guardExactFields({
244
+ record: output,
245
+ required: [],
246
+ optional: permitted,
247
+ path: label,
248
+ fail: (path, reason) => {
249
+ throw new Error(`${path}: ${reason}`);
250
+ },
251
+ });
252
+ return output;
253
+ }
254
+ /**
255
+ * Narrow an untrusted value to a plain record.
256
+ * @param value - Untrusted decoded payload.
257
+ * @returns - The value as a plain record, or undefined when it is not one.
258
+ */
259
+ export function asRecord(value) {
260
+ return isPlainObject(value) ? { ...value } : undefined;
261
+ }