@polygraph/opencode-plugin 0.4.24 → 0.4.26

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/opencode-plugin",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
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
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
2
3
  import path from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import yaml from 'js-yaml';
@@ -29,6 +30,18 @@ export const PolygraphPlugin = async () => {
29
30
  output.env.POLYGRAPH_AGENT_SESSION_ID = input.sessionID;
30
31
  output.env.POLYGRAPH_AGENT_TYPE = 'opencode';
31
32
  },
33
+
34
+ // OpenCode has no SessionStart hook, but the Polygraph CLI already seeds the
35
+ // session id into context at launch. The one thing compaction can drop is
36
+ // that identity, so we steer the summary prompt to retain it. This fires
37
+ // before each compaction; the note is appended to the summarization prompt
38
+ // (best-effort — we trust the model to keep the id + repos in the summary).
39
+ 'experimental.session.compacting': async (input, output) => {
40
+ const note = polygraphCompactionNote(input.sessionID);
41
+ if (note) {
42
+ output.context.push(note);
43
+ }
44
+ },
32
45
  };
33
46
  };
34
47
 
@@ -92,3 +105,82 @@ function numberValue(value) {
92
105
  function recordValue(value) {
93
106
  return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
94
107
  }
108
+
109
+ // --- Polygraph session context (mirrors source/hooks/reinject-polygraph-context.mjs) ---
110
+ //
111
+ // Resolve the Polygraph session for this OpenCode session id from local state,
112
+ // then build a short instruction telling the summarizer to keep the Polygraph
113
+ // session id and repo list in the compaction summary. Returns null when this is
114
+ // not a Polygraph session (no matching parent-log sidecar) — a silent no-op.
115
+ //
116
+ // These helpers must NOT be exported: OpenCode calls every export of this
117
+ // module as a plugin factory, and a factory that doesn't return a hooks object
118
+ // crashes the server on startup ("Unexpected server error").
119
+
120
+ function polygraphCompactionNote(agentSessionId, root) {
121
+ const session = readPolygraphSession(agentSessionId, root);
122
+ if (!session) {
123
+ return undefined;
124
+ }
125
+
126
+ const repos = session.repos
127
+ .map((repo) => repo.repoFullName)
128
+ .filter(Boolean)
129
+ .join(', ');
130
+
131
+ return (
132
+ `This conversation is running inside Polygraph session ${session.sessionId}` +
133
+ (repos ? ` (repos: ${repos})` : '') +
134
+ '. Preserve the Polygraph session id and the repo list verbatim in the ' +
135
+ 'summary so they remain available after compaction.'
136
+ );
137
+ }
138
+
139
+ function readPolygraphSession(
140
+ agentSessionId,
141
+ root = path.join(homedir(), '.polygraph')
142
+ ) {
143
+ if (!agentSessionId) {
144
+ return undefined;
145
+ }
146
+
147
+ const sidecarsDir = path.join(root, 'sidecars');
148
+ if (!existsSync(sidecarsDir)) {
149
+ return undefined;
150
+ }
151
+
152
+ const fileName = `parent-${agentSessionId}.json`;
153
+ let polygraphSessionId;
154
+ for (const entry of readdirSync(sidecarsDir, { withFileTypes: true })) {
155
+ if (!entry.isDirectory()) continue;
156
+ const candidate = path.join(sidecarsDir, entry.name, fileName);
157
+ if (existsSync(candidate)) {
158
+ try {
159
+ polygraphSessionId = JSON.parse(readFileSync(candidate, 'utf8')).sessionId;
160
+ } catch {
161
+ polygraphSessionId = undefined;
162
+ }
163
+ break;
164
+ }
165
+ }
166
+ if (!polygraphSessionId) {
167
+ return undefined;
168
+ }
169
+
170
+ let session = {};
171
+ try {
172
+ session = JSON.parse(
173
+ readFileSync(
174
+ path.join(root, 'sessions', polygraphSessionId, 'session', 'session.json'),
175
+ 'utf8'
176
+ )
177
+ );
178
+ } catch {
179
+ session = {};
180
+ }
181
+
182
+ return {
183
+ sessionId: polygraphSessionId,
184
+ repos: Array.isArray(session.repos) ? session.repos : [],
185
+ };
186
+ }
@@ -105,7 +105,7 @@ The subagent will:
105
105
  4. Call `show_session` to retrieve session details
106
106
  5. Return a summary with session URL and repo info
107
107
 
108
- **Case C only — new session just created:** After the init subagent completes and creates the new session, render the session welcome card (skip the session-details block below for this case). Prefer the `session_intro` MCP tool — call it with the session ID; it returns the card as markdown. If that tool is unavailable, run `polygraph session intro -s <sessionId>` via the CLI instead. Either way, print the result to the user verbatim as markdown — do NOT wrap it in a code block or reformat it (the logo is pre-fenced; the rest is live markdown). It needs no other input, and you do not need to call `show_session` first. Then continue (ask the user what they want, or start the requested task).
108
+ **Case C only — new session just created:** After the init subagent completes and creates the new session, render the session welcome card (skip the session-details block below for this case). Prefer the `session_intro` MCP tool — call it with the session ID; it returns the card as markdown. If that tool is unavailable, run `polygraph session intro -s <sessionId>` via the CLI instead. The CLI command is intentionally hidden/internal and may not appear in public command listings, but it remains the correct skill fallback for rendering the welcome card. Either way, print the result to the user verbatim as markdown — do NOT wrap it in a code block or reformat it (the logo is pre-fenced; the rest is live markdown). It needs no other input, and you do not need to call `show_session` first. Then continue (ask the user what they want, or start the requested task).
109
109
 
110
110
  **After receiving the subagent's summary (Case B) or after calling `show_session` for an existing session (Case A), print the session details:**
111
111