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.
Files changed (124) hide show
  1. package/README.md +573 -114
  2. package/dist/{chunk-WV2YWHOC.js → chunk-26UB55E5.js} +44 -4
  3. package/dist/{chunk-AO33IM2J.js → chunk-2LE3E2QP.js} +24 -23
  4. package/dist/{chunk-NIRPHJLO.js → chunk-3Z7Q6SQ5.js} +501 -242
  5. package/dist/{chunk-CVTPGIZE.js → chunk-RZUZX6PW.js} +2 -4
  6. package/dist/index.js +41152 -41395
  7. package/docs/public/README.md +1 -0
  8. package/docs/public/extending.md +21 -3
  9. package/docs/public/getting-started.md +9 -1
  10. package/docs/public/how-it-works.md +2 -2
  11. package/docs/public/launcher.md +111 -0
  12. package/package.json +1 -1
  13. package/ui-dist/apple-touch-icon.png +0 -0
  14. package/ui-dist/{chunk-EZTB7Q33.js → chunk-HEF6F5HN.js} +3 -3
  15. package/ui-dist/{chunk-BKIP5WRK.js → chunk-IASSR2ON.js} +1 -1
  16. package/ui-dist/favicon-32x32.png +0 -0
  17. package/ui-dist/favicon.ico +0 -0
  18. package/ui-dist/index.html +5 -2
  19. package/ui-dist/main-NBS6BFBP.js +323 -0
  20. package/ui-dist/styles-M2URERWI.css +1 -0
  21. package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +1 -1
  22. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +9 -21
  23. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +9 -3
  24. package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.d.ts +0 -1
  25. package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.js +0 -9
  26. package/vendor/node-sdk/dist/node-factory/define-node/define-node.d.ts +5 -1
  27. package/vendor/node-sdk/dist/node-factory/define-node/define-node.js +6 -3
  28. package/vendor/node-sdk/dist/node-factory/define-reactive-node/capabilities.d.ts +128 -0
  29. package/vendor/node-sdk/dist/node-factory/define-reactive-node/capabilities.js +25 -0
  30. package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.d.ts +29 -17
  31. package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.js +14 -6
  32. package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.d.ts +19 -5
  33. package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.js +4 -3
  34. package/vendor/node-sdk/dist/node-factory/define-reactive-node/port-meta.d.ts +6 -1
  35. package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.d.ts +40 -22
  36. package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.js +2 -0
  37. package/vendor/node-sdk/dist/node-factory/define-reactive-node/ui-schema-inference.d.ts +1 -1
  38. package/vendor/node-sdk/dist/node-factory/define-tool-registrations/define-tool-registrations.d.ts +13 -25
  39. package/vendor/node-sdk/dist/node-factory/define-tool-registrations/define-tool-registrations.js +9 -8
  40. package/vendor/node-sdk/dist/testing/create-node-harness.d.ts +9 -2
  41. package/vendor/node-sdk/dist/testing/create-node-harness.js +6 -2
  42. package/vendor/node-sdk/package.json +5 -0
  43. package/vendor/runtime/dist/bypass-ports.d.ts +0 -6
  44. package/vendor/runtime/dist/bypass-ports.js +0 -7
  45. package/vendor/runtime/dist/runtime-editor.d.ts +1 -1
  46. package/vendor/runtime/dist/runtime-editor.js +1 -1
  47. package/vendor/runtime/dist/runtime-helpers.d.ts +0 -1
  48. package/vendor/runtime/dist/runtime-helpers.js +0 -33
  49. package/vendor/runtime/dist/runtime-runner.d.ts +4 -0
  50. package/vendor/runtime/dist/runtime-runner.js +60 -36
  51. package/vendor/runtime/dist/runtime.d.ts +0 -1
  52. package/vendor/runtime/dist/runtime.js +0 -1
  53. package/vendor/runtime/dist/types.d.ts +27 -17
  54. package/vendor/server/skeleton/instructions.md +6 -0
  55. package/vendor/server/skeleton/nodes/my-nodes/README.md +19 -1
  56. package/vendor/server/skeleton/schemas/langflower-config.schema.json +1 -1
  57. package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +44 -10
  58. package/vendor/server/skeleton/skills/langflower-helper/architecture.md +4 -1
  59. package/vendor/server/skeleton/skills/langflower-helper/layout.md +6 -4
  60. package/vendor/server/skeleton/skills/langflower-node-writer/SKILL.md +47 -3
  61. package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +29 -17
  62. package/vendor/server/skeleton/workflows/advanced-coder.json +24 -2
  63. package/vendor/server/skeleton/workflows/kb-create.json +24 -16
  64. package/vendor/server/skeleton/workflows/kb-manual-search.json +1 -1
  65. package/vendor/server/skeleton/workflows/kb-navigate.json +24 -2
  66. package/vendor/server/skeleton/workflows/kb-rag.json +1 -0
  67. package/vendor/server/skeleton/workflows/simple-coder.json +3 -0
  68. package/vendor/server/skeleton/workflows/starter.json +2 -0
  69. package/ui-dist/main-JJURZFKA.js +0 -318
  70. package/ui-dist/styles-VA3HMZK7.css +0 -1
  71. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.d.ts +0 -5
  72. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.js +0 -5
  73. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm.d.ts +0 -3
  74. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm.js +0 -2
  75. package/vendor/node-sdk/dist/node-factory/define-mcp/define-mcp.d.ts +0 -50
  76. package/vendor/node-sdk/dist/node-factory/define-mcp/define-mcp.js +0 -60
  77. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.d.ts +0 -15
  78. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.js +0 -2
  79. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-server-config.d.ts +0 -17
  80. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-server-config.js +0 -2
  81. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-transport.d.ts +0 -20
  82. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-transport.js +0 -2
  83. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp.d.ts +0 -3
  84. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp.js +0 -2
  85. package/vendor/node-sdk/dist/node-factory/define-node/test/samples/gate-node.d.ts +0 -20
  86. package/vendor/node-sdk/dist/node-factory/define-node/test/samples/gate-node.js +0 -22
  87. package/vendor/node-sdk/dist/node-factory/define-reactive-node/chat-completion-stream.d.ts +0 -50
  88. package/vendor/node-sdk/dist/node-factory/define-reactive-node/chat-completion-stream.js +0 -2
  89. package/vendor/node-sdk/dist/node-factory/define-reactive-node/crawl-context.d.ts +0 -22
  90. package/vendor/node-sdk/dist/node-factory/define-reactive-node/crawl-context.js +0 -2
  91. package/vendor/node-sdk/dist/node-factory/define-reactive-node/create-embedding.d.ts +0 -6
  92. package/vendor/node-sdk/dist/node-factory/define-reactive-node/create-embedding.js +0 -2
  93. package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-tool-registrations.d.ts +0 -52
  94. package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-tool-registrations.js +0 -52
  95. package/vendor/node-sdk/dist/node-factory/define-reactive-node/kb-context.d.ts +0 -111
  96. package/vendor/node-sdk/dist/node-factory/define-reactive-node/kb-context.js +0 -2
  97. package/vendor/node-sdk/dist/node-factory/define-reactive-node/memory-context.d.ts +0 -12
  98. package/vendor/node-sdk/dist/node-factory/define-reactive-node/memory-context.js +0 -2
  99. package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-files-context.d.ts +0 -10
  100. package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-files-context.js +0 -2
  101. package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-harness.d.ts +0 -56
  102. package/vendor/node-sdk/dist/node-factory/define-reactive-node/project-harness.js +0 -2
  103. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/agent-node.d.ts +0 -29
  104. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/agent-node.js +0 -60
  105. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/combine-node.d.ts +0 -29
  106. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/combine-node.js +0 -26
  107. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/constant-node.d.ts +0 -29
  108. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/constant-node.js +0 -21
  109. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/delay-node.d.ts +0 -29
  110. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/delay-node.js +0 -27
  111. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/finish-node.d.ts +0 -17
  112. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/finish-node.js +0 -21
  113. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/hitl-node.d.ts +0 -21
  114. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/hitl-node.js +0 -43
  115. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/join-node.d.ts +0 -29
  116. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/join-node.js +0 -31
  117. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/preview-node.d.ts +0 -17
  118. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/preview-node.js +0 -19
  119. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/router-node.d.ts +0 -18
  120. package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/router-node.js +0 -12
  121. package/vendor/node-sdk/dist/node-factory/define-reactive-node/tool-registration.d.ts +0 -36
  122. package/vendor/node-sdk/dist/node-factory/define-reactive-node/tool-registration.js +0 -2
  123. package/vendor/node-sdk/dist/node-factory/define-tool-registrations/tool-registration.d.ts +0 -46
  124. 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 question = asString(args, "question");
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({ question: question.trim() }), ctx.signal);
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 provide additional information.",
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: "The question to show the operator. Be specific about what you need."
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 isPathFenceOptions = (value) => !Array.isArray(value);
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 = true) => {
545
- const opts = normalizeOptions(options);
546
- const respectGitignore = opts.respectGitignore !== false;
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(opts.signal);
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(opts.signal);
845
+ throwIfAborted(options.signal);
572
846
  steps += 1;
573
847
  if (steps % yieldEvery === 0) {
574
848
  await yieldEventLoop();
575
- throwIfAborted(opts.signal);
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 (opts.maxFiles !== void 0 && out.length >= opts.maxFiles) {
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/read/tool.js
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 invoke8 = async (ctx, args) => {
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 fs9.readFile(absolute, "utf8");
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: invoke8
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 fs10 from "node:fs/promises";
1167
- import path8 from "node:path";
1168
- var invoke9 = async (ctx, args) => {
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 fs10.mkdir(path8.dirname(absolute), { recursive: true });
1176
- await fs10.writeFile(absolute, content, "utf8");
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: invoke9
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, detail, grants, permission, requestPermission, whenMissingToolConfig } = options;
1416
- const key = grantKeyForCall(toolId, detail);
1417
- if (grants.has(key)) {
1418
- return "allow";
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
- const decision = whenMissingToolConfig !== void 0 && permission[toolId] === void 0 ? whenMissingToolConfig : resolvePermission(permission, toolId, detail);
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, detail)
1434
- });
1661
+ detail: combined,
1662
+ summary: permissionAskSummary(toolId, combined)
1663
+ }, options.signal);
1435
1664
  if (reply === "allow") {
1436
- grants.add(key);
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 gate = (toolId, detail) => gateToolCall({
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
- detail,
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 detail = permissionDetailForCall(toolId, call.args);
1468
- return gate(toolId, detail);
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 detail = permissionDetailForCall(toolId, call.args);
1479
- const access = await gate(toolId, detail);
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, detail);
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
  };