@plannotator/pi-extension 0.28.38 → 0.28.39
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/generated/ai/providers/ask-ai-env.ts +217 -0
- package/generated/ai/providers/claude-agent-sdk.ts +19 -0
- package/generated/ai/providers/codex-app-server.ts +13 -5
- package/generated/ai/providers/opencode-sdk.ts +13 -5
- package/generated/ai/providers/pi-events.ts +1 -1
- package/generated/ai/providers/pi-sdk-node.ts +5 -0
- package/generated/ai/providers/pi-sdk.ts +6 -2
- package/generated/ai/providers/qwen-sdk.ts +5 -1
- package/generated/call-flow-languages.ts +4 -2
- package/generated/call-flow-runtime/package-lock.json +7 -7
- package/generated/call-flow-runtime/package.json +2 -2
- package/generated/call-flow.ts +149 -32
- package/generated/child-env.ts +12 -0
- package/generated/claude-review.ts +3 -0
- package/generated/codex-review.ts +3 -0
- package/generated/guide-review.ts +3 -0
- package/generated/guide-viewer-manifest.ts +4 -4
- package/generated/inbox/connection.ts +59 -16
- package/generated/question-block.ts +2 -2
- package/generated/tour-review.ts +3 -0
- package/generated/update-check.ts +62 -15
- package/package.json +1 -1
- package/plannotator.html +85 -85
- package/review-editor.html +422 -397
- package/server/agent-jobs-ask-ai-child.test.ts +149 -0
- package/server/agent-jobs.ts +5 -1
- package/server/serverReview.ts +4 -0
- package/skills/plannotator/SKILL.md +3 -3
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// @generated — DO NOT EDIT. Source: packages/ai/providers/ask-ai-env.ts
|
|
2
|
+
/**
|
|
3
|
+
* The environment of every agent process Ask AI starts (Claude, Codex,
|
|
4
|
+
* OpenCode, Pi, Qwen Code): the live env with Plannotator's own integration
|
|
5
|
+
* switched off inside it.
|
|
6
|
+
*
|
|
7
|
+
* Ask AI drives an agent headlessly, and that agent loads the person's
|
|
8
|
+
* configuration, Plannotator's integration included: the Inbox MCP server
|
|
9
|
+
* (`plannotator inbox mcp`), the agent tool, and the plan hooks (Claude Code's
|
|
10
|
+
* ExitPlanMode PermissionRequest, the Codex Stop hook, Qwen Code's
|
|
11
|
+
* exit_plan_mode). Left on, a question could post to the person's Inbox or
|
|
12
|
+
* block on a plan review nobody is waiting for. So the child gets:
|
|
13
|
+
*
|
|
14
|
+
* - `PLANNOTATOR_INBOX_TOOL=0` and `PLANNOTATOR_AGENT_TOOL=0`: the switches
|
|
15
|
+
* the Claude Code mod, the OpenCode plugin and the Pi extension already
|
|
16
|
+
* read, and which win over config.json (`resolveInboxTool` /
|
|
17
|
+
* `resolveAgentTool` in packages/shared/config.ts).
|
|
18
|
+
* - `PLANNOTATOR_ASK_AI_CHILD=1`: the marker the rest honours
|
|
19
|
+
* (`isAskAiChild` in packages/shared/child-env.ts): the Inbox MCP shim
|
|
20
|
+
* lists no tools, the plan-review hook (`plannotator` with no arguments)
|
|
21
|
+
* exits 0 at once, and the OpenCode plugin stands down.
|
|
22
|
+
*
|
|
23
|
+
* Codex does not pass the env to a stdio MCP server, so codex-app-server.ts
|
|
24
|
+
* also turns the Inbox server off by config (`codexAskAiConfigArgs`, below).
|
|
25
|
+
* Claude sessions disallow it by name (claude-agent-sdk.ts).
|
|
26
|
+
*
|
|
27
|
+
* The review server's agent jobs (code review, Code Tour, Guided Review,
|
|
28
|
+
* `agent-jobs.ts` in both runtimes) get the same env and, for `codex exec`,
|
|
29
|
+
* the same `-c` override: none of them uses Plannotator's integration (each
|
|
30
|
+
* returns its result on stdout or in its `-o` file).
|
|
31
|
+
*
|
|
32
|
+
* Bun-free and Node-safe: vendored into the Pi extension.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
36
|
+
import { homedir } from "node:os";
|
|
37
|
+
import { dirname, join, resolve } from "node:path";
|
|
38
|
+
|
|
39
|
+
/** The marker's name. Only the exact value "1" counts. */
|
|
40
|
+
export const ASK_AI_CHILD_ENV = "PLANNOTATOR_ASK_AI_CHILD";
|
|
41
|
+
|
|
42
|
+
/** What `askAiChildEnv` lays over the env it copies. */
|
|
43
|
+
export const ASK_AI_CHILD_SWITCHES: Readonly<Record<string, string>> = {
|
|
44
|
+
PLANNOTATOR_INBOX_TOOL: "0",
|
|
45
|
+
PLANNOTATOR_AGENT_TOOL: "0",
|
|
46
|
+
[ASK_AI_CHILD_ENV]: "1",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** A copy of `env` (default: the LIVE process.env) with Plannotator's integration off. */
|
|
50
|
+
export function askAiChildEnv(
|
|
51
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
52
|
+
): Record<string, string | undefined> {
|
|
53
|
+
return { ...env, ...ASK_AI_CHILD_SWITCHES };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Run `start` with `ASK_AI_CHILD_SWITCHES` set on process.env, and put
|
|
58
|
+
* process.env back before returning. Only for a spawn API that takes no env
|
|
59
|
+
* and copies process.env synchronously inside the call: the OpenCode SDK's
|
|
60
|
+
* `createOpencodeServer` (its `cross-spawn` call with `{ ...process.env }`
|
|
61
|
+
* comes before its first await). JavaScript runs one call at a time, so no
|
|
62
|
+
* other code, a concurrent Ask AI start included, sees the change.
|
|
63
|
+
* `ask-ai-env.test.ts` runs the installed SDK against a fake `opencode` to
|
|
64
|
+
* keep that true.
|
|
65
|
+
*/
|
|
66
|
+
export function withAskAiChildEnv<T>(start: () => T): T {
|
|
67
|
+
const saved = Object.keys(ASK_AI_CHILD_SWITCHES).map((key) => [key, process.env[key]] as const);
|
|
68
|
+
Object.assign(process.env, ASK_AI_CHILD_SWITCHES);
|
|
69
|
+
try {
|
|
70
|
+
return start();
|
|
71
|
+
} finally {
|
|
72
|
+
for (const [key, value] of saved) {
|
|
73
|
+
if (value === undefined) delete process.env[key];
|
|
74
|
+
else process.env[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The Inbox MCP server Plannotator connects to Codex (`INBOX_MCP_SERVER_NAME` in packages/server/inbox-agents.ts). */
|
|
80
|
+
const CODEX_INBOX_SERVER = "plannotator-inbox";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The global `-c` override every `codex app-server` that Ask AI starts gets,
|
|
84
|
+
* and every `codex exec` agent job of the review server (code review, Code
|
|
85
|
+
* Tour, Guided Review). It turns off the Inbox MCP server. The marker in the
|
|
86
|
+
* agent's env (`askAiChildEnv`) cannot do this, because Codex starts a stdio MCP server with
|
|
87
|
+
* a cleared environment that holds only HOME, PATH and a few others
|
|
88
|
+
* (codex-rs/rmcp-client `create_env_for_mcp_server`). Codex hooks get the
|
|
89
|
+
* whole env, so the Stop hook does see the marker.
|
|
90
|
+
*
|
|
91
|
+
* Codex deep-merges `-c` tables over config.toml and then rejects an entry
|
|
92
|
+
* that has both `command` and `url`, or neither. So:
|
|
93
|
+
* - the person's entry has a `url` (the HTTP transport): only
|
|
94
|
+
* `enabled=false`, because the entry already has its transport;
|
|
95
|
+
* - otherwise: a whole disabled stdio server. It is merged over a stdio entry,
|
|
96
|
+
* or added when there is none. Its own `env` carries the marker, so a Codex
|
|
97
|
+
* that ignored `enabled` would still start a shim that lists no tools.
|
|
98
|
+
* `configTexts` is every config.toml Codex merges for the run
|
|
99
|
+
* (`readCodexConfigTexts`): a `url` in any of them, the person's or the
|
|
100
|
+
* project's, picks the first form. Gap, not handled (rare): Codex ignores the
|
|
101
|
+
* config of a project it does not trust, so a `url` entry only there, with no
|
|
102
|
+
* entry in `$CODEX_HOME`, leaves `enabled=false` with no transport, and Codex
|
|
103
|
+
* (without `--strict-config`) warns "Invalid configuration; using defaults."
|
|
104
|
+
* and runs that session or job on its default config. Literal strings and no
|
|
105
|
+
* spaces: the argument stays unquoted on the cmd.exe line of a Windows `.cmd`
|
|
106
|
+
* shim.
|
|
107
|
+
*/
|
|
108
|
+
export function codexAskAiConfigArgs(configTexts: string | null | readonly string[]): string[] {
|
|
109
|
+
const texts = configTexts === null ? [] : typeof configTexts === "string" ? [configTexts] : configTexts;
|
|
110
|
+
if (texts.some((text) => codexInboxEntryHasUrl(text))) {
|
|
111
|
+
return ["-c", `mcp_servers.${CODEX_INBOX_SERVER}.enabled=false`];
|
|
112
|
+
}
|
|
113
|
+
return [
|
|
114
|
+
"-c",
|
|
115
|
+
`mcp_servers.${CODEX_INBOX_SERVER}={command='plannotator',args=['inbox','mcp'],env={PLANNOTATOR_ASK_AI_CHILD='1'},enabled=false}`,
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The config.toml files Codex reads for a run in `cwd`, read only, the
|
|
121
|
+
* unreadable ones left out: `$CODEX_HOME/config.toml` (a relative CODEX_HOME
|
|
122
|
+
* resolved against `cwd`, as Codex does; else `~/.codex`), then the
|
|
123
|
+
* `.codex/config.toml` of each folder from `cwd` up to the project root, the
|
|
124
|
+
* nearest folder with a `.git` (a folder holding `HEAD`, or a worktree's
|
|
125
|
+
* file). With no root, only `cwd`'s. The `.codex` that is CODEX_HOME is not
|
|
126
|
+
* read twice. This is Codex's own walk (codex-rs/config `discover_project_root`
|
|
127
|
+
* and `discover_project_layers`), with its default `project_root_markers`.
|
|
128
|
+
*/
|
|
129
|
+
export function readCodexConfigTexts(cwd: string, env: Readonly<Record<string, string | undefined>> = process.env): string[] {
|
|
130
|
+
const start = resolve(cwd);
|
|
131
|
+
const home = env.CODEX_HOME?.trim();
|
|
132
|
+
const codexHome = home ? resolve(start, home) : join(homedir(), ".codex");
|
|
133
|
+
const dirs: string[] = [];
|
|
134
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
135
|
+
dirs.push(dir);
|
|
136
|
+
if (isGitRoot(dir)) break;
|
|
137
|
+
if (dirname(dir) === dir) {
|
|
138
|
+
dirs.length = 1;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const folders = [codexHome, ...dirs.map((dir) => join(dir, ".codex")).filter((dot) => dot !== codexHome)];
|
|
143
|
+
return folders.flatMap((folder) => {
|
|
144
|
+
try {
|
|
145
|
+
return [readFileSync(join(folder, "config.toml"), "utf8")];
|
|
146
|
+
} catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Whether `dir` holds a `.git` that Codex takes as the project root marker. */
|
|
153
|
+
function isGitRoot(dir: string): boolean {
|
|
154
|
+
try {
|
|
155
|
+
const git = statSync(join(dir, ".git"));
|
|
156
|
+
return git.isFile() || (git.isDirectory() && existsSync(join(dir, ".git", "HEAD")));
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** A TOML key (`a."b-c".d`) as its parts; null when the text is not a plain key. */
|
|
163
|
+
function tomlKeyPath(text: string): string[] | null {
|
|
164
|
+
const part = /\s*(?:"((?:[^"\\]|\\.)*)"|'([^']*)'|([A-Za-z0-9_-]+))\s*(\.|$)/y;
|
|
165
|
+
const parts: string[] = [];
|
|
166
|
+
let at = 0;
|
|
167
|
+
while (at < text.length) {
|
|
168
|
+
part.lastIndex = at;
|
|
169
|
+
const match = part.exec(text);
|
|
170
|
+
if (!match) return null;
|
|
171
|
+
parts.push(match[1] ?? match[2] ?? match[3]!);
|
|
172
|
+
at = part.lastIndex;
|
|
173
|
+
if (match[4] !== ".") break;
|
|
174
|
+
}
|
|
175
|
+
return parts.length > 0 && at >= text.length ? parts : null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The first key of each top-level member of an inline table (`{ url = "…", env = { … } }`). */
|
|
179
|
+
function inlineTableKeys(value: string): string[] {
|
|
180
|
+
let inner = value.replace(/"(?:[^"\\]|\\.)*"|'[^']*'/g, '""').trim();
|
|
181
|
+
if (!inner.startsWith("{")) return [];
|
|
182
|
+
const close = inner.lastIndexOf("}");
|
|
183
|
+
inner = inner.slice(1, close < 0 ? undefined : close);
|
|
184
|
+
// Drop nested tables and arrays, innermost first, so only top-level members remain.
|
|
185
|
+
const nested = /[{[][^{}[\]]*[}\]]/;
|
|
186
|
+
while (nested.test(inner)) inner = inner.replace(new RegExp(nested.source, "g"), "");
|
|
187
|
+
return inner
|
|
188
|
+
.split(",")
|
|
189
|
+
.map((member) => tomlKeyPath(member.slice(0, Math.max(0, member.indexOf("="))))?.[0])
|
|
190
|
+
.filter((key): key is string => key !== undefined);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Whether config.toml's `mcp_servers.plannotator-inbox` has a `url`, in any
|
|
195
|
+
* form: its `[mcp_servers.plannotator-inbox]` table, an inline table, or
|
|
196
|
+
* dotted keys. A line scan, not a full TOML parser: something it cannot read
|
|
197
|
+
* counts as no `url`.
|
|
198
|
+
*/
|
|
199
|
+
export function codexInboxEntryHasUrl(configText: string): boolean {
|
|
200
|
+
let table: string[] = [];
|
|
201
|
+
for (const raw of configText.split(/\r?\n/)) {
|
|
202
|
+
const line = raw.trim();
|
|
203
|
+
if (!line || line.startsWith("#")) continue;
|
|
204
|
+
const header = /^\[\[?([^\]]*)\]\]?\s*(?:#.*)?$/.exec(line);
|
|
205
|
+
if (header) {
|
|
206
|
+
table = tomlKeyPath(header[1]!) ?? [""];
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const eq = line.indexOf("=");
|
|
210
|
+
const key = eq > 0 ? tomlKeyPath(line.slice(0, eq)) : null;
|
|
211
|
+
if (!key) continue;
|
|
212
|
+
const path = [...table, ...key];
|
|
213
|
+
if (path[0] !== "mcp_servers" || path[1] !== CODEX_INBOX_SERVER) continue;
|
|
214
|
+
if (path.length > 2 ? path[2] === "url" : inlineTableKeys(line.slice(eq + 1)).includes("url")) return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type CatalogModel,
|
|
31
31
|
type ClaudeSdkModelInfo,
|
|
32
32
|
} from '../model-catalog.ts';
|
|
33
|
+
import { askAiChildEnv } from "./ask-ai-env.ts";
|
|
33
34
|
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
// Constants
|
|
@@ -162,8 +163,20 @@ interface ClaudeSDKQueryOptions {
|
|
|
162
163
|
settingSources?: string[];
|
|
163
164
|
effort?: string;
|
|
164
165
|
strictMcpConfig?: boolean;
|
|
166
|
+
/** The whole env of the `claude` process (the SDK does not merge it with process.env). */
|
|
167
|
+
env: Record<string, string | undefined>;
|
|
168
|
+
disallowedTools: string[];
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Removed from every Ask AI session whatever the person's settings allow: the
|
|
173
|
+
* Plannotator Inbox MCP server, by its whole-server name (`mcp__<server>`, a
|
|
174
|
+
* server-level spec the SDK's `disallowedTools` takes). This covers an
|
|
175
|
+
* HTTP-transport `plannotator-inbox` entry too, which the env marker
|
|
176
|
+
* (ask-ai-env.ts) cannot reach.
|
|
177
|
+
*/
|
|
178
|
+
const ASK_AI_DISALLOWED_TOOLS = ["mcp__plannotator-inbox"];
|
|
179
|
+
|
|
167
180
|
// ---------------------------------------------------------------------------
|
|
168
181
|
// Provider
|
|
169
182
|
// ---------------------------------------------------------------------------
|
|
@@ -281,6 +294,7 @@ export class ClaudeAgentSDKProvider implements AIProvider {
|
|
|
281
294
|
// option out, so the SDK loads every source, like the CLI.
|
|
282
295
|
settingSources: [],
|
|
283
296
|
strictMcpConfig: true,
|
|
297
|
+
env: askAiChildEnv(),
|
|
284
298
|
...(this.config.claudeExecutablePath && {
|
|
285
299
|
pathToClaudeCodeExecutable: this.config.claudeExecutablePath,
|
|
286
300
|
}),
|
|
@@ -462,10 +476,15 @@ class ClaudeAgentSDKSession extends BaseSession {
|
|
|
462
476
|
...(this.config.effort && { effort: this.config.effort }),
|
|
463
477
|
maxTurns: this.config.maxTurns,
|
|
464
478
|
allowedTools: this.config.allowedTools,
|
|
479
|
+
disallowedTools: ASK_AI_DISALLOWED_TOOLS,
|
|
465
480
|
cwd: this.config.cwd,
|
|
466
481
|
abortController: this._currentAbort!,
|
|
467
482
|
includePartialMessages: true,
|
|
468
483
|
persistSession: true,
|
|
484
|
+
// Plannotator's integration off in this `claude` (ask-ai-env.ts): the
|
|
485
|
+
// plugin's ExitPlanMode hook exits at once instead of opening a review.
|
|
486
|
+
// The SDK replaces the child's env with this, so it is the full live env.
|
|
487
|
+
env: askAiChildEnv(),
|
|
469
488
|
...(this.config.claudeExecutablePath && {
|
|
470
489
|
pathToClaudeCodeExecutable: this.config.claudeExecutablePath,
|
|
471
490
|
}),
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
resolveWindowsCommandShim,
|
|
46
46
|
} from "./command-path.ts";
|
|
47
47
|
import { guardChildStreams, writeChildLine } from "./child-io.ts";
|
|
48
|
+
import { askAiChildEnv, codexAskAiConfigArgs, readCodexConfigTexts } from "./ask-ai-env.ts";
|
|
48
49
|
import { CODEX_FALLBACK_MODELS, cliVersionFrom, effortList, type CatalogModel } from '../model-catalog.ts';
|
|
49
50
|
|
|
50
51
|
const PROVIDER_NAME = "codex-sdk";
|
|
@@ -68,6 +69,10 @@ const FILE_APPROVAL_METHOD = "item/fileChange/requestApproval";
|
|
|
68
69
|
// response shape from the two above ({permissions} grant, not {decision}).
|
|
69
70
|
const PERMISSIONS_APPROVAL_METHOD = "item/permissions/requestApproval";
|
|
70
71
|
|
|
72
|
+
// The `-c` override that turns the Inbox MCP server off lives in
|
|
73
|
+
// ask-ai-env.ts, so the review server's `codex exec` jobs use it too.
|
|
74
|
+
export { codexAskAiConfigArgs, codexInboxEntryHasUrl, readCodexConfigTexts } from "./ask-ai-env.ts";
|
|
75
|
+
|
|
71
76
|
// ---------------------------------------------------------------------------
|
|
72
77
|
// Pure helpers (exported for testing)
|
|
73
78
|
// ---------------------------------------------------------------------------
|
|
@@ -292,7 +297,7 @@ export function mapApprovalRequest(
|
|
|
292
297
|
// JSON-RPC over stdio (bidirectional: responses, notifications, server requests)
|
|
293
298
|
// ---------------------------------------------------------------------------
|
|
294
299
|
|
|
295
|
-
type NotificationListener =
|
|
300
|
+
type NotificationListener =(notification: { method: string; params: RpcMessage }) => void;
|
|
296
301
|
type RequestHandler = (method: string, id: string | number, params: RpcMessage) => void;
|
|
297
302
|
|
|
298
303
|
class CodexAppServerProcess {
|
|
@@ -327,18 +332,21 @@ class CodexAppServerProcess {
|
|
|
327
332
|
|
|
328
333
|
private async doStart(codexPath: string, cwd: string, initTimeoutMs?: number): Promise<void> {
|
|
329
334
|
const commandPath = resolveWindowsCommandShim(codexPath);
|
|
335
|
+
const appServerArgs = [...codexAskAiConfigArgs(readCodexConfigTexts(cwd)), "app-server"];
|
|
330
336
|
const command =
|
|
331
|
-
buildWindowsCommandScriptSpawnCommand(commandPath,
|
|
337
|
+
buildWindowsCommandScriptSpawnCommand(commandPath, appServerArgs) ?? [
|
|
332
338
|
commandPath,
|
|
333
|
-
|
|
339
|
+
...appServerArgs,
|
|
334
340
|
];
|
|
335
341
|
|
|
336
342
|
let proc: ChildProcess;
|
|
337
343
|
try {
|
|
338
344
|
const [file, ...args] = command;
|
|
339
345
|
// stderr is "ignore", not "pipe": we never read it, and an un-drained
|
|
340
|
-
// stderr pipe deadlocks the child once its buffer fills.
|
|
341
|
-
|
|
346
|
+
// stderr pipe deadlocks the child once its buffer fills. The env turns
|
|
347
|
+
// Plannotator's integration off in this Codex (ask-ai-env.ts): its hooks
|
|
348
|
+
// read the marker; the Inbox MCP server is off by the `-c` overrides.
|
|
349
|
+
proc = spawn(file, args, { cwd, env: askAiChildEnv(), stdio: ["pipe", "pipe", "ignore"] });
|
|
342
350
|
} catch (err) {
|
|
343
351
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
344
352
|
this.handleProcessEnd(error);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type { OpencodeClient } from "@opencode-ai/sdk";
|
|
15
15
|
import { BaseSession } from "../base-session.ts";
|
|
16
16
|
import { buildSystemPrompt } from "../context.ts";
|
|
17
|
+
import { withAskAiChildEnv } from "./ask-ai-env.ts";
|
|
17
18
|
import type {
|
|
18
19
|
AIMessage,
|
|
19
20
|
AIProvider,
|
|
@@ -93,11 +94,18 @@ export class OpenCodeProvider implements AIProvider {
|
|
|
93
94
|
// port. An explicitly configured port is still honored verbatim.
|
|
94
95
|
let server: { url: string; close: () => void };
|
|
95
96
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
// Plannotator's integration off in this `opencode serve`
|
|
98
|
+
// (ask-ai-env.ts): the Plannotator OpenCode plugin it loads reads
|
|
99
|
+
// process.env and stands down. The SDK takes no env, so the switches
|
|
100
|
+
// are set on process.env only for the synchronous part of the call,
|
|
101
|
+
// where the SDK copies it into the spawn.
|
|
102
|
+
server = await withAskAiChildEnv(() =>
|
|
103
|
+
createOpencodeServer({
|
|
104
|
+
hostname: this.config.hostname ?? "127.0.0.1",
|
|
105
|
+
port: this.config.port ?? 0,
|
|
106
|
+
timeout: 15_000,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
101
109
|
} catch (err: unknown) {
|
|
102
110
|
// A missing CLI is a spawn error with code ENOENT in every runtime
|
|
103
111
|
// (Node: "spawn opencode ENOENT"; Bun: "Executable not found in
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "./command-path.ts";
|
|
34
34
|
import { createPiVersionProbe } from "./pi-version.ts";
|
|
35
35
|
import { guardChildStreams, writeChildLine } from "./child-io.ts";
|
|
36
|
+
import { askAiChildEnv } from "./ask-ai-env.ts";
|
|
36
37
|
|
|
37
38
|
// Re-export mapPiEvent from shared (runtime-agnostic)
|
|
38
39
|
export { mapPiEvent } from "./pi-events.ts";
|
|
@@ -77,6 +78,10 @@ export class PiProcessNode {
|
|
|
77
78
|
// (same reasoning as codex-app-server.ts).
|
|
78
79
|
proc = spawn(file, args, {
|
|
79
80
|
cwd,
|
|
81
|
+
// Plannotator's integration off in this throwaway Pi: Pi's RPC
|
|
82
|
+
// mode reports `ctx.hasUI`, so the Plannotator Pi extension would
|
|
83
|
+
// otherwise take it for a person's session (ask-ai-env.ts).
|
|
84
|
+
env: askAiChildEnv(),
|
|
80
85
|
stdio: ["pipe", "pipe", "ignore"],
|
|
81
86
|
});
|
|
82
87
|
} catch (err) {
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
resolveWindowsCommandShim,
|
|
30
30
|
} from "./command-path.ts";
|
|
31
31
|
import { createPiVersionProbe } from "./pi-version.ts";
|
|
32
|
+
import { askAiChildEnv } from "./ask-ai-env.ts";
|
|
32
33
|
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
// Constants
|
|
@@ -72,8 +73,11 @@ class PiProcess {
|
|
|
72
73
|
cwd,
|
|
73
74
|
// The LIVE env, never Bun's startup env (which still holds the
|
|
74
75
|
// variables the CLI scrubbed, like the bridge token): see
|
|
75
|
-
// packages/shared/child-env.ts.
|
|
76
|
-
env:
|
|
76
|
+
// packages/shared/child-env.ts. Plannotator's integration off
|
|
77
|
+
// (ask-ai-env.ts): Pi's RPC mode reports `ctx.hasUI`, so the
|
|
78
|
+
// Plannotator Pi extension would otherwise take this throwaway
|
|
79
|
+
// process for a person's session and connect it to the Inbox.
|
|
80
|
+
env: askAiChildEnv(),
|
|
77
81
|
stdin: "pipe",
|
|
78
82
|
stdout: "pipe",
|
|
79
83
|
stderr: "ignore",
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
resolveWindowsCommandShim,
|
|
52
52
|
} from "./command-path.ts";
|
|
53
53
|
import { guardChildStreams, toChildError, writeChildLine } from "./child-io.ts";
|
|
54
|
+
import { askAiChildEnv } from "./ask-ai-env.ts";
|
|
54
55
|
import { mapQwenLine, newQwenAskAISessionId } from "./qwen-events.ts";
|
|
55
56
|
|
|
56
57
|
export { mapQwenLine, QWEN_ASK_AI_SESSION_PREFIX } from "./qwen-events.ts";
|
|
@@ -213,7 +214,10 @@ export class QwenProcess {
|
|
|
213
214
|
try {
|
|
214
215
|
proc = spawn(file!, rest, {
|
|
215
216
|
cwd,
|
|
216
|
-
|
|
217
|
+
// Plannotator's integration off in this Qwen (ask-ai-env.ts):
|
|
218
|
+
// the Inbox MCP server Plannotator adds to Qwen's settings.json
|
|
219
|
+
// inherits the env, and so lists no tools.
|
|
220
|
+
env: askAiChildEnv(),
|
|
217
221
|
stdio: ["pipe", "pipe", "pipe"],
|
|
218
222
|
windowsHide: true,
|
|
219
223
|
});
|
|
@@ -41,7 +41,9 @@ export interface CallFlowLanguageDefinition {
|
|
|
41
41
|
const MB = 1024 * 1024;
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* CallDiff 0.
|
|
44
|
+
* CallDiff 0.6.0's supported extensions and pinned grammar packages. Perl,
|
|
45
|
+
* new upstream in 0.6.0, is left out: tree-sitter-perl ships no prebuilt
|
|
46
|
+
* binaries, so a pack would need a C compiler on every machine.
|
|
45
47
|
*
|
|
46
48
|
* Size estimates describe Plannotator's pruned managed artifacts, not the
|
|
47
49
|
* much larger npm tarball expansion. They are deliberately rounded up and
|
|
@@ -55,7 +57,7 @@ export const CALL_FLOW_LANGUAGES = [
|
|
|
55
57
|
{ id: "java", label: "Java", extensions: [".java"], kind: "pack", packageName: "tree-sitter-java", packageVersion: "0.23.5", installSizeBytes: 1 * MB },
|
|
56
58
|
{ id: "ruby", label: "Ruby", extensions: [".rb"], kind: "pack", packageName: "tree-sitter-ruby", packageVersion: "0.23.1", installSizeBytes: 3 * MB },
|
|
57
59
|
{ id: "c", label: "C", extensions: [".c", ".h"], kind: "pack", packageName: "tree-sitter-c", packageVersion: "0.24.1", installSizeBytes: 1 * MB },
|
|
58
|
-
{ id: "cpp", label: "C++", extensions: [".cc", ".cpp", ".cxx", ".hpp", ".hh"], kind: "pack", packageName: "tree-sitter-cpp", packageVersion: "0.23.4", installSizeBytes: 4 * MB },
|
|
60
|
+
{ id: "cpp", label: "C++", extensions: [".cc", ".cpp", ".cu", ".cuh", ".cxx", ".hpp", ".hh"], kind: "pack", packageName: "tree-sitter-cpp", packageVersion: "0.23.4", installSizeBytes: 4 * MB },
|
|
59
61
|
{ id: "csharp", label: "C#", extensions: [".cs"], kind: "pack", packageName: "tree-sitter-c-sharp", packageVersion: "0.23.1", installSizeBytes: 6 * MB },
|
|
60
62
|
{ id: "php", label: "PHP", extensions: [".php"], kind: "pack", packageName: "tree-sitter-php", packageVersion: "0.24.2", installSizeBytes: 17 * MB },
|
|
61
63
|
{ id: "kotlin", label: "Kotlin", extensions: [".kt", ".kts"], kind: "pack", packageName: "tree-sitter-kotlin", packageVersion: "0.3.8", installSizeBytes: 4 * MB },
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plannotator-call-flow-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-plannotator.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "plannotator-call-flow-runtime",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.6.0-plannotator.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@types/node": "24.2.0",
|
|
12
|
-
"calldiff": "file:calldiff-
|
|
12
|
+
"calldiff": "file:calldiff-2086f6e4e9ed3c410bba7c94ca3dde77791b1956.tar.gz",
|
|
13
13
|
"tree-sitter": "0.25.1",
|
|
14
14
|
"tree-sitter-javascript": "0.25.0",
|
|
15
15
|
"tree-sitter-typescript": "0.23.2",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"node_modules/calldiff": {
|
|
67
|
-
"version": "0.
|
|
68
|
-
"resolved": "file:calldiff-
|
|
69
|
-
"integrity": "sha512-
|
|
67
|
+
"version": "0.6.0",
|
|
68
|
+
"resolved": "file:calldiff-2086f6e4e9ed3c410bba7c94ca3dde77791b1956.tar.gz",
|
|
69
|
+
"integrity": "sha512-t+YCPz0WkETiwSOznwx3mLxVkEUe7DVACV+it2HJER7q/ha+K0IavN9IfBvP+7/ijzPXoUHgJIkAM/kg1O5S6A==",
|
|
70
70
|
"license": "MIT",
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"incur": "^0.4.26",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"calldiff": "dist/cli.js"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
|
-
"node": ">=22"
|
|
81
|
+
"node": ">=22.18.0"
|
|
82
82
|
}
|
|
83
83
|
},
|
|
84
84
|
"node_modules/incur": {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plannotator-call-flow-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-plannotator.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Managed offline runtime for Plannotator call-flow analysis",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@types/node": "24.2.0",
|
|
8
|
-
"calldiff": "file:calldiff-
|
|
8
|
+
"calldiff": "file:calldiff-2086f6e4e9ed3c410bba7c94ca3dde77791b1956.tar.gz",
|
|
9
9
|
"tree-sitter": "0.25.1",
|
|
10
10
|
"tree-sitter-javascript": "0.25.0",
|
|
11
11
|
"tree-sitter-typescript": "0.23.2",
|