@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,49 @@
|
|
|
1
|
+
/** Renderer-independent color value accepted by a presentation adapter. */
|
|
2
|
+
export type TuiThemeColor = string;
|
|
3
|
+
/**
|
|
4
|
+
* A role that paints nothing, leaving whatever is already in the cell.
|
|
5
|
+
*
|
|
6
|
+
* A terminal cell has no alpha, so a fill is the only way to change a background; this is the way to
|
|
7
|
+
* decline one and let the surface below stay as it is.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TUI_NO_FILL = "transparent";
|
|
10
|
+
/** Semantic color roles shared by TUI screens and renderer adapters. */
|
|
11
|
+
export interface TuiTheme {
|
|
12
|
+
/** Structural and textual presentation roles. */
|
|
13
|
+
readonly structure: {
|
|
14
|
+
readonly background: TuiThemeColor;
|
|
15
|
+
readonly selection: {
|
|
16
|
+
readonly background: TuiThemeColor;
|
|
17
|
+
readonly text: TuiThemeColor;
|
|
18
|
+
};
|
|
19
|
+
readonly dimBackdrop: TuiThemeColor;
|
|
20
|
+
readonly rulesAndLabels: TuiThemeColor;
|
|
21
|
+
readonly bodyText: TuiThemeColor;
|
|
22
|
+
readonly emphasis: TuiThemeColor;
|
|
23
|
+
};
|
|
24
|
+
/** Stable palette slots assigned to agent identities. */
|
|
25
|
+
readonly agents: readonly [TuiThemeColor, TuiThemeColor, TuiThemeColor, TuiThemeColor, TuiThemeColor, TuiThemeColor];
|
|
26
|
+
/** Message and operation lifecycle roles. */
|
|
27
|
+
readonly states: {
|
|
28
|
+
readonly complete: TuiThemeColor;
|
|
29
|
+
readonly inProgress: TuiThemeColor;
|
|
30
|
+
readonly failed: TuiThemeColor;
|
|
31
|
+
readonly expired: TuiThemeColor;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Preserve one semantic theme as an immutable renderer-independent value.
|
|
36
|
+
* @param theme - Complete semantic role mapping supplied by the presentation authority.
|
|
37
|
+
* @returns Frozen theme value safe to share across renderer adapters.
|
|
38
|
+
*/
|
|
39
|
+
export declare function defineTuiTheme(theme: TuiTheme): Readonly<TuiTheme>;
|
|
40
|
+
/**
|
|
41
|
+
* Collapse every foreground role onto the backdrop colour so content behind an overlay recedes.
|
|
42
|
+
*
|
|
43
|
+
* A terminal cell carries one foreground and one background, so a backdrop is a recolour rather
|
|
44
|
+
* than an opacity: the panes keep their layout and their text, and lose every distinction the
|
|
45
|
+
* overlay is not about.
|
|
46
|
+
* @param theme - Semantic palette in normal use.
|
|
47
|
+
* @returns Frozen palette carrying the same structure with backdrop-coloured content.
|
|
48
|
+
*/
|
|
49
|
+
export declare function dimTuiTheme(theme: Readonly<TuiTheme>): Readonly<TuiTheme>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A role that paints nothing, leaving whatever is already in the cell.
|
|
3
|
+
*
|
|
4
|
+
* A terminal cell has no alpha, so a fill is the only way to change a background; this is the way to
|
|
5
|
+
* decline one and let the surface below stay as it is.
|
|
6
|
+
*/
|
|
7
|
+
export const TUI_NO_FILL = "transparent";
|
|
8
|
+
/**
|
|
9
|
+
* Preserve one semantic theme as an immutable renderer-independent value.
|
|
10
|
+
* @param theme - Complete semantic role mapping supplied by the presentation authority.
|
|
11
|
+
* @returns Frozen theme value safe to share across renderer adapters.
|
|
12
|
+
*/
|
|
13
|
+
export function defineTuiTheme(theme) {
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
structure: Object.freeze({
|
|
16
|
+
...theme.structure,
|
|
17
|
+
selection: Object.freeze({ ...theme.structure.selection }),
|
|
18
|
+
}),
|
|
19
|
+
agents: Object.freeze([
|
|
20
|
+
theme.agents[0],
|
|
21
|
+
theme.agents[1],
|
|
22
|
+
theme.agents[2],
|
|
23
|
+
theme.agents[3],
|
|
24
|
+
theme.agents[4],
|
|
25
|
+
theme.agents[5],
|
|
26
|
+
]),
|
|
27
|
+
states: Object.freeze({ ...theme.states }),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Collapse every foreground role onto the backdrop colour so content behind an overlay recedes.
|
|
32
|
+
*
|
|
33
|
+
* A terminal cell carries one foreground and one background, so a backdrop is a recolour rather
|
|
34
|
+
* than an opacity: the panes keep their layout and their text, and lose every distinction the
|
|
35
|
+
* overlay is not about.
|
|
36
|
+
* @param theme - Semantic palette in normal use.
|
|
37
|
+
* @returns Frozen palette carrying the same structure with backdrop-coloured content.
|
|
38
|
+
*/
|
|
39
|
+
export function dimTuiTheme(theme) {
|
|
40
|
+
const backdrop = theme.structure.dimBackdrop;
|
|
41
|
+
return defineTuiTheme({
|
|
42
|
+
structure: {
|
|
43
|
+
background: theme.structure.background,
|
|
44
|
+
// Dimming is a recolour, so a selection behind an overlay recedes with everything else instead
|
|
45
|
+
// of keeping the one fill on screen.
|
|
46
|
+
selection: { background: TUI_NO_FILL, text: backdrop },
|
|
47
|
+
dimBackdrop: backdrop,
|
|
48
|
+
rulesAndLabels: backdrop,
|
|
49
|
+
bodyText: backdrop,
|
|
50
|
+
emphasis: backdrop,
|
|
51
|
+
},
|
|
52
|
+
agents: [backdrop, backdrop, backdrop, backdrop, backdrop, backdrop],
|
|
53
|
+
states: { complete: backdrop, inProgress: backdrop, failed: backdrop, expired: backdrop },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type OperatorDiagnostic, type OperatorEventEnvelope, type OperatorSnapshot } from "../operator/protocol.js";
|
|
2
|
+
/** Receives atomic snapshots, ordered events, or a recovery request from a TUI transport consumer. */
|
|
3
|
+
export type TuiTransportActions = {
|
|
4
|
+
replace(snapshot: OperatorSnapshot, replacement: boolean): void;
|
|
5
|
+
event(envelope: OperatorEventEnvelope): void;
|
|
6
|
+
recover(): void;
|
|
7
|
+
incompatible(): void;
|
|
8
|
+
diagnostic(diagnostic: OperatorDiagnostic): void;
|
|
9
|
+
live(): void;
|
|
10
|
+
awaitReplacement(): void;
|
|
11
|
+
replacementProgress(): void;
|
|
12
|
+
};
|
|
13
|
+
/** Validates and atomically assembles operator stream frames without a renderer dependency. */
|
|
14
|
+
export declare class TuiOperatorTransportController {
|
|
15
|
+
#private;
|
|
16
|
+
private readonly actions;
|
|
17
|
+
/**
|
|
18
|
+
* Construct a frame reducer with explicit model actions.
|
|
19
|
+
* @param actions - Model boundary: snapshots and events arrive complete and validated, and progress, pending-replacement and recovery signals as they occur.
|
|
20
|
+
*/
|
|
21
|
+
constructor(actions: TuiTransportActions);
|
|
22
|
+
/**
|
|
23
|
+
* Consume one untrusted server frame.
|
|
24
|
+
*
|
|
25
|
+
* A frame that fails validation asks the model to recover rather than throwing, as does one arriving in
|
|
26
|
+
* a phase that cannot accept it, except a heartbeat while a replacement is owed, which is dropped. Once
|
|
27
|
+
* the stream is incompatible, every frame is ignored.
|
|
28
|
+
* @param value - Candidate operator transport frame, as parsed from one line of the stream.
|
|
29
|
+
*/
|
|
30
|
+
receive(value: unknown): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { OPERATOR_PROTOCOL_VERSION, OPERATOR_SNAPSHOT_CHUNK_BYTES, OPERATOR_SNAPSHOT_TOTAL_LIMIT_BYTES, } from "../operator/protocol.js";
|
|
2
|
+
import { validateOperatorCursor, validateOperatorDiagnostic, validateOperatorEventEnvelope, validateOperatorSnapshot, } from "../operator/validation.js";
|
|
3
|
+
import { exactFields, isPlainObject } from "../shared/guards.js";
|
|
4
|
+
const MAX_CHUNK_BASE64_BYTES = Math.ceil(OPERATOR_SNAPSHOT_CHUNK_BYTES / 3) * 4;
|
|
5
|
+
/** Validates and atomically assembles operator stream frames without a renderer dependency. */
|
|
6
|
+
export class TuiOperatorTransportController {
|
|
7
|
+
actions;
|
|
8
|
+
#phase = { type: "idle" };
|
|
9
|
+
/**
|
|
10
|
+
* Construct a frame reducer with explicit model actions.
|
|
11
|
+
* @param actions - Model boundary: snapshots and events arrive complete and validated, and progress, pending-replacement and recovery signals as they occur.
|
|
12
|
+
*/
|
|
13
|
+
constructor(actions) {
|
|
14
|
+
this.actions = actions;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Consume one untrusted server frame.
|
|
18
|
+
*
|
|
19
|
+
* A frame that fails validation asks the model to recover rather than throwing, as does one arriving in
|
|
20
|
+
* a phase that cannot accept it, except a heartbeat while a replacement is owed, which is dropped. Once
|
|
21
|
+
* the stream is incompatible, every frame is ignored.
|
|
22
|
+
* @param value - Candidate operator transport frame, as parsed from one line of the stream.
|
|
23
|
+
*/
|
|
24
|
+
receive(value) {
|
|
25
|
+
if (this.#phase.type === "incompatible")
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
const frame = readFrame(value);
|
|
29
|
+
switch (frame.type) {
|
|
30
|
+
case "incompatible_version":
|
|
31
|
+
this.#phase = { type: "incompatible" };
|
|
32
|
+
this.actions.incompatible();
|
|
33
|
+
return;
|
|
34
|
+
case "diagnostic":
|
|
35
|
+
this.actions.diagnostic(frame.diagnostic);
|
|
36
|
+
return;
|
|
37
|
+
case "heartbeat":
|
|
38
|
+
if (this.#owedReplacement())
|
|
39
|
+
return;
|
|
40
|
+
this.actions.live();
|
|
41
|
+
return;
|
|
42
|
+
case "resync_required":
|
|
43
|
+
// A gap or an epoch change is the server's answer to a resume request, and the replacement
|
|
44
|
+
// snapshot for it follows on this same connection. Only an unavailable stream is terminal.
|
|
45
|
+
if (frame.reason === "stream_unavailable") {
|
|
46
|
+
this.#fail();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.#phase = { type: "awaiting_replacement" };
|
|
50
|
+
this.actions.awaitReplacement();
|
|
51
|
+
return;
|
|
52
|
+
case "event":
|
|
53
|
+
if (this.#phase.type !== "idle") {
|
|
54
|
+
this.#fail();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.actions.event(frame.envelope);
|
|
58
|
+
return;
|
|
59
|
+
case "snapshot_begin":
|
|
60
|
+
if (this.#phase.type === "transferring") {
|
|
61
|
+
this.#fail();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if ((frame.mode === "replacement") !== (this.#phase.type === "awaiting_replacement")) {
|
|
65
|
+
this.#fail();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.#phase = { type: "transferring", transfer: { ...frame, chunks: [] } };
|
|
69
|
+
return;
|
|
70
|
+
case "snapshot_chunk": {
|
|
71
|
+
if (this.#phase.type !== "transferring") {
|
|
72
|
+
this.#fail();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const transfer = this.#phase.transfer;
|
|
76
|
+
if (frame.index !== transfer.chunks.length || frame.index >= transfer.chunkCount) {
|
|
77
|
+
this.#fail();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (frame.bytes.byteLength !==
|
|
81
|
+
(frame.index + 1 === transfer.chunkCount
|
|
82
|
+
? transfer.totalBytes - frame.index * OPERATOR_SNAPSHOT_CHUNK_BYTES
|
|
83
|
+
: OPERATOR_SNAPSHOT_CHUNK_BYTES)) {
|
|
84
|
+
this.#fail();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
transfer.chunks.push(frame.bytes);
|
|
88
|
+
if (transfer.mode === "replacement")
|
|
89
|
+
this.actions.replacementProgress();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case "snapshot_end":
|
|
93
|
+
this.#finish(frame);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
this.#fail();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
#owedReplacement() {
|
|
101
|
+
return (this.#phase.type === "awaiting_replacement" ||
|
|
102
|
+
(this.#phase.type === "transferring" && this.#phase.transfer.mode === "replacement"));
|
|
103
|
+
}
|
|
104
|
+
#finish(end) {
|
|
105
|
+
if (this.#phase.type !== "transferring") {
|
|
106
|
+
this.#fail();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const transfer = this.#phase.transfer;
|
|
110
|
+
this.#phase = { type: "idle" };
|
|
111
|
+
if (!sameCursor(transfer.cursor, end.cursor) ||
|
|
112
|
+
transfer.totalBytes !== end.totalBytes ||
|
|
113
|
+
transfer.chunkCount !== end.chunkCount ||
|
|
114
|
+
transfer.chunks.length !== end.chunkCount) {
|
|
115
|
+
this.#fail();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const bytes = Buffer.concat(transfer.chunks);
|
|
119
|
+
if (bytes.byteLength !== transfer.totalBytes) {
|
|
120
|
+
this.#fail();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const snapshot = validateOperatorSnapshot(JSON.parse(bytes.toString("utf8")));
|
|
125
|
+
if (!sameCursor(snapshot.cursor, transfer.cursor)) {
|
|
126
|
+
this.#fail();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.actions.replace(snapshot, transfer.mode === "replacement");
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
this.#fail();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
#fail() {
|
|
136
|
+
// The terminal latch, once set, is never reopened: a throwing `incompatible` action reaches this
|
|
137
|
+
// through the catch in `receive` after the latch is already set, and must not undo it.
|
|
138
|
+
if (this.#phase.type === "incompatible")
|
|
139
|
+
return;
|
|
140
|
+
this.#phase = { type: "idle" };
|
|
141
|
+
this.actions.recover();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function readFrame(value) {
|
|
145
|
+
const record = exactRecord(value);
|
|
146
|
+
if (typeof record.type !== "string")
|
|
147
|
+
throw new Error("frame type");
|
|
148
|
+
switch (record.type) {
|
|
149
|
+
case "snapshot_begin": {
|
|
150
|
+
exact(record, ["type", "protocolVersion", "mode", "cursor", "encoding", "totalBytes", "chunkCount"]);
|
|
151
|
+
const totalBytes = boundedBytes(record.totalBytes);
|
|
152
|
+
const chunkCount = boundedCount(record.chunkCount, totalBytes);
|
|
153
|
+
if (record.protocolVersion !== OPERATOR_PROTOCOL_VERSION ||
|
|
154
|
+
(record.mode !== "initial" && record.mode !== "replacement") ||
|
|
155
|
+
record.encoding !== "base64")
|
|
156
|
+
throw new Error("begin");
|
|
157
|
+
return {
|
|
158
|
+
type: "snapshot_begin",
|
|
159
|
+
mode: record.mode,
|
|
160
|
+
cursor: validateOperatorCursor(record.cursor),
|
|
161
|
+
totalBytes,
|
|
162
|
+
chunkCount,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
case "snapshot_chunk": {
|
|
166
|
+
exact(record, ["type", "protocolVersion", "index", "data"]);
|
|
167
|
+
const index = record.index;
|
|
168
|
+
const data = record.data;
|
|
169
|
+
if (record.protocolVersion !== OPERATOR_PROTOCOL_VERSION ||
|
|
170
|
+
typeof index !== "number" ||
|
|
171
|
+
!Number.isSafeInteger(index) ||
|
|
172
|
+
index < 0 ||
|
|
173
|
+
typeof data !== "string" ||
|
|
174
|
+
data.length > MAX_CHUNK_BASE64_BYTES ||
|
|
175
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(data))
|
|
176
|
+
throw new Error("chunk");
|
|
177
|
+
const bytes = Buffer.from(data, "base64");
|
|
178
|
+
if (bytes.byteLength > OPERATOR_SNAPSHOT_CHUNK_BYTES || bytes.toString("base64") !== data)
|
|
179
|
+
throw new Error("chunk encoding");
|
|
180
|
+
return { type: "snapshot_chunk", index, bytes };
|
|
181
|
+
}
|
|
182
|
+
case "snapshot_end":
|
|
183
|
+
exact(record, ["type", "protocolVersion", "cursor", "totalBytes", "chunkCount"]);
|
|
184
|
+
if (record.protocolVersion !== OPERATOR_PROTOCOL_VERSION)
|
|
185
|
+
throw new Error("end");
|
|
186
|
+
{
|
|
187
|
+
const totalBytes = boundedBytes(record.totalBytes);
|
|
188
|
+
return {
|
|
189
|
+
type: "snapshot_end",
|
|
190
|
+
cursor: validateOperatorCursor(record.cursor),
|
|
191
|
+
totalBytes,
|
|
192
|
+
chunkCount: boundedCount(record.chunkCount, totalBytes),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
case "event":
|
|
196
|
+
exact(record, ["type", "envelope"]);
|
|
197
|
+
return { type: "event", envelope: validateOperatorEventEnvelope(record.envelope) };
|
|
198
|
+
case "diagnostic":
|
|
199
|
+
exact(record, ["type", "diagnostic"]);
|
|
200
|
+
return { type: "diagnostic", diagnostic: validateOperatorDiagnostic(record.diagnostic) };
|
|
201
|
+
case "resync_required":
|
|
202
|
+
exact(record, ["type", "reason"]);
|
|
203
|
+
if (record.reason !== "cursor_gap" && record.reason !== "epoch_changed" && record.reason !== "stream_unavailable")
|
|
204
|
+
throw new Error("resync");
|
|
205
|
+
return { type: "resync_required", reason: record.reason };
|
|
206
|
+
case "incompatible_version":
|
|
207
|
+
exact(record, ["type", "supportedVersion", "receivedVersion"]);
|
|
208
|
+
// `supportedVersion` is the refusing peer's own version, so it differs from this build's by
|
|
209
|
+
// definition and is checked for shape only.
|
|
210
|
+
if (!Number.isSafeInteger(record.supportedVersion) || !Number.isSafeInteger(record.receivedVersion))
|
|
211
|
+
throw new Error("incompatible");
|
|
212
|
+
return { type: "incompatible_version" };
|
|
213
|
+
case "heartbeat":
|
|
214
|
+
exact(record, ["type", "cursor", "sentAt"]);
|
|
215
|
+
if (!Number.isSafeInteger(record.sentAt))
|
|
216
|
+
throw new Error("heartbeat");
|
|
217
|
+
validateOperatorCursor(record.cursor);
|
|
218
|
+
return { type: "heartbeat" };
|
|
219
|
+
default:
|
|
220
|
+
throw new Error("frame type");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function exactRecord(value) {
|
|
224
|
+
if (!isPlainObject(value))
|
|
225
|
+
throw new Error("record");
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
function exact(value, keys) {
|
|
229
|
+
exactFields({
|
|
230
|
+
record: value,
|
|
231
|
+
required: keys,
|
|
232
|
+
optional: [],
|
|
233
|
+
path: "frame",
|
|
234
|
+
fail: (_path, reason) => {
|
|
235
|
+
throw new Error(reason);
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function boundedBytes(value) {
|
|
240
|
+
if (typeof value !== "number" ||
|
|
241
|
+
!Number.isSafeInteger(value) ||
|
|
242
|
+
value < 1 ||
|
|
243
|
+
value > OPERATOR_SNAPSHOT_TOTAL_LIMIT_BYTES)
|
|
244
|
+
throw new Error("bytes");
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
function boundedCount(value, total) {
|
|
248
|
+
const expected = Math.ceil(total / OPERATOR_SNAPSHOT_CHUNK_BYTES);
|
|
249
|
+
if (!Number.isSafeInteger(value) || value !== expected)
|
|
250
|
+
throw new Error("count");
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function sameCursor(left, right) {
|
|
254
|
+
return left.epoch === right.epoch && left.sequence === right.sequence;
|
|
255
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the version a package manifest declares.
|
|
3
|
+
*
|
|
4
|
+
* Throws for a manifest without one, which would otherwise build `agent-pager@undefined`.
|
|
5
|
+
* @param contents - Manifest text as read from disk.
|
|
6
|
+
* @param source - Manifest path named in the rejection.
|
|
7
|
+
* @returns - The declared version.
|
|
8
|
+
*/
|
|
9
|
+
export declare function manifestVersion(contents: string, source: string): string;
|
|
10
|
+
/** Names the published package version, read once from the nearest `package.json`. */
|
|
11
|
+
export declare const PAGER_VERSION: string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { isPlainObject } from "./shared/guards.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read the version a package manifest declares.
|
|
7
|
+
*
|
|
8
|
+
* Throws for a manifest without one, which would otherwise build `agent-pager@undefined`.
|
|
9
|
+
* @param contents - Manifest text as read from disk.
|
|
10
|
+
* @param source - Manifest path named in the rejection.
|
|
11
|
+
* @returns - The declared version.
|
|
12
|
+
*/
|
|
13
|
+
export function manifestVersion(contents, source) {
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(contents);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error(`package.json is not valid JSON: ${source}`);
|
|
20
|
+
}
|
|
21
|
+
if (!isPlainObject(parsed) || typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
22
|
+
throw new Error(`package.json declares no version: ${source}`);
|
|
23
|
+
}
|
|
24
|
+
return parsed.version;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the published package version from the nearest `package.json` above this file.
|
|
28
|
+
*
|
|
29
|
+
* Walks up rather than using one fixed relative path: this module runs both directly from `src/`
|
|
30
|
+
* (Bun executing TypeScript unbuilt) and from the compiled `dist/src/` tree, one directory deeper, so
|
|
31
|
+
* no single relative path reaches `package.json` in both.
|
|
32
|
+
* @returns - The version field from the nearest ancestor `package.json`.
|
|
33
|
+
*/
|
|
34
|
+
function readPackageVersion() {
|
|
35
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
for (;;) {
|
|
37
|
+
try {
|
|
38
|
+
const path = join(directory, "package.json");
|
|
39
|
+
return manifestVersion(readFileSync(path, "utf8"), path);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"))
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const parent = dirname(directory);
|
|
46
|
+
if (parent === directory)
|
|
47
|
+
throw new Error(`package.json not found above ${import.meta.url}`);
|
|
48
|
+
directory = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Names the published package version, read once from the nearest `package.json`. */
|
|
52
|
+
export const PAGER_VERSION = readPackageVersion();
|
package/package.json
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haywoodmarx/agent-pager",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Durable local messaging between agent implementations.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"packageManager": "bun@1.4.2",
|
|
9
|
+
"engines": {
|
|
10
|
+
"bun": ">=1.4.2"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"pager": "dist/src/launch/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/src/index.d.ts",
|
|
18
|
+
"bun": "./dist/src/index.js",
|
|
19
|
+
"import": "./dist/src/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./implementation/opencode": {
|
|
22
|
+
"types": "./dist/src/implementations/opencode/plugin.d.ts",
|
|
23
|
+
"bun": "./dist/src/implementations/opencode/plugin.js",
|
|
24
|
+
"import": "./dist/src/implementations/opencode/plugin.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"skills",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"pi": {
|
|
34
|
+
"extensions": [
|
|
35
|
+
"./dist/src/implementations/pi/extension.js"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"prepare": "bun install --frozen-lockfile --ignore-scripts && bun run build && husky",
|
|
40
|
+
"build": "bun scripts/tooling/build.mjs",
|
|
41
|
+
"prepack": "bun run build",
|
|
42
|
+
"check:client-boundary": "bun scripts/tooling/client-boundary-self-test.mjs && bun scripts/tooling/client-boundary.mjs",
|
|
43
|
+
"check:broker-writes": "bun scripts/tooling/broker-writes-self-test.mjs && bun scripts/tooling/broker-writes.mjs",
|
|
44
|
+
"check:fixture-checksums": "bun scripts/tooling/fixture-checksums.mjs",
|
|
45
|
+
"check:skill-limits": "bun scripts/tooling/skill-limits-check.mjs",
|
|
46
|
+
"format:check": "bun run --bun prettier --check 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx,mjs}' 'scripts/**/*.{mjs,mts}' '*.md' 'docs/**/*.{md,json}' 'skills/**/*.md' '.claude/skills/final-review/**/*.md' 'fixtures/implementations/**/*.json' '**/*.{json,js,ts,tsx,cjs}'",
|
|
47
|
+
"lint": "bun run --bun eslint . --report-unused-disable-directives --max-warnings=0",
|
|
48
|
+
"lint:target": "bun scripts/tooling/lint-target.mjs",
|
|
49
|
+
"gate": "bun scripts/tooling/gate.mjs",
|
|
50
|
+
"mutation:verify": "bun scripts/tooling/mutation-verify.mjs",
|
|
51
|
+
"seed:tui": "bun scripts/tooling/seed-tui.mjs",
|
|
52
|
+
"seed:conversations": "bun scripts/tooling/seed-conversations.mjs",
|
|
53
|
+
"test": "bun run test:unit",
|
|
54
|
+
"test:unit": "bun test tests/unit",
|
|
55
|
+
"test:coverage": "bun run build && bun scripts/tooling/coverage-inventory.mjs",
|
|
56
|
+
"test:implementation-contracts": "bun run build && bun scripts/implementations/contracts/adapter-contract.mjs",
|
|
57
|
+
"test:package": "bun scripts/tooling/package-smoke.mjs",
|
|
58
|
+
"test:package:npm-compat": "bun scripts/tooling/package-smoke.mjs --npm",
|
|
59
|
+
"typecheck": "bun run --bun tsc --noEmit && bun run --bun tsc --noEmit -p tsconfig.scripts-tooling.json",
|
|
60
|
+
"live:configs": "bun scripts/implementations/acceptance/live-configs.mjs",
|
|
61
|
+
"live:mcp": "bun run build && bun scripts/implementations/acceptance/live-mcp-server.mjs",
|
|
62
|
+
"test:live-implementations": "bun run build && bun scripts/implementations/acceptance/message-lifecycle.mjs",
|
|
63
|
+
"test:integration": "bun run build && bun test tests/integration",
|
|
64
|
+
"test:live-terminal": "bun run build && bun scripts/implementations/acceptance/terminal-message-lifecycle.mjs",
|
|
65
|
+
"test:live-launch": "bun run build && bun scripts/implementations/acceptance/launch-lifecycle.mjs",
|
|
66
|
+
"test:live-wrapper-launch": "bun run build && bun scripts/implementations/acceptance/wrapper-launch.mjs",
|
|
67
|
+
"test:live-production": "bun run build && bun scripts/implementations/acceptance/production-lifecycle.mjs",
|
|
68
|
+
"test:live-pi": "bun run build && bun scripts/implementations/acceptance/pi-lifecycle.mjs",
|
|
69
|
+
"test:live-editor-push": "bun run build && bun scripts/implementations/acceptance/editor-push.mjs",
|
|
70
|
+
"test:live-editor-close": "bun run build && bun scripts/implementations/acceptance/editor-self-close.mjs",
|
|
71
|
+
"test:live-claude": "bun run build && bun scripts/implementations/acceptance/claude-observer.mjs",
|
|
72
|
+
"test:terminal-provider": "bun run build && bun scripts/implementations/acceptance/terminal-provider.mjs"
|
|
73
|
+
},
|
|
74
|
+
"gateSteps": [
|
|
75
|
+
"check:client-boundary",
|
|
76
|
+
"check:broker-writes",
|
|
77
|
+
"check:fixture-checksums",
|
|
78
|
+
"check:skill-limits",
|
|
79
|
+
"typecheck",
|
|
80
|
+
"lint",
|
|
81
|
+
"format:check",
|
|
82
|
+
"test",
|
|
83
|
+
"test:integration",
|
|
84
|
+
"test:coverage",
|
|
85
|
+
"build",
|
|
86
|
+
"test:package",
|
|
87
|
+
"test:implementation-contracts"
|
|
88
|
+
],
|
|
89
|
+
"repository": {
|
|
90
|
+
"type": "git",
|
|
91
|
+
"url": "git+ssh://git@github.com/haywoodmarx/agent-pager.git"
|
|
92
|
+
},
|
|
93
|
+
"publishConfig": {
|
|
94
|
+
"access": "public"
|
|
95
|
+
},
|
|
96
|
+
"keywords": [
|
|
97
|
+
"agents",
|
|
98
|
+
"messaging",
|
|
99
|
+
"orchestration",
|
|
100
|
+
"pi-package"
|
|
101
|
+
],
|
|
102
|
+
"author": "Haywood-Marx",
|
|
103
|
+
"license": "MIT",
|
|
104
|
+
"devDependencies": {
|
|
105
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
106
|
+
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.2",
|
|
107
|
+
"@eslint/js": "^10.0.1",
|
|
108
|
+
"@types/bun": "^1.3.14",
|
|
109
|
+
"@types/node": "^26.1.2",
|
|
110
|
+
"@types/react": "^19.2.17",
|
|
111
|
+
"eslint": "^10.8.0",
|
|
112
|
+
"eslint-plugin-jsdoc": "^63.3.1",
|
|
113
|
+
"husky": "^9.1.7",
|
|
114
|
+
"lint-staged": "^17.2.0",
|
|
115
|
+
"prettier": "^3.9.6",
|
|
116
|
+
"typebox": "1.3.3",
|
|
117
|
+
"typescript": "6.0.3",
|
|
118
|
+
"typescript-eslint": "^8.65.0"
|
|
119
|
+
},
|
|
120
|
+
"dependencies": {
|
|
121
|
+
"@opentui/core": "0.5.10",
|
|
122
|
+
"@opentui/react": "0.5.10",
|
|
123
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
124
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
125
|
+
"react": "^19.2.8"
|
|
126
|
+
},
|
|
127
|
+
"peerDependencies": {
|
|
128
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
129
|
+
"typebox": "^1.3.3"
|
|
130
|
+
},
|
|
131
|
+
"peerDependenciesMeta": {
|
|
132
|
+
"@earendil-works/pi-coding-agent": {
|
|
133
|
+
"optional": true
|
|
134
|
+
},
|
|
135
|
+
"typebox": {
|
|
136
|
+
"optional": true
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"overrides": {
|
|
140
|
+
"brace-expansion": "5.0.9"
|
|
141
|
+
}
|
|
142
|
+
}
|