@pontia/pi-client-plugin 0.5.3 → 0.6.0
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/control-socket.ts +0 -10
- package/src/events.ts +8 -1
- package/src/index.ts +12 -2
- package/src/reporter.ts +37 -57
package/package.json
CHANGED
package/src/control-socket.ts
CHANGED
|
@@ -462,16 +462,6 @@ export async function connectPi(
|
|
|
462
462
|
current = await open();
|
|
463
463
|
return {
|
|
464
464
|
async request(method, params) {
|
|
465
|
-
if (method === "turn.startFailure") {
|
|
466
|
-
if (!identity?.clientSessionKey || stopped)
|
|
467
|
-
throw new Error("Pi reporting identity is unavailable");
|
|
468
|
-
await ready();
|
|
469
|
-
if (!current || stopped) throw new Error("Pi connection is unavailable");
|
|
470
|
-
return current.request(method, {
|
|
471
|
-
...params,
|
|
472
|
-
client_session_key: identity.clientSessionKey,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
465
|
await ready();
|
|
476
466
|
if (!current || stopped) return Promise.reject(new Error("Pi connection is unavailable"));
|
|
477
467
|
return current.request(method, params);
|
package/src/events.ts
CHANGED
|
@@ -61,6 +61,7 @@ export interface PiTopologyContext {
|
|
|
61
61
|
export interface InternalEvent {
|
|
62
62
|
session_id: string;
|
|
63
63
|
turn_id?: string;
|
|
64
|
+
report_key?: string;
|
|
64
65
|
type: InternalEventType;
|
|
65
66
|
data: Record<string, unknown>;
|
|
66
67
|
[legacyField: string]: any;
|
|
@@ -314,6 +315,7 @@ export function contextUsageFromPiHook(
|
|
|
314
315
|
|
|
315
316
|
export function buildTurnStartedEvent(
|
|
316
317
|
context: TurnContext,
|
|
318
|
+
reportKey: string,
|
|
317
319
|
previousLeafId: string | null = null,
|
|
318
320
|
topologyContext?: PiTopologyContext,
|
|
319
321
|
): InternalEvent {
|
|
@@ -327,7 +329,12 @@ export function buildTurnStartedEvent(
|
|
|
327
329
|
};
|
|
328
330
|
if (context.inboxMessageId) data.inbox_message_id = context.inboxMessageId;
|
|
329
331
|
if (topologyContext) data.topology_context = topologyContext;
|
|
330
|
-
return {
|
|
332
|
+
return {
|
|
333
|
+
session_id: context.sessionId,
|
|
334
|
+
report_key: reportKey,
|
|
335
|
+
type: "turn.started",
|
|
336
|
+
data,
|
|
337
|
+
};
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
export function buildTurnOutputEvent(context: ActiveTurnContext, output: string): InternalEvent {
|
package/src/index.ts
CHANGED
|
@@ -740,7 +740,7 @@ export function createPontiaPiExtension(
|
|
|
740
740
|
|
|
741
741
|
pi.on("agent_start", async (_event, ctx) => {
|
|
742
742
|
const topologyContext = topologyContextFromHookContext(ctx);
|
|
743
|
-
const
|
|
743
|
+
const reportKey = leafIdFromHookContext(ctx);
|
|
744
744
|
if (reportingDisabled) return;
|
|
745
745
|
try {
|
|
746
746
|
const dispatch = directInput.getStore();
|
|
@@ -750,6 +750,16 @@ export function createPontiaPiExtension(
|
|
|
750
750
|
: undefined;
|
|
751
751
|
if (dispatch) dispatch.consumed = true;
|
|
752
752
|
const logFile = currentHookLogFile();
|
|
753
|
+
if (!reportKey) {
|
|
754
|
+
activeTurn = undefined;
|
|
755
|
+
pendingPrompt = undefined;
|
|
756
|
+
await logDiagnostic(logFile, {
|
|
757
|
+
level: "error",
|
|
758
|
+
code: "turn_start_entry_id_missing",
|
|
759
|
+
message: "pi agent_start has no native session entry id; turn.started was not reported",
|
|
760
|
+
});
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
753
763
|
if (!boundSessionContext) {
|
|
754
764
|
const hookSessionDetails = piSessionDetailsFromHookContext(ctx);
|
|
755
765
|
const sessionDetails = hookSessionDetails.clientSessionKey
|
|
@@ -816,7 +826,7 @@ export function createPontiaPiExtension(
|
|
|
816
826
|
lastContextUsageJson = undefined;
|
|
817
827
|
const started = await reporter.report(
|
|
818
828
|
turnContext,
|
|
819
|
-
buildTurnStartedEvent(turnContext,
|
|
829
|
+
buildTurnStartedEvent(turnContext, reportKey, reportKey, topologyContext),
|
|
820
830
|
);
|
|
821
831
|
const canonicalTurnId = typeof started === "boolean" ? turnContext.turnId : started.turnId;
|
|
822
832
|
if (!reportAccepted(started) || !canonicalTurnId) {
|
package/src/reporter.ts
CHANGED
|
@@ -23,72 +23,52 @@ export class EventReporter {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async report(context: { runtimeId: string }, event: InternalEvent): Promise<EventReportResult> {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
runtime_id: context.runtimeId,
|
|
29
|
-
event,
|
|
30
|
-
});
|
|
31
|
-
const record =
|
|
32
|
-
body && typeof body === "object" ? (body as Record<string, unknown>) : undefined;
|
|
33
|
-
if (
|
|
34
|
-
event.type === "turn.started" &&
|
|
35
|
-
(record?.accepted !== true || typeof record?.turn_id !== "string" || !record.turn_id)
|
|
36
|
-
) {
|
|
37
|
-
await this.reportStartFailure(event, "missing_turn_id");
|
|
38
|
-
return { accepted: false };
|
|
39
|
-
}
|
|
40
|
-
if (record?.accepted !== true)
|
|
41
|
-
throw new Error("Pi event RPC returned an invalid acknowledgement");
|
|
42
|
-
return {
|
|
43
|
-
accepted: true,
|
|
44
|
-
eventId: typeof record.event_id === "string" ? record.event_id : undefined,
|
|
45
|
-
turnId: typeof record.turn_id === "string" ? record.turn_id : undefined,
|
|
46
|
-
};
|
|
47
|
-
} catch (error) {
|
|
48
|
-
await appendDiagnostic(this.logFile, {
|
|
49
|
-
level: "error",
|
|
50
|
-
code: "pi_event_report_failed",
|
|
51
|
-
message: `Pi event RPC failed for ${event.type}`,
|
|
52
|
-
details: {
|
|
53
|
-
event_type: event.type,
|
|
54
|
-
error: error instanceof Error ? error.message : String(error),
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
await this.reportStartFailure(
|
|
58
|
-
event,
|
|
59
|
-
error instanceof RpcError ? "event_rejected" : "transport_failed",
|
|
60
|
-
);
|
|
61
|
-
return { accepted: false };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
26
|
+
const attempts = event.type === "turn.started" ? 3 : 1;
|
|
27
|
+
let lastError: unknown;
|
|
64
28
|
|
|
65
|
-
|
|
66
|
-
event: InternalEvent,
|
|
67
|
-
reason: "event_rejected" | "transport_failed" | "missing_turn_id",
|
|
68
|
-
): Promise<void> {
|
|
69
|
-
if (event.type !== "turn.started" || typeof event.data.runtime_id !== "string") return;
|
|
70
|
-
// A lost turn.started response is ambiguous: retrying it could create a
|
|
71
|
-
// second Turn. Only this fenced, idempotent failure notification is retried.
|
|
72
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
29
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
73
30
|
if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
|
|
74
31
|
try {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
32
|
+
const body = await this.connection.request("event.report", {
|
|
33
|
+
runtime_id: context.runtimeId,
|
|
34
|
+
event,
|
|
35
|
+
});
|
|
36
|
+
const record =
|
|
37
|
+
body && typeof body === "object" ? (body as Record<string, unknown>) : undefined;
|
|
38
|
+
if (record?.accepted !== true) {
|
|
39
|
+
if (record?.accepted === false) {
|
|
40
|
+
lastError = new Error("Pi event report was rejected");
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
throw new Error("Pi event RPC returned an invalid acknowledgement");
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
event.type === "turn.started" &&
|
|
47
|
+
(typeof record.turn_id !== "string" || !record.turn_id)
|
|
48
|
+
) {
|
|
49
|
+
throw new Error("Pi turn.started acknowledgement has no canonical turn_id");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
accepted: true,
|
|
53
|
+
eventId: typeof record.event_id === "string" ? record.event_id : undefined,
|
|
54
|
+
turnId: typeof record.turn_id === "string" ? record.turn_id : undefined,
|
|
55
|
+
};
|
|
81
56
|
} catch (error) {
|
|
57
|
+
lastError = error;
|
|
82
58
|
if (error instanceof RpcError && error.code !== -32603) break;
|
|
83
|
-
// Exhaustion is visible locally; an unreachable server cannot persist
|
|
84
|
-
// a state transition. Never claim that this notification succeeded.
|
|
85
59
|
}
|
|
86
60
|
}
|
|
61
|
+
|
|
87
62
|
await appendDiagnostic(this.logFile, {
|
|
88
63
|
level: "error",
|
|
89
|
-
code: "
|
|
90
|
-
message:
|
|
91
|
-
details: {
|
|
64
|
+
code: "pi_event_report_failed",
|
|
65
|
+
message: `Pi event RPC failed for ${event.type}`,
|
|
66
|
+
details: {
|
|
67
|
+
event_type: event.type,
|
|
68
|
+
report_key: event.report_key,
|
|
69
|
+
error: lastError instanceof Error ? lastError.message : String(lastError),
|
|
70
|
+
},
|
|
92
71
|
});
|
|
72
|
+
return { accepted: false };
|
|
93
73
|
}
|
|
94
74
|
}
|