@polygraph/opencode-plugin 0.4.33 → 0.4.35

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/nx-ai-agents-config/main/assets/nx-logo-light.svg">
4
- <img src="https://raw.githubusercontent.com/nrwl/nx-ai-agents-config/main/assets/nx-logo.svg" alt="Nx Logo" width="140">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/polygraph-skills/source/assets/polygraph-light.svg">
4
+ <img src="https://raw.githubusercontent.com/nrwl/polygraph-skills/source/assets/polygraph.svg" alt="Polygraph Logo" width="140">
5
5
  </picture>
6
6
  </p>
7
7
 
@@ -17,7 +17,15 @@
17
17
  // - Refresh: preserves firstSeenAt when a valid prior mapping exists.
18
18
  // - All failures are silently swallowed.
19
19
 
20
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
20
+ import {
21
+ appendFileSync,
22
+ existsSync,
23
+ mkdirSync,
24
+ readFileSync,
25
+ renameSync,
26
+ statSync,
27
+ writeFileSync,
28
+ } from 'node:fs';
21
29
  import { homedir } from 'node:os';
22
30
  import path from 'node:path';
23
31
 
@@ -25,6 +33,55 @@ function sanitizeMappingFilename(str) {
25
33
  return str.replace(/[^A-Za-z0-9._-]/g, '_');
26
34
  }
27
35
 
36
+ const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
37
+
38
+ /**
39
+ * Append a one-line JSON record of a hook failure to ~/.polygraph/logs/hooks.log.
40
+ *
41
+ * Hooks otherwise swallow their errors silently (a broken hook must never break
42
+ * the agent session) and must never write to stdout — so this on-disk log is the
43
+ * only record that something went wrong. The logger is itself failure-proof:
44
+ * any error here is swallowed so a logging bug can never break a hook.
45
+ *
46
+ * @param {string} hook Identifier for the failing hook.
47
+ * @param {unknown} error The thrown value.
48
+ * @param {object} [meta] Extra context to record (sessionID, etc.).
49
+ * @param {string} [home] Override HOME for testing.
50
+ */
51
+ export function logHookFailure(
52
+ hook,
53
+ error,
54
+ meta = {},
55
+ home = process.env.HOME?.trim() || homedir()
56
+ ) {
57
+ try {
58
+ const logsDir = path.join(home, '.polygraph', 'logs');
59
+ mkdirSync(logsDir, { recursive: true });
60
+ const logFile = path.join(logsDir, 'hooks.log');
61
+
62
+ // Best-effort rotation so the log can't grow unbounded.
63
+ try {
64
+ if (statSync(logFile).size > HOOK_LOG_MAX_BYTES) {
65
+ renameSync(logFile, `${logFile}.1`);
66
+ }
67
+ } catch {
68
+ // no prior log, or rotation failed — ignore
69
+ }
70
+
71
+ const entry = {
72
+ time: new Date().toISOString(),
73
+ hook,
74
+ pid: process.pid,
75
+ ...meta,
76
+ error: error instanceof Error ? error.message : String(error),
77
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
78
+ };
79
+ appendFileSync(logFile, JSON.stringify(entry) + '\n');
80
+ } catch {
81
+ // Logging must never throw — a failing logger must not break the hook.
82
+ }
83
+ }
84
+
28
85
  /**
29
86
  * Write (or refresh) the agent-capture mapping for an OpenCode session.
30
87
  * Reads POLYGRAPH_SESSION_ID and POLYGRAPH_CHILD_AGENT from process.env.
@@ -84,7 +141,9 @@ export function writeAgentCaptureMapping(
84
141
 
85
142
  writeFileSync(tmpPath, JSON.stringify(mapping, null, 2) + '\n');
86
143
  renameSync(tmpPath, finalPath);
87
- } catch {
88
- // Silent — a broken plugin hook must never break the agent session.
144
+ } catch (error) {
145
+ // Silent toward the agent — a broken plugin hook must never break the
146
+ // session — but record it so failures are not invisible.
147
+ logHookFailure('opencode:writeAgentCaptureMapping', error, { agentSessionId }, home);
89
148
  }
90
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/opencode-plugin",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
4
4
  "description": "AI agent skills and subagents for Polygraph multi-repo coordination",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
package/server.js CHANGED
@@ -14,7 +14,7 @@ import path from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import yaml from 'js-yaml';
16
16
 
17
- import { writeAgentCaptureMapping } from './agent-capture-mapping.mjs';
17
+ import { writeAgentCaptureMapping, logHookFailure } from './agent-capture-mapping.mjs';
18
18
 
19
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
20
  const packageRoot = __dirname;
@@ -39,11 +39,16 @@ export const PolygraphPlugin = async () => {
39
39
  },
40
40
 
41
41
  'shell.env': async (input, output) => {
42
- output.env.POLYGRAPH_AGENT_SESSION_ID = input.sessionID;
43
- output.env.POLYGRAPH_AGENT_TYPE = 'opencode';
44
- // Record the agent-capture mapping so the Polygraph CLI can bind
45
- // parent-log capture deterministically for this session.
46
- writeAgentCaptureMapping(input.sessionID);
42
+ try {
43
+ output.env.POLYGRAPH_AGENT_SESSION_ID = input.sessionID;
44
+ output.env.POLYGRAPH_AGENT_TYPE = 'opencode';
45
+ // Record the agent-capture mapping so the Polygraph CLI can bind
46
+ // parent-log capture deterministically for this session.
47
+ writeAgentCaptureMapping(input.sessionID);
48
+ } catch (error) {
49
+ // Never let a hook failure break the OpenCode session; just record it.
50
+ logHookFailure('opencode:shell.env', error, { sessionID: input?.sessionID });
51
+ }
47
52
  },
48
53
 
49
54
  // OpenCode has no SessionStart hook, but the Polygraph CLI already seeds the
@@ -52,13 +57,20 @@ export const PolygraphPlugin = async () => {
52
57
  // before each compaction; the note is appended to the summarization prompt
53
58
  // (best-effort — we trust the model to keep the id + repos in the summary).
54
59
  'experimental.session.compacting': async (input, output) => {
55
- const note = polygraphCompactionNote(input.sessionID);
56
- if (note) {
57
- output.context.push(note);
60
+ try {
61
+ const note = polygraphCompactionNote(input.sessionID);
62
+ if (note) {
63
+ output.context.push(note);
64
+ }
65
+ // Refresh the mapping on compaction (same refresh semantics as Claude/Codex
66
+ // SessionStart hooks firing on 'compact').
67
+ writeAgentCaptureMapping(input.sessionID);
68
+ } catch (error) {
69
+ // Never let a hook failure break the OpenCode session; just record it.
70
+ logHookFailure('opencode:session.compacting', error, {
71
+ sessionID: input?.sessionID,
72
+ });
58
73
  }
59
- // Refresh the mapping on compaction (same refresh semantics as Claude/Codex
60
- // SessionStart hooks firing on 'compact').
61
- writeAgentCaptureMapping(input.sessionID);
62
74
  },
63
75
  };
64
76
  };