mono-pilot 0.2.9 → 0.2.12
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 +270 -7
- package/dist/src/agents-paths.js +36 -0
- package/dist/src/brief/blocks.js +83 -0
- package/dist/src/brief/defaults.js +60 -0
- package/dist/src/brief/frontmatter.js +53 -0
- package/dist/src/brief/paths.js +10 -0
- package/dist/src/brief/reflection.js +27 -0
- package/dist/src/cli.js +62 -5
- package/dist/src/cluster/bus.js +102 -0
- package/dist/src/cluster/follower.js +137 -0
- package/dist/src/cluster/init.js +182 -0
- package/dist/src/cluster/leader.js +97 -0
- package/dist/src/cluster/log.js +49 -0
- package/dist/src/cluster/protocol.js +34 -0
- package/dist/src/cluster/services/bus.js +243 -0
- package/dist/src/cluster/services/embedding.js +12 -0
- package/dist/src/cluster/socket.js +86 -0
- package/dist/src/cluster/test-bus.js +175 -0
- package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
- package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
- package/dist/src/cluster_v2/connection.js +159 -0
- package/dist/src/cluster_v2/connection.test.js +55 -0
- package/dist/src/cluster_v2/events.js +102 -0
- package/dist/src/cluster_v2/index.js +2 -0
- package/dist/src/cluster_v2/observability.js +99 -0
- package/dist/src/cluster_v2/observability.test.js +46 -0
- package/dist/src/cluster_v2/rpc.js +389 -0
- package/dist/src/cluster_v2/rpc.test.js +110 -0
- package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
- package/dist/src/cluster_v2/runtime.js +531 -0
- package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
- package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
- package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
- package/dist/src/cluster_v2/services/bus.js +450 -0
- package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
- package/dist/src/cluster_v2/services/discord/collector.js +569 -0
- package/dist/src/cluster_v2/services/discord/index.js +1 -0
- package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
- package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
- package/dist/src/cluster_v2/services/embedding.js +66 -0
- package/dist/src/cluster_v2/services/registry-cache.js +107 -0
- package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
- package/dist/src/cluster_v2/services/registry.js +36 -0
- package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
- package/dist/src/cluster_v2/services/twitter/index.js +1 -0
- package/dist/src/config/digest.js +78 -0
- package/dist/src/config/discord.js +143 -0
- package/dist/src/config/image-gen.js +48 -0
- package/dist/src/config/mono-pilot.js +31 -0
- package/dist/src/config/twitter.js +100 -0
- package/dist/src/extensions/cluster.js +311 -0
- package/dist/src/extensions/commands/build-memory.js +76 -0
- package/dist/src/extensions/commands/digest/backfill.js +779 -0
- package/dist/src/extensions/commands/digest/index.js +1133 -0
- package/dist/src/extensions/commands/image-model.js +214 -0
- package/dist/src/extensions/game/bus-injection.js +47 -0
- package/dist/src/extensions/game/identity.js +83 -0
- package/dist/src/extensions/game/mailbox.js +61 -0
- package/dist/src/extensions/game/system-prompt.js +134 -0
- package/dist/src/extensions/game/tools.js +28 -0
- package/dist/src/extensions/lifecycle.js +337 -0
- package/dist/src/extensions/mode-runtime.js +26 -2
- package/dist/src/extensions/mono-game.js +66 -0
- package/dist/src/extensions/mono-pilot.js +100 -18
- package/dist/src/extensions/nvim.js +47 -0
- package/dist/src/extensions/session-hints.js +60 -35
- package/dist/src/extensions/sftp.js +897 -0
- package/dist/src/extensions/status.js +676 -0
- package/dist/src/extensions/system-events.js +478 -0
- package/dist/src/extensions/system-prompt.js +24 -14
- package/dist/src/extensions/user-message.js +94 -50
- package/dist/src/lsp/client.js +235 -0
- package/dist/src/lsp/index.js +165 -0
- package/dist/src/lsp/runtime.js +67 -0
- package/dist/src/lsp/server.js +242 -0
- package/dist/src/mcp/config.js +112 -0
- package/dist/src/{utils/mcp-client.js → mcp/protocol.js} +1 -100
- package/dist/src/mcp/servers.js +90 -0
- package/dist/src/memory/build-memory.js +103 -0
- package/dist/src/memory/config/defaults.js +55 -0
- package/dist/src/memory/config/loader.js +29 -0
- package/dist/src/memory/config/paths.js +9 -0
- package/dist/src/memory/config/resolve.js +90 -0
- package/dist/src/memory/config/types.js +1 -0
- package/dist/src/memory/embeddings/batch-runner.js +39 -0
- package/dist/src/memory/embeddings/cache.js +47 -0
- package/dist/src/memory/embeddings/chunk-limits.js +26 -0
- package/dist/src/memory/embeddings/input-limits.js +48 -0
- package/dist/src/memory/embeddings/local.js +108 -0
- package/dist/src/memory/embeddings/types.js +1 -0
- package/dist/src/memory/index-manager.js +552 -0
- package/dist/src/memory/indexing/embeddings.js +67 -0
- package/dist/src/memory/indexing/files.js +180 -0
- package/dist/src/memory/indexing/index-file.js +105 -0
- package/dist/src/memory/log.js +38 -0
- package/dist/src/memory/paths.js +15 -0
- package/dist/src/memory/runtime/index.js +299 -0
- package/dist/src/memory/runtime/thread.js +116 -0
- package/dist/src/memory/search/fts.js +57 -0
- package/dist/src/memory/search/hybrid.js +50 -0
- package/dist/src/memory/search/text.js +30 -0
- package/dist/src/memory/search/vector.js +43 -0
- package/dist/src/memory/session/content-hash.js +7 -0
- package/dist/src/memory/session/entry.js +33 -0
- package/dist/src/memory/session/flush-policy.js +34 -0
- package/dist/src/memory/session/hook.js +191 -0
- package/dist/src/memory/session/paths.js +15 -0
- package/dist/src/memory/session/session-reader.js +88 -0
- package/dist/src/memory/session/transcript/content-hash.js +7 -0
- package/dist/src/memory/session/transcript/entry.js +28 -0
- package/dist/src/memory/session/transcript/flush.js +56 -0
- package/dist/src/memory/session/transcript/paths.js +28 -0
- package/dist/src/memory/session/transcript/reader.js +112 -0
- package/dist/src/memory/session/transcript/state.js +31 -0
- package/dist/src/memory/store/schema.js +89 -0
- package/dist/src/memory/store/sqlite.js +89 -0
- package/dist/src/memory/types.js +1 -0
- package/dist/src/memory/warm.js +25 -0
- package/dist/src/rules/discovery.js +41 -0
- package/dist/{tools → src/tools}/README.md +29 -3
- package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
- package/dist/{tools → src/tools}/apply-patch.js +174 -104
- package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
- package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
- package/dist/src/tools/ast-grep.js +357 -0
- package/dist/src/tools/brief-write.js +122 -0
- package/dist/src/tools/bus-send.js +100 -0
- package/dist/{tools → src/tools}/call-mcp-tool.js +40 -124
- package/dist/src/tools/codex-apply-patch-description.md +52 -0
- package/dist/src/tools/codex-apply-patch.js +540 -0
- package/dist/{tools → src/tools}/delete.js +24 -0
- package/dist/src/tools/exit-plan-mode.js +83 -0
- package/dist/{tools → src/tools}/fetch-mcp-resource.js +56 -100
- package/dist/src/tools/generate-image.js +567 -0
- package/dist/{tools → src/tools}/glob.js +55 -1
- package/dist/{tools → src/tools}/list-mcp-resources.js +46 -57
- package/dist/{tools → src/tools}/list-mcp-tools.js +52 -63
- package/dist/src/tools/ls.js +48 -0
- package/dist/src/tools/lsp-diagnostics.js +67 -0
- package/dist/src/tools/lsp-symbols.js +54 -0
- package/dist/src/tools/mailbox.js +85 -0
- package/dist/src/tools/memory-get.js +90 -0
- package/dist/src/tools/memory-search.js +180 -0
- package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
- package/dist/{tools → src/tools}/read-file.js +8 -19
- package/dist/{tools → src/tools}/rg.js +10 -20
- package/dist/{tools → src/tools}/shell.js +19 -42
- package/dist/{tools → src/tools}/subagent.js +255 -6
- package/dist/{tools → src/tools}/switch-mode.js +37 -6
- package/dist/{tools → src/tools}/web-fetch.js +105 -7
- package/dist/{tools → src/tools}/web-search.js +29 -1
- package/package.json +21 -9
- /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
- /package/dist/{tools → src/tools}/rg.test.js +0 -0
- /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
- /package/dist/{tools → src/tools}/semantic-search.js +0 -0
- /package/dist/{tools → src/tools}/shell-description.md +0 -0
- /package/dist/{tools → src/tools}/subagent-description.md +0 -0
|
@@ -61,6 +61,71 @@ function compact(value, maxLength) {
|
|
|
61
61
|
return normalized;
|
|
62
62
|
return `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
63
63
|
}
|
|
64
|
+
function compactTail(value, maxLength) {
|
|
65
|
+
const normalized = value.replace(/\r/g, "");
|
|
66
|
+
if (normalized.length <= maxLength)
|
|
67
|
+
return normalized;
|
|
68
|
+
return `…${normalized.slice(Math.max(0, normalized.length - (maxLength - 1)))}`;
|
|
69
|
+
}
|
|
70
|
+
function safeStringify(value) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.stringify(value);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return String(value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function appendTrace(stats, line) {
|
|
79
|
+
const trimmed = line.trim();
|
|
80
|
+
if (!trimmed)
|
|
81
|
+
return;
|
|
82
|
+
stats.traceLines.push(trimmed);
|
|
83
|
+
const TRACE_MAX_LINES = 240;
|
|
84
|
+
if (stats.traceLines.length > TRACE_MAX_LINES) {
|
|
85
|
+
stats.traceLines.splice(0, stats.traceLines.length - TRACE_MAX_LINES);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function getAssistantTraceLines(message) {
|
|
89
|
+
if (!message || typeof message !== "object")
|
|
90
|
+
return [];
|
|
91
|
+
const record = message;
|
|
92
|
+
if (record.role !== "assistant")
|
|
93
|
+
return [];
|
|
94
|
+
const parts = record.content;
|
|
95
|
+
if (!Array.isArray(parts))
|
|
96
|
+
return [];
|
|
97
|
+
const lines = [];
|
|
98
|
+
for (const part of parts) {
|
|
99
|
+
if (!part || typeof part !== "object")
|
|
100
|
+
continue;
|
|
101
|
+
const partRecord = part;
|
|
102
|
+
if (partRecord.type === "thinking" && typeof partRecord.thinking === "string") {
|
|
103
|
+
lines.push(`[thinking] ${partRecord.thinking}`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (partRecord.type === "toolCall") {
|
|
107
|
+
const name = typeof partRecord.name === "string" ? partRecord.name : "(unknown)";
|
|
108
|
+
const args = safeStringify(partRecord.arguments ?? {});
|
|
109
|
+
lines.push(`[tool_call] ${name} ${args}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (partRecord.type === "text" && typeof partRecord.text === "string") {
|
|
113
|
+
lines.push(`[text] ${partRecord.text}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return lines;
|
|
117
|
+
}
|
|
118
|
+
function getCurrentLine(text) {
|
|
119
|
+
const lines = text.replace(/\r/g, "").split("\n");
|
|
120
|
+
const last = lines[lines.length - 1] ?? "";
|
|
121
|
+
if (last.trim().length > 0)
|
|
122
|
+
return last;
|
|
123
|
+
for (let i = lines.length - 2; i >= 0; i--) {
|
|
124
|
+
if (lines[i].trim().length > 0)
|
|
125
|
+
return lines[i];
|
|
126
|
+
}
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
64
129
|
function parseBooleanLike(value) {
|
|
65
130
|
if (typeof value === "boolean")
|
|
66
131
|
return value;
|
|
@@ -457,10 +522,63 @@ function parseMessageMeta(message) {
|
|
|
457
522
|
errorMessage: typeof record.errorMessage === "string" ? record.errorMessage : undefined,
|
|
458
523
|
};
|
|
459
524
|
}
|
|
525
|
+
function getToolResultText(result) {
|
|
526
|
+
if (!result || typeof result !== "object")
|
|
527
|
+
return undefined;
|
|
528
|
+
const record = result;
|
|
529
|
+
const content = record.content;
|
|
530
|
+
if (!Array.isArray(content))
|
|
531
|
+
return undefined;
|
|
532
|
+
const textParts = [];
|
|
533
|
+
for (const part of content) {
|
|
534
|
+
if (!part || typeof part !== "object")
|
|
535
|
+
continue;
|
|
536
|
+
const partRecord = part;
|
|
537
|
+
if (partRecord.type === "text" && typeof partRecord.text === "string") {
|
|
538
|
+
textParts.push(partRecord.text);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (textParts.length === 0)
|
|
542
|
+
return undefined;
|
|
543
|
+
return textParts.join("\n\n").trim();
|
|
544
|
+
}
|
|
460
545
|
function getProgressPreview(stats) {
|
|
461
|
-
if (stats.lastAssistantText.length > 0)
|
|
462
|
-
|
|
463
|
-
|
|
546
|
+
if (stats.lastAssistantText.length > 0) {
|
|
547
|
+
const line = getCurrentLine(stats.lastAssistantText);
|
|
548
|
+
if (line.length > 0)
|
|
549
|
+
return compactTail(line, 220);
|
|
550
|
+
return compactTail(stats.lastAssistantText.replace(/\s+/g, " ").trim(), 220);
|
|
551
|
+
}
|
|
552
|
+
if (stats.traceLines.length > 0) {
|
|
553
|
+
return compactTail(stats.traceLines[stats.traceLines.length - 1], 220);
|
|
554
|
+
}
|
|
555
|
+
return "running...";
|
|
556
|
+
}
|
|
557
|
+
function getTraceOutput(stats) {
|
|
558
|
+
if (stats.traceLines.length === 0)
|
|
559
|
+
return "";
|
|
560
|
+
return stats.traceLines.join("\n");
|
|
561
|
+
}
|
|
562
|
+
function getLiveOutput(stats) {
|
|
563
|
+
const trace = getTraceOutput(stats);
|
|
564
|
+
if (trace.length > 0) {
|
|
565
|
+
const MAX_TRACE_CHARS = 12000;
|
|
566
|
+
if (trace.length <= MAX_TRACE_CHARS)
|
|
567
|
+
return trace;
|
|
568
|
+
return `...\n${trace.slice(trace.length - MAX_TRACE_CHARS)}`;
|
|
569
|
+
}
|
|
570
|
+
const raw = stats.lastAssistantText.replace(/\r/g, "").trim();
|
|
571
|
+
if (!raw)
|
|
572
|
+
return "running...";
|
|
573
|
+
const MAX_CHARS = 8000;
|
|
574
|
+
if (raw.length <= MAX_CHARS)
|
|
575
|
+
return raw;
|
|
576
|
+
return `...\n${raw.slice(raw.length - MAX_CHARS)}`;
|
|
577
|
+
}
|
|
578
|
+
const RUNNING_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
579
|
+
function getRunningSpinner(seed) {
|
|
580
|
+
const index = Math.abs(Math.floor(seed)) % RUNNING_SPINNER_FRAMES.length;
|
|
581
|
+
return RUNNING_SPINNER_FRAMES[index] ?? RUNNING_SPINNER_FRAMES[0];
|
|
464
582
|
}
|
|
465
583
|
async function runSubagentForeground(piCliPath, args, cwd, signal, onAssistantUpdate) {
|
|
466
584
|
return new Promise((resolveRun, rejectRun) => {
|
|
@@ -481,6 +599,17 @@ async function runSubagentForeground(piCliPath, args, cwd, signal, onAssistantUp
|
|
|
481
599
|
parsedEvents: 0,
|
|
482
600
|
assistantMessages: 0,
|
|
483
601
|
lastAssistantText: "",
|
|
602
|
+
traceLines: [],
|
|
603
|
+
};
|
|
604
|
+
// Throttle streaming updates to avoid excessive re-renders
|
|
605
|
+
let lastUpdateTime = 0;
|
|
606
|
+
const THROTTLE_MS = 150;
|
|
607
|
+
const throttledUpdate = () => {
|
|
608
|
+
const now = Date.now();
|
|
609
|
+
if (now - lastUpdateTime >= THROTTLE_MS) {
|
|
610
|
+
lastUpdateTime = now;
|
|
611
|
+
onAssistantUpdate?.({ ...stats });
|
|
612
|
+
}
|
|
484
613
|
};
|
|
485
614
|
const parseLine = (line) => {
|
|
486
615
|
const trimmed = line.trim();
|
|
@@ -494,7 +623,35 @@ async function runSubagentForeground(piCliPath, args, cwd, signal, onAssistantUp
|
|
|
494
623
|
return;
|
|
495
624
|
}
|
|
496
625
|
stats.parsedEvents++;
|
|
497
|
-
|
|
626
|
+
// Streaming: capture partial assistant text for real-time preview
|
|
627
|
+
if (event.type === "message_update" && event.message) {
|
|
628
|
+
const text = getAssistantTextFromMessage(event.message);
|
|
629
|
+
if (text) {
|
|
630
|
+
stats.lastAssistantText = text;
|
|
631
|
+
throttledUpdate();
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
else if (event.type === "tool_execution_start") {
|
|
635
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
636
|
+
stats.lastAssistantText = `[tool:${toolName}] running`;
|
|
637
|
+
throttledUpdate();
|
|
638
|
+
}
|
|
639
|
+
else if (event.type === "tool_execution_update") {
|
|
640
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
641
|
+
const partialText = getToolResultText(event.partialResult);
|
|
642
|
+
stats.lastAssistantText = partialText ? `[tool:${toolName}] ${partialText}` : `[tool:${toolName}] running`;
|
|
643
|
+
throttledUpdate();
|
|
644
|
+
}
|
|
645
|
+
else if (event.type === "tool_execution_end") {
|
|
646
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
647
|
+
const resultText = getToolResultText(event.result);
|
|
648
|
+
stats.lastAssistantText = resultText ? `[tool:${toolName}] ${resultText}` : `[tool:${toolName}] done`;
|
|
649
|
+
throttledUpdate();
|
|
650
|
+
}
|
|
651
|
+
else if (event.type === "message_end" && event.message) {
|
|
652
|
+
for (const traceLine of getAssistantTraceLines(event.message)) {
|
|
653
|
+
appendTrace(stats, traceLine);
|
|
654
|
+
}
|
|
498
655
|
const text = getAssistantTextFromMessage(event.message);
|
|
499
656
|
const meta = parseMessageMeta(event.message);
|
|
500
657
|
if (meta.stopReason)
|
|
@@ -750,7 +907,10 @@ async function executeTask(task, index, options) {
|
|
|
750
907
|
selected_model: selectedModel?.modelId,
|
|
751
908
|
parsed_events: 0,
|
|
752
909
|
assistant_messages: 0,
|
|
753
|
-
preview: "
|
|
910
|
+
preview: "running...",
|
|
911
|
+
live_output: "running...",
|
|
912
|
+
trace_output: "",
|
|
913
|
+
spinner_tick: 0,
|
|
754
914
|
};
|
|
755
915
|
options.onProgress?.({ ...detail });
|
|
756
916
|
if (isBackground) {
|
|
@@ -788,12 +948,23 @@ async function executeTask(task, index, options) {
|
|
|
788
948
|
output_path: session.outputPath,
|
|
789
949
|
started_at: new Date().toISOString(),
|
|
790
950
|
});
|
|
951
|
+
let spinnerTimer = null;
|
|
791
952
|
try {
|
|
953
|
+
const SPINNER_HEARTBEAT_MS = 80;
|
|
954
|
+
spinnerTimer = setInterval(() => {
|
|
955
|
+
if (detail.status !== "running")
|
|
956
|
+
return;
|
|
957
|
+
detail.spinner_tick = (detail.spinner_tick ?? 0) + 1;
|
|
958
|
+
options.onProgress?.({ ...detail });
|
|
959
|
+
}, SPINNER_HEARTBEAT_MS);
|
|
960
|
+
spinnerTimer.unref?.();
|
|
792
961
|
const runResult = await runSubagentForeground(options.piCliPath, args, options.ctx.cwd, options.signal, (stats) => {
|
|
793
962
|
detail.status = "running";
|
|
794
963
|
detail.parsed_events = stats.parsedEvents;
|
|
795
964
|
detail.assistant_messages = stats.assistantMessages;
|
|
796
965
|
detail.preview = getProgressPreview(stats);
|
|
966
|
+
detail.live_output = getLiveOutput(stats);
|
|
967
|
+
detail.trace_output = getTraceOutput(stats);
|
|
797
968
|
options.onProgress?.({ ...detail });
|
|
798
969
|
});
|
|
799
970
|
const finalOutput = runResult.stats.lastAssistantText.trim() || runResult.stdout.trim();
|
|
@@ -802,6 +973,7 @@ async function executeTask(task, index, options) {
|
|
|
802
973
|
detail.parsed_events = runResult.stats.parsedEvents;
|
|
803
974
|
detail.assistant_messages = runResult.stats.assistantMessages;
|
|
804
975
|
detail.stderr = runResult.stderr.trim() || undefined;
|
|
976
|
+
detail.trace_output = getTraceOutput(runResult.stats);
|
|
805
977
|
if (isError) {
|
|
806
978
|
const errorText = runResult.stats.errorMessage || runResult.stderr.trim() || finalOutput || "Subagent execution failed.";
|
|
807
979
|
detail.status = "failed";
|
|
@@ -822,6 +994,7 @@ async function executeTask(task, index, options) {
|
|
|
822
994
|
detail.status = "completed";
|
|
823
995
|
detail.final_output = finalOutput || "(no output)";
|
|
824
996
|
detail.preview = compact(detail.final_output, 220);
|
|
997
|
+
detail.live_output = undefined;
|
|
825
998
|
writeSubagentState(session.statePath, {
|
|
826
999
|
id: session.id,
|
|
827
1000
|
status: "completed",
|
|
@@ -840,6 +1013,7 @@ async function executeTask(task, index, options) {
|
|
|
840
1013
|
detail.exit_code = 1;
|
|
841
1014
|
detail.stderr = message;
|
|
842
1015
|
detail.preview = compact(message, 220);
|
|
1016
|
+
detail.live_output = undefined;
|
|
843
1017
|
detail.final_output = message;
|
|
844
1018
|
writeSubagentState(session.statePath, {
|
|
845
1019
|
id: session.id,
|
|
@@ -853,6 +1027,11 @@ async function executeTask(task, index, options) {
|
|
|
853
1027
|
});
|
|
854
1028
|
return detail;
|
|
855
1029
|
}
|
|
1030
|
+
finally {
|
|
1031
|
+
if (spinnerTimer) {
|
|
1032
|
+
clearInterval(spinnerTimer);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
856
1035
|
}
|
|
857
1036
|
export default function (pi) {
|
|
858
1037
|
pi.registerTool({
|
|
@@ -895,6 +1074,76 @@ export default function (pi) {
|
|
|
895
1074
|
}
|
|
896
1075
|
return new Text(text, 0, 0);
|
|
897
1076
|
},
|
|
1077
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
1078
|
+
const details = result.details;
|
|
1079
|
+
if (!details) {
|
|
1080
|
+
const raw = result.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
1081
|
+
return new Text(raw || "(no output)", 0, 0);
|
|
1082
|
+
}
|
|
1083
|
+
const isParallel = details.mode === "parallel";
|
|
1084
|
+
const hasFailed = details.failed_tasks > 0;
|
|
1085
|
+
// Streaming: collapsed shows one-line preview, expanded shows live multiline output.
|
|
1086
|
+
if (isPartial) {
|
|
1087
|
+
if (expanded) {
|
|
1088
|
+
const lines = [];
|
|
1089
|
+
for (const task of details.results) {
|
|
1090
|
+
if (isParallel) {
|
|
1091
|
+
const spinner = getRunningSpinner((task.spinner_tick ?? 0) + task.task_index);
|
|
1092
|
+
lines.push(theme.fg("warning", `${spinner} [${task.task_index + 1}] ${task.description}`));
|
|
1093
|
+
}
|
|
1094
|
+
const live = task.trace_output ?? task.live_output ?? task.preview ?? "running...";
|
|
1095
|
+
lines.push(live);
|
|
1096
|
+
if (isParallel && task.task_index < details.results.length - 1)
|
|
1097
|
+
lines.push("");
|
|
1098
|
+
}
|
|
1099
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
1100
|
+
}
|
|
1101
|
+
if (isParallel) {
|
|
1102
|
+
const running = details.results.filter((r) => r.status === "running");
|
|
1103
|
+
const done = details.completed_tasks;
|
|
1104
|
+
const label = `${done}/${details.total_tasks}`;
|
|
1105
|
+
const preview = running.length > 0 ? compact(running[0].preview ?? "", 90) : "...";
|
|
1106
|
+
const seed = running.length > 0 ? (running[0]?.spinner_tick ?? 0) : 0;
|
|
1107
|
+
const spinner = getRunningSpinner(seed);
|
|
1108
|
+
return new Text(theme.fg("warning", `${spinner} ${label} `) + theme.fg("dim", preview), 0, 0);
|
|
1109
|
+
}
|
|
1110
|
+
const task = details.results[0];
|
|
1111
|
+
const preview = task?.preview ?? "running...";
|
|
1112
|
+
const spinner = getRunningSpinner(task?.spinner_tick ?? 0);
|
|
1113
|
+
return new Text(theme.fg("warning", `${spinner} `) + theme.fg("dim", compact(preview, 110)), 0, 0);
|
|
1114
|
+
}
|
|
1115
|
+
// Completed, collapsed: compact summary
|
|
1116
|
+
if (!expanded) {
|
|
1117
|
+
if (isParallel) {
|
|
1118
|
+
const icon = hasFailed ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
1119
|
+
const summary = `${details.completed_tasks}/${details.total_tasks} completed`;
|
|
1120
|
+
const failText = hasFailed ? `, ${details.failed_tasks} failed` : "";
|
|
1121
|
+
return new Text(`${icon} ${summary}${failText}`, 0, 0);
|
|
1122
|
+
}
|
|
1123
|
+
const task = details.results[0];
|
|
1124
|
+
const icon = task?.status === "failed" ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
1125
|
+
const preview = compact(task?.final_output ?? task?.preview ?? "(no output)", 110);
|
|
1126
|
+
return new Text(`${icon} ${preview}`, 0, 0);
|
|
1127
|
+
}
|
|
1128
|
+
// Expanded: full output
|
|
1129
|
+
const lines = [];
|
|
1130
|
+
for (const task of details.results) {
|
|
1131
|
+
if (isParallel) {
|
|
1132
|
+
const icon = task.status === "failed" ? "✗" : "✓";
|
|
1133
|
+
lines.push(theme.fg(task.status === "failed" ? "error" : "success", `${icon} [${task.task_index + 1}] ${task.description}`));
|
|
1134
|
+
}
|
|
1135
|
+
if (task.trace_output && task.trace_output.trim().length > 0) {
|
|
1136
|
+
lines.push(task.trace_output);
|
|
1137
|
+
if (task.final_output && task.final_output.trim().length > 0)
|
|
1138
|
+
lines.push("");
|
|
1139
|
+
}
|
|
1140
|
+
const output = task.final_output ?? task.preview ?? "(no output)";
|
|
1141
|
+
lines.push(output);
|
|
1142
|
+
if (isParallel && task.task_index < details.results.length - 1)
|
|
1143
|
+
lines.push("");
|
|
1144
|
+
}
|
|
1145
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
1146
|
+
},
|
|
898
1147
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
899
1148
|
try {
|
|
900
1149
|
const piCliPath = resolvePiCliPath();
|
|
@@ -963,7 +1212,7 @@ export default function (pi) {
|
|
|
963
1212
|
onProgress: (detail) => {
|
|
964
1213
|
lastProgress = detail;
|
|
965
1214
|
onUpdate?.({
|
|
966
|
-
content: [{ type: "text", text: detail.preview ?? "
|
|
1215
|
+
content: [{ type: "text", text: detail.preview ?? "running..." }],
|
|
967
1216
|
details: buildSubagentDetails("single", [detail]),
|
|
968
1217
|
});
|
|
969
1218
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
-
import {
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { createModeStateData, deriveInitialModeState, modeRuntimeStore, MODE_STATE_ENTRY_TYPE, parseModeStateEntry, } from "../extensions/mode-runtime.js";
|
|
4
6
|
const MODE_STATUS_KEY = "mono-pilot-mode";
|
|
5
7
|
const DESCRIPTION = `Switch the interaction mode to better match the current task. Each mode is optimized for a specific type of work.
|
|
6
8
|
|
|
@@ -60,7 +62,20 @@ const switchModeSchema = Type.Object({
|
|
|
60
62
|
explanation: Type.Optional(Type.String({
|
|
61
63
|
description: "Optional explanation for why the mode switch is requested. This helps the user understand why you're switching modes.",
|
|
62
64
|
})),
|
|
65
|
+
plan_file: Type.Optional(Type.String({
|
|
66
|
+
description: "Optional plan file path for Plan mode. If relative, it is resolved against the current workspace.",
|
|
67
|
+
})),
|
|
63
68
|
});
|
|
69
|
+
function normalizePlanFilePath(value) {
|
|
70
|
+
if (typeof value !== "string") {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (trimmed.length === 0) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return isAbsolute(trimmed) ? trimmed : resolve(process.cwd(), trimmed);
|
|
78
|
+
}
|
|
64
79
|
function sanitizeStatusText(text) {
|
|
65
80
|
return text
|
|
66
81
|
.replace(/[\r\n\t]/g, " ")
|
|
@@ -261,8 +276,8 @@ function updateModeStatus(ctx) {
|
|
|
261
276
|
function persistModeState(pi) {
|
|
262
277
|
pi.appendEntry(MODE_STATE_ENTRY_TYPE, createModeStateData(modeRuntimeStore.getSnapshot()));
|
|
263
278
|
}
|
|
264
|
-
function setMode(pi, nextMode, ctx) {
|
|
265
|
-
const { changed } = modeRuntimeStore.setMode(nextMode);
|
|
279
|
+
function setMode(pi, nextMode, ctx, options) {
|
|
280
|
+
const { changed } = modeRuntimeStore.setMode(nextMode, options);
|
|
266
281
|
if (changed) {
|
|
267
282
|
persistModeState(pi);
|
|
268
283
|
}
|
|
@@ -318,16 +333,32 @@ export default function switchModeExtension(pi) {
|
|
|
318
333
|
description: DESCRIPTION,
|
|
319
334
|
parameters: switchModeSchema,
|
|
320
335
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
321
|
-
const
|
|
336
|
+
const before = modeRuntimeStore.getSnapshot();
|
|
337
|
+
const planFilePath = normalizePlanFilePath(params.plan_file);
|
|
338
|
+
const { changed } = setMode(pi, "plan", ctx, { planFilePath });
|
|
339
|
+
const after = modeRuntimeStore.getSnapshot();
|
|
322
340
|
const explanation = params.explanation?.trim();
|
|
323
341
|
const details = {
|
|
324
342
|
active_mode: "plan",
|
|
325
343
|
explanation: explanation || undefined,
|
|
344
|
+
plan_file: after.planFilePath,
|
|
326
345
|
};
|
|
327
346
|
const explanationSuffix = explanation ? ` Reason: ${explanation}` : "";
|
|
328
|
-
const
|
|
347
|
+
const modeChanged = before.activeMode !== after.activeMode;
|
|
348
|
+
const planFileChanged = before.planFilePath !== after.planFilePath;
|
|
349
|
+
let prefix;
|
|
350
|
+
if (modeChanged) {
|
|
351
|
+
prefix = "Switched to Plan mode.";
|
|
352
|
+
}
|
|
353
|
+
else if (planFileChanged) {
|
|
354
|
+
prefix = "Plan mode is already active. Updated plan file.";
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
prefix = changed ? "Switched to Plan mode." : "Plan mode is already active.";
|
|
358
|
+
}
|
|
359
|
+
const planFileSuffix = after.planFilePath ? ` Plan file: ${after.planFilePath}` : "";
|
|
329
360
|
return {
|
|
330
|
-
content: [{ type: "text", text: `${prefix}${explanationSuffix}` }],
|
|
361
|
+
content: [{ type: "text", text: `${prefix}${planFileSuffix}${explanationSuffix}` }],
|
|
331
362
|
details,
|
|
332
363
|
};
|
|
333
364
|
},
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { lookup } from "node:dns/promises";
|
|
2
2
|
import { isIP } from "node:net";
|
|
3
|
-
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateTail, } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, keyHint, truncateTail, } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
4
5
|
import { Type } from "@sinclair/typebox";
|
|
5
6
|
const DESCRIPTION = `Fetch content from a specified URL and return its contents in a readable markdown format. Use this tool when you need to retrieve and analyze web content.
|
|
6
7
|
|
|
7
8
|
- The URL must be a fully-formed, valid URL.
|
|
8
9
|
- This tool is read-only and will not work for requests intended to have side effects.
|
|
9
10
|
- This fetch tries to return live results but may return previously cached content.
|
|
10
|
-
- This fetch runs
|
|
11
|
+
- This fetch runs in the current runtime network context, and blocks localhost/private IP targets for safety.
|
|
11
12
|
- Authentication is not supported, and an error will be returned if the URL requires authentication.
|
|
12
13
|
- If the URL is returning a non-200 status code, e.g. 404, the tool will not return the content and will instead return an error message.
|
|
13
|
-
- The tool prefers
|
|
14
|
+
- The tool prefers readable text output and converts HTML responses into markdown-like text when needed.
|
|
14
15
|
- If present, metadata like \`x-markdown-tokens\` and \`content-signal\` may be returned in tool details.`;
|
|
15
16
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
16
17
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
17
|
-
const
|
|
18
|
+
const DEFAULT_BROWSER_ACCEPT = "text/markdown, text/html;q=0.95, application/xhtml+xml;q=0.9, application/xml;q=0.85, image/avif,image/webp,*/*;q=0.8";
|
|
19
|
+
const DEFAULT_BROWSER_ACCEPT_LANGUAGE = "en-US,en;q=0.9";
|
|
20
|
+
const DEFAULT_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36";
|
|
21
|
+
const WECHAT_MOBILE_ACCEPT = "text/markdown, text/html;q=0.95, application/xhtml+xml;q=0.9, application/xml;q=0.85,*/*;q=0.8";
|
|
22
|
+
const WECHAT_MOBILE_ACCEPT_LANGUAGE = "zh-CN,zh;q=0.9";
|
|
23
|
+
const WECHAT_MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.20(0x17001422) NetType/WIFI Language/zh_CN";
|
|
18
24
|
const webFetchSchema = Type.Object({
|
|
19
25
|
url: Type.String({
|
|
20
26
|
description: "The URL to fetch. The content will be converted to a readable markdown format.",
|
|
@@ -190,12 +196,29 @@ function normalizeFetchedBody(rawText, contentType) {
|
|
|
190
196
|
body: rawText.trim(),
|
|
191
197
|
};
|
|
192
198
|
}
|
|
199
|
+
function getFetchProfile(url) {
|
|
200
|
+
if (url.hostname.toLowerCase() === "mp.weixin.qq.com") {
|
|
201
|
+
return {
|
|
202
|
+
name: "wechat_mobile",
|
|
203
|
+
accept: WECHAT_MOBILE_ACCEPT,
|
|
204
|
+
acceptLanguage: WECHAT_MOBILE_ACCEPT_LANGUAGE,
|
|
205
|
+
userAgent: WECHAT_MOBILE_USER_AGENT,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
name: "browser_default",
|
|
210
|
+
accept: DEFAULT_BROWSER_ACCEPT,
|
|
211
|
+
acceptLanguage: DEFAULT_BROWSER_ACCEPT_LANGUAGE,
|
|
212
|
+
userAgent: DEFAULT_BROWSER_USER_AGENT,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
193
215
|
function formatFetchedOutput(entry) {
|
|
194
216
|
const lines = [];
|
|
195
217
|
lines.push(`Source URL: ${entry.finalUrl}`);
|
|
196
218
|
if (entry.url !== entry.finalUrl) {
|
|
197
219
|
lines.push(`Requested URL: ${entry.url}`);
|
|
198
220
|
}
|
|
221
|
+
lines.push(`Request profile: ${entry.requestProfile}`);
|
|
199
222
|
lines.push(`Fetched at: ${entry.fetchedAtIso}`);
|
|
200
223
|
lines.push(`Content-Type: ${entry.contentType || "unknown"}`);
|
|
201
224
|
lines.push(`Markdown negotiated: ${entry.markdownNegotiated ? "yes" : "no"}`);
|
|
@@ -209,6 +232,27 @@ function formatFetchedOutput(entry) {
|
|
|
209
232
|
lines.push(entry.markdownContent.length > 0 ? entry.markdownContent : "(No readable text content found.)");
|
|
210
233
|
return lines.join("\n");
|
|
211
234
|
}
|
|
235
|
+
function isBlockedInterstital(content) {
|
|
236
|
+
const finalUrlLower = content.finalUrl.toLowerCase();
|
|
237
|
+
const textLower = content.rawText.toLowerCase();
|
|
238
|
+
const looksLikeWechatCaptcha = finalUrlLower.includes("wappoc_appmsgcaptcha") ||
|
|
239
|
+
textLower.includes("wappoc_appmsgcaptcha") ||
|
|
240
|
+
(content.contentType.includes("text/html") && textLower.includes("环境异常") && textLower.includes("去验证"));
|
|
241
|
+
if (looksLikeWechatCaptcha) {
|
|
242
|
+
return {
|
|
243
|
+
blockedBy: "wechat_risk_control",
|
|
244
|
+
message: "WebFetch blocked by WeChat risk-control/captcha interstitial. Try local fetch with WeChat mobile UA or provide article text manually.",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
function isBlockedCachedEntry(entry) {
|
|
250
|
+
return isBlockedInterstital({
|
|
251
|
+
finalUrl: entry.finalUrl,
|
|
252
|
+
contentType: entry.contentType,
|
|
253
|
+
rawText: entry.markdownContent,
|
|
254
|
+
}) !== undefined;
|
|
255
|
+
}
|
|
212
256
|
function getCachedEntry(url) {
|
|
213
257
|
const cached = responseCache.get(url);
|
|
214
258
|
if (!cached)
|
|
@@ -217,13 +261,18 @@ function getCachedEntry(url) {
|
|
|
217
261
|
responseCache.delete(url);
|
|
218
262
|
return undefined;
|
|
219
263
|
}
|
|
264
|
+
if (isBlockedCachedEntry(cached)) {
|
|
265
|
+
responseCache.delete(url);
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
220
268
|
return cached;
|
|
221
269
|
}
|
|
222
|
-
function formatErrorResult(url, error) {
|
|
270
|
+
function formatErrorResult(url, error, extraDetails) {
|
|
223
271
|
return {
|
|
224
272
|
content: [{ type: "text", text: error }],
|
|
225
273
|
details: {
|
|
226
274
|
url,
|
|
275
|
+
...extraDetails,
|
|
227
276
|
error,
|
|
228
277
|
},
|
|
229
278
|
};
|
|
@@ -235,6 +284,32 @@ export default function webFetchExtension(pi) {
|
|
|
235
284
|
label: "WebFetch",
|
|
236
285
|
description: DESCRIPTION,
|
|
237
286
|
parameters: webFetchSchema,
|
|
287
|
+
renderCall(args, theme) {
|
|
288
|
+
const input = args;
|
|
289
|
+
const url = typeof input.url === "string" && input.url.trim().length > 0 ? input.url.trim() : "(missing url)";
|
|
290
|
+
const displayUrl = url.length > 120 ? `${url.slice(0, 119)}…` : url;
|
|
291
|
+
let text = theme.fg("toolTitle", theme.bold("WebFetch"));
|
|
292
|
+
text += ` ${theme.fg("toolOutput", displayUrl)}`;
|
|
293
|
+
return new Text(text, 0, 0);
|
|
294
|
+
},
|
|
295
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
296
|
+
if (isPartial) {
|
|
297
|
+
return new Text(theme.fg("muted", "Fetching URL..."), 0, 0);
|
|
298
|
+
}
|
|
299
|
+
const textBlock = result.content.find((entry) => entry.type === "text" && typeof entry.text === "string");
|
|
300
|
+
if (!textBlock) {
|
|
301
|
+
return new Text(theme.fg("error", "No text result returned."), 0, 0);
|
|
302
|
+
}
|
|
303
|
+
const fullText = textBlock.text;
|
|
304
|
+
const lineCount = fullText.split("\n").length;
|
|
305
|
+
if (!expanded) {
|
|
306
|
+
const summary = `${lineCount} lines (click or ${keyHint("expandTools", "to expand")})`;
|
|
307
|
+
return new Text(theme.fg("muted", summary), 0, 0);
|
|
308
|
+
}
|
|
309
|
+
let text = fullText.split("\n").map((line) => theme.fg("toolOutput", line)).join("\n");
|
|
310
|
+
text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
|
|
311
|
+
return new Text(text, 0, 0);
|
|
312
|
+
},
|
|
238
313
|
async execute(_toolCallId, params, signal) {
|
|
239
314
|
let parsedUrl;
|
|
240
315
|
try {
|
|
@@ -258,6 +333,7 @@ export default function webFetchExtension(pi) {
|
|
|
258
333
|
details: {
|
|
259
334
|
url: normalizedUrl,
|
|
260
335
|
final_url: cached.finalUrl,
|
|
336
|
+
request_profile: cached.requestProfile,
|
|
261
337
|
status: cached.status,
|
|
262
338
|
content_type: cached.contentType,
|
|
263
339
|
markdown_negotiated: cached.markdownNegotiated,
|
|
@@ -274,14 +350,16 @@ export default function webFetchExtension(pi) {
|
|
|
274
350
|
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
275
351
|
const onAbort = () => controller.abort();
|
|
276
352
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
353
|
+
const fetchProfile = getFetchProfile(parsedUrl);
|
|
277
354
|
try {
|
|
278
355
|
const response = await fetch(normalizedUrl, {
|
|
279
356
|
method: "GET",
|
|
280
357
|
redirect: "follow",
|
|
281
358
|
signal: controller.signal,
|
|
282
359
|
headers: {
|
|
283
|
-
Accept:
|
|
284
|
-
"
|
|
360
|
+
Accept: fetchProfile.accept,
|
|
361
|
+
"Accept-Language": fetchProfile.acceptLanguage,
|
|
362
|
+
"User-Agent": fetchProfile.userAgent,
|
|
285
363
|
},
|
|
286
364
|
});
|
|
287
365
|
const finalUrl = response.url || normalizedUrl;
|
|
@@ -300,6 +378,24 @@ export default function webFetchExtension(pi) {
|
|
|
300
378
|
const markdownTokens = parsePositiveInt(response.headers.get("x-markdown-tokens"));
|
|
301
379
|
const contentSignal = response.headers.get("content-signal") ?? undefined;
|
|
302
380
|
const rawText = await response.text();
|
|
381
|
+
const blocked = isBlockedInterstital({
|
|
382
|
+
finalUrl: finalParsed.toString(),
|
|
383
|
+
contentType,
|
|
384
|
+
rawText,
|
|
385
|
+
});
|
|
386
|
+
if (blocked) {
|
|
387
|
+
return formatErrorResult(normalizedUrl, blocked.message, {
|
|
388
|
+
final_url: finalParsed.toString(),
|
|
389
|
+
request_profile: fetchProfile.name,
|
|
390
|
+
status: response.status,
|
|
391
|
+
content_type: contentType,
|
|
392
|
+
markdown_negotiated: markdownNegotiated,
|
|
393
|
+
markdown_tokens: markdownTokens,
|
|
394
|
+
content_signal: contentSignal,
|
|
395
|
+
blocked_by: blocked.blockedBy,
|
|
396
|
+
bytes_received: Buffer.byteLength(rawText, "utf-8"),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
303
399
|
const normalizedBody = normalizeFetchedBody(rawText, contentType);
|
|
304
400
|
const markdownBody = normalizedBody.title
|
|
305
401
|
? `# ${normalizedBody.title}\n\n${normalizedBody.body}`.trim()
|
|
@@ -308,6 +404,7 @@ export default function webFetchExtension(pi) {
|
|
|
308
404
|
const cacheEntry = {
|
|
309
405
|
url: normalizedUrl,
|
|
310
406
|
finalUrl: finalParsed.toString(),
|
|
407
|
+
requestProfile: fetchProfile.name,
|
|
311
408
|
status: response.status,
|
|
312
409
|
contentType,
|
|
313
410
|
markdownNegotiated,
|
|
@@ -329,6 +426,7 @@ export default function webFetchExtension(pi) {
|
|
|
329
426
|
details: {
|
|
330
427
|
url: normalizedUrl,
|
|
331
428
|
final_url: cacheEntry.finalUrl,
|
|
429
|
+
request_profile: cacheEntry.requestProfile,
|
|
332
430
|
status: cacheEntry.status,
|
|
333
431
|
content_type: cacheEntry.contentType,
|
|
334
432
|
markdown_negotiated: cacheEntry.markdownNegotiated,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateTail, } from "@mariozechner/pi-coding-agent";
|
|
1
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, keyHint, truncateTail, } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
2
3
|
import { Type } from "@sinclair/typebox";
|
|
3
4
|
const DESCRIPTION = "Search web for real-time info on any topic; use for up-to-date facts not in training data, like current events or tech updates. Results include snippets and URLs.";
|
|
4
5
|
const PROVIDER_NAME = "Brave Search API";
|
|
@@ -190,6 +191,33 @@ export default function webSearchExtension(pi) {
|
|
|
190
191
|
label: "WebSearch",
|
|
191
192
|
description: DESCRIPTION,
|
|
192
193
|
parameters: webSearchSchema,
|
|
194
|
+
renderCall(args, theme) {
|
|
195
|
+
const input = args;
|
|
196
|
+
const term = typeof input.search_term === "string" && input.search_term.trim().length > 0 ? input.search_term.trim() : "(missing search_term)";
|
|
197
|
+
const displayTerm = term.length > 120 ? `${term.slice(0, 119)}…` : term;
|
|
198
|
+
let text = theme.fg("toolTitle", theme.bold("WebSearch"));
|
|
199
|
+
text += ` ${theme.fg("toolOutput", displayTerm)}`;
|
|
200
|
+
return new Text(text, 0, 0);
|
|
201
|
+
},
|
|
202
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
203
|
+
if (isPartial) {
|
|
204
|
+
return new Text(theme.fg("muted", "Searching..."), 0, 0);
|
|
205
|
+
}
|
|
206
|
+
const textBlock = result.content.find((entry) => entry.type === "text" && typeof entry.text === "string");
|
|
207
|
+
if (!textBlock) {
|
|
208
|
+
return new Text(theme.fg("error", "No text result returned."), 0, 0);
|
|
209
|
+
}
|
|
210
|
+
const fullText = textBlock.text;
|
|
211
|
+
const details = result.details;
|
|
212
|
+
const count = details?.result_count ?? 0;
|
|
213
|
+
if (!expanded) {
|
|
214
|
+
const summary = `${count} results (click or ${keyHint("expandTools", "to expand")})`;
|
|
215
|
+
return new Text(theme.fg("muted", summary), 0, 0);
|
|
216
|
+
}
|
|
217
|
+
let text = fullText.split("\n").map((line) => theme.fg("toolOutput", line)).join("\n");
|
|
218
|
+
text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
|
|
219
|
+
return new Text(text, 0, 0);
|
|
220
|
+
},
|
|
193
221
|
async execute(_toolCallId, params, signal) {
|
|
194
222
|
const query = normalizeSearchTerm(params.search_term);
|
|
195
223
|
if (query.length === 0) {
|