terry-core 1.0.0 → 1.0.3
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/config.d.ts +1 -0
- package/dist/config.js +61 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +227 -14
- package/dist/permissions.js +3 -1
- package/dist/policy.js +23 -3
- package/dist/status-events.d.ts +9 -0
- package/dist/status-events.js +27 -0
- package/dist/telemetry.d.ts +9 -0
- package/dist/telemetry.js +12 -0
- package/dist/tools.d.ts +7 -0
- package/dist/tools.js +158 -1
- package/dist/types.d.ts +13 -2
- package/package.json +2 -2
package/dist/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type WorkspaceSettings } from "./types.js";
|
|
|
2
2
|
export declare function terryHomeDir(): string;
|
|
3
3
|
export declare function workspacesFile(): string;
|
|
4
4
|
export declare function auditLogFile(): string;
|
|
5
|
+
export declare function telemetryLogFile(): string;
|
|
5
6
|
export declare function usageDir(): string;
|
|
6
7
|
export declare function cloudSyncConfigFile(): string;
|
|
7
8
|
export declare function memoryFile(): string;
|
package/dist/config.js
CHANGED
|
@@ -7,11 +7,13 @@ const DEFAULT_TOOLS = {
|
|
|
7
7
|
listDirectory: "enabled",
|
|
8
8
|
readFile: "enabled",
|
|
9
9
|
writeFile: "enabled",
|
|
10
|
+
moveFile: "enabled",
|
|
10
11
|
searchInFiles: "enabled",
|
|
11
12
|
webSearch: "enabled",
|
|
12
13
|
gitStatus: "enabled",
|
|
13
14
|
gitDiff: "enabled",
|
|
14
15
|
runCommand: "enabled",
|
|
16
|
+
imageGenerate: "ask",
|
|
15
17
|
memorySet: "enabled",
|
|
16
18
|
memoryGet: "enabled",
|
|
17
19
|
openCalendarEvent: "ask",
|
|
@@ -22,6 +24,22 @@ const DEFAULT_TOOLS = {
|
|
|
22
24
|
openEmail: "ask",
|
|
23
25
|
openMapsDirections: "ask"
|
|
24
26
|
};
|
|
27
|
+
function normalizeExecutionMode(value) {
|
|
28
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
29
|
+
if (normalized === "autonomous")
|
|
30
|
+
return "full";
|
|
31
|
+
if (normalized === "plan" || normalized === "safe" || normalized === "full") {
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
return DEFAULT_EXECUTION_MODE;
|
|
35
|
+
}
|
|
36
|
+
function normalizeWorkspaceKey(workspacePath) {
|
|
37
|
+
const resolved = path.resolve(workspacePath);
|
|
38
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
39
|
+
}
|
|
40
|
+
function canonicalWorkspacePath(workspacePath) {
|
|
41
|
+
return path.resolve(workspacePath);
|
|
42
|
+
}
|
|
25
43
|
export function terryHomeDir() {
|
|
26
44
|
return path.join(os.homedir(), ".terry");
|
|
27
45
|
}
|
|
@@ -31,6 +49,9 @@ export function workspacesFile() {
|
|
|
31
49
|
export function auditLogFile() {
|
|
32
50
|
return path.join(terryHomeDir(), "audit.log.jsonl");
|
|
33
51
|
}
|
|
52
|
+
export function telemetryLogFile() {
|
|
53
|
+
return path.join(terryHomeDir(), "telemetry.log.jsonl");
|
|
54
|
+
}
|
|
34
55
|
export function usageDir() {
|
|
35
56
|
return path.join(terryHomeDir(), "usage");
|
|
36
57
|
}
|
|
@@ -60,10 +81,23 @@ export async function saveWorkspaceMap(map) {
|
|
|
60
81
|
}
|
|
61
82
|
export async function getWorkspaceSettings(workspacePath) {
|
|
62
83
|
const map = await loadWorkspaceMap();
|
|
63
|
-
const key =
|
|
84
|
+
const key = normalizeWorkspaceKey(workspacePath);
|
|
85
|
+
const canonicalPath = canonicalWorkspacePath(workspacePath);
|
|
86
|
+
const legacyKey = Object.keys(map).find((entry) => normalizeWorkspaceKey(entry) === key);
|
|
87
|
+
if (legacyKey && legacyKey !== key) {
|
|
88
|
+
const legacyValue = map[legacyKey];
|
|
89
|
+
if (legacyValue) {
|
|
90
|
+
map[key] = {
|
|
91
|
+
...legacyValue,
|
|
92
|
+
workspacePath: canonicalPath
|
|
93
|
+
};
|
|
94
|
+
delete map[legacyKey];
|
|
95
|
+
await saveWorkspaceMap(map);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
64
98
|
if (!map[key]) {
|
|
65
99
|
map[key] = {
|
|
66
|
-
workspacePath:
|
|
100
|
+
workspacePath: canonicalPath,
|
|
67
101
|
dryRun: false,
|
|
68
102
|
mode: DEFAULT_MODE,
|
|
69
103
|
executionMode: DEFAULT_EXECUTION_MODE,
|
|
@@ -75,12 +109,20 @@ export async function getWorkspaceSettings(workspacePath) {
|
|
|
75
109
|
map[key] = {
|
|
76
110
|
...map[key],
|
|
77
111
|
mode: DEFAULT_MODE,
|
|
78
|
-
executionMode: map[key].executionMode
|
|
112
|
+
executionMode: normalizeExecutionMode(map[key].executionMode),
|
|
79
113
|
tools: { ...DEFAULT_TOOLS, ...map[key].tools }
|
|
80
114
|
};
|
|
81
115
|
await saveWorkspaceMap(map);
|
|
82
116
|
}
|
|
83
117
|
else {
|
|
118
|
+
const normalizedExecution = normalizeExecutionMode(map[key].executionMode);
|
|
119
|
+
if (normalizedExecution !== map[key].executionMode) {
|
|
120
|
+
map[key] = {
|
|
121
|
+
...map[key],
|
|
122
|
+
executionMode: normalizedExecution
|
|
123
|
+
};
|
|
124
|
+
await saveWorkspaceMap(map);
|
|
125
|
+
}
|
|
84
126
|
const levels = Object.values(map[key].tools ?? {});
|
|
85
127
|
const allDisabled = levels.length > 0 && levels.every((level) => level === "disabled");
|
|
86
128
|
const legacyPattern = map[key].tools?.listDirectory === "disabled" &&
|
|
@@ -110,17 +152,31 @@ export async function getWorkspaceSettings(workspacePath) {
|
|
|
110
152
|
}
|
|
111
153
|
export async function setWorkspaceSettings(workspacePath, patch) {
|
|
112
154
|
const map = await loadWorkspaceMap();
|
|
113
|
-
const key =
|
|
155
|
+
const key = normalizeWorkspaceKey(workspacePath);
|
|
156
|
+
const canonicalPath = canonicalWorkspacePath(workspacePath);
|
|
157
|
+
const legacyKey = Object.keys(map).find((entry) => normalizeWorkspaceKey(entry) === key);
|
|
158
|
+
if (legacyKey && legacyKey !== key) {
|
|
159
|
+
const legacyValue = map[legacyKey];
|
|
160
|
+
if (legacyValue) {
|
|
161
|
+
map[key] = {
|
|
162
|
+
...legacyValue,
|
|
163
|
+
workspacePath: canonicalPath
|
|
164
|
+
};
|
|
165
|
+
delete map[legacyKey];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
114
168
|
const current = map[key] ?? {
|
|
115
|
-
workspacePath:
|
|
169
|
+
workspacePath: canonicalPath,
|
|
116
170
|
dryRun: false,
|
|
117
171
|
mode: DEFAULT_MODE,
|
|
118
172
|
executionMode: DEFAULT_EXECUTION_MODE,
|
|
119
173
|
tools: { ...DEFAULT_TOOLS }
|
|
120
174
|
};
|
|
175
|
+
const nextExecutionMode = normalizeExecutionMode((patch.executionMode ?? current.executionMode));
|
|
121
176
|
map[key] = {
|
|
122
177
|
...current,
|
|
123
178
|
...patch,
|
|
179
|
+
executionMode: nextExecutionMode,
|
|
124
180
|
tools: { ...current.tools, ...(patch.tools ?? {}) }
|
|
125
181
|
};
|
|
126
182
|
await saveWorkspaceMap(map);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/orchestrator.d.ts
CHANGED
package/dist/orchestrator.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
const KNOWN_COMMANDS = [
|
|
1
|
+
const KNOWN_COMMANDS = [
|
|
2
|
+
"git",
|
|
3
|
+
"npm",
|
|
4
|
+
"pnpm",
|
|
5
|
+
"yarn",
|
|
6
|
+
"node",
|
|
7
|
+
"npx",
|
|
8
|
+
"powershell",
|
|
9
|
+
"pwsh",
|
|
10
|
+
"cmd",
|
|
11
|
+
"bash",
|
|
12
|
+
"sh",
|
|
13
|
+
"python",
|
|
14
|
+
"python3"
|
|
15
|
+
];
|
|
2
16
|
const TOOL_HINTS = [
|
|
3
|
-
{ tool: "webSearch", match: /\b(web search|search web|lookup online|latest
|
|
17
|
+
{ tool: "webSearch", match: /\b(web search|search web|lookup online|latest(?:\s+on)?|news(?:\s+on)?|current events?)\b/i },
|
|
18
|
+
{ tool: "imageGenerate", match: /\b(generate|create|make|design)\b.*\b(image|logo|icon|picture|art|illustration)\b|\b(dall[- ]?e|image generation)\b/i },
|
|
19
|
+
{ tool: "moveFile", match: /\b(move|rename)\b.*\b(file|folder|directory)\b|\bmove\s+.+\s+to\s+.+\b/i },
|
|
4
20
|
{ tool: "memorySet", match: /\bremember(?:\s+that)?\b/i },
|
|
5
21
|
{ tool: "memoryGet", match: /\b(what do you remember|what did i tell you|recall|remember my|what is my)\b/i },
|
|
6
22
|
{ tool: "openCalendarEvent", match: /\b(schedule|calendar|meeting|event)\b/i },
|
|
@@ -10,7 +26,7 @@ const TOOL_HINTS = [
|
|
|
10
26
|
{ tool: "openSlackCompose", match: /\bslack\b.*\b(message|send|compose)\b/i },
|
|
11
27
|
{ tool: "openEmail", match: /\b(draft an email|send an email|mailto|email)\b/i },
|
|
12
28
|
{ tool: "openMapsDirections", match: /\b(directions?|navigate|maps?)\b/i },
|
|
13
|
-
{ tool: "runCommand", match: /\b(run|execute)\b|\b(git|npm|pnpm|yarn|node|npx)\b/i },
|
|
29
|
+
{ tool: "runCommand", match: /\b(run|execute)\b|\b(git|npm|pnpm|yarn|node|npx|powershell|pwsh|cmd|bash|python|python3)\b/i },
|
|
14
30
|
{ tool: "writeFile", match: /\b(write|create|update|edit|save)\b.*\b(file|readme|json|md|ts|js)\b/i },
|
|
15
31
|
{ tool: "readFile", match: /\b(read|open|show)\b.*\b(file|readme|json|md|ts|js)\b/i },
|
|
16
32
|
{ tool: "searchInFiles", match: /\b(search|find|grep|look for)\b/i },
|
|
@@ -18,6 +34,27 @@ const TOOL_HINTS = [
|
|
|
18
34
|
{ tool: "gitDiff", match: /\bgit diff|show diff|changes\b/i },
|
|
19
35
|
{ tool: "listDirectory", match: /\b(list|ls|folders?|directories?)\b/i }
|
|
20
36
|
];
|
|
37
|
+
const TOOL_PRECEDENCE = [
|
|
38
|
+
"imageGenerate",
|
|
39
|
+
"webSearch",
|
|
40
|
+
"openCalendarEvent",
|
|
41
|
+
"openGitHubIssue",
|
|
42
|
+
"openNotionPage",
|
|
43
|
+
"openLinearIssue",
|
|
44
|
+
"openSlackCompose",
|
|
45
|
+
"openEmail",
|
|
46
|
+
"openMapsDirections",
|
|
47
|
+
"memorySet",
|
|
48
|
+
"memoryGet",
|
|
49
|
+
"runCommand",
|
|
50
|
+
"writeFile",
|
|
51
|
+
"moveFile",
|
|
52
|
+
"readFile",
|
|
53
|
+
"searchInFiles",
|
|
54
|
+
"gitStatus",
|
|
55
|
+
"gitDiff",
|
|
56
|
+
"listDirectory"
|
|
57
|
+
];
|
|
21
58
|
function stripWrappers(value) {
|
|
22
59
|
return value.trim().replace(/^["'`]|["'`]$/g, "");
|
|
23
60
|
}
|
|
@@ -59,7 +96,13 @@ function extractFirstPath(content) {
|
|
|
59
96
|
const quoted = content.match(/["'`]([^"'`]+)["'`]/);
|
|
60
97
|
if (quoted?.[1])
|
|
61
98
|
return quoted[1].trim();
|
|
62
|
-
const
|
|
99
|
+
const windows = content.match(/\b([a-zA-Z]:\\[^\s"'`]+(?:\\[^\s"'`]+)*\.[a-zA-Z0-9_]+)\b/);
|
|
100
|
+
if (windows?.[1])
|
|
101
|
+
return windows[1].trim();
|
|
102
|
+
const unixAbs = content.match(/(?:^|[\s"'`])(\/[^\s"'`]+(?:\/[^\s"'`]+)*\.[a-zA-Z0-9_]+)/);
|
|
103
|
+
if (unixAbs?.[1])
|
|
104
|
+
return unixAbs[1].trim();
|
|
105
|
+
const direct = content.match(/\b([./\\]?[a-zA-Z0-9_./\\-]+\.[a-zA-Z0-9_]+)\b/);
|
|
63
106
|
if (direct?.[1])
|
|
64
107
|
return direct[1].trim();
|
|
65
108
|
return null;
|
|
@@ -121,7 +164,7 @@ function parseRunCommand(content) {
|
|
|
121
164
|
content.match(/\b(?:run|execute)(?:\s+the)?(?:\s+command)?\s+(.+)$/i)?.[1] ??
|
|
122
165
|
content.match(new RegExp(`^\\s*(${KNOWN_COMMANDS.join("|")})\\b(.+)?$`, "i"))?.[0] ??
|
|
123
166
|
"";
|
|
124
|
-
const normalized = candidate.trim().replace(/[.;,\s]+$/g, "");
|
|
167
|
+
const normalized = candidate.trim().replace(/^[>]+/, "").replace(/[.;,\s]+$/g, "");
|
|
125
168
|
if (!normalized)
|
|
126
169
|
return null;
|
|
127
170
|
const tokens = tokenizeCommand(normalized);
|
|
@@ -151,6 +194,25 @@ function parseWriteFileArgs(content) {
|
|
|
151
194
|
content: contentMatch.trim()
|
|
152
195
|
};
|
|
153
196
|
}
|
|
197
|
+
function parseMoveFileArgs(content) {
|
|
198
|
+
const quoted = [...content.matchAll(/["'`]([^"'`]+)["'`]/g)]
|
|
199
|
+
.map((match) => match[1]?.trim())
|
|
200
|
+
.filter((value) => Boolean(value));
|
|
201
|
+
if (quoted.length >= 2) {
|
|
202
|
+
return {
|
|
203
|
+
fromPathRelativeToWorkspace: quoted[0],
|
|
204
|
+
toPathRelativeToWorkspace: quoted[1]
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const moveTo = content.match(/\b(?:move|rename)\s+(.+?)\s+\bto\b\s+(.+)$/i);
|
|
208
|
+
if (moveTo?.[1] && moveTo?.[2]) {
|
|
209
|
+
return {
|
|
210
|
+
fromPathRelativeToWorkspace: stripWrappers(moveTo[1].trim()),
|
|
211
|
+
toPathRelativeToWorkspace: stripWrappers(moveTo[2].trim())
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
154
216
|
function parseSearchArgs(content) {
|
|
155
217
|
const quoted = content.match(/["'`]([^"'`]+)["'`]/)?.[1];
|
|
156
218
|
const phrase = quoted ??
|
|
@@ -187,6 +249,29 @@ function parseMemoryGetArgs(content) {
|
|
|
187
249
|
content.replace(/\?+$/, "");
|
|
188
250
|
return { key: parsed.trim() || "note" };
|
|
189
251
|
}
|
|
252
|
+
function parseImageGenerateArgs(content) {
|
|
253
|
+
const prompt = extractQuoted(content) ??
|
|
254
|
+
content
|
|
255
|
+
.replace(/\b(generate|create|make|design)\b/gi, "")
|
|
256
|
+
.replace(/\b(an?|the)\b/gi, "")
|
|
257
|
+
.replace(/\b(image|logo|icon|picture|art|illustration)\b/gi, "")
|
|
258
|
+
.replace(/\b(for me|please)\b/gi, "")
|
|
259
|
+
.replace(/\s+/g, " ")
|
|
260
|
+
.trim();
|
|
261
|
+
const sizeMatch = content.match(/\b(1024x1024|1024x1536|1536x1024)\b/i)?.[1]?.toLowerCase();
|
|
262
|
+
const quality = /\b(hd|high quality)\b/i.test(content) ? "hd" : "standard";
|
|
263
|
+
const style = /\bnatural\b/i.test(content) ? "natural" : "vivid";
|
|
264
|
+
const filePath = content.match(/\b(?:to|into|save(?:d)?(?:\s+to)?)\s+["'`]([^"'`]+\.(?:png|jpg|jpeg))["'`]/i)?.[1] ??
|
|
265
|
+
content.match(/\b(?:to|into|save(?:d)?(?:\s+to)?)\s+([^\s]+\.(?:png|jpg|jpeg))\b/i)?.[1] ??
|
|
266
|
+
undefined;
|
|
267
|
+
return {
|
|
268
|
+
prompt: prompt || "A clean terminal-themed image concept",
|
|
269
|
+
size: sizeMatch ?? "1024x1024",
|
|
270
|
+
quality,
|
|
271
|
+
style,
|
|
272
|
+
pathRelativeToWorkspace: filePath
|
|
273
|
+
};
|
|
274
|
+
}
|
|
190
275
|
function parseCalendarArgs(content) {
|
|
191
276
|
const quoted = extractQuoted(content);
|
|
192
277
|
const title = quoted ??
|
|
@@ -254,18 +339,125 @@ function parseDirectionsArgs(content) {
|
|
|
254
339
|
const provider = /\bapple maps?\b/i.test(content) ? "apple" : "google";
|
|
255
340
|
return { destination, provider };
|
|
256
341
|
}
|
|
257
|
-
function
|
|
342
|
+
function isDeepLinkTool(tool) {
|
|
343
|
+
return (tool === "openCalendarEvent" ||
|
|
344
|
+
tool === "openGitHubIssue" ||
|
|
345
|
+
tool === "openNotionPage" ||
|
|
346
|
+
tool === "openLinearIssue" ||
|
|
347
|
+
tool === "openSlackCompose" ||
|
|
348
|
+
tool === "openEmail" ||
|
|
349
|
+
tool === "openMapsDirections");
|
|
350
|
+
}
|
|
351
|
+
function precedenceIndex(tool) {
|
|
352
|
+
const idx = TOOL_PRECEDENCE.indexOf(tool);
|
|
353
|
+
return idx === -1 ? TOOL_PRECEDENCE.length : idx;
|
|
354
|
+
}
|
|
355
|
+
function scoreToolMatch(tool, content, baseScore) {
|
|
356
|
+
let score = baseScore;
|
|
357
|
+
if (tool === "runCommand") {
|
|
358
|
+
score += parseRunCommand(content) ? 3 : -0.7;
|
|
359
|
+
if (/\b(git status|git diff|show diff|repo status|what changed)\b/i.test(content)) {
|
|
360
|
+
score -= 2;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (tool === "gitStatus" && /\bgit status\b|\brepo status\b|\bwhat changed\b/i.test(content)) {
|
|
364
|
+
score += 3;
|
|
365
|
+
}
|
|
366
|
+
if (tool === "gitDiff" && /\bgit diff\b|\bshow diff\b|\bchanges\b/i.test(content)) {
|
|
367
|
+
score += 2.5;
|
|
368
|
+
}
|
|
369
|
+
if (tool === "imageGenerate" && /\b(image|logo|icon|picture|photo|art|illustration|graphic)\b/i.test(content)) {
|
|
370
|
+
score += 1.5;
|
|
371
|
+
}
|
|
372
|
+
if (tool === "webSearch" && /\b(latest|today|news|current|online|web)\b/i.test(content)) {
|
|
373
|
+
score += 1.4;
|
|
374
|
+
}
|
|
375
|
+
if ((tool === "readFile" || tool === "writeFile" || tool === "moveFile") && extractFirstPath(content)) {
|
|
376
|
+
score += 1.1;
|
|
377
|
+
}
|
|
378
|
+
if (tool === "listDirectory" && /\b(list|ls|folders?|directories?)\b/i.test(content)) {
|
|
379
|
+
score += 1.2;
|
|
380
|
+
}
|
|
381
|
+
if (tool === "searchInFiles" && /\b(todo|fixme|pattern|match|grep)\b/i.test(content)) {
|
|
382
|
+
score += 0.8;
|
|
383
|
+
}
|
|
384
|
+
if (tool === "openCalendarEvent" && /\b(tomorrow|today|at\s+\d|meeting|event)\b/i.test(content)) {
|
|
385
|
+
score += 1;
|
|
386
|
+
}
|
|
387
|
+
if (tool === "openNotionPage" && /\bnotion\b.*\b(page|note)\b/i.test(content)) {
|
|
388
|
+
score += 2;
|
|
389
|
+
}
|
|
390
|
+
if (tool === "openLinearIssue" && /\b(linear|jira)\b.*\b(issue|ticket)\b/i.test(content)) {
|
|
391
|
+
score += 2;
|
|
392
|
+
}
|
|
393
|
+
if (tool === "openSlackCompose" && /\bslack\b.*\b(message|send|compose)\b/i.test(content)) {
|
|
394
|
+
score += 2;
|
|
395
|
+
}
|
|
396
|
+
if (tool === "openMapsDirections" && /\b(directions?|navigate|maps?)\b/i.test(content)) {
|
|
397
|
+
score += 1.8;
|
|
398
|
+
}
|
|
399
|
+
if (tool === "openEmail" && /\b(draft an email|send an email|mailto|email)\b/i.test(content)) {
|
|
400
|
+
score += 1.4;
|
|
401
|
+
}
|
|
402
|
+
if (tool === "memorySet" && /\bremember(?:\s+that)?\b/i.test(content) && /\b(is|=|as|:)\b/i.test(content)) {
|
|
403
|
+
score += 1.3;
|
|
404
|
+
}
|
|
405
|
+
if (tool === "memoryGet" && /\b(what do you remember|what did i tell you|recall|remember my|what is my)\b/i.test(content)) {
|
|
406
|
+
score += 1.3;
|
|
407
|
+
}
|
|
408
|
+
if (tool === "openGitHubIssue" && /\b(github|label|repo|issue)\b/i.test(content)) {
|
|
409
|
+
score += 1;
|
|
410
|
+
}
|
|
411
|
+
if (tool === "openEmail" && /\b(to\s+[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}|subject)\b/i.test(content)) {
|
|
412
|
+
score += 1;
|
|
413
|
+
}
|
|
414
|
+
return score;
|
|
415
|
+
}
|
|
416
|
+
function inferTool(content, availableTools) {
|
|
417
|
+
const candidates = [];
|
|
258
418
|
for (const hint of TOOL_HINTS) {
|
|
259
|
-
if (hint.
|
|
260
|
-
|
|
419
|
+
if (!availableTools.includes(hint.tool))
|
|
420
|
+
continue;
|
|
421
|
+
if (!hint.match.test(content))
|
|
422
|
+
continue;
|
|
423
|
+
const candidate = {
|
|
424
|
+
tool: hint.tool,
|
|
425
|
+
score: scoreToolMatch(hint.tool, content, 1)
|
|
426
|
+
};
|
|
427
|
+
candidates.push(candidate);
|
|
261
428
|
}
|
|
262
|
-
|
|
429
|
+
candidates.sort((a, b) => {
|
|
430
|
+
if (b.score !== a.score)
|
|
431
|
+
return b.score - a.score;
|
|
432
|
+
return precedenceIndex(a.tool) - precedenceIndex(b.tool);
|
|
433
|
+
});
|
|
434
|
+
if (candidates.length === 0) {
|
|
435
|
+
return { tool: null, confidence: "none", alternatives: [] };
|
|
436
|
+
}
|
|
437
|
+
const top = candidates[0];
|
|
438
|
+
const second = candidates[1];
|
|
439
|
+
const gap = second ? top.score - second.score : top.score;
|
|
440
|
+
let confidence = "medium";
|
|
441
|
+
if (top.score >= 2.4 && gap >= 0.7)
|
|
442
|
+
confidence = "high";
|
|
443
|
+
else if (top.score < 1.2 || gap < 0.2)
|
|
444
|
+
confidence = "low";
|
|
445
|
+
if (second && isDeepLinkTool(top.tool) && isDeepLinkTool(second.tool) && top.tool !== second.tool) {
|
|
446
|
+
confidence = "low";
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
tool: top.tool,
|
|
450
|
+
confidence,
|
|
451
|
+
alternatives: candidates.slice(1, 3).map((candidate) => candidate.tool)
|
|
452
|
+
};
|
|
263
453
|
}
|
|
264
|
-
function
|
|
454
|
+
export function inferArgsForTool(tool, content) {
|
|
265
455
|
if (tool === "searchInFiles")
|
|
266
456
|
return parseSearchArgs(content);
|
|
267
457
|
if (tool === "webSearch")
|
|
268
458
|
return parseWebSearchArgs(content);
|
|
459
|
+
if (tool === "imageGenerate")
|
|
460
|
+
return parseImageGenerateArgs(content);
|
|
269
461
|
if (tool === "runCommand")
|
|
270
462
|
return parseRunCommand(content);
|
|
271
463
|
if (tool === "listDirectory")
|
|
@@ -280,6 +472,8 @@ function inferArgs(tool, content) {
|
|
|
280
472
|
return parseReadFileArgs(content);
|
|
281
473
|
if (tool === "writeFile")
|
|
282
474
|
return parseWriteFileArgs(content);
|
|
475
|
+
if (tool === "moveFile")
|
|
476
|
+
return parseMoveFileArgs(content);
|
|
283
477
|
if (tool === "memorySet")
|
|
284
478
|
return parseMemorySetArgs(content);
|
|
285
479
|
if (tool === "memoryGet")
|
|
@@ -300,24 +494,43 @@ function inferArgs(tool, content) {
|
|
|
300
494
|
return parseDirectionsArgs(content);
|
|
301
495
|
return {};
|
|
302
496
|
}
|
|
303
|
-
export const rulesOrchestrator = (messages) => {
|
|
497
|
+
export const rulesOrchestrator = (messages, tools) => {
|
|
304
498
|
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
305
499
|
if (!lastUser)
|
|
306
500
|
return { kind: "summarize", summary: "No task provided." };
|
|
307
|
-
const
|
|
501
|
+
const inference = inferTool(lastUser.content, tools);
|
|
502
|
+
const tool = inference.tool;
|
|
308
503
|
if (!tool) {
|
|
309
504
|
return {
|
|
310
505
|
kind: "summarize",
|
|
311
|
-
summary: "I can help with workspace files, search, git, memory,
|
|
506
|
+
summary: "I can help with workspace files (read/write/move), search, git, memory, deep-links (calendar/email/issues), and image generation."
|
|
312
507
|
};
|
|
313
508
|
}
|
|
314
|
-
const args =
|
|
509
|
+
const args = inferArgsForTool(tool, lastUser.content);
|
|
315
510
|
if (tool === "runCommand" && !args) {
|
|
316
511
|
return {
|
|
317
512
|
kind: "summarize",
|
|
318
513
|
summary: "I can run approved workspace commands. Please provide an explicit command, for example: run `npm test`."
|
|
319
514
|
};
|
|
320
515
|
}
|
|
516
|
+
if (tool === "moveFile" && !args) {
|
|
517
|
+
return {
|
|
518
|
+
kind: "summarize",
|
|
519
|
+
summary: "Please provide both source and destination paths. Example: move \"docs/old.md\" to \"docs/new.md\"."
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
if (inference.confidence === "low") {
|
|
523
|
+
if (inference.alternatives.length > 0) {
|
|
524
|
+
return {
|
|
525
|
+
kind: "summarize",
|
|
526
|
+
summary: `I can do this, but I need one clarification. Did you want ${tool}, or ${inference.alternatives.join(" / ")}?`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
kind: "summarize",
|
|
531
|
+
summary: `I can handle this with ${tool}, but I need one detail to avoid the wrong action. Please provide exact file path or command.`
|
|
532
|
+
};
|
|
533
|
+
}
|
|
321
534
|
return {
|
|
322
535
|
kind: "ask_permission",
|
|
323
536
|
tool,
|
package/dist/permissions.js
CHANGED
|
@@ -2,6 +2,8 @@ export function getToolPermission(settings, tool) {
|
|
|
2
2
|
return settings.tools[tool] ?? "disabled";
|
|
3
3
|
}
|
|
4
4
|
export function shouldAskEveryTime(settings, tool) {
|
|
5
|
+
if (settings.executionMode === "full")
|
|
6
|
+
return false;
|
|
5
7
|
if (tool === "runCommand")
|
|
6
8
|
return true;
|
|
7
9
|
if (tool === "writeFile")
|
|
@@ -18,7 +20,7 @@ export function isToolEnabled(settings, tool) {
|
|
|
18
20
|
export function toolRiskLevel(tool) {
|
|
19
21
|
if (tool === "runCommand")
|
|
20
22
|
return "high";
|
|
21
|
-
if (tool === "writeFile")
|
|
23
|
+
if (tool === "writeFile" || tool === "moveFile" || tool === "imageGenerate")
|
|
22
24
|
return "medium";
|
|
23
25
|
if (tool === "openCalendarEvent" ||
|
|
24
26
|
tool === "openGitHubIssue" ||
|
package/dist/policy.js
CHANGED
|
@@ -7,7 +7,21 @@ const READ_ONLY_TOOLS = [
|
|
|
7
7
|
"gitDiff",
|
|
8
8
|
"memoryGet"
|
|
9
9
|
];
|
|
10
|
-
const COMMAND_ALLOWLIST = new Set([
|
|
10
|
+
const COMMAND_ALLOWLIST = new Set([
|
|
11
|
+
"git",
|
|
12
|
+
"npm",
|
|
13
|
+
"pnpm",
|
|
14
|
+
"yarn",
|
|
15
|
+
"node",
|
|
16
|
+
"npx",
|
|
17
|
+
"powershell",
|
|
18
|
+
"pwsh",
|
|
19
|
+
"cmd",
|
|
20
|
+
"bash",
|
|
21
|
+
"sh",
|
|
22
|
+
"python",
|
|
23
|
+
"python3"
|
|
24
|
+
]);
|
|
11
25
|
export function isReadOnlyTool(tool) {
|
|
12
26
|
return READ_ONLY_TOOLS.includes(tool);
|
|
13
27
|
}
|
|
@@ -27,7 +41,10 @@ export function shouldExecuteInMode(settings, tool, args) {
|
|
|
27
41
|
return { execute: true, ask: false, reason: "Safe mode auto-executes read-only tools." };
|
|
28
42
|
return { execute: false, ask: true, reason: "Safe mode requires approval for writes and commands." };
|
|
29
43
|
}
|
|
30
|
-
if (
|
|
44
|
+
if (mode === "full") {
|
|
45
|
+
return { execute: true, ask: false, reason: "Full access executes tools directly." };
|
|
46
|
+
}
|
|
47
|
+
if (mode === "autonomous" && tool === "runCommand") {
|
|
31
48
|
const cmd = args?.cmd ?? "";
|
|
32
49
|
if (!isCommandAllowlisted(cmd)) {
|
|
33
50
|
return {
|
|
@@ -37,5 +54,8 @@ export function shouldExecuteInMode(settings, tool, args) {
|
|
|
37
54
|
};
|
|
38
55
|
}
|
|
39
56
|
}
|
|
40
|
-
|
|
57
|
+
if (mode === "autonomous") {
|
|
58
|
+
return { execute: true, ask: false, reason: "Autonomous mode executes approved plan directly." };
|
|
59
|
+
}
|
|
60
|
+
return { execute: false, ask: true, reason: "Unknown execution mode; approval required." };
|
|
41
61
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolName } from "./types.js";
|
|
2
|
+
export type ToolStage = "planning" | "requesting_model" | "rendering" | "receiving_payload" | "saving_file" | "completed" | "failed";
|
|
3
|
+
export type StatusEvent = "planning" | "planning:cloud" | `proposed:${ToolName}` | `approval:${ToolName}` | `executing:${ToolName}` | `blocked:${ToolName}` | `tool:${ToolName}:${string}` | "complete" | "failed" | "cancelled";
|
|
4
|
+
export declare function statusProposed(tool: ToolName): StatusEvent;
|
|
5
|
+
export declare function statusApproval(tool: ToolName): StatusEvent;
|
|
6
|
+
export declare function statusExecuting(tool: ToolName): StatusEvent;
|
|
7
|
+
export declare function statusBlocked(tool: ToolName): StatusEvent;
|
|
8
|
+
export declare function statusToolStage(tool: ToolName, stage: ToolStage | string): StatusEvent;
|
|
9
|
+
export declare function parseStatusEvent(raw: string): StatusEvent;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function statusProposed(tool) {
|
|
2
|
+
return `proposed:${tool}`;
|
|
3
|
+
}
|
|
4
|
+
export function statusApproval(tool) {
|
|
5
|
+
return `approval:${tool}`;
|
|
6
|
+
}
|
|
7
|
+
export function statusExecuting(tool) {
|
|
8
|
+
return `executing:${tool}`;
|
|
9
|
+
}
|
|
10
|
+
export function statusBlocked(tool) {
|
|
11
|
+
return `blocked:${tool}`;
|
|
12
|
+
}
|
|
13
|
+
export function statusToolStage(tool, stage) {
|
|
14
|
+
return `tool:${tool}:${stage}`;
|
|
15
|
+
}
|
|
16
|
+
export function parseStatusEvent(raw) {
|
|
17
|
+
if (raw === "planning" || raw === "planning:cloud" || raw === "complete" || raw === "failed" || raw === "cancelled") {
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
if (/^(proposed|approval|executing|blocked):[a-zA-Z]+$/.test(raw)) {
|
|
21
|
+
return raw;
|
|
22
|
+
}
|
|
23
|
+
if (/^tool:[a-zA-Z]+:[a-zA-Z0-9_:-]+$/.test(raw)) {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
return "failed";
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type TelemetryEvent = {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
event: string;
|
|
4
|
+
workspacePath?: string;
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
export declare function appendTelemetryEvent(event: TelemetryEvent): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { telemetryLogFile } from "./config.js";
|
|
4
|
+
export async function appendTelemetryEvent(event) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.mkdir(path.dirname(telemetryLogFile()), { recursive: true });
|
|
7
|
+
await fs.appendFile(telemetryLogFile(), `${JSON.stringify(event)}\n`, "utf8");
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Telemetry failures should never block primary flows.
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ToolArgsMap, ToolName, ToolResult, WorkspaceSettings } from "./typ
|
|
|
2
2
|
type ToolContext = {
|
|
3
3
|
workspacePath: string;
|
|
4
4
|
settings: WorkspaceSettings;
|
|
5
|
+
onStatus?: (status: string) => void;
|
|
5
6
|
cloud?: {
|
|
6
7
|
webSearch?: (query: string, maxResults?: number) => Promise<{
|
|
7
8
|
query: string;
|
|
@@ -12,6 +13,12 @@ type ToolContext = {
|
|
|
12
13
|
snippet: string;
|
|
13
14
|
}>;
|
|
14
15
|
}>;
|
|
16
|
+
imageGenerate?: (args: ToolArgsMap["imageGenerate"], onProgress?: (stage: "requesting" | "rendering" | "received") => void) => Promise<{
|
|
17
|
+
model: string;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
base64: string;
|
|
20
|
+
revisedPrompt?: string;
|
|
21
|
+
}>;
|
|
15
22
|
};
|
|
16
23
|
};
|
|
17
24
|
export declare function executeTool<T extends ToolName>(context: ToolContext, tool: T, args: ToolArgsMap[T]): Promise<ToolResult>;
|
package/dist/tools.js
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveInWorkspace } from "./sandbox.js";
|
|
|
7
7
|
import { appendAudit } from "./audit.js";
|
|
8
8
|
import { applyPatchProposal, createPatchProposal } from "./patches.js";
|
|
9
9
|
import { memoryFile } from "./config.js";
|
|
10
|
+
import { statusToolStage } from "./status-events.js";
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
11
12
|
function dryRunResult(tool, files = []) {
|
|
12
13
|
return { ok: true, output: `[dry-run] ${tool} skipped execution.`, files };
|
|
@@ -98,6 +99,61 @@ function buildMapsUrl(args) {
|
|
|
98
99
|
}
|
|
99
100
|
return `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(args.destination)}`;
|
|
100
101
|
}
|
|
102
|
+
function sanitizeFileStem(value) {
|
|
103
|
+
return value
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
106
|
+
.replace(/^-+|-+$/g, "")
|
|
107
|
+
.slice(0, 48);
|
|
108
|
+
}
|
|
109
|
+
function imageExtFromMime(mimeType) {
|
|
110
|
+
const lowered = mimeType.toLowerCase();
|
|
111
|
+
if (lowered.includes("jpeg") || lowered.includes("jpg"))
|
|
112
|
+
return "jpg";
|
|
113
|
+
if (lowered.includes("webp"))
|
|
114
|
+
return "webp";
|
|
115
|
+
return "png";
|
|
116
|
+
}
|
|
117
|
+
function imageFailureMessage(kind, detail) {
|
|
118
|
+
const cleanDetail = (detail ?? "").trim();
|
|
119
|
+
const suffix = cleanDetail ? ` Detail: ${cleanDetail}` : "";
|
|
120
|
+
if (kind === "invalid_prompt") {
|
|
121
|
+
return ("Image generation failed [invalid_prompt]. Provide a non-empty prompt after /image or \"generate image\"." +
|
|
122
|
+
suffix);
|
|
123
|
+
}
|
|
124
|
+
if (kind === "config_missing") {
|
|
125
|
+
return ("Image generation failed [config_missing]. Configure Supabase secrets OPENAI_API_KEY and OPENAI_IMAGE_MODEL, then redeploy image-generate." +
|
|
126
|
+
suffix);
|
|
127
|
+
}
|
|
128
|
+
if (kind === "auth_session") {
|
|
129
|
+
return ("Image generation failed [auth_session]. Run \"terry auth login\", then verify \"terry cloud status\" shows a valid session." +
|
|
130
|
+
suffix);
|
|
131
|
+
}
|
|
132
|
+
if (kind === "file_save") {
|
|
133
|
+
return ("Image generation failed [file_save]. Terry generated the image but could not save it. Check file path permissions and disk availability." +
|
|
134
|
+
suffix);
|
|
135
|
+
}
|
|
136
|
+
return ("Image generation failed [upstream]. The image API request failed. Retry once, then inspect Supabase function logs for image-generate." +
|
|
137
|
+
suffix);
|
|
138
|
+
}
|
|
139
|
+
function classifyImageCloudFailure(raw) {
|
|
140
|
+
const lowered = raw.toLowerCase();
|
|
141
|
+
if (lowered.includes("[cloud_misconfigured]") ||
|
|
142
|
+
lowered.includes("cloud is not configured") ||
|
|
143
|
+
lowered.includes("server configuration is incomplete") ||
|
|
144
|
+
lowered.includes("openai_api_key")) {
|
|
145
|
+
return "config_missing";
|
|
146
|
+
}
|
|
147
|
+
if (lowered.includes("[auth_failed]") ||
|
|
148
|
+
lowered.includes("[auth_required]") ||
|
|
149
|
+
lowered.includes("not logged in") ||
|
|
150
|
+
lowered.includes("session expired") ||
|
|
151
|
+
lowered.includes("authentication failed") ||
|
|
152
|
+
lowered.includes("unauthorized")) {
|
|
153
|
+
return "auth_session";
|
|
154
|
+
}
|
|
155
|
+
return "upstream";
|
|
156
|
+
}
|
|
101
157
|
async function openExternalUrl(url) {
|
|
102
158
|
if (process.platform === "win32") {
|
|
103
159
|
const escaped = url.replace(/'/g, "''");
|
|
@@ -168,7 +224,7 @@ export async function executeTool(context, tool, args) {
|
|
|
168
224
|
const full = resolveInWorkspace(context.workspacePath, parsed.pathRelativeToWorkspace);
|
|
169
225
|
const proposal = await createPatchProposal(context.workspacePath, parsed.pathRelativeToWorkspace, parsed.content, {
|
|
170
226
|
persist: !context.settings.dryRun,
|
|
171
|
-
preApproveAll: context.settings.executionMode === "autonomous"
|
|
227
|
+
preApproveAll: context.settings.executionMode === "full" || context.settings.executionMode === "autonomous"
|
|
172
228
|
});
|
|
173
229
|
if (context.settings.dryRun) {
|
|
174
230
|
return {
|
|
@@ -193,6 +249,21 @@ export async function executeTool(context, tool, args) {
|
|
|
193
249
|
patchProposalId: proposal.id
|
|
194
250
|
};
|
|
195
251
|
});
|
|
252
|
+
case "moveFile":
|
|
253
|
+
return withAudit(context, tool, args, [], async () => {
|
|
254
|
+
const parsed = args;
|
|
255
|
+
const fromPath = resolveInWorkspace(context.workspacePath, parsed.fromPathRelativeToWorkspace);
|
|
256
|
+
const toPath = resolveInWorkspace(context.workspacePath, parsed.toPathRelativeToWorkspace);
|
|
257
|
+
if (context.settings.dryRun)
|
|
258
|
+
return dryRunResult(tool, [fromPath, toPath]);
|
|
259
|
+
await fs.mkdir(path.dirname(toPath), { recursive: true });
|
|
260
|
+
await fs.rename(fromPath, toPath);
|
|
261
|
+
return {
|
|
262
|
+
ok: true,
|
|
263
|
+
output: `Moved ${parsed.fromPathRelativeToWorkspace} -> ${parsed.toPathRelativeToWorkspace}`,
|
|
264
|
+
files: [fromPath, toPath]
|
|
265
|
+
};
|
|
266
|
+
});
|
|
196
267
|
case "searchInFiles":
|
|
197
268
|
return withAudit(context, tool, args, [], async () => {
|
|
198
269
|
const parsed = args;
|
|
@@ -265,6 +336,92 @@ export async function executeTool(context, tool, args) {
|
|
|
265
336
|
const { stdout, stderr } = await execFileAsync(parsed.cmd, parsed.args ?? [], { cwd });
|
|
266
337
|
return { ok: true, output: `${stdout}${stderr}`.trim() };
|
|
267
338
|
});
|
|
339
|
+
case "imageGenerate":
|
|
340
|
+
return withAudit(context, tool, args, [], async () => {
|
|
341
|
+
const imageStart = Date.now();
|
|
342
|
+
const parsed = args;
|
|
343
|
+
context.onStatus?.(statusToolStage("imageGenerate", "planning"));
|
|
344
|
+
if (!parsed.prompt?.trim()) {
|
|
345
|
+
context.onStatus?.(statusToolStage("imageGenerate", "failed"));
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
error: imageFailureMessage("invalid_prompt")
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (!context.cloud?.imageGenerate) {
|
|
352
|
+
context.onStatus?.(statusToolStage("imageGenerate", "failed"));
|
|
353
|
+
return {
|
|
354
|
+
ok: false,
|
|
355
|
+
error: imageFailureMessage("config_missing", "imageGenerate requires cloud relay integration.")
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const requestedPath = parsed.pathRelativeToWorkspace?.trim();
|
|
359
|
+
const defaultStem = sanitizeFileStem(parsed.prompt) || "terry-image";
|
|
360
|
+
const safePath = requestedPath || `generated/${defaultStem}-${Date.now()}.png`;
|
|
361
|
+
const fullPath = resolveInWorkspace(context.workspacePath, safePath);
|
|
362
|
+
if (context.settings.dryRun) {
|
|
363
|
+
context.onStatus?.(statusToolStage("imageGenerate", "completed"));
|
|
364
|
+
return {
|
|
365
|
+
ok: true,
|
|
366
|
+
output: [
|
|
367
|
+
"[dry-run] image generation preview",
|
|
368
|
+
`Saved path: ${safePath}`
|
|
369
|
+
].join("\n"),
|
|
370
|
+
files: [fullPath]
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
let generated;
|
|
374
|
+
try {
|
|
375
|
+
generated = await context.cloud.imageGenerate(parsed, (stage) => {
|
|
376
|
+
if (stage === "requesting")
|
|
377
|
+
context.onStatus?.(statusToolStage("imageGenerate", "requesting_model"));
|
|
378
|
+
if (stage === "rendering")
|
|
379
|
+
context.onStatus?.(statusToolStage("imageGenerate", "rendering"));
|
|
380
|
+
if (stage === "received")
|
|
381
|
+
context.onStatus?.(statusToolStage("imageGenerate", "receiving_payload"));
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
context.onStatus?.(statusToolStage("imageGenerate", "failed"));
|
|
386
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
387
|
+
return {
|
|
388
|
+
ok: false,
|
|
389
|
+
error: imageFailureMessage(classifyImageCloudFailure(detail), detail)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const ext = path.extname(fullPath) ? "" : `.${imageExtFromMime(generated.mimeType)}`;
|
|
394
|
+
const writePath = ext ? `${fullPath}${ext}` : fullPath;
|
|
395
|
+
context.onStatus?.(statusToolStage("imageGenerate", "saving_file"));
|
|
396
|
+
await fs.mkdir(path.dirname(writePath), { recursive: true });
|
|
397
|
+
await fs.writeFile(writePath, Buffer.from(generated.base64, "base64"));
|
|
398
|
+
context.onStatus?.(statusToolStage("imageGenerate", "completed"));
|
|
399
|
+
const relPath = path.relative(context.workspacePath, writePath);
|
|
400
|
+
const elapsedMs = Date.now() - imageStart;
|
|
401
|
+
const lines = [
|
|
402
|
+
`Saved path: ${relPath}`,
|
|
403
|
+
`Model: ${generated.model}`,
|
|
404
|
+
`Mime type: ${generated.mimeType}`,
|
|
405
|
+
`Total duration: ${(elapsedMs / 1000).toFixed(2)}s`
|
|
406
|
+
];
|
|
407
|
+
if (generated.revisedPrompt?.trim()) {
|
|
408
|
+
lines.push(`Revised prompt: ${generated.revisedPrompt.trim()}`);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
output: lines.join("\n"),
|
|
413
|
+
files: [writePath]
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
context.onStatus?.(statusToolStage("imageGenerate", "failed"));
|
|
418
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
419
|
+
return {
|
|
420
|
+
ok: false,
|
|
421
|
+
error: imageFailureMessage("file_save", message)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
});
|
|
268
425
|
case "memorySet":
|
|
269
426
|
return withAudit(context, tool, args, [memoryFile()], async () => {
|
|
270
427
|
const parsed = args;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type PermissionLevel = "disabled" | "enabled" | "ask";
|
|
2
2
|
export type AgentMode = "general" | "focus" | "builder";
|
|
3
|
-
export type ExecutionMode = "plan" | "safe" | "autonomous";
|
|
4
|
-
export type ToolName = "listDirectory" | "readFile" | "writeFile" | "searchInFiles" | "webSearch" | "gitStatus" | "gitDiff" | "runCommand" | "memorySet" | "memoryGet" | "openCalendarEvent" | "openGitHubIssue" | "openNotionPage" | "openLinearIssue" | "openSlackCompose" | "openEmail" | "openMapsDirections";
|
|
3
|
+
export type ExecutionMode = "plan" | "safe" | "full" | "autonomous";
|
|
4
|
+
export type ToolName = "listDirectory" | "readFile" | "writeFile" | "moveFile" | "searchInFiles" | "webSearch" | "gitStatus" | "gitDiff" | "runCommand" | "imageGenerate" | "memorySet" | "memoryGet" | "openCalendarEvent" | "openGitHubIssue" | "openNotionPage" | "openLinearIssue" | "openSlackCompose" | "openEmail" | "openMapsDirections";
|
|
5
5
|
export type ToolArgsMap = {
|
|
6
6
|
listDirectory: {
|
|
7
7
|
pathRelativeToWorkspace?: string;
|
|
@@ -13,6 +13,10 @@ export type ToolArgsMap = {
|
|
|
13
13
|
pathRelativeToWorkspace: string;
|
|
14
14
|
content: string;
|
|
15
15
|
};
|
|
16
|
+
moveFile: {
|
|
17
|
+
fromPathRelativeToWorkspace: string;
|
|
18
|
+
toPathRelativeToWorkspace: string;
|
|
19
|
+
};
|
|
16
20
|
searchInFiles: {
|
|
17
21
|
query: string;
|
|
18
22
|
};
|
|
@@ -29,6 +33,13 @@ export type ToolArgsMap = {
|
|
|
29
33
|
args?: string[];
|
|
30
34
|
cwdRelativeToWorkspace?: string;
|
|
31
35
|
};
|
|
36
|
+
imageGenerate: {
|
|
37
|
+
prompt: string;
|
|
38
|
+
size?: "1024x1024" | "1024x1536" | "1536x1024";
|
|
39
|
+
quality?: "standard" | "hd";
|
|
40
|
+
style?: "vivid" | "natural";
|
|
41
|
+
pathRelativeToWorkspace?: string;
|
|
42
|
+
};
|
|
32
43
|
memorySet: {
|
|
33
44
|
key: string;
|
|
34
45
|
value: string;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terry-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Core policy, tooling, and orchestration modules for Terry Agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ELEVAREL",
|
|
7
7
|
"homepage": "https://github.com/ELEVAREL/terry-agent",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/ELEVAREL/terry-agent.git",
|
|
10
|
+
"url": "git+https://github.com/ELEVAREL/terry-agent.git",
|
|
11
11
|
"directory": "packages/terry-core"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|