terry-core 0.1.2 → 0.1.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/orchestrator.js +119 -21
- package/package.json +1 -1
package/dist/orchestrator.js
CHANGED
|
@@ -1,12 +1,108 @@
|
|
|
1
|
+
const KNOWN_COMMANDS = ["git", "npm", "pnpm", "yarn", "node", "npx"];
|
|
1
2
|
const TOOL_HINTS = [
|
|
2
|
-
{ tool: "
|
|
3
|
-
{ tool: "
|
|
3
|
+
{ tool: "runCommand", match: /\b(run|execute)\b|\b(git|npm|pnpm|yarn|node|npx)\b/i },
|
|
4
|
+
{ tool: "writeFile", match: /\b(write|create|update|edit|save)\b.*\b(file|readme|json|md|ts|js)\b/i },
|
|
5
|
+
{ tool: "readFile", match: /\b(read|open|show)\b.*\b(file|readme|json|md|ts|js)\b/i },
|
|
4
6
|
{ tool: "searchInFiles", match: /\b(search|find|grep|look for)\b/i },
|
|
5
7
|
{ tool: "gitStatus", match: /\bgit status|repo status|what changed\b/i },
|
|
6
8
|
{ tool: "gitDiff", match: /\bgit diff|show diff|changes\b/i },
|
|
7
|
-
{ tool: "runCommand", match: /\b(run|execute|command|npm|pnpm|yarn|test|build)\b/i },
|
|
8
9
|
{ tool: "listDirectory", match: /\b(list|ls|folders?|directories?)\b/i }
|
|
9
10
|
];
|
|
11
|
+
function stripWrappers(value) {
|
|
12
|
+
return value.trim().replace(/^["'`]|["'`]$/g, "");
|
|
13
|
+
}
|
|
14
|
+
function tokenizeCommand(raw) {
|
|
15
|
+
const tokens = [];
|
|
16
|
+
let current = "";
|
|
17
|
+
let quote = "";
|
|
18
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
19
|
+
const ch = raw[i];
|
|
20
|
+
if (!ch)
|
|
21
|
+
continue;
|
|
22
|
+
if (quote) {
|
|
23
|
+
if (ch === quote) {
|
|
24
|
+
quote = "";
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
current += ch;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === '"' || ch === "'") {
|
|
32
|
+
quote = ch;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (/\s/.test(ch)) {
|
|
36
|
+
if (current) {
|
|
37
|
+
tokens.push(current);
|
|
38
|
+
current = "";
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
current += ch;
|
|
43
|
+
}
|
|
44
|
+
if (current)
|
|
45
|
+
tokens.push(current);
|
|
46
|
+
return tokens;
|
|
47
|
+
}
|
|
48
|
+
function extractFirstPath(content) {
|
|
49
|
+
const quoted = content.match(/["'`]([^"'`]+)["'`]/);
|
|
50
|
+
if (quoted?.[1])
|
|
51
|
+
return quoted[1].trim();
|
|
52
|
+
const direct = content.match(/\b([./\\]?[a-zA-Z0-9_\-/\\.]+\.[a-zA-Z0-9_]+)\b/);
|
|
53
|
+
if (direct?.[1])
|
|
54
|
+
return direct[1].trim();
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function parseRunCommand(content) {
|
|
58
|
+
const backtick = content.match(/`([^`]+)`/);
|
|
59
|
+
const candidate = backtick?.[1] ??
|
|
60
|
+
content.match(/\b(?:run|execute)(?:\s+the)?(?:\s+command)?\s+(.+)$/i)?.[1] ??
|
|
61
|
+
content.match(new RegExp(`^\\s*(${KNOWN_COMMANDS.join("|")})\\b(.+)?$`, "i"))?.[0] ??
|
|
62
|
+
"";
|
|
63
|
+
const normalized = candidate.trim().replace(/[.;,\s]+$/g, "");
|
|
64
|
+
if (!normalized)
|
|
65
|
+
return null;
|
|
66
|
+
const tokens = tokenizeCommand(normalized);
|
|
67
|
+
if (tokens.length === 0)
|
|
68
|
+
return null;
|
|
69
|
+
const [cmd, ...args] = tokens;
|
|
70
|
+
if (!cmd || !KNOWN_COMMANDS.includes(cmd.toLowerCase())) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return { cmd, args };
|
|
74
|
+
}
|
|
75
|
+
function parseReadFileArgs(content) {
|
|
76
|
+
const pathMatch = content.match(/\b(?:read|open|show)(?:\s+the)?(?:\s+file)?\s+["'`]([^"'`]+)["'`]/i)?.[1] ??
|
|
77
|
+
extractFirstPath(content);
|
|
78
|
+
return { pathRelativeToWorkspace: pathMatch ?? "README.md" };
|
|
79
|
+
}
|
|
80
|
+
function parseWriteFileArgs(content) {
|
|
81
|
+
const pathMatch = content.match(/\b(?:write|create|update|edit|save)(?:\s+(?:to|into))?(?:\s+file)?\s+["'`]([^"'`]+)["'`]/i)?.[1] ??
|
|
82
|
+
content.match(/\b(?:write|create|update|edit|save)(?:\s+(?:to|into))?(?:\s+file)?\s+([^\s]+)\b/i)?.[1] ??
|
|
83
|
+
extractFirstPath(content) ??
|
|
84
|
+
"README.md";
|
|
85
|
+
const contentMatch = content.match(/\b(?:with content|with text|saying|that says)\s+([\s\S]+)$/i)?.[1] ??
|
|
86
|
+
content.match(/:\s*([\s\S]+)$/)?.[1] ??
|
|
87
|
+
`# Terry update\n\nGenerated from task:\n${content}\n`;
|
|
88
|
+
return {
|
|
89
|
+
pathRelativeToWorkspace: stripWrappers(pathMatch),
|
|
90
|
+
content: contentMatch.trim()
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function parseSearchArgs(content) {
|
|
94
|
+
const quoted = content.match(/["'`]([^"'`]+)["'`]/)?.[1];
|
|
95
|
+
const phrase = quoted ??
|
|
96
|
+
content.match(/\b(?:search|find|grep|look for)\b\s+([\s\S]+)$/i)?.[1] ??
|
|
97
|
+
content;
|
|
98
|
+
return { query: phrase.replace(/^for\s+/i, "").trim() };
|
|
99
|
+
}
|
|
100
|
+
function parseListDirectoryArgs(content) {
|
|
101
|
+
const target = content.match(/\b(?:in|inside|under)\s+["'`]([^"'`]+)["'`]/i)?.[1] ??
|
|
102
|
+
content.match(/\b(?:in|inside|under)\s+([^\s]+)\b/i)?.[1] ??
|
|
103
|
+
".";
|
|
104
|
+
return { pathRelativeToWorkspace: stripWrappers(target) || "." };
|
|
105
|
+
}
|
|
10
106
|
function inferTool(content) {
|
|
11
107
|
for (const hint of TOOL_HINTS) {
|
|
12
108
|
if (hint.match.test(content))
|
|
@@ -15,27 +111,22 @@ function inferTool(content) {
|
|
|
15
111
|
return null;
|
|
16
112
|
}
|
|
17
113
|
function inferArgs(tool, content) {
|
|
18
|
-
if (tool === "searchInFiles")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (tool === "runCommand") {
|
|
23
|
-
return { cmd: "echo", args: [content] };
|
|
24
|
-
}
|
|
114
|
+
if (tool === "searchInFiles")
|
|
115
|
+
return parseSearchArgs(content);
|
|
116
|
+
if (tool === "runCommand")
|
|
117
|
+
return parseRunCommand(content);
|
|
25
118
|
if (tool === "listDirectory")
|
|
26
|
-
return
|
|
119
|
+
return parseListDirectoryArgs(content);
|
|
27
120
|
if (tool === "gitStatus")
|
|
28
121
|
return {};
|
|
29
|
-
if (tool === "gitDiff")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return { pathRelativeToWorkspace: "README.md" };
|
|
33
|
-
if (tool === "writeFile") {
|
|
34
|
-
return {
|
|
35
|
-
pathRelativeToWorkspace: "README.md",
|
|
36
|
-
content: `# Terry update\n\nGenerated from task:\n${content}\n`
|
|
37
|
-
};
|
|
122
|
+
if (tool === "gitDiff") {
|
|
123
|
+
const target = extractFirstPath(content);
|
|
124
|
+
return target ? { path: target } : {};
|
|
38
125
|
}
|
|
126
|
+
if (tool === "readFile")
|
|
127
|
+
return parseReadFileArgs(content);
|
|
128
|
+
if (tool === "writeFile")
|
|
129
|
+
return parseWriteFileArgs(content);
|
|
39
130
|
return {};
|
|
40
131
|
}
|
|
41
132
|
export const rulesOrchestrator = (messages) => {
|
|
@@ -49,10 +140,17 @@ export const rulesOrchestrator = (messages) => {
|
|
|
49
140
|
summary: "I can help with workspace files, search, git, and command planning. Ask a concrete repo or file operation."
|
|
50
141
|
};
|
|
51
142
|
}
|
|
143
|
+
const args = inferArgs(tool, lastUser.content);
|
|
144
|
+
if (tool === "runCommand" && !args) {
|
|
145
|
+
return {
|
|
146
|
+
kind: "summarize",
|
|
147
|
+
summary: "I can run approved workspace commands. Please provide an explicit command, for example: run `npm test`."
|
|
148
|
+
};
|
|
149
|
+
}
|
|
52
150
|
return {
|
|
53
151
|
kind: "ask_permission",
|
|
54
152
|
tool,
|
|
55
|
-
args
|
|
153
|
+
args,
|
|
56
154
|
reason: `Detected request likely needs tool: ${tool}.`
|
|
57
155
|
};
|
|
58
156
|
};
|