@agentlogs/opencode 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 +177 -0
- package/dist/dev.js +11919 -0
- package/dist/index.js +199 -0
- package/package.json +38 -0
- package/src/index.ts +303 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
var LOG_FILE = "/tmp/agentlogs-opencode.log";
|
|
5
|
+
function log(message, data) {
|
|
6
|
+
const timestamp = new Date().toISOString();
|
|
7
|
+
const logLine = data ? `[${timestamp}] ${message}
|
|
8
|
+
${JSON.stringify(data, null, 2)}
|
|
9
|
+
` : `[${timestamp}] ${message}
|
|
10
|
+
`;
|
|
11
|
+
try {
|
|
12
|
+
appendFileSync(LOG_FILE, logLine);
|
|
13
|
+
} catch {}
|
|
14
|
+
}
|
|
15
|
+
async function uploadViaCli(sessionId, cwd) {
|
|
16
|
+
const cliPath = process.env.VI_CLI_PATH;
|
|
17
|
+
let command;
|
|
18
|
+
let args;
|
|
19
|
+
if (cliPath) {
|
|
20
|
+
const parts = cliPath.split(" ");
|
|
21
|
+
command = parts[0];
|
|
22
|
+
args = [...parts.slice(1), "opencode", "upload", sessionId];
|
|
23
|
+
} else {
|
|
24
|
+
command = "npx";
|
|
25
|
+
args = ["-y", "agentlogs@latest", "opencode", "upload", sessionId];
|
|
26
|
+
}
|
|
27
|
+
log("Spawning CLI", { command, args, sessionId });
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const proc = spawn(command, args, {
|
|
30
|
+
cwd,
|
|
31
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
32
|
+
});
|
|
33
|
+
let stdout = "";
|
|
34
|
+
let stderr = "";
|
|
35
|
+
proc.stdout.on("data", (data) => {
|
|
36
|
+
stdout += data.toString();
|
|
37
|
+
});
|
|
38
|
+
proc.stderr.on("data", (data) => {
|
|
39
|
+
stderr += data.toString();
|
|
40
|
+
});
|
|
41
|
+
proc.on("close", (code) => {
|
|
42
|
+
log("CLI process exited", { code, stdout, stderr });
|
|
43
|
+
if (code === 0) {
|
|
44
|
+
try {
|
|
45
|
+
const result = JSON.parse(stdout.trim());
|
|
46
|
+
resolve({
|
|
47
|
+
success: true,
|
|
48
|
+
transcriptId: result.transcriptId,
|
|
49
|
+
transcriptUrl: result.transcriptUrl
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
resolve({
|
|
53
|
+
success: true,
|
|
54
|
+
error: "Failed to parse CLI output"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
resolve({
|
|
59
|
+
success: false,
|
|
60
|
+
error: stderr || `CLI exited with code ${code}`
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
proc.on("error", (err) => {
|
|
65
|
+
log("CLI spawn error", { error: String(err) });
|
|
66
|
+
resolve({
|
|
67
|
+
success: false,
|
|
68
|
+
error: `Failed to spawn agentlogs CLI: ${err.message}`
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function isGitCommitCommand(input) {
|
|
74
|
+
if (!input || typeof input !== "object")
|
|
75
|
+
return false;
|
|
76
|
+
const record = input;
|
|
77
|
+
const cmd = Array.isArray(record.command) ? record.command.join(" ") : typeof record.command === "string" ? record.command : "";
|
|
78
|
+
return /\bgit\s+commit\b/.test(cmd);
|
|
79
|
+
}
|
|
80
|
+
function appendTranscriptLinkToCommit(input, transcriptUrl) {
|
|
81
|
+
if (!input || typeof input !== "object")
|
|
82
|
+
return null;
|
|
83
|
+
const record = { ...input };
|
|
84
|
+
let cmdString;
|
|
85
|
+
if (Array.isArray(record.command)) {
|
|
86
|
+
cmdString = record.command.join(" ");
|
|
87
|
+
} else if (typeof record.command === "string") {
|
|
88
|
+
cmdString = record.command;
|
|
89
|
+
} else {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const messageMatch = cmdString.match(/-m\s+(?:"([^"]+)"|'([^']+)'|(\S+))/);
|
|
93
|
+
const existingMessage = messageMatch?.[1] || messageMatch?.[2] || messageMatch?.[3];
|
|
94
|
+
if (!existingMessage)
|
|
95
|
+
return null;
|
|
96
|
+
const newMessage = `${existingMessage}
|
|
97
|
+
|
|
98
|
+
Transcript: ${transcriptUrl}`;
|
|
99
|
+
if (Array.isArray(record.command)) {
|
|
100
|
+
const cmdArray = [...record.command];
|
|
101
|
+
for (let i = 0;i < cmdArray.length; i++) {
|
|
102
|
+
if (cmdArray[i] === "-m" && i + 1 < cmdArray.length) {
|
|
103
|
+
cmdArray[i + 1] = newMessage;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
record.command = cmdArray;
|
|
108
|
+
} else {
|
|
109
|
+
record.command = cmdString.replace(/-m\s+(?:"[^"]+"|'[^']+'|\S+)/, `-m "${newMessage.replace(/"/g, "\\\"")}"`);
|
|
110
|
+
}
|
|
111
|
+
return record;
|
|
112
|
+
}
|
|
113
|
+
var agentLogsPlugin = async (ctx) => {
|
|
114
|
+
const state = {
|
|
115
|
+
sessions: new Map,
|
|
116
|
+
uploading: new Set
|
|
117
|
+
};
|
|
118
|
+
log("Plugin initialized", {
|
|
119
|
+
directory: ctx.directory,
|
|
120
|
+
projectId: ctx.project?.id
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
event: async (rawEvent) => {
|
|
124
|
+
const event = rawEvent?.event ?? rawEvent;
|
|
125
|
+
const eventType = event?.type;
|
|
126
|
+
const properties = event?.properties;
|
|
127
|
+
log(`Event: ${eventType}`, properties);
|
|
128
|
+
if (eventType === "session.created") {
|
|
129
|
+
const sessionId = properties?.info?.id;
|
|
130
|
+
const parentId = properties?.info?.parentID;
|
|
131
|
+
const isSubagent = !!parentId;
|
|
132
|
+
if (sessionId) {
|
|
133
|
+
state.sessions.set(sessionId, {
|
|
134
|
+
isSubagent,
|
|
135
|
+
transcriptUrl: null
|
|
136
|
+
});
|
|
137
|
+
log("Session created", { sessionId, isSubagent, parentId });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (eventType === "session.idle") {
|
|
141
|
+
const sessionId = properties?.sessionID;
|
|
142
|
+
if (!sessionId)
|
|
143
|
+
return;
|
|
144
|
+
const session = state.sessions.get(sessionId);
|
|
145
|
+
if (session?.isSubagent) {
|
|
146
|
+
log("Skipping subagent session", { sessionId });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (state.uploading.has(sessionId)) {
|
|
150
|
+
log("Already uploading session", { sessionId });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
state.uploading.add(sessionId);
|
|
154
|
+
log("Session idle, uploading", { sessionId });
|
|
155
|
+
try {
|
|
156
|
+
const result = await uploadViaCli(sessionId, ctx.directory);
|
|
157
|
+
if (result.success && result.transcriptUrl) {
|
|
158
|
+
if (session) {
|
|
159
|
+
session.transcriptUrl = result.transcriptUrl;
|
|
160
|
+
}
|
|
161
|
+
log("Upload success", { sessionId, url: result.transcriptUrl });
|
|
162
|
+
} else {
|
|
163
|
+
log("Upload failed", { sessionId, error: result.error });
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
log("Session idle error", { sessionId, error: String(error) });
|
|
167
|
+
} finally {
|
|
168
|
+
state.uploading.delete(sessionId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
tool: {
|
|
173
|
+
execute: {
|
|
174
|
+
before: async (args) => {
|
|
175
|
+
let transcriptUrl = null;
|
|
176
|
+
for (const [, session] of state.sessions) {
|
|
177
|
+
if (!session.isSubagent && session.transcriptUrl) {
|
|
178
|
+
transcriptUrl = session.transcriptUrl;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if ((args.name === "shell" || args.name === "bash") && isGitCommitCommand(args.input) && transcriptUrl) {
|
|
182
|
+
log("Intercepting git commit", { transcriptUrl });
|
|
183
|
+
const modified = appendTranscriptLinkToCommit(args.input, transcriptUrl);
|
|
184
|
+
if (modified) {
|
|
185
|
+
log("Added transcript link to commit");
|
|
186
|
+
return { ...args, input: modified };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return args;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
var src_default = agentLogsPlugin;
|
|
196
|
+
export {
|
|
197
|
+
src_default as default,
|
|
198
|
+
agentLogsPlugin
|
|
199
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentlogs/opencode",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "AgentLogs plugin for OpenCode - automatically captures and uploads AI coding session transcripts",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentlogs",
|
|
7
|
+
"ai-coding",
|
|
8
|
+
"opencode",
|
|
9
|
+
"plugin",
|
|
10
|
+
"transcript"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/agentlogs/agentlogs.git",
|
|
16
|
+
"directory": "packages/opencode"
|
|
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",
|
|
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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLogs OpenCode Plugin
|
|
3
|
+
*
|
|
4
|
+
* Lightweight plugin that captures OpenCode session data and uploads via the agentlogs CLI.
|
|
5
|
+
* No external dependencies - all heavy lifting is done by the CLI.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // opencode.json
|
|
9
|
+
* { "plugin": ["@agentlogs/opencode"] }
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { appendFileSync } from "node:fs";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Debug Logging
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
const LOG_FILE = "/tmp/agentlogs-opencode.log";
|
|
20
|
+
|
|
21
|
+
function log(message: string, data?: unknown): void {
|
|
22
|
+
const timestamp = new Date().toISOString();
|
|
23
|
+
const logLine = data
|
|
24
|
+
? `[${timestamp}] ${message}\n${JSON.stringify(data, null, 2)}\n`
|
|
25
|
+
: `[${timestamp}] ${message}\n`;
|
|
26
|
+
try {
|
|
27
|
+
appendFileSync(LOG_FILE, logLine);
|
|
28
|
+
} catch {
|
|
29
|
+
// Ignore write errors
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Types (minimal, no external deps)
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
interface PluginContext {
|
|
38
|
+
directory: string;
|
|
39
|
+
worktree?: string;
|
|
40
|
+
project?: { id: string; path: string };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface SessionInfo {
|
|
44
|
+
isSubagent: boolean;
|
|
45
|
+
transcriptUrl: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface PluginState {
|
|
49
|
+
// Track sessions: sessionId → info
|
|
50
|
+
sessions: Map<string, SessionInfo>;
|
|
51
|
+
// Currently uploading session IDs (to prevent concurrent uploads)
|
|
52
|
+
uploading: Set<string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// CLI Integration
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
interface UploadResult {
|
|
60
|
+
success: boolean;
|
|
61
|
+
transcriptId?: string;
|
|
62
|
+
transcriptUrl?: string;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Upload transcript by shelling out to the agentlogs CLI.
|
|
68
|
+
* Passes session ID - CLI reads directly from OpenCode storage.
|
|
69
|
+
* Uses $VI_CLI_PATH if set, otherwise falls back to npx.
|
|
70
|
+
*/
|
|
71
|
+
async function uploadViaCli(sessionId: string, cwd: string): Promise<UploadResult> {
|
|
72
|
+
// Use VI_CLI_PATH if set, otherwise fall back to npx
|
|
73
|
+
// VI_CLI_PATH can be "bun /path/to/cli.ts" or just "/path/to/agentlogs"
|
|
74
|
+
const cliPath = process.env.VI_CLI_PATH;
|
|
75
|
+
|
|
76
|
+
let command: string;
|
|
77
|
+
let args: string[];
|
|
78
|
+
|
|
79
|
+
if (cliPath) {
|
|
80
|
+
const parts = cliPath.split(" ");
|
|
81
|
+
command = parts[0];
|
|
82
|
+
args = [...parts.slice(1), "opencode", "upload", sessionId];
|
|
83
|
+
} else {
|
|
84
|
+
command = "npx";
|
|
85
|
+
args = ["-y", "agentlogs@latest", "opencode", "upload", sessionId];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
log("Spawning CLI", { command, args, sessionId });
|
|
89
|
+
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const proc = spawn(command, args, {
|
|
92
|
+
cwd,
|
|
93
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
let stdout = "";
|
|
97
|
+
let stderr = "";
|
|
98
|
+
|
|
99
|
+
proc.stdout.on("data", (data) => {
|
|
100
|
+
stdout += data.toString();
|
|
101
|
+
});
|
|
102
|
+
proc.stderr.on("data", (data) => {
|
|
103
|
+
stderr += data.toString();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
proc.on("close", (code) => {
|
|
107
|
+
log("CLI process exited", { code, stdout, stderr });
|
|
108
|
+
|
|
109
|
+
if (code === 0) {
|
|
110
|
+
// Parse JSON output from CLI
|
|
111
|
+
try {
|
|
112
|
+
const result = JSON.parse(stdout.trim());
|
|
113
|
+
resolve({
|
|
114
|
+
success: true,
|
|
115
|
+
transcriptId: result.transcriptId,
|
|
116
|
+
transcriptUrl: result.transcriptUrl,
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
resolve({
|
|
120
|
+
success: true,
|
|
121
|
+
error: "Failed to parse CLI output",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
resolve({
|
|
126
|
+
success: false,
|
|
127
|
+
error: stderr || `CLI exited with code ${code}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
proc.on("error", (err) => {
|
|
133
|
+
log("CLI spawn error", { error: String(err) });
|
|
134
|
+
resolve({
|
|
135
|
+
success: false,
|
|
136
|
+
error: `Failed to spawn agentlogs CLI: ${err.message}`,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Git Utilities
|
|
144
|
+
// ============================================================================
|
|
145
|
+
|
|
146
|
+
function isGitCommitCommand(input: unknown): boolean {
|
|
147
|
+
if (!input || typeof input !== "object") return false;
|
|
148
|
+
const record = input as Record<string, unknown>;
|
|
149
|
+
|
|
150
|
+
const cmd = Array.isArray(record.command)
|
|
151
|
+
? record.command.join(" ")
|
|
152
|
+
: typeof record.command === "string"
|
|
153
|
+
? record.command
|
|
154
|
+
: "";
|
|
155
|
+
|
|
156
|
+
return /\bgit\s+commit\b/.test(cmd);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function appendTranscriptLinkToCommit(input: unknown, transcriptUrl: string): Record<string, unknown> | null {
|
|
160
|
+
if (!input || typeof input !== "object") return null;
|
|
161
|
+
const record = { ...(input as Record<string, unknown>) };
|
|
162
|
+
|
|
163
|
+
let cmdString: string;
|
|
164
|
+
if (Array.isArray(record.command)) {
|
|
165
|
+
cmdString = record.command.join(" ");
|
|
166
|
+
} else if (typeof record.command === "string") {
|
|
167
|
+
cmdString = record.command;
|
|
168
|
+
} else {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Extract existing message
|
|
173
|
+
const messageMatch = cmdString.match(/-m\s+(?:"([^"]+)"|'([^']+)'|(\S+))/);
|
|
174
|
+
const existingMessage = messageMatch?.[1] || messageMatch?.[2] || messageMatch?.[3];
|
|
175
|
+
if (!existingMessage) return null;
|
|
176
|
+
|
|
177
|
+
const newMessage = `${existingMessage}\n\nTranscript: ${transcriptUrl}`;
|
|
178
|
+
|
|
179
|
+
if (Array.isArray(record.command)) {
|
|
180
|
+
const cmdArray = [...(record.command as string[])];
|
|
181
|
+
for (let i = 0; i < cmdArray.length; i++) {
|
|
182
|
+
if (cmdArray[i] === "-m" && i + 1 < cmdArray.length) {
|
|
183
|
+
cmdArray[i + 1] = newMessage;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
record.command = cmdArray;
|
|
188
|
+
} else {
|
|
189
|
+
record.command = cmdString.replace(/-m\s+(?:"[^"]+"|'[^']+'|\S+)/, `-m "${newMessage.replace(/"/g, '\\"')}"`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return record;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Main Plugin
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
export const agentLogsPlugin = async (ctx: PluginContext) => {
|
|
200
|
+
const state: PluginState = {
|
|
201
|
+
sessions: new Map(),
|
|
202
|
+
uploading: new Set(),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
log("Plugin initialized", {
|
|
206
|
+
directory: ctx.directory,
|
|
207
|
+
projectId: ctx.project?.id,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
event: async (rawEvent: any) => {
|
|
212
|
+
// OpenCode wraps events: { event: { type, properties } }
|
|
213
|
+
const event = rawEvent?.event ?? rawEvent;
|
|
214
|
+
const eventType = event?.type;
|
|
215
|
+
const properties = event?.properties;
|
|
216
|
+
|
|
217
|
+
log(`Event: ${eventType}`, properties);
|
|
218
|
+
|
|
219
|
+
if (eventType === "session.created") {
|
|
220
|
+
const sessionId = properties?.info?.id;
|
|
221
|
+
const parentId = properties?.info?.parentID;
|
|
222
|
+
const isSubagent = !!parentId;
|
|
223
|
+
|
|
224
|
+
if (sessionId) {
|
|
225
|
+
state.sessions.set(sessionId, {
|
|
226
|
+
isSubagent,
|
|
227
|
+
transcriptUrl: null,
|
|
228
|
+
});
|
|
229
|
+
log("Session created", { sessionId, isSubagent, parentId });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (eventType === "session.idle") {
|
|
234
|
+
// Use session ID from the event, not from state
|
|
235
|
+
const sessionId = properties?.sessionID;
|
|
236
|
+
if (!sessionId) return;
|
|
237
|
+
|
|
238
|
+
const session = state.sessions.get(sessionId);
|
|
239
|
+
|
|
240
|
+
// Skip subagent sessions
|
|
241
|
+
if (session?.isSubagent) {
|
|
242
|
+
log("Skipping subagent session", { sessionId });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Skip if already uploading this session
|
|
247
|
+
if (state.uploading.has(sessionId)) {
|
|
248
|
+
log("Already uploading session", { sessionId });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
state.uploading.add(sessionId);
|
|
253
|
+
log("Session idle, uploading", { sessionId });
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
// Upload via CLI - it reads directly from OpenCode storage
|
|
257
|
+
const result = await uploadViaCli(sessionId, ctx.directory);
|
|
258
|
+
|
|
259
|
+
if (result.success && result.transcriptUrl) {
|
|
260
|
+
// Store transcript URL for this session (for git commit linking)
|
|
261
|
+
if (session) {
|
|
262
|
+
session.transcriptUrl = result.transcriptUrl;
|
|
263
|
+
}
|
|
264
|
+
log("Upload success", { sessionId, url: result.transcriptUrl });
|
|
265
|
+
} else {
|
|
266
|
+
log("Upload failed", { sessionId, error: result.error });
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
log("Session idle error", { sessionId, error: String(error) });
|
|
270
|
+
} finally {
|
|
271
|
+
state.uploading.delete(sessionId);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
tool: {
|
|
277
|
+
execute: {
|
|
278
|
+
before: async (args: { name: string; input: unknown }) => {
|
|
279
|
+
// Intercept git commits to add transcript link
|
|
280
|
+
// Use the most recent non-subagent session's transcript URL
|
|
281
|
+
let transcriptUrl: string | null = null;
|
|
282
|
+
for (const [, session] of state.sessions) {
|
|
283
|
+
if (!session.isSubagent && session.transcriptUrl) {
|
|
284
|
+
transcriptUrl = session.transcriptUrl;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if ((args.name === "shell" || args.name === "bash") && isGitCommitCommand(args.input) && transcriptUrl) {
|
|
289
|
+
log("Intercepting git commit", { transcriptUrl });
|
|
290
|
+
const modified = appendTranscriptLinkToCommit(args.input, transcriptUrl);
|
|
291
|
+
if (modified) {
|
|
292
|
+
log("Added transcript link to commit");
|
|
293
|
+
return { ...args, input: modified };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return args;
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export default agentLogsPlugin;
|