remote-pi 0.4.0 → 0.4.2
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/dist/actions/handlers.d.ts +2 -2
- package/dist/actions/registry.d.ts +1 -1
- package/dist/actions/registry.js +1 -1
- package/dist/actions/registry.js.map +1 -1
- package/dist/daemon/control_protocol.d.ts +1 -1
- package/dist/daemon/control_protocol.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +61 -59
- package/dist/index.js.map +1 -1
- package/dist/mcp/mesh_server.js +170 -44
- package/dist/mcp/mesh_server.js.map +1 -1
- package/dist/protocol/types.d.ts +1 -1
- package/dist/session/broker.d.ts +18 -22
- package/dist/session/broker.js +10 -51
- package/dist/session/broker.js.map +1 -1
- package/dist/session/broker_remote.js +5 -5
- package/dist/session/broker_remote.js.map +1 -1
- package/dist/session/cwd_lock.js +11 -4
- package/dist/session/cwd_lock.js.map +1 -1
- package/dist/session/mesh_node.d.ts +11 -0
- package/dist/session/mesh_node.js +81 -2
- package/dist/session/mesh_node.js.map +1 -1
- package/dist/session/tools.d.ts +1 -1
- package/dist/session/tools.js +17 -11
- package/dist/session/tools.js.map +1 -1
- package/package.json +2 -2
- package/skills/agent-network/SKILL.md +163 -280
- package/skills/claude-agent-network/SKILL.md +0 -239
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
import type { ClientMessage, ServerMessage, WireModel } from "../protocol/types.js";
|
|
24
24
|
/**
|
|
25
25
|
* Structural subset of the SDK's `Model<Api>` interface (defined in
|
|
26
|
-
* `@
|
|
27
|
-
* `@
|
|
26
|
+
* `@earendil-works/pi-ai`, which is a transitive dep — not re-exported by
|
|
27
|
+
* `@earendil-works/pi-coding-agent`'s main entry). Capturing just the
|
|
28
28
|
* fields we touch keeps the handler decoupled from the SDK's full Model
|
|
29
29
|
* surface and avoids a direct dep on `pi-ai`.
|
|
30
30
|
*/
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* imports, no internal-state coupling — see the probe note in
|
|
15
15
|
* `plan/28-pi-commands.md` Wave 0.
|
|
16
16
|
*/
|
|
17
|
-
import { ModelRegistry } from "@
|
|
17
|
+
import { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
/**
|
|
19
19
|
* Lazily instantiate the shared `ModelRegistry`. Subsequent calls return
|
|
20
20
|
* the same instance — keep it cached so `refresh()` cycles are cheap and
|
package/dist/actions/registry.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* imports, no internal-state coupling — see the probe note in
|
|
15
15
|
* `plan/28-pi-commands.md` Wave 0.
|
|
16
16
|
*/
|
|
17
|
-
import { ModelRegistry, AuthStorage } from "@
|
|
17
|
+
import { ModelRegistry, AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
let _registry = null;
|
|
19
19
|
/**
|
|
20
20
|
* Lazily instantiate the shared `ModelRegistry`. Subsequent calls return
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/actions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/actions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE7E,IAAI,SAAS,GAAyB,IAAI,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,2BAA2B;IACzC,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Plan/26 W2. The Pi RPC protocol (`pi --mode rpc`) used by the daemon
|
|
10
10
|
* children themselves is a separate contract — see
|
|
11
|
-
* `node_modules/@
|
|
11
|
+
* `node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts`.
|
|
12
12
|
* This file is strictly the supervisor's own control plane.
|
|
13
13
|
*/
|
|
14
14
|
/** Per-daemon runtime state observable through the supervisor. */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Plan/26 W2. The Pi RPC protocol (`pi --mode rpc`) used by the daemon
|
|
10
10
|
* children themselves is a separate contract — see
|
|
11
|
-
* `node_modules/@
|
|
11
|
+
* `node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts`.
|
|
12
12
|
* This file is strictly the supervisor's own control plane.
|
|
13
13
|
*/
|
|
14
14
|
// ── Serialization helpers ────────────────────────────────────────────────────
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* AgentBridge (src/session/agent_bridge.ts) remains the tested, mockable unit
|
|
30
30
|
* for integration tests.
|
|
31
31
|
*/
|
|
32
|
-
import type { ExtensionContext, ExtensionFactory } from "@
|
|
32
|
+
import type { ExtensionContext, ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
33
33
|
import type { ClientMessage, SessionHistoryEvent } from "./protocol/types.js";
|
|
34
34
|
import { PlainPeerChannel } from "./transport/peer_channel.js";
|
|
35
35
|
export type RemoteState = "idle" | "started";
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* for integration tests.
|
|
31
31
|
*/
|
|
32
32
|
import { randomUUID } from "node:crypto";
|
|
33
|
-
import { SettingsManager } from "@
|
|
33
|
+
import { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
34
34
|
import { buildQRUri, qrSession, renderQRAscii } from "./pairing/qr.js";
|
|
35
35
|
import { addPeer, getOrCreateEd25519Keypair, listOwnerPubkeys, listPeers, removePeer, } from "./pairing/storage.js";
|
|
36
36
|
import { MeshClient } from "./mesh/client.js";
|
|
@@ -56,7 +56,7 @@ import { fileURLToPath } from "node:url";
|
|
|
56
56
|
import { mkdirSync, copyFileSync, existsSync, unlinkSync, readFileSync, writeFileSync, realpathSync } from "node:fs";
|
|
57
57
|
import { createInterface } from "node:readline";
|
|
58
58
|
import { spawnSync } from "node:child_process";
|
|
59
|
-
import { hostname
|
|
59
|
+
import { hostname } from "node:os";
|
|
60
60
|
import { kDefaultRelayUrl, resolveRelayUrl, saveConfig, isValidRelayUrl, isWebSocketScheme, toWebSocketUrl, } from "./config.js";
|
|
61
61
|
let _state = "idle";
|
|
62
62
|
let _relay = null;
|
|
@@ -930,11 +930,10 @@ const extension = (pi) => {
|
|
|
930
930
|
_broadcastToActive({ type: "agent_done", in_reply_to: _currentTurnId });
|
|
931
931
|
_currentTurnId = null;
|
|
932
932
|
});
|
|
933
|
-
// plan/
|
|
934
|
-
//
|
|
935
|
-
//
|
|
936
|
-
//
|
|
937
|
-
// next turn boundary. Skip silently when no mesh session is joined.
|
|
933
|
+
// plan/34: the broker no longer gates delivery on busy state, so we no
|
|
934
|
+
// longer notify it of turn lifecycle. Working state is still published as
|
|
935
|
+
// room_meta over the relay (plan/32) below — that's independent of the
|
|
936
|
+
// broker and drives the app's working indicator.
|
|
938
937
|
pi.on("turn_start", (_event, ctx) => {
|
|
939
938
|
// Late model hydration: if the model was still unknown at connect (resolved
|
|
940
939
|
// lazily by the SDK), grab it on the first turn and fan it out — so a daemon
|
|
@@ -955,10 +954,6 @@ const extension = (pi) => {
|
|
|
955
954
|
if (_relay && _myRoomId) {
|
|
956
955
|
_relay.sendControl({ type: "room_meta_update", room_id: _myRoomId, meta: { working: true } });
|
|
957
956
|
}
|
|
958
|
-
if (!_meshNode)
|
|
959
|
-
return;
|
|
960
|
-
void _meshNode.send("broker", { type: "turn_state", busy: true })
|
|
961
|
-
.catch(() => { });
|
|
962
957
|
});
|
|
963
958
|
pi.on("turn_end", () => {
|
|
964
959
|
// Plan/32 Part B: publish working=false as room_meta (raw, no debounce).
|
|
@@ -967,10 +962,6 @@ const extension = (pi) => {
|
|
|
967
962
|
if (_relay && _myRoomId) {
|
|
968
963
|
_relay.sendControl({ type: "room_meta_update", room_id: _myRoomId, meta: { working: false } });
|
|
969
964
|
}
|
|
970
|
-
if (!_meshNode)
|
|
971
|
-
return;
|
|
972
|
-
void _meshNode.send("broker", { type: "turn_state", busy: false })
|
|
973
|
-
.catch(() => { });
|
|
974
965
|
});
|
|
975
966
|
// Plan/32: compaction feedback. compact() doesn't run a turn, so bracket it
|
|
976
967
|
// with working=true/false here. Returning void = no veto → default
|
|
@@ -2650,34 +2641,27 @@ if (_isDirectRun()) {
|
|
|
2650
2641
|
}
|
|
2651
2642
|
// ── `remote-pi claude` — launch Claude Code connected to the mesh ─────────────
|
|
2652
2643
|
/**
|
|
2653
|
-
*
|
|
2654
|
-
* (
|
|
2655
|
-
*
|
|
2656
|
-
*
|
|
2657
|
-
*
|
|
2658
|
-
* MCP `instructions` + tool descriptions still give Claude the basics).
|
|
2659
|
-
*
|
|
2660
|
-
* Mirrors the Pi extension's `_deployAgentNetworkSkill()` (which targets
|
|
2661
|
-
* `~/.pi/remote/skills/`): same concept, each runtime's own skills dir.
|
|
2644
|
+
* Resolve the packaged agent-network skill path
|
|
2645
|
+
* (`<pkgRoot>/skills/agent-network/SKILL.md`). Single source of truth shared
|
|
2646
|
+
* by both runtimes: Pi discovers it via `resources_discover`, and the Claude
|
|
2647
|
+
* launcher injects it as a system prompt (see `_cmdClaudeCli`). Returns null
|
|
2648
|
+
* if the file is missing (e.g. running before `pnpm build`).
|
|
2662
2649
|
*/
|
|
2663
|
-
function
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
if (!existsSync(template))
|
|
2669
|
-
return;
|
|
2670
|
-
const destDir = join(homedir(), ".claude", "skills", "agent-network");
|
|
2671
|
-
mkdirSync(destDir, { recursive: true });
|
|
2672
|
-
copyFileSync(template, join(destDir, "SKILL.md"));
|
|
2673
|
-
}
|
|
2674
|
-
catch {
|
|
2675
|
-
/* best-effort — never block the launch on skill deploy */
|
|
2676
|
-
}
|
|
2650
|
+
function _agentNetworkSkillPath() {
|
|
2651
|
+
const here = fileURLToPath(import.meta.url); // dist/index.js (or src/index.ts via tsx)
|
|
2652
|
+
const pkgRoot = dirname(dirname(here)); // package root (dist → ..; src → ..)
|
|
2653
|
+
const skill = join(pkgRoot, "skills", "agent-network", "SKILL.md");
|
|
2654
|
+
return existsSync(skill) ? skill : null;
|
|
2677
2655
|
}
|
|
2678
2656
|
async function _cmdClaudeCli(args) {
|
|
2679
|
-
//
|
|
2680
|
-
|
|
2657
|
+
// Contract: `remote-pi claude [cwd] [claude-flags...]`. The optional cwd is
|
|
2658
|
+
// ONLY the leading positional (first token, not a flag); everything after it
|
|
2659
|
+
// is forwarded verbatim to the `claude` binary (e.g. `--resume`, `-c`,
|
|
2660
|
+
// `-p "prompt"`). Restricting cwd to the leading token avoids mistaking a
|
|
2661
|
+
// flag's value (e.g. the id in `--resume <id>`) for the cwd.
|
|
2662
|
+
const hasCwdArg = args.length > 0 && !args[0].startsWith("-");
|
|
2663
|
+
const targetCwd = hasCwdArg ? args[0] : process.cwd();
|
|
2664
|
+
const passthroughArgs = hasCwdArg ? args.slice(1) : args;
|
|
2681
2665
|
// Wizard when no local config exists
|
|
2682
2666
|
if (!localConfigExists(targetCwd)) {
|
|
2683
2667
|
const suggested = defaultAgentName(targetCwd);
|
|
@@ -2698,14 +2682,6 @@ async function _cmdClaudeCli(args) {
|
|
|
2698
2682
|
}
|
|
2699
2683
|
const absCwd = resolve(targetCwd);
|
|
2700
2684
|
const SERVER_NAME = "remote-pi-mesh";
|
|
2701
|
-
// Deploy the agent-network skill so Claude knows HOW to use the mesh tools
|
|
2702
|
-
// (when to call get_messages, ACK statuses, cross-PC addressing, replies).
|
|
2703
|
-
// Skills load only from disk — an MCP server can't inject one — so we write
|
|
2704
|
-
// it to the user-global Claude skills dir, mirroring how the Pi extension
|
|
2705
|
-
// deploys its own agent-network skill to ~/.pi/remote/skills/. The skill's
|
|
2706
|
-
// `description` self-gates activation to mesh contexts, so a global deploy
|
|
2707
|
-
// doesn't pollute unrelated Claude sessions.
|
|
2708
|
-
_deployClaudeMeshSkill();
|
|
2709
2685
|
// The channel feature (claude/channel push) only recognizes MCP servers
|
|
2710
2686
|
// registered in one of the persistent scopes Claude Code enumerates:
|
|
2711
2687
|
// enterprise / user / project / local. A server passed via `--mcp-config`
|
|
@@ -2713,30 +2689,42 @@ async function _cmdClaudeCli(args) {
|
|
|
2713
2689
|
// scopes, so `--dangerously-load-development-channels server:<name>` can't
|
|
2714
2690
|
// match it ("no MCP server configured with that name").
|
|
2715
2691
|
//
|
|
2716
|
-
// We therefore register in the **local** scope
|
|
2717
|
-
//
|
|
2718
|
-
//
|
|
2719
|
-
//
|
|
2692
|
+
// We therefore register in the **local** scope, stored in `~/.claude.json`
|
|
2693
|
+
// (NOT written into the project dir, NOT committed to VCS, NOT global) so the
|
|
2694
|
+
// dev-channel flag matches it. Caveat that drives the next decision: `-s
|
|
2695
|
+
// local` is keyed by the **git repo root** and shares ONE entry per server
|
|
2696
|
+
// name across every subdir of the repo — a same-name re-add overwrites it.
|
|
2720
2697
|
//
|
|
2721
2698
|
// remove-then-add makes it idempotent and refreshes the path if the
|
|
2722
2699
|
// extension moved (Pi can reinstall to a new hash dir on upgrade).
|
|
2700
|
+
//
|
|
2701
|
+
// Crucially we do NOT bake `--cwd <absCwd>` into the registration. In a
|
|
2702
|
+
// monorepo (app/, relay/, site/, …) every subproject is its own agent, but
|
|
2703
|
+
// they'd all share this single repo-keyed entry — so whichever agent ran
|
|
2704
|
+
// `remote-pi claude` LAST would leak its cwd to every other session, and the
|
|
2705
|
+
// repo-root session would then spawn a peer that locks a subdir another agent
|
|
2706
|
+
// owns ("Failed to connect"). Instead, the server resolves its folder from
|
|
2707
|
+
// its own `process.cwd()`, which Claude sets to the directory each `claude`
|
|
2708
|
+
// session was launched in (verified empirically — NOT the git root, NOT
|
|
2709
|
+
// CLAUDE_PROJECT_DIR). We launch claude with `cwd: absCwd` below, so a single
|
|
2710
|
+
// shared registration self-identifies as the right agent in every session.
|
|
2723
2711
|
spawnSync("claude", ["mcp", "remove", SERVER_NAME, "-s", "local"], {
|
|
2724
2712
|
cwd: absCwd, stdio: "ignore", shell: false,
|
|
2725
2713
|
});
|
|
2726
2714
|
const add = spawnSync("claude", [
|
|
2727
2715
|
"mcp", "add", SERVER_NAME, "-s", "local", "--",
|
|
2728
|
-
process.execPath, meshServerPath,
|
|
2716
|
+
process.execPath, meshServerPath,
|
|
2729
2717
|
], { cwd: absCwd, stdio: ["ignore", "pipe", "pipe"], shell: false, encoding: "utf8" });
|
|
2730
2718
|
if (add.status !== 0) {
|
|
2731
2719
|
console.log(`[remote-pi] failed to register MCP server: ${add.stderr || add.stdout}`);
|
|
2732
2720
|
process.exit(1);
|
|
2733
2721
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2722
|
+
// Inject the agent-network protocol as a system prompt instead of deploying a
|
|
2723
|
+
// skill file into ~/.claude. Anyone running `remote-pi claude` is here to use
|
|
2724
|
+
// the mesh, so load the protocol unconditionally — no lazy skill gating, no
|
|
2725
|
+
// global skills-dir pollution, and the packaged file is the single source of
|
|
2726
|
+
// truth shared with the Pi runtime. Skipped only if the file is missing.
|
|
2727
|
+
const skillPath = _agentNetworkSkillPath();
|
|
2740
2728
|
// Launch flags:
|
|
2741
2729
|
// --dangerously-load-development-channels TAG — enable claude/channel push
|
|
2742
2730
|
// for our local (non-allowlisted) server, so incoming mesh messages
|
|
@@ -2745,9 +2733,23 @@ async function _cmdClaudeCli(args) {
|
|
|
2745
2733
|
// (`plugin:<name>@<marketplace>` is the plugin form). Shows a one-time
|
|
2746
2734
|
// confirmation dialog at startup.
|
|
2747
2735
|
// --dangerously-skip-permissions — auto-approve tool calls
|
|
2736
|
+
// --append-system-prompt-file=<skill> — load the mesh protocol
|
|
2737
|
+
// `--append-system-prompt-file` uses the glued `--flag=value` form (a SINGLE
|
|
2738
|
+
// argv token) on purpose: tools that restore a session by capturing and
|
|
2739
|
+
// replaying the live process's argv (e.g. cmux) drop the TRAILING token,
|
|
2740
|
+
// which here was the skill path — leaving a dangling `--append-system-prompt-file`
|
|
2741
|
+
// → `claude` aborts with "argument missing" and the session never comes back.
|
|
2742
|
+
// As one token, the worst case is the whole flag being dropped: claude still
|
|
2743
|
+
// starts (just without the injected protocol), which is recoverable instead
|
|
2744
|
+
// of fatal. (The channels flag stays a separate pair — it's never last, so
|
|
2745
|
+
// it isn't affected, and we don't risk a parser that may not accept `=`.)
|
|
2746
|
+
// Any extra args the user passed (e.g. `--resume`, `-c`) are appended last so
|
|
2747
|
+
// they reach the claude binary; ours come first as sensible defaults.
|
|
2748
2748
|
spawnSync("claude", [
|
|
2749
2749
|
"--dangerously-load-development-channels", `server:${SERVER_NAME}`,
|
|
2750
2750
|
"--dangerously-skip-permissions",
|
|
2751
|
+
...(skillPath ? [`--append-system-prompt-file=${skillPath}`] : []),
|
|
2752
|
+
...passthroughArgs,
|
|
2751
2753
|
], {
|
|
2752
2754
|
cwd: absCwd,
|
|
2753
2755
|
stdio: "inherit",
|