@steerable/agent-shell 0.6.50 → 0.6.52
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/brand.d.ts +5 -0
- package/dist/goal-store.d.ts +43 -0
- package/dist/goal-store.js +155 -0
- package/dist/host/runtime.js +13 -3
- package/dist/host-tools-runtime.d.ts +6 -0
- package/dist/host-tools-runtime.js +36 -0
- package/dist/host-tools.d.ts +1 -1
- package/dist/host-tools.js +6 -0
- package/dist/local-backend/router.js +22 -17
- package/dist/local-backend/subagent-profiles.js +4 -0
- package/dist/local-backend/task-service.d.ts +6 -0
- package/dist/local-backend/task-service.js +47 -2
- package/dist/local-executor.d.ts +14 -2
- package/dist/local-executor.js +53 -18
- package/dist/product-config.d.ts +10 -0
- package/dist/project-home.d.ts +1 -1
- package/dist/project-home.js +1 -1
- package/dist/project-registry.d.ts +4 -4
- package/dist/project-registry.js +2 -2
- package/dist/sidecar/boot.d.ts +5 -2
- package/dist/sidecar/boot.js +1 -0
- package/dist/sidecar/reverse-approval.d.ts +2 -0
- package/dist/sidecar/reverse-approval.js +2 -0
- package/dist/sidecar/reverse-tools.d.ts +6 -2
- package/dist/sidecar/reverse-tools.js +9 -1
- package/dist/sidecar/types.d.ts +10 -1
- package/dist/storage/write-lease-error.js +4 -1
- package/dist/tool-presentation.d.ts +23 -0
- package/dist/tool-presentation.js +266 -0
- package/dist/tool-router.d.ts +26 -8
- package/dist/tool-router.js +254 -18
- package/dist/workspace-search.d.ts +35 -0
- package/dist/workspace-search.js +186 -0
- package/package.json +4 -4
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主侧 grep / glob。
|
|
3
|
+
*
|
|
4
|
+
* 调用方先把起点收进项目围栏。这里只在该目录内遍历,跳过符号链接,
|
|
5
|
+
* 并丢掉依赖目录和超大文件。命中数有上限。
|
|
6
|
+
*/
|
|
7
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
const SKIP_DIRS = new Set([
|
|
10
|
+
'node_modules',
|
|
11
|
+
'.git',
|
|
12
|
+
'dist',
|
|
13
|
+
'build',
|
|
14
|
+
'target',
|
|
15
|
+
'.steerable',
|
|
16
|
+
'__pycache__',
|
|
17
|
+
'.venv',
|
|
18
|
+
'venv',
|
|
19
|
+
]);
|
|
20
|
+
const MAX_FILES = 4_000;
|
|
21
|
+
const MAX_MATCHES = 80;
|
|
22
|
+
const MAX_FILE_BYTES = 1_000_000;
|
|
23
|
+
const MAX_DEPTH = 20;
|
|
24
|
+
function globToRegExp(pattern) {
|
|
25
|
+
const normalized = pattern.replaceAll('\\', '/');
|
|
26
|
+
const withPrefix = normalized.includes('/') ? normalized : `**/${normalized}`;
|
|
27
|
+
let out = '';
|
|
28
|
+
for (let i = 0; i < withPrefix.length; i += 1) {
|
|
29
|
+
const char = withPrefix[i];
|
|
30
|
+
if (char === '*' && withPrefix[i + 1] === '*') {
|
|
31
|
+
if (withPrefix[i + 2] === '/') {
|
|
32
|
+
out += '(?:.*/)?';
|
|
33
|
+
i += 2;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
out += '.*';
|
|
37
|
+
i += 1;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === '*') {
|
|
42
|
+
out += '[^/]*';
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (char === '?') {
|
|
46
|
+
out += '[^/]';
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if ('\\^$+?.()|{}[]'.includes(char))
|
|
50
|
+
out += `\\${char}`;
|
|
51
|
+
else
|
|
52
|
+
out += char;
|
|
53
|
+
}
|
|
54
|
+
return new RegExp(`^${out}$`);
|
|
55
|
+
}
|
|
56
|
+
async function walk(root, visit) {
|
|
57
|
+
let files = 0;
|
|
58
|
+
let truncated = false;
|
|
59
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
60
|
+
while (queue.length > 0) {
|
|
61
|
+
const next = queue.pop();
|
|
62
|
+
if (!next)
|
|
63
|
+
break;
|
|
64
|
+
let entries;
|
|
65
|
+
try {
|
|
66
|
+
entries = await readdir(next.dir, { withFileTypes: true });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.isSymbolicLink())
|
|
73
|
+
continue;
|
|
74
|
+
const absolute = path.join(next.dir, entry.name);
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
if (SKIP_DIRS.has(entry.name) || next.depth >= MAX_DEPTH)
|
|
77
|
+
continue;
|
|
78
|
+
queue.push({ dir: absolute, depth: next.depth + 1 });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!entry.isFile())
|
|
82
|
+
continue;
|
|
83
|
+
files += 1;
|
|
84
|
+
if (files > MAX_FILES) {
|
|
85
|
+
truncated = true;
|
|
86
|
+
return { truncated, files };
|
|
87
|
+
}
|
|
88
|
+
const relative = path.relative(root, absolute).split(path.sep).join('/');
|
|
89
|
+
const stop = await visit(absolute, relative);
|
|
90
|
+
if (stop === true) {
|
|
91
|
+
truncated = true;
|
|
92
|
+
return { truncated, files };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { truncated, files };
|
|
97
|
+
}
|
|
98
|
+
export async function searchGrep(input) {
|
|
99
|
+
const pattern = input.pattern.trim();
|
|
100
|
+
if (!pattern)
|
|
101
|
+
return { success: false, error: 'pattern 不能为空', needsFollowup: true };
|
|
102
|
+
let matcher;
|
|
103
|
+
try {
|
|
104
|
+
matcher = new RegExp(pattern);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
return { success: false, error: `无效的正则:${message}`, needsFollowup: true };
|
|
109
|
+
}
|
|
110
|
+
let fileFilter = null;
|
|
111
|
+
if (input.glob && input.glob.trim()) {
|
|
112
|
+
try {
|
|
113
|
+
fileFilter = globToRegExp(input.glob.trim());
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
return { success: false, error: `无效的 glob:${message}`, needsFollowup: true };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const matches = [];
|
|
121
|
+
const walked = await walk(input.root, async (absolute, relative) => {
|
|
122
|
+
if (fileFilter && !fileFilter.test(relative))
|
|
123
|
+
return;
|
|
124
|
+
let info;
|
|
125
|
+
try {
|
|
126
|
+
info = await stat(absolute);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!info.isFile() || info.size > MAX_FILE_BYTES)
|
|
132
|
+
return;
|
|
133
|
+
let text;
|
|
134
|
+
try {
|
|
135
|
+
text = await readFile(absolute, 'utf8');
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (text.includes('\0'))
|
|
141
|
+
return;
|
|
142
|
+
const lines = text.split(/\r?\n/);
|
|
143
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
144
|
+
if (!matcher.test(lines[i]))
|
|
145
|
+
continue;
|
|
146
|
+
matches.push({ path: relative, line: i + 1, text: lines[i].slice(0, 400) });
|
|
147
|
+
if (matches.length >= MAX_MATCHES)
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
matches,
|
|
155
|
+
truncated: walked.truncated || matches.length >= MAX_MATCHES,
|
|
156
|
+
filesScanned: walked.files,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export async function searchGlob(input) {
|
|
160
|
+
const pattern = input.pattern.trim();
|
|
161
|
+
if (!pattern)
|
|
162
|
+
return { success: false, error: 'pattern 不能为空', needsFollowup: true };
|
|
163
|
+
let matcher;
|
|
164
|
+
try {
|
|
165
|
+
matcher = globToRegExp(pattern);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
169
|
+
return { success: false, error: `无效的 glob:${message}`, needsFollowup: true };
|
|
170
|
+
}
|
|
171
|
+
const files = [];
|
|
172
|
+
const walked = await walk(input.root, async (_absolute, relative) => {
|
|
173
|
+
if (!matcher.test(relative))
|
|
174
|
+
return;
|
|
175
|
+
files.push(relative);
|
|
176
|
+
if (files.length >= MAX_MATCHES)
|
|
177
|
+
return true;
|
|
178
|
+
return undefined;
|
|
179
|
+
});
|
|
180
|
+
files.sort();
|
|
181
|
+
return {
|
|
182
|
+
success: true,
|
|
183
|
+
files,
|
|
184
|
+
truncated: walked.truncated || files.length >= MAX_MATCHES,
|
|
185
|
+
};
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.52",
|
|
4
4
|
"description": "Steerable framework — product-neutral Node HostRuntime and headless HTTP server shared by Tauri desktop and browser deployments; scenario packs extend it through @steerable/pack-sdk.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"he": "^1.2.0",
|
|
56
56
|
"node-pty": "^1.1.0",
|
|
57
57
|
"semver": "^7.8.0",
|
|
58
|
-
"@steerable/agent-harness": "0.6.
|
|
59
|
-
"@steerable/
|
|
60
|
-
"@steerable/
|
|
58
|
+
"@steerable/agent-harness": "0.6.52",
|
|
59
|
+
"@steerable/agent-protocol": "0.6.52",
|
|
60
|
+
"@steerable/pack-sdk": "0.6.52"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/better-sqlite3": "^7.6.13",
|