@trim21/personal-pi-extensions 0.0.124 → 0.0.126
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 +11 -5
- package/src/bwrap/index.ts +19 -30
- package/src/gh-readonly.ts +116 -141
- package/src/opencode-edit.ts +3 -1
- package/src/opencode-read.ts +8 -16
- package/src/opencode-write.ts +2 -5
- package/src/todo-pendant.ts +6 -2
- package/src/workspace-guard.ts +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trim21/personal-pi-extensions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.126",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"check": "tsc --noEmit && prettier --check .",
|
|
22
|
+
"lint": "eslint .",
|
|
22
23
|
"format": "prettier --write .",
|
|
23
|
-
"build:cli": "esbuild bin/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --external:@earendil-works/pi-coding-agent --external:typebox --external:node:*",
|
|
24
24
|
"test": "vitest run",
|
|
25
25
|
"prepare": "husky"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@earendil-works/pi-ai": "*",
|
|
29
28
|
"@earendil-works/pi-agent-core": "*",
|
|
29
|
+
"@earendil-works/pi-ai": "*",
|
|
30
30
|
"@earendil-works/pi-coding-agent": "*",
|
|
31
31
|
"@earendil-works/pi-tui": "*",
|
|
32
32
|
"typebox": "*"
|
|
@@ -36,11 +36,13 @@
|
|
|
36
36
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
37
37
|
"@types/node": "^24.0.0",
|
|
38
38
|
"esbuild": "^0.28.1",
|
|
39
|
+
"eslint": "^10.8.0",
|
|
39
40
|
"husky": "^9.1.7",
|
|
40
41
|
"lint-staged": "^17.0.8",
|
|
41
42
|
"prettier": "^3.6.0",
|
|
42
43
|
"typebox": "1.3.1",
|
|
43
|
-
"typescript": "^
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"typescript-eslint": "^8.65.0",
|
|
44
46
|
"vitest": "^4.1.10"
|
|
45
47
|
},
|
|
46
48
|
"pi": {
|
|
@@ -55,7 +57,11 @@
|
|
|
55
57
|
]
|
|
56
58
|
},
|
|
57
59
|
"lint-staged": {
|
|
58
|
-
"*.{ts,tsx,js,jsx
|
|
60
|
+
"*.{ts,tsx,js,jsx}": [
|
|
61
|
+
"eslint --fix",
|
|
62
|
+
"prettier --write"
|
|
63
|
+
],
|
|
64
|
+
"*.{json,md,yaml,yml}": [
|
|
59
65
|
"prettier --write"
|
|
60
66
|
]
|
|
61
67
|
},
|
package/src/bwrap/index.ts
CHANGED
|
@@ -62,7 +62,12 @@ import { fileURLToPath } from "node:url";
|
|
|
62
62
|
import { Type } from "typebox";
|
|
63
63
|
import { Value } from "typebox/value";
|
|
64
64
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
65
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
type BashOperations,
|
|
67
|
+
createBashTool,
|
|
68
|
+
getAgentDir,
|
|
69
|
+
Theme,
|
|
70
|
+
} from "@earendil-works/pi-coding-agent";
|
|
66
71
|
|
|
67
72
|
const SANDBOX_PROMPT = `
|
|
68
73
|
## Command Execution
|
|
@@ -166,10 +171,10 @@ function resolveBwrap(config: BwrapConfig): ResolvedBwrap {
|
|
|
166
171
|
const base = {
|
|
167
172
|
mode: config.mode,
|
|
168
173
|
bwrapPath: config.bwrapPath,
|
|
169
|
-
writablePaths: config.writablePaths ??
|
|
174
|
+
writablePaths: config.writablePaths ?? [".", "/tmp"],
|
|
170
175
|
extraWritablePaths: config.extraWritablePaths,
|
|
171
|
-
tmpfsPaths: config.tmpfsPaths ??
|
|
172
|
-
extraArgs: config.extraArgs ??
|
|
176
|
+
tmpfsPaths: config.tmpfsPaths ?? [],
|
|
177
|
+
extraArgs: config.extraArgs ?? [],
|
|
173
178
|
};
|
|
174
179
|
switch (config.mode) {
|
|
175
180
|
case "allow-all":
|
|
@@ -229,7 +234,7 @@ function loadConfig(cwd: string): BwrapConfig {
|
|
|
229
234
|
try {
|
|
230
235
|
Object.assign(target, JSON.parse(readFileSync(path, "utf-8")));
|
|
231
236
|
} catch (e) {
|
|
232
|
-
console.error(`Warning: Could not parse ${path}: ${e}`);
|
|
237
|
+
console.error(`Warning: Could not parse ${path}: ${String(e)}`);
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
}
|
|
@@ -507,13 +512,6 @@ const sandboxedBashSchema = Type.Object({
|
|
|
507
512
|
),
|
|
508
513
|
});
|
|
509
514
|
|
|
510
|
-
interface SandboxedBashInput {
|
|
511
|
-
command: string;
|
|
512
|
-
timeout?: number;
|
|
513
|
-
request_full_access?: boolean;
|
|
514
|
-
request_full_access_reason?: string;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
515
|
export default function (pi: ExtensionAPI) {
|
|
518
516
|
pi.registerFlag("no-bwrap", {
|
|
519
517
|
description: "Disable bwrap sandboxing for bash commands",
|
|
@@ -525,7 +523,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
525
523
|
const localBash = createBashTool(localCwd);
|
|
526
524
|
|
|
527
525
|
let resolved: ResolvedBwrap | null = null;
|
|
528
|
-
let manuallyDisabled = false;
|
|
529
526
|
|
|
530
527
|
function getResolved(): ResolvedBwrap {
|
|
531
528
|
if (!resolved) {
|
|
@@ -534,10 +531,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
534
531
|
return resolved;
|
|
535
532
|
}
|
|
536
533
|
|
|
537
|
-
function isEnabled() {
|
|
538
|
-
return !manuallyDisabled && getResolved().bwrapEnabled;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
534
|
function setMode(mode: BwrapMode) {
|
|
542
535
|
const config = loadConfig(localCwd);
|
|
543
536
|
config.mode = mode;
|
|
@@ -607,24 +600,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
607
600
|
},
|
|
608
601
|
});
|
|
609
602
|
|
|
610
|
-
pi.on("session_start",
|
|
611
|
-
const noBwrap = pi.getFlag("no-bwrap")
|
|
603
|
+
pi.on("session_start", (_event, ctx) => {
|
|
604
|
+
const noBwrap = pi.getFlag("no-bwrap") === true;
|
|
612
605
|
|
|
613
606
|
if (noBwrap) {
|
|
614
|
-
manuallyDisabled = true;
|
|
615
607
|
resolved = null;
|
|
616
608
|
ctx.ui.notify("bwrap sandbox disabled via --no-bwrap", "warning");
|
|
617
609
|
return;
|
|
618
610
|
}
|
|
619
611
|
|
|
620
612
|
if (!process.env.HOME) {
|
|
621
|
-
manuallyDisabled = true;
|
|
622
613
|
ctx.ui.notify("bwrap requires HOME environment variable", "error");
|
|
623
614
|
return;
|
|
624
615
|
}
|
|
625
616
|
|
|
626
617
|
if (process.platform !== "linux") {
|
|
627
|
-
manuallyDisabled = true;
|
|
628
618
|
ctx.ui.notify("bwrap sandbox requires Linux", "warning");
|
|
629
619
|
return;
|
|
630
620
|
}
|
|
@@ -637,7 +627,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
637
627
|
findBwrap(resolved.bwrapPath);
|
|
638
628
|
} catch (err) {
|
|
639
629
|
resolved = null;
|
|
640
|
-
manuallyDisabled = true;
|
|
641
630
|
ctx.ui.notify(err instanceof Error ? err.message : "bwrap not found", "error");
|
|
642
631
|
return;
|
|
643
632
|
}
|
|
@@ -657,7 +646,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
657
646
|
|
|
658
647
|
pi.on("session_shutdown", () => {
|
|
659
648
|
resolved = null;
|
|
660
|
-
manuallyDisabled = false;
|
|
661
649
|
});
|
|
662
650
|
|
|
663
651
|
pi.on("before_agent_start", (event) => {
|
|
@@ -671,11 +659,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
671
659
|
|
|
672
660
|
pi.registerCommand("bwrap", {
|
|
673
661
|
description: "Show bwrap sandbox configuration",
|
|
674
|
-
handler:
|
|
662
|
+
handler: (_args, ctx) => {
|
|
675
663
|
const r = getResolved();
|
|
676
664
|
if (!r.bwrapEnabled) {
|
|
677
665
|
ctx.ui.notify(`bwrap disabled (mode: ${r.mode})`, "info");
|
|
678
|
-
return;
|
|
666
|
+
return Promise.resolve();
|
|
679
667
|
}
|
|
680
668
|
|
|
681
669
|
const net = r.network ? "net" : "no-net";
|
|
@@ -686,6 +674,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
686
674
|
`bwrap ${r.mode} ${net} write:[${w.join(", ")}] tmpfs:[${t.join(", ") || "-"}]`,
|
|
687
675
|
"info",
|
|
688
676
|
);
|
|
677
|
+
return Promise.resolve();
|
|
689
678
|
},
|
|
690
679
|
});
|
|
691
680
|
|
|
@@ -694,7 +683,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
694
683
|
ctx: {
|
|
695
684
|
ui: {
|
|
696
685
|
notify: (m: string, t?: "info" | "warning" | "error") => void;
|
|
697
|
-
theme:
|
|
686
|
+
theme: Theme;
|
|
698
687
|
setStatus: (k: string, t: string | undefined) => void;
|
|
699
688
|
};
|
|
700
689
|
},
|
|
@@ -718,16 +707,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
718
707
|
|
|
719
708
|
pi.registerCommand("bwrap-allow-all", {
|
|
720
709
|
description: "Disable bwrap sandbox, full access",
|
|
721
|
-
handler:
|
|
710
|
+
handler: (_args, ctx) => Promise.resolve(switchMode("allow-all", ctx)),
|
|
722
711
|
});
|
|
723
712
|
|
|
724
713
|
pi.registerCommand("bwrap-workspace-write", {
|
|
725
714
|
description: "Sandbox on, network off, workspace writable",
|
|
726
|
-
handler:
|
|
715
|
+
handler: (_args, ctx) => Promise.resolve(switchMode("workspace-write", ctx)),
|
|
727
716
|
});
|
|
728
717
|
|
|
729
718
|
pi.registerCommand("bwrap-readonly", {
|
|
730
719
|
description: "Sandbox on, network off, no writes",
|
|
731
|
-
handler:
|
|
720
|
+
handler: (_args, ctx) => Promise.resolve(switchMode("readonly", ctx)),
|
|
732
721
|
});
|
|
733
722
|
}
|
package/src/gh-readonly.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Tools:
|
|
7
7
|
* - read-github-issue: Get issue details
|
|
8
|
-
* - list-github-issues: List issues
|
|
8
|
+
* - list-github-issues: List or search issues
|
|
9
9
|
* - read-github-issue-comments: Get issue comments
|
|
10
10
|
* - read-github-pr: Get PR details
|
|
11
|
-
* - list-github-prs: List PRs
|
|
11
|
+
* - list-github-prs: List or search PRs
|
|
12
12
|
* - read-github-pr-diff: Get PR diff
|
|
13
13
|
* - read-github-pr-status: Get PR status checks
|
|
14
14
|
* - read-github-pr-comments: Get PR comments
|
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
* - read-github-release: Get release details
|
|
21
21
|
* - wait-github-pr-checks: Watch PR CI checks
|
|
22
22
|
* - watch-github-run: Watch a workflow run
|
|
23
|
-
* - search-github-issues: Search GitHub issues
|
|
24
|
-
* - search-github-prs: Search GitHub pull requests
|
|
25
23
|
*
|
|
26
24
|
* Install:
|
|
27
25
|
* cp gh-readonly.ts ~/.pi/agent/extensions/
|
|
@@ -33,6 +31,7 @@
|
|
|
33
31
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
34
32
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
35
33
|
import { Type } from "typebox";
|
|
34
|
+
import { Value } from "typebox/value";
|
|
36
35
|
import { spawn } from "node:child_process";
|
|
37
36
|
import { homedir } from "node:os";
|
|
38
37
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -108,7 +107,7 @@ function runGh(
|
|
|
108
107
|
resolve({ stdout, stderr, code: code ?? 0, killed, combined: combined.join("") });
|
|
109
108
|
});
|
|
110
109
|
|
|
111
|
-
proc.on("error", (
|
|
110
|
+
proc.on("error", () => {
|
|
112
111
|
if (timeoutId) clearTimeout(timeoutId);
|
|
113
112
|
if (ctx.signal) {
|
|
114
113
|
ctx.signal.removeEventListener("abort", killProcess);
|
|
@@ -158,6 +157,27 @@ function repoArgs(repo?: string): string[] {
|
|
|
158
157
|
return repo ? ["--repo", repo] : [];
|
|
159
158
|
}
|
|
160
159
|
|
|
160
|
+
// ── runtime validation schemas for JSON.parse results ───────────────────────
|
|
161
|
+
|
|
162
|
+
const repoViewSchema = Type.Object({ nameWithOwner: Type.String() });
|
|
163
|
+
|
|
164
|
+
const stepSchema = Type.Object({
|
|
165
|
+
name: Type.String(),
|
|
166
|
+
number: Type.Number(),
|
|
167
|
+
status: Type.String(),
|
|
168
|
+
conclusion: Type.Union([Type.String(), Type.Null()]),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const jobRunSchema = Type.Object({
|
|
172
|
+
id: Type.Number(),
|
|
173
|
+
name: Type.String(),
|
|
174
|
+
status: Type.String(),
|
|
175
|
+
conclusion: Type.Union([Type.String(), Type.Null()]),
|
|
176
|
+
steps: Type.Array(stepSchema),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const jobsResponseSchema = Type.Object({ jobs: Type.Array(jobRunSchema) });
|
|
180
|
+
|
|
161
181
|
function truncate(
|
|
162
182
|
text: string,
|
|
163
183
|
maxLines = 2000,
|
|
@@ -192,6 +212,55 @@ function toToolResult(stdout: string): {
|
|
|
192
212
|
return { content: [{ type: "text", text }], details: { truncated } };
|
|
193
213
|
}
|
|
194
214
|
|
|
215
|
+
interface ListFilters {
|
|
216
|
+
repo?: string;
|
|
217
|
+
keywords?: string;
|
|
218
|
+
state?: string;
|
|
219
|
+
label?: string;
|
|
220
|
+
author?: string;
|
|
221
|
+
assignee?: string;
|
|
222
|
+
milestone?: string;
|
|
223
|
+
limit?: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* List or search issues/PRs with structured filters.
|
|
228
|
+
*
|
|
229
|
+
* `gh issue list` / `gh pr list` are used when a repo is available (repo param or
|
|
230
|
+
* current directory), with keywords passed via `--search`. When no repo is given
|
|
231
|
+
* and keywords are present, falls back to `gh search issues` / `gh search prs`
|
|
232
|
+
* with plain keywords — never embedding a `repo:` qualifier in the query string,
|
|
233
|
+
* because `gh` mis-parses `repo:` values followed by spaces.
|
|
234
|
+
*/
|
|
235
|
+
async function listGithub(
|
|
236
|
+
kind: "issue" | "pr",
|
|
237
|
+
params: ListFilters,
|
|
238
|
+
ctx: { cwd?: string; signal?: AbortSignal; input?: unknown },
|
|
239
|
+
): Promise<string> {
|
|
240
|
+
const { repo, keywords, state, label, author, assignee, milestone, limit } = params;
|
|
241
|
+
|
|
242
|
+
if (!repo && keywords) {
|
|
243
|
+
const args = ["search", kind === "issue" ? "issues" : "prs", keywords];
|
|
244
|
+
if (state && state !== "all") args.push("--state", state);
|
|
245
|
+
if (label) args.push("--label", label);
|
|
246
|
+
if (author) args.push("--author", author);
|
|
247
|
+
if (assignee) args.push("--assignee", assignee);
|
|
248
|
+
if (milestone) args.push("--milestone", milestone);
|
|
249
|
+
if (limit) args.push("--limit", String(limit));
|
|
250
|
+
return ghExec(args, ctx);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const args = [kind, "list", ...repoArgs(repo)];
|
|
254
|
+
if (state) args.push("--state", state);
|
|
255
|
+
if (keywords) args.push("--search", keywords);
|
|
256
|
+
if (label) args.push("--label", label);
|
|
257
|
+
if (author) args.push("--author", author);
|
|
258
|
+
if (assignee) args.push("--assignee", assignee);
|
|
259
|
+
if (milestone) args.push("--milestone", milestone);
|
|
260
|
+
if (limit) args.push("--limit", String(limit));
|
|
261
|
+
return ghExec(args, ctx);
|
|
262
|
+
}
|
|
263
|
+
|
|
195
264
|
// ── CI helpers ───────────────────────────────────────────────────────────────
|
|
196
265
|
|
|
197
266
|
export interface StepInfo {
|
|
@@ -280,7 +349,8 @@ async function resolveRepo(
|
|
|
280
349
|
): Promise<string> {
|
|
281
350
|
if (repo) return repo;
|
|
282
351
|
const stdout = await ghExec(["repo", "view", "--json", "nameWithOwner"], { cwd, signal, input });
|
|
283
|
-
|
|
352
|
+
const { nameWithOwner } = Value.Parse(repoViewSchema, JSON.parse(stdout));
|
|
353
|
+
return nameWithOwner;
|
|
284
354
|
}
|
|
285
355
|
|
|
286
356
|
export function statusIcon(conclusion: string | null): string {
|
|
@@ -400,7 +470,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
400
470
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
|
|
401
471
|
}),
|
|
402
472
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
403
|
-
const { number, repo } = params
|
|
473
|
+
const { number, repo } = params;
|
|
404
474
|
return toToolResult(
|
|
405
475
|
await ghExec(
|
|
406
476
|
[
|
|
@@ -421,19 +491,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
421
491
|
pi.registerTool({
|
|
422
492
|
name: "list-github-issues",
|
|
423
493
|
label: "GitHub Issues List",
|
|
424
|
-
description:
|
|
425
|
-
|
|
494
|
+
description:
|
|
495
|
+
"List GitHub issues with optional filters and keyword search. When repo is omitted, searches across GitHub using keywords.",
|
|
496
|
+
promptSnippet: "List or search GitHub issues",
|
|
426
497
|
parameters: Type.Object({
|
|
427
|
-
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
498
|
+
repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
|
|
499
|
+
keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
|
|
428
500
|
state: Type.Optional(Type.String({ description: "open, closed, all (default: open)" })),
|
|
501
|
+
label: Type.Optional(Type.String({ description: "Filter by label" })),
|
|
502
|
+
author: Type.Optional(Type.String({ description: "Filter by author" })),
|
|
503
|
+
assignee: Type.Optional(Type.String({ description: "Filter by assignee" })),
|
|
504
|
+
milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
|
|
429
505
|
limit: Type.Optional(Type.Number({ description: "Max results (default 30)" })),
|
|
430
506
|
}),
|
|
431
507
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (limit) args.push("--limit", String(limit));
|
|
436
|
-
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
508
|
+
return toToolResult(
|
|
509
|
+
await listGithub("issue", params, { cwd: ctx.cwd, signal, input: params }),
|
|
510
|
+
);
|
|
437
511
|
},
|
|
438
512
|
});
|
|
439
513
|
|
|
@@ -448,7 +522,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
448
522
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
449
523
|
}),
|
|
450
524
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
451
|
-
const { number, repo } = params
|
|
525
|
+
const { number, repo } = params;
|
|
452
526
|
return toToolResult(
|
|
453
527
|
await ghExec(
|
|
454
528
|
[
|
|
@@ -469,21 +543,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
469
543
|
pi.registerTool({
|
|
470
544
|
name: "list-github-prs",
|
|
471
545
|
label: "GitHub PRs List",
|
|
472
|
-
description:
|
|
473
|
-
|
|
546
|
+
description:
|
|
547
|
+
"List GitHub pull requests with optional filters and keyword search. When repo is omitted, searches across GitHub using keywords.",
|
|
548
|
+
promptSnippet: "List or search GitHub PRs",
|
|
474
549
|
parameters: Type.Object({
|
|
475
|
-
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
550
|
+
repo: Type.Optional(Type.String({ description: "OWNER/REPO (defaults to current repo)" })),
|
|
551
|
+
keywords: Type.Optional(Type.String({ description: "Search keywords (free text)" })),
|
|
476
552
|
state: Type.Optional(
|
|
477
553
|
Type.String({ description: "open, closed, merged, all (default: open)" }),
|
|
478
554
|
),
|
|
555
|
+
label: Type.Optional(Type.String({ description: "Filter by label" })),
|
|
556
|
+
author: Type.Optional(Type.String({ description: "Filter by author" })),
|
|
557
|
+
assignee: Type.Optional(Type.String({ description: "Filter by assignee" })),
|
|
558
|
+
milestone: Type.Optional(Type.String({ description: "Filter by milestone" })),
|
|
479
559
|
limit: Type.Optional(Type.Number({ description: "Max results (default 30)" })),
|
|
480
560
|
}),
|
|
481
561
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
482
|
-
|
|
483
|
-
const args = ["pr", "list", ...repoArgs(repo)];
|
|
484
|
-
if (state) args.push("--state", state);
|
|
485
|
-
if (limit) args.push("--limit", String(limit));
|
|
486
|
-
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
562
|
+
return toToolResult(await listGithub("pr", params, { cwd: ctx.cwd, signal, input: params }));
|
|
487
563
|
},
|
|
488
564
|
});
|
|
489
565
|
|
|
@@ -498,10 +574,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
498
574
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
499
575
|
}),
|
|
500
576
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
501
|
-
const { number, repo } = params
|
|
502
|
-
number: number | string;
|
|
503
|
-
repo?: string;
|
|
504
|
-
};
|
|
577
|
+
const { number, repo } = params;
|
|
505
578
|
const args = ["pr", "diff", String(number), ...repoArgs(repo)];
|
|
506
579
|
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
507
580
|
},
|
|
@@ -518,7 +591,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
518
591
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
519
592
|
}),
|
|
520
593
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
521
|
-
const { number, repo } = params
|
|
594
|
+
const { number, repo } = params;
|
|
522
595
|
return toToolResult(
|
|
523
596
|
await ghExec(["pr", "checks", String(number), ...repoArgs(repo)], {
|
|
524
597
|
cwd: ctx.cwd,
|
|
@@ -547,11 +620,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
547
620
|
),
|
|
548
621
|
}),
|
|
549
622
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
550
|
-
const { number, repo, reviews } = params
|
|
551
|
-
number: number | string;
|
|
552
|
-
repo?: string;
|
|
553
|
-
reviews?: boolean;
|
|
554
|
-
};
|
|
623
|
+
const { number, repo, reviews } = params;
|
|
555
624
|
let out: string;
|
|
556
625
|
if (reviews) {
|
|
557
626
|
const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
|
|
@@ -569,8 +638,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
569
638
|
}),
|
|
570
639
|
]);
|
|
571
640
|
|
|
572
|
-
const reviewComments = JSON.parse(comments);
|
|
573
|
-
const reviewSummaries = JSON.parse(reviewsOut);
|
|
641
|
+
const reviewComments = Value.Parse(Type.Array(Type.Unknown()), JSON.parse(comments));
|
|
642
|
+
const reviewSummaries = Value.Parse(Type.Array(Type.Unknown()), JSON.parse(reviewsOut));
|
|
574
643
|
|
|
575
644
|
out = JSON.stringify(
|
|
576
645
|
{
|
|
@@ -609,7 +678,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
609
678
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
610
679
|
}),
|
|
611
680
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
612
|
-
const { number, repo } = params
|
|
681
|
+
const { number, repo } = params;
|
|
613
682
|
return toToolResult(
|
|
614
683
|
await ghExec(["issue", "view", String(number), ...repoArgs(repo), "--json", "comments"], {
|
|
615
684
|
cwd: ctx.cwd,
|
|
@@ -635,12 +704,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
635
704
|
workflow: Type.Optional(Type.String({ description: "Filter by workflow name or file" })),
|
|
636
705
|
}),
|
|
637
706
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
638
|
-
const { repo, limit, status, workflow } = params
|
|
639
|
-
repo?: string;
|
|
640
|
-
limit?: number;
|
|
641
|
-
status?: string;
|
|
642
|
-
workflow?: string;
|
|
643
|
-
};
|
|
707
|
+
const { repo, limit, status, workflow } = params;
|
|
644
708
|
const args = ["run", "list", ...repoArgs(repo)];
|
|
645
709
|
if (limit) args.push("--limit", String(limit));
|
|
646
710
|
if (status) args.push("--status", status);
|
|
@@ -685,14 +749,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
685
749
|
),
|
|
686
750
|
}),
|
|
687
751
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
688
|
-
const { run_id, repo, job, step, offset, limit } = params
|
|
689
|
-
run_id: number | string;
|
|
690
|
-
repo?: string;
|
|
691
|
-
job?: string;
|
|
692
|
-
step?: string;
|
|
693
|
-
offset?: number;
|
|
694
|
-
limit?: number;
|
|
695
|
-
};
|
|
752
|
+
const { run_id, repo, job, step, offset, limit } = params;
|
|
696
753
|
|
|
697
754
|
// ── Fetch specific step logs ───────────────────────────────────────
|
|
698
755
|
if (step !== undefined && step !== null) {
|
|
@@ -702,20 +759,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
702
759
|
["api", `/repos/${effectiveRepo}/actions/runs/${run_id}/jobs`],
|
|
703
760
|
{ cwd: ctx.cwd, signal, input: params },
|
|
704
761
|
);
|
|
705
|
-
const { jobs } = JSON.parse(jobsOut)
|
|
706
|
-
jobs: Array<{
|
|
707
|
-
id: number;
|
|
708
|
-
name: string;
|
|
709
|
-
status: string;
|
|
710
|
-
conclusion: string | null;
|
|
711
|
-
steps: Array<{
|
|
712
|
-
name: string;
|
|
713
|
-
number: number;
|
|
714
|
-
status: string;
|
|
715
|
-
conclusion: string | null;
|
|
716
|
-
}>;
|
|
717
|
-
}>;
|
|
718
|
-
};
|
|
762
|
+
const { jobs } = Value.Parse(jobsResponseSchema, JSON.parse(jobsOut));
|
|
719
763
|
|
|
720
764
|
if (!jobs || jobs.length === 0) {
|
|
721
765
|
return {
|
|
@@ -889,20 +933,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
889
933
|
signal,
|
|
890
934
|
input: params,
|
|
891
935
|
});
|
|
892
|
-
const { jobs } = JSON.parse(jobsOut)
|
|
893
|
-
jobs: Array<{
|
|
894
|
-
id: number;
|
|
895
|
-
name: string;
|
|
896
|
-
status: string;
|
|
897
|
-
conclusion: string | null;
|
|
898
|
-
steps: Array<{
|
|
899
|
-
name: string;
|
|
900
|
-
number: number;
|
|
901
|
-
status: string;
|
|
902
|
-
conclusion: string | null;
|
|
903
|
-
}>;
|
|
904
|
-
}>;
|
|
905
|
-
};
|
|
936
|
+
const { jobs } = Value.Parse(jobsResponseSchema, JSON.parse(jobsOut));
|
|
906
937
|
|
|
907
938
|
if (!jobs || jobs.length === 0) {
|
|
908
939
|
return {
|
|
@@ -1034,7 +1065,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1034
1065
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
1035
1066
|
}),
|
|
1036
1067
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1037
|
-
const { run_id, repo } = params
|
|
1068
|
+
const { run_id, repo } = params;
|
|
1038
1069
|
const effectiveRepo = await resolveRepo(repo, signal, ctx.cwd, params);
|
|
1039
1070
|
return toToolResult(
|
|
1040
1071
|
await ghExec(["api", `/repos/${effectiveRepo}/actions/runs/${run_id}/jobs`], {
|
|
@@ -1056,7 +1087,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1056
1087
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
1057
1088
|
}),
|
|
1058
1089
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1059
|
-
const { repo } = params
|
|
1090
|
+
const { repo } = params;
|
|
1060
1091
|
const args = ["repo", "view"];
|
|
1061
1092
|
if (repo) args.push(repo);
|
|
1062
1093
|
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
@@ -1074,7 +1105,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1074
1105
|
limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
|
|
1075
1106
|
}),
|
|
1076
1107
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1077
|
-
const { repo, limit } = params
|
|
1108
|
+
const { repo, limit } = params;
|
|
1078
1109
|
const args = ["release", "list", ...repoArgs(repo)];
|
|
1079
1110
|
if (limit) args.push("--limit", String(limit));
|
|
1080
1111
|
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
@@ -1092,7 +1123,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1092
1123
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
1093
1124
|
}),
|
|
1094
1125
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1095
|
-
const { tag, repo } = params
|
|
1126
|
+
const { tag, repo } = params;
|
|
1096
1127
|
return toToolResult(
|
|
1097
1128
|
await ghExec(["release", "view", tag, ...repoArgs(repo)], {
|
|
1098
1129
|
cwd: ctx.cwd,
|
|
@@ -1119,11 +1150,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1119
1150
|
),
|
|
1120
1151
|
}),
|
|
1121
1152
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
1122
|
-
const { number, repo, fail_fast } = params
|
|
1123
|
-
number: number | string;
|
|
1124
|
-
repo?: string;
|
|
1125
|
-
fail_fast?: boolean;
|
|
1126
|
-
};
|
|
1153
|
+
const { number, repo, fail_fast } = params;
|
|
1127
1154
|
|
|
1128
1155
|
onUpdate?.({
|
|
1129
1156
|
content: [{ type: "text", text: `Watching CI checks for PR #${number}...` }],
|
|
@@ -1173,7 +1200,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1173
1200
|
repo: Type.Optional(Type.String({ description: "OWNER/REPO" })),
|
|
1174
1201
|
}),
|
|
1175
1202
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
1176
|
-
const { run_id, repo } = params
|
|
1203
|
+
const { run_id, repo } = params;
|
|
1177
1204
|
|
|
1178
1205
|
onUpdate?.({
|
|
1179
1206
|
content: [{ type: "text", text: `Watching workflow run ${run_id}...` }],
|
|
@@ -1198,56 +1225,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
1198
1225
|
};
|
|
1199
1226
|
},
|
|
1200
1227
|
});
|
|
1201
|
-
|
|
1202
|
-
// ── search-github-issues ───────────────────────────────────────────────────
|
|
1203
|
-
pi.registerTool({
|
|
1204
|
-
name: "search-github-issues",
|
|
1205
|
-
label: "GitHub Issue Search",
|
|
1206
|
-
description: "Search GitHub issues using GitHub search syntax.",
|
|
1207
|
-
promptSnippet: "Search GitHub issues",
|
|
1208
|
-
parameters: Type.Object({
|
|
1209
|
-
query: Type.String({
|
|
1210
|
-
description:
|
|
1211
|
-
"GitHub search syntax (e.g. 'repo:owner/name keyword', 'is:open label:bug'). Do NOT include 'type:issue' or 'type:pr' qualifiers.",
|
|
1212
|
-
}),
|
|
1213
|
-
include_prs: Type.Optional(
|
|
1214
|
-
Type.Boolean({
|
|
1215
|
-
description: "Whether to include pull requests in results (default: false)",
|
|
1216
|
-
}),
|
|
1217
|
-
),
|
|
1218
|
-
limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
|
|
1219
|
-
}),
|
|
1220
|
-
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1221
|
-
const { query, include_prs, limit } = params as {
|
|
1222
|
-
query: string;
|
|
1223
|
-
include_prs?: boolean;
|
|
1224
|
-
limit?: number;
|
|
1225
|
-
};
|
|
1226
|
-
const args = ["search", "issues", query];
|
|
1227
|
-
if (include_prs) args.push("--include-prs");
|
|
1228
|
-
if (limit) args.push("--limit", String(limit));
|
|
1229
|
-
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
1230
|
-
},
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
// ── search-github-prs ──────────────────────────────────────────────────────
|
|
1234
|
-
pi.registerTool({
|
|
1235
|
-
name: "search-github-prs",
|
|
1236
|
-
label: "GitHub PR Search",
|
|
1237
|
-
description: "Search GitHub pull requests using GitHub search syntax.",
|
|
1238
|
-
promptSnippet: "Search GitHub PRs",
|
|
1239
|
-
parameters: Type.Object({
|
|
1240
|
-
query: Type.String({
|
|
1241
|
-
description:
|
|
1242
|
-
"GitHub search syntax (e.g. 'repo:owner/name keyword', 'is:open label:bug'). Do NOT include 'type:issue' or 'type:pr' qualifiers.",
|
|
1243
|
-
}),
|
|
1244
|
-
limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
|
|
1245
|
-
}),
|
|
1246
|
-
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1247
|
-
const { query, limit } = params as { query: string; limit?: number };
|
|
1248
|
-
const args = ["search", "prs", query];
|
|
1249
|
-
if (limit) args.push("--limit", String(limit));
|
|
1250
|
-
return toToolResult(await ghExec(args, { cwd: ctx.cwd, signal, input: params }));
|
|
1251
|
-
},
|
|
1252
|
-
});
|
|
1253
1228
|
}
|
package/src/opencode-edit.ts
CHANGED
|
@@ -517,7 +517,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
517
517
|
} catch (error: unknown) {
|
|
518
518
|
throwIfAborted();
|
|
519
519
|
const msg =
|
|
520
|
-
error instanceof Error && "code" in error
|
|
520
|
+
error instanceof Error && "code" in error && typeof error.code === "string"
|
|
521
|
+
? `Error code: ${error.code}`
|
|
522
|
+
: String(error);
|
|
521
523
|
throw new Error(`Could not edit file: ${filePath}. ${msg}.`);
|
|
522
524
|
}
|
|
523
525
|
throwIfAborted();
|
package/src/opencode-read.ts
CHANGED
|
@@ -317,11 +317,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
317
317
|
),
|
|
318
318
|
}),
|
|
319
319
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
320
|
-
const {
|
|
321
|
-
filePath: rawPath,
|
|
322
|
-
offset,
|
|
323
|
-
limit,
|
|
324
|
-
} = params as { filePath: string; offset?: number; limit?: number };
|
|
320
|
+
const { filePath: rawPath, offset, limit } = params;
|
|
325
321
|
|
|
326
322
|
const absolutePath = isAbsolute(rawPath) ? rawPath : resolvePath(ctx.cwd, rawPath);
|
|
327
323
|
|
|
@@ -336,9 +332,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
336
332
|
} catch {
|
|
337
333
|
const suggestion = await didYouMean(absolutePath);
|
|
338
334
|
return {
|
|
339
|
-
content: [
|
|
340
|
-
{ type: "text", text: `File not found: ${absolutePath}${suggestion}` },
|
|
341
|
-
] as TextContent[],
|
|
335
|
+
content: [{ type: "text", text: `File not found: ${absolutePath}${suggestion}` }],
|
|
342
336
|
details: undefined,
|
|
343
337
|
};
|
|
344
338
|
}
|
|
@@ -366,7 +360,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
366
360
|
output += `\n</entries>`;
|
|
367
361
|
|
|
368
362
|
return {
|
|
369
|
-
content: [{ type: "text", text: output }]
|
|
363
|
+
content: [{ type: "text", text: output }],
|
|
370
364
|
details: undefined,
|
|
371
365
|
};
|
|
372
366
|
}
|
|
@@ -380,7 +374,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
380
374
|
await access(absolutePath, constants.R_OK);
|
|
381
375
|
} catch {
|
|
382
376
|
return {
|
|
383
|
-
content: [{ type: "text", text: `File not readable: ${absolutePath}` }]
|
|
377
|
+
content: [{ type: "text", text: `File not readable: ${absolutePath}` }],
|
|
384
378
|
details: undefined,
|
|
385
379
|
};
|
|
386
380
|
}
|
|
@@ -392,7 +386,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
392
386
|
const base64 = buffer.toString("base64");
|
|
393
387
|
content = [
|
|
394
388
|
{ type: "text", text: `[Image: ${mimeType}, ${formatSize(buffer.length)}]` },
|
|
395
|
-
{ type: "image", data: base64, mimeType }
|
|
389
|
+
{ type: "image", data: base64, mimeType },
|
|
396
390
|
];
|
|
397
391
|
return { content, details: undefined };
|
|
398
392
|
}
|
|
@@ -404,9 +398,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
404
398
|
// Binary file detection
|
|
405
399
|
if (isBinaryExtension(absolutePath) || isBinaryFileBySample(sample)) {
|
|
406
400
|
return {
|
|
407
|
-
content: [
|
|
408
|
-
{ type: "text", text: `Cannot read binary file: ${absolutePath}` },
|
|
409
|
-
] as TextContent[],
|
|
401
|
+
content: [{ type: "text", text: `Cannot read binary file: ${absolutePath}` }],
|
|
410
402
|
details: undefined,
|
|
411
403
|
};
|
|
412
404
|
}
|
|
@@ -426,7 +418,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
426
418
|
type: "text",
|
|
427
419
|
text: `Offset ${offset} is beyond end of file (${allLines.length} lines total)`,
|
|
428
420
|
},
|
|
429
|
-
]
|
|
421
|
+
],
|
|
430
422
|
details: undefined,
|
|
431
423
|
};
|
|
432
424
|
}
|
|
@@ -477,7 +469,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
477
469
|
}
|
|
478
470
|
}
|
|
479
471
|
|
|
480
|
-
content = [{ type: "text", text: outputText }]
|
|
472
|
+
content = [{ type: "text", text: outputText }];
|
|
481
473
|
|
|
482
474
|
return { content, details };
|
|
483
475
|
},
|
package/src/opencode-write.ts
CHANGED
|
@@ -35,7 +35,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
35
35
|
content: Type.String({ description: "The content to write to the file" }),
|
|
36
36
|
}),
|
|
37
37
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
38
|
-
const { filePath: rawPath, content } = params
|
|
38
|
+
const { filePath: rawPath, content } = params;
|
|
39
39
|
const absolutePath = resolvePath(ctx.cwd, rawPath);
|
|
40
40
|
const dir = dirname(absolutePath);
|
|
41
41
|
|
|
@@ -51,10 +51,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
51
51
|
throwIfAborted();
|
|
52
52
|
|
|
53
53
|
return {
|
|
54
|
-
content: [{ type: "text", text: `Wrote file successfully: ${absolutePath}` }]
|
|
55
|
-
type: "text";
|
|
56
|
-
text: string;
|
|
57
|
-
}>,
|
|
54
|
+
content: [{ type: "text", text: `Wrote file successfully: ${absolutePath}` }],
|
|
58
55
|
details: undefined,
|
|
59
56
|
};
|
|
60
57
|
});
|
package/src/todo-pendant.ts
CHANGED
|
@@ -36,6 +36,10 @@ interface TaskDetails {
|
|
|
36
36
|
error?: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function isTaskDetails(d: unknown): d is TaskDetails {
|
|
40
|
+
return typeof d === "object" && d !== null && "tasks" in d && Array.isArray(d.tasks);
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
// ---------------------------------------------------------------------------
|
|
40
44
|
// Helpers
|
|
41
45
|
// ---------------------------------------------------------------------------
|
|
@@ -65,8 +69,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
65
69
|
pi.on("tool_result", (event, ctx) => {
|
|
66
70
|
if (event.toolName !== "todo") return;
|
|
67
71
|
|
|
68
|
-
const details = event.details
|
|
69
|
-
if (!details
|
|
72
|
+
const details = event.details;
|
|
73
|
+
if (!isTaskDetails(details) || !details.tasks.length) {
|
|
70
74
|
ctx.ui.setWidget("todo-pendant", undefined);
|
|
71
75
|
return;
|
|
72
76
|
}
|
package/src/workspace-guard.ts
CHANGED
|
@@ -13,11 +13,18 @@
|
|
|
13
13
|
|
|
14
14
|
import { isAbsolute, join, resolve, relative, sep } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { ExtensionAPI, ToolCallEvent } from "@earendil-works/pi-coding-agent";
|
|
17
17
|
|
|
18
18
|
const WRITE_TOOLS = new Set(["write", "edit"]);
|
|
19
19
|
const ALWAYS_ALLOW = ["/tmp"];
|
|
20
20
|
|
|
21
|
+
function getWriteTarget(input: ToolCallEvent["input"]): string | undefined {
|
|
22
|
+
if (typeof input !== "object" || input === null) return undefined;
|
|
23
|
+
if ("path" in input && typeof input.path === "string") return input.path;
|
|
24
|
+
if ("filePath" in input && typeof input.filePath === "string") return input.filePath;
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
function resolvePath(filePath: string, cwd: string): string {
|
|
22
29
|
let p = filePath;
|
|
23
30
|
if (p.startsWith("~")) {
|
|
@@ -46,7 +53,7 @@ function isPathAllowed(resolvedPath: string, cwd: string): boolean {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
export default function (pi: ExtensionAPI) {
|
|
49
|
-
pi.on("before_agent_start",
|
|
56
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
50
57
|
const currentCwd = ctx.cwd;
|
|
51
58
|
return {
|
|
52
59
|
systemPrompt:
|
|
@@ -60,8 +67,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
60
67
|
pi.on("tool_call", async (event, ctx) => {
|
|
61
68
|
if (!WRITE_TOOLS.has(event.toolName)) return;
|
|
62
69
|
|
|
63
|
-
const
|
|
64
|
-
const rawPath = input.path ?? input.filePath;
|
|
70
|
+
const rawPath = getWriteTarget(event.input);
|
|
65
71
|
if (!rawPath) return;
|
|
66
72
|
|
|
67
73
|
const resolved = resolvePath(rawPath, ctx.cwd);
|