@mindstudio-ai/remy 0.1.195 → 0.1.197
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 +9 -0
- package/dist/compaction/conversation.md +8 -3
- package/dist/compaction/subagent.md +10 -4
- package/dist/headless.d.ts +5 -6
- package/dist/headless.js +690 -653
- package/dist/index.js +70 -32
- package/dist/subagents/browserAutomation/prompt.md +1 -1
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -647,375 +647,215 @@ async function generateBackgroundAck(params) {
|
|
|
647
647
|
}
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
// src/
|
|
651
|
-
import fs5 from "fs";
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
650
|
+
// src/tools/spec/readSpec.ts
|
|
651
|
+
import fs5 from "fs/promises";
|
|
652
|
+
|
|
653
|
+
// src/tools/spec/_helpers.ts
|
|
654
|
+
var HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
655
|
+
function parseHeadings(content) {
|
|
656
|
+
const lines = content.split("\n");
|
|
657
|
+
const headings = [];
|
|
658
|
+
for (let i = 0; i < lines.length; i++) {
|
|
659
|
+
const match = lines[i].match(HEADING_RE);
|
|
660
|
+
if (match) {
|
|
661
|
+
headings.push({
|
|
662
|
+
level: match[1].length,
|
|
663
|
+
text: match[2].trim(),
|
|
664
|
+
startLine: i,
|
|
665
|
+
contentStart: i + 1,
|
|
666
|
+
contentEnd: lines.length
|
|
667
|
+
// placeholder — resolved below
|
|
668
|
+
});
|
|
662
669
|
}
|
|
663
|
-
fs5.writeSync(fd, JSON.stringify(entry) + "\n");
|
|
664
|
-
} catch {
|
|
665
670
|
}
|
|
671
|
+
for (let i = 0; i < headings.length; i++) {
|
|
672
|
+
const current = headings[i];
|
|
673
|
+
let end = lines.length;
|
|
674
|
+
for (let j = i + 1; j < headings.length; j++) {
|
|
675
|
+
if (headings[j].level <= current.level) {
|
|
676
|
+
end = headings[j].startLine;
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
current.contentEnd = end;
|
|
681
|
+
}
|
|
682
|
+
return headings;
|
|
666
683
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const conversationMessages = getConversationMessagesForSummary(
|
|
678
|
-
messages,
|
|
679
|
-
endIndex
|
|
680
|
-
);
|
|
681
|
-
if (conversationMessages.length > 0) {
|
|
682
|
-
tasks.push(
|
|
683
|
-
generateSummary(
|
|
684
|
-
apiConfig,
|
|
685
|
-
"conversation",
|
|
686
|
-
CONVERSATION_SUMMARY_PROMPT,
|
|
687
|
-
conversationMessages,
|
|
688
|
-
system,
|
|
689
|
-
tools2,
|
|
690
|
-
model
|
|
691
|
-
).then((text) => {
|
|
692
|
-
if (text) {
|
|
693
|
-
summaries.push({ name: "conversation", text });
|
|
694
|
-
}
|
|
695
|
-
})
|
|
696
|
-
);
|
|
684
|
+
function resolveHeadingPath(content, headingPath) {
|
|
685
|
+
const lines = content.split("\n");
|
|
686
|
+
const headings = parseHeadings(content);
|
|
687
|
+
if (headingPath === "") {
|
|
688
|
+
const firstHeadingLine = headings.length > 0 ? headings[0].startLine : lines.length;
|
|
689
|
+
return {
|
|
690
|
+
startLine: 0,
|
|
691
|
+
contentStart: 0,
|
|
692
|
+
contentEnd: firstHeadingLine
|
|
693
|
+
};
|
|
697
694
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
695
|
+
const segments = headingPath.split(">").map((s) => s.trim());
|
|
696
|
+
let searchStart = 0;
|
|
697
|
+
let searchEnd = lines.length;
|
|
698
|
+
let resolved = null;
|
|
699
|
+
for (let si = 0; si < segments.length; si++) {
|
|
700
|
+
const segment = segments[si].toLowerCase();
|
|
701
|
+
const candidates = headings.filter(
|
|
702
|
+
(h) => h.startLine >= searchStart && h.startLine < searchEnd && h.text.toLowerCase() === segment
|
|
703
703
|
);
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
tools2,
|
|
713
|
-
model
|
|
714
|
-
).then((text) => {
|
|
715
|
-
if (text) {
|
|
716
|
-
summaries.push({ name, text });
|
|
717
|
-
}
|
|
718
|
-
})
|
|
704
|
+
if (candidates.length === 0) {
|
|
705
|
+
const available = headings.filter((h) => h.startLine >= searchStart && h.startLine < searchEnd).map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
706
|
+
const searchedPath = segments.slice(0, si + 1).join(" > ");
|
|
707
|
+
throw new Error(
|
|
708
|
+
`Heading not found: "${searchedPath}"
|
|
709
|
+
|
|
710
|
+
Available headings:
|
|
711
|
+
${available || "(none)"}`
|
|
719
712
|
);
|
|
720
713
|
}
|
|
714
|
+
resolved = candidates[0];
|
|
715
|
+
searchStart = resolved.contentStart;
|
|
716
|
+
searchEnd = resolved.contentEnd;
|
|
721
717
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
{
|
|
728
|
-
type: "summary",
|
|
729
|
-
name: s.name,
|
|
730
|
-
text: s.text,
|
|
731
|
-
startedAt: Date.now()
|
|
732
|
-
}
|
|
733
|
-
]
|
|
734
|
-
}));
|
|
735
|
-
log3.info("Compaction complete", { summaries: summaries.length });
|
|
736
|
-
return checkpointMessages;
|
|
718
|
+
return {
|
|
719
|
+
startLine: resolved.startLine,
|
|
720
|
+
contentStart: resolved.contentStart,
|
|
721
|
+
contentEnd: resolved.contentEnd
|
|
722
|
+
};
|
|
737
723
|
}
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const msg = messages[idx - 1];
|
|
742
|
-
if (msg.role === "user" && msg.toolCallId) {
|
|
743
|
-
idx--;
|
|
744
|
-
} else {
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
if (idx < messages.length && idx > 0) {
|
|
749
|
-
const msg = messages[idx - 1];
|
|
750
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
751
|
-
const hasToolUse = msg.content.some(
|
|
752
|
-
(b) => b.type === "tool"
|
|
753
|
-
);
|
|
754
|
-
if (hasToolUse) {
|
|
755
|
-
idx--;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
724
|
+
function validateSpecPath(filePath) {
|
|
725
|
+
if (!filePath.startsWith("src/")) {
|
|
726
|
+
throw new Error(`Spec tool paths must start with src/. Got: "${filePath}"`);
|
|
758
727
|
}
|
|
759
|
-
return
|
|
728
|
+
return filePath;
|
|
760
729
|
}
|
|
761
|
-
function
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (!Array.isArray(msg.content)) {
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
|
-
for (const block of msg.content) {
|
|
769
|
-
if (block.type === "summary" && block.name === "conversation") {
|
|
770
|
-
startIdx = i + 1;
|
|
771
|
-
break;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (startIdx > 0) {
|
|
775
|
-
break;
|
|
776
|
-
}
|
|
730
|
+
function getHeadingTree(content) {
|
|
731
|
+
const headings = parseHeadings(content);
|
|
732
|
+
if (headings.length === 0) {
|
|
733
|
+
return "(no headings)";
|
|
777
734
|
}
|
|
778
|
-
return
|
|
735
|
+
return headings.map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
779
736
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
737
|
+
|
|
738
|
+
// src/tools/spec/readSpec.ts
|
|
739
|
+
var DEFAULT_MAX_LINES = 500;
|
|
740
|
+
var readSpecTool = {
|
|
741
|
+
clearable: true,
|
|
742
|
+
definition: {
|
|
743
|
+
name: "readSpec",
|
|
744
|
+
description: "Read a spec file from src/ with line numbers. Always read a spec file before editing it. Paths are relative to the project root and must start with src/ (e.g., src/app.md, src/interfaces/web.md).",
|
|
745
|
+
inputSchema: {
|
|
746
|
+
type: "object",
|
|
747
|
+
properties: {
|
|
748
|
+
path: {
|
|
749
|
+
type: "string",
|
|
750
|
+
description: "File path relative to project root, must start with src/ (e.g., src/app.md)."
|
|
751
|
+
},
|
|
752
|
+
offset: {
|
|
753
|
+
type: "number",
|
|
754
|
+
description: "Line number to start reading from (1-indexed). Use a negative number to read from the end. Defaults to 1."
|
|
755
|
+
},
|
|
756
|
+
maxLines: {
|
|
757
|
+
type: "number",
|
|
758
|
+
description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
required: ["path"]
|
|
786
762
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
763
|
+
},
|
|
764
|
+
async execute(input) {
|
|
765
|
+
try {
|
|
766
|
+
validateSpecPath(input.path);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
return `Error: ${err.message}`;
|
|
792
769
|
}
|
|
793
|
-
|
|
794
|
-
|
|
770
|
+
try {
|
|
771
|
+
const content = await fs5.readFile(input.path, "utf-8");
|
|
772
|
+
const allLines = content.split("\n");
|
|
773
|
+
const totalLines = allLines.length;
|
|
774
|
+
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES;
|
|
775
|
+
let startIdx;
|
|
776
|
+
if (input.offset && input.offset < 0) {
|
|
777
|
+
startIdx = Math.max(0, totalLines + input.offset);
|
|
778
|
+
} else {
|
|
779
|
+
startIdx = Math.max(0, (input.offset || 1) - 1);
|
|
780
|
+
}
|
|
781
|
+
const sliced = allLines.slice(startIdx, startIdx + maxLines);
|
|
782
|
+
const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
|
|
783
|
+
let result = numbered;
|
|
784
|
+
const endLine = startIdx + sliced.length;
|
|
785
|
+
const displayStart = startIdx + 1;
|
|
786
|
+
if (endLine < totalLines) {
|
|
787
|
+
result += `
|
|
788
|
+
|
|
789
|
+
(showing lines ${displayStart}\u2013${endLine} of ${totalLines} \u2014 use offset and maxLines to read more)`;
|
|
790
|
+
}
|
|
791
|
+
return result;
|
|
792
|
+
} catch (err) {
|
|
793
|
+
return `Error reading file: ${err.message}`;
|
|
795
794
|
}
|
|
796
795
|
}
|
|
797
|
-
|
|
798
|
-
const collected = [];
|
|
799
|
-
for (let i = startIdx; i < endIndex; i++) {
|
|
800
|
-
const msg = messages[i];
|
|
801
|
-
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
for (const block of msg.content) {
|
|
805
|
-
if (block.type === "tool" && block.name === subAgentName && block.subAgentMessages?.length) {
|
|
806
|
-
collected.push(...block.subAgentMessages);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return collected;
|
|
811
|
-
}
|
|
812
|
-
function serializeForSummary(messages) {
|
|
813
|
-
return messages.map((msg) => {
|
|
814
|
-
if (typeof msg.content === "string") {
|
|
815
|
-
return `[${msg.role}]: ${msg.content}`;
|
|
816
|
-
}
|
|
817
|
-
if (!Array.isArray(msg.content)) {
|
|
818
|
-
return `[${msg.role}]: (empty)`;
|
|
819
|
-
}
|
|
820
|
-
const blocks = msg.content;
|
|
821
|
-
const parts = [];
|
|
822
|
-
for (const block of blocks) {
|
|
823
|
-
if (block.type === "text") {
|
|
824
|
-
parts.push(block.text);
|
|
825
|
-
} else if (block.type === "tool") {
|
|
826
|
-
parts.push(
|
|
827
|
-
`[tool: ${block.name}(${JSON.stringify(block.input).slice(0, 200)})] \u2192 ${(block.result ?? "").slice(0, 500)}`
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
return `[${msg.role}]: ${parts.join("\n")}`;
|
|
832
|
-
}).join("\n\n");
|
|
833
|
-
}
|
|
834
|
-
var CHUNK_CHAR_LIMIT = 24e5;
|
|
835
|
-
async function generateSummary(apiConfig, name, compactionPrompt, messagesToSummarize, mainSystem, mainTools, model) {
|
|
836
|
-
const serialized = serializeForSummary(messagesToSummarize);
|
|
837
|
-
if (!serialized.trim()) {
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
if (serialized.length > CHUNK_CHAR_LIMIT && messagesToSummarize.length > 1) {
|
|
841
|
-
const mid = Math.floor(messagesToSummarize.length / 2);
|
|
842
|
-
log3.info("Chunking summary", {
|
|
843
|
-
name,
|
|
844
|
-
messageCount: messagesToSummarize.length,
|
|
845
|
-
serializedLength: serialized.length
|
|
846
|
-
});
|
|
847
|
-
const [first, second] = await Promise.all([
|
|
848
|
-
generateSummary(
|
|
849
|
-
apiConfig,
|
|
850
|
-
`${name} [pt1]`,
|
|
851
|
-
compactionPrompt,
|
|
852
|
-
messagesToSummarize.slice(0, mid),
|
|
853
|
-
mainSystem,
|
|
854
|
-
mainTools,
|
|
855
|
-
model
|
|
856
|
-
),
|
|
857
|
-
generateSummary(
|
|
858
|
-
apiConfig,
|
|
859
|
-
`${name} [pt2]`,
|
|
860
|
-
compactionPrompt,
|
|
861
|
-
messagesToSummarize.slice(mid),
|
|
862
|
-
mainSystem,
|
|
863
|
-
mainTools,
|
|
864
|
-
model
|
|
865
|
-
)
|
|
866
|
-
]);
|
|
867
|
-
const parts = [first, second].filter((p) => !!p);
|
|
868
|
-
return parts.length > 0 ? parts.join("\n\n---\n\n") : null;
|
|
869
|
-
}
|
|
870
|
-
log3.info("Generating summary", {
|
|
871
|
-
name,
|
|
872
|
-
messageCount: messagesToSummarize.length,
|
|
873
|
-
cacheReuse: !!mainSystem
|
|
874
|
-
});
|
|
875
|
-
let summaryText = "";
|
|
876
|
-
const useMainCache = !!mainSystem;
|
|
877
|
-
const system = useMainCache ? mainSystem : compactionPrompt;
|
|
878
|
-
const tools2 = [];
|
|
879
|
-
const userContent = useMainCache ? `${compactionPrompt}
|
|
880
|
-
|
|
881
|
-
---
|
|
882
|
-
|
|
883
|
-
Conversation to summarize:
|
|
884
|
-
|
|
885
|
-
${serialized}` : serialized;
|
|
886
|
-
const iterStart = Date.now();
|
|
887
|
-
for await (const event of streamChat({
|
|
888
|
-
...apiConfig,
|
|
889
|
-
model,
|
|
890
|
-
subAgentId: "conversationSummarizer",
|
|
891
|
-
system,
|
|
892
|
-
messages: [{ role: "user", content: userContent }],
|
|
893
|
-
tools: tools2
|
|
894
|
-
})) {
|
|
895
|
-
if (event.type === "text") {
|
|
896
|
-
summaryText += event.text;
|
|
897
|
-
} else if (event.type === "done") {
|
|
898
|
-
recordUsage({
|
|
899
|
-
ts: Date.now(),
|
|
900
|
-
agentName: "conversationSummarizer",
|
|
901
|
-
modelId: event.modelId,
|
|
902
|
-
inputTokens: event.usage.inputTokens,
|
|
903
|
-
outputTokens: event.usage.outputTokens,
|
|
904
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
905
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
906
|
-
cost: nanoToDollars(event.cost),
|
|
907
|
-
billingEvents: event.billingEvents,
|
|
908
|
-
durationMs: Date.now() - iterStart,
|
|
909
|
-
toolNames: []
|
|
910
|
-
});
|
|
911
|
-
} else if (event.type === "error") {
|
|
912
|
-
log3.error("Summary generation failed", { name, error: event.error });
|
|
913
|
-
return null;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
if (!summaryText.trim()) {
|
|
917
|
-
log3.warn("Empty summary generated", { name });
|
|
918
|
-
return null;
|
|
919
|
-
}
|
|
920
|
-
log3.info("Summary generated", { name, summaryLength: summaryText.length });
|
|
921
|
-
return summaryText.trim();
|
|
922
|
-
}
|
|
796
|
+
};
|
|
923
797
|
|
|
924
|
-
// src/tools/spec/
|
|
798
|
+
// src/tools/spec/writeSpec.ts
|
|
925
799
|
import fs6 from "fs/promises";
|
|
800
|
+
import path4 from "path";
|
|
926
801
|
|
|
927
|
-
// src/tools/
|
|
928
|
-
var
|
|
929
|
-
function
|
|
930
|
-
const
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
headings.push({
|
|
936
|
-
level: match[1].length,
|
|
937
|
-
text: match[2].trim(),
|
|
938
|
-
startLine: i,
|
|
939
|
-
contentStart: i + 1,
|
|
940
|
-
contentEnd: lines.length
|
|
941
|
-
// placeholder — resolved below
|
|
942
|
-
});
|
|
943
|
-
}
|
|
802
|
+
// src/tools/_helpers/diff.ts
|
|
803
|
+
var CONTEXT_LINES = 3;
|
|
804
|
+
function unifiedDiff(filePath, oldText, newText) {
|
|
805
|
+
const oldLines = oldText ? oldText.split("\n") : [];
|
|
806
|
+
const newLines = newText ? newText.split("\n") : [];
|
|
807
|
+
let firstDiff = 0;
|
|
808
|
+
while (firstDiff < oldLines.length && firstDiff < newLines.length && oldLines[firstDiff] === newLines[firstDiff]) {
|
|
809
|
+
firstDiff++;
|
|
944
810
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
end = headings[j].startLine;
|
|
951
|
-
break;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
current.contentEnd = end;
|
|
811
|
+
let oldEnd = oldLines.length - 1;
|
|
812
|
+
let newEnd = newLines.length - 1;
|
|
813
|
+
while (oldEnd > firstDiff && newEnd > firstDiff && oldLines[oldEnd] === newLines[newEnd]) {
|
|
814
|
+
oldEnd--;
|
|
815
|
+
newEnd--;
|
|
955
816
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
const lines =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
};
|
|
817
|
+
const ctxStart = Math.max(0, firstDiff - CONTEXT_LINES);
|
|
818
|
+
const ctxOldEnd = Math.min(oldLines.length - 1, oldEnd + CONTEXT_LINES);
|
|
819
|
+
const ctxNewEnd = Math.min(newLines.length - 1, newEnd + CONTEXT_LINES);
|
|
820
|
+
const lines = [];
|
|
821
|
+
lines.push(`--- ${filePath}`);
|
|
822
|
+
lines.push(`+++ ${filePath}`);
|
|
823
|
+
lines.push(
|
|
824
|
+
`@@ -${ctxStart + 1},${ctxOldEnd - ctxStart + 1} +${ctxStart + 1},${ctxNewEnd - ctxStart + 1} @@`
|
|
825
|
+
);
|
|
826
|
+
for (let i = ctxStart; i < firstDiff; i++) {
|
|
827
|
+
lines.push(` ${oldLines[i]}`);
|
|
968
828
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
let searchEnd = lines.length;
|
|
972
|
-
let resolved = null;
|
|
973
|
-
for (let si = 0; si < segments.length; si++) {
|
|
974
|
-
const segment = segments[si].toLowerCase();
|
|
975
|
-
const candidates = headings.filter(
|
|
976
|
-
(h) => h.startLine >= searchStart && h.startLine < searchEnd && h.text.toLowerCase() === segment
|
|
977
|
-
);
|
|
978
|
-
if (candidates.length === 0) {
|
|
979
|
-
const available = headings.filter((h) => h.startLine >= searchStart && h.startLine < searchEnd).map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
980
|
-
const searchedPath = segments.slice(0, si + 1).join(" > ");
|
|
981
|
-
throw new Error(
|
|
982
|
-
`Heading not found: "${searchedPath}"
|
|
983
|
-
|
|
984
|
-
Available headings:
|
|
985
|
-
${available || "(none)"}`
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
resolved = candidates[0];
|
|
989
|
-
searchStart = resolved.contentStart;
|
|
990
|
-
searchEnd = resolved.contentEnd;
|
|
829
|
+
for (let i = firstDiff; i <= oldEnd; i++) {
|
|
830
|
+
lines.push(`-${oldLines[i]}`);
|
|
991
831
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
contentStart: resolved.contentStart,
|
|
995
|
-
contentEnd: resolved.contentEnd
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
function validateSpecPath(filePath) {
|
|
999
|
-
if (!filePath.startsWith("src/")) {
|
|
1000
|
-
throw new Error(`Spec tool paths must start with src/. Got: "${filePath}"`);
|
|
832
|
+
for (let i = firstDiff; i <= newEnd; i++) {
|
|
833
|
+
lines.push(`+${newLines[i]}`);
|
|
1001
834
|
}
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
function getHeadingTree(content) {
|
|
1005
|
-
const headings = parseHeadings(content);
|
|
1006
|
-
if (headings.length === 0) {
|
|
1007
|
-
return "(no headings)";
|
|
835
|
+
for (let i = oldEnd + 1; i <= ctxOldEnd; i++) {
|
|
836
|
+
lines.push(` ${oldLines[i]}`);
|
|
1008
837
|
}
|
|
1009
|
-
return
|
|
838
|
+
return lines.join("\n");
|
|
1010
839
|
}
|
|
1011
840
|
|
|
1012
|
-
// src/tools/
|
|
1013
|
-
var
|
|
1014
|
-
|
|
841
|
+
// src/tools/_helpers/fileLock.ts
|
|
842
|
+
var locks = /* @__PURE__ */ new Map();
|
|
843
|
+
function acquireFileLock(filePath) {
|
|
844
|
+
let release;
|
|
845
|
+
const next = new Promise((res) => {
|
|
846
|
+
release = res;
|
|
847
|
+
});
|
|
848
|
+
const wait = locks.get(filePath) ?? Promise.resolve();
|
|
849
|
+
locks.set(filePath, next);
|
|
850
|
+
return wait.then(() => release);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/tools/spec/writeSpec.ts
|
|
854
|
+
var writeSpecTool = {
|
|
1015
855
|
clearable: true,
|
|
1016
856
|
definition: {
|
|
1017
|
-
name: "
|
|
1018
|
-
description: "
|
|
857
|
+
name: "writeSpec",
|
|
858
|
+
description: "Create a new spec file or completely overwrite an existing one in src/. Parent directories are created automatically. Use this for new spec files or full rewrites. For targeted changes to existing specs, use editSpec instead.",
|
|
1019
859
|
inputSchema: {
|
|
1020
860
|
type: "object",
|
|
1021
861
|
properties: {
|
|
@@ -1023,131 +863,17 @@ var readSpecTool = {
|
|
|
1023
863
|
type: "string",
|
|
1024
864
|
description: "File path relative to project root, must start with src/ (e.g., src/app.md)."
|
|
1025
865
|
},
|
|
1026
|
-
|
|
1027
|
-
type: "
|
|
1028
|
-
description: "
|
|
1029
|
-
},
|
|
1030
|
-
maxLines: {
|
|
1031
|
-
type: "number",
|
|
1032
|
-
description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
|
|
866
|
+
content: {
|
|
867
|
+
type: "string",
|
|
868
|
+
description: "The full MSFM markdown content to write."
|
|
1033
869
|
}
|
|
1034
870
|
},
|
|
1035
|
-
required: ["path"]
|
|
1036
|
-
}
|
|
1037
|
-
},
|
|
1038
|
-
async execute(input) {
|
|
1039
|
-
try {
|
|
1040
|
-
validateSpecPath(input.path);
|
|
1041
|
-
} catch (err) {
|
|
1042
|
-
return `Error: ${err.message}`;
|
|
1043
|
-
}
|
|
1044
|
-
try {
|
|
1045
|
-
const content = await fs6.readFile(input.path, "utf-8");
|
|
1046
|
-
const allLines = content.split("\n");
|
|
1047
|
-
const totalLines = allLines.length;
|
|
1048
|
-
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES;
|
|
1049
|
-
let startIdx;
|
|
1050
|
-
if (input.offset && input.offset < 0) {
|
|
1051
|
-
startIdx = Math.max(0, totalLines + input.offset);
|
|
1052
|
-
} else {
|
|
1053
|
-
startIdx = Math.max(0, (input.offset || 1) - 1);
|
|
1054
|
-
}
|
|
1055
|
-
const sliced = allLines.slice(startIdx, startIdx + maxLines);
|
|
1056
|
-
const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
|
|
1057
|
-
let result = numbered;
|
|
1058
|
-
const endLine = startIdx + sliced.length;
|
|
1059
|
-
const displayStart = startIdx + 1;
|
|
1060
|
-
if (endLine < totalLines) {
|
|
1061
|
-
result += `
|
|
1062
|
-
|
|
1063
|
-
(showing lines ${displayStart}\u2013${endLine} of ${totalLines} \u2014 use offset and maxLines to read more)`;
|
|
1064
|
-
}
|
|
1065
|
-
return result;
|
|
1066
|
-
} catch (err) {
|
|
1067
|
-
return `Error reading file: ${err.message}`;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
|
|
1072
|
-
// src/tools/spec/writeSpec.ts
|
|
1073
|
-
import fs7 from "fs/promises";
|
|
1074
|
-
import path4 from "path";
|
|
1075
|
-
|
|
1076
|
-
// src/tools/_helpers/diff.ts
|
|
1077
|
-
var CONTEXT_LINES = 3;
|
|
1078
|
-
function unifiedDiff(filePath, oldText, newText) {
|
|
1079
|
-
const oldLines = oldText ? oldText.split("\n") : [];
|
|
1080
|
-
const newLines = newText ? newText.split("\n") : [];
|
|
1081
|
-
let firstDiff = 0;
|
|
1082
|
-
while (firstDiff < oldLines.length && firstDiff < newLines.length && oldLines[firstDiff] === newLines[firstDiff]) {
|
|
1083
|
-
firstDiff++;
|
|
1084
|
-
}
|
|
1085
|
-
let oldEnd = oldLines.length - 1;
|
|
1086
|
-
let newEnd = newLines.length - 1;
|
|
1087
|
-
while (oldEnd > firstDiff && newEnd > firstDiff && oldLines[oldEnd] === newLines[newEnd]) {
|
|
1088
|
-
oldEnd--;
|
|
1089
|
-
newEnd--;
|
|
1090
|
-
}
|
|
1091
|
-
const ctxStart = Math.max(0, firstDiff - CONTEXT_LINES);
|
|
1092
|
-
const ctxOldEnd = Math.min(oldLines.length - 1, oldEnd + CONTEXT_LINES);
|
|
1093
|
-
const ctxNewEnd = Math.min(newLines.length - 1, newEnd + CONTEXT_LINES);
|
|
1094
|
-
const lines = [];
|
|
1095
|
-
lines.push(`--- ${filePath}`);
|
|
1096
|
-
lines.push(`+++ ${filePath}`);
|
|
1097
|
-
lines.push(
|
|
1098
|
-
`@@ -${ctxStart + 1},${ctxOldEnd - ctxStart + 1} +${ctxStart + 1},${ctxNewEnd - ctxStart + 1} @@`
|
|
1099
|
-
);
|
|
1100
|
-
for (let i = ctxStart; i < firstDiff; i++) {
|
|
1101
|
-
lines.push(` ${oldLines[i]}`);
|
|
1102
|
-
}
|
|
1103
|
-
for (let i = firstDiff; i <= oldEnd; i++) {
|
|
1104
|
-
lines.push(`-${oldLines[i]}`);
|
|
1105
|
-
}
|
|
1106
|
-
for (let i = firstDiff; i <= newEnd; i++) {
|
|
1107
|
-
lines.push(`+${newLines[i]}`);
|
|
1108
|
-
}
|
|
1109
|
-
for (let i = oldEnd + 1; i <= ctxOldEnd; i++) {
|
|
1110
|
-
lines.push(` ${oldLines[i]}`);
|
|
1111
|
-
}
|
|
1112
|
-
return lines.join("\n");
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// src/tools/_helpers/fileLock.ts
|
|
1116
|
-
var locks = /* @__PURE__ */ new Map();
|
|
1117
|
-
function acquireFileLock(filePath) {
|
|
1118
|
-
let release;
|
|
1119
|
-
const next = new Promise((res) => {
|
|
1120
|
-
release = res;
|
|
1121
|
-
});
|
|
1122
|
-
const wait = locks.get(filePath) ?? Promise.resolve();
|
|
1123
|
-
locks.set(filePath, next);
|
|
1124
|
-
return wait.then(() => release);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// src/tools/spec/writeSpec.ts
|
|
1128
|
-
var writeSpecTool = {
|
|
1129
|
-
clearable: true,
|
|
1130
|
-
definition: {
|
|
1131
|
-
name: "writeSpec",
|
|
1132
|
-
description: "Create a new spec file or completely overwrite an existing one in src/. Parent directories are created automatically. Use this for new spec files or full rewrites. For targeted changes to existing specs, use editSpec instead.",
|
|
1133
|
-
inputSchema: {
|
|
1134
|
-
type: "object",
|
|
1135
|
-
properties: {
|
|
1136
|
-
path: {
|
|
1137
|
-
type: "string",
|
|
1138
|
-
description: "File path relative to project root, must start with src/ (e.g., src/app.md)."
|
|
1139
|
-
},
|
|
1140
|
-
content: {
|
|
1141
|
-
type: "string",
|
|
1142
|
-
description: "The full MSFM markdown content to write."
|
|
1143
|
-
}
|
|
1144
|
-
},
|
|
1145
|
-
required: ["path", "content"]
|
|
871
|
+
required: ["path", "content"]
|
|
1146
872
|
}
|
|
1147
873
|
},
|
|
1148
874
|
streaming: {
|
|
1149
875
|
transform: async (partial) => {
|
|
1150
|
-
const oldContent = await
|
|
876
|
+
const oldContent = await fs6.readFile(partial.path, "utf-8").catch(() => "");
|
|
1151
877
|
const lineCount = partial.content.split("\n").length;
|
|
1152
878
|
return `Writing ${partial.path} (${lineCount} lines)
|
|
1153
879
|
${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
@@ -1161,13 +887,13 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
|
|
|
1161
887
|
}
|
|
1162
888
|
const release = await acquireFileLock(input.path);
|
|
1163
889
|
try {
|
|
1164
|
-
await
|
|
890
|
+
await fs6.mkdir(path4.dirname(input.path), { recursive: true });
|
|
1165
891
|
let oldContent = null;
|
|
1166
892
|
try {
|
|
1167
|
-
oldContent = await
|
|
893
|
+
oldContent = await fs6.readFile(input.path, "utf-8");
|
|
1168
894
|
} catch {
|
|
1169
895
|
}
|
|
1170
|
-
await
|
|
896
|
+
await fs6.writeFile(input.path, input.content, "utf-8");
|
|
1171
897
|
const lineCount = input.content.split("\n").length;
|
|
1172
898
|
const label = oldContent !== null ? "Wrote" : "Created";
|
|
1173
899
|
return `${label} ${input.path} (${lineCount} lines)
|
|
@@ -1181,7 +907,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
|
|
|
1181
907
|
};
|
|
1182
908
|
|
|
1183
909
|
// src/tools/spec/editSpec.ts
|
|
1184
|
-
import
|
|
910
|
+
import fs7 from "fs/promises";
|
|
1185
911
|
var editSpecTool = {
|
|
1186
912
|
clearable: true,
|
|
1187
913
|
definition: {
|
|
@@ -1231,7 +957,7 @@ var editSpecTool = {
|
|
|
1231
957
|
try {
|
|
1232
958
|
let originalContent;
|
|
1233
959
|
try {
|
|
1234
|
-
originalContent = await
|
|
960
|
+
originalContent = await fs7.readFile(input.path, "utf-8");
|
|
1235
961
|
} catch (err) {
|
|
1236
962
|
return `Error reading file: ${err.message}`;
|
|
1237
963
|
}
|
|
@@ -1287,7 +1013,7 @@ ${tree}`;
|
|
|
1287
1013
|
content = lines.join("\n");
|
|
1288
1014
|
}
|
|
1289
1015
|
try {
|
|
1290
|
-
await
|
|
1016
|
+
await fs7.writeFile(input.path, content, "utf-8");
|
|
1291
1017
|
} catch (err) {
|
|
1292
1018
|
return `Error writing file: ${err.message}`;
|
|
1293
1019
|
}
|
|
@@ -1299,7 +1025,7 @@ ${tree}`;
|
|
|
1299
1025
|
};
|
|
1300
1026
|
|
|
1301
1027
|
// src/tools/spec/listSpecFiles.ts
|
|
1302
|
-
import
|
|
1028
|
+
import fs8 from "fs/promises";
|
|
1303
1029
|
import path5 from "path";
|
|
1304
1030
|
var listSpecFilesTool = {
|
|
1305
1031
|
clearable: false,
|
|
@@ -1329,7 +1055,7 @@ var listSpecFilesTool = {
|
|
|
1329
1055
|
};
|
|
1330
1056
|
async function listRecursive(dir) {
|
|
1331
1057
|
const results = [];
|
|
1332
|
-
const entries = await
|
|
1058
|
+
const entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
1333
1059
|
entries.sort((a, b) => {
|
|
1334
1060
|
if (a.isDirectory() && !b.isDirectory()) {
|
|
1335
1061
|
return -1;
|
|
@@ -1375,7 +1101,7 @@ var presentPublishPlanTool = {
|
|
|
1375
1101
|
};
|
|
1376
1102
|
|
|
1377
1103
|
// src/tools/spec/writePlan.ts
|
|
1378
|
-
import
|
|
1104
|
+
import fs9 from "fs/promises";
|
|
1379
1105
|
var PLAN_FILE = ".remy-plan.md";
|
|
1380
1106
|
var writePlanTool = {
|
|
1381
1107
|
clearable: false,
|
|
@@ -1400,13 +1126,13 @@ status: pending
|
|
|
1400
1126
|
---
|
|
1401
1127
|
|
|
1402
1128
|
${content}`;
|
|
1403
|
-
await
|
|
1129
|
+
await fs9.writeFile(PLAN_FILE, file, "utf-8");
|
|
1404
1130
|
return "Plan written to .remy-plan.md. Waiting for user approval.";
|
|
1405
1131
|
}
|
|
1406
1132
|
};
|
|
1407
1133
|
|
|
1408
1134
|
// src/tools/spec/updatePlanStatus.ts
|
|
1409
|
-
import
|
|
1135
|
+
import fs10 from "fs/promises";
|
|
1410
1136
|
var PLAN_FILE2 = ".remy-plan.md";
|
|
1411
1137
|
var updatePlanStatusTool = {
|
|
1412
1138
|
clearable: false,
|
|
@@ -1429,15 +1155,15 @@ var updatePlanStatusTool = {
|
|
|
1429
1155
|
const status = input.status;
|
|
1430
1156
|
let content;
|
|
1431
1157
|
try {
|
|
1432
|
-
content = await
|
|
1158
|
+
content = await fs10.readFile(PLAN_FILE2, "utf-8");
|
|
1433
1159
|
} catch {
|
|
1434
1160
|
return "No plan file found.";
|
|
1435
1161
|
}
|
|
1436
1162
|
if (status === "rejected") {
|
|
1437
|
-
await
|
|
1163
|
+
await fs10.unlink(PLAN_FILE2);
|
|
1438
1164
|
return "Plan rejected and removed.";
|
|
1439
1165
|
}
|
|
1440
|
-
await
|
|
1166
|
+
await fs10.writeFile(
|
|
1441
1167
|
PLAN_FILE2,
|
|
1442
1168
|
content.replace(/^status:\s*\w+/m, `status: ${status}`),
|
|
1443
1169
|
"utf-8"
|
|
@@ -1743,6 +1469,24 @@ var askMindStudioSdkTool = {
|
|
|
1743
1469
|
}
|
|
1744
1470
|
};
|
|
1745
1471
|
|
|
1472
|
+
// src/usageLedger.ts
|
|
1473
|
+
import fs11 from "fs";
|
|
1474
|
+
var LEDGER_FILE = ".logs/usage.ndjson";
|
|
1475
|
+
var fd = null;
|
|
1476
|
+
function nanoToDollars(nano) {
|
|
1477
|
+
return typeof nano === "number" ? nano / 1e9 : void 0;
|
|
1478
|
+
}
|
|
1479
|
+
function recordUsage(entry) {
|
|
1480
|
+
try {
|
|
1481
|
+
if (fd === null) {
|
|
1482
|
+
fs11.mkdirSync(".logs", { recursive: true });
|
|
1483
|
+
fd = fs11.openSync(LEDGER_FILE, "a");
|
|
1484
|
+
}
|
|
1485
|
+
fs11.writeSync(fd, JSON.stringify(entry) + "\n");
|
|
1486
|
+
} catch {
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1746
1490
|
// src/subagents/common/runMindstudioCli.ts
|
|
1747
1491
|
function stripFlags(args) {
|
|
1748
1492
|
const out = [];
|
|
@@ -2584,11 +2328,11 @@ var editsFinishedTool = {
|
|
|
2584
2328
|
};
|
|
2585
2329
|
|
|
2586
2330
|
// src/tools/_helpers/sidecar.ts
|
|
2587
|
-
var
|
|
2331
|
+
var log3 = createLogger("sidecar");
|
|
2588
2332
|
var baseUrl = null;
|
|
2589
2333
|
function setSidecarBaseUrl(url) {
|
|
2590
2334
|
baseUrl = url;
|
|
2591
|
-
|
|
2335
|
+
log3.info("Configured", { url });
|
|
2592
2336
|
}
|
|
2593
2337
|
async function sidecarRequest(endpoint, body = {}, options) {
|
|
2594
2338
|
if (!baseUrl) {
|
|
@@ -2603,7 +2347,7 @@ async function sidecarRequest(endpoint, body = {}, options) {
|
|
|
2603
2347
|
signal: options?.timeout ? AbortSignal.timeout(options.timeout) : void 0
|
|
2604
2348
|
});
|
|
2605
2349
|
if (!res.ok) {
|
|
2606
|
-
|
|
2350
|
+
log3.error("Sidecar error", { endpoint, status: res.status });
|
|
2607
2351
|
throw new Error(`Sidecar error: ${res.status}`);
|
|
2608
2352
|
}
|
|
2609
2353
|
const data = await res.json();
|
|
@@ -2616,7 +2360,7 @@ async function sidecarRequest(endpoint, body = {}, options) {
|
|
|
2616
2360
|
if (err.message.startsWith("Sidecar error")) {
|
|
2617
2361
|
throw err;
|
|
2618
2362
|
}
|
|
2619
|
-
|
|
2363
|
+
log3.error("Sidecar connection error", { endpoint, error: err.message });
|
|
2620
2364
|
throw new Error(`Sidecar connection error: ${err.message}`);
|
|
2621
2365
|
}
|
|
2622
2366
|
}
|
|
@@ -2907,7 +2651,7 @@ function acquireBrowserLock() {
|
|
|
2907
2651
|
}
|
|
2908
2652
|
|
|
2909
2653
|
// src/toolRegistry.ts
|
|
2910
|
-
var
|
|
2654
|
+
var log4 = createLogger("tool-registry");
|
|
2911
2655
|
var USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
2912
2656
|
var ToolRegistry = class {
|
|
2913
2657
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -2934,7 +2678,7 @@ var ToolRegistry = class {
|
|
|
2934
2678
|
if (!entry) {
|
|
2935
2679
|
return false;
|
|
2936
2680
|
}
|
|
2937
|
-
|
|
2681
|
+
log4.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
2938
2682
|
entry.abortController.abort(mode);
|
|
2939
2683
|
if (mode === "graceful") {
|
|
2940
2684
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -2967,7 +2711,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
2967
2711
|
if (!entry) {
|
|
2968
2712
|
return false;
|
|
2969
2713
|
}
|
|
2970
|
-
|
|
2714
|
+
log4.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
2971
2715
|
entry.abortController.abort("restart");
|
|
2972
2716
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
2973
2717
|
this.onEvent?.({
|
|
@@ -3196,15 +2940,6 @@ ${content}` : attachmentHeader;
|
|
|
3196
2940
|
const blocks = msg.content;
|
|
3197
2941
|
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
3198
2942
|
const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
|
|
3199
|
-
const thinking = blocks.filter(
|
|
3200
|
-
(b) => b.type === "thinking" || b.type === "redacted_thinking"
|
|
3201
|
-
).map(
|
|
3202
|
-
(b) => b.type === "thinking" ? {
|
|
3203
|
-
type: "thinking",
|
|
3204
|
-
thinking: b.thinking,
|
|
3205
|
-
signature: b.signature
|
|
3206
|
-
} : { type: "redacted_thinking", data: b.data }
|
|
3207
|
-
);
|
|
3208
2943
|
const cleaned2 = {
|
|
3209
2944
|
role: msg.role,
|
|
3210
2945
|
content: text
|
|
@@ -3212,9 +2947,6 @@ ${content}` : attachmentHeader;
|
|
|
3212
2947
|
if (toolCalls.length > 0) {
|
|
3213
2948
|
cleaned2.toolCalls = toolCalls;
|
|
3214
2949
|
}
|
|
3215
|
-
if (thinking.length > 0) {
|
|
3216
|
-
cleaned2.thinking = thinking;
|
|
3217
|
-
}
|
|
3218
2950
|
if (msg.providerMetadata) {
|
|
3219
2951
|
cleaned2.providerMetadata = msg.providerMetadata;
|
|
3220
2952
|
}
|
|
@@ -3227,7 +2959,7 @@ ${content}` : attachmentHeader;
|
|
|
3227
2959
|
}
|
|
3228
2960
|
|
|
3229
2961
|
// src/subagents/runner.ts
|
|
3230
|
-
var
|
|
2962
|
+
var log5 = createLogger("sub-agent");
|
|
3231
2963
|
async function runSubAgent(config) {
|
|
3232
2964
|
const {
|
|
3233
2965
|
system,
|
|
@@ -3254,7 +2986,7 @@ async function runSubAgent(config) {
|
|
|
3254
2986
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
3255
2987
|
const agentName = subAgentId || "sub-agent";
|
|
3256
2988
|
const runStart = Date.now();
|
|
3257
|
-
|
|
2989
|
+
log5.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
3258
2990
|
const emit = (e) => {
|
|
3259
2991
|
onEvent({ ...e, parentToolId });
|
|
3260
2992
|
};
|
|
@@ -3471,7 +3203,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3471
3203
|
...hasArtifacts ? { artifacts } : {}
|
|
3472
3204
|
};
|
|
3473
3205
|
}
|
|
3474
|
-
|
|
3206
|
+
log5.info("Tools executing", {
|
|
3475
3207
|
requestId,
|
|
3476
3208
|
parentToolId,
|
|
3477
3209
|
count: toolCalls.length,
|
|
@@ -3548,7 +3280,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3548
3280
|
run2(tc.input);
|
|
3549
3281
|
const r = await resultPromise;
|
|
3550
3282
|
toolRegistry?.unregister(tc.id);
|
|
3551
|
-
|
|
3283
|
+
log5.info("Tool completed", {
|
|
3552
3284
|
requestId,
|
|
3553
3285
|
parentToolId,
|
|
3554
3286
|
toolCallId: tc.id,
|
|
@@ -3599,7 +3331,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3599
3331
|
const wrapRun = async () => {
|
|
3600
3332
|
try {
|
|
3601
3333
|
const result = await run();
|
|
3602
|
-
|
|
3334
|
+
log5.info("Sub-agent complete", {
|
|
3603
3335
|
requestId,
|
|
3604
3336
|
parentToolId,
|
|
3605
3337
|
agentName,
|
|
@@ -3608,7 +3340,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3608
3340
|
});
|
|
3609
3341
|
return result;
|
|
3610
3342
|
} catch (err) {
|
|
3611
|
-
|
|
3343
|
+
log5.warn("Sub-agent error", {
|
|
3612
3344
|
requestId,
|
|
3613
3345
|
parentToolId,
|
|
3614
3346
|
agentName,
|
|
@@ -3620,7 +3352,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3620
3352
|
if (!background) {
|
|
3621
3353
|
return wrapRun();
|
|
3622
3354
|
}
|
|
3623
|
-
|
|
3355
|
+
log5.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
3624
3356
|
toolRegistry?.register({
|
|
3625
3357
|
id: parentToolId,
|
|
3626
3358
|
name: agentName,
|
|
@@ -3686,7 +3418,7 @@ var BROWSER_TOOLS = [
|
|
|
3686
3418
|
{
|
|
3687
3419
|
clearable: true,
|
|
3688
3420
|
name: "browserCommand",
|
|
3689
|
-
description: "Interact with the app's live preview by sending browser commands. Commands execute sequentially with an animated cursor. Always start with a snapshot to see the current state and get ref identifiers. The result includes a snapshot field with the final page state after all steps complete. On error, the failing step has an error field and execution stops. Batches that contain an interactive step (click, type, select) also return a `
|
|
3421
|
+
description: "Interact with the app's live preview by sending browser commands. Commands execute sequentially with an animated cursor. Always start with a snapshot to see the current state and get ref identifiers. The result includes a snapshot field with the final page state after all steps complete. On error, the failing step has an error field and execution stops. Batches that contain an interactive step (click, type, select) also return a `recording` object \u2014 one chunk of a continuous per-session rrweb recording that the viewer stitches into a single replay (not a standalone per-call clip). Timeout: 120s.",
|
|
3690
3422
|
inputSchema: {
|
|
3691
3423
|
type: "object",
|
|
3692
3424
|
properties: {
|
|
@@ -3904,7 +3636,7 @@ function resolveModel(surfaceId, models, fallback) {
|
|
|
3904
3636
|
}
|
|
3905
3637
|
|
|
3906
3638
|
// src/subagents/browserAutomation/index.ts
|
|
3907
|
-
var
|
|
3639
|
+
var log6 = createLogger("browser-automation");
|
|
3908
3640
|
async function runBrowserAutomation(task, context, opts) {
|
|
3909
3641
|
const release = await acquireBrowserLock();
|
|
3910
3642
|
try {
|
|
@@ -4003,7 +3735,7 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4003
3735
|
}
|
|
4004
3736
|
}
|
|
4005
3737
|
} catch {
|
|
4006
|
-
|
|
3738
|
+
log6.debug("Failed to parse batch analysis result", {
|
|
4007
3739
|
batchResult
|
|
4008
3740
|
});
|
|
4009
3741
|
}
|
|
@@ -5537,158 +5269,454 @@ var SANITY_CHECK_TOOLS = [
|
|
|
5537
5269
|
required: ["command"]
|
|
5538
5270
|
}
|
|
5539
5271
|
}
|
|
5540
|
-
];
|
|
5541
|
-
|
|
5542
|
-
// src/subagents/codeSanityCheck/index.ts
|
|
5543
|
-
var BASE_PROMPT3 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
5544
|
-
var codeSanityCheckTool = {
|
|
5545
|
-
clearable: false,
|
|
5546
|
-
definition: {
|
|
5547
|
-
name: "codeSanityCheck",
|
|
5548
|
-
description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
|
|
5549
|
-
inputSchema: {
|
|
5550
|
-
type: "object",
|
|
5551
|
-
properties: {
|
|
5552
|
-
task: {
|
|
5553
|
-
type: "string",
|
|
5554
|
-
description: "What you're about to build and how. Include the plan, packages you intend to use, and any architectural decisions you've made."
|
|
5272
|
+
];
|
|
5273
|
+
|
|
5274
|
+
// src/subagents/codeSanityCheck/index.ts
|
|
5275
|
+
var BASE_PROMPT3 = readAsset("subagents/codeSanityCheck", "prompt.md");
|
|
5276
|
+
var codeSanityCheckTool = {
|
|
5277
|
+
clearable: false,
|
|
5278
|
+
definition: {
|
|
5279
|
+
name: "codeSanityCheck",
|
|
5280
|
+
description: 'Quick sanity check on an approach before building. Reviews architecture, package choices, and flags potential issues. Usually responds with "looks good." Occasionally catches something important. Readonly \u2014 can search the web and read code but cannot modify anything.',
|
|
5281
|
+
inputSchema: {
|
|
5282
|
+
type: "object",
|
|
5283
|
+
properties: {
|
|
5284
|
+
task: {
|
|
5285
|
+
type: "string",
|
|
5286
|
+
description: "What you're about to build and how. Include the plan, packages you intend to use, and any architectural decisions you've made."
|
|
5287
|
+
}
|
|
5288
|
+
},
|
|
5289
|
+
required: ["task"]
|
|
5290
|
+
}
|
|
5291
|
+
},
|
|
5292
|
+
async execute(input, context) {
|
|
5293
|
+
if (!context) {
|
|
5294
|
+
return "Error: code sanity check requires execution context";
|
|
5295
|
+
}
|
|
5296
|
+
const specIndex = loadSpecIndex();
|
|
5297
|
+
const parts = [BASE_PROMPT3, loadPlatformBrief()];
|
|
5298
|
+
parts.push("<!-- cache_breakpoint -->");
|
|
5299
|
+
if (specIndex) {
|
|
5300
|
+
parts.push(specIndex);
|
|
5301
|
+
}
|
|
5302
|
+
const system = parts.join("\n\n");
|
|
5303
|
+
const result = await runSubAgent({
|
|
5304
|
+
system,
|
|
5305
|
+
task: input.task,
|
|
5306
|
+
tools: SANITY_CHECK_TOOLS,
|
|
5307
|
+
externalTools: /* @__PURE__ */ new Set(),
|
|
5308
|
+
executeTool: (name, toolInput) => executeTool(name, toolInput, context),
|
|
5309
|
+
apiConfig: context.apiConfig,
|
|
5310
|
+
model: resolveModel("codeSanityCheck", context.models, context.model),
|
|
5311
|
+
subAgentId: "codeSanityCheck",
|
|
5312
|
+
signal: context.signal,
|
|
5313
|
+
parentToolId: context.toolCallId,
|
|
5314
|
+
requestId: context.requestId,
|
|
5315
|
+
onEvent: context.onEvent,
|
|
5316
|
+
resolveExternalTool: context.resolveExternalTool,
|
|
5317
|
+
toolRegistry: context.toolRegistry
|
|
5318
|
+
});
|
|
5319
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
5320
|
+
return result.text;
|
|
5321
|
+
}
|
|
5322
|
+
};
|
|
5323
|
+
|
|
5324
|
+
// src/tools/common/scrapeWebUrl.ts
|
|
5325
|
+
var scrapeWebUrlTool = {
|
|
5326
|
+
clearable: false,
|
|
5327
|
+
definition: {
|
|
5328
|
+
name: "scrapeWebUrl",
|
|
5329
|
+
description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
|
|
5330
|
+
inputSchema: {
|
|
5331
|
+
type: "object",
|
|
5332
|
+
properties: {
|
|
5333
|
+
url: {
|
|
5334
|
+
type: "string",
|
|
5335
|
+
description: "The URL to fetch."
|
|
5336
|
+
},
|
|
5337
|
+
screenshot: {
|
|
5338
|
+
type: "boolean",
|
|
5339
|
+
description: "Capture a screenshot of the page in addition to the text content. Adds latency; only use when you need to see the visual design."
|
|
5340
|
+
}
|
|
5341
|
+
},
|
|
5342
|
+
required: ["url"]
|
|
5343
|
+
}
|
|
5344
|
+
},
|
|
5345
|
+
async execute(input, context) {
|
|
5346
|
+
const url = input.url;
|
|
5347
|
+
const screenshot = input.screenshot;
|
|
5348
|
+
const pageOptions = { onlyMainContent: true };
|
|
5349
|
+
if (screenshot) {
|
|
5350
|
+
pageOptions.screenshot = true;
|
|
5351
|
+
}
|
|
5352
|
+
return runMindstudioCli(
|
|
5353
|
+
[
|
|
5354
|
+
"scrape-url",
|
|
5355
|
+
"--url",
|
|
5356
|
+
url,
|
|
5357
|
+
"--page-options",
|
|
5358
|
+
JSON.stringify(pageOptions)
|
|
5359
|
+
],
|
|
5360
|
+
{ onLog: context?.onLog }
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
};
|
|
5364
|
+
|
|
5365
|
+
// src/tools/index.ts
|
|
5366
|
+
function deriveContext(parent, toolCallId) {
|
|
5367
|
+
return { ...parent, toolCallId };
|
|
5368
|
+
}
|
|
5369
|
+
var ALL_TOOLS = [
|
|
5370
|
+
// Common
|
|
5371
|
+
setProjectOnboardingStateTool,
|
|
5372
|
+
promptUserTool,
|
|
5373
|
+
confirmDestructiveActionTool,
|
|
5374
|
+
askMindStudioSdkTool,
|
|
5375
|
+
scrapeWebUrlTool,
|
|
5376
|
+
searchGoogleTool,
|
|
5377
|
+
setProjectMetadataTool,
|
|
5378
|
+
designExpertTool,
|
|
5379
|
+
productVisionTool,
|
|
5380
|
+
codeSanityCheckTool,
|
|
5381
|
+
compactConversationTool,
|
|
5382
|
+
// Post-onboarding
|
|
5383
|
+
presentPublishPlanTool,
|
|
5384
|
+
writePlanTool,
|
|
5385
|
+
updatePlanStatusTool,
|
|
5386
|
+
// Spec
|
|
5387
|
+
readSpecTool,
|
|
5388
|
+
writeSpecTool,
|
|
5389
|
+
editSpecTool,
|
|
5390
|
+
listSpecFilesTool,
|
|
5391
|
+
// Code
|
|
5392
|
+
readFileTool,
|
|
5393
|
+
writeFileTool,
|
|
5394
|
+
editFileTool,
|
|
5395
|
+
bashTool,
|
|
5396
|
+
grepTool,
|
|
5397
|
+
globTool,
|
|
5398
|
+
listDirTool,
|
|
5399
|
+
editsFinishedTool,
|
|
5400
|
+
runScenarioTool,
|
|
5401
|
+
runMethodTool,
|
|
5402
|
+
queryDatabaseTool,
|
|
5403
|
+
screenshotTool,
|
|
5404
|
+
browserAutomationTool,
|
|
5405
|
+
// LSP
|
|
5406
|
+
lspDiagnosticsTool,
|
|
5407
|
+
restartProcessTool
|
|
5408
|
+
];
|
|
5409
|
+
var CLEARABLE_TOOLS = new Set(
|
|
5410
|
+
ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
|
|
5411
|
+
);
|
|
5412
|
+
var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
5413
|
+
"visualDesignExpert",
|
|
5414
|
+
"productVision",
|
|
5415
|
+
"codeSanityCheck",
|
|
5416
|
+
"runAutomatedBrowserTest",
|
|
5417
|
+
"askMindStudioSdk"
|
|
5418
|
+
]);
|
|
5419
|
+
function getToolDefinitions(_onboardingState) {
|
|
5420
|
+
return ALL_TOOLS.map((t) => t.definition);
|
|
5421
|
+
}
|
|
5422
|
+
function getToolByName(name) {
|
|
5423
|
+
return ALL_TOOLS.find((t) => t.definition.name === name);
|
|
5424
|
+
}
|
|
5425
|
+
function executeTool(name, input, context) {
|
|
5426
|
+
const tool = getToolByName(name);
|
|
5427
|
+
if (!tool) {
|
|
5428
|
+
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
5429
|
+
}
|
|
5430
|
+
return tool.execute(input, context);
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
// src/compaction/index.ts
|
|
5434
|
+
var log7 = createLogger("compaction");
|
|
5435
|
+
var CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
|
|
5436
|
+
var SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
|
|
5437
|
+
var SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
|
|
5438
|
+
async function compactConversation(messages, apiConfig, system, tools2, model) {
|
|
5439
|
+
const endIndex = findSafeInsertionPoint(messages);
|
|
5440
|
+
const summaries = [];
|
|
5441
|
+
const tasks = [];
|
|
5442
|
+
const conversationMessages = getConversationMessagesForSummary(
|
|
5443
|
+
messages,
|
|
5444
|
+
endIndex
|
|
5445
|
+
);
|
|
5446
|
+
if (conversationMessages.length > 0) {
|
|
5447
|
+
tasks.push(
|
|
5448
|
+
generateSummary(
|
|
5449
|
+
apiConfig,
|
|
5450
|
+
"conversation",
|
|
5451
|
+
CONVERSATION_SUMMARY_PROMPT,
|
|
5452
|
+
conversationMessages,
|
|
5453
|
+
system,
|
|
5454
|
+
tools2,
|
|
5455
|
+
model
|
|
5456
|
+
).then((text) => {
|
|
5457
|
+
if (text) {
|
|
5458
|
+
summaries.push({ name: "conversation", text });
|
|
5459
|
+
}
|
|
5460
|
+
})
|
|
5461
|
+
);
|
|
5462
|
+
}
|
|
5463
|
+
for (const name of SUMMARIZABLE_SUBAGENTS) {
|
|
5464
|
+
const subagentMessages = getSubAgentMessagesForSummary(
|
|
5465
|
+
messages,
|
|
5466
|
+
name,
|
|
5467
|
+
endIndex
|
|
5468
|
+
);
|
|
5469
|
+
if (subagentMessages.length > 0) {
|
|
5470
|
+
tasks.push(
|
|
5471
|
+
generateSummary(
|
|
5472
|
+
apiConfig,
|
|
5473
|
+
name,
|
|
5474
|
+
SUBAGENT_SUMMARY_PROMPT,
|
|
5475
|
+
subagentMessages,
|
|
5476
|
+
system,
|
|
5477
|
+
tools2,
|
|
5478
|
+
model
|
|
5479
|
+
).then((text) => {
|
|
5480
|
+
if (text) {
|
|
5481
|
+
summaries.push({ name, text });
|
|
5482
|
+
}
|
|
5483
|
+
})
|
|
5484
|
+
);
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
await Promise.all(tasks);
|
|
5488
|
+
const checkpointMessages = summaries.map((s) => ({
|
|
5489
|
+
role: "user",
|
|
5490
|
+
hidden: true,
|
|
5491
|
+
content: [
|
|
5492
|
+
{
|
|
5493
|
+
type: "summary",
|
|
5494
|
+
name: s.name,
|
|
5495
|
+
text: s.text,
|
|
5496
|
+
startedAt: Date.now()
|
|
5497
|
+
}
|
|
5498
|
+
]
|
|
5499
|
+
}));
|
|
5500
|
+
log7.info("Compaction complete", { summaries: summaries.length });
|
|
5501
|
+
return checkpointMessages;
|
|
5502
|
+
}
|
|
5503
|
+
function findSafeInsertionPoint(messages) {
|
|
5504
|
+
let idx = messages.length;
|
|
5505
|
+
while (idx > 0) {
|
|
5506
|
+
const msg = messages[idx - 1];
|
|
5507
|
+
if (msg.role === "user" && msg.toolCallId) {
|
|
5508
|
+
idx--;
|
|
5509
|
+
} else {
|
|
5510
|
+
break;
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
if (idx < messages.length && idx > 0) {
|
|
5514
|
+
const msg = messages[idx - 1];
|
|
5515
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
5516
|
+
const hasToolUse = msg.content.some(
|
|
5517
|
+
(b) => b.type === "tool"
|
|
5518
|
+
);
|
|
5519
|
+
if (hasToolUse) {
|
|
5520
|
+
idx--;
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
return idx;
|
|
5525
|
+
}
|
|
5526
|
+
function getConversationMessagesForSummary(messages, endIndex) {
|
|
5527
|
+
let startIdx = 0;
|
|
5528
|
+
for (let i = endIndex - 1; i >= 0; i--) {
|
|
5529
|
+
const msg = messages[i];
|
|
5530
|
+
if (!Array.isArray(msg.content)) {
|
|
5531
|
+
continue;
|
|
5532
|
+
}
|
|
5533
|
+
for (const block of msg.content) {
|
|
5534
|
+
if (block.type === "summary" && block.name === "conversation") {
|
|
5535
|
+
startIdx = i + 1;
|
|
5536
|
+
break;
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
if (startIdx > 0) {
|
|
5540
|
+
break;
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
return messages.slice(startIdx, endIndex);
|
|
5544
|
+
}
|
|
5545
|
+
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
5546
|
+
let checkpointIdx = -1;
|
|
5547
|
+
for (let i = endIndex - 1; i >= 0; i--) {
|
|
5548
|
+
const msg = messages[i];
|
|
5549
|
+
if (!Array.isArray(msg.content)) {
|
|
5550
|
+
continue;
|
|
5551
|
+
}
|
|
5552
|
+
for (const block of msg.content) {
|
|
5553
|
+
if (block.type === "summary" && block.name === subAgentName) {
|
|
5554
|
+
checkpointIdx = i;
|
|
5555
|
+
break;
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
if (checkpointIdx !== -1) {
|
|
5559
|
+
break;
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
const startIdx = checkpointIdx !== -1 ? checkpointIdx + 1 : 0;
|
|
5563
|
+
const collected = [];
|
|
5564
|
+
for (let i = startIdx; i < endIndex; i++) {
|
|
5565
|
+
const msg = messages[i];
|
|
5566
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
5567
|
+
continue;
|
|
5568
|
+
}
|
|
5569
|
+
for (const block of msg.content) {
|
|
5570
|
+
if (block.type === "tool" && block.name === subAgentName && block.subAgentMessages?.length) {
|
|
5571
|
+
collected.push(...block.subAgentMessages);
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
return collected;
|
|
5576
|
+
}
|
|
5577
|
+
function serializeForSummary(messages) {
|
|
5578
|
+
const toolNameById = /* @__PURE__ */ new Map();
|
|
5579
|
+
for (const msg of messages) {
|
|
5580
|
+
if (!Array.isArray(msg.content)) {
|
|
5581
|
+
continue;
|
|
5582
|
+
}
|
|
5583
|
+
for (const block of msg.content) {
|
|
5584
|
+
if (block.type === "tool") {
|
|
5585
|
+
toolNameById.set(block.id, block.name);
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
const lines = [];
|
|
5590
|
+
for (const msg of messages) {
|
|
5591
|
+
if (msg.role === "user" && msg.toolCallId) {
|
|
5592
|
+
const toolName = toolNameById.get(msg.toolCallId);
|
|
5593
|
+
if (toolName && SUBAGENT_TOOL_NAMES.has(toolName)) {
|
|
5594
|
+
const content = typeof msg.content === "string" ? msg.content : Array.isArray(msg.content) ? msg.content.filter(
|
|
5595
|
+
(b) => b.type === "text"
|
|
5596
|
+
).map((b) => b.text).join("\n") : "";
|
|
5597
|
+
if (content.trim()) {
|
|
5598
|
+
lines.push(`[${toolName} result]: ${content}`);
|
|
5555
5599
|
}
|
|
5556
|
-
}
|
|
5557
|
-
|
|
5600
|
+
}
|
|
5601
|
+
continue;
|
|
5558
5602
|
}
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5603
|
+
if (typeof msg.content === "string") {
|
|
5604
|
+
if (msg.content.trim()) {
|
|
5605
|
+
lines.push(`[${msg.role}]: ${msg.content}`);
|
|
5606
|
+
}
|
|
5607
|
+
continue;
|
|
5563
5608
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
parts.push("<!-- cache_breakpoint -->");
|
|
5567
|
-
if (specIndex) {
|
|
5568
|
-
parts.push(specIndex);
|
|
5609
|
+
if (!Array.isArray(msg.content)) {
|
|
5610
|
+
continue;
|
|
5569
5611
|
}
|
|
5570
|
-
const
|
|
5571
|
-
const
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5612
|
+
const blocks = msg.content;
|
|
5613
|
+
const parts = [];
|
|
5614
|
+
let toolCount = 0;
|
|
5615
|
+
for (const block of blocks) {
|
|
5616
|
+
if (block.type === "text") {
|
|
5617
|
+
parts.push(block.text);
|
|
5618
|
+
} else if (block.type === "tool") {
|
|
5619
|
+
toolCount++;
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
if (toolCount > 0) {
|
|
5623
|
+
parts.push(`[used ${toolCount} tool${toolCount === 1 ? "" : "s"}]`);
|
|
5624
|
+
}
|
|
5625
|
+
const body = parts.join("\n").trim();
|
|
5626
|
+
if (body) {
|
|
5627
|
+
lines.push(`[${msg.role}]: ${body}`);
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
return lines.join("\n\n");
|
|
5631
|
+
}
|
|
5632
|
+
var CHUNK_CHAR_LIMIT = 24e5;
|
|
5633
|
+
async function generateSummary(apiConfig, name, compactionPrompt, messagesToSummarize, mainSystem, mainTools, model) {
|
|
5634
|
+
const serialized = serializeForSummary(messagesToSummarize);
|
|
5635
|
+
if (!serialized.trim()) {
|
|
5636
|
+
return null;
|
|
5637
|
+
}
|
|
5638
|
+
if (serialized.length > CHUNK_CHAR_LIMIT && messagesToSummarize.length > 1) {
|
|
5639
|
+
const mid = Math.floor(messagesToSummarize.length / 2);
|
|
5640
|
+
log7.info("Chunking summary", {
|
|
5641
|
+
name,
|
|
5642
|
+
messageCount: messagesToSummarize.length,
|
|
5643
|
+
serializedLength: serialized.length
|
|
5586
5644
|
});
|
|
5587
|
-
|
|
5588
|
-
|
|
5645
|
+
const [first, second] = await Promise.all([
|
|
5646
|
+
generateSummary(
|
|
5647
|
+
apiConfig,
|
|
5648
|
+
`${name} [pt1]`,
|
|
5649
|
+
compactionPrompt,
|
|
5650
|
+
messagesToSummarize.slice(0, mid),
|
|
5651
|
+
mainSystem,
|
|
5652
|
+
mainTools,
|
|
5653
|
+
model
|
|
5654
|
+
),
|
|
5655
|
+
generateSummary(
|
|
5656
|
+
apiConfig,
|
|
5657
|
+
`${name} [pt2]`,
|
|
5658
|
+
compactionPrompt,
|
|
5659
|
+
messagesToSummarize.slice(mid),
|
|
5660
|
+
mainSystem,
|
|
5661
|
+
mainTools,
|
|
5662
|
+
model
|
|
5663
|
+
)
|
|
5664
|
+
]);
|
|
5665
|
+
const parts = [first, second].filter((p) => !!p);
|
|
5666
|
+
return parts.length > 0 ? parts.join("\n\n---\n\n") : null;
|
|
5589
5667
|
}
|
|
5590
|
-
|
|
5668
|
+
log7.info("Generating summary", {
|
|
5669
|
+
name,
|
|
5670
|
+
messageCount: messagesToSummarize.length,
|
|
5671
|
+
cacheReuse: !!mainSystem
|
|
5672
|
+
});
|
|
5673
|
+
let summaryText = "";
|
|
5674
|
+
const useMainCache = !!mainSystem;
|
|
5675
|
+
const system = useMainCache ? mainSystem : compactionPrompt;
|
|
5676
|
+
const tools2 = [];
|
|
5677
|
+
const userContent = useMainCache ? `${compactionPrompt}
|
|
5591
5678
|
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5679
|
+
---
|
|
5680
|
+
|
|
5681
|
+
Conversation to summarize:
|
|
5682
|
+
|
|
5683
|
+
${serialized}` : serialized;
|
|
5684
|
+
const iterStart = Date.now();
|
|
5685
|
+
for await (const event of streamChat({
|
|
5686
|
+
...apiConfig,
|
|
5687
|
+
model,
|
|
5688
|
+
subAgentId: "conversationSummarizer",
|
|
5689
|
+
system,
|
|
5690
|
+
messages: [{ role: "user", content: userContent }],
|
|
5691
|
+
tools: tools2
|
|
5692
|
+
})) {
|
|
5693
|
+
if (event.type === "text") {
|
|
5694
|
+
summaryText += event.text;
|
|
5695
|
+
} else if (event.type === "done") {
|
|
5696
|
+
recordUsage({
|
|
5697
|
+
ts: Date.now(),
|
|
5698
|
+
agentName: "conversationSummarizer",
|
|
5699
|
+
modelId: event.modelId,
|
|
5700
|
+
inputTokens: event.usage.inputTokens,
|
|
5701
|
+
outputTokens: event.usage.outputTokens,
|
|
5702
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
5703
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
5704
|
+
cost: nanoToDollars(event.cost),
|
|
5705
|
+
billingEvents: event.billingEvents,
|
|
5706
|
+
durationMs: Date.now() - iterStart,
|
|
5707
|
+
toolNames: []
|
|
5708
|
+
});
|
|
5709
|
+
} else if (event.type === "error") {
|
|
5710
|
+
log7.error("Summary generation failed", { name, error: event.error });
|
|
5711
|
+
return null;
|
|
5619
5712
|
}
|
|
5620
|
-
return runMindstudioCli(
|
|
5621
|
-
[
|
|
5622
|
-
"scrape-url",
|
|
5623
|
-
"--url",
|
|
5624
|
-
url,
|
|
5625
|
-
"--page-options",
|
|
5626
|
-
JSON.stringify(pageOptions)
|
|
5627
|
-
],
|
|
5628
|
-
{ onLog: context?.onLog }
|
|
5629
|
-
);
|
|
5630
5713
|
}
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
function deriveContext(parent, toolCallId) {
|
|
5635
|
-
return { ...parent, toolCallId };
|
|
5636
|
-
}
|
|
5637
|
-
var ALL_TOOLS = [
|
|
5638
|
-
// Common
|
|
5639
|
-
setProjectOnboardingStateTool,
|
|
5640
|
-
promptUserTool,
|
|
5641
|
-
confirmDestructiveActionTool,
|
|
5642
|
-
askMindStudioSdkTool,
|
|
5643
|
-
scrapeWebUrlTool,
|
|
5644
|
-
searchGoogleTool,
|
|
5645
|
-
setProjectMetadataTool,
|
|
5646
|
-
designExpertTool,
|
|
5647
|
-
productVisionTool,
|
|
5648
|
-
codeSanityCheckTool,
|
|
5649
|
-
compactConversationTool,
|
|
5650
|
-
// Post-onboarding
|
|
5651
|
-
presentPublishPlanTool,
|
|
5652
|
-
writePlanTool,
|
|
5653
|
-
updatePlanStatusTool,
|
|
5654
|
-
// Spec
|
|
5655
|
-
readSpecTool,
|
|
5656
|
-
writeSpecTool,
|
|
5657
|
-
editSpecTool,
|
|
5658
|
-
listSpecFilesTool,
|
|
5659
|
-
// Code
|
|
5660
|
-
readFileTool,
|
|
5661
|
-
writeFileTool,
|
|
5662
|
-
editFileTool,
|
|
5663
|
-
bashTool,
|
|
5664
|
-
grepTool,
|
|
5665
|
-
globTool,
|
|
5666
|
-
listDirTool,
|
|
5667
|
-
editsFinishedTool,
|
|
5668
|
-
runScenarioTool,
|
|
5669
|
-
runMethodTool,
|
|
5670
|
-
queryDatabaseTool,
|
|
5671
|
-
screenshotTool,
|
|
5672
|
-
browserAutomationTool,
|
|
5673
|
-
// LSP
|
|
5674
|
-
lspDiagnosticsTool,
|
|
5675
|
-
restartProcessTool
|
|
5676
|
-
];
|
|
5677
|
-
var CLEARABLE_TOOLS = new Set(
|
|
5678
|
-
ALL_TOOLS.filter((t) => t.clearable).map((t) => t.definition.name)
|
|
5679
|
-
);
|
|
5680
|
-
function getToolDefinitions(_onboardingState) {
|
|
5681
|
-
return ALL_TOOLS.map((t) => t.definition);
|
|
5682
|
-
}
|
|
5683
|
-
function getToolByName(name) {
|
|
5684
|
-
return ALL_TOOLS.find((t) => t.definition.name === name);
|
|
5685
|
-
}
|
|
5686
|
-
function executeTool(name, input, context) {
|
|
5687
|
-
const tool = getToolByName(name);
|
|
5688
|
-
if (!tool) {
|
|
5689
|
-
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
5714
|
+
if (!summaryText.trim()) {
|
|
5715
|
+
log7.warn("Empty summary generated", { name });
|
|
5716
|
+
return null;
|
|
5690
5717
|
}
|
|
5691
|
-
|
|
5718
|
+
log7.info("Summary generated", { name, summaryLength: summaryText.length });
|
|
5719
|
+
return summaryText.trim();
|
|
5692
5720
|
}
|
|
5693
5721
|
|
|
5694
5722
|
// src/compaction/trigger.ts
|
|
@@ -7827,13 +7855,11 @@ var HeadlessSession = class {
|
|
|
7827
7855
|
clearSession(this.state);
|
|
7828
7856
|
return {};
|
|
7829
7857
|
}
|
|
7830
|
-
/**
|
|
7831
|
-
*
|
|
7832
|
-
*
|
|
7833
|
-
*
|
|
7834
|
-
|
|
7835
|
-
handleNewSession(models) {
|
|
7836
|
-
clearSession(this.state);
|
|
7858
|
+
/** Change per-agent model picks without clearing history. Takes effect on
|
|
7859
|
+
* the next turn — the model is resolved live, per LLM call, from
|
|
7860
|
+
* `state.models`. Omitting `models` (or sending an empty object) resets
|
|
7861
|
+
* every agent to "use server defaults". */
|
|
7862
|
+
handleChangeModels(models) {
|
|
7837
7863
|
this.state.models = models && Object.keys(models).length > 0 ? models : void 0;
|
|
7838
7864
|
saveSession(this.state);
|
|
7839
7865
|
return {
|
|
@@ -7930,12 +7956,23 @@ var HeadlessSession = class {
|
|
|
7930
7956
|
);
|
|
7931
7957
|
return;
|
|
7932
7958
|
}
|
|
7933
|
-
if (action === "
|
|
7959
|
+
if (action === "changeModels") {
|
|
7960
|
+
if (this.running) {
|
|
7961
|
+
this.emit(
|
|
7962
|
+
"completed",
|
|
7963
|
+
{
|
|
7964
|
+
success: false,
|
|
7965
|
+
error: "cannot change models while a turn is running"
|
|
7966
|
+
},
|
|
7967
|
+
requestId
|
|
7968
|
+
);
|
|
7969
|
+
return;
|
|
7970
|
+
}
|
|
7934
7971
|
const models = parsed.models;
|
|
7935
7972
|
this.dispatchSimple(
|
|
7936
7973
|
requestId,
|
|
7937
|
-
"
|
|
7938
|
-
() => this.
|
|
7974
|
+
"models_changed",
|
|
7975
|
+
() => this.handleChangeModels(models)
|
|
7939
7976
|
);
|
|
7940
7977
|
return;
|
|
7941
7978
|
}
|