@multiclawcli/core 0.6.3 → 0.7.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/dist/index.js +109 -61
- package/dist/types/runtime.d.ts +8 -1
- package/dist/utils/toolSummary.d.ts +9 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -241,6 +241,36 @@ function isPermanent(err) {
|
|
|
241
241
|
return err instanceof PermanentError;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
// src/utils/toolSummary.ts
|
|
245
|
+
var MAX_SUMMARY_COLUMNS = 120;
|
|
246
|
+
function stringifySafe(value) {
|
|
247
|
+
if (typeof value === "string") return value;
|
|
248
|
+
if (value === void 0 || value === null) return "";
|
|
249
|
+
try {
|
|
250
|
+
return JSON.stringify(value) ?? "";
|
|
251
|
+
} catch {
|
|
252
|
+
return String(value);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function firstLineTruncated(value) {
|
|
256
|
+
const text = stringifySafe(value);
|
|
257
|
+
const newline = text.indexOf("\n");
|
|
258
|
+
const firstLine = newline === -1 ? text : text.slice(0, newline);
|
|
259
|
+
if (firstLine.length <= MAX_SUMMARY_COLUMNS) return firstLine;
|
|
260
|
+
return `${firstLine.slice(0, MAX_SUMMARY_COLUMNS - 1)}\u2026`;
|
|
261
|
+
}
|
|
262
|
+
function hasToolArgs(input) {
|
|
263
|
+
if (input === void 0 || input === null) return false;
|
|
264
|
+
if (typeof input !== "object" || Array.isArray(input)) return true;
|
|
265
|
+
return Object.keys(input).length > 0;
|
|
266
|
+
}
|
|
267
|
+
function summarizeToolInput(input) {
|
|
268
|
+
return hasToolArgs(input) ? firstLineTruncated(input) : "";
|
|
269
|
+
}
|
|
270
|
+
function summarizeToolOutput(output) {
|
|
271
|
+
return firstLineTruncated(output);
|
|
272
|
+
}
|
|
273
|
+
|
|
244
274
|
// src/runtime/RuntimeBase.ts
|
|
245
275
|
var RuntimeBase = class {
|
|
246
276
|
};
|
|
@@ -305,16 +335,26 @@ var KIND_TO_TOOLS = {
|
|
|
305
335
|
search: ["Grep", "Glob"],
|
|
306
336
|
fetch: ["WebFetch"]
|
|
307
337
|
};
|
|
338
|
+
function matchesTool(pattern, name) {
|
|
339
|
+
if (!pattern.includes("*")) return pattern === name;
|
|
340
|
+
const source = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
341
|
+
return new RegExp(`^${source}$`).test(name);
|
|
342
|
+
}
|
|
308
343
|
function toolPermitted(params, tools) {
|
|
309
|
-
const allowed =
|
|
344
|
+
const allowed = tools.map((tool) => tool.toLowerCase());
|
|
310
345
|
const name = params.toolCall.name ?? void 0;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
346
|
+
if (name !== void 0) {
|
|
347
|
+
const candidate = name.toLowerCase();
|
|
348
|
+
return allowed.some((pattern) => matchesTool(pattern, candidate));
|
|
349
|
+
}
|
|
350
|
+
return (KIND_TO_TOOLS[params.toolCall.kind ?? ""] ?? []).some((tool) => {
|
|
351
|
+
const candidate = tool.toLowerCase();
|
|
352
|
+
return allowed.some((pattern) => matchesTool(pattern, candidate));
|
|
353
|
+
});
|
|
314
354
|
}
|
|
315
355
|
function decidePermission(params, tools, sessionId) {
|
|
316
356
|
if (sessionId !== void 0 && params.sessionId !== sessionId) {
|
|
317
|
-
return { response: { outcome: { outcome: "cancelled" } } };
|
|
357
|
+
return { response: { outcome: { outcome: "cancelled" } }, approved: false };
|
|
318
358
|
}
|
|
319
359
|
const name = params.toolCall.name ?? void 0;
|
|
320
360
|
const kind = params.toolCall.kind ?? void 0;
|
|
@@ -323,9 +363,10 @@ function decidePermission(params, tools, sessionId) {
|
|
|
323
363
|
(o) => approved ? o.kind === "allow_once" : o.kind === "reject_once"
|
|
324
364
|
);
|
|
325
365
|
const response = option ? { outcome: { outcome: "selected", optionId: option.optionId } } : { outcome: { outcome: "cancelled" } };
|
|
326
|
-
if (approved) return { response };
|
|
366
|
+
if (approved) return { response, approved };
|
|
327
367
|
return {
|
|
328
368
|
response,
|
|
369
|
+
approved,
|
|
329
370
|
rejected: {
|
|
330
371
|
toolCallId: params.toolCall.toolCallId,
|
|
331
372
|
name,
|
|
@@ -506,6 +547,8 @@ var AcpRuntime = class extends RuntimeBase {
|
|
|
506
547
|
let closeResolve;
|
|
507
548
|
let closeReject;
|
|
508
549
|
let connection;
|
|
550
|
+
const argsSurfaced = /* @__PURE__ */ new Set();
|
|
551
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
509
552
|
const killTree = (signal) => {
|
|
510
553
|
if (child.pid !== void 0) {
|
|
511
554
|
try {
|
|
@@ -640,15 +683,23 @@ var AcpRuntime = class extends RuntimeBase {
|
|
|
640
683
|
}
|
|
641
684
|
break;
|
|
642
685
|
}
|
|
643
|
-
case "tool_call":
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
input: update.rawInput
|
|
649
|
-
});
|
|
686
|
+
case "tool_call": {
|
|
687
|
+
const name = update.name ?? update.title;
|
|
688
|
+
toolNames.set(update.toolCallId, name);
|
|
689
|
+
if (hasToolArgs(update.rawInput)) argsSurfaced.add(update.toolCallId);
|
|
690
|
+
emit({ type: "tool_call", id: update.toolCallId, name, input: update.rawInput });
|
|
650
691
|
break;
|
|
651
|
-
|
|
692
|
+
}
|
|
693
|
+
case "tool_call_update": {
|
|
694
|
+
if (!argsSurfaced.has(update.toolCallId) && hasToolArgs(update.rawInput)) {
|
|
695
|
+
argsSurfaced.add(update.toolCallId);
|
|
696
|
+
emit({
|
|
697
|
+
type: "tool_call",
|
|
698
|
+
id: update.toolCallId,
|
|
699
|
+
name: update.name ?? toolNames.get(update.toolCallId) ?? update.title ?? "",
|
|
700
|
+
input: update.rawInput
|
|
701
|
+
});
|
|
702
|
+
}
|
|
652
703
|
emit({
|
|
653
704
|
type: "tool_result",
|
|
654
705
|
id: update.toolCallId,
|
|
@@ -656,6 +707,7 @@ var AcpRuntime = class extends RuntimeBase {
|
|
|
656
707
|
isError: update.status === "failed"
|
|
657
708
|
});
|
|
658
709
|
break;
|
|
710
|
+
}
|
|
659
711
|
case "plan":
|
|
660
712
|
emit({ type: "plan", entries: update.entries.map((entry3) => entry3.content) });
|
|
661
713
|
break;
|
|
@@ -690,22 +742,26 @@ var AcpRuntime = class extends RuntimeBase {
|
|
|
690
742
|
if (state !== "running" || !turn || params.sessionId !== sessionId || context.signal.aborted)
|
|
691
743
|
return cancelled;
|
|
692
744
|
const mode = turn.options.permissionMode ?? "auto";
|
|
693
|
-
const { response, rejected } = decidePermission(params, options.tools, sessionId);
|
|
694
|
-
if (
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
vendor: "acp",
|
|
699
|
-
payload: { type: "permission_rejected", ...rejected }
|
|
700
|
-
});
|
|
701
|
-
return response;
|
|
745
|
+
const { response, approved, rejected } = decidePermission(params, options.tools, sessionId);
|
|
746
|
+
if (approved) {
|
|
747
|
+
if (mode !== "deny") return response;
|
|
748
|
+
warn("Tool permission denied by the configured approval mode.");
|
|
749
|
+
return cancelled;
|
|
702
750
|
}
|
|
703
|
-
|
|
704
|
-
if (
|
|
751
|
+
const askable = mode === "ask" && turn.options.onPermission !== void 0;
|
|
752
|
+
if (!askable) {
|
|
753
|
+
const label = rejected?.name ?? rejected?.kind ?? rejected?.toolCallId ?? "an unnamed tool";
|
|
705
754
|
warn(
|
|
706
|
-
mode === "ask" ?
|
|
755
|
+
mode === "ask" ? `Tool permission cancelled: no approval handler is available (${label}).` : `Tool permission denied by the configured tools policy: ${label}.`
|
|
707
756
|
);
|
|
708
|
-
|
|
757
|
+
if (rejected) {
|
|
758
|
+
emit({
|
|
759
|
+
type: "raw",
|
|
760
|
+
vendor: "acp",
|
|
761
|
+
payload: { type: "permission_rejected", ...rejected }
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return mode === "deny" ? cancelled : response;
|
|
709
765
|
}
|
|
710
766
|
const signal = AbortSignal.any([turn.decisions.signal, context.signal]);
|
|
711
767
|
const offered = new Set(params.options.map((option) => option.optionId));
|
|
@@ -736,7 +792,18 @@ var AcpRuntime = class extends RuntimeBase {
|
|
|
736
792
|
aborted
|
|
737
793
|
]);
|
|
738
794
|
if (signal.aborted || active !== turn || state !== "running") return cancelled;
|
|
739
|
-
|
|
795
|
+
const chosen = params.options.find((option) => option.optionId === optionId);
|
|
796
|
+
if (optionId === void 0 || chosen === void 0 || !offered.has(optionId)) {
|
|
797
|
+
return cancelled;
|
|
798
|
+
}
|
|
799
|
+
if (chosen.kind.startsWith("reject") && rejected) {
|
|
800
|
+
emit({
|
|
801
|
+
type: "raw",
|
|
802
|
+
vendor: "acp",
|
|
803
|
+
payload: { type: "permission_rejected", ...rejected, source: "user" }
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
return { outcome: { outcome: "selected", optionId } };
|
|
740
807
|
} catch {
|
|
741
808
|
return cancelled;
|
|
742
809
|
} finally {
|
|
@@ -2366,31 +2433,6 @@ function withTimeout(factory, ms, label, signal, options) {
|
|
|
2366
2433
|
});
|
|
2367
2434
|
}
|
|
2368
2435
|
|
|
2369
|
-
// src/utils/toolSummary.ts
|
|
2370
|
-
var MAX_SUMMARY_COLUMNS = 120;
|
|
2371
|
-
function stringifySafe(value) {
|
|
2372
|
-
if (typeof value === "string") return value;
|
|
2373
|
-
if (value === void 0 || value === null) return "";
|
|
2374
|
-
try {
|
|
2375
|
-
return JSON.stringify(value) ?? "";
|
|
2376
|
-
} catch {
|
|
2377
|
-
return String(value);
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
function firstLineTruncated(value) {
|
|
2381
|
-
const text = stringifySafe(value);
|
|
2382
|
-
const newline = text.indexOf("\n");
|
|
2383
|
-
const firstLine = newline === -1 ? text : text.slice(0, newline);
|
|
2384
|
-
if (firstLine.length <= MAX_SUMMARY_COLUMNS) return firstLine;
|
|
2385
|
-
return `${firstLine.slice(0, MAX_SUMMARY_COLUMNS - 1)}\u2026`;
|
|
2386
|
-
}
|
|
2387
|
-
function summarizeToolInput(input) {
|
|
2388
|
-
return firstLineTruncated(input);
|
|
2389
|
-
}
|
|
2390
|
-
function summarizeToolOutput(output) {
|
|
2391
|
-
return firstLineTruncated(output);
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
2436
|
// src/orchestrator/AgentRunner.ts
|
|
2395
2437
|
var AgentRunner = class {
|
|
2396
2438
|
constructor(eventBus, contextManager, globalWorkDir, config, sessionManager) {
|
|
@@ -2451,6 +2493,16 @@ ${prompt}`;
|
|
|
2451
2493
|
if (instruction) prompt += `
|
|
2452
2494
|
|
|
2453
2495
|
${instruction}`;
|
|
2496
|
+
const toolRows = /* @__PURE__ */ new Set();
|
|
2497
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
2498
|
+
const printToolRow = (id, name, input) => {
|
|
2499
|
+
if (toolRows.has(id)) return;
|
|
2500
|
+
toolRows.add(id);
|
|
2501
|
+
const summary = summarizeToolInput(input);
|
|
2502
|
+
this.emitOutput(agent.id, `
|
|
2503
|
+
\u203A ${name}${summary ? ` ${summary}` : ""}
|
|
2504
|
+
`, "tool");
|
|
2505
|
+
};
|
|
2454
2506
|
const task = {
|
|
2455
2507
|
agentId: agent.id,
|
|
2456
2508
|
systemPrompt: agent.systemPrompt,
|
|
@@ -2492,17 +2544,13 @@ ${instruction}`;
|
|
|
2492
2544
|
return;
|
|
2493
2545
|
}
|
|
2494
2546
|
if (e.type === "tool_call") {
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
agent.id,
|
|
2498
|
-
`
|
|
2499
|
-
\u203A ${e.name}${summary ? ` ${summary}` : ""}
|
|
2500
|
-
`,
|
|
2501
|
-
"tool"
|
|
2502
|
-
);
|
|
2547
|
+
toolNames.set(e.id, e.name);
|
|
2548
|
+
if (summarizeToolInput(e.input)) printToolRow(e.id, e.name, e.input);
|
|
2503
2549
|
return;
|
|
2504
2550
|
}
|
|
2505
2551
|
if (e.type === "tool_result") {
|
|
2552
|
+
const name = toolNames.get(e.id);
|
|
2553
|
+
if (name !== void 0) printToolRow(e.id, name, void 0);
|
|
2506
2554
|
const summary = summarizeToolOutput(e.output);
|
|
2507
2555
|
this.emitOutput(
|
|
2508
2556
|
agent.id,
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -28,7 +28,14 @@ export type RuntimeEvent = (RuntimeEventBase & {
|
|
|
28
28
|
type: "message";
|
|
29
29
|
role: "agent" | "thought" | "user";
|
|
30
30
|
text: string;
|
|
31
|
-
})
|
|
31
|
+
})
|
|
32
|
+
/**
|
|
33
|
+
* A tool call. The same `id` may arrive more than once: providers announce a
|
|
34
|
+
* call before its arguments have streamed, so the first event can carry an
|
|
35
|
+
* empty `input` and a later one the completed arguments. Consumers should key
|
|
36
|
+
* on `id` and render a row once.
|
|
37
|
+
*/
|
|
38
|
+
| (RuntimeEventBase & {
|
|
32
39
|
type: "tool_call";
|
|
33
40
|
id: string;
|
|
34
41
|
name: string;
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
* truncated line and are deliberately total — a summary is diagnostics and must
|
|
8
8
|
* never be the reason a run fails.
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Whether a tool call was given any arguments.
|
|
12
|
+
*
|
|
13
|
+
* "No arguments yet" reaches us as `{}` (or missing) rather than as
|
|
14
|
+
* `undefined`: ACP announces a call before its input has finished streaming.
|
|
15
|
+
* Both mean the same thing to a reader — there is nothing to show yet — so the
|
|
16
|
+
* two are collapsed here instead of at every call site.
|
|
17
|
+
*/
|
|
18
|
+
export declare function hasToolArgs(input: unknown): boolean;
|
|
10
19
|
/** One-line summary of a tool call's input. `input` is untrusted and may be circular. */
|
|
11
20
|
export declare function summarizeToolInput(input: unknown): string;
|
|
12
21
|
/** One-line summary of a tool result's output, which may be a string or a value. */
|