@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.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/src/broker/address.d.ts +49 -0
- package/dist/src/broker/address.js +110 -0
- package/dist/src/broker/broker.d.ts +665 -0
- package/dist/src/broker/broker.js +2270 -0
- package/dist/src/broker/database.d.ts +81 -0
- package/dist/src/broker/database.js +69 -0
- package/dist/src/broker/operator-projection.d.ts +291 -0
- package/dist/src/broker/operator-projection.js +722 -0
- package/dist/src/broker/protocol.d.ts +237 -0
- package/dist/src/broker/protocol.js +311 -0
- package/dist/src/broker/schema.d.ts +94 -0
- package/dist/src/broker/schema.js +454 -0
- package/dist/src/broker/snapshot.d.ts +171 -0
- package/dist/src/broker/snapshot.js +78 -0
- package/dist/src/configuration-defaults.d.ts +64 -0
- package/dist/src/configuration-defaults.js +48 -0
- package/dist/src/configuration.d.ts +76 -0
- package/dist/src/configuration.js +156 -0
- package/dist/src/implementations/claude/environment.d.ts +8 -0
- package/dist/src/implementations/claude/environment.js +9 -0
- package/dist/src/implementations/claude/gateway-cli.d.ts +2 -0
- package/dist/src/implementations/claude/gateway-cli.js +136 -0
- package/dist/src/implementations/claude/gateway-identity.d.ts +134 -0
- package/dist/src/implementations/claude/gateway-identity.js +103 -0
- package/dist/src/implementations/claude/gateway.d.ts +148 -0
- package/dist/src/implementations/claude/gateway.js +236 -0
- package/dist/src/implementations/claude/instructions.d.ts +33 -0
- package/dist/src/implementations/claude/instructions.js +64 -0
- package/dist/src/implementations/claude/manifest.d.ts +33 -0
- package/dist/src/implementations/claude/manifest.js +108 -0
- package/dist/src/implementations/claude/plugin-directory.d.ts +80 -0
- package/dist/src/implementations/claude/plugin-directory.js +137 -0
- package/dist/src/implementations/claude/session-continuation.d.ts +25 -0
- package/dist/src/implementations/claude/session-continuation.js +125 -0
- package/dist/src/implementations/claude/session-identity.d.ts +25 -0
- package/dist/src/implementations/claude/session-identity.js +31 -0
- package/dist/src/implementations/claude/session-start-cli.d.ts +2 -0
- package/dist/src/implementations/claude/session-start-cli.js +16 -0
- package/dist/src/implementations/claude/socket-delivery.d.ts +52 -0
- package/dist/src/implementations/claude/socket-delivery.js +159 -0
- package/dist/src/implementations/model.d.ts +162 -0
- package/dist/src/implementations/model.js +1 -0
- package/dist/src/implementations/opencode/adapter.d.ts +71 -0
- package/dist/src/implementations/opencode/adapter.js +94 -0
- package/dist/src/implementations/opencode/environment.d.ts +8 -0
- package/dist/src/implementations/opencode/environment.js +9 -0
- package/dist/src/implementations/opencode/manifest.d.ts +30 -0
- package/dist/src/implementations/opencode/manifest.js +93 -0
- package/dist/src/implementations/opencode/plugin-api.d.ts +120 -0
- package/dist/src/implementations/opencode/plugin-api.js +1 -0
- package/dist/src/implementations/opencode/plugin.d.ts +38 -0
- package/dist/src/implementations/opencode/plugin.js +303 -0
- package/dist/src/implementations/opencode/self-close-tool.d.ts +9 -0
- package/dist/src/implementations/opencode/self-close-tool.js +31 -0
- package/dist/src/implementations/orca/terminal.d.ts +48 -0
- package/dist/src/implementations/orca/terminal.js +154 -0
- package/dist/src/implementations/pi/broker-client.d.ts +56 -0
- package/dist/src/implementations/pi/broker-client.js +156 -0
- package/dist/src/implementations/pi/environment.d.ts +8 -0
- package/dist/src/implementations/pi/environment.js +9 -0
- package/dist/src/implementations/pi/extension.d.ts +57 -0
- package/dist/src/implementations/pi/extension.js +253 -0
- package/dist/src/implementations/pi/host-slots.d.ts +18 -0
- package/dist/src/implementations/pi/host-slots.js +27 -0
- package/dist/src/implementations/pi/manifest.d.ts +11 -0
- package/dist/src/implementations/pi/manifest.js +61 -0
- package/dist/src/implementations/pi/self-close-tool.d.ts +7 -0
- package/dist/src/implementations/pi/self-close-tool.js +27 -0
- package/dist/src/implementations/registry.d.ts +47 -0
- package/dist/src/implementations/registry.js +157 -0
- package/dist/src/implementations/session-key.d.ts +13 -0
- package/dist/src/implementations/session-key.js +19 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.js +26 -0
- package/dist/src/launch/broker-lock.d.ts +257 -0
- package/dist/src/launch/broker-lock.js +644 -0
- package/dist/src/launch/cli.d.ts +37 -0
- package/dist/src/launch/cli.js +317 -0
- package/dist/src/launch/environment.d.ts +45 -0
- package/dist/src/launch/environment.js +48 -0
- package/dist/src/launch/launch.d.ts +235 -0
- package/dist/src/launch/launch.js +223 -0
- package/dist/src/launch/on-demand.d.ts +202 -0
- package/dist/src/launch/on-demand.js +435 -0
- package/dist/src/launch/runtime-composition.d.ts +88 -0
- package/dist/src/launch/runtime-composition.js +253 -0
- package/dist/src/launch/runtime.d.ts +79 -0
- package/dist/src/launch/runtime.js +196 -0
- package/dist/src/launch/service-system.d.ts +11 -0
- package/dist/src/launch/service-system.js +39 -0
- package/dist/src/launch/service.d.ts +173 -0
- package/dist/src/launch/service.js +492 -0
- package/dist/src/launch/skill.d.ts +57 -0
- package/dist/src/launch/skill.js +81 -0
- package/dist/src/launch/terminal.d.ts +26 -0
- package/dist/src/launch/terminal.js +43 -0
- package/dist/src/launch/workspace-branch.d.ts +10 -0
- package/dist/src/launch/workspace-branch.js +29 -0
- package/dist/src/messaging/delivery-client.d.ts +144 -0
- package/dist/src/messaging/delivery-client.js +261 -0
- package/dist/src/messaging/delivery.d.ts +75 -0
- package/dist/src/messaging/delivery.js +22 -0
- package/dist/src/messaging/inbox-socket.d.ts +98 -0
- package/dist/src/messaging/inbox-socket.js +278 -0
- package/dist/src/messaging/mcp.d.ts +53 -0
- package/dist/src/messaging/mcp.js +501 -0
- package/dist/src/messaging/notifications.d.ts +231 -0
- package/dist/src/messaging/notifications.js +489 -0
- package/dist/src/messaging/push-delivery.d.ts +77 -0
- package/dist/src/messaging/push-delivery.js +187 -0
- package/dist/src/messaging/tools.d.ts +11 -0
- package/dist/src/messaging/tools.js +102 -0
- package/dist/src/messaging/wake.d.ts +23 -0
- package/dist/src/messaging/wake.js +39 -0
- package/dist/src/operator/agent-projection.d.ts +102 -0
- package/dist/src/operator/agent-projection.js +249 -0
- package/dist/src/operator/commands.d.ts +50 -0
- package/dist/src/operator/commands.js +199 -0
- package/dist/src/operator/diagnostics.d.ts +21 -0
- package/dist/src/operator/diagnostics.js +22 -0
- package/dist/src/operator/protocol.d.ts +511 -0
- package/dist/src/operator/protocol.js +49 -0
- package/dist/src/operator/repository-system.d.ts +14 -0
- package/dist/src/operator/repository-system.js +150 -0
- package/dist/src/operator/repository.d.ts +79 -0
- package/dist/src/operator/repository.js +72 -0
- package/dist/src/operator/traffic-projection.d.ts +19 -0
- package/dist/src/operator/traffic-projection.js +229 -0
- package/dist/src/operator/transport.d.ts +87 -0
- package/dist/src/operator/transport.js +549 -0
- package/dist/src/operator/update-store.d.ts +53 -0
- package/dist/src/operator/update-store.js +162 -0
- package/dist/src/operator/updates.d.ts +94 -0
- package/dist/src/operator/updates.js +280 -0
- package/dist/src/operator/validation.d.ts +114 -0
- package/dist/src/operator/validation.js +916 -0
- package/dist/src/shared/guards.d.ts +59 -0
- package/dist/src/shared/guards.js +119 -0
- package/dist/src/shared/http.d.ts +30 -0
- package/dist/src/shared/http.js +50 -0
- package/dist/src/shared/row.d.ts +2 -0
- package/dist/src/shared/row.js +1 -0
- package/dist/src/shared/shell.d.ts +6 -0
- package/dist/src/shared/shell.js +8 -0
- package/dist/src/tui/actions.d.ts +166 -0
- package/dist/src/tui/actions.js +253 -0
- package/dist/src/tui/agent-tree.d.ts +125 -0
- package/dist/src/tui/agent-tree.js +663 -0
- package/dist/src/tui/app.d.ts +38 -0
- package/dist/src/tui/app.js +378 -0
- package/dist/src/tui/chrome.d.ts +86 -0
- package/dist/src/tui/chrome.js +85 -0
- package/dist/src/tui/client.d.ts +41 -0
- package/dist/src/tui/client.js +279 -0
- package/dist/src/tui/composition.d.ts +41 -0
- package/dist/src/tui/composition.js +146 -0
- package/dist/src/tui/message-views.d.ts +94 -0
- package/dist/src/tui/message-views.js +515 -0
- package/dist/src/tui/model.d.ts +414 -0
- package/dist/src/tui/model.js +758 -0
- package/dist/src/tui/presentation.d.ts +350 -0
- package/dist/src/tui/presentation.js +541 -0
- package/dist/src/tui/production.d.ts +79 -0
- package/dist/src/tui/production.js +253 -0
- package/dist/src/tui/run-terminal.d.ts +32 -0
- package/dist/src/tui/run-terminal.js +54 -0
- package/dist/src/tui/runtime.d.ts +113 -0
- package/dist/src/tui/runtime.js +189 -0
- package/dist/src/tui/theme.d.ts +49 -0
- package/dist/src/tui/theme.js +55 -0
- package/dist/src/tui/transport.d.ts +31 -0
- package/dist/src/tui/transport.js +255 -0
- package/dist/src/version.d.ts +11 -0
- package/dist/src/version.js +52 -0
- package/package.json +142 -0
- package/skills/pager/SKILL.md +112 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { ProtocolError } from "../../broker/protocol.js";
|
|
7
|
+
import { isPlainObject } from "../../shared/guards.js";
|
|
8
|
+
import { shellQuote } from "../../shared/shell.js";
|
|
9
|
+
import { spawnForegroundProcess, spawnInheritedSession } from "../../launch/terminal.js";
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
/** Launch terminal plans through the Orca terminal client. */
|
|
12
|
+
export class OrcaTerminalProvider {
|
|
13
|
+
/** Identifies this terminal provider. */
|
|
14
|
+
name = "orca";
|
|
15
|
+
/** Prefers the externally hosted terminal over inherited local stdio. */
|
|
16
|
+
interactivePriority = 100;
|
|
17
|
+
#operations = [];
|
|
18
|
+
#command;
|
|
19
|
+
#currentTerminalHandle;
|
|
20
|
+
#handoffRoot;
|
|
21
|
+
#spawnProcess;
|
|
22
|
+
/**
|
|
23
|
+
* Construct the provider around an explicit Orca executable.
|
|
24
|
+
* @param options - `command` is the Orca executable. A `currentTerminalHandle` makes launches run as a
|
|
25
|
+
* foreground child of this process through `spawnProcess` instead of creating a terminal.
|
|
26
|
+
* `handoffRoot` holds the owner-only environment files and defaults to a fresh temporary directory.
|
|
27
|
+
*/
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.#command = options.command;
|
|
30
|
+
this.#currentTerminalHandle = options.currentTerminalHandle;
|
|
31
|
+
this.#handoffRoot = options.handoffRoot;
|
|
32
|
+
this.#spawnProcess = options.spawnProcess;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Report every terminal operation this provider has recorded.
|
|
36
|
+
* @returns - Operations in the order they occurred.
|
|
37
|
+
*/
|
|
38
|
+
get operations() {
|
|
39
|
+
return this.#operations;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Report whether the configured terminal executable can answer locally.
|
|
43
|
+
* @returns - Whether the provider executable is present and runnable.
|
|
44
|
+
*/
|
|
45
|
+
async available() {
|
|
46
|
+
try {
|
|
47
|
+
await execFileAsync(this.#command, ["status", "--json"]);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return (typeof error === "object" &&
|
|
52
|
+
error !== null &&
|
|
53
|
+
"stdout" in error &&
|
|
54
|
+
typeof error.stdout === "string" &&
|
|
55
|
+
error.stdout.length > 0);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Launch one implementation-owned plan in an Orca terminal.
|
|
60
|
+
* @param request - Neutral terminal launch request.
|
|
61
|
+
* @returns - Started terminal session and explicit controls.
|
|
62
|
+
*/
|
|
63
|
+
async launch(request) {
|
|
64
|
+
if (this.#currentTerminalHandle)
|
|
65
|
+
return this.#launchInCurrentTerminal(request, this.#currentTerminalHandle);
|
|
66
|
+
const handoffDirectory = this.#handoffRoot ?? mkdtempSync(join(tmpdir(), "pager-terminal-"));
|
|
67
|
+
mkdirSync(handoffDirectory, { recursive: true });
|
|
68
|
+
const environmentPath = join(handoffDirectory, `${safeFileName(request.agentId)}.env`);
|
|
69
|
+
// Sensitive environment values are handed off through this mode-0600 file sourced inside the
|
|
70
|
+
// pane, never through the terminal creation command or diagnostics.
|
|
71
|
+
writeFileSync(environmentPath, sensitiveEnvironmentFile(request.plan.sensitiveEnvironment), { mode: 0o600 });
|
|
72
|
+
const command = [
|
|
73
|
+
...Object.entries(request.plan.environment).map(([key, value]) => `${safeEnvironmentKey(key)}=${shellQuote(value)}`),
|
|
74
|
+
shellQuote(request.plan.command),
|
|
75
|
+
...request.plan.args.map((argument) => shellQuote(argument)),
|
|
76
|
+
].join(" ");
|
|
77
|
+
const shellCommand = [
|
|
78
|
+
`cd ${shellQuote(request.plan.workspacePath)}`,
|
|
79
|
+
`. ${shellQuote(environmentPath)}`,
|
|
80
|
+
command,
|
|
81
|
+
].join(" && ");
|
|
82
|
+
const { stdout } = await execFileAsync(this.#command, [
|
|
83
|
+
"terminal",
|
|
84
|
+
"create",
|
|
85
|
+
"--worktree",
|
|
86
|
+
"active",
|
|
87
|
+
"--title",
|
|
88
|
+
request.plan.title,
|
|
89
|
+
"--command",
|
|
90
|
+
shellCommand,
|
|
91
|
+
"--json",
|
|
92
|
+
]);
|
|
93
|
+
const parsed = JSON.parse(stdout);
|
|
94
|
+
const result = isPlainObject(parsed) ? parsed.result : undefined;
|
|
95
|
+
const terminal = isPlainObject(result) ? result.terminal : undefined;
|
|
96
|
+
const handle = isPlainObject(terminal) ? terminal.handle : undefined;
|
|
97
|
+
if (typeof handle !== "string" || handle.length === 0) {
|
|
98
|
+
throw new ProtocolError("validation_failed", "terminal creation did not return a handle");
|
|
99
|
+
}
|
|
100
|
+
this.#operations.push({
|
|
101
|
+
type: "terminal_create",
|
|
102
|
+
detail: { agentId: request.agentId, handle, command: request.plan.command },
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
provider: this.name,
|
|
106
|
+
taskCompletion: {
|
|
107
|
+
supported: true,
|
|
108
|
+
run: (taskId) => {
|
|
109
|
+
this.#operations.push({ type: "task_complete", detail: { agentId: request.agentId, taskId } });
|
|
110
|
+
return Promise.resolve();
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
#launchInCurrentTerminal(request, handle) {
|
|
116
|
+
const { child, exit } = spawnInheritedSession(this.#spawnProcess, request);
|
|
117
|
+
this.#operations.push({
|
|
118
|
+
type: "terminal_reuse",
|
|
119
|
+
detail: { agentId: request.agentId, handle, command: request.plan.command },
|
|
120
|
+
});
|
|
121
|
+
return Promise.resolve({
|
|
122
|
+
provider: this.name,
|
|
123
|
+
...(child.pid === undefined ? {} : { pid: child.pid }),
|
|
124
|
+
taskCompletion: { supported: false },
|
|
125
|
+
exit,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Construct the terminal provider from process configuration owned by this implementation.
|
|
131
|
+
* @param environment - Process environment carrying the optional Orca executable override.
|
|
132
|
+
* @returns - Configured Orca terminal provider.
|
|
133
|
+
*/
|
|
134
|
+
export function createTerminalProvider(environment) {
|
|
135
|
+
return new OrcaTerminalProvider({
|
|
136
|
+
command: environment.ORCA_CLI_COMMAND ?? "orca",
|
|
137
|
+
...(environment.ORCA_TERMINAL_HANDLE ? { currentTerminalHandle: environment.ORCA_TERMINAL_HANDLE } : {}),
|
|
138
|
+
spawnProcess: spawnForegroundProcess,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function sensitiveEnvironmentFile(environment) {
|
|
142
|
+
return `${Object.entries(environment)
|
|
143
|
+
.map(([key, value]) => `export ${safeEnvironmentKey(key)}=${shellQuote(value)}`)
|
|
144
|
+
.join("\n")}\n`;
|
|
145
|
+
}
|
|
146
|
+
function safeEnvironmentKey(value) {
|
|
147
|
+
if (!/^[A-Z_][A-Z0-9_]*$/u.test(value)) {
|
|
148
|
+
throw new ProtocolError("validation_failed", "implementation supplied an invalid environment key");
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
function safeFileName(value) {
|
|
153
|
+
return value.replace(/[^A-Za-z0-9_.-]/gu, "_");
|
|
154
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type AgentId } from "../../broker/protocol.js";
|
|
2
|
+
import { type BrokerIdentity, type BrokerRequest } from "../../messaging/delivery-client.js";
|
|
3
|
+
/** Options for {@link registerPiAgent}. */
|
|
4
|
+
export type RegisterPiAgentOptions = {
|
|
5
|
+
/** HTTP operation used to reach the loopback broker. */
|
|
6
|
+
request: BrokerRequest;
|
|
7
|
+
/** Shared broker MCP endpoint. */
|
|
8
|
+
brokerUrl: string;
|
|
9
|
+
/** Absolute Pi working directory. */
|
|
10
|
+
workspacePath: string;
|
|
11
|
+
/** Stable Pi session identity used as the initial alias. */
|
|
12
|
+
sessionName: string;
|
|
13
|
+
/** Resumable key that rejoins this conversation's identity after a restart. */
|
|
14
|
+
sessionKey?: string | undefined;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Register a directly started Pi session through the unauthenticated broker boundary.
|
|
18
|
+
* @param options - Broker request seam, endpoint, and the Pi session's registration identity.
|
|
19
|
+
* @returns - Registered identity with separate messaging and delivery attachments.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerPiAgent({ request, brokerUrl, workspacePath, sessionName, sessionKey, }: RegisterPiAgentOptions): Promise<BrokerIdentity>;
|
|
22
|
+
/** Authenticated messaging and delivery operations used by the Pi extension. */
|
|
23
|
+
export declare class PiBrokerClient {
|
|
24
|
+
#private;
|
|
25
|
+
private constructor();
|
|
26
|
+
/**
|
|
27
|
+
* Consume a Pi identity's messaging and delivery reservations.
|
|
28
|
+
* @param brokerUrl - Shared broker MCP endpoint.
|
|
29
|
+
* @param identity - Reserved or directly registered broker identity.
|
|
30
|
+
* @param request - HTTP operation used to reach the loopback broker.
|
|
31
|
+
* @param sessionKey - Resumable conversation key, which the broker may resolve to another identity.
|
|
32
|
+
* @returns - Connected client whose identity is the one the broker resolved, not the one requested.
|
|
33
|
+
*/
|
|
34
|
+
static connect(brokerUrl: string, identity: BrokerIdentity, request: BrokerRequest, sessionKey?: string): Promise<PiBrokerClient>;
|
|
35
|
+
/**
|
|
36
|
+
* Invoke one model-callable Pager MCP tool.
|
|
37
|
+
* @param name - Public MCP tool name.
|
|
38
|
+
* @param args - Validated Pi tool arguments.
|
|
39
|
+
* @returns - Decoded broker tool result.
|
|
40
|
+
*/
|
|
41
|
+
callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Present an inbox address on every later delivery-channel request, including each heartbeat.
|
|
44
|
+
* @param address - Address text the session's inbox produced.
|
|
45
|
+
*/
|
|
46
|
+
presentDeliveryAddress(address: string): void;
|
|
47
|
+
/** Renew both independently fenced broker attachment leases. */
|
|
48
|
+
heartbeat(): Promise<void>;
|
|
49
|
+
/** Tell the broker Pi is shutting down, ending both attachments so the session name is free at once. */
|
|
50
|
+
detach(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Return the authenticated broker identity.
|
|
53
|
+
* @returns - Identity the broker resolved, which differs from the requested one after adoption.
|
|
54
|
+
*/
|
|
55
|
+
agentId(): AgentId;
|
|
56
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { ATTACH_HEADER, isAgentId } from "../../broker/protocol.js";
|
|
2
|
+
import { adoptRotation, asRecord, BrokerDeliveryClient, exactRecord, registerAgent, rpcEnvelope, } from "../../messaging/delivery-client.js";
|
|
3
|
+
import { PAGER_VERSION } from "../../version.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register a directly started Pi session through the unauthenticated broker boundary.
|
|
6
|
+
* @param options - Broker request seam, endpoint, and the Pi session's registration identity.
|
|
7
|
+
* @returns - Registered identity with separate messaging and delivery attachments.
|
|
8
|
+
*/
|
|
9
|
+
export async function registerPiAgent({ request, brokerUrl, workspacePath, sessionName, sessionKey, }) {
|
|
10
|
+
return registerAgent(request, brokerUrl, {
|
|
11
|
+
workspacePath,
|
|
12
|
+
implementationId: "pi",
|
|
13
|
+
sessionName,
|
|
14
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Authenticated messaging and delivery operations used by the Pi extension. */
|
|
18
|
+
export class PiBrokerClient {
|
|
19
|
+
#brokerUrl;
|
|
20
|
+
#agentId;
|
|
21
|
+
#request;
|
|
22
|
+
#messaging;
|
|
23
|
+
#delivery;
|
|
24
|
+
constructor(brokerUrl, agentId, request, messaging, delivery) {
|
|
25
|
+
this.#brokerUrl = brokerUrl;
|
|
26
|
+
this.#agentId = agentId;
|
|
27
|
+
this.#request = request;
|
|
28
|
+
this.#messaging = messaging;
|
|
29
|
+
this.#delivery = delivery;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Consume a Pi identity's messaging and delivery reservations.
|
|
33
|
+
* @param brokerUrl - Shared broker MCP endpoint.
|
|
34
|
+
* @param identity - Reserved or directly registered broker identity.
|
|
35
|
+
* @param request - HTTP operation used to reach the loopback broker.
|
|
36
|
+
* @param sessionKey - Resumable conversation key, which the broker may resolve to another identity.
|
|
37
|
+
* @returns - Connected client whose identity is the one the broker resolved, not the one requested.
|
|
38
|
+
*/
|
|
39
|
+
static async connect(brokerUrl, identity, request, sessionKey) {
|
|
40
|
+
const messaging = await initializeMessaging(request, brokerUrl, identity.messagingReservation, sessionKey);
|
|
41
|
+
const delivery = await BrokerDeliveryClient.attach({
|
|
42
|
+
brokerUrl,
|
|
43
|
+
agentId: identity.agentId,
|
|
44
|
+
reservation: identity.deliveryReservation,
|
|
45
|
+
request,
|
|
46
|
+
sessionKey,
|
|
47
|
+
});
|
|
48
|
+
return new PiBrokerClient(brokerUrl, messaging.agentId ?? delivery.agentId(), request, messaging, delivery);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Invoke one model-callable Pager MCP tool.
|
|
52
|
+
* @param name - Public MCP tool name.
|
|
53
|
+
* @param args - Validated Pi tool arguments.
|
|
54
|
+
* @returns - Decoded broker tool result.
|
|
55
|
+
*/
|
|
56
|
+
async callTool(name, args) {
|
|
57
|
+
const response = await this.#request(this.#brokerUrl, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: messagingHeaders(this.#messaging),
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
jsonrpc: "2.0",
|
|
62
|
+
id: crypto.randomUUID(),
|
|
63
|
+
method: "tools/call",
|
|
64
|
+
params: { name, arguments: args },
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
this.#messaging = adoptRotation(this.#messaging, response);
|
|
68
|
+
const envelope = await rpcEnvelope(response, "Broker MCP tool call");
|
|
69
|
+
const result = exactRecord(envelope.result, "Broker MCP tool result", ["content", "isError"]);
|
|
70
|
+
if (!Array.isArray(result.content) || result.content.length !== 1) {
|
|
71
|
+
throw new Error("Broker MCP tool result has invalid content");
|
|
72
|
+
}
|
|
73
|
+
const content = exactRecord(result.content[0], "Broker MCP tool content", ["type", "text"]);
|
|
74
|
+
if (content.type !== "text" || typeof content.text !== "string") {
|
|
75
|
+
throw new Error("Broker MCP tool result has invalid text content");
|
|
76
|
+
}
|
|
77
|
+
const decoded = JSON.parse(content.text);
|
|
78
|
+
if (result.isError === true) {
|
|
79
|
+
const failure = asRecord(decoded);
|
|
80
|
+
throw new Error(typeof failure?.message === "string" ? failure.message : "Broker MCP tool failed");
|
|
81
|
+
}
|
|
82
|
+
return decoded;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Present an inbox address on every later delivery-channel request, including each heartbeat.
|
|
86
|
+
* @param address - Address text the session's inbox produced.
|
|
87
|
+
*/
|
|
88
|
+
presentDeliveryAddress(address) {
|
|
89
|
+
this.#delivery.presentDeliveryAddress(address);
|
|
90
|
+
}
|
|
91
|
+
/** Renew both independently fenced broker attachment leases. */
|
|
92
|
+
async heartbeat() {
|
|
93
|
+
await Promise.all([this.callTool("whoami", {}), this.#delivery.heartbeat()]);
|
|
94
|
+
}
|
|
95
|
+
/** Tell the broker Pi is shutting down, ending both attachments so the session name is free at once. */
|
|
96
|
+
async detach() {
|
|
97
|
+
await this.#delivery.detach();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Return the authenticated broker identity.
|
|
101
|
+
* @returns - Identity the broker resolved, which differs from the requested one after adoption.
|
|
102
|
+
*/
|
|
103
|
+
agentId() {
|
|
104
|
+
return this.#agentId;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function initializeMessaging(request, brokerUrl, reservation, sessionKey) {
|
|
108
|
+
const response = await request(brokerUrl, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: attachHeaders(reservation, sessionKey),
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
jsonrpc: "2.0",
|
|
113
|
+
id: crypto.randomUUID(),
|
|
114
|
+
method: "initialize",
|
|
115
|
+
params: {
|
|
116
|
+
protocolVersion: "2025-03-26",
|
|
117
|
+
capabilities: {},
|
|
118
|
+
clientInfo: { name: "pager-pi", version: PAGER_VERSION },
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
await rpcEnvelope(response, "Broker MCP initialization");
|
|
123
|
+
const session = messagingSession(response);
|
|
124
|
+
const initialized = await request(brokerUrl, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: messagingHeaders(session),
|
|
127
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
|
|
128
|
+
});
|
|
129
|
+
if (!initialized.ok)
|
|
130
|
+
throw new Error(`Broker MCP initialization acknowledgement failed (${String(initialized.status)})`);
|
|
131
|
+
return adoptRotation(session, initialized);
|
|
132
|
+
}
|
|
133
|
+
function messagingSession(response) {
|
|
134
|
+
const sessionId = response.headers.get("mcp-session-id");
|
|
135
|
+
const epoch = response.headers.get("x-pager-epoch");
|
|
136
|
+
const resumeToken = response.headers.get("x-pager-resume-token");
|
|
137
|
+
const agentId = response.headers.get("x-pager-agent-id");
|
|
138
|
+
if (!sessionId || !epoch || !resumeToken)
|
|
139
|
+
throw new Error("Broker attachment omitted session headers");
|
|
140
|
+
return { sessionId, epoch, resumeToken, ...(isAgentId(agentId) ? { agentId } : {}) };
|
|
141
|
+
}
|
|
142
|
+
function attachHeaders(reservation, sessionKey) {
|
|
143
|
+
return {
|
|
144
|
+
"content-type": "application/json",
|
|
145
|
+
[ATTACH_HEADER]: reservation,
|
|
146
|
+
...(sessionKey ? { "x-pager-session-key": sessionKey } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function messagingHeaders(session) {
|
|
150
|
+
return {
|
|
151
|
+
"content-type": "application/json",
|
|
152
|
+
"mcp-session-id": session.sessionId,
|
|
153
|
+
"x-pager-epoch": session.epoch,
|
|
154
|
+
...(session.resumeToken ? { "x-pager-resume-token": session.resumeToken } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Names the broker endpoint supplied to the Pi extension. */
|
|
2
|
+
export declare const PI_BROKER_URL_ENVIRONMENT = "PAGER_MCP_URL";
|
|
3
|
+
/** Names the reserved broker identity supplied to the Pi extension. */
|
|
4
|
+
export declare const PI_AGENT_ID_ENVIRONMENT = "PAGER_AGENT_ID";
|
|
5
|
+
/** Names the single-use messaging attachment supplied to the Pi extension. */
|
|
6
|
+
export declare const PI_MESSAGING_RESERVATION_ENVIRONMENT = "PAGER_PI_MESSAGING_ATTACH_RESERVATION";
|
|
7
|
+
/** Names the single-use delivery attachment supplied to the Pi extension. */
|
|
8
|
+
export declare const PI_DELIVERY_RESERVATION_ENVIRONMENT = "PAGER_PI_DELIVERY_ATTACH_RESERVATION";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PAGER_AGENT_ID_ENVIRONMENT, PAGER_MCP_URL_ENVIRONMENT } from "../../launch/environment.js";
|
|
2
|
+
/** Names the broker endpoint supplied to the Pi extension. */
|
|
3
|
+
export const PI_BROKER_URL_ENVIRONMENT = PAGER_MCP_URL_ENVIRONMENT;
|
|
4
|
+
/** Names the reserved broker identity supplied to the Pi extension. */
|
|
5
|
+
export const PI_AGENT_ID_ENVIRONMENT = PAGER_AGENT_ID_ENVIRONMENT;
|
|
6
|
+
/** Names the single-use messaging attachment supplied to the Pi extension. */
|
|
7
|
+
export const PI_MESSAGING_RESERVATION_ENVIRONMENT = "PAGER_PI_MESSAGING_ATTACH_RESERVATION";
|
|
8
|
+
/** Names the single-use delivery attachment supplied to the Pi extension. */
|
|
9
|
+
export const PI_DELIVERY_RESERVATION_ENVIRONMENT = "PAGER_PI_DELIVERY_ATTACH_RESERVATION";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { RuntimeEnvironment } from "../../launch/environment.js";
|
|
3
|
+
import { type BrokerEndpoints } from "../../launch/on-demand.js";
|
|
4
|
+
import { type PagerInbox, type PagerInboxOpener } from "../../messaging/inbox-socket.js";
|
|
5
|
+
import { PiBrokerClient } from "./broker-client.js";
|
|
6
|
+
import type { BrokerRequest } from "../../messaging/delivery-client.js";
|
|
7
|
+
import { type PiHostSlot } from "./host-slots.js";
|
|
8
|
+
type ScheduledTask = {
|
|
9
|
+
cancel(): void;
|
|
10
|
+
};
|
|
11
|
+
type PiExtensionRuntime = {
|
|
12
|
+
client?: PiBrokerClient;
|
|
13
|
+
connectionKey?: string;
|
|
14
|
+
directCwd: string | undefined;
|
|
15
|
+
inbox?: PagerInbox;
|
|
16
|
+
/** Set once opening the inbox failed; the process then receives no deliveries, and is not retried. */
|
|
17
|
+
inboxFailed?: true;
|
|
18
|
+
/** Injects one delivery into the current session; absent between sessions. */
|
|
19
|
+
inject?: (content: string) => void;
|
|
20
|
+
tasks: ScheduledTask[];
|
|
21
|
+
};
|
|
22
|
+
/** Owns repeating background work started by the Pi extension. */
|
|
23
|
+
export interface PiExtensionScheduler {
|
|
24
|
+
/**
|
|
25
|
+
* Start repeating work until cancellation.
|
|
26
|
+
* @param intervalMs - Period between invocations in milliseconds.
|
|
27
|
+
* @param task - Invoked without being awaited, so a task that starts asynchronous work guards its
|
|
28
|
+
* own overlap.
|
|
29
|
+
* @returns - Stops further invocations; a call already in flight is not interrupted.
|
|
30
|
+
*/
|
|
31
|
+
repeat(intervalMs: number, task: () => void): () => void;
|
|
32
|
+
}
|
|
33
|
+
/** Supplies all external operations used by the Pi extension. */
|
|
34
|
+
export type PiExtensionDependencies = {
|
|
35
|
+
/** Reads service configuration without opening the broker database. */
|
|
36
|
+
readEnvironment(): RuntimeEnvironment;
|
|
37
|
+
/** Starts or adopts the shared broker service. */
|
|
38
|
+
ensureService(environment: RuntimeEnvironment): Promise<BrokerEndpoints>;
|
|
39
|
+
/** Supplies launch identity and runtime selection values. */
|
|
40
|
+
processEnv: NodeJS.ProcessEnv;
|
|
41
|
+
/** Performs one broker HTTP request. */
|
|
42
|
+
fetch: BrokerRequest;
|
|
43
|
+
/** Owns the attachment heartbeat cadence. */
|
|
44
|
+
scheduler: PiExtensionScheduler;
|
|
45
|
+
/** Opens the inbox the broker pushes this process's deliveries to. */
|
|
46
|
+
openInbox: PagerInboxOpener;
|
|
47
|
+
/** Shares this extension's runtime across duplicate loaded copies of the module. */
|
|
48
|
+
runtimeSlot: PiHostSlot<PiExtensionRuntime>;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Construct the Pager Pi extension around explicit host dependencies.
|
|
52
|
+
* @param dependencies - Service, HTTP, environment, and scheduling operations.
|
|
53
|
+
* @returns - Pi extension factory that registers broker tools and session lifecycle hooks.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createPiExtension(dependencies: PiExtensionDependencies): (pi: ExtensionAPI) => void;
|
|
56
|
+
declare const _default: (pi: ExtensionAPI) => void;
|
|
57
|
+
export default _default;
|