@fpr1m3/opencode-pai-plugin 1.1.1 → 1.2.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/dist/index.js CHANGED
@@ -91,8 +91,12 @@ function generateTabTitle(completedLine) {
91
91
  return 'PAI Task Done';
92
92
  }
93
93
  export const PAIPlugin = async ({ worktree }) => {
94
- let logger = null;
95
- let currentSessionId = null;
94
+ const loggers = new Map();
95
+ // Track the latest text content for each message (from streaming parts)
96
+ // Key: messageID, Value: latest full text from part.text
97
+ const messageTextCache = new Map();
98
+ // Track which messages we've already processed for archival (deduplication)
99
+ const processedMessageIds = new Set();
96
100
  // Auto-initialize PAI infrastructure if needed
97
101
  ensurePAIStructure();
98
102
  // Load CORE skill content from $PAI_DIR/skill/core/SKILL.md
@@ -131,17 +135,33 @@ export const PAIPlugin = async ({ worktree }) => {
131
135
  const hooks = {
132
136
  event: async ({ event }) => {
133
137
  const anyEvent = event;
134
- // Initialize Logger on session creation
135
- if (event.type === 'session.created') {
136
- currentSessionId = anyEvent.properties.info.id;
137
- logger = new Logger(currentSessionId);
138
+ // Get Session ID from event (try multiple locations)
139
+ const sessionId = anyEvent.properties?.part?.sessionID ||
140
+ anyEvent.properties?.info?.sessionID ||
141
+ anyEvent.properties?.sessionID ||
142
+ anyEvent.sessionID;
143
+ if (!sessionId)
144
+ return;
145
+ // Initialize Logger if needed
146
+ if (!loggers.has(sessionId)) {
147
+ loggers.set(sessionId, new Logger(sessionId, worktree));
138
148
  }
139
- // Handle generic event logging
140
- if (logger &&
141
- event.type !== 'message.part.updated' &&
142
- !shouldSkipEvent(event, currentSessionId)) {
149
+ const logger = loggers.get(sessionId);
150
+ // Handle generic event logging (skip streaming parts to reduce noise)
151
+ if (!shouldSkipEvent(event, sessionId) && event.type !== 'message.part.updated') {
143
152
  logger.logOpenCodeEvent(event);
144
153
  }
154
+ // STREAMING CAPTURE: Cache the latest text from message.part.updated
155
+ // The part.text field contains the FULL accumulated text, not a delta
156
+ if (event.type === 'message.part.updated') {
157
+ const part = anyEvent.properties?.part;
158
+ const messageId = part?.messageID;
159
+ const partType = part?.type;
160
+ // Only cache text parts (not tool parts)
161
+ if (messageId && partType === 'text' && part?.text) {
162
+ messageTextCache.set(messageId, part.text);
163
+ }
164
+ }
145
165
  // Handle real-time tab title updates (Pre-Tool Use)
146
166
  if (anyEvent.type === 'tool.call') {
147
167
  const props = anyEvent.properties;
@@ -158,39 +178,67 @@ export const PAIPlugin = async ({ worktree }) => {
158
178
  process.stderr.write(`\x1b]0;Agent: ${type}...\x07`);
159
179
  }
160
180
  }
161
- // Handle assistant completion (Tab Titles & UOCS)
181
+ // Handle assistant message completion (Tab Titles & Artifact Archival)
162
182
  if (event.type === 'message.updated') {
163
183
  const info = anyEvent.properties?.info;
164
184
  const role = info?.role || info?.author;
165
- if (role === 'assistant') {
166
- // Robust content extraction
167
- const content = info?.content || info?.text || '';
168
- const contentStr = typeof content === 'string' ? content : '';
169
- // Look for COMPLETED: line (can be prefaced by 🎯 or just text)
170
- const completedMatch = contentStr.match(/(?:🎯\s*)?COMPLETED:\s*(.+?)(?:\n|$)/i);
171
- if (completedMatch) {
172
- const completedLine = completedMatch[1].trim();
173
- // Set Tab Title
174
- const tabTitle = generateTabTitle(completedLine);
175
- process.stderr.write(`\x1b]0;${tabTitle}\x07`);
176
- // UOCS: Process response for artifact generation
177
- if (logger && contentStr) {
178
- await logger.processAssistantMessage(contentStr);
185
+ const messageId = info?.id;
186
+ if (role === 'assistant' && messageId) {
187
+ // Get content from our streaming cache first, fallback to info.content
188
+ let contentStr = messageTextCache.get(messageId) || '';
189
+ // Fallback: try to get content from the event itself
190
+ if (!contentStr) {
191
+ const content = info?.content || info?.text || '';
192
+ if (typeof content === 'string') {
193
+ contentStr = content;
194
+ }
195
+ else if (Array.isArray(content)) {
196
+ contentStr = content
197
+ .map((p) => {
198
+ if (typeof p === 'string')
199
+ return p;
200
+ if (p?.text)
201
+ return p.text;
202
+ if (p?.content)
203
+ return p.content;
204
+ return '';
205
+ })
206
+ .join('');
179
207
  }
180
208
  }
209
+ // Process if we have content and haven't processed this message yet
210
+ if (contentStr && !processedMessageIds.has(messageId)) {
211
+ processedMessageIds.add(messageId);
212
+ // Look for COMPLETED: line for tab title
213
+ const completedMatch = contentStr.match(/(?:🎯\s*)?COMPLETED:\s*(.+?)(?:\n|$)/i);
214
+ if (completedMatch) {
215
+ const completedLine = completedMatch[1].trim();
216
+ const tabTitle = generateTabTitle(completedLine);
217
+ process.stderr.write(`\x1b]0;${tabTitle}\x07`);
218
+ }
219
+ // Archive structured response
220
+ await logger.processAssistantMessage(contentStr, messageId);
221
+ // Clean up cache for this message
222
+ messageTextCache.delete(messageId);
223
+ }
181
224
  }
182
225
  }
183
226
  // Handle session deletion / end or idle (for one-shot commands)
184
227
  if (event.type === 'session.deleted' || event.type === 'session.idle') {
185
- if (logger) {
186
- await logger.generateSessionSummary();
187
- logger.flush();
188
- }
228
+ await logger.generateSessionSummary();
229
+ logger.flush();
230
+ loggers.delete(sessionId);
231
+ // Clean up any stale cache entries for this session
232
+ // (In practice, messages are cleaned up after processing)
189
233
  }
190
234
  },
191
235
  "tool.execute.after": async (input, output) => {
192
- if (logger) {
193
- logger.logToolExecution(input, output);
236
+ const sessionId = input.sessionID;
237
+ if (sessionId) {
238
+ if (!loggers.has(sessionId)) {
239
+ loggers.set(sessionId, new Logger(sessionId, worktree));
240
+ }
241
+ loggers.get(sessionId).logToolExecution(input, output);
194
242
  }
195
243
  },
196
244
  "permission.ask": async (permission) => {
@@ -1,24 +1,21 @@
1
1
  import type { Event } from '@opencode-ai/sdk';
2
2
  export declare class Logger {
3
3
  private sessionId;
4
+ private worktree;
4
5
  private toolsUsed;
5
6
  private filesChanged;
6
7
  private commandsExecuted;
8
+ private processedMessageIds;
7
9
  private startTime;
8
- constructor(sessionId: string);
10
+ constructor(sessionId: string, worktree?: string);
11
+ private getHistoryDir;
12
+ processAssistantMessage(content: string, messageId?: string): Promise<void>;
9
13
  private getPSTTimestamp;
10
14
  private getEventsFilePath;
11
15
  private getSessionMappingFile;
12
16
  private getAgentForSession;
13
17
  private setAgentForSession;
14
- logEvent(event: Event): void;
15
18
  logOpenCodeEvent(event: Event): void;
16
- /**
17
- * Log tool execution from tool.execute.after hook
18
- *
19
- * Input structure: { tool: string; sessionID: string; callID: string }
20
- * Output structure: { title: string; output: string; metadata: any }
21
- */
22
19
  logToolExecution(input: {
23
20
  tool: string;
24
21
  sessionID: string;
@@ -29,7 +26,6 @@ export declare class Logger {
29
26
  metadata: any;
30
27
  }): void;
31
28
  generateSessionSummary(): Promise<string | null>;
32
- processAssistantMessage(content: string): Promise<void>;
33
29
  private parseStructuredResponse;
34
30
  private isLearningCapture;
35
31
  private determineArtifactType;
@@ -1,18 +1,52 @@
1
1
  import { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
- import { PAI_DIR, getHistoryFilePath, HISTORY_DIR } from './paths';
4
- import { enrichEventWithAgentMetadata, isAgentSpawningCall } from './metadata-extraction';
3
+ import { PAI_DIR, HISTORY_DIR } from './paths';
4
+ import { isAgentSpawningCall, enrichEventWithAgentMetadata } from './metadata-extraction';
5
5
  import { redactString, redactObject } from './redaction';
6
6
  export class Logger {
7
7
  sessionId;
8
+ worktree;
8
9
  toolsUsed = new Set();
9
10
  filesChanged = new Set();
10
11
  commandsExecuted = [];
12
+ processedMessageIds = new Set();
11
13
  startTime = Date.now();
12
- constructor(sessionId) {
14
+ constructor(sessionId, worktree = '/') {
13
15
  this.sessionId = sessionId;
16
+ this.worktree = worktree;
17
+ }
18
+ getHistoryDir() {
19
+ if (process.env.HISTORY_DIR)
20
+ return process.env.HISTORY_DIR;
21
+ if (existsSync(PAI_DIR))
22
+ return HISTORY_DIR;
23
+ return join(this.worktree, '.opencode', 'history');
24
+ }
25
+ async processAssistantMessage(content, messageId) {
26
+ try {
27
+ // Deduplication: skip if we've already processed this message
28
+ if (messageId) {
29
+ if (this.processedMessageIds.has(messageId))
30
+ return;
31
+ this.processedMessageIds.add(messageId);
32
+ }
33
+ // Parse structured response sections
34
+ const sections = this.parseStructuredResponse(content);
35
+ // Require at least SUMMARY or COMPLETED to be a valid structured response
36
+ // This prevents archiving every random message
37
+ const hasRequiredSection = sections['SUMMARY'] || sections['COMPLETED'];
38
+ if (!hasRequiredSection || Object.keys(sections).length < 2) {
39
+ return;
40
+ }
41
+ const agentRole = this.getAgentForSession(this.sessionId);
42
+ const isLearning = this.isLearningCapture(sections);
43
+ const type = this.determineArtifactType(agentRole, isLearning, sections);
44
+ await this.createArtifact(type, content, sections);
45
+ }
46
+ catch (error) {
47
+ this.logError('ProcessAssistantMessage', error);
48
+ }
14
49
  }
15
- // Get PST timestamp
16
50
  getPSTTimestamp() {
17
51
  const date = new Date();
18
52
  const pstDate = new Date(date.toLocaleString('en-US', { timeZone: process.env.TIME_ZONE || 'America/Los_Angeles' }));
@@ -31,11 +65,11 @@ export class Logger {
31
65
  const month = String(pstDate.getMonth() + 1).padStart(2, '0');
32
66
  const day = String(pstDate.getDate()).padStart(2, '0');
33
67
  const filename = `${year}-${month}-${day}_all-events.jsonl`;
34
- const filePath = getHistoryFilePath('raw-outputs', filename);
68
+ const historyDir = this.getHistoryDir();
69
+ const filePath = join(historyDir, 'raw-outputs', filename);
35
70
  const dir = dirname(filePath);
36
- if (!existsSync(dir)) {
71
+ if (!existsSync(dir))
37
72
  mkdirSync(dir, { recursive: true });
38
- }
39
73
  return filePath;
40
74
  }
41
75
  getSessionMappingFile() {
@@ -49,39 +83,24 @@ export class Logger {
49
83
  return mappings[sessionId] || 'pai';
50
84
  }
51
85
  }
52
- catch (error) {
53
- // Ignore errors, default to pai
54
- }
86
+ catch (error) { }
55
87
  return 'pai';
56
88
  }
57
89
  setAgentForSession(sessionId, agentName) {
58
90
  try {
59
91
  const mappingFile = this.getSessionMappingFile();
60
92
  let mappings = {};
61
- if (existsSync(mappingFile)) {
93
+ if (existsSync(mappingFile))
62
94
  mappings = JSON.parse(readFileSync(mappingFile, 'utf-8'));
63
- }
64
95
  mappings[sessionId] = agentName;
65
96
  writeFileSync(mappingFile, JSON.stringify(mappings, null, 2), 'utf-8');
66
97
  }
67
- catch (error) {
68
- // Silently fail - don't block
69
- }
98
+ catch (error) { }
70
99
  }
71
- logEvent(event) {
72
- // Legacy method, not used much as we use logOpenCodeEvent
73
- // But might be called from index.ts if I didn't update all calls
74
- this.logOpenCodeEvent(event);
75
- }
76
- // Method to log generic OpenCode event
77
100
  logOpenCodeEvent(event) {
78
101
  const anyEvent = event;
79
102
  const timestamp = anyEvent.timestamp || Date.now();
80
- const payload = {
81
- ...anyEvent.properties,
82
- timestamp: timestamp
83
- };
84
- // Track stats for summary
103
+ const payload = { ...anyEvent.properties, timestamp };
85
104
  if (anyEvent.type === 'tool.call' || anyEvent.type === 'tool.execute.before') {
86
105
  const props = anyEvent.properties;
87
106
  const tool = props?.tool || props?.tool_name;
@@ -93,8 +112,8 @@ export class Logger {
93
112
  this.commandsExecuted.push(redactString(command));
94
113
  }
95
114
  if (['Edit', 'Write', 'edit', 'write'].includes(tool)) {
96
- const path = props?.input?.file_path || props?.input?.path ||
97
- props?.tool_input?.file_path || props?.tool_input?.path;
115
+ const path = props?.input?.file_path || props?.input?.path || props?.input?.filePath ||
116
+ props?.tool_input?.file_path || props?.tool_input?.path || props?.tool_input?.filePath;
98
117
  if (path)
99
118
  this.filesChanged.add(path);
100
119
  }
@@ -102,19 +121,11 @@ export class Logger {
102
121
  }
103
122
  this.writeEvent(anyEvent.type, redactObject(payload));
104
123
  }
105
- /**
106
- * Log tool execution from tool.execute.after hook
107
- *
108
- * Input structure: { tool: string; sessionID: string; callID: string }
109
- * Output structure: { title: string; output: string; metadata: any }
110
- */
111
124
  logToolExecution(input, output) {
112
125
  const toolName = input.tool;
113
126
  const sessionId = this.sessionId;
114
127
  this.toolsUsed.add(toolName);
115
- // Extract metadata - may contain additional tool info
116
128
  const metadata = output.metadata || {};
117
- // Logic to update agent mapping based on Task tool spawning subagents
118
129
  if (toolName === 'Task' && metadata?.subagent_type) {
119
130
  this.setAgentForSession(sessionId, metadata.subagent_type);
120
131
  }
@@ -129,22 +140,23 @@ export class Logger {
129
140
  call_id: input.callID,
130
141
  };
131
142
  this.writeEvent('ToolUse', redactObject(payload), toolName, metadata);
143
+ if (toolName === 'task' || toolName === 'Task') {
144
+ if (output.output)
145
+ this.processAssistantMessage(output.output);
146
+ }
132
147
  }
133
148
  async generateSessionSummary() {
134
149
  try {
135
150
  const now = new Date();
136
- const timestamp = now.toISOString()
137
- .replace(/:/g, '')
138
- .replace(/\..+/, '')
139
- .replace('T', '-'); // YYYY-MM-DD-HHMMSS
151
+ const timestamp = now.toISOString().replace(/:/g, '').replace(/\..+/, '').replace('T', '-');
140
152
  const yearMonth = timestamp.substring(0, 7);
141
153
  const date = timestamp.substring(0, 10);
142
154
  const time = timestamp.substring(11).replace(/-/g, ':');
143
155
  const duration = Math.round((Date.now() - this.startTime) / 60000);
144
- const sessionDir = join(HISTORY_DIR, 'sessions', yearMonth);
145
- if (!existsSync(sessionDir)) {
156
+ const historyDir = this.getHistoryDir();
157
+ const sessionDir = join(historyDir, 'sessions', yearMonth);
158
+ if (!existsSync(sessionDir))
146
159
  mkdirSync(sessionDir, { recursive: true });
147
- }
148
160
  const focus = this.filesChanged.size > 0 ? 'development' : 'research';
149
161
  const filename = `${timestamp}_SESSION_${focus}.md`;
150
162
  const filePath = join(sessionDir, filename);
@@ -204,35 +216,19 @@ This session summary was automatically generated by the PAI OpenCode Plugin.
204
216
  return filePath;
205
217
  }
206
218
  catch (error) {
207
- this.logError('SessionSummary', error);
208
219
  return null;
209
220
  }
210
221
  }
211
- async processAssistantMessage(content) {
212
- try {
213
- const sections = this.parseStructuredResponse(content);
214
- if (Object.keys(sections).length === 0)
215
- return;
216
- const agentRole = this.getAgentForSession(this.sessionId);
217
- const isLearning = this.isLearningCapture(sections);
218
- const type = this.determineArtifactType(agentRole, isLearning, sections);
219
- await this.createArtifact(type, content, sections);
220
- }
221
- catch (error) {
222
- this.logError('ProcessAssistantMessage', error);
223
- }
224
- }
225
222
  parseStructuredResponse(content) {
226
223
  const sections = {};
227
- const sectionHeaders = [
228
- 'SUMMARY', 'ANALYSIS', 'ACTIONS', 'RESULTS', 'STATUS', 'CAPTURE', 'NEXT', 'STORY EXPLANATION', 'COMPLETED'
229
- ];
224
+ const sectionHeaders = ['SUMMARY', 'ANALYSIS', 'ACTIONS', 'RESULTS', 'STATUS', 'CAPTURE', 'NEXT', 'STORY EXPLANATION', 'COMPLETED'];
230
225
  for (const header of sectionHeaders) {
231
- const regex = new RegExp(`${header}:\\s*([\\s\\S]*?)(?=\\n(?:${sectionHeaders.join('|')}):|$)`, 'i');
226
+ // Match header with optional markdown bold (**) or other formatting
227
+ // Handles: "SUMMARY:", "**SUMMARY:**", "**SUMMARY**:", "* SUMMARY:", etc.
228
+ const regex = new RegExp(`(?:^|\\n)\\*{0,2}\\s*${header}\\s*\\*{0,2}:\\s*([\\s\\S]*?)(?=\\n\\*{0,2}\\s*(?:${sectionHeaders.join('|')})\\s*\\*{0,2}:|$)`, 'i');
232
229
  const match = content.match(regex);
233
- if (match && match[1]) {
230
+ if (match && match[1])
234
231
  sections[header] = match[1].trim();
235
- }
236
232
  }
237
233
  return sections;
238
234
  }
@@ -240,11 +236,9 @@ This session summary was automatically generated by the PAI OpenCode Plugin.
240
236
  const indicators = ['fixed', 'solved', 'discovered', 'lesson', 'troubleshoot', 'debug', 'root cause', 'learning', 'bug', 'issue', 'resolved'];
241
237
  const textToSearch = ((sections['ANALYSIS'] || '') + ' ' + (sections['RESULTS'] || '')).toLowerCase();
242
238
  let count = 0;
243
- for (const indicator of indicators) {
244
- if (textToSearch.includes(indicator)) {
239
+ for (const indicator of indicators)
240
+ if (textToSearch.includes(indicator))
245
241
  count++;
246
- }
247
- }
248
242
  return count >= 2;
249
243
  }
250
244
  determineArtifactType(agentRole, isLearning, sections) {
@@ -263,43 +257,37 @@ This session summary was automatically generated by the PAI OpenCode Plugin.
263
257
  return isLearning ? 'LEARNING' : 'WORK';
264
258
  }
265
259
  async createArtifact(type, content, sections) {
266
- const now = new Date();
267
- const timestamp = now.toISOString()
268
- .replace(/:/g, '')
269
- .replace(/\..+/, '')
270
- .replace('T', '-');
271
- const yearMonth = timestamp.substring(0, 7);
272
- const summary = sections['SUMMARY'] || 'no-summary';
273
- const slug = summary.toLowerCase()
274
- .replace(/[^\w\s-]/g, '')
275
- .replace(/\s+/g, '-')
276
- .substring(0, 50);
277
- const filename = `${timestamp}_${type}_${slug}.md`;
278
- let subdir = 'execution';
279
- if (type === 'LEARNING')
280
- subdir = 'learnings';
281
- else if (type === 'DECISION')
282
- subdir = 'decisions';
283
- else if (type === 'RESEARCH')
284
- subdir = 'research';
285
- else if (type === 'WORK')
286
- subdir = 'sessions';
287
- else {
288
- // For BUG, REFACTOR, FEATURE
289
- if (type === 'BUG')
290
- subdir = join('execution', 'bugs');
291
- else if (type === 'REFACTOR')
292
- subdir = join('execution', 'refactors');
293
- else
294
- subdir = join('execution', 'features');
295
- }
296
- const targetDir = join(HISTORY_DIR, subdir, yearMonth);
297
- if (!existsSync(targetDir)) {
298
- mkdirSync(targetDir, { recursive: true });
299
- }
300
- const filePath = join(targetDir, filename);
301
- const agentRole = this.getAgentForSession(this.sessionId);
302
- const frontmatter = `---
260
+ try {
261
+ const now = new Date();
262
+ const timestamp = now.toISOString().replace(/:/g, '').replace(/\..+/, '').replace('T', '-');
263
+ const yearMonth = timestamp.substring(0, 7);
264
+ const summary = sections['SUMMARY'] || 'no-summary';
265
+ const slug = summary.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-').substring(0, 50);
266
+ const filename = `${timestamp}_${type}_${slug}.md`;
267
+ let subdir = 'execution';
268
+ if (type === 'LEARNING')
269
+ subdir = 'learnings';
270
+ else if (type === 'DECISION')
271
+ subdir = 'decisions';
272
+ else if (type === 'RESEARCH')
273
+ subdir = 'research';
274
+ else if (type === 'WORK')
275
+ subdir = 'sessions';
276
+ else {
277
+ if (type === 'BUG')
278
+ subdir = join('execution', 'bugs');
279
+ else if (type === 'REFACTOR')
280
+ subdir = join('execution', 'refactors');
281
+ else
282
+ subdir = join('execution', 'features');
283
+ }
284
+ const historyDir = this.getHistoryDir();
285
+ const targetDir = join(historyDir, subdir, yearMonth);
286
+ if (!existsSync(targetDir))
287
+ mkdirSync(targetDir, { recursive: true });
288
+ const filePath = join(targetDir, filename);
289
+ const agentRole = this.getAgentForSession(this.sessionId);
290
+ const frontmatter = `---
303
291
  capture_type: ${type}
304
292
  timestamp: ${new Date().toISOString()}
305
293
  session_id: ${this.sessionId}
@@ -309,7 +297,11 @@ ${sections['SUMMARY'] ? `summary: ${sections['SUMMARY'].replace(/"/g, '\\"')}` :
309
297
 
310
298
  ${content}
311
299
  `;
312
- writeFileSync(filePath, redactString(frontmatter), 'utf-8');
300
+ writeFileSync(filePath, redactString(frontmatter), 'utf-8');
301
+ }
302
+ catch (e) {
303
+ this.logError('CreateArtifact', e);
304
+ }
313
305
  }
314
306
  logError(context, error) {
315
307
  try {
@@ -319,26 +311,22 @@ ${content}
319
311
  const month = String(pstDate.getMonth() + 1).padStart(2, '0');
320
312
  const day = String(pstDate.getDate()).padStart(2, '0');
321
313
  const filename = `${year}-${month}-${day}_errors.log`;
322
- const filePath = getHistoryFilePath('system-logs', filename);
314
+ const historyDir = this.getHistoryDir();
315
+ const filePath = join(historyDir, 'system-logs', filename);
323
316
  const dir = dirname(filePath);
324
- if (!existsSync(dir)) {
317
+ if (!existsSync(dir))
325
318
  mkdirSync(dir, { recursive: true });
326
- }
327
319
  const timestamp = this.getPSTTimestamp();
328
320
  const errorMessage = error instanceof Error ? error.message : String(error);
329
321
  const stack = error instanceof Error ? error.stack : '';
330
322
  const logEntry = `[${timestamp}] [${context}] ${errorMessage}\n${stack}\n-------------------\n`;
331
323
  appendFileSync(filePath, logEntry, 'utf-8');
332
324
  }
333
- catch (e) {
334
- // Intentionally silent - TUI protection
335
- }
325
+ catch (e) { }
336
326
  }
337
- // Core write method
338
327
  writeEvent(eventType, payload, toolName, toolInput) {
339
328
  const sessionId = this.sessionId;
340
329
  let agentName = this.getAgentForSession(sessionId);
341
- // Create base event object
342
330
  let hookEvent = {
343
331
  source_app: agentName,
344
332
  session_id: sessionId,
@@ -347,10 +335,8 @@ ${content}
347
335
  timestamp: Date.now(),
348
336
  timestamp_pst: this.getPSTTimestamp()
349
337
  };
350
- // Enrich with agent instance metadata if this is a Task tool call
351
338
  if (toolName && toolInput && isAgentSpawningCall(toolName, toolInput)) {
352
- hookEvent = enrichEventWithAgentMetadata(hookEvent, toolInput, payload.description // Assuming description is available in payload if passed
353
- );
339
+ hookEvent = enrichEventWithAgentMetadata(hookEvent, toolInput, payload.description);
354
340
  }
355
341
  try {
356
342
  const eventsFile = this.getEventsFilePath();
@@ -361,7 +347,5 @@ ${content}
361
347
  this.logError('EventCapture', error);
362
348
  }
363
349
  }
364
- flush() {
365
- // No-op for now as we append synchronously
366
- }
350
+ flush() { }
367
351
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fpr1m3/opencode-pai-plugin",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Personal AI Infrastructure (PAI) plugin for OpenCode",
6
6
  "main": "dist/index.js",