@pontia/pi-client-plugin 0.1.0 → 0.2.1
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 +17 -25
- package/package.json +30 -23
- package/src/context.ts +0 -104
- package/src/control-socket.ts +456 -0
- package/src/discovery.ts +2 -63
- package/src/events.ts +99 -20
- package/src/index.ts +479 -166
- package/src/live-output.ts +339 -0
- package/src/managed-runtime.ts +5 -1
- package/src/pi-message.ts +14 -4
- package/src/profile.ts +14 -22
- package/src/reporter.ts +62 -32
- package/src/runtime-binding.ts +22 -46
- package/src/session.ts +0 -1
- package/src/values.ts +9 -0
- package/src/workspace.ts +8 -13
- package/src/internal-api.ts +0 -29
package/README.md
CHANGED
|
@@ -1,49 +1,41 @@
|
|
|
1
1
|
# @pontia/pi-client-plugin
|
|
2
2
|
|
|
3
|
-
Connect
|
|
3
|
+
Connect your pi coding-agent sessions to Pontia so you can follow and continue your work from the web dashboard.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
7
|
- [Pontia](https://pontia.dev/) running locally
|
|
8
|
-
-
|
|
8
|
+
- pi CLI
|
|
9
9
|
- tmux
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
If you selected the pi integration during `pontia init`, the plugin is already installed.
|
|
14
|
+
|
|
15
|
+
To install it separately:
|
|
16
|
+
|
|
13
17
|
```bash
|
|
14
18
|
pi install npm:@pontia/pi-client-plugin
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Set the Pi command in `$PONTIA_HOME/config.toml`:
|
|
21
|
+
The plugin loads automatically when pi starts.
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
[runtime.pi]
|
|
23
|
-
tui_command = "pi"
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Then launch Pi from Pontia, or run it in a tmux pane inside a workspace registered with Pontia. The extension loads automatically.
|
|
23
|
+
## Get started
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
Open the Pontia dashboard and create a pi session. You can then view the session and continue the conversation from the dashboard.
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
You can also start pi in a tmux pane inside a workspace registered with Pontia.
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
- the current workspace is registered with Pontia
|
|
34
|
-
- Pi is running inside tmux
|
|
29
|
+
For Pontia installation and setup, see the [getting started guide](../../README.md#get-started).
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```text
|
|
39
|
-
${PONTIA_HOME:-$HOME/.pontia}/state/pi-hook.log
|
|
40
|
-
```
|
|
31
|
+
## Troubleshooting
|
|
41
32
|
|
|
42
|
-
|
|
33
|
+
If a session does not appear in the dashboard, check that:
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
- Pontia is running (`pontia status`).
|
|
36
|
+
- The workspace is registered with Pontia.
|
|
37
|
+
- pi is running inside tmux.
|
|
38
|
+
- If you just installed the plugin, you have restarted pi.
|
|
47
39
|
|
|
48
40
|
## License
|
|
49
41
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pontia/pi-client-plugin",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pi extension that connects coding-agent sessions to Pontia.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pontia-client-plugin"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://pontia.dev/",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/anthod0/pontia/issues"
|
|
12
|
+
},
|
|
5
13
|
"license": "Apache-2.0",
|
|
6
14
|
"author": {
|
|
7
15
|
"name": "anthod0",
|
|
@@ -12,39 +20,38 @@
|
|
|
12
20
|
"url": "git+https://github.com/anthod0/pontia.git",
|
|
13
21
|
"directory": "clients/pi"
|
|
14
22
|
},
|
|
15
|
-
"homepage": "https://pontia.dev/",
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/anthod0/pontia/issues"
|
|
18
|
-
},
|
|
19
|
-
"type": "module",
|
|
20
23
|
"files": [
|
|
21
24
|
"src",
|
|
22
25
|
"README.md",
|
|
23
26
|
"LICENSE"
|
|
24
27
|
],
|
|
28
|
+
"type": "module",
|
|
25
29
|
"publishConfig": {
|
|
26
30
|
"access": "public"
|
|
27
31
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"extensions": [
|
|
34
|
-
"./src/index.ts"
|
|
35
|
-
]
|
|
36
|
-
},
|
|
37
|
-
"peerDependencies": {
|
|
38
|
-
"@mariozechner/pi-coding-agent": "*"
|
|
32
|
+
"scripts": {
|
|
33
|
+
"fmt": "oxfmt",
|
|
34
|
+
"fmt:check": "oxfmt --check",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
|
-
"@
|
|
39
|
+
"@earendil-works/pi-coding-agent": ">=0.86.1 <1",
|
|
42
40
|
"@types/node": "^22.15.3",
|
|
41
|
+
"oxfmt": "^0.70.0",
|
|
43
42
|
"typescript": "^5.8.3",
|
|
44
43
|
"vitest": "^3.1.2"
|
|
45
44
|
},
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-coding-agent": ">=0.86.1 <1"
|
|
47
|
+
},
|
|
48
|
+
"pi": {
|
|
49
|
+
"extensions": [
|
|
50
|
+
"./src/index.ts"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"trustedDependencies": [
|
|
54
|
+
"esbuild",
|
|
55
|
+
"koffi"
|
|
56
|
+
]
|
|
57
|
+
}
|
package/src/context.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { pontiaHomeFromEnv, resolvePontiaConnection } from "./discovery.js";
|
|
3
|
-
import type { SessionContext } from "./session.js";
|
|
4
2
|
|
|
5
3
|
export interface TurnContext {
|
|
6
4
|
sessionId: string;
|
|
@@ -9,112 +7,10 @@ export interface TurnContext {
|
|
|
9
7
|
input?: string;
|
|
10
8
|
inboxMessageId?: string;
|
|
11
9
|
clientType: "pi";
|
|
12
|
-
internalEventUrl: string;
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
export type LoadTurnContextResult =
|
|
16
|
-
| { ok: true; context: TurnContext; logFile: string }
|
|
17
|
-
| { ok: false; reason: string; logFile?: string; silent?: boolean };
|
|
18
|
-
|
|
19
12
|
export type EnvLike = Record<string, string | undefined>;
|
|
20
13
|
|
|
21
|
-
export interface LoadTurnContextOptions {
|
|
22
|
-
fetch?: typeof fetch;
|
|
23
|
-
sessionContext?: SessionContext;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
14
|
export function defaultHookLogFile(pontiaHome: string): string {
|
|
27
15
|
return join(pontiaHome, "state", "pi-hook.log");
|
|
28
16
|
}
|
|
29
|
-
|
|
30
|
-
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
31
|
-
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function optionalString(value: unknown): string | undefined {
|
|
35
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function claimUrl(internalEventUrl: string, sessionId: string): string | undefined {
|
|
39
|
-
try {
|
|
40
|
-
const url = new URL(internalEventUrl);
|
|
41
|
-
url.pathname = url.pathname.replace(/\/events\/?$/, `/sessions/${encodeURIComponent(sessionId)}/current-turn/claim`);
|
|
42
|
-
return url.toString();
|
|
43
|
-
} catch {
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function contextFromRecord(record: Record<string, unknown>, logFile: string, discoveredInternalEventUrl?: string): LoadTurnContextResult {
|
|
49
|
-
const errors: string[] = [];
|
|
50
|
-
const sessionId = optionalString(record.session_id);
|
|
51
|
-
const turnId = optionalString(record.turn_id);
|
|
52
|
-
const clientType = optionalString(record.client_type);
|
|
53
|
-
const internalEventUrl = optionalString(record.internal_event_url) ?? discoveredInternalEventUrl;
|
|
54
|
-
const runtimeInstanceId = optionalString(record.runtime_instance_id);
|
|
55
|
-
const input = optionalString(record.input);
|
|
56
|
-
const inboxMessageId = optionalString(record.inbox_message_id);
|
|
57
|
-
|
|
58
|
-
if (!sessionId) errors.push("session_id is required");
|
|
59
|
-
if (clientType !== "pi") errors.push("client_type must be pi");
|
|
60
|
-
if (!internalEventUrl) errors.push("internal_event_url or pontia connection from PONTIA_HOME/config.toml is required");
|
|
61
|
-
if (!runtimeInstanceId) errors.push("runtime_instance_id is required");
|
|
62
|
-
|
|
63
|
-
if (errors.length > 0) {
|
|
64
|
-
return { ok: false, reason: errors.join("; "), logFile };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
ok: true,
|
|
69
|
-
logFile,
|
|
70
|
-
context: {
|
|
71
|
-
sessionId: sessionId!,
|
|
72
|
-
turnId,
|
|
73
|
-
runtimeInstanceId: runtimeInstanceId!,
|
|
74
|
-
input,
|
|
75
|
-
inboxMessageId,
|
|
76
|
-
clientType: "pi",
|
|
77
|
-
internalEventUrl: internalEventUrl!,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function claimTurnContext(
|
|
83
|
-
pontiaHome: string,
|
|
84
|
-
logFile: string,
|
|
85
|
-
fetchImpl: typeof fetch,
|
|
86
|
-
sessionContext?: SessionContext,
|
|
87
|
-
): Promise<LoadTurnContextResult | undefined> {
|
|
88
|
-
if (!sessionContext) return undefined;
|
|
89
|
-
const connection = await resolvePontiaConnection({ pontiaHome, fetch: fetchImpl });
|
|
90
|
-
const internalEventUrl = sessionContext.internalEventUrl ?? connection?.internalEventUrl;
|
|
91
|
-
const url = claimUrl(internalEventUrl, sessionContext.sessionId);
|
|
92
|
-
if (!url) return undefined;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const response = await fetchImpl(url, {
|
|
96
|
-
method: "POST",
|
|
97
|
-
headers: { "Content-Type": "application/json" },
|
|
98
|
-
body: JSON.stringify({ runtime_instance_id: sessionContext.runtimeInstanceId, client_type: "pi" }),
|
|
99
|
-
});
|
|
100
|
-
if (!response.ok) return undefined;
|
|
101
|
-
const body = (await response.json()) as unknown;
|
|
102
|
-
const data = asRecord(body)?.data;
|
|
103
|
-
const currentTurn = asRecord(asRecord(data)?.current_turn);
|
|
104
|
-
if (!currentTurn) return { ok: false, reason: "no pending current turn", logFile, silent: true };
|
|
105
|
-
return contextFromRecord(currentTurn, logFile, internalEventUrl);
|
|
106
|
-
} catch {
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export async function loadTurnContext(env: EnvLike = process.env, options: LoadTurnContextOptions = {}): Promise<LoadTurnContextResult> {
|
|
112
|
-
const pontiaHome = pontiaHomeFromEnv(env);
|
|
113
|
-
if (!pontiaHome) {
|
|
114
|
-
return { ok: false, reason: "Pontia home must resolve to a non-root absolute path", silent: true };
|
|
115
|
-
}
|
|
116
|
-
const logFile = defaultHookLogFile(pontiaHome);
|
|
117
|
-
const claimed = await claimTurnContext(pontiaHome, logFile, options.fetch ?? fetch, options.sessionContext);
|
|
118
|
-
if (claimed) return claimed;
|
|
119
|
-
return { ok: false, reason: "current turn claim unavailable", logFile, silent: true };
|
|
120
|
-
}
|