@polygraph/opencode-plugin 0.4.29 → 0.4.31
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/agent-capture-mapping.mjs +90 -0
- package/package.json +2 -1
- package/server.js +18 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Agent-capture mapping writer for the OpenCode plugin.
|
|
2
|
+
// Exported from this sibling module (NOT from server.js) so it can be tested
|
|
3
|
+
// independently. See server.js for the constraint on why server.js itself must
|
|
4
|
+
// stay export-free beyond its plugin entry.
|
|
5
|
+
//
|
|
6
|
+
// Records the (agentSessionId ↔ polygraphSessionId) mapping file so the
|
|
7
|
+
// Polygraph CLI can bind parent-log capture deterministically. Written on
|
|
8
|
+
// every session start and compaction; the CLI reader looks for mapping-*.json
|
|
9
|
+
// files in the per-session sidecars directory.
|
|
10
|
+
//
|
|
11
|
+
// File contract:
|
|
12
|
+
// ~/.polygraph/sidecars/<POLYGRAPH_SESSION_ID>/mapping-opencode-<sessionId>.json
|
|
13
|
+
//
|
|
14
|
+
// Behaviour:
|
|
15
|
+
// - Silent no-op when POLYGRAPH_SESSION_ID is unset or POLYGRAPH_CHILD_AGENT is set.
|
|
16
|
+
// - Atomic write via tmp-file rename.
|
|
17
|
+
// - Refresh: preserves firstSeenAt when a valid prior mapping exists.
|
|
18
|
+
// - All failures are silently swallowed.
|
|
19
|
+
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
function sanitizeMappingFilename(str) {
|
|
25
|
+
return str.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Write (or refresh) the agent-capture mapping for an OpenCode session.
|
|
30
|
+
* Reads POLYGRAPH_SESSION_ID and POLYGRAPH_CHILD_AGENT from process.env.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} agentSessionId The OpenCode session id (input.sessionID).
|
|
33
|
+
* @param {string} [home] Override HOME for testing.
|
|
34
|
+
*/
|
|
35
|
+
export function writeAgentCaptureMapping(
|
|
36
|
+
agentSessionId,
|
|
37
|
+
home = process.env.HOME?.trim() || homedir()
|
|
38
|
+
) {
|
|
39
|
+
try {
|
|
40
|
+
const polygraphSessionId = process.env.POLYGRAPH_SESSION_ID;
|
|
41
|
+
if (!polygraphSessionId) return;
|
|
42
|
+
if (process.env.POLYGRAPH_CHILD_AGENT) return;
|
|
43
|
+
if (!agentSessionId) return;
|
|
44
|
+
|
|
45
|
+
const sidecarDir = path.join(home, '.polygraph', 'sidecars', polygraphSessionId);
|
|
46
|
+
mkdirSync(sidecarDir, { recursive: true });
|
|
47
|
+
|
|
48
|
+
const filenamePart = sanitizeMappingFilename(`opencode-${agentSessionId}`);
|
|
49
|
+
const finalPath = path.join(sidecarDir, `mapping-${filenamePart}.json`);
|
|
50
|
+
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
51
|
+
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
let firstSeenAt = now;
|
|
54
|
+
|
|
55
|
+
if (existsSync(finalPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const existing = JSON.parse(readFileSync(finalPath, 'utf8'));
|
|
58
|
+
if (
|
|
59
|
+
existing.version === 1 &&
|
|
60
|
+
existing.polygraphSessionId === polygraphSessionId &&
|
|
61
|
+
existing.agentSessionId === agentSessionId &&
|
|
62
|
+
Number.isFinite(existing.firstSeenAt)
|
|
63
|
+
) {
|
|
64
|
+
firstSeenAt = existing.firstSeenAt;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// ignore — treat as missing
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const mapping = {
|
|
72
|
+
version: 1,
|
|
73
|
+
polygraphSessionId,
|
|
74
|
+
agentType: 'opencode',
|
|
75
|
+
agentSessionId,
|
|
76
|
+
cwd: process.cwd(),
|
|
77
|
+
// OpenCode transcripts are resolved by the CLI from its own storage by
|
|
78
|
+
// session id, so we omit transcriptPath here.
|
|
79
|
+
pid: process.pid,
|
|
80
|
+
source: 'hook',
|
|
81
|
+
firstSeenAt,
|
|
82
|
+
lastSeenAt: now,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
writeFileSync(tmpPath, JSON.stringify(mapping, null, 2) + '\n');
|
|
86
|
+
renameSync(tmpPath, finalPath);
|
|
87
|
+
} catch {
|
|
88
|
+
// Silent — a broken plugin hook must never break the agent session.
|
|
89
|
+
}
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polygraph/opencode-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.31",
|
|
4
4
|
"description": "AI agent skills and subagents for Polygraph multi-repo coordination",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"server.js",
|
|
27
|
+
"agent-capture-mapping.mjs",
|
|
27
28
|
"skills/",
|
|
28
29
|
"agents/",
|
|
29
30
|
"README.md"
|
package/server.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
// ⚠️ IMPORTANT — READ BEFORE EDITING OR ADDING CODE TO THIS FILE ⚠️
|
|
2
|
+
//
|
|
3
|
+
// OpenCode loads this file directly as a plugin module. Any `export` beyond
|
|
4
|
+
// the plugin entry (`PolygraphPlugin` / `export default`) breaks OpenCode
|
|
5
|
+
// plugin loading ENTIRELY for every user who has the plugin installed.
|
|
6
|
+
//
|
|
7
|
+
// DO NOT add new exports to this file. Put shared or testable logic in sibling
|
|
8
|
+
// modules under source/opencode/ (e.g. agent-capture-mapping.mjs) and import
|
|
9
|
+
// it here instead.
|
|
10
|
+
|
|
1
11
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
12
|
import { homedir } from 'node:os';
|
|
3
13
|
import path from 'node:path';
|
|
4
14
|
import { fileURLToPath } from 'node:url';
|
|
5
15
|
import yaml from 'js-yaml';
|
|
6
16
|
|
|
17
|
+
import { writeAgentCaptureMapping } from './agent-capture-mapping.mjs';
|
|
18
|
+
|
|
7
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
20
|
const packageRoot = __dirname;
|
|
9
21
|
const skillsDir = path.join(packageRoot, 'skills');
|
|
@@ -29,6 +41,9 @@ export const PolygraphPlugin = async () => {
|
|
|
29
41
|
'shell.env': async (input, output) => {
|
|
30
42
|
output.env.POLYGRAPH_AGENT_SESSION_ID = input.sessionID;
|
|
31
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);
|
|
32
47
|
},
|
|
33
48
|
|
|
34
49
|
// OpenCode has no SessionStart hook, but the Polygraph CLI already seeds the
|
|
@@ -41,6 +56,9 @@ export const PolygraphPlugin = async () => {
|
|
|
41
56
|
if (note) {
|
|
42
57
|
output.context.push(note);
|
|
43
58
|
}
|
|
59
|
+
// Refresh the mapping on compaction (same refresh semantics as Claude/Codex
|
|
60
|
+
// SessionStart hooks firing on 'compact').
|
|
61
|
+
writeAgentCaptureMapping(input.sessionID);
|
|
44
62
|
},
|
|
45
63
|
};
|
|
46
64
|
};
|