@apholdings/jensen-code 0.0.3 → 0.0.5
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/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +6 -6
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +32 -25
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +25 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +1 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +4 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +25 -11
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +5 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +0 -2
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -146
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/header.d.ts +9 -3
- package/dist/modes/interactive/components/header.d.ts.map +1 -1
- package/dist/modes/interactive/components/header.js +125 -196
- package/dist/modes/interactive/components/header.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +1 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +23 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +657 -243
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +2 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +8 -4
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/examples/extensions/osgrep.ts +643 -0
- package/examples/extensions/subagent/agents.ts +150 -38
- package/examples/extensions/subagent/index.ts +634 -514
- package/package.json +4 -3
- package/examples/README.md +0 -25
- package/examples/extensions/README.md +0 -206
- package/examples/extensions/antigravity-image-gen.ts +0 -416
- package/examples/extensions/auto-commit-on-exit.ts +0 -50
- package/examples/extensions/bash-spawn-hook.ts +0 -31
- package/examples/extensions/bookmark.ts +0 -51
- package/examples/extensions/built-in-tool-renderer.ts +0 -247
- package/examples/extensions/claude-rules.ts +0 -87
- package/examples/extensions/commands.ts +0 -73
- package/examples/extensions/confirm-destructive.ts +0 -60
- package/examples/extensions/custom-compaction.ts +0 -115
- package/examples/extensions/custom-footer.ts +0 -65
- package/examples/extensions/custom-header.ts +0 -74
- package/examples/extensions/custom-provider-anthropic/index.ts +0 -605
- package/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
- package/examples/extensions/custom-provider-anthropic/package.json +0 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -350
- package/examples/extensions/custom-provider-gitlab-duo/package.json +0 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +0 -82
- package/examples/extensions/custom-provider-qwen-cli/index.ts +0 -346
- package/examples/extensions/custom-provider-qwen-cli/package.json +0 -16
- package/examples/extensions/dirty-repo-guard.ts +0 -57
- package/examples/extensions/doom-overlay/README.md +0 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +0 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +0 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +0 -72
- package/examples/extensions/doom-overlay/doom-component.ts +0 -132
- package/examples/extensions/doom-overlay/doom-engine.ts +0 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +0 -104
- package/examples/extensions/doom-overlay/index.ts +0 -75
- package/examples/extensions/doom-overlay/wad-finder.ts +0 -51
- package/examples/extensions/dynamic-resources/SKILL.md +0 -8
- package/examples/extensions/dynamic-resources/dynamic.json +0 -79
- package/examples/extensions/dynamic-resources/dynamic.md +0 -5
- package/examples/extensions/dynamic-resources/index.ts +0 -16
- package/examples/extensions/dynamic-tools.ts +0 -75
- package/examples/extensions/event-bus.ts +0 -44
- package/examples/extensions/file-trigger.ts +0 -42
- package/examples/extensions/git-checkpoint.ts +0 -54
- package/examples/extensions/handoff.ts +0 -151
- package/examples/extensions/hello.ts +0 -26
- package/examples/extensions/inline-bash.ts +0 -95
- package/examples/extensions/input-transform.ts +0 -44
- package/examples/extensions/interactive-shell.ts +0 -197
- package/examples/extensions/mac-system-theme.ts +0 -48
- package/examples/extensions/message-renderer.ts +0 -60
- package/examples/extensions/minimal-mode.ts +0 -427
- package/examples/extensions/modal-editor.ts +0 -86
- package/examples/extensions/model-status.ts +0 -32
- package/examples/extensions/notify.ts +0 -56
- package/examples/extensions/overlay-qa-tests.ts +0 -1349
- package/examples/extensions/overlay-test.ts +0 -151
- package/examples/extensions/permission-gate.ts +0 -35
- package/examples/extensions/pirate.ts +0 -48
- package/examples/extensions/plan-mode/README.md +0 -65
- package/examples/extensions/plan-mode/index.ts +0 -341
- package/examples/extensions/plan-mode/utils.ts +0 -168
- package/examples/extensions/preset.ts +0 -399
- package/examples/extensions/protected-paths.ts +0 -31
- package/examples/extensions/provider-payload.ts +0 -15
- package/examples/extensions/qna.ts +0 -120
- package/examples/extensions/question.ts +0 -265
- package/examples/extensions/questionnaire.ts +0 -428
- package/examples/extensions/rainbow-editor.ts +0 -89
- package/examples/extensions/reload-runtime.ts +0 -38
- package/examples/extensions/rpc-demo.ts +0 -125
- package/examples/extensions/sandbox/index.ts +0 -319
- package/examples/extensions/sandbox/package-lock.json +0 -92
- package/examples/extensions/sandbox/package.json +0 -19
- package/examples/extensions/send-user-message.ts +0 -98
- package/examples/extensions/session-name.ts +0 -28
- package/examples/extensions/shutdown-command.ts +0 -64
- package/examples/extensions/snake.ts +0 -344
- package/examples/extensions/space-invaders.ts +0 -561
- package/examples/extensions/ssh.ts +0 -221
- package/examples/extensions/status-line.ts +0 -41
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/examples/extensions/summarize.ts +0 -196
- package/examples/extensions/system-prompt-header.ts +0 -18
- package/examples/extensions/timed-confirm.ts +0 -71
- package/examples/extensions/titlebar-spinner.ts +0 -59
- package/examples/extensions/todo.ts +0 -300
- package/examples/extensions/tool-override.ts +0 -144
- package/examples/extensions/tools.ts +0 -147
- package/examples/extensions/trigger-compact.ts +0 -41
- package/examples/extensions/truncated-tool.ts +0 -193
- package/examples/extensions/widget-placement.ts +0 -18
- package/examples/extensions/with-deps/index.ts +0 -33
- package/examples/extensions/with-deps/package-lock.json +0 -31
- package/examples/extensions/with-deps/package.json +0 -22
- package/examples/rpc-extension-ui.ts +0 -632
- package/examples/sdk/01-minimal.ts +0 -23
- package/examples/sdk/02-custom-model.ts +0 -50
- package/examples/sdk/03-custom-prompt.ts +0 -56
- package/examples/sdk/04-skills.ts +0 -47
- package/examples/sdk/05-tools.ts +0 -57
- package/examples/sdk/06-extensions.ts +0 -89
- package/examples/sdk/07-context-files.ts +0 -41
- package/examples/sdk/08-prompt-templates.ts +0 -48
- package/examples/sdk/09-api-keys-and-oauth.ts +0 -49
- package/examples/sdk/10-settings.ts +0 -52
- package/examples/sdk/11-sessions.ts +0 -49
- package/examples/sdk/12-full-control.ts +0 -83
- package/examples/sdk/README.md +0 -145
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
type AgentToolResult,
|
|
7
|
+
DEFAULT_MAX_BYTES,
|
|
8
|
+
DEFAULT_MAX_LINES,
|
|
9
|
+
type ExtensionAPI,
|
|
10
|
+
formatSize,
|
|
11
|
+
type ToolRenderResultOptions,
|
|
12
|
+
type TruncationResult,
|
|
13
|
+
truncateHead,
|
|
14
|
+
} from "@apholdings/jensen-code";
|
|
15
|
+
import { Text } from "@apholdings/jensen-tui";
|
|
16
|
+
import { Type } from "@sinclair/typebox";
|
|
17
|
+
|
|
18
|
+
const OSGREP_BIN = process.platform === "win32" ? "osgrep.cmd" : "osgrep";
|
|
19
|
+
|
|
20
|
+
const OsgrepSearchParams = Type.Object({
|
|
21
|
+
query: Type.String({
|
|
22
|
+
description: "Semantic search query, e.g. 'where do we validate JWT tokens?'",
|
|
23
|
+
}),
|
|
24
|
+
maxResults: Type.Optional(
|
|
25
|
+
Type.Integer({
|
|
26
|
+
minimum: 1,
|
|
27
|
+
maximum: 200,
|
|
28
|
+
description: "Maximum total results to return (-m). Default: 12",
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
perFile: Type.Optional(
|
|
32
|
+
Type.Integer({
|
|
33
|
+
minimum: 1,
|
|
34
|
+
maximum: 50,
|
|
35
|
+
description: "Maximum matches per file (--per-file). Default: 2",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
content: Type.Optional(
|
|
39
|
+
Type.Boolean({
|
|
40
|
+
description: "Show full chunk content instead of snippets (--content)",
|
|
41
|
+
default: false,
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
scores: Type.Optional(
|
|
45
|
+
Type.Boolean({
|
|
46
|
+
description: "Show relevance scores (--scores)",
|
|
47
|
+
default: false,
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
minScore: Type.Optional(
|
|
51
|
+
Type.Number({
|
|
52
|
+
minimum: 0,
|
|
53
|
+
maximum: 1,
|
|
54
|
+
description: "Minimum score threshold (--min-score)",
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
compact: Type.Optional(
|
|
58
|
+
Type.Boolean({
|
|
59
|
+
description: "Show file paths only (--compact)",
|
|
60
|
+
default: false,
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
sync: Type.Optional(
|
|
64
|
+
Type.Boolean({
|
|
65
|
+
description: "Force re-index changed files before searching (--sync)",
|
|
66
|
+
default: false,
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
reset: Type.Optional(
|
|
70
|
+
Type.Boolean({
|
|
71
|
+
description: "Reset the index and re-index from scratch before searching (--reset)",
|
|
72
|
+
default: false,
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const OsgrepTraceParams = Type.Object({
|
|
78
|
+
symbol: Type.String({
|
|
79
|
+
description: "Function, method, or symbol to trace, e.g. 'registerTool'",
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
interface BaseDetails {
|
|
84
|
+
tool: "osgrep_search" | "osgrep_trace";
|
|
85
|
+
cwd: string;
|
|
86
|
+
args: string[];
|
|
87
|
+
outputLines: number;
|
|
88
|
+
exitCode?: number | null;
|
|
89
|
+
durationMs?: number;
|
|
90
|
+
truncation?: TruncationResult;
|
|
91
|
+
fullOutputPath?: string;
|
|
92
|
+
error?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface OsgrepSearchDetails extends BaseDetails {
|
|
96
|
+
tool: "osgrep_search";
|
|
97
|
+
query: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface OsgrepTraceDetails extends BaseDetails {
|
|
101
|
+
tool: "osgrep_trace";
|
|
102
|
+
symbol: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type OsgrepDetails = OsgrepSearchDetails | OsgrepTraceDetails;
|
|
106
|
+
|
|
107
|
+
function getTextContent(result: { content?: Array<{ type: string; text?: string }> }): string {
|
|
108
|
+
const block = result.content?.find((c) => c.type === "text");
|
|
109
|
+
return block?.text ?? "";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function countOutputLines(text: string): number {
|
|
113
|
+
return text
|
|
114
|
+
.split("\n")
|
|
115
|
+
.map((line) => line.trim())
|
|
116
|
+
.filter(Boolean).length;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function summarizeError(err: unknown): string {
|
|
120
|
+
if (typeof err === "string") return err;
|
|
121
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
122
|
+
const message = (err as { message?: unknown }).message;
|
|
123
|
+
if (typeof message === "string") return message;
|
|
124
|
+
}
|
|
125
|
+
return "unknown error";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function runOsgrep(
|
|
129
|
+
args: string[],
|
|
130
|
+
cwd: string,
|
|
131
|
+
signal?: AbortSignal,
|
|
132
|
+
): Promise<{
|
|
133
|
+
stdout: string;
|
|
134
|
+
stderr: string;
|
|
135
|
+
code: number | null;
|
|
136
|
+
durationMs: number;
|
|
137
|
+
}> {
|
|
138
|
+
const started = Date.now();
|
|
139
|
+
|
|
140
|
+
return await new Promise((resolve, reject) => {
|
|
141
|
+
const child = spawn(OSGREP_BIN, args, {
|
|
142
|
+
cwd,
|
|
143
|
+
env: {
|
|
144
|
+
...process.env,
|
|
145
|
+
NO_COLOR: "1",
|
|
146
|
+
},
|
|
147
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
148
|
+
windowsHide: true,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let stdout = "";
|
|
152
|
+
let stderr = "";
|
|
153
|
+
let settled = false;
|
|
154
|
+
|
|
155
|
+
const finish = (fn: () => void) => {
|
|
156
|
+
if (settled) return;
|
|
157
|
+
settled = true;
|
|
158
|
+
signal?.removeEventListener("abort", onAbort);
|
|
159
|
+
fn();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const onAbort = () => {
|
|
163
|
+
try {
|
|
164
|
+
child.kill("SIGTERM");
|
|
165
|
+
} catch {
|
|
166
|
+
// ignore
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
try {
|
|
171
|
+
child.kill("SIGKILL");
|
|
172
|
+
} catch {
|
|
173
|
+
// ignore
|
|
174
|
+
}
|
|
175
|
+
}, 250).unref?.();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
179
|
+
|
|
180
|
+
child.stdout.on("data", (chunk) => {
|
|
181
|
+
stdout += chunk.toString();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
child.stderr.on("data", (chunk) => {
|
|
185
|
+
stderr += chunk.toString();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
child.on("error", (err) => {
|
|
189
|
+
finish(() => reject(err));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
child.on("close", (code) => {
|
|
193
|
+
finish(() =>
|
|
194
|
+
resolve({
|
|
195
|
+
stdout: stdout.trim(),
|
|
196
|
+
stderr: stderr.trim(),
|
|
197
|
+
code,
|
|
198
|
+
durationMs: Date.now() - started,
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function withTruncation(text: string, details: OsgrepDetails): string {
|
|
206
|
+
const truncation = truncateHead(text, {
|
|
207
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
208
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
let resultText = truncation.content;
|
|
212
|
+
|
|
213
|
+
if (truncation.truncated) {
|
|
214
|
+
const tempDir = mkdtempSync(join(tmpdir(), "pi-osgrep-"));
|
|
215
|
+
const tempFile = join(tempDir, "output.txt");
|
|
216
|
+
writeFileSync(tempFile, text, { mode: 0o600 });
|
|
217
|
+
|
|
218
|
+
details.truncation = truncation;
|
|
219
|
+
details.fullOutputPath = tempFile;
|
|
220
|
+
|
|
221
|
+
resultText += `\n\n[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`;
|
|
222
|
+
resultText += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;
|
|
223
|
+
resultText += ` Full output saved to ${tempFile}]`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return resultText;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function renderSearchCall(
|
|
230
|
+
args: {
|
|
231
|
+
query: string;
|
|
232
|
+
maxResults?: number;
|
|
233
|
+
perFile?: number;
|
|
234
|
+
content?: boolean;
|
|
235
|
+
scores?: boolean;
|
|
236
|
+
minScore?: number;
|
|
237
|
+
compact?: boolean;
|
|
238
|
+
sync?: boolean;
|
|
239
|
+
reset?: boolean;
|
|
240
|
+
},
|
|
241
|
+
theme: any,
|
|
242
|
+
) {
|
|
243
|
+
const flags: string[] = [];
|
|
244
|
+
if (args.maxResults !== undefined) flags.push(`m=${args.maxResults}`);
|
|
245
|
+
if (args.perFile !== undefined) flags.push(`per-file=${args.perFile}`);
|
|
246
|
+
if (args.content) flags.push("content");
|
|
247
|
+
if (args.scores) flags.push("scores");
|
|
248
|
+
if (args.compact) flags.push("compact");
|
|
249
|
+
if (args.sync) flags.push("sync");
|
|
250
|
+
if (args.reset) flags.push("reset");
|
|
251
|
+
if (typeof args.minScore === "number") flags.push(`min-score=${args.minScore}`);
|
|
252
|
+
|
|
253
|
+
let text = theme.fg("toolTitle", theme.bold("osgrep_search ")) + theme.fg("accent", JSON.stringify(args.query));
|
|
254
|
+
|
|
255
|
+
if (flags.length > 0) {
|
|
256
|
+
text += theme.fg("muted", ` [${flags.join(" ")}]`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return new Text(text, 0, 0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function renderTraceCall(args: { symbol: string }, theme: any) {
|
|
263
|
+
const text = theme.fg("toolTitle", theme.bold("osgrep_trace ")) + theme.fg("accent", JSON.stringify(args.symbol));
|
|
264
|
+
|
|
265
|
+
return new Text(text, 0, 0);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderToolResult(
|
|
269
|
+
result: AgentToolResult<OsgrepDetails> & { isError?: boolean },
|
|
270
|
+
expanded: boolean,
|
|
271
|
+
theme: any,
|
|
272
|
+
) {
|
|
273
|
+
const details = result.details;
|
|
274
|
+
const content = getTextContent(result);
|
|
275
|
+
|
|
276
|
+
let header = theme.fg("toolTitle", theme.bold(details?.tool ?? "osgrep"));
|
|
277
|
+
if (details) header += theme.fg("muted", ` · ${details.outputLines} lines`);
|
|
278
|
+
if (details?.durationMs !== undefined) header += theme.fg("muted", ` · ${details.durationMs}ms`);
|
|
279
|
+
if (details?.truncation?.truncated) header += theme.fg("warning", " · truncated");
|
|
280
|
+
if (details?.error || result.isError) header += theme.fg("error", " · error");
|
|
281
|
+
|
|
282
|
+
if (expanded) {
|
|
283
|
+
return new Text(`${header}\n${content || "(no output)"}`, 0, 0);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const lines = (content || "(no output)").split("\n");
|
|
287
|
+
const preview = lines.slice(0, 12).join("\n");
|
|
288
|
+
const needsMore = lines.length > 12;
|
|
289
|
+
|
|
290
|
+
let text = `${header}\n${preview}`;
|
|
291
|
+
if (needsMore) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
292
|
+
|
|
293
|
+
return new Text(text, 0, 0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export default function (pi: ExtensionAPI) {
|
|
297
|
+
pi.registerTool<typeof OsgrepSearchParams, OsgrepSearchDetails>({
|
|
298
|
+
name: "osgrep_search",
|
|
299
|
+
label: "osgrep search",
|
|
300
|
+
description: `Semantic code search using local osgrep in the current working directory. Best for concept-based repository reconnaissance. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)}; if truncated, full output is saved to a temp file.`,
|
|
301
|
+
promptSnippet: "Semantic repository search with osgrep for concept-level code discovery.",
|
|
302
|
+
promptGuidelines: [
|
|
303
|
+
"Use osgrep_search when the task is about behavior, responsibility, or architecture rather than exact text.",
|
|
304
|
+
"Use grep for exact strings/symbols and osgrep_search for semantic reconnaissance.",
|
|
305
|
+
"Use compact=true for broad discovery, then rerun a targeted query for detailed content.",
|
|
306
|
+
"Keep queries concrete and repository-specific.",
|
|
307
|
+
],
|
|
308
|
+
parameters: OsgrepSearchParams,
|
|
309
|
+
|
|
310
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
311
|
+
if (params.content && params.compact) {
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: "text",
|
|
316
|
+
text: "Invalid parameters: content and compact cannot both be true.",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
details: {
|
|
320
|
+
tool: "osgrep_search",
|
|
321
|
+
query: params.query,
|
|
322
|
+
cwd: ctx.cwd,
|
|
323
|
+
args: [],
|
|
324
|
+
outputLines: 0,
|
|
325
|
+
error: "invalid_parameters",
|
|
326
|
+
} as OsgrepSearchDetails,
|
|
327
|
+
isError: true,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const args: string[] = [];
|
|
332
|
+
const maxResults = params.maxResults ?? 12;
|
|
333
|
+
const perFile = params.perFile ?? 2;
|
|
334
|
+
|
|
335
|
+
args.push("-m", String(maxResults));
|
|
336
|
+
args.push("--per-file", String(perFile));
|
|
337
|
+
|
|
338
|
+
if (params.content) args.push("--content");
|
|
339
|
+
if (params.scores) args.push("--scores");
|
|
340
|
+
if (typeof params.minScore === "number") args.push("--min-score", String(params.minScore));
|
|
341
|
+
if (params.compact) args.push("--compact");
|
|
342
|
+
if (params.sync) args.push("--sync");
|
|
343
|
+
if (params.reset) args.push("--reset");
|
|
344
|
+
|
|
345
|
+
args.push(params.query);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const execResult = await runOsgrep(args, ctx.cwd, signal);
|
|
349
|
+
const combined = [execResult.stderr, execResult.stdout].filter(Boolean).join("\n").trim();
|
|
350
|
+
|
|
351
|
+
if (signal?.aborted) {
|
|
352
|
+
return {
|
|
353
|
+
content: [{ type: "text", text: "osgrep search was aborted." }],
|
|
354
|
+
details: {
|
|
355
|
+
tool: "osgrep_search",
|
|
356
|
+
query: params.query,
|
|
357
|
+
cwd: ctx.cwd,
|
|
358
|
+
args,
|
|
359
|
+
outputLines: 0,
|
|
360
|
+
exitCode: execResult.code,
|
|
361
|
+
durationMs: execResult.durationMs,
|
|
362
|
+
error: "aborted",
|
|
363
|
+
} as OsgrepSearchDetails,
|
|
364
|
+
isError: true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (execResult.code && execResult.code !== 0) {
|
|
369
|
+
if (execResult.code === 1 || /no matches/i.test(combined)) {
|
|
370
|
+
return {
|
|
371
|
+
content: [{ type: "text", text: "No matches found" }],
|
|
372
|
+
details: {
|
|
373
|
+
tool: "osgrep_search",
|
|
374
|
+
query: params.query,
|
|
375
|
+
cwd: ctx.cwd,
|
|
376
|
+
args,
|
|
377
|
+
outputLines: 0,
|
|
378
|
+
exitCode: execResult.code,
|
|
379
|
+
durationMs: execResult.durationMs,
|
|
380
|
+
} as OsgrepSearchDetails,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: `osgrep failed: ${combined || `exit code ${String(execResult.code)}`}`,
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
details: {
|
|
392
|
+
tool: "osgrep_search",
|
|
393
|
+
query: params.query,
|
|
394
|
+
cwd: ctx.cwd,
|
|
395
|
+
args,
|
|
396
|
+
outputLines: 0,
|
|
397
|
+
exitCode: execResult.code,
|
|
398
|
+
durationMs: execResult.durationMs,
|
|
399
|
+
error: combined || `exit code ${String(execResult.code)}`,
|
|
400
|
+
} as OsgrepSearchDetails,
|
|
401
|
+
isError: true,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!execResult.stdout.trim()) {
|
|
406
|
+
return {
|
|
407
|
+
content: [{ type: "text", text: "No matches found" }],
|
|
408
|
+
details: {
|
|
409
|
+
tool: "osgrep_search",
|
|
410
|
+
query: params.query,
|
|
411
|
+
cwd: ctx.cwd,
|
|
412
|
+
args,
|
|
413
|
+
outputLines: 0,
|
|
414
|
+
exitCode: execResult.code,
|
|
415
|
+
durationMs: execResult.durationMs,
|
|
416
|
+
} as OsgrepSearchDetails,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const details: OsgrepSearchDetails = {
|
|
421
|
+
tool: "osgrep_search",
|
|
422
|
+
query: params.query,
|
|
423
|
+
cwd: ctx.cwd,
|
|
424
|
+
args,
|
|
425
|
+
outputLines: countOutputLines(execResult.stdout),
|
|
426
|
+
exitCode: execResult.code,
|
|
427
|
+
durationMs: execResult.durationMs,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const resultText = withTruncation(execResult.stdout, details);
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
content: [{ type: "text", text: resultText }],
|
|
434
|
+
details,
|
|
435
|
+
};
|
|
436
|
+
} catch (err) {
|
|
437
|
+
const message = summarizeError(err);
|
|
438
|
+
|
|
439
|
+
if (/ENOENT/i.test(message) || /not found/i.test(message)) {
|
|
440
|
+
return {
|
|
441
|
+
content: [
|
|
442
|
+
{
|
|
443
|
+
type: "text",
|
|
444
|
+
text: "osgrep executable not found in PATH. Install osgrep and ensure Jensen can see it.",
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
details: {
|
|
448
|
+
tool: "osgrep_search",
|
|
449
|
+
query: params.query,
|
|
450
|
+
cwd: ctx.cwd,
|
|
451
|
+
args,
|
|
452
|
+
outputLines: 0,
|
|
453
|
+
error: "ENOENT",
|
|
454
|
+
} as OsgrepSearchDetails,
|
|
455
|
+
isError: true,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
{
|
|
462
|
+
type: "text",
|
|
463
|
+
text: `osgrep failed: ${message}`,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
details: {
|
|
467
|
+
tool: "osgrep_search",
|
|
468
|
+
query: params.query,
|
|
469
|
+
cwd: ctx.cwd,
|
|
470
|
+
args,
|
|
471
|
+
outputLines: 0,
|
|
472
|
+
error: message,
|
|
473
|
+
} as OsgrepSearchDetails,
|
|
474
|
+
isError: true,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
renderCall(args, theme) {
|
|
480
|
+
return renderSearchCall(args, theme);
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
renderResult(result: AgentToolResult<OsgrepSearchDetails>, { expanded }: ToolRenderResultOptions, theme) {
|
|
484
|
+
return renderToolResult(result, expanded, theme);
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
pi.registerTool<typeof OsgrepTraceParams, OsgrepTraceDetails>({
|
|
489
|
+
name: "osgrep_trace",
|
|
490
|
+
label: "osgrep trace",
|
|
491
|
+
description: `Call-graph tracing using local osgrep in the current working directory. Use it for impact analysis: who calls a symbol and what it calls. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)}; if truncated, full output is saved to a temp file.`,
|
|
492
|
+
promptSnippet: "Call-graph and impact tracing with osgrep for upstream/downstream code understanding.",
|
|
493
|
+
promptGuidelines: [
|
|
494
|
+
"Use osgrep_trace when you need blast-radius analysis for a function, method, or symbol.",
|
|
495
|
+
"Use osgrep_trace before refactors that may affect callers or callees.",
|
|
496
|
+
"Use osgrep_search to find concepts; use osgrep_trace to understand dependencies around a specific symbol.",
|
|
497
|
+
],
|
|
498
|
+
parameters: OsgrepTraceParams,
|
|
499
|
+
|
|
500
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
501
|
+
const args = ["trace", params.symbol];
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
const execResult = await runOsgrep(args, ctx.cwd, signal);
|
|
505
|
+
const combined = [execResult.stderr, execResult.stdout].filter(Boolean).join("\n").trim();
|
|
506
|
+
|
|
507
|
+
if (signal?.aborted) {
|
|
508
|
+
return {
|
|
509
|
+
content: [{ type: "text", text: "osgrep trace was aborted." }],
|
|
510
|
+
details: {
|
|
511
|
+
tool: "osgrep_trace",
|
|
512
|
+
symbol: params.symbol,
|
|
513
|
+
cwd: ctx.cwd,
|
|
514
|
+
args,
|
|
515
|
+
outputLines: 0,
|
|
516
|
+
exitCode: execResult.code,
|
|
517
|
+
durationMs: execResult.durationMs,
|
|
518
|
+
error: "aborted",
|
|
519
|
+
} as OsgrepTraceDetails,
|
|
520
|
+
isError: true,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (execResult.code && execResult.code !== 0) {
|
|
525
|
+
if (execResult.code === 1 || /no matches|not found|no trace/i.test(combined)) {
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: "text", text: "No trace data found" }],
|
|
528
|
+
details: {
|
|
529
|
+
tool: "osgrep_trace",
|
|
530
|
+
symbol: params.symbol,
|
|
531
|
+
cwd: ctx.cwd,
|
|
532
|
+
args,
|
|
533
|
+
outputLines: 0,
|
|
534
|
+
exitCode: execResult.code,
|
|
535
|
+
durationMs: execResult.durationMs,
|
|
536
|
+
} as OsgrepTraceDetails,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
text: `osgrep trace failed: ${combined || `exit code ${String(execResult.code)}`}`,
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
details: {
|
|
548
|
+
tool: "osgrep_trace",
|
|
549
|
+
symbol: params.symbol,
|
|
550
|
+
cwd: ctx.cwd,
|
|
551
|
+
args,
|
|
552
|
+
outputLines: 0,
|
|
553
|
+
exitCode: execResult.code,
|
|
554
|
+
durationMs: execResult.durationMs,
|
|
555
|
+
error: combined || `exit code ${String(execResult.code)}`,
|
|
556
|
+
} as OsgrepTraceDetails,
|
|
557
|
+
isError: true,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (!execResult.stdout.trim()) {
|
|
562
|
+
return {
|
|
563
|
+
content: [{ type: "text", text: "No trace data found" }],
|
|
564
|
+
details: {
|
|
565
|
+
tool: "osgrep_trace",
|
|
566
|
+
symbol: params.symbol,
|
|
567
|
+
cwd: ctx.cwd,
|
|
568
|
+
args,
|
|
569
|
+
outputLines: 0,
|
|
570
|
+
exitCode: execResult.code,
|
|
571
|
+
durationMs: execResult.durationMs,
|
|
572
|
+
} as OsgrepTraceDetails,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const details: OsgrepTraceDetails = {
|
|
577
|
+
tool: "osgrep_trace",
|
|
578
|
+
symbol: params.symbol,
|
|
579
|
+
cwd: ctx.cwd,
|
|
580
|
+
args,
|
|
581
|
+
outputLines: countOutputLines(execResult.stdout),
|
|
582
|
+
exitCode: execResult.code,
|
|
583
|
+
durationMs: execResult.durationMs,
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const resultText = withTruncation(execResult.stdout, details);
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
content: [{ type: "text", text: resultText }],
|
|
590
|
+
details,
|
|
591
|
+
};
|
|
592
|
+
} catch (err) {
|
|
593
|
+
const message = summarizeError(err);
|
|
594
|
+
|
|
595
|
+
if (/ENOENT/i.test(message) || /not found/i.test(message)) {
|
|
596
|
+
return {
|
|
597
|
+
content: [
|
|
598
|
+
{
|
|
599
|
+
type: "text",
|
|
600
|
+
text: "osgrep executable not found in PATH. Install osgrep and ensure Jensen can see it.",
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
details: {
|
|
604
|
+
tool: "osgrep_trace",
|
|
605
|
+
symbol: params.symbol,
|
|
606
|
+
cwd: ctx.cwd,
|
|
607
|
+
args,
|
|
608
|
+
outputLines: 0,
|
|
609
|
+
error: "ENOENT",
|
|
610
|
+
} as OsgrepTraceDetails,
|
|
611
|
+
isError: true,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
content: [
|
|
617
|
+
{
|
|
618
|
+
type: "text",
|
|
619
|
+
text: `osgrep trace failed: ${message}`,
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
details: {
|
|
623
|
+
tool: "osgrep_trace",
|
|
624
|
+
symbol: params.symbol,
|
|
625
|
+
cwd: ctx.cwd,
|
|
626
|
+
args,
|
|
627
|
+
outputLines: 0,
|
|
628
|
+
error: message,
|
|
629
|
+
} as OsgrepTraceDetails,
|
|
630
|
+
isError: true,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
renderCall(args, theme) {
|
|
636
|
+
return renderTraceCall(args, theme);
|
|
637
|
+
},
|
|
638
|
+
|
|
639
|
+
renderResult(result: AgentToolResult<OsgrepTraceDetails>, { expanded }: ToolRenderResultOptions, theme) {
|
|
640
|
+
return renderToolResult(result, expanded, theme);
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
}
|