@pontia/pi-client-plugin 0.2.2 → 0.2.3
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/package.json +1 -1
- package/src/context.ts +1 -1
- package/src/control-socket.ts +10 -13
- package/src/events.ts +3 -3
- package/src/index.ts +10 -10
- package/src/live-output.ts +1 -1
- package/src/managed-runtime.ts +5 -5
- package/src/reporter.ts +4 -7
- package/src/runtime-binding.ts +9 -9
- package/src/session.ts +1 -1
package/package.json
CHANGED
package/src/context.ts
CHANGED
package/src/control-socket.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createConnection, type Socket } from "node:net";
|
|
2
2
|
import { isAbsolute, join } from "node:path";
|
|
3
3
|
|
|
4
|
-
export const CONTROL_VERSION =
|
|
4
|
+
export const CONTROL_VERSION = 7;
|
|
5
5
|
export const MAX_CONTROL_FRAME_BYTES = 64 * 1024;
|
|
6
6
|
// Match the former HTTP event body limit, including the RPC envelope.
|
|
7
7
|
export const MAX_RPC_FRAME_BYTES = 2 * 1024 * 1024 + 1024;
|
|
@@ -9,7 +9,7 @@ const REQUEST_TIMEOUT_MS = 5_000;
|
|
|
9
9
|
|
|
10
10
|
export interface ControlIdentity {
|
|
11
11
|
sessionId: string;
|
|
12
|
-
|
|
12
|
+
runtimeId: string;
|
|
13
13
|
clientSessionKey?: string;
|
|
14
14
|
}
|
|
15
15
|
export interface ControlInput {
|
|
@@ -392,12 +392,12 @@ export async function connectPi(
|
|
|
392
392
|
const result = (await peer.request("runtime.attach", {
|
|
393
393
|
version: CONTROL_VERSION,
|
|
394
394
|
session_id: identity!.sessionId,
|
|
395
|
-
|
|
395
|
+
runtime_id: identity!.runtimeId,
|
|
396
396
|
client_session_key: identity!.clientSessionKey,
|
|
397
397
|
})) as Record<string, unknown>;
|
|
398
398
|
if (
|
|
399
399
|
result?.session_id !== identity!.sessionId ||
|
|
400
|
-
result?.
|
|
400
|
+
result?.runtime_id !== identity!.runtimeId
|
|
401
401
|
) {
|
|
402
402
|
throw new RpcError(-32009, "Pi reconnect identity mismatch");
|
|
403
403
|
}
|
|
@@ -425,15 +425,12 @@ export async function connectPi(
|
|
|
425
425
|
if (method === "turn.startFailure") {
|
|
426
426
|
if (!identity?.clientSessionKey || stopped)
|
|
427
427
|
throw new Error("Pi reporting identity is unavailable");
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
} finally {
|
|
435
|
-
peer.close();
|
|
436
|
-
}
|
|
428
|
+
await ready();
|
|
429
|
+
if (!current || stopped) throw new Error("Pi connection is unavailable");
|
|
430
|
+
return current.request(method, {
|
|
431
|
+
...params,
|
|
432
|
+
client_session_key: identity.clientSessionKey,
|
|
433
|
+
});
|
|
437
434
|
}
|
|
438
435
|
await ready();
|
|
439
436
|
if (!current || stopped) return Promise.reject(new Error("Pi connection is unavailable"));
|
package/src/events.ts
CHANGED
|
@@ -329,7 +329,7 @@ export function buildTurnStartedEvent(
|
|
|
329
329
|
topologyContext?: PiTopologyContext,
|
|
330
330
|
): InternalEvent {
|
|
331
331
|
const data: Record<string, unknown> = {
|
|
332
|
-
|
|
332
|
+
runtime_id: context.runtimeId,
|
|
333
333
|
input_summary:
|
|
334
334
|
context.input === undefined
|
|
335
335
|
? undefined
|
|
@@ -378,7 +378,7 @@ export function buildTurnInterruptedEvent(
|
|
|
378
378
|
|
|
379
379
|
export function buildSessionReadyEvent(context: SessionContext): InternalEvent {
|
|
380
380
|
const payload: Record<string, unknown> = {
|
|
381
|
-
|
|
381
|
+
runtime_id: context.runtimeId,
|
|
382
382
|
};
|
|
383
383
|
if (context.clientSessionKey) payload.client_session_key = context.clientSessionKey;
|
|
384
384
|
if (context.clientSessionFile) payload.client_session_file = context.clientSessionFile;
|
|
@@ -398,7 +398,7 @@ export function buildSessionExitedEvent(context: SessionContext, reason: string)
|
|
|
398
398
|
type: "session.exited",
|
|
399
399
|
data: {
|
|
400
400
|
reason,
|
|
401
|
-
|
|
401
|
+
runtime_id: context.runtimeId,
|
|
402
402
|
},
|
|
403
403
|
};
|
|
404
404
|
}
|
package/src/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ import { isActiveRegisteredWorkspace } from "./workspace.js";
|
|
|
53
53
|
|
|
54
54
|
interface ReporterLike {
|
|
55
55
|
report(
|
|
56
|
-
context: {
|
|
56
|
+
context: { runtimeId: string },
|
|
57
57
|
event: InternalEvent,
|
|
58
58
|
): Promise<EventReportResult | boolean>;
|
|
59
59
|
}
|
|
@@ -205,7 +205,7 @@ export function createPontiaPiExtension(
|
|
|
205
205
|
const result = await makeReporter(currentHookLogFile()).report(context, {
|
|
206
206
|
session_id: context.sessionId,
|
|
207
207
|
type: "session.model_updated",
|
|
208
|
-
data: { model: modelId(model),
|
|
208
|
+
data: { model: modelId(model), runtime_id: context.runtimeId },
|
|
209
209
|
});
|
|
210
210
|
if (!reportAccepted(result)) throw new Error("Pi model observation was not accepted");
|
|
211
211
|
}
|
|
@@ -361,7 +361,7 @@ export function createPontiaPiExtension(
|
|
|
361
361
|
if (!runtimeIdentity) return undefined;
|
|
362
362
|
return {
|
|
363
363
|
sessionId: runtimeIdentity.sessionId,
|
|
364
|
-
|
|
364
|
+
runtimeId: runtimeIdentity.runtimeId,
|
|
365
365
|
clientType: "pi",
|
|
366
366
|
};
|
|
367
367
|
}
|
|
@@ -399,7 +399,7 @@ export function createPontiaPiExtension(
|
|
|
399
399
|
const body = await connection.request("branch.resolve", {
|
|
400
400
|
inbox_message_id: inboxMessageId,
|
|
401
401
|
session_id: commandContext.sessionId,
|
|
402
|
-
|
|
402
|
+
runtime_id: commandContext.runtimeId,
|
|
403
403
|
client_type: "pi",
|
|
404
404
|
});
|
|
405
405
|
const replay = asRecord(asRecord(body)?.branch_replay);
|
|
@@ -408,7 +408,7 @@ export function createPontiaPiExtension(
|
|
|
408
408
|
if (
|
|
409
409
|
optionalString(replay?.inbox_message_id) !== inboxMessageId ||
|
|
410
410
|
optionalString(replay?.session_id) !== commandContext.sessionId ||
|
|
411
|
-
optionalString(replay?.
|
|
411
|
+
optionalString(replay?.runtime_id) !== commandContext.runtimeId ||
|
|
412
412
|
optionalString(replay?.client_type) !== "pi" ||
|
|
413
413
|
!replacementInput ||
|
|
414
414
|
!targetEntryId
|
|
@@ -452,7 +452,7 @@ export function createPontiaPiExtension(
|
|
|
452
452
|
const current = await currentManagedSessionContext();
|
|
453
453
|
if (
|
|
454
454
|
current?.sessionId !== commandContext.sessionId ||
|
|
455
|
-
current?.
|
|
455
|
+
current?.runtimeId !== commandContext.runtimeId
|
|
456
456
|
) {
|
|
457
457
|
throw new Error("branch replay Runtime is no longer current");
|
|
458
458
|
}
|
|
@@ -636,15 +636,15 @@ export function createPontiaPiExtension(
|
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
context = await bindSession(await registrationConnection(), env, sessionDetails, {
|
|
639
|
-
|
|
639
|
+
runtimeId:
|
|
640
640
|
reason === "reload" || existingSession?.sessionState === "starting"
|
|
641
|
-
? existingSession?.
|
|
641
|
+
? existingSession?.runtimeId
|
|
642
642
|
: undefined,
|
|
643
643
|
});
|
|
644
644
|
if (
|
|
645
645
|
reason === "reload" &&
|
|
646
646
|
(context?.sessionId !== existingSession!.sessionId ||
|
|
647
|
-
context.
|
|
647
|
+
context.runtimeId !== existingSession!.runtimeId)
|
|
648
648
|
) {
|
|
649
649
|
reportingDisabled = true;
|
|
650
650
|
deferredManualSessionDetails = undefined;
|
|
@@ -770,7 +770,7 @@ export function createPontiaPiExtension(
|
|
|
770
770
|
}
|
|
771
771
|
const turnContext: TurnContext = {
|
|
772
772
|
sessionId: boundSessionContext.sessionId,
|
|
773
|
-
|
|
773
|
+
runtimeId: boundSessionContext.runtimeId,
|
|
774
774
|
clientType: "pi",
|
|
775
775
|
...(submission ?? { input: pendingPrompt }),
|
|
776
776
|
};
|
package/src/live-output.ts
CHANGED
|
@@ -236,7 +236,7 @@ export class LiveOutputPublisher implements LiveOutputPublisherLike {
|
|
|
236
236
|
return {
|
|
237
237
|
session_id: this.context.sessionId,
|
|
238
238
|
turn_id: this.context.turnId,
|
|
239
|
-
|
|
239
|
+
runtime_id: this.context.runtimeId,
|
|
240
240
|
stream_id: this.streamId,
|
|
241
241
|
};
|
|
242
242
|
}
|
package/src/managed-runtime.ts
CHANGED
|
@@ -6,7 +6,7 @@ const execFileAsync = promisify(execFile);
|
|
|
6
6
|
|
|
7
7
|
export interface ManagedRuntimeIdentity {
|
|
8
8
|
sessionId: string;
|
|
9
|
-
|
|
9
|
+
runtimeId: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function hasTmuxPaneEnvironment(env: EnvLike = process.env): boolean {
|
|
@@ -43,12 +43,12 @@ export async function loadPontiaManagedRuntimeIdentity(
|
|
|
43
43
|
const socketPath = env.TMUX!.trim().split(",", 1)[0]!.trim();
|
|
44
44
|
const paneId = env.TMUX_PANE!.trim();
|
|
45
45
|
|
|
46
|
-
const [sessionId,
|
|
46
|
+
const [sessionId, runtimeId] = await Promise.all([
|
|
47
47
|
paneOption(socketPath, paneId, "@pontia_session_id"),
|
|
48
|
-
paneOption(socketPath, paneId, "@
|
|
48
|
+
paneOption(socketPath, paneId, "@pontia_runtime_id"),
|
|
49
49
|
]);
|
|
50
|
-
if (!sessionId || !
|
|
51
|
-
return { sessionId,
|
|
50
|
+
if (!sessionId || !runtimeId) return undefined;
|
|
51
|
+
return { sessionId, runtimeId };
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export async function isPontiaManagedTmuxPane(env: EnvLike = process.env): Promise<boolean> {
|
package/src/reporter.ts
CHANGED
|
@@ -22,13 +22,10 @@ export class EventReporter {
|
|
|
22
22
|
this.logFile = options.logFile;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
async report(
|
|
26
|
-
context: { runtimeInstanceId: string },
|
|
27
|
-
event: InternalEvent,
|
|
28
|
-
): Promise<EventReportResult> {
|
|
25
|
+
async report(context: { runtimeId: string }, event: InternalEvent): Promise<EventReportResult> {
|
|
29
26
|
try {
|
|
30
27
|
const body = await this.connection.request("event.report", {
|
|
31
|
-
|
|
28
|
+
runtime_id: context.runtimeId,
|
|
32
29
|
event,
|
|
33
30
|
});
|
|
34
31
|
const record =
|
|
@@ -69,7 +66,7 @@ export class EventReporter {
|
|
|
69
66
|
event: InternalEvent,
|
|
70
67
|
reason: "event_rejected" | "transport_failed" | "missing_turn_id",
|
|
71
68
|
): Promise<void> {
|
|
72
|
-
if (event.type !== "turn.started" || typeof event.data.
|
|
69
|
+
if (event.type !== "turn.started" || typeof event.data.runtime_id !== "string") return;
|
|
73
70
|
// A lost turn.started response is ambiguous: retrying it could create a
|
|
74
71
|
// second Turn. Only this fenced, idempotent failure notification is retried.
|
|
75
72
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
@@ -77,7 +74,7 @@ export class EventReporter {
|
|
|
77
74
|
try {
|
|
78
75
|
const result = (await this.connection.request("turn.startFailure", {
|
|
79
76
|
session_id: event.session_id,
|
|
80
|
-
|
|
77
|
+
runtime_id: event.data.runtime_id,
|
|
81
78
|
reason,
|
|
82
79
|
})) as { accepted?: unknown } | null;
|
|
83
80
|
if (result?.accepted === true) return;
|
package/src/runtime-binding.ts
CHANGED
|
@@ -42,7 +42,7 @@ export async function bindSession(
|
|
|
42
42
|
connection: PiConnection,
|
|
43
43
|
env: EnvLike,
|
|
44
44
|
sessionDetails: PiSessionDetails,
|
|
45
|
-
options: { startKind?: "fork"; parentSessionId?: string;
|
|
45
|
+
options: { startKind?: "fork"; parentSessionId?: string; runtimeId?: string } = {},
|
|
46
46
|
): Promise<SessionContext | undefined> {
|
|
47
47
|
if (!sessionDetails.clientSessionKey) return undefined;
|
|
48
48
|
const tmux = tmuxBindingFromEnv(env);
|
|
@@ -57,7 +57,7 @@ export async function bindSession(
|
|
|
57
57
|
launch_cwd: sessionDetails.clientCwd,
|
|
58
58
|
...(options.startKind ? { start_kind: options.startKind } : {}),
|
|
59
59
|
...(options.parentSessionId ? { parent_session_id: options.parentSessionId } : {}),
|
|
60
|
-
...(options.
|
|
60
|
+
...(options.runtimeId ? { runtime_id: options.runtimeId } : {}),
|
|
61
61
|
...(tmux ? { tmux } : {}),
|
|
62
62
|
},
|
|
63
63
|
});
|
|
@@ -66,14 +66,14 @@ export async function bindSession(
|
|
|
66
66
|
const session = asRecord(record?.session);
|
|
67
67
|
const runtime = asRecord(record?.runtime);
|
|
68
68
|
const sessionId = optionalString(session?.session_id);
|
|
69
|
-
const
|
|
69
|
+
const resolvedRuntimeId = optionalString(runtime?.runtime_id);
|
|
70
70
|
if (!sessionId) throw new Error("runtime binding upsert response missing session.session_id");
|
|
71
|
-
if (!
|
|
72
|
-
throw new Error("runtime binding upsert response missing runtime.
|
|
71
|
+
if (!resolvedRuntimeId)
|
|
72
|
+
throw new Error("runtime binding upsert response missing runtime.runtime_id");
|
|
73
73
|
return {
|
|
74
74
|
sessionId,
|
|
75
75
|
clientType: "pi",
|
|
76
|
-
|
|
76
|
+
runtimeId: resolvedRuntimeId,
|
|
77
77
|
...sessionDetails,
|
|
78
78
|
};
|
|
79
79
|
}
|
|
@@ -95,15 +95,15 @@ export async function loadExistingSessionContext(
|
|
|
95
95
|
const sessionId = optionalString(record?.session_id);
|
|
96
96
|
const sessionState = optionalString(record?.session_state);
|
|
97
97
|
const clientType = optionalString(record?.client_type);
|
|
98
|
-
const
|
|
99
|
-
if (!sessionId || !sessionState || clientType !== "pi" || !
|
|
98
|
+
const runtimeId = optionalString(record?.runtime_id);
|
|
99
|
+
if (!sessionId || !sessionState || clientType !== "pi" || !runtimeId) {
|
|
100
100
|
throw new Error("agent binding session context lookup returned an invalid context");
|
|
101
101
|
}
|
|
102
102
|
return {
|
|
103
103
|
sessionId,
|
|
104
104
|
sessionState,
|
|
105
105
|
clientType: "pi",
|
|
106
|
-
|
|
106
|
+
runtimeId,
|
|
107
107
|
...sessionDetails,
|
|
108
108
|
};
|
|
109
109
|
}
|