@samuelho-dev/coordinator 0.3.0-beta.2 → 0.3.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -47
- package/THIRD_PARTY_NOTICES.md +3 -1
- package/dist/plugins/omp.js +136 -7
- package/dist/plugins/pi.js +207 -77
- package/dist/scripts/install.js +9 -5
- package/dist/src/inspection.js +7 -1
- package/dist/src/main.js +4 -1
- package/dist/src/native.js +10 -3
- package/dist/src/plugin.js +107 -8
- package/dist/src/rex.js +36 -0
- package/dist/src/setup.js +134 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,14 +6,14 @@ Read-only inspection of existing Pi, OMP, Claude Code, Codex CLI and Hermes sess
|
|
|
6
6
|
|
|
7
7
|
There are two parts: **one inspector service**, plus **native integrations on each host**. Configuration/Helm values describe desired settings; activation and service startup are explicit commands. Building and artifact generation do not deploy, activate or hot-reload anything. CI/release workflows produce artifacts, not host installations.
|
|
8
8
|
|
|
9
|
-
The API and
|
|
9
|
+
The API and setup CLI require Node >=22.19. Install the version-pinned release:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
|
|
13
|
-
pnpm build
|
|
14
|
-
export PACKAGE_ROOT="$PWD"
|
|
12
|
+
npm install --global @samuelho-dev/coordinator@0.3.0-beta.4
|
|
15
13
|
```
|
|
16
14
|
|
|
15
|
+
For source development only, use a reviewed checkout with `pnpm install --frozen-lockfile && pnpm build` (pnpm 11.9.0). `coordinator-install --source` deliberately activates that exact local package; never source-link a native package manager's own `node_modules` directory.
|
|
16
|
+
|
|
17
17
|
The [esbuild build](scripts/build.ts) keeps Pi dependencies external and bundles OMP dependencies to avoid compatibility-loader traversal. Native host APIs stay external; OMP uses [Bun's native CommonJS support](https://bun.com/docs/runtime/module-resolution#module-systems), not an injected shim. Rebuild and restart OMP after source updates.
|
|
18
18
|
|
|
19
19
|
For Kubernetes, follow the [chart operator guide](deploy/helm/coordinator/README.md): provide an existing Secret, verified image digest and matching source SHA. The chart creates an inspector Deployment/internal Service, not native plugins, TLS routing, a database or runners. Operators own rollout and supervision. Existing broker state requires the guide's preservation/migration procedure; the plugin does not migrate or delete it.
|
|
@@ -24,51 +24,36 @@ For Kubernetes, follow the [chart operator guide](deploy/helm/coordinator/README
|
|
|
24
24
|
|
|
25
25
|
Provision owner-scoped bearer keys. The server stores SHA-256 hashes, not raw tokens; every principal contains only `id`, `owner` and `tokenHash`. A key authorizes reads and publishing across that owner's fleet, not one physical device. Same-owner keys can announce multiple installations; installation IDs are labels, not device authentication. Anyone holding an owner key can publish under any of that owner's machine labels. Revoking a shared key revokes every holder; there is no separate read-only or health-token role.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Bootstrap a new API with one owner key:
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
cd "$PACKAGE_ROOT"
|
|
34
|
-
node --input-type=module <<'JS'
|
|
35
|
-
import { Config, Console, Crypto, Effect, FileSystem } from 'effect';
|
|
36
|
-
import { Hex } from 'effect/encoding';
|
|
37
|
-
import { NodeRuntime, NodeServices } from '@effect/platform-node';
|
|
38
|
-
NodeRuntime.runMain(Effect.gen(function* () {
|
|
39
|
-
const fs = yield* FileSystem.FileSystem;
|
|
40
|
-
const crypto = yield* Crypto.Crypto;
|
|
41
|
-
const directory = yield* Config.String('PRIVATE');
|
|
42
|
-
yield* fs.makeDirectory(directory, { mode: 0o700 });
|
|
43
|
-
yield* fs.chmod(directory, 0o700);
|
|
44
|
-
const token = Hex.encode(yield* crypto.randomBytes(32));
|
|
45
|
-
const file = `${directory}/owner.token`;
|
|
46
|
-
yield* fs.writeFileString(file, token, { mode: 0o600, flag: 'wx' });
|
|
47
|
-
yield* fs.chmod(file, 0o600);
|
|
48
|
-
yield* Console.log(Hex.encode(yield* crypto.digest('SHA-256', new TextEncoder().encode(token))));
|
|
49
|
-
}).pipe(Effect.provide(NodeServices.layer)));
|
|
50
|
-
JS
|
|
51
|
-
)
|
|
30
|
+
PRIVATE="${XDG_CONFIG_HOME:-$HOME/.config}/coordinator/server"
|
|
31
|
+
coordinator init --directory "$PRIVATE" --owner operator
|
|
32
|
+
COORDINATOR_CONFIG="$PRIVATE/server.json" coordinator inspector
|
|
52
33
|
```
|
|
53
34
|
|
|
54
|
-
|
|
35
|
+
`init` generates a random 32-byte bearer in `owner.token` and its SHA-256 principal in `server.json`, with directory0700/files0600. It prints paths and the start command, never the bearer. Existing files, unsafe ownership/permissions and symlinks are rejected; partial writes are removed. Defaults are loopback `127.0.0.1:8787`; `--listen-host` and `--port` select another bind address. No database is required.
|
|
55
36
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
Run the API under your normal process supervisor. Remote clients require operator-provided HTTPS with ordinary certificate verification; only loopback permits HTTP. For Kubernetes, bootstrap with `--listen-host 0.0.0.0`, provision the existing Secret from `server.json` and `owner.token` as described in the chart guide, and deploy the verified image/chart. Do not publish the token or expose an unauthenticated HTTP service.
|
|
38
|
+
|
|
39
|
+
`COORDINATOR_CONFIG` selects server configuration, not client credentials. Initialization does not start a service, provision TLS or replace an existing server configuration.
|
|
40
|
+
|
|
41
|
+
### Native-owner/client setup
|
|
42
|
+
|
|
43
|
+
Configure a native client interactively:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
coordinator connect
|
|
61
47
|
```
|
|
62
48
|
|
|
63
|
-
|
|
49
|
+
This prompts for the endpoint and a masked owner credential, performs an authenticated API read, and only then saves the private client configuration. It does not create machine identity or change native settings. For private-file provisioning:
|
|
64
50
|
|
|
65
51
|
```sh
|
|
66
|
-
|
|
52
|
+
coordinator connect --endpoint https://inspector.example.net --token-file /absolute/private/owner.token
|
|
67
53
|
```
|
|
68
54
|
|
|
69
|
-
`
|
|
55
|
+
`--config` chooses a new client file; otherwise the shared default location below is used. Existing files are never overwritten. `COORDINATOR_API_TOKEN` may supply the credential instead of the prompt, but credentials are never accepted as argv. Environment URL/token overrides still take precedence after setup.
|
|
70
56
|
|
|
71
|
-
### Native-owner/client setup
|
|
72
57
|
|
|
73
58
|
Clients need only `inspectorUrl` and `apiToken`, supplied through private JSON or `COORDINATOR_API_URL` / `COORDINATOR_API_TOKEN`. The environment overrides corresponding file fields. Have the native owner's secret manager inject the token into the environment of the native host, reader bridge and separately supervised connector; never put raw keys in Git, argv, generated native MCP configuration or logs:
|
|
74
59
|
|
|
@@ -97,8 +82,9 @@ Run as the nonroot native user with the endpoint and credentials provisioned as
|
|
|
97
82
|
|
|
98
83
|
```sh
|
|
99
84
|
TARGET=omp
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
PRIVATE="${XDG_CONFIG_HOME:-$HOME/.config}/coordinator"
|
|
86
|
+
coordinator-install --target "$TARGET" --destination "$PRIVATE/$TARGET-host" --dry-run
|
|
87
|
+
coordinator-install --target "$TARGET" --destination "$PRIVATE/$TARGET-host"
|
|
102
88
|
```
|
|
103
89
|
|
|
104
90
|
`--config "$PRIVATE/inspector.json"` is optional. An explicit flag or `COORDINATOR_CONNECTOR_CONFIG` selection is retained as a literal private-file pointer, never the raw token. Without either, clients look up `$XDG_CONFIG_HOME/coordinator/inspector.json` or `~/.config/coordinator/inspector.json` at runtime; the installer does not freeze that file's current endpoint into launchers or hooks. File-backed endpoint/credential changes take effect after ordinary process restart. Keep the same configuration environment available to the native host, hooks and separately supervised connector:
|
|
@@ -114,13 +100,13 @@ The installer validates prerequisites and creates launchers/config fragments. `-
|
|
|
114
100
|
|
|
115
101
|
| Target | Activation | Presence publisher |
|
|
116
102
|
| --- | --- | --- |
|
|
117
|
-
| OMP | `omp plugin
|
|
118
|
-
| Pi | `pi install
|
|
103
|
+
| OMP | `omp plugin install @samuelho-dev/coordinator@0.3.0-beta.4`; merge only the generated `mcp.json` coordinator entry into native MCP settings. `/coordinator list` needs no MCP registration. | In-session extension. |
|
|
104
|
+
| Pi | `pi install npm:@samuelho-dev/coordinator@0.3.0-beta.4`; directly registers `coordinator_session_list` / `coordinator_session_inspect`, without an MCP bridge. | In-session extension. |
|
|
119
105
|
| Claude Code | `claude --plugin-dir "$PRIVATE/claude-host"`; approve generated hooks/MCP tools; preserve existing status-line configuration. | Separate `connector.sh`. |
|
|
120
106
|
| Codex CLI | Merge generated `hooks/hooks.json` and coordinator `mcp.toml`; trust only Coordinator's handlers in native `/hooks`. | Separate `connector.sh`. |
|
|
121
107
|
| Hermes | Merge generated hook entries and `mcp_servers.coordinator` from `mcp.yaml` into the selected profile; no Python plugin installation. | Separate `connector.sh`. |
|
|
122
108
|
|
|
123
|
-
Generated directories contain configuration, not a
|
|
109
|
+
Generated directories contain configuration, not a package copy. Launchers reference the installed package's absolute paths, so keep that package available. Updates use explicit native package installs, followed by deliberate native reload/new sessions and restart of affected reader bridges/external connectors. Already-open sessions retain loaded code. Updating configuration alone does not update the plugin. Private token files are reread for rotation/recovery; environment or config-token changes require process reload. Staged server Secret changes require a controlled inspector restart.
|
|
124
110
|
|
|
125
111
|
`mcp.sh` is the stdio reader bridge; `connector.sh` publishes external hook metadata. The latter's equivalent foreground command is:
|
|
126
112
|
|
|
@@ -130,6 +116,16 @@ COORDINATOR_CONNECTOR_CONFIG="$PRIVATE/inspector.json" node dist/src/main.js con
|
|
|
130
116
|
|
|
131
117
|
Hook/status-line/notification capture is local and can initialize identity/cache on first use while offline, without a token; it is not an offline delivery queue. Network publishing still needs credentials and a running publisher. Metadata cache is bounded to 1024 observations and 64 KiB per file, normally expires after 120 s, and may retain Hermes observations while the attributed host remains live. No transcripts are collected.
|
|
132
118
|
|
|
119
|
+
### Native session commands and Rex
|
|
120
|
+
|
|
121
|
+
Pi/OMP register `/coordinator list`. It follows every API page visible to the owner key and returns pretty JSON (`{ "sessions": [...] }`) using Effect's JSON formatter, with native JSON syntax highlighting in the terminal. Each session preserves full native session/agent IDs, machine names/installation IDs, harness and all reported metadata: model/provider, workspace, activity, connection liveness, process identity and Rex association. Missing metadata is omitted; an empty listing returns `{ "sessions": [] }`. It reads the API directly, with no model turn, per-session resource sampling or separate MCP registration.
|
|
122
|
+
|
|
123
|
+
Session JSON is Coordinator's fleet model, not an OSC wire payload. `rex` preserves `REX_SERVER` as `serverAddress` and the actual full `REX_SESSION`/`REX_BLOCK` IDs; these are separate from native agent IDs. Optional `programStatus` uses [Rex Program Status Protocol](https://www.superlogical.com/rex/docs/build/program-status) semantics: idle/working/done/error, or blocked with permission/question/auth kind. Agent `kind` remains separate. Tool/API waits are working; unknown outcomes are omitted, not inferred from disconnects or generic stops. API connection leases do not expire terminal status records.
|
|
124
|
+
|
|
125
|
+
Stock OMP 18.12.6 already owns OSC7501 through `terminal.programStatus`; Coordinator does not duplicate, override or patch that reporter. Pi's terminal-only adapter uses a private scoped record, full replacements and change-only reporting; no controls are written to RPC/JSON/print output. Pi user prompts are blocked/question; completion waits for native final settlement, not an intermediate loop end. Claude hooks supply explicit permission/question/completion/failure states; unavailable native UI-only reasons are not guessed. Prompt/error text is never copied into status reports.
|
|
126
|
+
|
|
127
|
+
Deploy the matching API **before** upgrading clients. Strict schemas reject new fields on older APIs, and older readers cannot decode new presence fields. Coordinate native reloads; preserve existing credentials and installation identity.
|
|
128
|
+
|
|
133
129
|
## Coordinated migration
|
|
134
130
|
|
|
135
131
|
This is a clean schema cutover, with no legacy aliases or compatibility shims. Before a separately approved rollout, coordinate the server and all native owners: provision fresh owner keys privately, replace server principals with `{ id, owner, tokenHash }`, update the chart's `health.token` to match an owner key, and replace client files with endpoint/credential fields only. Remove manual `machineId`/`stateDirectory` settings, regenerate affected installer artifacts, then deliberately restart the inspector and affected native hosts/bridges/connectors. Preserve the automatically created local identity on subsequent upgrades. Old server/client configuration is rejected; do not mix releases expecting the old machine-bound schema. These instructions do not perform a live rollout or rotate live keys.
|
|
@@ -137,17 +133,23 @@ This is a clean schema cutover, with no legacy aliases or compatibility shims. B
|
|
|
137
133
|
|
|
138
134
|
## Plugin publication
|
|
139
135
|
|
|
140
|
-
The
|
|
136
|
+
The version-pinned beta is `@samuelho-dev/coordinator@0.3.0-beta.4`. The [npm workflow](.github/workflows/npm-release.yaml) promotes the exact-source CI-accepted tarball with owner authorization; [first-publication authentication and trust setup](#first-npm-publication) are prerequisites for a new publisher. The existing [release workflow](.github/workflows/release.yaml) publishes server/chart/GitHub artifacts, not native marketplace listings. Generated per-machine configuration is not a portable marketplace package.
|
|
141
137
|
|
|
142
|
-
|
|
138
|
+
For a version-pinned install, use your native package manager, then follow [configuration](#configuration) and [native host setup](#native-host-setup):
|
|
143
139
|
|
|
144
140
|
```sh
|
|
145
|
-
omp plugin install @samuelho-dev/coordinator@0.3.0-beta.
|
|
146
|
-
pi install npm:@samuelho-dev/coordinator@0.3.0-beta.
|
|
141
|
+
omp plugin install @samuelho-dev/coordinator@0.3.0-beta.4
|
|
142
|
+
pi install npm:@samuelho-dev/coordinator@0.3.0-beta.4
|
|
147
143
|
```
|
|
148
144
|
|
|
145
|
+
Stock OMP 18.12.6 does not automatically update npm plugins. Until upstream fixes its channel handling and dry-run behavior, use explicit versioned installs for updates; avoid `omp plugin upgrade` and `upgrade --dry-run` for beta releases.
|
|
146
|
+
|
|
149
147
|
`0.3.0-beta.2` fixes clean-install startup failures by pinning `effect`, `@effect/platform-node` and `@effect/platform-node-shared` together at 4.0.3 in the published dependencies. Checkout-only pnpm overrides do not constrain npm/OMP consumers; leaving the shared package floating can select an incompatible release (`effect/Path` missing `make`). Keep these three pins aligned when upgrading.
|
|
150
148
|
|
|
149
|
+
`0.3.0-beta.3` adds native `/coordinator list`, secure self-host bootstrap/authenticated client setup, and Rex references/status normalization without an OMP fork. Runtime dependency pins are unchanged.
|
|
150
|
+
|
|
151
|
+
`0.3.0-beta.4` fixes `/coordinator list` output: Effect-formatted, indented JSON with native syntax highlighting instead of hand-built text and literal fences. Native UI imports are static and selected at build time; the renderer registers once at startup. The session wire format and runtime dependencies are unchanged, so the existing beta.3 API needs no redeployment.
|
|
152
|
+
|
|
151
153
|
| Host | Publication route |
|
|
152
154
|
| --- | --- |
|
|
153
155
|
| [OMP](https://github.com/can1357/oh-my-pi/blob/main/docs/marketplace.md) | Publish a Git-backed `.omp-plugin/marketplace.json` catalog and runnable plugin tree with `package.json` `omp.extensions`. Claude-compatible `.claude-plugin/marketplace.json` is a fallback. npm catalog sources are not supported; ordinary `omp plugin install PACKAGE` is a separate npm route. |
|
|
@@ -178,7 +180,7 @@ Release gaps and constraints:
|
|
|
178
180
|
- **Startup:** generated integrations do not await the inspector during activation. Pi/OMP publish in the background; Claude/Codex/Hermes hooks capture locally; the local stdio bridge initializes and lists tools without a remote handshake. Explicit inspection/`coordinate` requests may await a bounded remote reply. Local configuration, filesystem and native loading still run during initialization.
|
|
179
181
|
- **State/lifetime:** the owner-isolated registry is in memory; restart loses discovery until reannouncement, never native sessions. A Pi/OMP plugin owns one Effect managed runtime; presence/poll failures cancel their peer and shutdown disposes its fibers without killing the host. Closing the stdio reader's stdin ends its scope and cancels pending HTTP reads. Failed startup/terminated connectors can restart on a later native event.
|
|
180
182
|
- **Effect composition:** `NodeServices.layer` provides filesystem/path/crypto/process/stdio services; `Host.layer` provides host facts. `Mcp.layer` privately supplies its HTTP client. The CLI scope owns the inspector/server; one Pi/OMP `ManagedRuntime` owns plugin fibers/layers through shutdown. CLI-only stdin closes on completion; plugin stdin remains host-owned. Layer construction, provisioning and sharing follow the [v4 layer contract](https://effect.website/docs/v4/api/effect/Layer).
|
|
181
|
-
- **Node bindings:** [`platform.ts`](src/platform.ts) confines
|
|
183
|
+
- **Node bindings:** [`platform.ts`](src/platform.ts) confines host facts, CLI-owned stdin, server composition and constant-time digest comparison. Runtime private readers use Effect `FileSystem`; setup's exclusive private-file transaction uses Node opened descriptors and no-follow flags. Native SDK callbacks remain foreign interop boundaries.
|
|
182
184
|
- **OMP shortcut:** a leading `coordinate` enriches the current input with fleet presence and an inspection cue. It does not contact other sessions with prompts.
|
|
183
185
|
|
|
184
186
|
| Timing/bound | Value |
|
|
@@ -208,6 +210,8 @@ CPU/RSS sampling runs only on inspection, over approximately 200 ms. PID plus OS
|
|
|
208
210
|
| [mcp.ts](src/mcp.ts) | `Mcp` service: bounded HTTP/MCP transport and stdio reader. |
|
|
209
211
|
| [native.ts](src/native.ts) | Private files/cache, native projections and connector policy. |
|
|
210
212
|
| [plugin.ts](src/plugin.ts) | Pi/OMP hooks, observers, tools and runtime ownership. |
|
|
213
|
+
| [rex.ts](src/rex.ts) | Native Rex environment association and Pi-only scoped OSC reporting. |
|
|
214
|
+
| [setup.ts](src/setup.ts) | Private self-host initialization, masked client prompts and authenticated configuration creation. |
|
|
211
215
|
| [resources.ts](src/resources.ts) | Linux/macOS process identity and on-demand sampling. |
|
|
212
216
|
| [platform.ts](src/platform.ts) | Effect Node platform composition, scoped CLI stdin, and host/crypto/executable bindings. |
|
|
213
217
|
| [main.ts](src/main.ts) | CLI entrypoints and layer composition. |
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -17,10 +17,12 @@ The implemented inspector cutover changes the shipped runtime, not the completen
|
|
|
17
17
|
|
|
18
18
|
The current Dockerfile does **not** download/copy standalone OMP or collect OMP notices into `/usr/local/share/coordinator/third-party/`; it adds no PostgreSQL client/backup tooling or application database. `tsx`, `undici` and `ws` are no longer direct runtime dependencies. Their absence from the direct manifest is not proof of absence from every transitive package or base-image byte. No Pi/OMP/native binary closure is asserted to be shipped by the current inspector image.
|
|
19
19
|
|
|
20
|
-
## npm plugin bundle notices — candidate 0.3.0-beta.
|
|
20
|
+
## npm plugin bundle notices — candidate 0.3.0-beta.3
|
|
21
21
|
|
|
22
22
|
The OMP extension embeds runtime dependencies to support its native compatibility loader; Pi keeps those dependencies external. Native Pi/OMP SDK implementations are not part of this inventory. The entries below were traced in the existing emitted OMP artifact and matching package distributions. Reconcile them against the newly built, accepted tarball before publication; this source review is not a completed image, history, or legal certification.
|
|
23
23
|
|
|
24
|
+
`0.3.0-beta.3` keeps the `0.3.0-beta.2` runtime dependency pins; bootstrap uses Node built-ins and the already-declared Effect CLI. No native host binary or SDK implementation is added to the published package.
|
|
25
|
+
|
|
24
26
|
| Embedded component | Version | Matching notice source |
|
|
25
27
|
| --- | --- | --- |
|
|
26
28
|
| `effect`, `@effect/platform-node`, `@effect/platform-node-shared` | 4.0.3 | Matching installed `LICENSE` files; [Effect package](https://registry.npmjs.org/effect/4.0.3) |
|
package/dist/plugins/omp.js
CHANGED
|
@@ -39214,6 +39214,13 @@ var ProcessIdentity = Schema_exports.Struct({
|
|
|
39214
39214
|
ownership: Schema_exports.Literals(["owned", "shared", "unknown"])
|
|
39215
39215
|
});
|
|
39216
39216
|
var NativeWorkState = Schema_exports.Struct({ id: Id, state: Schema_exports.Literals(["pending", "active", "completed", "cancelled", "unknown"]) });
|
|
39217
|
+
var RexText = Schema_exports.String.check(Schema_exports.isMinLength(1), Schema_exports.isMaxLength(4096), Schema_exports.makeFilter(
|
|
39218
|
+
(value5) => /[\u0000-\u001f\u007f-\u009f]/u.test(value5) ? "Control characters are forbidden" : void 0
|
|
39219
|
+
));
|
|
39220
|
+
var ProgramStatus = Schema_exports.Union([
|
|
39221
|
+
Schema_exports.Struct({ state: Schema_exports.Literals(["idle", "working", "done", "error"]) }),
|
|
39222
|
+
Schema_exports.Struct({ state: Schema_exports.Literal("blocked"), kind: Schema_exports.Literals(["permission", "question", "auth"]) })
|
|
39223
|
+
]);
|
|
39217
39224
|
var SessionPresence = Schema_exports.Struct({
|
|
39218
39225
|
ref: SessionRef,
|
|
39219
39226
|
observedAt: Timestamp,
|
|
@@ -39234,7 +39241,8 @@ var SessionPresence = Schema_exports.Struct({
|
|
|
39234
39241
|
agent: Schema_exports.optional(Id),
|
|
39235
39242
|
cwd: Schema_exports.optional(Path3),
|
|
39236
39243
|
repository: Schema_exports.optional(Path3),
|
|
39237
|
-
rex: Schema_exports.optional(Schema_exports.Struct({
|
|
39244
|
+
rex: Schema_exports.optional(Schema_exports.Struct({ serverAddress: Schema_exports.optional(RexText), blockId: Schema_exports.optional(RexText), sessionId: Schema_exports.optional(RexText) })),
|
|
39245
|
+
programStatus: Schema_exports.optional(ProgramStatus),
|
|
39238
39246
|
processes: Schema_exports.optional(bounded3(ProcessIdentity, 128)),
|
|
39239
39247
|
activity: Schema_exports.optional(Schema_exports.Struct({
|
|
39240
39248
|
state: Schema_exports.Literals(["idle", "working", "waiting", "stopped", "unknown"]),
|
|
@@ -72016,6 +72024,40 @@ var observeResources = (roots) => Effect_exports.gen(function* () {
|
|
|
72016
72024
|
};
|
|
72017
72025
|
});
|
|
72018
72026
|
|
|
72027
|
+
// src/rex.ts
|
|
72028
|
+
import { randomUUID } from "node:crypto";
|
|
72029
|
+
var readRexContext = Effect_exports.gen(function* () {
|
|
72030
|
+
const serverAddress = yield* Config_exports.String("REX_SERVER").pipe(Config_exports.withDefault(""));
|
|
72031
|
+
const sessionId = yield* Config_exports.String("REX_SESSION").pipe(Config_exports.withDefault(""));
|
|
72032
|
+
const blockId = yield* Config_exports.String("REX_BLOCK").pipe(Config_exports.withDefault(""));
|
|
72033
|
+
return serverAddress || sessionId || blockId ? {
|
|
72034
|
+
...serverAddress ? { serverAddress } : {},
|
|
72035
|
+
...sessionId ? { sessionId } : {},
|
|
72036
|
+
...blockId ? { blockId } : {}
|
|
72037
|
+
} : void 0;
|
|
72038
|
+
});
|
|
72039
|
+
var rexSequence = (id2, status) => {
|
|
72040
|
+
if (id2.length > 128 || id2.split("/").length > 8 || !/^[A-Za-z0-9_.+-]{1,32}(?:\/[A-Za-z0-9_.+-]{1,32})*$/u.test(id2)) return "";
|
|
72041
|
+
return `\x1B]7501;id=${id2}:state=${status?.state ?? "clear"}:app=coordinator:title=Q29vcmRpbmF0b3I=${status?.state === "blocked" ? `:kind=${status.kind}` : ""}\x1B\\`;
|
|
72042
|
+
};
|
|
72043
|
+
var createRexReporter = (write2) => {
|
|
72044
|
+
const id2 = `coordinator/${randomUUID().replaceAll("-", "")}`;
|
|
72045
|
+
let previous;
|
|
72046
|
+
return {
|
|
72047
|
+
update: (status, terminal) => {
|
|
72048
|
+
if (!terminal) return;
|
|
72049
|
+
const sequence = rexSequence(id2, status);
|
|
72050
|
+
if (previous === sequence || previous === void 0 && status === void 0) return;
|
|
72051
|
+
write2(sequence);
|
|
72052
|
+
previous = sequence;
|
|
72053
|
+
},
|
|
72054
|
+
close: (terminal) => {
|
|
72055
|
+
if (terminal && previous !== void 0 && !/:state=(?:done|error):/u.test(previous)) write2(rexSequence(id2, void 0));
|
|
72056
|
+
previous = void 0;
|
|
72057
|
+
}
|
|
72058
|
+
};
|
|
72059
|
+
};
|
|
72060
|
+
|
|
72019
72061
|
// src/native.ts
|
|
72020
72062
|
var NativeError = class extends Data_exports.TaggedError("NativeError") {
|
|
72021
72063
|
};
|
|
@@ -72285,6 +72327,8 @@ var Cache = Schema_exports.Struct({ version: Schema_exports.Literal(2), kind: Sc
|
|
|
72285
72327
|
var ClaudeRoster = Schema_exports.Array(Schema_exports.Struct({ sessionId: Schema_exports.optional(Id2), id: Schema_exports.optional(Id2), pid: Schema_exports.optional(Schema_exports.Int), cwd: Schema_exports.optional(Schema_exports.String), kind: Schema_exports.optional(Id2), status: Schema_exports.optional(Schema_exports.Literals(["busy", "waiting", "idle"])), waitingFor: Schema_exports.optional(Schema_exports.String) })).check(Schema_exports.isMaxLength(256));
|
|
72286
72328
|
|
|
72287
72329
|
// src/plugin.ts
|
|
72330
|
+
import { highlightCode } from "@oh-my-pi/pi-coding-agent";
|
|
72331
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
72288
72332
|
var provenance = (source, scope3, scopeDescription, observedAt, nativeUpdatedAt = null) => ({
|
|
72289
72333
|
source,
|
|
72290
72334
|
scope: scope3,
|
|
@@ -72300,8 +72344,10 @@ var provenance = (source, scope3, scopeDescription, observedAt, nativeUpdatedAt
|
|
|
72300
72344
|
var createNativeObserver = (harness, machineId, processes = []) => Effect_exports.gen(function* () {
|
|
72301
72345
|
const host = yield* Host2;
|
|
72302
72346
|
const clock = yield* Clock_exports.Clock;
|
|
72303
|
-
const
|
|
72304
|
-
|
|
72347
|
+
const rex = yield* readRexContext;
|
|
72348
|
+
let programStatus;
|
|
72349
|
+
let userPrompt;
|
|
72350
|
+
let stopReason;
|
|
72305
72351
|
let current;
|
|
72306
72352
|
let identity2;
|
|
72307
72353
|
let lastActivityAt;
|
|
@@ -72313,6 +72359,9 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect_export
|
|
|
72313
72359
|
const key = JSON.stringify([ctx.sessionManager.getSessionId(), harness === "omp" ? ctx.agent?.id : void 0]);
|
|
72314
72360
|
if (identity2 !== key) {
|
|
72315
72361
|
latest = void 0;
|
|
72362
|
+
programStatus = void 0;
|
|
72363
|
+
userPrompt = void 0;
|
|
72364
|
+
stopReason = void 0;
|
|
72316
72365
|
activeTools.clear();
|
|
72317
72366
|
identity2 = key;
|
|
72318
72367
|
}
|
|
@@ -72321,6 +72370,24 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect_export
|
|
|
72321
72370
|
if (event?.type === "tool_execution_start" && event.toolCallId && event.toolName) activeTools.set(event.toolCallId, event.toolName);
|
|
72322
72371
|
if (event?.type === "tool_execution_end" && event.toolCallId) activeTools.delete(event.toolCallId);
|
|
72323
72372
|
if (event?.type === "agent_end") activeTools.clear();
|
|
72373
|
+
if (event?.type === "agent_start") {
|
|
72374
|
+
programStatus = { state: "working" };
|
|
72375
|
+
stopReason = void 0;
|
|
72376
|
+
}
|
|
72377
|
+
if (event?.type === "turn_start") {
|
|
72378
|
+
programStatus = { state: "working" };
|
|
72379
|
+
stopReason = void 0;
|
|
72380
|
+
}
|
|
72381
|
+
if (event?.type === "tool_execution_start") programStatus = { state: "working" };
|
|
72382
|
+
if (event?.message?.role === "assistant" && event.message.stopReason) stopReason = event.message.stopReason;
|
|
72383
|
+
if (event?.type === "agent_end") stopReason = event.messages?.findLast((message2) => message2.role === "assistant")?.stopReason ?? stopReason;
|
|
72384
|
+
if (harness === "pi" && event?.type === "agent_before_settle") stopReason = event.outcome === "completed" ? "stop" : event.outcome;
|
|
72385
|
+
if (event?.type === "agent_settled" || harness !== "pi" && event?.type === "agent_end") {
|
|
72386
|
+
programStatus = event.willContinue ? { state: "working" } : stopReason === "aborted" ? { state: "idle" } : stopReason === "error" ? { state: "error" } : stopReason === "stop" || stopReason === "length" ? { state: "done" } : void 0;
|
|
72387
|
+
} else if (event?.type === "agent_end") programStatus = { state: "working" };
|
|
72388
|
+
if (harness === "pi" && event?.type === "ui_prompt_start") userPrompt = { state: "blocked", kind: "question" };
|
|
72389
|
+
if (harness === "pi" && event?.type === "ui_prompt_end") userPrompt = void 0;
|
|
72390
|
+
if ((event === void 0 || event.type === "session_start" || event.type === "session_switch") && programStatus === void 0) programStatus = { state: ctx.isIdle() ? "idle" : "working" };
|
|
72324
72391
|
const message = event?.message;
|
|
72325
72392
|
if (message?.role === "assistant" && message.usage) {
|
|
72326
72393
|
const u = message.usage;
|
|
@@ -72340,6 +72407,7 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect_export
|
|
|
72340
72407
|
const presence = (now2 = clock.currentTimeMillisUnsafe()) => {
|
|
72341
72408
|
if (!current) return void 0;
|
|
72342
72409
|
const currentToolName = activeTools.values().next().value;
|
|
72410
|
+
const status = userPrompt ?? programStatus;
|
|
72343
72411
|
return {
|
|
72344
72412
|
ref: { machineId, harness, nativeSessionId: current.sessionManager.getSessionId(), ...harness === "omp" && current.agent?.id ? { nativeAgentId: current.agent.id } : {} },
|
|
72345
72413
|
observedAt: now2,
|
|
@@ -72348,13 +72416,15 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect_export
|
|
|
72348
72416
|
processes,
|
|
72349
72417
|
machineName,
|
|
72350
72418
|
...os ? { os } : {},
|
|
72351
|
-
...
|
|
72419
|
+
...rex ? { rex } : {},
|
|
72420
|
+
...status ? { programStatus: status } : {},
|
|
72352
72421
|
...current.model ? { provider: current.model.provider, model: current.model.id } : {},
|
|
72353
72422
|
...current.agent ? { kind: current.agent.kind, ...current.agent.name ? { agent: current.agent.name } : {} } : {},
|
|
72354
72423
|
activity: {
|
|
72355
|
-
state: current.isIdle() ? "idle" : "working",
|
|
72424
|
+
state: userPrompt ? "waiting" : current.isIdle() ? "idle" : "working",
|
|
72356
72425
|
streaming: !current.isIdle(),
|
|
72357
|
-
...currentToolName === void 0 ? {} : { currentToolName
|
|
72426
|
+
...currentToolName === void 0 ? {} : { currentToolName },
|
|
72427
|
+
...userPrompt ? { wait: "user" } : currentToolName === void 0 ? {} : { wait: "tool" }
|
|
72358
72428
|
}
|
|
72359
72429
|
};
|
|
72360
72430
|
};
|
|
@@ -72458,6 +72528,9 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect_export
|
|
|
72458
72528
|
current = void 0;
|
|
72459
72529
|
identity2 = void 0;
|
|
72460
72530
|
latest = void 0;
|
|
72531
|
+
programStatus = void 0;
|
|
72532
|
+
userPrompt = void 0;
|
|
72533
|
+
stopReason = void 0;
|
|
72461
72534
|
lastActivityAt = void 0;
|
|
72462
72535
|
activeTools.clear();
|
|
72463
72536
|
} };
|
|
@@ -72486,6 +72559,53 @@ function plugin(host) {
|
|
|
72486
72559
|
let observer;
|
|
72487
72560
|
let starting;
|
|
72488
72561
|
let stopped = false;
|
|
72562
|
+
const rexReporter = harness === "pi" ? createRexReporter((sequence) => process.stdout.write(sequence)) : void 0;
|
|
72563
|
+
let terminal = false;
|
|
72564
|
+
host.registerMessageRenderer("coordinator-list", (message) => {
|
|
72565
|
+
if (typeof message.content !== "string") return void 0;
|
|
72566
|
+
return new Text(message.details === void 0 ? message.content : highlightCode(message.content, "json").join("\n"), 0, 0);
|
|
72567
|
+
});
|
|
72568
|
+
host.registerCommand("coordinator", {
|
|
72569
|
+
description: "List visible sessions, their machines, and native metadata",
|
|
72570
|
+
handler: async (args2) => {
|
|
72571
|
+
if (args2.trim() !== "list") {
|
|
72572
|
+
host.sendMessage({ customType: "coordinator-list", content: "Usage: /coordinator list", display: true }, { triggerTurn: false });
|
|
72573
|
+
return;
|
|
72574
|
+
}
|
|
72575
|
+
const exit3 = await runtime2.runPromiseExit(Effect_exports.gen(function* () {
|
|
72576
|
+
const activeConfig = config2 ?? (yield* loadConnectorConfig());
|
|
72577
|
+
if (!activeConfig) return yield* new NativeError({ operation: "Connector configuration required" });
|
|
72578
|
+
const token = yield* readConnectorToken(activeConfig);
|
|
72579
|
+
const mcp = yield* Mcp;
|
|
72580
|
+
const sessions = [];
|
|
72581
|
+
let cursor;
|
|
72582
|
+
do {
|
|
72583
|
+
const page = yield* mcp.call(activeConfig.inspectorUrl, token, InspectionTools.list, { limit: 100, ...cursor === void 0 ? {} : { cursor } });
|
|
72584
|
+
for (const session of page.sessions) sessions.push(session);
|
|
72585
|
+
cursor = page.nextCursor ?? void 0;
|
|
72586
|
+
} while (cursor !== void 0);
|
|
72587
|
+
return sessions;
|
|
72588
|
+
}));
|
|
72589
|
+
if (stopped) return;
|
|
72590
|
+
if (exit3._tag === "Failure") {
|
|
72591
|
+
const error3 = Cause_exports.findErrorOption(exit3.cause);
|
|
72592
|
+
const reason = error3._tag === "Some" ? error3.value._tag === "HttpError" ? `HTTP ${error3.value.status}` : error3.value._tag : "unexpected failure";
|
|
72593
|
+
host.sendMessage({
|
|
72594
|
+
customType: "coordinator-list",
|
|
72595
|
+
content: `Coordinator list unavailable (${reason}). Check inspectorUrl, your access token, and API reachability.`,
|
|
72596
|
+
display: true
|
|
72597
|
+
}, { triggerTurn: false });
|
|
72598
|
+
return;
|
|
72599
|
+
}
|
|
72600
|
+
const result5 = { sessions: exit3.value };
|
|
72601
|
+
host.sendMessage({
|
|
72602
|
+
customType: "coordinator-list",
|
|
72603
|
+
content: Formatter_exports.formatJson(result5, { space: 2 }),
|
|
72604
|
+
display: true,
|
|
72605
|
+
details: result5
|
|
72606
|
+
}, { triggerTurn: false });
|
|
72607
|
+
}
|
|
72608
|
+
});
|
|
72489
72609
|
if (harness === "pi") {
|
|
72490
72610
|
const register2 = (descriptor) => {
|
|
72491
72611
|
const document = Schema_exports.toJsonSchemaDocument(Schema_exports.toCodecJson(descriptor.input), { onExcessProperty: "error" });
|
|
@@ -72540,6 +72660,8 @@ function plugin(host) {
|
|
|
72540
72660
|
const processIdentity = yield* identifyProcess(host2.pid);
|
|
72541
72661
|
observer = yield* createNativeObserver(harness, nativeConfig.machineId, processIdentity.status === "available" ? [{ pid: host2.pid, startIdentity: processIdentity.startTime, ownership: harness === "omp" ? "shared" : "owned" }] : []);
|
|
72542
72662
|
observer.update(ctx);
|
|
72663
|
+
terminal = ctx.mode === "tui" && ctx.hasUI && process.stdout.isTTY === true;
|
|
72664
|
+
rexReporter?.update(observer.presence()?.programStatus, terminal);
|
|
72543
72665
|
if (harness === "omp" && ctx.hasUI && ctx.mode === "tui") yield* registerCoordinateKeyword;
|
|
72544
72666
|
runtime2.runFork(connectNative(nativeConfig, {
|
|
72545
72667
|
presence: () => Effect_exports.sync(() => {
|
|
@@ -72571,16 +72693,23 @@ function plugin(host) {
|
|
|
72571
72693
|
}
|
|
72572
72694
|
});
|
|
72573
72695
|
const events = ["session_start", "session_switch", "agent_start", "agent_end", "turn_start", "turn_end", "message_end", "tool_execution_start", "tool_execution_end"];
|
|
72696
|
+
if (harness === "pi") events.push("agent_before_settle", "agent_settled", "ui_prompt_start", "ui_prompt_end");
|
|
72574
72697
|
for (const name of events) {
|
|
72575
72698
|
host.on(name, async (event, ctx) => {
|
|
72576
72699
|
if (stopped) return;
|
|
72577
72700
|
await start(ctx);
|
|
72578
72701
|
if (stopped || !observer) return;
|
|
72579
|
-
await runtime2.runPromiseExit(Effect_exports.sync(() =>
|
|
72702
|
+
await runtime2.runPromiseExit(Effect_exports.sync(() => {
|
|
72703
|
+
if (name === "session_switch") rexReporter?.update(void 0, terminal);
|
|
72704
|
+
observer?.update(ctx, event);
|
|
72705
|
+
terminal = ctx.mode === "tui" && ctx.hasUI && process.stdout.isTTY === true;
|
|
72706
|
+
rexReporter?.update(observer?.presence()?.programStatus, terminal);
|
|
72707
|
+
}).pipe(Effect_exports.tapCause(reportCause(ctx, "native update"))));
|
|
72580
72708
|
});
|
|
72581
72709
|
}
|
|
72582
72710
|
host.on("session_shutdown", async () => {
|
|
72583
72711
|
stopped = true;
|
|
72712
|
+
rexReporter?.close(terminal);
|
|
72584
72713
|
try {
|
|
72585
72714
|
await runtime2.dispose();
|
|
72586
72715
|
} finally {
|