@polygraph/claude-plugin 0.4.52 → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygraph",
3
- "version": "0.4.52",
3
+ "version": "0.5.0",
4
4
  "description": "AI agent skills and subagents for Polygraph sessions, repository context, and coordination",
5
5
  "author": {
6
6
  "name": "Narwhal Technologies Inc",
@@ -8,6 +8,13 @@ const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
8
8
  const AGENT_TYPES = new Set(['claude', 'codex', 'opencode', 'cursor']);
9
9
  const COMMAND_HOOK_TOOL = /^mcp__(?:plugin_polygraph_)?polygraph[-_]mcp__/;
10
10
  const OPENCODE_TOOL = /^polygraph(?:(?:-|_)mcp)?_/;
11
+ // Cursor reports MCP tools as `MCP:<tool>` with no server namespace, so the
12
+ // shim filters on the claim-worthy tool names to avoid spawning the CLI on
13
+ // every unrelated MCP call. This list is an optimization mirror of
14
+ // PARENT_CLAIM_POLICIES in the Polygraph CLI (parent-session-claim-evidence);
15
+ // classification authority stays in the CLI.
16
+ const CURSOR_MCP_CLAIM_TOOL =
17
+ /^MCP:(?:add_repo|allow_agent|archive_session|associate_pr|create_pr|deny_agent|git_fetch|link_reference|mark_pr_ready|pack_and_copy|push_branch|spawn_agent|start_session|stop_agent|update_session|upload_artifact)$/;
11
18
 
12
19
  function nonEmptyString(value) {
13
20
  return typeof value === 'string' && value.trim() ? value : undefined;
@@ -30,6 +37,7 @@ export function buildLinkAgentSessionArgs({
30
37
  transcriptPath,
31
38
  pid,
32
39
  source,
40
+ hookOperation,
33
41
  }) {
34
42
  const session = nonEmptyString(polygraphSessionId);
35
43
  const harnessSession = nonEmptyString(agentSessionId);
@@ -52,8 +60,21 @@ export function buildLinkAgentSessionArgs({
52
60
  args.push('--pid', String(pid));
53
61
  }
54
62
 
63
+ // Cursor post-tool evidence rides the hook payload (the transcript stores
64
+ // no tool results); forwarded verbatim, classified by the CLI. The
65
+ // operation travels on STDIN, never argv: toolInput can carry an entire
66
+ // upload_artifact document and Linux caps one argv string at 128KB, so an
67
+ // inline argument would kill the spawn with E2BIG and silently lose the
68
+ // evidence. The flag tells the CLI to read stdin; older strict CLIs
69
+ // reject it, which is why publication is gated on the Ocean deployment.
70
+ let input;
71
+ if (hookOperation && typeof hookOperation === 'object') {
72
+ args.push('--hook-operation-stdin');
73
+ input = JSON.stringify(hookOperation);
74
+ }
75
+
55
76
  args.push('--source', claimSource);
56
- return args;
77
+ return { args, input };
57
78
  }
58
79
 
59
80
  /**
@@ -70,7 +91,7 @@ function nodeRuntime() {
70
91
  export function linkAgentSession(claim, spawn = spawnSync, env = process.env) {
71
92
  if (isManagedChildEnvironment(env)) return false;
72
93
 
73
- const args = buildLinkAgentSessionArgs(claim);
94
+ const { args, input } = buildLinkAgentSessionArgs(claim);
74
95
  const command = nonEmptyString(env?.POLYGRAPH_CLI) ?? 'polygraph';
75
96
  const commandEnv = nonEmptyString(claim.polygraphSessionId) ? env : { ...env };
76
97
  if (commandEnv !== env) {
@@ -81,7 +102,8 @@ export function linkAgentSession(claim, spawn = spawnSync, env = process.env) {
81
102
  const spawnOptions = {
82
103
  encoding: 'utf8',
83
104
  env: commandEnv,
84
- stdio: ['ignore', 'ignore', 'pipe'],
105
+ stdio: [input === undefined ? 'ignore' : 'pipe', 'ignore', 'pipe'],
106
+ ...(input === undefined ? {} : { input }),
85
107
  };
86
108
 
87
109
  let result = spawn(command, args, spawnOptions);
@@ -148,6 +170,24 @@ export function buildCommandHookLink(payload, agentType, env = process.env) {
148
170
  return isPolygraphMcpToolName(payload.tool_name) ? common : undefined;
149
171
  }
150
172
 
173
+ // Cursor's camelCase postToolUse: the payload carries the whole operation
174
+ // (tool_name `MCP:<tool>`, tool_input, tool_output) and is forwarded as
175
+ // evidence because the cursor transcript stores no tool results and lags
176
+ // the hook. The CLI classifies the operation; this filter only avoids
177
+ // spawning the CLI for unrelated tools.
178
+ if (payload.hook_event_name === 'postToolUse') {
179
+ const toolName = nonEmptyString(payload.tool_name);
180
+ if (!toolName || !CURSOR_MCP_CLAIM_TOOL.test(toolName)) return undefined;
181
+ return {
182
+ ...common,
183
+ hookOperation: {
184
+ toolName,
185
+ toolInput: payload.tool_input,
186
+ toolOutput: payload.tool_output,
187
+ },
188
+ };
189
+ }
190
+
151
191
  return undefined;
152
192
  }
153
193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/claude-plugin",
3
- "version": "0.4.52",
3
+ "version": "0.5.0",
4
4
  "description": "AI agent skills and subagents for Polygraph sessions, repository context, and coordination",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -40,7 +40,7 @@ For each id, launch one background poller subagent whose entire job is to block
40
40
 
41
41
  - **Claude Code** — a background `Task` with `subagent_type: "polygraph:polygraph-delegate-subagent"`, `run_in_background: true`, and description `Delegate to <repo>`. Fall back to the bare agent name only if the namespaced form is not found.
42
42
  - **OpenCode** — invoke `@polygraph-delegate-subagent`.
43
- - **Codex** — launch `agent_type: "polygraph-delegate-subagent"` via Codex's own `spawn_agent`, and collect it with `wait_agent`.
43
+ - **Codex** — launch `agent_type: "polygraph-delegate-subagent"` via Codex's own `spawn_agent`, and collect it with `wait_agent`, passing a long `timeout_ms` (five minutes or more): `wait_agent` returns as soon as the poller stops, so a short timeout only adds wake-ups that burn tokens and fill the user-visible transcript with waiting noise.
44
44
  - **Cursor** — a background `Task` with `subagent_type: "polygraph-delegate-subagent"`, `run_in_background: true`, and description `Delegate to <repo>`. Collect it with `Await`.
45
45
 
46
46
  A collect step that returns while the poller subagent is still running has not failed. It has only reached the end of its collection window. Collect the same background-task id again, as many times as it takes for the poller subagent to stop. A poller that runs for several minutes is ordinary, and it is never a reason to take the wait back into the main conversation.