@agentlogs/pi 0.0.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 +147 -0
- package/dist/index.js +1 -0
- package/package.json +38 -0
- package/src/index.ts +300 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @agentlogs/pi
|
|
2
|
+
|
|
3
|
+
AgentLogs extension for [pi](https://github.com/badlogic/pi-mono) - automatically captures and uploads AI coding session transcripts.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @agentlogs/pi
|
|
9
|
+
# or
|
|
10
|
+
bun add -g @agentlogs/pi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Add the extension to your pi configuration:
|
|
16
|
+
|
|
17
|
+
**Option 1: Global settings** (`~/.pi/agent/settings.json`)
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"extensions": ["@agentlogs/pi"]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Option 2: Project settings** (`.pi/settings.json`)
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"extensions": ["@agentlogs/pi"]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Option 3: Package.json**
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"pi": {
|
|
38
|
+
"extensions": ["@agentlogs/pi"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
### Automatic Transcript Upload
|
|
46
|
+
|
|
47
|
+
When you end a pi session (Ctrl+D), your conversation transcript is automatically uploaded to AgentLogs.
|
|
48
|
+
|
|
49
|
+
### Git Commit Tracking
|
|
50
|
+
|
|
51
|
+
When the AI makes git commits, the extension:
|
|
52
|
+
|
|
53
|
+
1. Adds a link to the transcript in the commit message footer
|
|
54
|
+
2. Tracks which commits are associated with which transcripts
|
|
55
|
+
3. Makes it easy to find the AI conversation that led to any commit
|
|
56
|
+
|
|
57
|
+
### Branch-Aware Transcripts
|
|
58
|
+
|
|
59
|
+
Pi supports conversation branching via `/tree`. The extension handles this by:
|
|
60
|
+
|
|
61
|
+
- Generating unique transcript IDs for each branch
|
|
62
|
+
- Only uploading the current branch (from leaf to root)
|
|
63
|
+
- Preserving links to older branches when you navigate away
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
### Environment Variables
|
|
68
|
+
|
|
69
|
+
- `AGENTLOGS_CLI_PATH` - Custom path to the agentlogs CLI (defaults to `npx -y agentlogs@latest`)
|
|
70
|
+
|
|
71
|
+
### Repository Allowlist
|
|
72
|
+
|
|
73
|
+
By default, transcripts are only uploaded for repositories you've explicitly allowed:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Allow the current repo
|
|
77
|
+
agentlogs allow
|
|
78
|
+
|
|
79
|
+
# Set visibility
|
|
80
|
+
agentlogs allow --public
|
|
81
|
+
agentlogs allow --team
|
|
82
|
+
agentlogs allow --private
|
|
83
|
+
|
|
84
|
+
# Deny the current repo
|
|
85
|
+
agentlogs deny
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Development Setup
|
|
89
|
+
|
|
90
|
+
For local development, use the setup script:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# From the repo root
|
|
94
|
+
./packages/pi/scripts/dev-setup.sh
|
|
95
|
+
|
|
96
|
+
# This creates a symlink and shows the CLI path to export:
|
|
97
|
+
export AGENTLOGS_CLI_PATH="bun /path/to/agentlogs/packages/cli/src/index.ts"
|
|
98
|
+
|
|
99
|
+
# Now run pi - the extension loads automatically
|
|
100
|
+
pi
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To remove the dev setup:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
./packages/pi/scripts/dev-teardown.sh
|
|
107
|
+
unset AGENTLOGS_CLI_PATH
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Debug Logging
|
|
111
|
+
|
|
112
|
+
Debug logs are written to `/tmp/agentlogs-pi.log` when not in production mode.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Watch logs in real-time
|
|
116
|
+
tail -f /tmp/agentlogs-pi.log
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## CLI Commands
|
|
120
|
+
|
|
121
|
+
The extension uses the `agentlogs` CLI under the hood:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# List recent sessions
|
|
125
|
+
agentlogs pi upload
|
|
126
|
+
|
|
127
|
+
# Upload a specific session
|
|
128
|
+
agentlogs pi upload <session-id>
|
|
129
|
+
agentlogs pi upload /path/to/session.jsonl
|
|
130
|
+
|
|
131
|
+
# Check login status
|
|
132
|
+
agentlogs status
|
|
133
|
+
|
|
134
|
+
# Login to AgentLogs
|
|
135
|
+
agentlogs login
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## How It Works
|
|
139
|
+
|
|
140
|
+
1. The extension registers handlers for pi's lifecycle events
|
|
141
|
+
2. On `session_shutdown`, it shells out to `agentlogs pi hook` with session data
|
|
142
|
+
3. The CLI converts the pi session format to AgentLogs' unified format
|
|
143
|
+
4. The transcript is uploaded to the AgentLogs API
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{appendFileSync as V}from"node:fs";import{spawn as W}from"node:child_process";var X="/tmp/agentlogs-pi.log";function D(J,M){return}async function R(J,M){let Q=process.env.AGENTLOGS_CLI_PATH,b,j;if(Q){let q=Q.split(" ");b=q[0],j=[...q.slice(1),"pi","hook"]}else b="npx",j=["-y","agentlogs@latest","pi","hook"];return D("Running hook",{command:b,args:j,payload:J.hook_event_name}),new Promise((q)=>{let z=W(b,j,{cwd:M,stdio:["pipe","pipe","pipe"]}),B="",K="";z.stdout.on("data",(A)=>{B+=A.toString()}),z.stderr.on("data",(A)=>{K+=A.toString()}),z.stdin.write(JSON.stringify(J)),z.stdin.end(),z.on("close",(A)=>{if(D("Hook process exited",{code:A,stdout:B.slice(0,500),stderr:K.slice(0,500)}),A===0&&B.trim())try{let U=JSON.parse(B.trim());q(U)}catch{q({modified:!1})}else q({modified:!1})}),z.on("error",(A)=>{D("Hook spawn error",{error:String(A)}),q({modified:!1})})})}function Y(J){D("Extension initialized");let M=new Set;J.on("tool_call",async(b,j)=>{if(b.toolName!=="bash")return;let q=b.input?.command;if(typeof q!=="string"||!/\bgit\s+commit\b/.test(q))return;D("tool_call (git commit)",{toolName:b.toolName,toolCallId:b.toolCallId});let z=j.sessionManager.getSessionId(),B=j.sessionManager.getSessionFile(),K=await R({hook_event_name:"tool_call",session_id:z,tool_call_id:b.toolCallId,tool:b.toolName,tool_input:b.input,cwd:j.cwd,session_file:B},j.cwd);if(K.modified&&K.updatedInput)D("tool_call: input modified",{modified:!0}),M.add(b.toolCallId)}),J.on("tool_result",async(b,j)=>{if(b.toolName!=="bash")return;let q=M.has(b.toolCallId),z=b.content?.map((A)=>A.type==="text"?A.text:"").join("").trim()??"",B=z.includes("agentlogs.ai/s/");if(!q&&!B)return;M.delete(b.toolCallId),D("tool_result (git commit)",{toolName:b.toolName,toolCallId:b.toolCallId,hasLink:B,wasIntercepted:q});let K=j.sessionManager.getSessionId();R({hook_event_name:"tool_result",session_id:K,tool_call_id:b.toolCallId,tool:b.toolName,tool_output:{content:z},cwd:j.cwd},j.cwd).catch((A)=>D("tool_result hook error",{error:String(A)}))});let Q=(b,j)=>{let q=j.sessionManager.getSessionId(),z=j.sessionManager.getSessionFile(),B=j.sessionManager.getLeafId();if(!z){D(`${b}: no session file (ephemeral session)`);return}D(b,{sessionId:q,sessionFile:z,leafId:B}),R({hook_event_name:b,session_id:q,session_file:z,leaf_id:B??void 0,cwd:j.cwd},j.cwd).catch((K)=>D(`${b} hook error`,{error:String(K)}))};J.on("agent_end",async(b,j)=>{Q("agent_end",j)}),J.on("session_shutdown",async(b,j)=>{Q("session_shutdown",j)})}export{Y as default,Y as agentLogsExtension};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentlogs/pi",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "AgentLogs extension for Pi - automatically captures and uploads AI coding session transcripts",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentlogs",
|
|
7
|
+
"ai-coding",
|
|
8
|
+
"extension",
|
|
9
|
+
"pi",
|
|
10
|
+
"transcript"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/agentlogs/agentlogs.git",
|
|
16
|
+
"directory": "packages/pi"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "src/index.ts",
|
|
24
|
+
"types": "src/index.ts",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"main": "dist/index.js",
|
|
28
|
+
"types": "dist/index.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node --define 'process.env.NODE_ENV=\"production\"' --minify",
|
|
32
|
+
"prepublishOnly": "bun run build",
|
|
33
|
+
"check": "tsgo --project tsconfig.json"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"bun": ">=1.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLogs Pi Extension
|
|
3
|
+
*
|
|
4
|
+
* Lightweight extension that shells out to the agentlogs CLI for all processing.
|
|
5
|
+
* The CLI handles transcript uploads, git commit interception, and commit tracking.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Install globally or in project
|
|
9
|
+
* npm install -g @agentlogs/pi
|
|
10
|
+
*
|
|
11
|
+
* // Add to pi settings.json
|
|
12
|
+
* { "extensions": ["@agentlogs/pi"] }
|
|
13
|
+
*
|
|
14
|
+
* // Or use the pi field in package.json
|
|
15
|
+
* { "pi": { "extensions": ["@agentlogs/pi"] } }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { appendFileSync } from "node:fs";
|
|
19
|
+
import { spawn } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Pi Types (inline to avoid external dependency)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
interface ExtensionContext {
|
|
26
|
+
cwd: string;
|
|
27
|
+
sessionManager: {
|
|
28
|
+
getSessionId(): string;
|
|
29
|
+
getSessionFile(): string | undefined;
|
|
30
|
+
getLeafId(): string | null;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ToolCallEvent {
|
|
35
|
+
toolName: string;
|
|
36
|
+
toolCallId: string;
|
|
37
|
+
input: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ToolResultEvent {
|
|
41
|
+
toolName: string;
|
|
42
|
+
toolCallId: string;
|
|
43
|
+
content?: Array<{ type: string; text?: string }>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ExtensionAPI {
|
|
47
|
+
on(event: "tool_call", handler: (event: ToolCallEvent, ctx: ExtensionContext) => void | Promise<void>): void;
|
|
48
|
+
on(event: "tool_result", handler: (event: ToolResultEvent, ctx: ExtensionContext) => void | Promise<void>): void;
|
|
49
|
+
on(event: "agent_end", handler: (event: unknown, ctx: ExtensionContext) => void | Promise<void>): void;
|
|
50
|
+
on(event: "session_shutdown", handler: (event: unknown, ctx: ExtensionContext) => void | Promise<void>): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Debug Logging (compiled out in production builds)
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
const LOG_FILE = "/tmp/agentlogs-pi.log";
|
|
58
|
+
|
|
59
|
+
function log(message: string, data?: unknown): void {
|
|
60
|
+
if (process.env.NODE_ENV === "production") return;
|
|
61
|
+
const timestamp = new Date().toISOString();
|
|
62
|
+
const logLine = data
|
|
63
|
+
? `[${timestamp}] ${message}\n${JSON.stringify(data, null, 2)}\n`
|
|
64
|
+
: `[${timestamp}] ${message}\n`;
|
|
65
|
+
try {
|
|
66
|
+
appendFileSync(LOG_FILE, logLine);
|
|
67
|
+
} catch {
|
|
68
|
+
// Ignore write errors
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Types
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
interface HookPayload {
|
|
77
|
+
hook_event_name: string;
|
|
78
|
+
session_id: string;
|
|
79
|
+
tool_call_id?: string;
|
|
80
|
+
tool?: string;
|
|
81
|
+
cwd?: string;
|
|
82
|
+
tool_input?: Record<string, unknown>;
|
|
83
|
+
tool_output?: Record<string, unknown>;
|
|
84
|
+
session_file?: string;
|
|
85
|
+
leaf_id?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface HookResponse {
|
|
89
|
+
modified: boolean;
|
|
90
|
+
updatedInput?: Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// CLI Integration
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Shell out to the agentlogs CLI hook command.
|
|
99
|
+
* Passes hook data via stdin, receives response via stdout.
|
|
100
|
+
*/
|
|
101
|
+
async function runHook(payload: HookPayload, cwd: string): Promise<HookResponse> {
|
|
102
|
+
const cliPath = process.env.AGENTLOGS_CLI_PATH;
|
|
103
|
+
|
|
104
|
+
let command: string;
|
|
105
|
+
let args: string[];
|
|
106
|
+
|
|
107
|
+
if (cliPath) {
|
|
108
|
+
const parts = cliPath.split(" ");
|
|
109
|
+
command = parts[0];
|
|
110
|
+
args = [...parts.slice(1), "pi", "hook"];
|
|
111
|
+
} else {
|
|
112
|
+
command = "npx";
|
|
113
|
+
args = ["-y", "agentlogs@latest", "pi", "hook"];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
log("Running hook", { command, args, payload: payload.hook_event_name });
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
const proc = spawn(command, args, {
|
|
120
|
+
cwd,
|
|
121
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
let stdout = "";
|
|
125
|
+
let stderr = "";
|
|
126
|
+
|
|
127
|
+
proc.stdout.on("data", (data) => {
|
|
128
|
+
stdout += data.toString();
|
|
129
|
+
});
|
|
130
|
+
proc.stderr.on("data", (data) => {
|
|
131
|
+
stderr += data.toString();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Send payload via stdin
|
|
135
|
+
proc.stdin.write(JSON.stringify(payload));
|
|
136
|
+
proc.stdin.end();
|
|
137
|
+
|
|
138
|
+
proc.on("close", (code) => {
|
|
139
|
+
log("Hook process exited", { code, stdout: stdout.slice(0, 500), stderr: stderr.slice(0, 500) });
|
|
140
|
+
|
|
141
|
+
if (code === 0 && stdout.trim()) {
|
|
142
|
+
try {
|
|
143
|
+
const response = JSON.parse(stdout.trim()) as HookResponse;
|
|
144
|
+
resolve(response);
|
|
145
|
+
} catch {
|
|
146
|
+
resolve({ modified: false });
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
resolve({ modified: false });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
proc.on("error", (err) => {
|
|
154
|
+
log("Hook spawn error", { error: String(err) });
|
|
155
|
+
resolve({ modified: false });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Main Extension
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
export default function agentLogsExtension(pi: ExtensionAPI) {
|
|
165
|
+
log("Extension initialized");
|
|
166
|
+
|
|
167
|
+
// Track tool call IDs where we intercepted a git commit
|
|
168
|
+
const interceptedToolCallIds = new Set<string>();
|
|
169
|
+
|
|
170
|
+
// Handle tool calls (intercept git commits)
|
|
171
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
172
|
+
// Only intercept bash tool
|
|
173
|
+
if (event.toolName !== "bash") {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Quick check: skip if not a git commit
|
|
178
|
+
const command = event.input?.command;
|
|
179
|
+
if (typeof command !== "string" || !/\bgit\s+commit\b/.test(command)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
log("tool_call (git commit)", {
|
|
184
|
+
toolName: event.toolName,
|
|
185
|
+
toolCallId: event.toolCallId,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
189
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
190
|
+
|
|
191
|
+
const response = await runHook(
|
|
192
|
+
{
|
|
193
|
+
hook_event_name: "tool_call",
|
|
194
|
+
session_id: sessionId,
|
|
195
|
+
tool_call_id: event.toolCallId,
|
|
196
|
+
tool: event.toolName,
|
|
197
|
+
tool_input: event.input as Record<string, unknown>,
|
|
198
|
+
cwd: ctx.cwd,
|
|
199
|
+
session_file: sessionFile,
|
|
200
|
+
},
|
|
201
|
+
ctx.cwd,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (response.modified && response.updatedInput) {
|
|
205
|
+
log("tool_call: input modified", { modified: true });
|
|
206
|
+
// Track this tool call ID so we know to process the result
|
|
207
|
+
interceptedToolCallIds.add(event.toolCallId);
|
|
208
|
+
|
|
209
|
+
// Note: Pi doesn't support modifying tool input from extensions
|
|
210
|
+
// The CLI will need to handle this differently (pre-upload the transcript)
|
|
211
|
+
// For now, we just track that we saw this commit
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Handle tool results (track commits)
|
|
216
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
217
|
+
// Only handle bash tool
|
|
218
|
+
if (event.toolName !== "bash") {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check if we intercepted this tool call
|
|
223
|
+
const wasIntercepted = interceptedToolCallIds.has(event.toolCallId);
|
|
224
|
+
|
|
225
|
+
// Also check if output contains our transcript link
|
|
226
|
+
const outputText =
|
|
227
|
+
event.content
|
|
228
|
+
?.map((c) => (c.type === "text" ? c.text : ""))
|
|
229
|
+
.join("")
|
|
230
|
+
.trim() ?? "";
|
|
231
|
+
const hasLink = outputText.includes("agentlogs.ai/s/");
|
|
232
|
+
|
|
233
|
+
if (!wasIntercepted && !hasLink) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Clean up tracked ID
|
|
238
|
+
interceptedToolCallIds.delete(event.toolCallId);
|
|
239
|
+
|
|
240
|
+
log("tool_result (git commit)", {
|
|
241
|
+
toolName: event.toolName,
|
|
242
|
+
toolCallId: event.toolCallId,
|
|
243
|
+
hasLink,
|
|
244
|
+
wasIntercepted,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
248
|
+
|
|
249
|
+
// Fire and forget - CLI handles commit tracking
|
|
250
|
+
runHook(
|
|
251
|
+
{
|
|
252
|
+
hook_event_name: "tool_result",
|
|
253
|
+
session_id: sessionId,
|
|
254
|
+
tool_call_id: event.toolCallId,
|
|
255
|
+
tool: event.toolName,
|
|
256
|
+
tool_output: { content: outputText },
|
|
257
|
+
cwd: ctx.cwd,
|
|
258
|
+
},
|
|
259
|
+
ctx.cwd,
|
|
260
|
+
).catch((err) => log("tool_result hook error", { error: String(err) }));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Helper to trigger transcript upload
|
|
264
|
+
const uploadTranscript = (eventName: string, ctx: ExtensionContext) => {
|
|
265
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
266
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
267
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
268
|
+
|
|
269
|
+
if (!sessionFile) {
|
|
270
|
+
log(`${eventName}: no session file (ephemeral session)`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
log(eventName, { sessionId, sessionFile, leafId });
|
|
275
|
+
|
|
276
|
+
// Fire and forget - CLI handles the upload
|
|
277
|
+
runHook(
|
|
278
|
+
{
|
|
279
|
+
hook_event_name: eventName,
|
|
280
|
+
session_id: sessionId,
|
|
281
|
+
session_file: sessionFile,
|
|
282
|
+
leaf_id: leafId ?? undefined,
|
|
283
|
+
cwd: ctx.cwd,
|
|
284
|
+
},
|
|
285
|
+
ctx.cwd,
|
|
286
|
+
).catch((err) => log(`${eventName} hook error`, { error: String(err) }));
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Handle agent end (upload after each turn)
|
|
290
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
291
|
+
uploadTranscript("agent_end", ctx);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Handle session shutdown (final upload)
|
|
295
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
296
|
+
uploadTranscript("session_shutdown", ctx);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { agentLogsExtension };
|