micode 0.7.0 → 0.7.2
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 +7 -13
- package/src/agents/artifact-searcher.ts +46 -0
- package/src/agents/brainstormer.ts +145 -0
- package/src/agents/codebase-analyzer.ts +75 -0
- package/src/agents/codebase-locator.ts +71 -0
- package/src/agents/commander.ts +138 -0
- package/src/agents/executor.ts +215 -0
- package/src/agents/implementer.ts +99 -0
- package/src/agents/index.ts +44 -0
- package/src/agents/ledger-creator.ts +113 -0
- package/src/agents/pattern-finder.ts +70 -0
- package/src/agents/planner.ts +230 -0
- package/src/agents/project-initializer.ts +264 -0
- package/src/agents/reviewer.ts +102 -0
- package/src/config-loader.ts +89 -0
- package/src/hooks/artifact-auto-index.ts +111 -0
- package/src/hooks/auto-clear-ledger.ts +230 -0
- package/src/hooks/auto-compact.ts +241 -0
- package/src/hooks/comment-checker.ts +120 -0
- package/src/hooks/context-injector.ts +163 -0
- package/src/hooks/context-window-monitor.ts +106 -0
- package/src/hooks/file-ops-tracker.ts +96 -0
- package/src/hooks/ledger-loader.ts +78 -0
- package/src/hooks/preemptive-compaction.ts +183 -0
- package/src/hooks/session-recovery.ts +258 -0
- package/src/hooks/token-aware-truncation.ts +189 -0
- package/src/index.ts +258 -0
- package/src/tools/artifact-index/index.ts +269 -0
- package/src/tools/artifact-index/schema.sql +44 -0
- package/src/tools/artifact-search.ts +49 -0
- package/src/tools/ast-grep/index.ts +189 -0
- package/src/tools/background-task/manager.ts +397 -0
- package/src/tools/background-task/tools.ts +145 -0
- package/src/tools/background-task/types.ts +68 -0
- package/src/tools/btca/index.ts +82 -0
- package/src/tools/look-at.ts +210 -0
- package/src/tools/pty/buffer.ts +49 -0
- package/src/tools/pty/index.ts +34 -0
- package/src/tools/pty/manager.ts +159 -0
- package/src/tools/pty/tools/kill.ts +68 -0
- package/src/tools/pty/tools/list.ts +55 -0
- package/src/tools/pty/tools/read.ts +152 -0
- package/src/tools/pty/tools/spawn.ts +78 -0
- package/src/tools/pty/tools/write.ts +97 -0
- package/src/tools/pty/types.ts +62 -0
- package/src/utils/model-limits.ts +36 -0
- package/dist/agents/artifact-searcher.d.ts +0 -2
- package/dist/agents/brainstormer.d.ts +0 -2
- package/dist/agents/codebase-analyzer.d.ts +0 -2
- package/dist/agents/codebase-locator.d.ts +0 -2
- package/dist/agents/commander.d.ts +0 -3
- package/dist/agents/executor.d.ts +0 -2
- package/dist/agents/implementer.d.ts +0 -2
- package/dist/agents/index.d.ts +0 -15
- package/dist/agents/ledger-creator.d.ts +0 -2
- package/dist/agents/pattern-finder.d.ts +0 -2
- package/dist/agents/planner.d.ts +0 -2
- package/dist/agents/project-initializer.d.ts +0 -2
- package/dist/agents/reviewer.d.ts +0 -2
- package/dist/config-loader.d.ts +0 -20
- package/dist/hooks/artifact-auto-index.d.ts +0 -19
- package/dist/hooks/auto-clear-ledger.d.ts +0 -11
- package/dist/hooks/auto-compact.d.ts +0 -9
- package/dist/hooks/comment-checker.d.ts +0 -9
- package/dist/hooks/context-injector.d.ts +0 -15
- package/dist/hooks/context-window-monitor.d.ts +0 -15
- package/dist/hooks/file-ops-tracker.d.ts +0 -26
- package/dist/hooks/ledger-loader.d.ts +0 -16
- package/dist/hooks/preemptive-compaction.d.ts +0 -9
- package/dist/hooks/session-recovery.d.ts +0 -9
- package/dist/hooks/token-aware-truncation.d.ts +0 -15
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -17089
- package/dist/tools/artifact-index/index.d.ts +0 -38
- package/dist/tools/artifact-search.d.ts +0 -17
- package/dist/tools/ast-grep/index.d.ts +0 -88
- package/dist/tools/background-task/manager.d.ts +0 -27
- package/dist/tools/background-task/tools.d.ts +0 -41
- package/dist/tools/background-task/types.d.ts +0 -53
- package/dist/tools/btca/index.d.ts +0 -19
- package/dist/tools/look-at.d.ts +0 -11
- package/dist/tools/pty/buffer.d.ts +0 -11
- package/dist/tools/pty/index.d.ts +0 -74
- package/dist/tools/pty/manager.d.ts +0 -14
- package/dist/tools/pty/tools/kill.d.ts +0 -12
- package/dist/tools/pty/tools/list.d.ts +0 -6
- package/dist/tools/pty/tools/read.d.ts +0 -18
- package/dist/tools/pty/tools/spawn.d.ts +0 -20
- package/dist/tools/pty/tools/write.d.ts +0 -12
- package/dist/tools/pty/types.d.ts +0 -54
- package/dist/utils/model-limits.d.ts +0 -7
- /package/{dist/tools/background-task/index.d.ts → src/tools/background-task/index.ts} +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import type { BackgroundTaskManager } from "./manager";
|
|
3
|
+
|
|
4
|
+
export function createBackgroundTaskTools(manager: BackgroundTaskManager) {
|
|
5
|
+
const background_task = tool({
|
|
6
|
+
description: `Launch a task to run in the background using a subagent.
|
|
7
|
+
The task runs independently while you continue working.
|
|
8
|
+
Use background_output to check progress or get results when complete.
|
|
9
|
+
Useful for: parallel research, concurrent implementation, async reviews.`,
|
|
10
|
+
args: {
|
|
11
|
+
description: tool.schema.string().describe("Short description of the task (shown in status)"),
|
|
12
|
+
prompt: tool.schema.string().describe("Full prompt/instructions for the background agent"),
|
|
13
|
+
agent: tool.schema.string().describe("Agent to use (e.g., 'codebase-analyzer', 'implementer')"),
|
|
14
|
+
},
|
|
15
|
+
execute: async (args, ctx) => {
|
|
16
|
+
try {
|
|
17
|
+
const task = await manager.launch({
|
|
18
|
+
description: args.description,
|
|
19
|
+
prompt: args.prompt,
|
|
20
|
+
agent: args.agent,
|
|
21
|
+
parentSessionID: ctx.sessionID,
|
|
22
|
+
parentMessageID: ctx.messageID || "",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return `## Background Task Launched
|
|
26
|
+
|
|
27
|
+
| Field | Value |
|
|
28
|
+
|-------|-------|
|
|
29
|
+
| Task ID | ${task.id} |
|
|
30
|
+
| Agent | ${args.agent} |
|
|
31
|
+
| Status | RUNNING |
|
|
32
|
+
|
|
33
|
+
Use \`background_output\` with task_id="${task.id}" to check progress or get results.`;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return `Failed to launch background task: ${error instanceof Error ? error.message : String(error)}`;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const background_output = tool({
|
|
41
|
+
description: `Get status or results from a background task.
|
|
42
|
+
Returns immediately with current status. Use background_list to poll for completion.`,
|
|
43
|
+
args: {
|
|
44
|
+
task_id: tool.schema.string().describe("ID of the task to check (e.g., 'bg_abc12345')"),
|
|
45
|
+
},
|
|
46
|
+
execute: async (args) => {
|
|
47
|
+
const { task_id } = args;
|
|
48
|
+
|
|
49
|
+
const task = manager.getTask(task_id);
|
|
50
|
+
if (!task) {
|
|
51
|
+
return `Task not found: ${task_id}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Format status
|
|
55
|
+
let output = manager.formatTaskStatus(task);
|
|
56
|
+
|
|
57
|
+
// Include result if completed
|
|
58
|
+
if (task.status === "completed") {
|
|
59
|
+
const result = await manager.getTaskResult(task_id);
|
|
60
|
+
if (result) {
|
|
61
|
+
output += `\n### Result\n${result}\n`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return output;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const background_cancel = tool({
|
|
70
|
+
description: `Cancel a running background task or all tasks.`,
|
|
71
|
+
args: {
|
|
72
|
+
task_id: tool.schema.string().optional().describe("ID of the task to cancel (omit to cancel all)"),
|
|
73
|
+
all: tool.schema.boolean().optional().describe("Cancel all running tasks (default: false)"),
|
|
74
|
+
},
|
|
75
|
+
execute: async (args) => {
|
|
76
|
+
const { task_id, all = false } = args;
|
|
77
|
+
|
|
78
|
+
if (all) {
|
|
79
|
+
const cancelled = await manager.cancelAll();
|
|
80
|
+
return `Cancelled ${cancelled} running task(s).`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!task_id) {
|
|
84
|
+
return "Provide task_id or set all=true to cancel tasks.";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const success = await manager.cancel(task_id);
|
|
88
|
+
if (success) {
|
|
89
|
+
return `Task ${task_id} cancelled.`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `Could not cancel task ${task_id}. It may already be completed or not exist.`;
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const background_list = tool({
|
|
97
|
+
description: `List all background tasks and their status.`,
|
|
98
|
+
args: {},
|
|
99
|
+
execute: async () => {
|
|
100
|
+
// Refresh status of running tasks before returning
|
|
101
|
+
await manager.refreshTaskStatus();
|
|
102
|
+
const tasks = manager.getAllTasks();
|
|
103
|
+
|
|
104
|
+
if (tasks.length === 0) {
|
|
105
|
+
return "No background tasks.";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
109
|
+
const errored = tasks.filter((t) => t.status === "error").length;
|
|
110
|
+
const running = tasks.filter((t) => t.status === "running").length;
|
|
111
|
+
const total = tasks.length;
|
|
112
|
+
const allDone = running === 0;
|
|
113
|
+
|
|
114
|
+
let output = "## Background Tasks\n\n";
|
|
115
|
+
output += `**Status: ${completed + errored}/${total} done${allDone ? " ✓ ALL COMPLETE" : ` (${running} still running)`}**\n\n`;
|
|
116
|
+
output += "| ID | Description | Agent | Status | Duration | Session |\n";
|
|
117
|
+
output += "|----|-------------|-------|--------|----------|---------|";
|
|
118
|
+
|
|
119
|
+
for (const task of tasks) {
|
|
120
|
+
const duration = task.completedAt
|
|
121
|
+
? `${Math.round((task.completedAt.getTime() - task.startedAt.getTime()) / 1000)}s`
|
|
122
|
+
: `${Math.round((Date.now() - task.startedAt.getTime()) / 1000)}s`;
|
|
123
|
+
|
|
124
|
+
// Show session status for debugging
|
|
125
|
+
const sessionStatus = (task as { _sessionStatus?: string })._sessionStatus || "?";
|
|
126
|
+
const statusDisplay = task.status === "running" ? `${task.status} (${sessionStatus})` : task.status;
|
|
127
|
+
|
|
128
|
+
output += `| ${task.id} | ${task.description} | ${task.agent} | ${statusDisplay} | ${duration} | ${task.sessionID} |\n`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (allDone) {
|
|
132
|
+
output += "\n**→ All tasks complete. Proceed to collect results with background_output.**";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return output;
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
background_task,
|
|
141
|
+
background_output,
|
|
142
|
+
background_cancel,
|
|
143
|
+
background_list,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface BackgroundTask {
|
|
2
|
+
id: string;
|
|
3
|
+
sessionID: string;
|
|
4
|
+
parentSessionID: string;
|
|
5
|
+
parentMessageID: string;
|
|
6
|
+
description: string;
|
|
7
|
+
prompt: string;
|
|
8
|
+
agent: string;
|
|
9
|
+
status: "running" | "completed" | "error" | "cancelled";
|
|
10
|
+
startedAt: Date;
|
|
11
|
+
completedAt?: Date;
|
|
12
|
+
result?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
progress?: {
|
|
15
|
+
toolCalls: number;
|
|
16
|
+
lastTool?: string;
|
|
17
|
+
lastUpdate: Date;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BackgroundTaskInput {
|
|
22
|
+
description: string;
|
|
23
|
+
prompt: string;
|
|
24
|
+
agent: string;
|
|
25
|
+
parentSessionID: string;
|
|
26
|
+
parentMessageID: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// API Response Types - SDK wraps responses in { data: T } format
|
|
30
|
+
export interface SessionCreateResponse {
|
|
31
|
+
data?: {
|
|
32
|
+
id?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// SessionStatus from OpenCode SDK - status is a discriminated union with 'type' field
|
|
37
|
+
export type SessionStatus =
|
|
38
|
+
| { type: "idle" }
|
|
39
|
+
| { type: "retry"; attempt: number; message: string; next: number }
|
|
40
|
+
| { type: "busy" };
|
|
41
|
+
|
|
42
|
+
// session.status() returns { data: map of sessionID -> SessionStatus }
|
|
43
|
+
export interface SessionStatusResponse {
|
|
44
|
+
data?: {
|
|
45
|
+
[sessionID: string]: SessionStatus;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MessagePart {
|
|
50
|
+
type: string;
|
|
51
|
+
text?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface MessageInfo {
|
|
55
|
+
role?: "user" | "assistant";
|
|
56
|
+
sessionID?: string;
|
|
57
|
+
type?: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SessionMessage {
|
|
62
|
+
info?: MessageInfo;
|
|
63
|
+
parts?: MessagePart[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SessionMessagesResponse {
|
|
67
|
+
data?: SessionMessage[];
|
|
68
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { spawn, which } from "bun";
|
|
2
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check if btca CLI is available on the system.
|
|
6
|
+
* Returns installation instructions if not found.
|
|
7
|
+
*/
|
|
8
|
+
export async function checkBtcaAvailable(): Promise<{ available: boolean; message?: string }> {
|
|
9
|
+
const btcaPath = which("btca");
|
|
10
|
+
if (btcaPath) {
|
|
11
|
+
return { available: true };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
available: false,
|
|
15
|
+
message:
|
|
16
|
+
"btca CLI not found. Library source code search will not work.\n" +
|
|
17
|
+
"Install from: https://github.com/davis7dotsh/better-context\n" +
|
|
18
|
+
" bun add -g btca",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const BTCA_TIMEOUT_MS = 120000; // 2 minutes for long clones
|
|
23
|
+
|
|
24
|
+
async function runBtca(args: string[]): Promise<{ output: string; error?: string }> {
|
|
25
|
+
try {
|
|
26
|
+
const proc = spawn(["btca", ...args], {
|
|
27
|
+
stdout: "pipe",
|
|
28
|
+
stderr: "pipe",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Create timeout promise
|
|
32
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
proc.kill();
|
|
35
|
+
reject(new Error("btca command timed out after 2 minutes"));
|
|
36
|
+
}, BTCA_TIMEOUT_MS);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Race between process completion and timeout
|
|
40
|
+
const [stdout, stderr, exitCode] = await Promise.race([
|
|
41
|
+
Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]),
|
|
42
|
+
timeoutPromise,
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
if (exitCode !== 0) {
|
|
46
|
+
const errorMsg = stderr.trim() || `Exit code ${exitCode}`;
|
|
47
|
+
return { output: "", error: errorMsg };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { output: stdout.trim() };
|
|
51
|
+
} catch (e) {
|
|
52
|
+
const err = e as Error;
|
|
53
|
+
if (err.message?.includes("ENOENT")) {
|
|
54
|
+
return {
|
|
55
|
+
output: "",
|
|
56
|
+
error:
|
|
57
|
+
"btca CLI not found. Install from: https://github.com/davis7dotsh/better-context\n" + " bun add -g btca",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { output: "", error: err.message };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const btca_ask = tool({
|
|
65
|
+
description:
|
|
66
|
+
"Ask questions about library/framework source code using btca. " +
|
|
67
|
+
"Clones repos locally and searches source code to answer questions. " +
|
|
68
|
+
"Use for understanding library internals, finding implementation details, or debugging.",
|
|
69
|
+
args: {
|
|
70
|
+
tech: tool.schema.string().describe("Resource name configured in btca (e.g., 'react', 'express')"),
|
|
71
|
+
question: tool.schema.string().describe("Question to ask about the library source code"),
|
|
72
|
+
},
|
|
73
|
+
execute: async (args) => {
|
|
74
|
+
const result = await runBtca(["ask", "-t", args.tech, "-q", args.question]);
|
|
75
|
+
|
|
76
|
+
if (result.error) {
|
|
77
|
+
return `Error: ${result.error}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result.output || "No answer found";
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { extname, basename } from "node:path";
|
|
4
|
+
|
|
5
|
+
// File size threshold for triggering extraction (100KB)
|
|
6
|
+
const LARGE_FILE_THRESHOLD = 100 * 1024;
|
|
7
|
+
|
|
8
|
+
// Max lines to return without extraction
|
|
9
|
+
const MAX_LINES_WITHOUT_EXTRACT = 200;
|
|
10
|
+
|
|
11
|
+
// Supported file types for smart extraction
|
|
12
|
+
const EXTRACTABLE_EXTENSIONS = [
|
|
13
|
+
".ts",
|
|
14
|
+
".tsx",
|
|
15
|
+
".js",
|
|
16
|
+
".jsx",
|
|
17
|
+
".py",
|
|
18
|
+
".go",
|
|
19
|
+
".rs",
|
|
20
|
+
".java",
|
|
21
|
+
".md",
|
|
22
|
+
".json",
|
|
23
|
+
".yaml",
|
|
24
|
+
".yml",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function extractStructure(content: string, ext: string): string {
|
|
28
|
+
const lines = content.split("\n");
|
|
29
|
+
|
|
30
|
+
switch (ext) {
|
|
31
|
+
case ".ts":
|
|
32
|
+
case ".tsx":
|
|
33
|
+
case ".js":
|
|
34
|
+
case ".jsx":
|
|
35
|
+
return extractTypeScriptStructure(lines);
|
|
36
|
+
case ".py":
|
|
37
|
+
return extractPythonStructure(lines);
|
|
38
|
+
case ".go":
|
|
39
|
+
return extractGoStructure(lines);
|
|
40
|
+
case ".md":
|
|
41
|
+
return extractMarkdownStructure(lines);
|
|
42
|
+
case ".json":
|
|
43
|
+
return extractJsonStructure(content);
|
|
44
|
+
case ".yaml":
|
|
45
|
+
case ".yml":
|
|
46
|
+
return extractYamlStructure(lines);
|
|
47
|
+
default:
|
|
48
|
+
return extractGenericStructure(lines);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractTypeScriptStructure(lines: string[]): string {
|
|
53
|
+
const output: string[] = ["## Structure\n"];
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
|
|
59
|
+
// Capture exports, classes, interfaces, types, functions
|
|
60
|
+
if (
|
|
61
|
+
trimmed.startsWith("export ") ||
|
|
62
|
+
trimmed.startsWith("class ") ||
|
|
63
|
+
trimmed.startsWith("interface ") ||
|
|
64
|
+
trimmed.startsWith("type ") ||
|
|
65
|
+
trimmed.startsWith("function ") ||
|
|
66
|
+
trimmed.startsWith("const ") ||
|
|
67
|
+
trimmed.startsWith("async function ")
|
|
68
|
+
) {
|
|
69
|
+
// Get the signature (first line only for multi-line)
|
|
70
|
+
const signature = trimmed.length > 80 ? `${trimmed.slice(0, 80)}...` : trimmed;
|
|
71
|
+
output.push(`Line ${i + 1}: ${signature}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return output.join("\n");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function extractPythonStructure(lines: string[]): string {
|
|
79
|
+
const output: string[] = ["## Structure\n"];
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < lines.length; i++) {
|
|
82
|
+
const line = lines[i];
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
|
|
85
|
+
// Capture classes, functions, decorators
|
|
86
|
+
if (
|
|
87
|
+
trimmed.startsWith("class ") ||
|
|
88
|
+
trimmed.startsWith("def ") ||
|
|
89
|
+
trimmed.startsWith("async def ") ||
|
|
90
|
+
trimmed.startsWith("@")
|
|
91
|
+
) {
|
|
92
|
+
const signature = trimmed.length > 80 ? `${trimmed.slice(0, 80)}...` : trimmed;
|
|
93
|
+
output.push(`Line ${i + 1}: ${signature}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return output.join("\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractGoStructure(lines: string[]): string {
|
|
101
|
+
const output: string[] = ["## Structure\n"];
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < lines.length; i++) {
|
|
104
|
+
const line = lines[i];
|
|
105
|
+
const trimmed = line.trim();
|
|
106
|
+
|
|
107
|
+
// Capture types, functions, methods
|
|
108
|
+
if (trimmed.startsWith("type ") || trimmed.startsWith("func ") || trimmed.startsWith("package ")) {
|
|
109
|
+
const signature = trimmed.length > 80 ? `${trimmed.slice(0, 80)}...` : trimmed;
|
|
110
|
+
output.push(`Line ${i + 1}: ${signature}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return output.join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function extractMarkdownStructure(lines: string[]): string {
|
|
118
|
+
const output: string[] = ["## Outline\n"];
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < lines.length; i++) {
|
|
121
|
+
const line = lines[i];
|
|
122
|
+
|
|
123
|
+
// Capture headings
|
|
124
|
+
if (line.startsWith("#")) {
|
|
125
|
+
output.push(`Line ${i + 1}: ${line}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return output.join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function extractJsonStructure(content: string): string {
|
|
133
|
+
try {
|
|
134
|
+
const obj = JSON.parse(content);
|
|
135
|
+
const keys = Object.keys(obj);
|
|
136
|
+
return `## Top-level keys (${keys.length})\n\n${keys.slice(0, 50).join(", ")}${keys.length > 50 ? "..." : ""}`;
|
|
137
|
+
} catch {
|
|
138
|
+
return "## Invalid JSON";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function extractYamlStructure(lines: string[]): string {
|
|
143
|
+
const output: string[] = ["## Top-level keys\n"];
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < lines.length; i++) {
|
|
146
|
+
const line = lines[i];
|
|
147
|
+
|
|
148
|
+
// Top-level keys (no indentation)
|
|
149
|
+
if (line.match(/^[a-zA-Z_][a-zA-Z0-9_]*:/)) {
|
|
150
|
+
output.push(`Line ${i + 1}: ${line}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return output.join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function extractGenericStructure(lines: string[]): string {
|
|
158
|
+
// For unknown files, just show first/last lines and line count
|
|
159
|
+
const total = lines.length;
|
|
160
|
+
const preview = lines.slice(0, 10).join("\n");
|
|
161
|
+
const tail = lines.slice(-5).join("\n");
|
|
162
|
+
|
|
163
|
+
return `## File Preview (${total} lines)\n\n### First 10 lines:\n${preview}\n\n### Last 5 lines:\n${tail}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const look_at = tool({
|
|
167
|
+
description: `Extract key information from a file to save context tokens.
|
|
168
|
+
For large files, returns structure/outline instead of full content.
|
|
169
|
+
Use when you need to understand a file without loading all content.
|
|
170
|
+
Ideal for: large files, getting file structure, quick overview.`,
|
|
171
|
+
args: {
|
|
172
|
+
filePath: tool.schema.string().describe("Path to the file"),
|
|
173
|
+
extract: tool.schema
|
|
174
|
+
.string()
|
|
175
|
+
.optional()
|
|
176
|
+
.describe("What to extract: 'structure', 'imports', 'exports', 'all' (default: auto)"),
|
|
177
|
+
},
|
|
178
|
+
execute: async (args) => {
|
|
179
|
+
try {
|
|
180
|
+
const stats = statSync(args.filePath);
|
|
181
|
+
const ext = extname(args.filePath).toLowerCase();
|
|
182
|
+
const name = basename(args.filePath);
|
|
183
|
+
|
|
184
|
+
// Read file
|
|
185
|
+
const content = readFileSync(args.filePath, "utf-8");
|
|
186
|
+
const lines = content.split("\n");
|
|
187
|
+
|
|
188
|
+
// For small files, return full content
|
|
189
|
+
if (stats.size < LARGE_FILE_THRESHOLD && lines.length <= MAX_LINES_WITHOUT_EXTRACT) {
|
|
190
|
+
return `## ${name} (${lines.length} lines)\n\n${content}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// For large files, extract structure
|
|
194
|
+
let output = `## ${name}\n`;
|
|
195
|
+
output += `**Size**: ${Math.round(stats.size / 1024)}KB | **Lines**: ${lines.length}\n\n`;
|
|
196
|
+
|
|
197
|
+
if (EXTRACTABLE_EXTENSIONS.includes(ext)) {
|
|
198
|
+
output += extractStructure(content, ext);
|
|
199
|
+
} else {
|
|
200
|
+
output += extractGenericStructure(lines);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
output += `\n\n---\n*Use Read tool with line offset/limit for specific sections*`;
|
|
204
|
+
|
|
205
|
+
return output;
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/tools/pty/buffer.ts
|
|
2
|
+
import type { SearchMatch } from "./types";
|
|
3
|
+
|
|
4
|
+
const parsed = parseInt(process.env.PTY_MAX_BUFFER_LINES || "50000", 10);
|
|
5
|
+
const DEFAULT_MAX_LINES = isNaN(parsed) ? 50000 : parsed;
|
|
6
|
+
|
|
7
|
+
export class RingBuffer {
|
|
8
|
+
private lines: string[] = [];
|
|
9
|
+
private maxLines: number;
|
|
10
|
+
|
|
11
|
+
constructor(maxLines: number = DEFAULT_MAX_LINES) {
|
|
12
|
+
this.maxLines = maxLines;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
append(data: string): void {
|
|
16
|
+
const newLines = data.split("\n");
|
|
17
|
+
for (const line of newLines) {
|
|
18
|
+
this.lines.push(line);
|
|
19
|
+
if (this.lines.length > this.maxLines) {
|
|
20
|
+
this.lines.shift();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
read(offset: number = 0, limit?: number): string[] {
|
|
26
|
+
const start = Math.max(0, offset);
|
|
27
|
+
const end = limit !== undefined ? start + limit : this.lines.length;
|
|
28
|
+
return this.lines.slice(start, end);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
search(pattern: RegExp): SearchMatch[] {
|
|
32
|
+
const matches: SearchMatch[] = [];
|
|
33
|
+
for (let i = 0; i < this.lines.length; i++) {
|
|
34
|
+
const line = this.lines[i];
|
|
35
|
+
if (line !== undefined && pattern.test(line)) {
|
|
36
|
+
matches.push({ lineNumber: i + 1, text: line });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return matches;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get length(): number {
|
|
43
|
+
return this.lines.length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
clear(): void {
|
|
47
|
+
this.lines = [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/tools/pty/index.ts
|
|
2
|
+
export { PTYManager } from "./manager";
|
|
3
|
+
export { RingBuffer } from "./buffer";
|
|
4
|
+
export { createPtySpawnTool } from "./tools/spawn";
|
|
5
|
+
export { createPtyWriteTool } from "./tools/write";
|
|
6
|
+
export { createPtyReadTool } from "./tools/read";
|
|
7
|
+
export { createPtyListTool } from "./tools/list";
|
|
8
|
+
export { createPtyKillTool } from "./tools/kill";
|
|
9
|
+
export type {
|
|
10
|
+
PTYSession,
|
|
11
|
+
PTYSessionInfo,
|
|
12
|
+
PTYStatus,
|
|
13
|
+
SpawnOptions,
|
|
14
|
+
ReadResult,
|
|
15
|
+
SearchMatch,
|
|
16
|
+
SearchResult,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
import type { PTYManager } from "./manager";
|
|
20
|
+
import { createPtySpawnTool } from "./tools/spawn";
|
|
21
|
+
import { createPtyWriteTool } from "./tools/write";
|
|
22
|
+
import { createPtyReadTool } from "./tools/read";
|
|
23
|
+
import { createPtyListTool } from "./tools/list";
|
|
24
|
+
import { createPtyKillTool } from "./tools/kill";
|
|
25
|
+
|
|
26
|
+
export function createPtyTools(manager: PTYManager) {
|
|
27
|
+
return {
|
|
28
|
+
pty_spawn: createPtySpawnTool(manager),
|
|
29
|
+
pty_write: createPtyWriteTool(manager),
|
|
30
|
+
pty_read: createPtyReadTool(manager),
|
|
31
|
+
pty_list: createPtyListTool(manager),
|
|
32
|
+
pty_kill: createPtyKillTool(manager),
|
|
33
|
+
};
|
|
34
|
+
}
|