langflower 0.1.2 → 0.2.0
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/README.md +573 -114
- package/dist/{chunk-WV2YWHOC.js → chunk-26UB55E5.js} +44 -4
- package/dist/{chunk-AO33IM2J.js → chunk-2LE3E2QP.js} +24 -23
- package/dist/{chunk-NIRPHJLO.js → chunk-3Z7Q6SQ5.js} +501 -242
- package/dist/{chunk-CVTPGIZE.js → chunk-RZUZX6PW.js} +2 -4
- package/dist/index.js +41152 -41395
- package/docs/public/README.md +1 -0
- package/docs/public/extending.md +21 -3
- package/docs/public/getting-started.md +9 -1
- package/docs/public/how-it-works.md +2 -2
- package/docs/public/launcher.md +111 -0
- package/package.json +1 -1
- package/ui-dist/apple-touch-icon.png +0 -0
- package/ui-dist/{chunk-EZTB7Q33.js → chunk-HEF6F5HN.js} +3 -3
- package/ui-dist/{chunk-BKIP5WRK.js → chunk-IASSR2ON.js} +1 -1
- package/ui-dist/favicon-32x32.png +0 -0
- package/ui-dist/favicon.ico +0 -0
- package/ui-dist/index.html +5 -2
- package/ui-dist/main-NBS6BFBP.js +323 -0
- package/ui-dist/styles-M2URERWI.css +1 -0
- package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +1 -1
- package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +9 -21
- package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +9 -3
- package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.d.ts +0 -1
- package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.js +0 -9
- package/vendor/node-sdk/dist/node-factory/define-node/define-node.d.ts +5 -1
- package/vendor/node-sdk/dist/node-factory/define-node/define-node.js +6 -3
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/capabilities.d.ts +128 -0
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/capabilities.js +25 -0
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.d.ts +29 -17
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.js +14 -6
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.d.ts +19 -5
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.js +4 -3
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/port-meta.d.ts +6 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.d.ts +40 -22
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.js +2 -0
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/ui-schema-inference.d.ts +1 -1
- package/vendor/node-sdk/dist/node-factory/define-tool-registrations/define-tool-registrations.d.ts +13 -25
- package/vendor/node-sdk/dist/node-factory/define-tool-registrations/define-tool-registrations.js +9 -8
- package/vendor/node-sdk/dist/testing/create-node-harness.d.ts +9 -2
- package/vendor/node-sdk/dist/testing/create-node-harness.js +6 -2
- package/vendor/node-sdk/package.json +5 -0
- package/vendor/runtime/dist/bypass-ports.d.ts +0 -6
- package/vendor/runtime/dist/bypass-ports.js +0 -7
- package/vendor/runtime/dist/runtime-editor.d.ts +1 -1
- package/vendor/runtime/dist/runtime-editor.js +1 -1
- package/vendor/runtime/dist/runtime-helpers.d.ts +0 -1
- package/vendor/runtime/dist/runtime-helpers.js +0 -33
- package/vendor/runtime/dist/runtime-runner.d.ts +4 -0
- package/vendor/runtime/dist/runtime-runner.js +60 -36
- package/vendor/runtime/dist/runtime.d.ts +0 -1
- package/vendor/runtime/dist/runtime.js +0 -1
- package/vendor/runtime/dist/types.d.ts +27 -17
- package/vendor/server/skeleton/instructions.md +6 -0
- package/vendor/server/skeleton/nodes/my-nodes/README.md +19 -1
- package/vendor/server/skeleton/schemas/langflower-config.schema.json +1 -1
- package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +44 -10
- package/vendor/server/skeleton/skills/langflower-helper/architecture.md +4 -1
- package/vendor/server/skeleton/skills/langflower-helper/layout.md +6 -4
- package/vendor/server/skeleton/skills/langflower-node-writer/SKILL.md +47 -3
- package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +29 -17
- package/vendor/server/skeleton/workflows/advanced-coder.json +24 -2
- package/vendor/server/skeleton/workflows/kb-create.json +24 -16
- package/vendor/server/skeleton/workflows/kb-manual-search.json +1 -1
- package/vendor/server/skeleton/workflows/kb-navigate.json +24 -2
- package/vendor/server/skeleton/workflows/kb-rag.json +1 -0
- package/vendor/server/skeleton/workflows/simple-coder.json +3 -0
- package/vendor/server/skeleton/workflows/starter.json +2 -0
- package/ui-dist/main-JJURZFKA.js +0 -318
- package/ui-dist/styles-VA3HMZK7.css +0 -1
- package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.d.ts +0 -5
- package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.js +0 -5
- package/vendor/node-sdk/dist/node-factory/define-llm-node/llm.d.ts +0 -3
- package/vendor/node-sdk/dist/node-factory/define-llm-node/llm.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-mcp/define-mcp.d.ts +0 -50
- package/vendor/node-sdk/dist/node-factory/define-mcp/define-mcp.js +0 -60
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.d.ts +0 -15
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-server-config.d.ts +0 -17
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-server-config.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-transport.d.ts +0 -20
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-transport.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp.d.ts +0 -3
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-node/test/samples/gate-node.d.ts +0 -20
- package/vendor/node-sdk/dist/node-factory/define-node/test/samples/gate-node.js +0 -22
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/chat-completion-stream.d.ts +0 -50
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/chat-completion-stream.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/crawl-context.d.ts +0 -22
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/crawl-context.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/create-embedding.d.ts +0 -6
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/create-embedding.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-tool-registrations.d.ts +0 -52
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-tool-registrations.js +0 -52
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/kb-context.d.ts +0 -111
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/kb-context.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/memory-context.d.ts +0 -12
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/memory-context.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-files-context.d.ts +0 -10
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-files-context.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-harness.d.ts +0 -56
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-harness.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/agent-node.d.ts +0 -29
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/agent-node.js +0 -60
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/combine-node.d.ts +0 -29
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/combine-node.js +0 -26
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/constant-node.d.ts +0 -29
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/constant-node.js +0 -21
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/delay-node.d.ts +0 -29
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/delay-node.js +0 -27
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/finish-node.d.ts +0 -17
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/finish-node.js +0 -21
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/hitl-node.d.ts +0 -21
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/hitl-node.js +0 -43
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/join-node.d.ts +0 -29
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/join-node.js +0 -31
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/preview-node.d.ts +0 -17
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/preview-node.js +0 -19
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/router-node.d.ts +0 -18
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/router-node.js +0 -12
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/tool-registration.d.ts +0 -36
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/tool-registration.js +0 -2
- package/vendor/node-sdk/dist/node-factory/define-tool-registrations/tool-registration.d.ts +0 -46
- package/vendor/node-sdk/dist/node-factory/define-tool-registrations/tool-registration.js +0 -2
|
@@ -2,6 +2,202 @@ import { createRequire as __lfCreateRequire } from 'node:module';
|
|
|
2
2
|
const require = __lfCreateRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
// packages/tools/dist/permission.js
|
|
6
|
+
var DEFAULT_PERMISSION_CONFIG = {
|
|
7
|
+
read: { "*": "allow" },
|
|
8
|
+
glob: { "*": "allow" },
|
|
9
|
+
grep: { "*": "allow" },
|
|
10
|
+
edit: { "*": "allow" },
|
|
11
|
+
write: { "*": "allow" },
|
|
12
|
+
create: { "*": "allow" },
|
|
13
|
+
delete: { "*": "allow" },
|
|
14
|
+
move: { "*": "allow" },
|
|
15
|
+
sleep: { "*": "allow" },
|
|
16
|
+
bash: { "*": "allow" },
|
|
17
|
+
ask_user: { "*": "allow" }
|
|
18
|
+
};
|
|
19
|
+
var DECISION_RANK = {
|
|
20
|
+
deny: 3,
|
|
21
|
+
ask: 2,
|
|
22
|
+
allow: 1
|
|
23
|
+
};
|
|
24
|
+
var isDecision = (value) => value === "allow" || value === "ask" || value === "deny";
|
|
25
|
+
var escapeRegExp = (text) => text.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
26
|
+
var matchPermissionPattern = (pattern, detail) => {
|
|
27
|
+
const normalizedPattern = pattern.trim();
|
|
28
|
+
const normalizedDetail = detail;
|
|
29
|
+
if (normalizedPattern.length === 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (normalizedPattern === "*") {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const pathStyle = normalizedPattern.includes("/") || normalizedPattern.includes("**");
|
|
36
|
+
if (pathStyle) {
|
|
37
|
+
const posixDetail = normalizedDetail.replace(/\\/g, "/");
|
|
38
|
+
let regexSource2 = "";
|
|
39
|
+
for (let i = 0; i < normalizedPattern.length; i += 1) {
|
|
40
|
+
if (normalizedPattern.startsWith("**/", i)) {
|
|
41
|
+
regexSource2 += "(?:.*/)?";
|
|
42
|
+
i += 2;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (normalizedPattern.startsWith("**", i)) {
|
|
46
|
+
regexSource2 += ".*";
|
|
47
|
+
i += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const ch = normalizedPattern[i] ?? "";
|
|
51
|
+
if (ch === "*") {
|
|
52
|
+
regexSource2 += "[^/]*";
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
regexSource2 += escapeRegExp(ch);
|
|
56
|
+
}
|
|
57
|
+
return new RegExp(`^${regexSource2}$`).test(posixDetail);
|
|
58
|
+
}
|
|
59
|
+
let regexSource = "";
|
|
60
|
+
for (const ch of normalizedPattern) {
|
|
61
|
+
if (ch === "*") {
|
|
62
|
+
regexSource += "[\\s\\S]*";
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
regexSource += escapeRegExp(ch);
|
|
66
|
+
}
|
|
67
|
+
return new RegExp(`^${regexSource}$`).test(normalizedDetail);
|
|
68
|
+
};
|
|
69
|
+
var rulesForTool = (config, toolId) => {
|
|
70
|
+
const raw = config[toolId] ?? DEFAULT_PERMISSION_CONFIG[toolId];
|
|
71
|
+
if (raw === void 0) {
|
|
72
|
+
return { "*": "deny" };
|
|
73
|
+
}
|
|
74
|
+
if (isDecision(raw)) {
|
|
75
|
+
return { "*": raw };
|
|
76
|
+
}
|
|
77
|
+
const entries = Object.entries(raw).filter((entry) => isDecision(entry[1]));
|
|
78
|
+
if (entries.length === 0) {
|
|
79
|
+
return { "*": "deny" };
|
|
80
|
+
}
|
|
81
|
+
return Object.fromEntries(entries);
|
|
82
|
+
};
|
|
83
|
+
var resolvePermission = (config, toolId, detail) => {
|
|
84
|
+
const merged = {
|
|
85
|
+
...DEFAULT_PERMISSION_CONFIG,
|
|
86
|
+
...config ?? {}
|
|
87
|
+
};
|
|
88
|
+
const rules = rulesForTool(merged, toolId);
|
|
89
|
+
const matches = Object.entries(rules).filter(([pattern]) => matchPermissionPattern(pattern, detail));
|
|
90
|
+
if (matches.length === 0) {
|
|
91
|
+
return "deny";
|
|
92
|
+
}
|
|
93
|
+
matches.sort((a, b) => {
|
|
94
|
+
const lengthDelta = b[0].length - a[0].length;
|
|
95
|
+
if (lengthDelta !== 0) {
|
|
96
|
+
return lengthDelta;
|
|
97
|
+
}
|
|
98
|
+
return DECISION_RANK[b[1]] - DECISION_RANK[a[1]];
|
|
99
|
+
});
|
|
100
|
+
return matches[0]?.[1] ?? "deny";
|
|
101
|
+
};
|
|
102
|
+
var posixArg = (value) => typeof value === "string" && value.length > 0 ? value.replace(/\\/g, "/") : void 0;
|
|
103
|
+
var singlePermissionDetail = (toolId, args) => {
|
|
104
|
+
if (toolId === "bash") {
|
|
105
|
+
return typeof args.command === "string" ? args.command : "";
|
|
106
|
+
}
|
|
107
|
+
if (toolId === "sleep") {
|
|
108
|
+
return typeof args.seconds === "number" && Number.isFinite(args.seconds) ? `${String(Math.trunc(args.seconds))}s` : "*";
|
|
109
|
+
}
|
|
110
|
+
if (toolId === "ask_user") {
|
|
111
|
+
if (typeof args.question === "string" && args.question.trim().length > 0) {
|
|
112
|
+
return args.question;
|
|
113
|
+
}
|
|
114
|
+
const questions = args.questions;
|
|
115
|
+
if (!Array.isArray(questions) || questions.length === 0) {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
const first = questions[0];
|
|
119
|
+
if (first !== null && typeof first === "object" && !Array.isArray(first) && "prompt" in first && typeof first.prompt === "string") {
|
|
120
|
+
return first.prompt;
|
|
121
|
+
}
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
return posixArg(args.path) ?? posixArg(args.file) ?? posixArg(args.url) ?? posixArg(args.key) ?? posixArg(args.collectionId) ?? "*";
|
|
125
|
+
};
|
|
126
|
+
var permissionDetailsForCall = (toolId, args) => {
|
|
127
|
+
if (toolId === "move") {
|
|
128
|
+
const details = [posixArg(args.from), posixArg(args.to)].filter((value) => value !== void 0);
|
|
129
|
+
return details.length > 0 ? details : ["*"];
|
|
130
|
+
}
|
|
131
|
+
return [singlePermissionDetail(toolId, args)];
|
|
132
|
+
};
|
|
133
|
+
var permissionAskSummary = (toolId, detail) => {
|
|
134
|
+
if (toolId === "bash") {
|
|
135
|
+
return `Allow bash: ${detail.length > 0 ? detail : "(empty command)"}?`;
|
|
136
|
+
}
|
|
137
|
+
return `Allow ${toolId}: ${detail.length > 0 ? detail : "(no path)"}?`;
|
|
138
|
+
};
|
|
139
|
+
var grantKeyForCall = (toolId, detail) => `${toolId}\0${detail}`;
|
|
140
|
+
var mergePermissionConfigs = (...layers) => layers.reduce((acc, layer) => layer === void 0 ? acc : { ...acc, ...layer }, {});
|
|
141
|
+
var stricterPermission = (left, right) => DECISION_RANK[left] >= DECISION_RANK[right] ? left : right;
|
|
142
|
+
var toolFloorDecision = (config, toolId) => {
|
|
143
|
+
const merged = {
|
|
144
|
+
...DEFAULT_PERMISSION_CONFIG,
|
|
145
|
+
...config ?? {}
|
|
146
|
+
};
|
|
147
|
+
if (merged[toolId] === void 0 && DEFAULT_PERMISSION_CONFIG[toolId] === void 0) {
|
|
148
|
+
return "allow";
|
|
149
|
+
}
|
|
150
|
+
const rules = rulesForTool(merged, toolId);
|
|
151
|
+
const star = rules["*"];
|
|
152
|
+
if (star !== void 0 && Object.keys(rules).length === 1) {
|
|
153
|
+
return star;
|
|
154
|
+
}
|
|
155
|
+
const decisions = Object.values(rules);
|
|
156
|
+
if (decisions.some((decision) => decision === "allow")) {
|
|
157
|
+
return "allow";
|
|
158
|
+
}
|
|
159
|
+
if (decisions.some((decision) => decision === "ask")) {
|
|
160
|
+
return "ask";
|
|
161
|
+
}
|
|
162
|
+
return "deny";
|
|
163
|
+
};
|
|
164
|
+
var clampToolPermission = (floor, node) => stricterPermission(floor, node);
|
|
165
|
+
var isToolAlwaysDenied = (config, toolId) => {
|
|
166
|
+
const merged = {
|
|
167
|
+
...DEFAULT_PERMISSION_CONFIG,
|
|
168
|
+
...config ?? {}
|
|
169
|
+
};
|
|
170
|
+
if (merged[toolId] === void 0 && DEFAULT_PERMISSION_CONFIG[toolId] === void 0) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const rules = rulesForTool(merged, toolId);
|
|
174
|
+
const decisions = Object.values(rules);
|
|
175
|
+
return decisions.length > 0 && decisions.every((decision) => decision === "deny");
|
|
176
|
+
};
|
|
177
|
+
var mergeProjectAndNodePermissions = (projectPermission, nodeToolPermissions) => {
|
|
178
|
+
const project = {
|
|
179
|
+
...DEFAULT_PERMISSION_CONFIG,
|
|
180
|
+
...projectPermission ?? {}
|
|
181
|
+
};
|
|
182
|
+
if (nodeToolPermissions === void 0) {
|
|
183
|
+
return projectPermission ?? {};
|
|
184
|
+
}
|
|
185
|
+
const nodeEntries = [];
|
|
186
|
+
for (const [toolId, decision] of Object.entries(nodeToolPermissions)) {
|
|
187
|
+
if (!isDecision(decision)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const floor = toolFloorDecision(project, toolId);
|
|
191
|
+
nodeEntries.push([toolId, clampToolPermission(floor, decision)]);
|
|
192
|
+
}
|
|
193
|
+
return mergePermissionConfigs(projectPermission, Object.fromEntries(nodeEntries));
|
|
194
|
+
};
|
|
195
|
+
var PERMISSION_DENY_HINT = "Tighten or loosen via Inspector tool permissions (within project floor) or edit permission.<tool> in .langflower/langflower.jsonc.";
|
|
196
|
+
var formatPermissionDeniedText = (toolId, detail) => {
|
|
197
|
+
const target = detail.length > 0 ? `${toolId} (${detail})` : toolId;
|
|
198
|
+
return `Permission denied for ${target}. Effective policy is deny (project floor and/or node toolPermissions). ${PERMISSION_DENY_HINT}`;
|
|
199
|
+
};
|
|
200
|
+
|
|
5
201
|
// packages/tools/dist/builtins/args.js
|
|
6
202
|
var asString = (args, key) => {
|
|
7
203
|
const value = args[key];
|
|
@@ -16,6 +212,69 @@ var asBoolean = (args, key, defaultValue) => {
|
|
|
16
212
|
return typeof value === "boolean" ? value : defaultValue;
|
|
17
213
|
};
|
|
18
214
|
|
|
215
|
+
// packages/tools/dist/builtins/ask_user/parse-ask-user-args.js
|
|
216
|
+
var ASK_USER_MAX_QUESTIONS = 8;
|
|
217
|
+
var ASK_USER_MAX_OPTIONS = 12;
|
|
218
|
+
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
219
|
+
var parseOptions = (raw) => {
|
|
220
|
+
if (raw === void 0) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
if (!Array.isArray(raw)) {
|
|
224
|
+
throw new Error("ask_user \xABoptions\xBB must be an array of strings.");
|
|
225
|
+
}
|
|
226
|
+
if (raw.length > ASK_USER_MAX_OPTIONS) {
|
|
227
|
+
throw new Error(`ask_user accepts at most ${ASK_USER_MAX_OPTIONS} options per question.`);
|
|
228
|
+
}
|
|
229
|
+
const options = [];
|
|
230
|
+
for (const [optionIndex, entry] of raw.entries()) {
|
|
231
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
232
|
+
throw new Error("ask_user option labels must be non-empty strings.");
|
|
233
|
+
}
|
|
234
|
+
options.push({
|
|
235
|
+
id: `o${optionIndex + 1}`,
|
|
236
|
+
label: entry.trim()
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return options;
|
|
240
|
+
};
|
|
241
|
+
var parseQuestions = (raw) => {
|
|
242
|
+
if (raw === void 0) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
if (!Array.isArray(raw)) {
|
|
246
|
+
throw new Error("ask_user \xABquestions\xBB must be an array.");
|
|
247
|
+
}
|
|
248
|
+
if (raw.length > ASK_USER_MAX_QUESTIONS) {
|
|
249
|
+
throw new Error(`ask_user accepts at most ${ASK_USER_MAX_QUESTIONS} questions.`);
|
|
250
|
+
}
|
|
251
|
+
const questions = [];
|
|
252
|
+
for (const [index, entry] of raw.entries()) {
|
|
253
|
+
if (!isRecord(entry)) {
|
|
254
|
+
throw new Error("ask_user each question must be an object with \xABprompt\xBB.");
|
|
255
|
+
}
|
|
256
|
+
const prompt = asString(entry, "prompt")?.trim();
|
|
257
|
+
if (prompt === void 0 || prompt.length === 0) {
|
|
258
|
+
throw new Error("ask_user each question needs a non-empty \xABprompt\xBB.");
|
|
259
|
+
}
|
|
260
|
+
questions.push({
|
|
261
|
+
id: `q${index + 1}`,
|
|
262
|
+
prompt,
|
|
263
|
+
options: parseOptions(entry["options"]),
|
|
264
|
+
allowMultiple: asBoolean(entry, "allowMultiple", false)
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return questions;
|
|
268
|
+
};
|
|
269
|
+
var parseAskUserArgs = (args) => {
|
|
270
|
+
const question = asString(args, "question")?.trim() ?? "";
|
|
271
|
+
const questions = parseQuestions(args["questions"]);
|
|
272
|
+
if (question.length === 0 && questions.length === 0) {
|
|
273
|
+
throw new Error("ask_user requires \xABquestion\xBB or a non-empty \xABquestions\xBB list.");
|
|
274
|
+
}
|
|
275
|
+
return { question, questions };
|
|
276
|
+
};
|
|
277
|
+
|
|
19
278
|
// packages/tools/dist/builtins/ask_user/tool.js
|
|
20
279
|
var ASK_USER_ABORTED = "ask_user aborted.";
|
|
21
280
|
var invokeWithAbort = (work, signal) => {
|
|
@@ -41,30 +300,49 @@ var invokeWithAbort = (work, signal) => {
|
|
|
41
300
|
});
|
|
42
301
|
};
|
|
43
302
|
var invoke = async (ctx, args) => {
|
|
44
|
-
const
|
|
45
|
-
if (question === void 0 || question.trim().length === 0) {
|
|
46
|
-
throw new Error("ask_user requires string argument \xABquestion\xBB.");
|
|
47
|
-
}
|
|
303
|
+
const request = parseAskUserArgs(args);
|
|
48
304
|
if (ctx.askUser === void 0) {
|
|
49
305
|
throw new Error("ask_user requires a live HITL host. No askUser hook is bound on this harness.");
|
|
50
306
|
}
|
|
51
|
-
return invokeWithAbort(ctx.askUser(
|
|
307
|
+
return invokeWithAbort(ctx.askUser(request), ctx.signal);
|
|
52
308
|
};
|
|
53
309
|
var askUserTool = {
|
|
54
310
|
id: "ask_user",
|
|
55
311
|
registration: {
|
|
56
312
|
toolId: "ask_user",
|
|
57
313
|
name: "ask_user",
|
|
58
|
-
description: "Do not guess. If you are not sure, ask the user. Pauses the run so the operator can
|
|
314
|
+
description: "Do not guess. If you are not sure, ask the user. Pauses the run so the operator can answer. Pass \xABquestion\xBB for a freeform prompt, and/or ordered \xABquestions\xBB with optional choice lists. The operator sees every question at once, may select options (multi-select when allowMultiple is true), and may also type a freeform reply. One Send returns the answers.",
|
|
59
315
|
inputSchema: {
|
|
60
316
|
type: "object",
|
|
61
317
|
properties: {
|
|
62
318
|
question: {
|
|
63
319
|
type: "string",
|
|
64
|
-
description: "
|
|
320
|
+
description: "Headline or single freeform question. Required when questions is omitted."
|
|
321
|
+
},
|
|
322
|
+
questions: {
|
|
323
|
+
type: "array",
|
|
324
|
+
description: "Ordered questions shown together. Each may include options. Max 8.",
|
|
325
|
+
items: {
|
|
326
|
+
type: "object",
|
|
327
|
+
properties: {
|
|
328
|
+
prompt: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Question text for the operator."
|
|
331
|
+
},
|
|
332
|
+
options: {
|
|
333
|
+
type: "array",
|
|
334
|
+
description: "Optional choice labels. Max 12. Empty means freeform only for this prompt.",
|
|
335
|
+
items: { type: "string" }
|
|
336
|
+
},
|
|
337
|
+
allowMultiple: {
|
|
338
|
+
type: "boolean",
|
|
339
|
+
description: "When true, the operator may select several options for this question."
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
required: ["prompt"]
|
|
343
|
+
}
|
|
65
344
|
}
|
|
66
345
|
},
|
|
67
|
-
required: ["question"],
|
|
68
346
|
additionalProperties: false
|
|
69
347
|
}
|
|
70
348
|
},
|
|
@@ -111,10 +389,7 @@ var toHarnessDisplayPath = (projectRoot, absolute, allowedRoots = []) => {
|
|
|
111
389
|
}
|
|
112
390
|
return absolute.split(path.sep).join("/");
|
|
113
391
|
};
|
|
114
|
-
var
|
|
115
|
-
var asPathFenceOptions = (denyOrOptions) => isPathFenceOptions(denyOrOptions) ? denyOrOptions : { denyPaths: denyOrOptions };
|
|
116
|
-
var resolveProjectPath = (projectRoot, userPath, denyOrOptions = {}) => {
|
|
117
|
-
const options = asPathFenceOptions(denyOrOptions);
|
|
392
|
+
var resolveProjectPath = (projectRoot, userPath, options = {}) => {
|
|
118
393
|
const denyPaths = options.denyPaths ?? DEFAULT_DENY;
|
|
119
394
|
const allowedRoots = options.allowedRoots ?? [];
|
|
120
395
|
const trimmed = userPath.trim();
|
|
@@ -172,7 +447,8 @@ var invoke2 = async (ctx, args) => {
|
|
|
172
447
|
const child = spawn(command, {
|
|
173
448
|
cwd,
|
|
174
449
|
shell: true,
|
|
175
|
-
windowsHide: true
|
|
450
|
+
windowsHide: true,
|
|
451
|
+
...ctx.signal === void 0 ? {} : { signal: ctx.signal }
|
|
176
452
|
});
|
|
177
453
|
let stdout = "";
|
|
178
454
|
let stderr = "";
|
|
@@ -532,7 +808,6 @@ var globToRegExp = (pattern) => {
|
|
|
532
808
|
const escaped = pattern.replace(/\\/g, "/").replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/::DOUBLESTAR::/g, ".*");
|
|
533
809
|
return new RegExp(`^${escaped}$`);
|
|
534
810
|
};
|
|
535
|
-
var normalizeOptions = (options) => typeof options === "boolean" ? { respectGitignore: options } : options;
|
|
536
811
|
var yieldEventLoop = () => new Promise((resolve) => {
|
|
537
812
|
setImmediate(resolve);
|
|
538
813
|
});
|
|
@@ -541,16 +816,15 @@ var throwIfAborted = (signal) => {
|
|
|
541
816
|
throw new Error("aborted");
|
|
542
817
|
}
|
|
543
818
|
};
|
|
544
|
-
var walkFiles = async (root, dir, options =
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
const yieldEvery = opts.yieldEvery ?? 32;
|
|
819
|
+
var walkFiles = async (root, dir, options = {}) => {
|
|
820
|
+
const respectGitignore = options.respectGitignore !== false;
|
|
821
|
+
const yieldEvery = options.yieldEvery ?? 32;
|
|
548
822
|
const matcher = respectGitignore ? await loadGitIgnoreMatcher(root) : { ignores: () => false };
|
|
549
823
|
const out = [];
|
|
550
824
|
const visitedDirs = /* @__PURE__ */ new Set();
|
|
551
825
|
let steps = 0;
|
|
552
826
|
const visit = async (absoluteDir) => {
|
|
553
|
-
throwIfAborted(
|
|
827
|
+
throwIfAborted(options.signal);
|
|
554
828
|
let realDir;
|
|
555
829
|
try {
|
|
556
830
|
realDir = await fs6.realpath(absoluteDir);
|
|
@@ -568,11 +842,11 @@ var walkFiles = async (root, dir, options = true) => {
|
|
|
568
842
|
return;
|
|
569
843
|
}
|
|
570
844
|
for (const entry of entries) {
|
|
571
|
-
throwIfAborted(
|
|
845
|
+
throwIfAborted(options.signal);
|
|
572
846
|
steps += 1;
|
|
573
847
|
if (steps % yieldEvery === 0) {
|
|
574
848
|
await yieldEventLoop();
|
|
575
|
-
throwIfAborted(
|
|
849
|
+
throwIfAborted(options.signal);
|
|
576
850
|
}
|
|
577
851
|
if (entry.isDirectory() && WALK_EXCLUDE_DIR_NAMES.includes(entry.name)) {
|
|
578
852
|
continue;
|
|
@@ -589,7 +863,7 @@ var walkFiles = async (root, dir, options = true) => {
|
|
|
589
863
|
await visit(absolute);
|
|
590
864
|
} else if (entry.isFile()) {
|
|
591
865
|
out.push(relative);
|
|
592
|
-
if (
|
|
866
|
+
if (options.maxFiles !== void 0 && out.length >= options.maxFiles) {
|
|
593
867
|
return;
|
|
594
868
|
}
|
|
595
869
|
}
|
|
@@ -610,7 +884,7 @@ var invoke6 = async (ctx, args) => {
|
|
|
610
884
|
const respectGitignore = asBoolean(args, "respectGitignore", true);
|
|
611
885
|
const searchRoot = resolveProjectPath(ctx.projectRoot, cwdArg, fenceOptions(ctx));
|
|
612
886
|
const fenceRoot = resolveFenceRoot(ctx.projectRoot, searchRoot, ctx.allowedRoots) ?? path5.resolve(ctx.projectRoot);
|
|
613
|
-
const files = await walkFiles(fenceRoot, searchRoot, respectGitignore);
|
|
887
|
+
const files = await walkFiles(fenceRoot, searchRoot, { respectGitignore });
|
|
614
888
|
const regex = globToRegExp(pattern.replace(/\\/g, "/"));
|
|
615
889
|
const searchRel = path5.relative(fenceRoot, searchRoot).split(path5.sep).join("/");
|
|
616
890
|
const cwdPrefix = searchRel === "" ? "" : `${searchRel.replace(/\/$/, "")}/`;
|
|
@@ -1072,8 +1346,88 @@ var grepTool = {
|
|
|
1072
1346
|
invoke: invoke7
|
|
1073
1347
|
};
|
|
1074
1348
|
|
|
1075
|
-
// packages/tools/dist/builtins/
|
|
1349
|
+
// packages/tools/dist/builtins/move/tool.js
|
|
1076
1350
|
import fs9 from "node:fs/promises";
|
|
1351
|
+
import path8 from "node:path";
|
|
1352
|
+
var errorCode = (error) => error !== null && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
1353
|
+
var isInsideDirectory = (parent, child) => {
|
|
1354
|
+
const relative = path8.relative(parent, child);
|
|
1355
|
+
return relative !== "" && !relative.startsWith("..") && !path8.isAbsolute(relative);
|
|
1356
|
+
};
|
|
1357
|
+
var invoke8 = async (ctx, args) => {
|
|
1358
|
+
const fromPath = asString(args, "from");
|
|
1359
|
+
const toPath = asString(args, "to");
|
|
1360
|
+
if (fromPath === void 0 || toPath === void 0) {
|
|
1361
|
+
throw new Error("move requires string arguments \xABfrom\xBB and \xABto\xBB.");
|
|
1362
|
+
}
|
|
1363
|
+
const fromAbs = resolveProjectPath(ctx.projectRoot, fromPath, fenceOptions(ctx));
|
|
1364
|
+
const toAbs = resolveProjectPath(ctx.projectRoot, toPath, fenceOptions(ctx));
|
|
1365
|
+
if (fromAbs === toAbs) {
|
|
1366
|
+
return `Already at \xAB${fromPath}\xBB.`;
|
|
1367
|
+
}
|
|
1368
|
+
let fromStat;
|
|
1369
|
+
try {
|
|
1370
|
+
fromStat = await fs9.lstat(fromAbs);
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
if (errorCode(error) === "ENOENT") {
|
|
1373
|
+
throw new Error(await formatNotFound(fromAbs, fromPath));
|
|
1374
|
+
}
|
|
1375
|
+
throw error;
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
const toStat = await fs9.lstat(toAbs);
|
|
1379
|
+
const kind = toStat.isDirectory() ? "directory" : "file";
|
|
1380
|
+
throw new Error(`move failed: \xAB${toPath}\xBB already exists (${kind}). Delete or rename it first, then retry.`);
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
if (error instanceof Error && error.message.startsWith("move failed:")) {
|
|
1383
|
+
throw error;
|
|
1384
|
+
}
|
|
1385
|
+
if (errorCode(error) !== "ENOENT") {
|
|
1386
|
+
throw error;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (fromStat.isDirectory() && isInsideDirectory(fromAbs, toAbs)) {
|
|
1390
|
+
throw new Error(`move failed: destination \xAB${toPath}\xBB is inside source directory \xAB${fromPath}\xBB.`);
|
|
1391
|
+
}
|
|
1392
|
+
await fs9.mkdir(path8.dirname(toAbs), { recursive: true });
|
|
1393
|
+
try {
|
|
1394
|
+
await fs9.rename(fromAbs, toAbs);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
if (errorCode(error) !== "EXDEV") {
|
|
1397
|
+
throw error;
|
|
1398
|
+
}
|
|
1399
|
+
await fs9.cp(fromAbs, toAbs, { recursive: true });
|
|
1400
|
+
await fs9.rm(fromAbs, { recursive: true, force: true });
|
|
1401
|
+
}
|
|
1402
|
+
return `Moved \xAB${fromPath}\xBB \u2192 \xAB${toPath}\xBB.`;
|
|
1403
|
+
};
|
|
1404
|
+
var moveTool = {
|
|
1405
|
+
id: "move",
|
|
1406
|
+
registration: {
|
|
1407
|
+
toolId: "move",
|
|
1408
|
+
name: "move",
|
|
1409
|
+
description: "Move or rename a file or directory under the project root. Fails if the destination exists (delete or rename it first). Prefer this over bash mv/ren/Move-Item. Both from and to must be the full path (not a parent directory).",
|
|
1410
|
+
inputSchema: {
|
|
1411
|
+
type: "object",
|
|
1412
|
+
properties: {
|
|
1413
|
+
from: {
|
|
1414
|
+
type: "string",
|
|
1415
|
+
description: "Project-relative source file or directory"
|
|
1416
|
+
},
|
|
1417
|
+
to: {
|
|
1418
|
+
type: "string",
|
|
1419
|
+
description: "Project-relative destination path (full new path, not a parent folder)"
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
required: ["from", "to"],
|
|
1423
|
+
additionalProperties: false
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
invoke: invoke8
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
// packages/tools/dist/builtins/read/tool.js
|
|
1430
|
+
import fs10 from "node:fs/promises";
|
|
1077
1431
|
var MAX_READ_CHARS = 2e5;
|
|
1078
1432
|
var sliceLines = (content, args) => {
|
|
1079
1433
|
const lines = content.split(/\r?\n/);
|
|
@@ -1103,7 +1457,7 @@ var sliceLines = (content, args) => {
|
|
|
1103
1457
|
}
|
|
1104
1458
|
return truncated;
|
|
1105
1459
|
};
|
|
1106
|
-
var
|
|
1460
|
+
var invoke9 = async (ctx, args) => {
|
|
1107
1461
|
const userPath = asString(args, "path");
|
|
1108
1462
|
if (userPath === void 0) {
|
|
1109
1463
|
throw new Error("read requires string argument \xABpath\xBB.");
|
|
@@ -1111,7 +1465,7 @@ var invoke8 = async (ctx, args) => {
|
|
|
1111
1465
|
const absolute = resolveProjectPath(ctx.projectRoot, userPath, fenceOptions(ctx));
|
|
1112
1466
|
let content;
|
|
1113
1467
|
try {
|
|
1114
|
-
content = await
|
|
1468
|
+
content = await fs10.readFile(absolute, "utf8");
|
|
1115
1469
|
} catch (error) {
|
|
1116
1470
|
const code = error !== null && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
1117
1471
|
if (code === "ENOENT") {
|
|
@@ -1159,21 +1513,74 @@ var readTool = {
|
|
|
1159
1513
|
additionalProperties: false
|
|
1160
1514
|
}
|
|
1161
1515
|
},
|
|
1162
|
-
invoke:
|
|
1516
|
+
invoke: invoke9
|
|
1517
|
+
};
|
|
1518
|
+
|
|
1519
|
+
// packages/tools/dist/builtins/sleep/tool.js
|
|
1520
|
+
var SLEEP_MIN_SECONDS = 1;
|
|
1521
|
+
var SLEEP_MAX_SECONDS = 300;
|
|
1522
|
+
var invoke10 = async (ctx, args) => {
|
|
1523
|
+
const seconds = asNumber(args, "seconds");
|
|
1524
|
+
if (seconds === void 0 || !Number.isInteger(seconds)) {
|
|
1525
|
+
throw new Error("sleep requires integer argument \xABseconds\xBB (1\u2013300).");
|
|
1526
|
+
}
|
|
1527
|
+
if (seconds < SLEEP_MIN_SECONDS || seconds > SLEEP_MAX_SECONDS) {
|
|
1528
|
+
throw new Error(`sleep \xABseconds\xBB must be between ${String(SLEEP_MIN_SECONDS)} and ${String(SLEEP_MAX_SECONDS)}.`);
|
|
1529
|
+
}
|
|
1530
|
+
const signal = ctx.signal;
|
|
1531
|
+
await new Promise((resolve, reject) => {
|
|
1532
|
+
const onAbort = () => {
|
|
1533
|
+
clearTimeout(timer);
|
|
1534
|
+
reject(new Error("sleep aborted."));
|
|
1535
|
+
};
|
|
1536
|
+
const timer = setTimeout(() => {
|
|
1537
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1538
|
+
resolve();
|
|
1539
|
+
}, seconds * 1e3);
|
|
1540
|
+
if (signal?.aborted) {
|
|
1541
|
+
clearTimeout(timer);
|
|
1542
|
+
reject(new Error("sleep aborted."));
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
signal?.addEventListener("abort", onAbort);
|
|
1546
|
+
});
|
|
1547
|
+
return `Slept ${String(seconds)}s.`;
|
|
1548
|
+
};
|
|
1549
|
+
var sleepTool = {
|
|
1550
|
+
id: "sleep",
|
|
1551
|
+
registration: {
|
|
1552
|
+
toolId: "sleep",
|
|
1553
|
+
name: "sleep",
|
|
1554
|
+
description: "Wait a bounded number of seconds (1\u2013300) without bash. Stop aborts the wait. Not a job-wait and not canvas Delay.",
|
|
1555
|
+
inputSchema: {
|
|
1556
|
+
type: "object",
|
|
1557
|
+
properties: {
|
|
1558
|
+
seconds: {
|
|
1559
|
+
type: "integer",
|
|
1560
|
+
minimum: SLEEP_MIN_SECONDS,
|
|
1561
|
+
maximum: SLEEP_MAX_SECONDS,
|
|
1562
|
+
description: "Seconds to wait (integer, 1\u2013300). Over the cap is an error."
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1565
|
+
required: ["seconds"],
|
|
1566
|
+
additionalProperties: false
|
|
1567
|
+
}
|
|
1568
|
+
},
|
|
1569
|
+
invoke: invoke10
|
|
1163
1570
|
};
|
|
1164
1571
|
|
|
1165
1572
|
// packages/tools/dist/builtins/write/tool.js
|
|
1166
|
-
import
|
|
1167
|
-
import
|
|
1168
|
-
var
|
|
1573
|
+
import fs11 from "node:fs/promises";
|
|
1574
|
+
import path9 from "node:path";
|
|
1575
|
+
var invoke11 = async (ctx, args) => {
|
|
1169
1576
|
const userPath = asString(args, "path");
|
|
1170
1577
|
const content = asString(args, "content");
|
|
1171
1578
|
if (userPath === void 0 || content === void 0) {
|
|
1172
1579
|
throw new Error("write requires string arguments \xABpath\xBB and \xABcontent\xBB.");
|
|
1173
1580
|
}
|
|
1174
1581
|
const absolute = resolveProjectPath(ctx.projectRoot, userPath, fenceOptions(ctx));
|
|
1175
|
-
await
|
|
1176
|
-
await
|
|
1582
|
+
await fs11.mkdir(path9.dirname(absolute), { recursive: true });
|
|
1583
|
+
await fs11.writeFile(absolute, content, "utf8");
|
|
1177
1584
|
return `Wrote \xAB${userPath}\xBB (${content.length} chars).`;
|
|
1178
1585
|
};
|
|
1179
1586
|
var writeTool = {
|
|
@@ -1198,7 +1605,7 @@ var writeTool = {
|
|
|
1198
1605
|
additionalProperties: false
|
|
1199
1606
|
}
|
|
1200
1607
|
},
|
|
1201
|
-
invoke:
|
|
1608
|
+
invoke: invoke11
|
|
1202
1609
|
};
|
|
1203
1610
|
|
|
1204
1611
|
// packages/tools/dist/builtins/catalog.js
|
|
@@ -1210,6 +1617,8 @@ var BUILTIN_TOOLS = [
|
|
|
1210
1617
|
writeTool,
|
|
1211
1618
|
createTool,
|
|
1212
1619
|
deleteTool,
|
|
1620
|
+
moveTool,
|
|
1621
|
+
sleepTool,
|
|
1213
1622
|
bashTool,
|
|
1214
1623
|
askUserTool
|
|
1215
1624
|
];
|
|
@@ -1219,226 +1628,71 @@ var byId = Object.fromEntries(BUILTIN_TOOLS.map((tool) => [tool.id, tool]));
|
|
|
1219
1628
|
var isBuiltinToolId = (toolId) => Object.hasOwn(byId, toolId);
|
|
1220
1629
|
var invokeBuiltin = (toolId, ctx, args) => byId[toolId].invoke(ctx, args);
|
|
1221
1630
|
|
|
1222
|
-
// packages/tools/dist/permission.js
|
|
1223
|
-
var DEFAULT_PERMISSION_CONFIG = {
|
|
1224
|
-
read: { "*": "allow" },
|
|
1225
|
-
glob: { "*": "allow" },
|
|
1226
|
-
grep: { "*": "allow" },
|
|
1227
|
-
edit: { "*": "allow" },
|
|
1228
|
-
write: { "*": "allow" },
|
|
1229
|
-
create: { "*": "allow" },
|
|
1230
|
-
delete: { "*": "allow" },
|
|
1231
|
-
bash: { "*": "allow" },
|
|
1232
|
-
ask_user: { "*": "allow" }
|
|
1233
|
-
};
|
|
1234
|
-
var DECISION_RANK = {
|
|
1235
|
-
deny: 3,
|
|
1236
|
-
ask: 2,
|
|
1237
|
-
allow: 1
|
|
1238
|
-
};
|
|
1239
|
-
var isDecision = (value) => value === "allow" || value === "ask" || value === "deny";
|
|
1240
|
-
var escapeRegExp = (text) => text.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
1241
|
-
var matchPermissionPattern = (pattern, detail) => {
|
|
1242
|
-
const normalizedPattern = pattern.trim();
|
|
1243
|
-
const normalizedDetail = detail;
|
|
1244
|
-
if (normalizedPattern.length === 0) {
|
|
1245
|
-
return false;
|
|
1246
|
-
}
|
|
1247
|
-
if (normalizedPattern === "*") {
|
|
1248
|
-
return true;
|
|
1249
|
-
}
|
|
1250
|
-
const pathStyle = normalizedPattern.includes("/") || normalizedPattern.includes("**");
|
|
1251
|
-
if (pathStyle) {
|
|
1252
|
-
const posixDetail = normalizedDetail.replace(/\\/g, "/");
|
|
1253
|
-
let regexSource2 = "";
|
|
1254
|
-
for (let i = 0; i < normalizedPattern.length; i += 1) {
|
|
1255
|
-
if (normalizedPattern.startsWith("**/", i)) {
|
|
1256
|
-
regexSource2 += "(?:.*/)?";
|
|
1257
|
-
i += 2;
|
|
1258
|
-
continue;
|
|
1259
|
-
}
|
|
1260
|
-
if (normalizedPattern.startsWith("**", i)) {
|
|
1261
|
-
regexSource2 += ".*";
|
|
1262
|
-
i += 1;
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
const ch = normalizedPattern[i] ?? "";
|
|
1266
|
-
if (ch === "*") {
|
|
1267
|
-
regexSource2 += "[^/]*";
|
|
1268
|
-
continue;
|
|
1269
|
-
}
|
|
1270
|
-
regexSource2 += escapeRegExp(ch);
|
|
1271
|
-
}
|
|
1272
|
-
return new RegExp(`^${regexSource2}$`).test(posixDetail);
|
|
1273
|
-
}
|
|
1274
|
-
let regexSource = "";
|
|
1275
|
-
for (const ch of normalizedPattern) {
|
|
1276
|
-
if (ch === "*") {
|
|
1277
|
-
regexSource += "[\\s\\S]*";
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
regexSource += escapeRegExp(ch);
|
|
1281
|
-
}
|
|
1282
|
-
return new RegExp(`^${regexSource}$`).test(normalizedDetail);
|
|
1283
|
-
};
|
|
1284
|
-
var rulesForTool = (config, toolId) => {
|
|
1285
|
-
const raw = config[toolId] ?? DEFAULT_PERMISSION_CONFIG[toolId];
|
|
1286
|
-
if (raw === void 0) {
|
|
1287
|
-
return { "*": "deny" };
|
|
1288
|
-
}
|
|
1289
|
-
if (isDecision(raw)) {
|
|
1290
|
-
return { "*": raw };
|
|
1291
|
-
}
|
|
1292
|
-
const entries = Object.entries(raw).filter((entry) => isDecision(entry[1]));
|
|
1293
|
-
if (entries.length === 0) {
|
|
1294
|
-
return { "*": "deny" };
|
|
1295
|
-
}
|
|
1296
|
-
return Object.fromEntries(entries);
|
|
1297
|
-
};
|
|
1298
|
-
var resolvePermission = (config, toolId, detail) => {
|
|
1299
|
-
const merged = {
|
|
1300
|
-
...DEFAULT_PERMISSION_CONFIG,
|
|
1301
|
-
...config ?? {}
|
|
1302
|
-
};
|
|
1303
|
-
const rules = rulesForTool(merged, toolId);
|
|
1304
|
-
const matches = Object.entries(rules).filter(([pattern]) => matchPermissionPattern(pattern, detail));
|
|
1305
|
-
if (matches.length === 0) {
|
|
1306
|
-
return "deny";
|
|
1307
|
-
}
|
|
1308
|
-
matches.sort((a, b) => {
|
|
1309
|
-
const lengthDelta = b[0].length - a[0].length;
|
|
1310
|
-
if (lengthDelta !== 0) {
|
|
1311
|
-
return lengthDelta;
|
|
1312
|
-
}
|
|
1313
|
-
return DECISION_RANK[b[1]] - DECISION_RANK[a[1]];
|
|
1314
|
-
});
|
|
1315
|
-
return matches[0]?.[1] ?? "deny";
|
|
1316
|
-
};
|
|
1317
|
-
var permissionDetailForCall = (toolId, args) => {
|
|
1318
|
-
if (toolId === "bash") {
|
|
1319
|
-
return typeof args.command === "string" ? args.command : "";
|
|
1320
|
-
}
|
|
1321
|
-
if (toolId === "ask_user") {
|
|
1322
|
-
return typeof args.question === "string" ? args.question : "";
|
|
1323
|
-
}
|
|
1324
|
-
if (typeof args.path === "string" && args.path.length > 0) {
|
|
1325
|
-
return args.path.replace(/\\/g, "/");
|
|
1326
|
-
}
|
|
1327
|
-
if (typeof args.file === "string" && args.file.length > 0) {
|
|
1328
|
-
return args.file.replace(/\\/g, "/");
|
|
1329
|
-
}
|
|
1330
|
-
if (typeof args.url === "string" && args.url.length > 0) {
|
|
1331
|
-
return args.url;
|
|
1332
|
-
}
|
|
1333
|
-
if (typeof args.key === "string" && args.key.length > 0) {
|
|
1334
|
-
return args.key;
|
|
1335
|
-
}
|
|
1336
|
-
if (typeof args.collectionId === "string" && args.collectionId.length > 0) {
|
|
1337
|
-
return args.collectionId;
|
|
1338
|
-
}
|
|
1339
|
-
return "*";
|
|
1340
|
-
};
|
|
1341
|
-
var permissionAskSummary = (toolId, detail) => {
|
|
1342
|
-
if (toolId === "bash") {
|
|
1343
|
-
return `Allow bash: ${detail.length > 0 ? detail : "(empty command)"}?`;
|
|
1344
|
-
}
|
|
1345
|
-
return `Allow ${toolId}: ${detail.length > 0 ? detail : "(no path)"}?`;
|
|
1346
|
-
};
|
|
1347
|
-
var grantKeyForCall = (toolId, detail) => `${toolId}\0${detail}`;
|
|
1348
|
-
var mergePermissionConfigs = (...layers) => layers.reduce((acc, layer) => layer === void 0 ? acc : { ...acc, ...layer }, {});
|
|
1349
|
-
var stricterPermission = (left, right) => DECISION_RANK[left] >= DECISION_RANK[right] ? left : right;
|
|
1350
|
-
var toolFloorDecision = (config, toolId) => {
|
|
1351
|
-
const merged = {
|
|
1352
|
-
...DEFAULT_PERMISSION_CONFIG,
|
|
1353
|
-
...config ?? {}
|
|
1354
|
-
};
|
|
1355
|
-
if (merged[toolId] === void 0 && DEFAULT_PERMISSION_CONFIG[toolId] === void 0) {
|
|
1356
|
-
return "allow";
|
|
1357
|
-
}
|
|
1358
|
-
const rules = rulesForTool(merged, toolId);
|
|
1359
|
-
const star = rules["*"];
|
|
1360
|
-
if (star !== void 0 && Object.keys(rules).length === 1) {
|
|
1361
|
-
return star;
|
|
1362
|
-
}
|
|
1363
|
-
const decisions = Object.values(rules);
|
|
1364
|
-
if (decisions.some((decision) => decision === "allow")) {
|
|
1365
|
-
return "allow";
|
|
1366
|
-
}
|
|
1367
|
-
if (decisions.some((decision) => decision === "ask")) {
|
|
1368
|
-
return "ask";
|
|
1369
|
-
}
|
|
1370
|
-
return "deny";
|
|
1371
|
-
};
|
|
1372
|
-
var clampToolPermission = (floor, node) => stricterPermission(floor, node);
|
|
1373
|
-
var isToolAlwaysDenied = (config, toolId) => {
|
|
1374
|
-
const merged = {
|
|
1375
|
-
...DEFAULT_PERMISSION_CONFIG,
|
|
1376
|
-
...config ?? {}
|
|
1377
|
-
};
|
|
1378
|
-
if (merged[toolId] === void 0 && DEFAULT_PERMISSION_CONFIG[toolId] === void 0) {
|
|
1379
|
-
return false;
|
|
1380
|
-
}
|
|
1381
|
-
const rules = rulesForTool(merged, toolId);
|
|
1382
|
-
const decisions = Object.values(rules);
|
|
1383
|
-
return decisions.length > 0 && decisions.every((decision) => decision === "deny");
|
|
1384
|
-
};
|
|
1385
|
-
var mergeProjectAndNodePermissions = (projectPermission, nodeToolPermissions) => {
|
|
1386
|
-
const project = {
|
|
1387
|
-
...DEFAULT_PERMISSION_CONFIG,
|
|
1388
|
-
...projectPermission ?? {}
|
|
1389
|
-
};
|
|
1390
|
-
if (nodeToolPermissions === void 0) {
|
|
1391
|
-
return projectPermission ?? {};
|
|
1392
|
-
}
|
|
1393
|
-
const nodeEntries = [];
|
|
1394
|
-
for (const [toolId, decision] of Object.entries(nodeToolPermissions)) {
|
|
1395
|
-
if (!isDecision(decision)) {
|
|
1396
|
-
continue;
|
|
1397
|
-
}
|
|
1398
|
-
const floor = toolFloorDecision(project, toolId);
|
|
1399
|
-
nodeEntries.push([toolId, clampToolPermission(floor, decision)]);
|
|
1400
|
-
}
|
|
1401
|
-
return mergePermissionConfigs(projectPermission, Object.fromEntries(nodeEntries));
|
|
1402
|
-
};
|
|
1403
|
-
var PERMISSION_DENY_HINT = "Tighten or loosen via Inspector tool permissions (within project floor) or edit permission.<tool> in .langflower/langflower.jsonc.";
|
|
1404
|
-
var formatPermissionDeniedText = (toolId, detail) => {
|
|
1405
|
-
const target = detail.length > 0 ? `${toolId} (${detail})` : toolId;
|
|
1406
|
-
return `Permission denied for ${target}. Effective policy is deny (project floor and/or node toolPermissions). ${PERMISSION_DENY_HINT}`;
|
|
1407
|
-
};
|
|
1408
|
-
|
|
1409
1631
|
// packages/tools/dist/gate-tool-call.js
|
|
1410
1632
|
var deniedToolResult = (toolId, detail) => ({
|
|
1411
1633
|
ok: false,
|
|
1412
1634
|
text: formatPermissionDeniedText(toolId, detail)
|
|
1413
1635
|
});
|
|
1414
1636
|
var gateToolCall = async (options) => {
|
|
1415
|
-
const { toolId,
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
1637
|
+
const { toolId, grants, permission, requestPermission } = options;
|
|
1638
|
+
const details = options.details.length > 0 ? options.details : ["*"];
|
|
1639
|
+
const pendingAsk = [];
|
|
1640
|
+
for (const detail of details) {
|
|
1641
|
+
if (grants.has(grantKeyForCall(toolId, detail))) {
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
const decision = resolvePermission(permission, toolId, detail);
|
|
1645
|
+
if (decision === "deny") {
|
|
1646
|
+
return "deny";
|
|
1647
|
+
}
|
|
1648
|
+
if (decision === "ask") {
|
|
1649
|
+
pendingAsk.push(detail);
|
|
1650
|
+
}
|
|
1419
1651
|
}
|
|
1420
|
-
|
|
1421
|
-
if (decision === "allow") {
|
|
1652
|
+
if (pendingAsk.length === 0) {
|
|
1422
1653
|
return "allow";
|
|
1423
1654
|
}
|
|
1424
|
-
if (decision === "deny") {
|
|
1425
|
-
return "deny";
|
|
1426
|
-
}
|
|
1427
1655
|
if (requestPermission === void 0) {
|
|
1428
1656
|
return "deny";
|
|
1429
1657
|
}
|
|
1658
|
+
const combined = details.join(" \u2192 ");
|
|
1430
1659
|
const reply = await requestPermission({
|
|
1431
1660
|
toolId,
|
|
1432
|
-
detail,
|
|
1433
|
-
summary: permissionAskSummary(toolId,
|
|
1434
|
-
});
|
|
1661
|
+
detail: combined,
|
|
1662
|
+
summary: permissionAskSummary(toolId, combined)
|
|
1663
|
+
}, options.signal);
|
|
1435
1664
|
if (reply === "allow") {
|
|
1436
|
-
|
|
1665
|
+
for (const detail of pendingAsk) {
|
|
1666
|
+
grants.add(grantKeyForCall(toolId, detail));
|
|
1667
|
+
}
|
|
1437
1668
|
return "allow";
|
|
1438
1669
|
}
|
|
1439
1670
|
return "deny";
|
|
1440
1671
|
};
|
|
1441
1672
|
|
|
1673
|
+
// packages/tools/dist/wrap-builtin-tool-handles.js
|
|
1674
|
+
var wrapBuiltinToolHandles = (harness, permission) => {
|
|
1675
|
+
const filtered = harness.listBuiltinRegistrations().filter((reg) => !isToolAlwaysDenied(permission, reg.toolId));
|
|
1676
|
+
return filtered.map((reg) => ({
|
|
1677
|
+
toolId: reg.toolId,
|
|
1678
|
+
name: reg.name,
|
|
1679
|
+
description: reg.description,
|
|
1680
|
+
inputSchema: reg.inputSchema,
|
|
1681
|
+
invoke: async (args, toolCtx) => {
|
|
1682
|
+
const hostCtx = toolCtx;
|
|
1683
|
+
const result = await harness.invoke({
|
|
1684
|
+
toolId: reg.toolId,
|
|
1685
|
+
args,
|
|
1686
|
+
...hostCtx?.signal !== void 0 ? { signal: hostCtx.signal } : {}
|
|
1687
|
+
});
|
|
1688
|
+
if (!result.ok) {
|
|
1689
|
+
throw new Error(result.text);
|
|
1690
|
+
}
|
|
1691
|
+
return result.text;
|
|
1692
|
+
}
|
|
1693
|
+
}));
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1442
1696
|
// packages/tools/dist/create-project-harness.js
|
|
1443
1697
|
var createProjectHarness = (options) => {
|
|
1444
1698
|
const ctx = {
|
|
@@ -1453,19 +1707,24 @@ var createProjectHarness = (options) => {
|
|
|
1453
1707
|
...options.permission ?? {}
|
|
1454
1708
|
};
|
|
1455
1709
|
const grants = /* @__PURE__ */ new Set();
|
|
1456
|
-
const
|
|
1710
|
+
const deniedDetail = (toolId, details) => {
|
|
1711
|
+
const denied = details.find((detail) => resolvePermission(permission, toolId, detail) === "deny");
|
|
1712
|
+
return denied ?? details.join(" \u2192 ");
|
|
1713
|
+
};
|
|
1714
|
+
const gate = (toolId, details, signal) => gateToolCall({
|
|
1457
1715
|
toolId,
|
|
1458
|
-
|
|
1716
|
+
details,
|
|
1459
1717
|
grants,
|
|
1460
1718
|
permission,
|
|
1461
|
-
...options.requestPermission !== void 0 ? { requestPermission: options.requestPermission } : {}
|
|
1719
|
+
...options.requestPermission !== void 0 ? { requestPermission: options.requestPermission } : {},
|
|
1720
|
+
...signal !== void 0 ? { signal } : {}
|
|
1462
1721
|
});
|
|
1463
1722
|
return {
|
|
1464
1723
|
listBuiltinRegistrations: () => BUILTIN_REGISTRATIONS,
|
|
1465
1724
|
authorize: async (call) => {
|
|
1466
1725
|
const toolId = call.toolId.trim();
|
|
1467
|
-
const
|
|
1468
|
-
return gate(toolId,
|
|
1726
|
+
const details = permissionDetailsForCall(toolId, call.args);
|
|
1727
|
+
return gate(toolId, details, call.signal);
|
|
1469
1728
|
},
|
|
1470
1729
|
invoke: async (call) => {
|
|
1471
1730
|
const toolId = call.toolId.trim();
|
|
@@ -1475,10 +1734,10 @@ var createProjectHarness = (options) => {
|
|
|
1475
1734
|
text: `Unknown builtin tool \xAB${call.toolId}\xBB. Available: ${BUILTIN_TOOL_IDS.join(", ")}. Domain/custom tools must expose registration.handler (import from @langflower/tools), not harness toolId lookup.`
|
|
1476
1735
|
};
|
|
1477
1736
|
}
|
|
1478
|
-
const
|
|
1479
|
-
const access = await gate(toolId,
|
|
1737
|
+
const details = permissionDetailsForCall(toolId, call.args);
|
|
1738
|
+
const access = await gate(toolId, details, call.signal);
|
|
1480
1739
|
if (access === "deny") {
|
|
1481
|
-
return deniedToolResult(toolId,
|
|
1740
|
+
return deniedToolResult(toolId, deniedDetail(toolId, details));
|
|
1482
1741
|
}
|
|
1483
1742
|
try {
|
|
1484
1743
|
const invokeCtx = call.signal === void 0 ? ctx : { ...ctx, signal: call.signal };
|
|
@@ -1494,12 +1753,12 @@ var createProjectHarness = (options) => {
|
|
|
1494
1753
|
|
|
1495
1754
|
export {
|
|
1496
1755
|
DEFAULT_PERMISSION_CONFIG,
|
|
1497
|
-
isToolAlwaysDenied,
|
|
1498
1756
|
mergeProjectAndNodePermissions,
|
|
1499
1757
|
formatPermissionDeniedText,
|
|
1500
1758
|
resolveProjectPath,
|
|
1501
1759
|
formatNotFound,
|
|
1502
1760
|
walkFiles,
|
|
1503
1761
|
isBuiltinToolId,
|
|
1762
|
+
wrapBuiltinToolHandles,
|
|
1504
1763
|
createProjectHarness
|
|
1505
1764
|
};
|