@jaggerxtrm/pi-extensions 0.7.21 → 0.7.22
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 +1 -1
- package/extensions/README.md +1 -1
- package/extensions/serena-pool/index.ts +110 -0
- package/extensions/serena-pool/package.json +23 -0
- package/extensions/sp-terminal-overlay/index.ts +7 -3
- package/package.json +1 -1
- package/src/extensions/serena-pool.ts +3 -0
- package/src/registry.ts +2 -0
package/README.md
CHANGED
|
@@ -47,4 +47,4 @@ After install, keep `.pi/settings.json` package wiring pointed at `npm:@jaggerxt
|
|
|
47
47
|
Notable bundled extensions include:
|
|
48
48
|
|
|
49
49
|
- `xtrm-ui` — XTRM Pi chrome, native tool summaries, selectable external tool chrome (`/xtrm-ui chrome background|box`).
|
|
50
|
-
- `sp-terminal-overlay` — `/sp-feed
|
|
50
|
+
- `sp-terminal-overlay` — `/sp-feed` streaming overlay, `/sp-ps` snapshot overlay, and `/xtrm-terminal` shell overlay for specialist monitoring.
|
package/extensions/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Streaming terminal-style overlay for specialist/process monitoring commands.
|
|
|
11
11
|
Commands:
|
|
12
12
|
|
|
13
13
|
- `/sp-feed [args]` — opens `sp feed -f [args]` in an overlay.
|
|
14
|
-
- `/sp-ps [args]` / `/xtrm-ps [args]` — opens `sp ps [args]`
|
|
14
|
+
- `/sp-ps [args]` / `/xtrm-ps [args]` — opens a one-shot `sp ps [args]` snapshot in an overlay; `--follow`/`-f` are stripped to avoid repaint loops.
|
|
15
15
|
- `/xtrm-terminal <command>` — opens an arbitrary shell command in an overlay.
|
|
16
16
|
|
|
17
17
|
Keys: `Esc`/`q` close, `r` restart, arrows/page keys scroll.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serena-pool — shared Serena daemon per repo root
|
|
3
|
+
*
|
|
4
|
+
* Sets SERENA_MCP_PORT to a deterministic port derived from the git root before
|
|
5
|
+
* pi-serena-tools session_start fires. If no Serena server is already listening
|
|
6
|
+
* on that port, spawns one (detached, persists after session ends).
|
|
7
|
+
*
|
|
8
|
+
* pi-serena-tools reads SERENA_MCP_PORT lazily in resolveSerenaPort(), finds the
|
|
9
|
+
* server already running, skips its own spawn, and stopServer() is a no-op since
|
|
10
|
+
* it never held serverProcess. No patches to pi-serena-tools needed.
|
|
11
|
+
*/
|
|
12
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { connect } from "node:net";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
const POOL_PORT_MIN = 40000;
|
|
18
|
+
const POOL_PORT_RANGE = 5000; // 40000–44999
|
|
19
|
+
const STARTUP_TIMEOUT_MS = 15_000;
|
|
20
|
+
const POLL_INTERVAL_MS = 300;
|
|
21
|
+
|
|
22
|
+
function hashToPort(s: string): number {
|
|
23
|
+
let h = 5381;
|
|
24
|
+
for (let i = 0; i < s.length; i++) {
|
|
25
|
+
h = ((h << 5) + h) ^ s.charCodeAt(i);
|
|
26
|
+
h = h >>> 0;
|
|
27
|
+
}
|
|
28
|
+
return POOL_PORT_MIN + (h % POOL_PORT_RANGE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getRepoRoot(cwd: string): string {
|
|
32
|
+
try {
|
|
33
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
34
|
+
cwd,
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
37
|
+
}).trim();
|
|
38
|
+
} catch {
|
|
39
|
+
return cwd;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isPortListening(port: number): Promise<boolean> {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const socket = connect({ host: "127.0.0.1", port }, () => {
|
|
46
|
+
socket.destroy();
|
|
47
|
+
resolve(true);
|
|
48
|
+
});
|
|
49
|
+
socket.on("error", () => resolve(false));
|
|
50
|
+
socket.setTimeout(500, () => {
|
|
51
|
+
socket.destroy();
|
|
52
|
+
resolve(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function waitForPort(port: number): Promise<boolean> {
|
|
58
|
+
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
if (await isPortListening(port)) return true;
|
|
61
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function ensureSerenaRunning(projectRoot: string, port: number): Promise<void> {
|
|
67
|
+
if (await isPortListening(port)) return;
|
|
68
|
+
|
|
69
|
+
const proc = spawn(
|
|
70
|
+
"uvx",
|
|
71
|
+
[
|
|
72
|
+
"--from", "git+https://github.com/oraios/serena",
|
|
73
|
+
"serena", "start-mcp-server",
|
|
74
|
+
"--transport", "streamable-http",
|
|
75
|
+
"--port", String(port),
|
|
76
|
+
"--project", projectRoot,
|
|
77
|
+
"--context", "agent",
|
|
78
|
+
],
|
|
79
|
+
{
|
|
80
|
+
cwd: projectRoot,
|
|
81
|
+
env: { ...process.env },
|
|
82
|
+
stdio: "ignore",
|
|
83
|
+
detached: true,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
proc.unref();
|
|
87
|
+
|
|
88
|
+
const ready = await waitForPort(port);
|
|
89
|
+
if (!ready) {
|
|
90
|
+
console.warn(`[serena-pool] Serena did not start on port ${port} within ${STARTUP_TIMEOUT_MS}ms`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default function registerSerenaPool(pi: ExtensionAPI) {
|
|
95
|
+
pi.on("session_start", async (_event: unknown, ctx: any) => {
|
|
96
|
+
const cwd: string = ctx?.cwd ?? process.cwd();
|
|
97
|
+
const projectRoot = getRepoRoot(cwd);
|
|
98
|
+
const port = hashToPort(projectRoot);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await ensureSerenaRunning(projectRoot, port);
|
|
102
|
+
process.env.SERENA_MCP_PORT = String(port);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.warn("[serena-pool] Error ensuring Serena running:", err);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// session_shutdown intentionally absent — server persists as daemon across sessions.
|
|
109
|
+
// Kill manually with: kill $(lsof -ti:PORT) or pkill -f "serena start-mcp-server"
|
|
110
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "serena-pool",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared Serena daemon per repo root — sets SERENA_MCP_PORT before pi-serena-tools fires",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"serena",
|
|
9
|
+
"mcp",
|
|
10
|
+
"pool"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "index.ts",
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@mariozechner/pi-coding-agent": "^0.56.0"
|
|
17
|
+
},
|
|
18
|
+
"pi": {
|
|
19
|
+
"extensions": [
|
|
20
|
+
"./index.ts"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -26,8 +26,12 @@ function resolveSpFeedCommand(args: string): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function resolveSpPsCommand(args: string): string {
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const snapshotArgs = args
|
|
30
|
+
.trim()
|
|
31
|
+
.split(/\s+/u)
|
|
32
|
+
.filter((part) => part && part !== "--follow" && part !== "-f")
|
|
33
|
+
.join(" ");
|
|
34
|
+
return snapshotArgs ? `sp ps ${snapshotArgs}` : "sp ps";
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
function openTerminalOverlay(ctx: ExtensionCommandContext, title: string, command: string): Promise<void> {
|
|
@@ -455,7 +459,7 @@ export default function spTerminalOverlayExtension(pi: ExtensionAPI): void {
|
|
|
455
459
|
});
|
|
456
460
|
|
|
457
461
|
pi.registerCommand("sp-ps", {
|
|
458
|
-
description: "Open a
|
|
462
|
+
description: "Open a snapshot overlay for `sp ps`",
|
|
459
463
|
handler: async (args, ctx) => {
|
|
460
464
|
await openTerminalOverlay(ctx, "sp ps", resolveSpPsCommand(args));
|
|
461
465
|
},
|
package/package.json
CHANGED
package/src/registry.ts
CHANGED
|
@@ -9,6 +9,7 @@ import customProviderQwenCliExtension from "./extensions/custom-provider-qwen-cl
|
|
|
9
9
|
import gitCheckpointExtension from "./extensions/git-checkpoint.ts";
|
|
10
10
|
import lspBootstrapExtension from "./extensions/lsp-bootstrap.ts";
|
|
11
11
|
import piSerenaCompactExtension from "./extensions/pi-serena-compact.ts";
|
|
12
|
+
import serenaPoolExtension from "./extensions/serena-pool.ts";
|
|
12
13
|
import qualityGatesExtension from "./extensions/quality-gates.ts";
|
|
13
14
|
import serviceSkillsExtension from "./extensions/service-skills.ts";
|
|
14
15
|
import sessionFlowExtension from "./extensions/session-flow.ts";
|
|
@@ -29,6 +30,7 @@ export const managedPiExtensions: readonly ManagedPiExtension[] = [
|
|
|
29
30
|
{ id: "custom-footer", register: customFooterExtension },
|
|
30
31
|
{ id: "custom-provider-qwen-cli", register: customProviderQwenCliExtension },
|
|
31
32
|
{ id: "git-checkpoint", register: gitCheckpointExtension },
|
|
33
|
+
{ id: "serena-pool", register: serenaPoolExtension },
|
|
32
34
|
{ id: "lsp-bootstrap", register: lspBootstrapExtension },
|
|
33
35
|
{ id: "pi-serena-compact", register: piSerenaCompactExtension },
|
|
34
36
|
{ id: "quality-gates", register: qualityGatesExtension },
|