portable-agent-layer 0.96.1 → 0.97.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
CHANGED
|
@@ -15,6 +15,7 @@ import { latestUserRequest } from "./antigravity-transcript";
|
|
|
15
15
|
import { autoUpdateOnStart } from "./auto-update";
|
|
16
16
|
import { regenerateIfNeeded } from "./claude-md";
|
|
17
17
|
import { buildSystemReminder } from "./context";
|
|
18
|
+
import { startHookTiming } from "./hook-timing";
|
|
18
19
|
import { type HookTurnPayload, hookSessionId } from "./hook-turn";
|
|
19
20
|
import { logContextSnapshot, logError, logPromptSnapshot } from "./log";
|
|
20
21
|
import { paths } from "./paths";
|
|
@@ -89,11 +90,18 @@ export async function invocationContext(
|
|
|
89
90
|
const prompt = latestUserRequest(payload.transcriptPath);
|
|
90
91
|
if (prompt === null) return null;
|
|
91
92
|
|
|
93
|
+
const timing = startHookTiming("prompt");
|
|
94
|
+
const sessionContext = claimConversationStart(conversationId)
|
|
95
|
+
? await timing.time("session-context", sessionStartReminder)
|
|
96
|
+
: null;
|
|
92
97
|
const parts = [
|
|
93
|
-
|
|
94
|
-
await promptReminder(prompt, conversationId),
|
|
98
|
+
sessionContext,
|
|
99
|
+
await timing.time("inject-context", () => promptReminder(prompt, conversationId)),
|
|
95
100
|
].filter((part): part is string => Boolean(part));
|
|
96
|
-
await
|
|
101
|
+
await timing.time("capture", () =>
|
|
102
|
+
capturePrompt(prompt, conversationId, sentAt, "InvocationContext")
|
|
103
|
+
);
|
|
104
|
+
timing.log(conversationId);
|
|
97
105
|
|
|
98
106
|
return parts.length > 0 ? injectedStep(parts.join("\n\n")) : null;
|
|
99
107
|
}
|
|
@@ -25,7 +25,26 @@ interface OpencodePermissionReply {
|
|
|
25
25
|
reply?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
interface OpencodeToolPart {
|
|
29
|
+
type?: string;
|
|
30
|
+
callID?: string;
|
|
31
|
+
sessionID?: string;
|
|
32
|
+
tool?: string;
|
|
33
|
+
state?: {
|
|
34
|
+
status?: string;
|
|
35
|
+
input?: unknown;
|
|
36
|
+
error?: string;
|
|
37
|
+
time?: { start?: number; end?: number };
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// opencode's PermissionV1 error messages: RejectedError / CorrectedError for a
|
|
42
|
+
// prompt the user refused, DeniedError for a configured rule.
|
|
43
|
+
const USER_REJECTED = "The user rejected permission";
|
|
44
|
+
const RULE_DENIED = "The user has specified a rule which prevents";
|
|
45
|
+
|
|
28
46
|
const askedTools = new Map<string, string>();
|
|
47
|
+
const loggedFailures = new Set<string>();
|
|
29
48
|
|
|
30
49
|
export function logOpencodeSessionCreated(session: OpencodeSession | undefined): void {
|
|
31
50
|
if (!session?.id || session.parentID) return;
|
|
@@ -57,3 +76,33 @@ export function logOpencodePermissionReply(reply: OpencodePermissionReply): void
|
|
|
57
76
|
session_id: reply.sessionID,
|
|
58
77
|
});
|
|
59
78
|
}
|
|
79
|
+
|
|
80
|
+
function durationOf(part: OpencodeToolPart): number | null {
|
|
81
|
+
const { start, end } = part.state?.time ?? {};
|
|
82
|
+
return start !== undefined && end !== undefined ? end - start : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function firstFailureOf(part: OpencodeToolPart | undefined): part is OpencodeToolPart {
|
|
86
|
+
if (part?.type !== "tool" || part.state?.status !== "error" || !part.callID)
|
|
87
|
+
return false;
|
|
88
|
+
if (loggedFailures.has(part.callID)) return false;
|
|
89
|
+
loggedFailures.add(part.callID);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A tool that threw never reaches tool.execute.after; its part turning to error is the only trace. */
|
|
94
|
+
export function logOpencodeToolFailure(part: OpencodeToolPart | undefined): void {
|
|
95
|
+
if (!firstFailureOf(part)) return;
|
|
96
|
+
const error = part.state?.error ?? "";
|
|
97
|
+
if (error.startsWith(USER_REJECTED)) return;
|
|
98
|
+
logToolCall({
|
|
99
|
+
hook_event_name: error.startsWith(RULE_DENIED)
|
|
100
|
+
? "PermissionDenied"
|
|
101
|
+
: "PostToolUseFailure",
|
|
102
|
+
tool_name: part.tool,
|
|
103
|
+
tool_input: part.state?.input,
|
|
104
|
+
session_id: part.sessionID,
|
|
105
|
+
error,
|
|
106
|
+
duration_ms: durationOf(part),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -69,6 +69,7 @@ const PALPlugin: Plugin = async ({ directory, client }: PluginInput) => {
|
|
|
69
69
|
logOpencodeCompaction,
|
|
70
70
|
logOpencodePermissionReply,
|
|
71
71
|
logOpencodeSessionCreated,
|
|
72
|
+
logOpencodeToolFailure,
|
|
72
73
|
rememberAskedTool,
|
|
73
74
|
} = await lib<typeof import("../../hooks/lib/opencode-events")>("opencode-events.ts");
|
|
74
75
|
const { captureSessionName } = await lib<
|
|
@@ -153,6 +154,8 @@ const PALPlugin: Plugin = async ({ directory, client }: PluginInput) => {
|
|
|
153
154
|
logOpencodeCompaction(event.properties.sessionID);
|
|
154
155
|
if (event.type === "permission.replied")
|
|
155
156
|
logOpencodePermissionReply(event.properties);
|
|
157
|
+
if (event.type === "message.part.updated")
|
|
158
|
+
logOpencodeToolFailure(event.properties.part);
|
|
156
159
|
|
|
157
160
|
if (event.type === "session.created" || event.type === "session.updated") {
|
|
158
161
|
const { regenerateIfNeeded } =
|