@cryer/star-cli 0.3.7 → 0.3.8
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 +5 -2
- package/README.zh-CN.md +5 -2
- package/dist/{chunk-4X25DNTF.js → chunk-IYWYSFSF.js} +3 -204
- package/dist/chunk-IYWYSFSF.js.map +1 -0
- package/dist/{chunk-OOOW7FC4.js → chunk-OHLA2DP7.js} +673 -178
- package/dist/chunk-OHLA2DP7.js.map +1 -0
- package/dist/loop-2MOFLXTG.js +12 -0
- package/dist/main.js +7 -7
- package/dist/{repl-RT6HBM5B.js → repl-YA23CK5R.js} +21 -21
- package/dist/repl-YA23CK5R.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-4X25DNTF.js.map +0 -1
- package/dist/chunk-OOOW7FC4.js.map +0 -1
- package/dist/loop-DVVPIOS7.js +0 -10
- package/dist/repl-RT6HBM5B.js.map +0 -1
- /package/dist/{loop-DVVPIOS7.js.map → loop-2MOFLXTG.js.map} +0 -0
|
@@ -75,6 +75,10 @@ var HookConfigSchema = z.object({
|
|
|
75
75
|
});
|
|
76
76
|
var ConfigSchema = z.object({
|
|
77
77
|
defaultModel: z.string().default(""),
|
|
78
|
+
// Optional [[models]] name for cheap auxiliary LLM calls (compaction
|
|
79
|
+
// summaries, session titles, turn completion checks) — Claude Code routes
|
|
80
|
+
// these to a Haiku-class model. Unset = the active main model does them.
|
|
81
|
+
smallModel: z.string().optional(),
|
|
78
82
|
permissionMode: z.enum(["auto", "ask", "readonly", "yolo", "plan"]).default("ask"),
|
|
79
83
|
providers: z.array(ProviderConfigSchema).default([]),
|
|
80
84
|
models: z.array(ModelConfigSchema).default([]),
|
|
@@ -219,6 +223,16 @@ var SUMMARY_SYSTEM_PROMPT = [
|
|
|
219
223
|
"- outstanding TODOs and next steps",
|
|
220
224
|
"Output only the summary, no preamble."
|
|
221
225
|
].join("\n");
|
|
226
|
+
var MAX_SUMMARY_INPUT_CHARS = 8e4;
|
|
227
|
+
var SUMMARY_HEAD_CHARS = 24e3;
|
|
228
|
+
function truncateSummaryInput(transcript) {
|
|
229
|
+
if (transcript.length <= MAX_SUMMARY_INPUT_CHARS) return transcript;
|
|
230
|
+
const tailChars = MAX_SUMMARY_INPUT_CHARS - SUMMARY_HEAD_CHARS;
|
|
231
|
+
const omitted = transcript.length - MAX_SUMMARY_INPUT_CHARS;
|
|
232
|
+
return `${transcript.slice(0, SUMMARY_HEAD_CHARS)}
|
|
233
|
+
[... ${omitted} characters omitted ...]
|
|
234
|
+
${transcript.slice(-tailChars)}`;
|
|
235
|
+
}
|
|
222
236
|
function serializeMessage(message) {
|
|
223
237
|
if (typeof message.content === "string") {
|
|
224
238
|
return `${message.role}: ${message.content}`;
|
|
@@ -234,7 +248,7 @@ function serializeMessage(message) {
|
|
|
234
248
|
return `${message.role}: ${parts.join("\n")}`;
|
|
235
249
|
}
|
|
236
250
|
async function summarizeMessages(messages, model, signal, temperature) {
|
|
237
|
-
const transcript = messages.map(serializeMessage).join("\n");
|
|
251
|
+
const transcript = truncateSummaryInput(messages.map(serializeMessage).join("\n"));
|
|
238
252
|
const { text } = await generateText({
|
|
239
253
|
model,
|
|
240
254
|
system: SUMMARY_SYSTEM_PROMPT,
|
|
@@ -710,6 +724,241 @@ async function runHooks(event, hooks2, ctx) {
|
|
|
710
724
|
return result;
|
|
711
725
|
}
|
|
712
726
|
|
|
727
|
+
// src/llm/provider.ts
|
|
728
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
729
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
730
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
731
|
+
|
|
732
|
+
// src/llm/activity.ts
|
|
733
|
+
var trackers = /* @__PURE__ */ new WeakMap();
|
|
734
|
+
function streamActivity(signal) {
|
|
735
|
+
return trackers.get(signal);
|
|
736
|
+
}
|
|
737
|
+
function wrapFetchWithActivity(baseFetch = fetch) {
|
|
738
|
+
return async (input, init) => {
|
|
739
|
+
const response = await baseFetch(input, init);
|
|
740
|
+
const signal = init?.signal ?? (input instanceof Request ? input.signal : null);
|
|
741
|
+
if (!signal || !response.body) return response;
|
|
742
|
+
const tracker = { lastChunkAt: Date.now() };
|
|
743
|
+
trackers.set(signal, tracker);
|
|
744
|
+
const reader = response.body.getReader();
|
|
745
|
+
const body = new ReadableStream({
|
|
746
|
+
async pull(controller) {
|
|
747
|
+
try {
|
|
748
|
+
const { done, value } = await reader.read();
|
|
749
|
+
if (done) {
|
|
750
|
+
controller.close();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
tracker.lastChunkAt = Date.now();
|
|
754
|
+
controller.enqueue(value);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
controller.error(error);
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
cancel(reason) {
|
|
760
|
+
return reader.cancel(reason);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
return new Response(body, {
|
|
764
|
+
status: response.status,
|
|
765
|
+
statusText: response.statusText,
|
|
766
|
+
headers: response.headers
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/llm/registry.ts
|
|
772
|
+
function listModels(config) {
|
|
773
|
+
return config.models;
|
|
774
|
+
}
|
|
775
|
+
function resolveModelConfig(config, modelName) {
|
|
776
|
+
const name = modelName ?? config.defaultModel;
|
|
777
|
+
const model = config.models.find((m) => m.name === name);
|
|
778
|
+
if (!model) {
|
|
779
|
+
const available = config.models.map((m) => m.name).join(", ") || "(none)";
|
|
780
|
+
throw new Error(`Model "${name}" not found. Available models: ${available}`);
|
|
781
|
+
}
|
|
782
|
+
return model;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/llm/sse-normalize.ts
|
|
786
|
+
function splitJsonPrefix(text) {
|
|
787
|
+
if (!text.startsWith("{")) return null;
|
|
788
|
+
let depth = 0;
|
|
789
|
+
let inString = false;
|
|
790
|
+
let escaped = false;
|
|
791
|
+
for (let i = 0; i < text.length; i++) {
|
|
792
|
+
const ch = text[i];
|
|
793
|
+
if (inString) {
|
|
794
|
+
if (escaped) escaped = false;
|
|
795
|
+
else if (ch === "\\") escaped = true;
|
|
796
|
+
else if (ch === '"') inString = false;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (ch === '"') inString = true;
|
|
800
|
+
else if (ch === "{") depth += 1;
|
|
801
|
+
else if (ch === "}") {
|
|
802
|
+
depth -= 1;
|
|
803
|
+
if (depth === 0) return [text.slice(0, i + 1), text.slice(i + 1).trimStart()];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
var SseNormalizeTransform = class extends TransformStream {
|
|
809
|
+
constructor() {
|
|
810
|
+
let buffer = "";
|
|
811
|
+
let pending = null;
|
|
812
|
+
const flushPending = (controller) => {
|
|
813
|
+
if (pending === null) return;
|
|
814
|
+
controller.enqueue(`data: ${pending}
|
|
815
|
+
|
|
816
|
+
`);
|
|
817
|
+
pending = null;
|
|
818
|
+
};
|
|
819
|
+
const emitPayload = (payload, controller) => {
|
|
820
|
+
let rest = payload;
|
|
821
|
+
for (; ; ) {
|
|
822
|
+
const split = splitJsonPrefix(rest);
|
|
823
|
+
if (!split) {
|
|
824
|
+
pending = rest;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const [json, tail] = split;
|
|
828
|
+
if (tail === "") {
|
|
829
|
+
pending = json;
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
controller.enqueue(`data: ${json}
|
|
833
|
+
|
|
834
|
+
`);
|
|
835
|
+
if (tail.startsWith("data:")) {
|
|
836
|
+
rest = tail.slice(5).replace(/^ /, "");
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (tail.startsWith("{")) {
|
|
840
|
+
rest = tail;
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
controller.enqueue(`${tail}
|
|
844
|
+
`);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
const handleLine = (raw, controller) => {
|
|
849
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
850
|
+
if (line === "") {
|
|
851
|
+
flushPending(controller);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (line.startsWith("data:")) {
|
|
855
|
+
flushPending(controller);
|
|
856
|
+
emitPayload(line.slice(5).replace(/^ /, ""), controller);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (line.startsWith("{") && pending !== null) {
|
|
860
|
+
flushPending(controller);
|
|
861
|
+
emitPayload(line, controller);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
flushPending(controller);
|
|
865
|
+
controller.enqueue(`${line}
|
|
866
|
+
`);
|
|
867
|
+
};
|
|
868
|
+
super({
|
|
869
|
+
transform(chunk, controller) {
|
|
870
|
+
buffer += chunk;
|
|
871
|
+
let index = buffer.indexOf("\n");
|
|
872
|
+
while (index !== -1) {
|
|
873
|
+
handleLine(buffer.slice(0, index), controller);
|
|
874
|
+
buffer = buffer.slice(index + 1);
|
|
875
|
+
index = buffer.indexOf("\n");
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
flush(controller) {
|
|
879
|
+
if (buffer.length > 0) handleLine(buffer, controller);
|
|
880
|
+
flushPending(controller);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
function createSseNormalizingFetch(baseFetch = fetch) {
|
|
886
|
+
return async (input, init) => {
|
|
887
|
+
const response = await baseFetch(input, init);
|
|
888
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
889
|
+
if (!response.body || !contentType.includes("text/event-stream")) {
|
|
890
|
+
return response;
|
|
891
|
+
}
|
|
892
|
+
const body = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new SseNormalizeTransform()).pipeThrough(new TextEncoderStream());
|
|
893
|
+
const headers = new Headers(response.headers);
|
|
894
|
+
headers.delete("content-length");
|
|
895
|
+
headers.delete("content-encoding");
|
|
896
|
+
return new Response(body, {
|
|
897
|
+
status: response.status,
|
|
898
|
+
statusText: response.statusText,
|
|
899
|
+
headers
|
|
900
|
+
});
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/llm/provider.ts
|
|
905
|
+
function resolveApiKey(provider) {
|
|
906
|
+
const fromEnv = provider.apiKeyEnv ? process.env[provider.apiKeyEnv] : void 0;
|
|
907
|
+
const apiKey = fromEnv ?? provider.apiKey;
|
|
908
|
+
if (!apiKey) {
|
|
909
|
+
const hint = provider.apiKeyEnv ? `set ${provider.apiKeyEnv} or configure apiKey` : "configure apiKey";
|
|
910
|
+
throw new Error(`Missing API key for provider "${provider.name}": ${hint}`);
|
|
911
|
+
}
|
|
912
|
+
return apiKey;
|
|
913
|
+
}
|
|
914
|
+
function createModel(config, modelName) {
|
|
915
|
+
const modelConfig = resolveModelConfig(config, modelName);
|
|
916
|
+
const provider = config.providers.find((p) => p.name === modelConfig.provider);
|
|
917
|
+
if (!provider) {
|
|
918
|
+
const available = config.providers.map((p) => p.name).join(", ") || "(none)";
|
|
919
|
+
throw new Error(
|
|
920
|
+
`Provider "${modelConfig.provider}" for model "${modelConfig.name}" not found. Available providers: ${available}`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
const apiKey = resolveApiKey(provider);
|
|
924
|
+
const trackedFetch = wrapFetchWithActivity();
|
|
925
|
+
switch (provider.protocol) {
|
|
926
|
+
case "anthropic":
|
|
927
|
+
return createAnthropic({
|
|
928
|
+
baseURL: provider.baseURL,
|
|
929
|
+
apiKey,
|
|
930
|
+
headers: provider.headers,
|
|
931
|
+
fetch: trackedFetch
|
|
932
|
+
})(modelConfig.model);
|
|
933
|
+
case "openai-compatible":
|
|
934
|
+
return createOpenAICompatible({
|
|
935
|
+
name: provider.name,
|
|
936
|
+
baseURL: provider.baseURL,
|
|
937
|
+
apiKey,
|
|
938
|
+
headers: provider.headers,
|
|
939
|
+
fetch: trackedFetch
|
|
940
|
+
})(modelConfig.model, {}, { includeUsage: true });
|
|
941
|
+
case "openai-responses":
|
|
942
|
+
return createOpenAI({
|
|
943
|
+
baseURL: provider.baseURL,
|
|
944
|
+
apiKey,
|
|
945
|
+
headers: provider.headers,
|
|
946
|
+
// Relays fronting the Responses API sometimes batch several JSON
|
|
947
|
+
// events into one SSE data payload, which the SDK's parser cannot
|
|
948
|
+
// read; normalize the wire format back to one event per data line.
|
|
949
|
+
fetch: createSseNormalizingFetch(trackedFetch)
|
|
950
|
+
}).responses(modelConfig.model);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function reasoningEffortMetadata(config, modelName) {
|
|
954
|
+
const modelConfig = resolveModelConfig(config, modelName);
|
|
955
|
+
if (!modelConfig.reasoningEffort) return void 0;
|
|
956
|
+
const provider = config.providers.find((p) => p.name === modelConfig.provider);
|
|
957
|
+
if (!provider || provider.protocol === "anthropic") return void 0;
|
|
958
|
+
const key = provider.protocol === "openai-responses" ? "openai" : provider.name;
|
|
959
|
+
return { [key]: { reasoningEffort: modelConfig.reasoningEffort } };
|
|
960
|
+
}
|
|
961
|
+
|
|
713
962
|
// src/llm/retry.ts
|
|
714
963
|
var NON_RETRYABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
715
964
|
"AI_NoSuchToolError",
|
|
@@ -788,45 +1037,6 @@ function summarizeStreamError(error) {
|
|
|
788
1037
|
// src/llm/stream.ts
|
|
789
1038
|
import { streamText } from "ai";
|
|
790
1039
|
|
|
791
|
-
// src/llm/activity.ts
|
|
792
|
-
var trackers = /* @__PURE__ */ new WeakMap();
|
|
793
|
-
function streamActivity(signal) {
|
|
794
|
-
return trackers.get(signal);
|
|
795
|
-
}
|
|
796
|
-
function wrapFetchWithActivity(baseFetch = fetch) {
|
|
797
|
-
return async (input, init) => {
|
|
798
|
-
const response = await baseFetch(input, init);
|
|
799
|
-
const signal = init?.signal ?? (input instanceof Request ? input.signal : null);
|
|
800
|
-
if (!signal || !response.body) return response;
|
|
801
|
-
const tracker = { lastChunkAt: Date.now() };
|
|
802
|
-
trackers.set(signal, tracker);
|
|
803
|
-
const reader = response.body.getReader();
|
|
804
|
-
const body = new ReadableStream({
|
|
805
|
-
async pull(controller) {
|
|
806
|
-
try {
|
|
807
|
-
const { done, value } = await reader.read();
|
|
808
|
-
if (done) {
|
|
809
|
-
controller.close();
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
tracker.lastChunkAt = Date.now();
|
|
813
|
-
controller.enqueue(value);
|
|
814
|
-
} catch (error) {
|
|
815
|
-
controller.error(error);
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
cancel(reason) {
|
|
819
|
-
return reader.cancel(reason);
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
return new Response(body, {
|
|
823
|
-
status: response.status,
|
|
824
|
-
statusText: response.statusText,
|
|
825
|
-
headers: response.headers
|
|
826
|
-
});
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
|
|
830
1040
|
// src/llm/debug.ts
|
|
831
1041
|
import { appendFileSync } from "fs";
|
|
832
1042
|
import path2 from "path";
|
|
@@ -874,6 +1084,24 @@ var GENERATION_PART_TYPES = /* @__PURE__ */ new Set(["text-delta", "reasoning",
|
|
|
874
1084
|
var MAX_KEEPALIVE_SILENCE_MS = 10 * 6e4;
|
|
875
1085
|
var CACHE_READ_KEYS = ["cacheReadInputTokens", "cache_read_input_tokens"];
|
|
876
1086
|
var CACHED_PROMPT_KEYS = ["cachedPromptTokens", "cached_prompt_tokens", "cachedTokens"];
|
|
1087
|
+
function withAnthropicCacheBreakpoints(model, messages) {
|
|
1088
|
+
if (!model.provider.startsWith("anthropic")) return messages;
|
|
1089
|
+
const mark = (message) => ({
|
|
1090
|
+
...message,
|
|
1091
|
+
experimental_providerMetadata: {
|
|
1092
|
+
...message.experimental_providerMetadata,
|
|
1093
|
+
anthropic: {
|
|
1094
|
+
...message.experimental_providerMetadata?.anthropic,
|
|
1095
|
+
cacheControl: { type: "ephemeral" }
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
const result = messages.slice();
|
|
1100
|
+
if (result[0]?.role === "system") result[0] = mark(result[0]);
|
|
1101
|
+
const last = result.length - 1;
|
|
1102
|
+
if (last > 0 && result[last]) result[last] = mark(result[last]);
|
|
1103
|
+
return result;
|
|
1104
|
+
}
|
|
877
1105
|
function extractCacheUsage(providerMetadata) {
|
|
878
1106
|
if (!providerMetadata || typeof providerMetadata !== "object") return {};
|
|
879
1107
|
for (const value of Object.values(providerMetadata)) {
|
|
@@ -898,7 +1126,7 @@ async function* streamChat(opts) {
|
|
|
898
1126
|
const { openai: openaiMetadata, ...otherMetadata } = opts.providerMetadata ?? {};
|
|
899
1127
|
const result = streamText({
|
|
900
1128
|
model: opts.model,
|
|
901
|
-
messages: opts.messages,
|
|
1129
|
+
messages: withAnthropicCacheBreakpoints(opts.model, opts.messages),
|
|
902
1130
|
tools: opts.tools,
|
|
903
1131
|
abortSignal: controller.signal,
|
|
904
1132
|
maxTokens: opts.maxTokens,
|
|
@@ -2979,74 +3207,108 @@ var schema5 = z6.object({
|
|
|
2979
3207
|
offset: z6.number().int().positive().optional().describe("1-based line number to start reading from"),
|
|
2980
3208
|
limit: z6.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
|
|
2981
3209
|
});
|
|
2982
|
-
var
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
}
|
|
2997
|
-
if (st.isDirectory()) {
|
|
2998
|
-
return { content: `Path is a directory, not a file: ${args.path}`, isError: true };
|
|
2999
|
-
}
|
|
3000
|
-
const bytesToRead = Math.min(st.size, MAX_READ_BYTES);
|
|
3001
|
-
const fileCapped = st.size > MAX_READ_BYTES;
|
|
3002
|
-
const handle = await open(filePath, "r").catch(() => null);
|
|
3003
|
-
if (!handle) {
|
|
3004
|
-
return { content: `Failed to read ${args.path}`, isError: true };
|
|
3005
|
-
}
|
|
3006
|
-
let buf;
|
|
3007
|
-
try {
|
|
3008
|
-
const { bytesRead, buffer } = await handle.read(Buffer.alloc(bytesToRead), 0, bytesToRead, 0);
|
|
3009
|
-
buf = buffer.subarray(0, bytesRead);
|
|
3010
|
-
} finally {
|
|
3011
|
-
await handle.close().catch(() => {
|
|
3012
|
-
});
|
|
3013
|
-
}
|
|
3014
|
-
const decoded = decodeTextBuffer(buf);
|
|
3015
|
-
if ("binary" in decoded) {
|
|
3016
|
-
return { content: `Cannot read binary file: ${args.path}`, isError: true };
|
|
3017
|
-
}
|
|
3018
|
-
const lines = decoded.text.split("\n");
|
|
3019
|
-
const totalDesc = fileCapped ? `at least ${lines.length}` : `${lines.length}`;
|
|
3020
|
-
const start = args.offset ?? 1;
|
|
3021
|
-
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
3022
|
-
if (start > lines.length) {
|
|
3023
|
-
return { content: `(offset ${start} beyond end of file; file has ${totalDesc} lines)` };
|
|
3024
|
-
}
|
|
3025
|
-
const end = Math.min(lines.length, start - 1 + limit);
|
|
3026
|
-
const out = [];
|
|
3027
|
-
let size = 0;
|
|
3028
|
-
let truncatedBySize = false;
|
|
3029
|
-
for (let i = start - 1; i < end; i++) {
|
|
3030
|
-
const line = (lines[i] ?? "").replace(/\r$/, "");
|
|
3031
|
-
const numbered = `${i + 1} ${line}`;
|
|
3032
|
-
if (size + numbered.length > MAX_CHARS) {
|
|
3033
|
-
truncatedBySize = true;
|
|
3034
|
-
break;
|
|
3210
|
+
var READ_CACHE_MAX_ENTRIES = 200;
|
|
3211
|
+
function createReadFileTool() {
|
|
3212
|
+
const cache4 = /* @__PURE__ */ new Map();
|
|
3213
|
+
return {
|
|
3214
|
+
name: "read_file",
|
|
3215
|
+
description: "Read a text file and return its contents with line numbers. Output is truncated to 2000 lines or 100KB by default. Re-reading an unchanged file range returns a short note instead of the content.",
|
|
3216
|
+
permission: "read",
|
|
3217
|
+
parameters: schema5,
|
|
3218
|
+
reset: () => cache4.clear(),
|
|
3219
|
+
async execute(args, ctx) {
|
|
3220
|
+
const filePath = path11.resolve(ctx.cwd, args.path);
|
|
3221
|
+
const resolvedPath = await realpath2(filePath).catch(() => null);
|
|
3222
|
+
if (isSensitivePath(filePath) || resolvedPath !== null && isSensitivePath(resolvedPath)) {
|
|
3223
|
+
return { content: `Refused to read sensitive file: ${args.path}`, isError: true };
|
|
3035
3224
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3225
|
+
const st = await stat5(filePath).catch(() => null);
|
|
3226
|
+
if (!st) {
|
|
3227
|
+
return { content: `File not found: ${args.path}`, isError: true };
|
|
3228
|
+
}
|
|
3229
|
+
if (st.isDirectory()) {
|
|
3230
|
+
return { content: `Path is a directory, not a file: ${args.path}`, isError: true };
|
|
3231
|
+
}
|
|
3232
|
+
const start = args.offset ?? 1;
|
|
3233
|
+
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
3234
|
+
const cacheKey = resolvedPath ?? filePath;
|
|
3235
|
+
const hit = cache4.get(cacheKey);
|
|
3236
|
+
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
|
|
3237
|
+
const requestedEnd = Math.min(hit.totalLines, start - 1 + limit);
|
|
3238
|
+
if (start >= hit.start && requestedEnd <= hit.end && start <= hit.totalLines) {
|
|
3239
|
+
return {
|
|
3240
|
+
content: `[unchanged since last read: ${args.path} \u2014 lines ${start}-${requestedEnd} of ${hit.totalLines} are already in context and the file has not changed]`
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
const bytesToRead = Math.min(st.size, MAX_READ_BYTES);
|
|
3245
|
+
const fileCapped = st.size > MAX_READ_BYTES;
|
|
3246
|
+
const handle = await open(filePath, "r").catch(() => null);
|
|
3247
|
+
if (!handle) {
|
|
3248
|
+
return { content: `Failed to read ${args.path}`, isError: true };
|
|
3249
|
+
}
|
|
3250
|
+
let buf;
|
|
3251
|
+
try {
|
|
3252
|
+
const { bytesRead, buffer } = await handle.read(
|
|
3253
|
+
Buffer.alloc(bytesToRead),
|
|
3254
|
+
0,
|
|
3255
|
+
bytesToRead,
|
|
3256
|
+
0
|
|
3257
|
+
);
|
|
3258
|
+
buf = buffer.subarray(0, bytesRead);
|
|
3259
|
+
} finally {
|
|
3260
|
+
await handle.close().catch(() => {
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
const decoded = decodeTextBuffer(buf);
|
|
3264
|
+
if ("binary" in decoded) {
|
|
3265
|
+
return { content: `Cannot read binary file: ${args.path}`, isError: true };
|
|
3266
|
+
}
|
|
3267
|
+
const lines = decoded.text.split("\n");
|
|
3268
|
+
const totalDesc = fileCapped ? `at least ${lines.length}` : `${lines.length}`;
|
|
3269
|
+
if (start > lines.length) {
|
|
3270
|
+
return { content: `(offset ${start} beyond end of file; file has ${totalDesc} lines)` };
|
|
3271
|
+
}
|
|
3272
|
+
const end = Math.min(lines.length, start - 1 + limit);
|
|
3273
|
+
const out = [];
|
|
3274
|
+
let size = 0;
|
|
3275
|
+
let truncatedBySize = false;
|
|
3276
|
+
for (let i = start - 1; i < end; i++) {
|
|
3277
|
+
const line = (lines[i] ?? "").replace(/\r$/, "");
|
|
3278
|
+
const numbered = `${i + 1} ${line}`;
|
|
3279
|
+
if (size + numbered.length > MAX_CHARS) {
|
|
3280
|
+
truncatedBySize = true;
|
|
3281
|
+
break;
|
|
3282
|
+
}
|
|
3283
|
+
out.push(numbered);
|
|
3284
|
+
size += numbered.length;
|
|
3285
|
+
}
|
|
3286
|
+
const lastShown = start - 1 + out.length;
|
|
3287
|
+
if (truncatedBySize) {
|
|
3288
|
+
out.push(
|
|
3289
|
+
`... (truncated: output exceeds 100KB, showing lines ${start}-${lastShown} of ${totalDesc})`
|
|
3290
|
+
);
|
|
3291
|
+
} else if (end < lines.length || fileCapped) {
|
|
3292
|
+
out.push(`... (truncated: showing lines ${start}-${end} of ${totalDesc})`);
|
|
3293
|
+
}
|
|
3294
|
+
if (out.length > 0) {
|
|
3295
|
+
if (cache4.size >= READ_CACHE_MAX_ENTRIES) cache4.clear();
|
|
3296
|
+
const prev = hit?.mtimeMs === st.mtimeMs && hit.size === st.size ? hit : void 0;
|
|
3297
|
+
cache4.set(cacheKey, {
|
|
3298
|
+
mtimeMs: st.mtimeMs,
|
|
3299
|
+
size: st.size,
|
|
3300
|
+
totalLines: lines.length,
|
|
3301
|
+
// Extend the remembered range when the new read overlaps or abuts
|
|
3302
|
+
// the previous one; disjoint ranges replace it.
|
|
3303
|
+
start: prev && start <= prev.end + 1 ? Math.min(prev.start, start) : start,
|
|
3304
|
+
end: prev && lastShown >= prev.start - 1 ? Math.max(prev.end, lastShown) : lastShown
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
return { content: out.join("\n") };
|
|
3046
3308
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
var readFileTool = createReadFileTool();
|
|
3050
3312
|
|
|
3051
3313
|
// src/tools/fs/read-image.ts
|
|
3052
3314
|
import { readFile as readFile5, realpath as realpath3, stat as stat6 } from "fs/promises";
|
|
@@ -3274,9 +3536,160 @@ var writeFileTool = {
|
|
|
3274
3536
|
}
|
|
3275
3537
|
};
|
|
3276
3538
|
|
|
3277
|
-
// src/tools/
|
|
3539
|
+
// src/tools/screenshot.ts
|
|
3278
3540
|
import { z as z9 } from "zod";
|
|
3279
3541
|
|
|
3542
|
+
// src/core/screenshot.ts
|
|
3543
|
+
import { execFile as execFile3 } from "child_process";
|
|
3544
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3545
|
+
import { readFile as readFile6, unlink as unlink2 } from "fs/promises";
|
|
3546
|
+
import os5 from "os";
|
|
3547
|
+
import path15 from "path";
|
|
3548
|
+
import { promisify as promisify2 } from "util";
|
|
3549
|
+
var SCREENSHOT_TIMEOUT_MS = 2e4;
|
|
3550
|
+
var execFileAsync2 = promisify2(execFile3);
|
|
3551
|
+
function windowsScreenshotScript(outputFile) {
|
|
3552
|
+
const output = outputFile.replace(/'/g, "''");
|
|
3553
|
+
return [
|
|
3554
|
+
`try { Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(int v);' -Name Dpi -Namespace StarShot -ErrorAction Stop; [void][StarShot.Dpi]::SetProcessDpiAwarenessContext(-4) } catch { }`,
|
|
3555
|
+
"Add-Type -AssemblyName System.Windows.Forms,System.Drawing",
|
|
3556
|
+
"$b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds",
|
|
3557
|
+
"$bmp = New-Object System.Drawing.Bitmap($b.Width, $b.Height)",
|
|
3558
|
+
"$g = [System.Drawing.Graphics]::FromImage($bmp)",
|
|
3559
|
+
"$g.CopyFromScreen($b.Left, $b.Top, 0, 0, $bmp.Size)",
|
|
3560
|
+
`$bmp.Save('${output}', [System.Drawing.Imaging.ImageFormat]::Png)`,
|
|
3561
|
+
"$g.Dispose(); $bmp.Dispose()"
|
|
3562
|
+
].join("; ");
|
|
3563
|
+
}
|
|
3564
|
+
function screenshotPlan(platform, outputFile, env = process.env, tools = {}) {
|
|
3565
|
+
if (platform === "win32") {
|
|
3566
|
+
return {
|
|
3567
|
+
command: "powershell.exe",
|
|
3568
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", windowsScreenshotScript(outputFile)]
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3571
|
+
if (platform === "darwin") {
|
|
3572
|
+
return { command: "screencapture", args: ["-x", "-D", "1", outputFile] };
|
|
3573
|
+
}
|
|
3574
|
+
if (platform === "linux") {
|
|
3575
|
+
if (env.WAYLAND_DISPLAY) {
|
|
3576
|
+
if (tools.grim) return { command: "grim", args: [outputFile] };
|
|
3577
|
+
if (tools["gnome-screenshot"]) {
|
|
3578
|
+
return { command: "gnome-screenshot", args: ["-f", outputFile] };
|
|
3579
|
+
}
|
|
3580
|
+
if (tools.spectacle) {
|
|
3581
|
+
return { command: "spectacle", args: ["-b", "-n", "-o", outputFile] };
|
|
3582
|
+
}
|
|
3583
|
+
return null;
|
|
3584
|
+
}
|
|
3585
|
+
if (env.DISPLAY) {
|
|
3586
|
+
if (tools.import) return { command: "import", args: ["-window", "root", outputFile] };
|
|
3587
|
+
if (tools.scrot) return { command: "scrot", args: ["--overwrite", outputFile] };
|
|
3588
|
+
if (tools["gnome-screenshot"]) {
|
|
3589
|
+
return { command: "gnome-screenshot", args: ["-f", outputFile] };
|
|
3590
|
+
}
|
|
3591
|
+
if (tools.spectacle) {
|
|
3592
|
+
return { command: "spectacle", args: ["-b", "-n", "-o", outputFile] };
|
|
3593
|
+
}
|
|
3594
|
+
return null;
|
|
3595
|
+
}
|
|
3596
|
+
return null;
|
|
3597
|
+
}
|
|
3598
|
+
return null;
|
|
3599
|
+
}
|
|
3600
|
+
function noScreenshotToolMessage(platform, env = process.env) {
|
|
3601
|
+
if (platform === "linux") {
|
|
3602
|
+
if (!env.WAYLAND_DISPLAY && !env.DISPLAY) {
|
|
3603
|
+
return "no display server (neither DISPLAY nor WAYLAND_DISPLAY is set) \u2014 this session is headless (e.g. SSH), there is no screen to capture";
|
|
3604
|
+
}
|
|
3605
|
+
return "no screenshot tool found \u2014 install one of: grim or spectacle (Wayland), scrot or ImageMagick's import (X11), or gnome-screenshot";
|
|
3606
|
+
}
|
|
3607
|
+
return `screenshots are not supported on this platform (${platform})`;
|
|
3608
|
+
}
|
|
3609
|
+
async function commandExists2(command) {
|
|
3610
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
3611
|
+
try {
|
|
3612
|
+
await execFileAsync2(checker, [command], { timeout: SCREENSHOT_TIMEOUT_MS });
|
|
3613
|
+
return true;
|
|
3614
|
+
} catch {
|
|
3615
|
+
return false;
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
var LINUX_TOOL_NAMES = ["grim", "gnome-screenshot", "spectacle", "import", "scrot"];
|
|
3619
|
+
async function captureScreenshot() {
|
|
3620
|
+
const outputFile = path15.join(os5.tmpdir(), `star-screenshot-${randomUUID3()}.png`);
|
|
3621
|
+
try {
|
|
3622
|
+
const tools = {};
|
|
3623
|
+
if (process.platform === "linux") {
|
|
3624
|
+
const found = await Promise.all(LINUX_TOOL_NAMES.map(commandExists2));
|
|
3625
|
+
for (const [i, name] of LINUX_TOOL_NAMES.entries()) {
|
|
3626
|
+
tools[name] = found[i] ?? false;
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
const plan = screenshotPlan(process.platform, outputFile, process.env, tools);
|
|
3630
|
+
if (!plan) {
|
|
3631
|
+
return { ok: false, error: noScreenshotToolMessage(process.platform) };
|
|
3632
|
+
}
|
|
3633
|
+
try {
|
|
3634
|
+
await execFileAsync2(plan.command, plan.args, { timeout: SCREENSHOT_TIMEOUT_MS });
|
|
3635
|
+
} catch (err) {
|
|
3636
|
+
const detail = err instanceof Error && err.message ? ` (${err.message})` : "";
|
|
3637
|
+
return { ok: false, error: `${plan.command} failed to capture the screen${detail}` };
|
|
3638
|
+
}
|
|
3639
|
+
const buf = await readFile6(outputFile).catch(() => null);
|
|
3640
|
+
if (!buf || buf.length === 0) {
|
|
3641
|
+
return { ok: false, error: `${plan.command} produced no image file` };
|
|
3642
|
+
}
|
|
3643
|
+
if (!probeImageDimensions(buf)) {
|
|
3644
|
+
return { ok: false, error: `${plan.command} produced an unrecognized image file` };
|
|
3645
|
+
}
|
|
3646
|
+
const downsampled = await downsampleImageIfNeeded({
|
|
3647
|
+
path: "screen",
|
|
3648
|
+
mimeType: "image/png",
|
|
3649
|
+
data: buf.toString("base64")
|
|
3650
|
+
});
|
|
3651
|
+
if (downsampled.oversized && !downsampled.resized) {
|
|
3652
|
+
return {
|
|
3653
|
+
ok: false,
|
|
3654
|
+
error: "screenshot exceeds 2000px and could not be downsampled on this platform"
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
return { ok: true, image: downsampled.image };
|
|
3658
|
+
} finally {
|
|
3659
|
+
await unlink2(outputFile).catch(() => {
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
// src/tools/screenshot.ts
|
|
3665
|
+
var schema8 = z9.object({});
|
|
3666
|
+
function createScreenshotTool(capture = captureScreenshot) {
|
|
3667
|
+
return {
|
|
3668
|
+
name: "screenshot",
|
|
3669
|
+
description: "Capture a screenshot of the primary display and see it. Use this to check the actual visual result of a running program (web page, GUI) \u2014 e.g. after starting a dev server \u2014 instead of inferring from code. The capture shows exactly what is visible on screen, including overlapping windows: content hidden behind other windows cannot be seen, so if a specific window matters ask the user to bring it to the front first. Note the capture may include anything else currently on screen.",
|
|
3670
|
+
permission: "read",
|
|
3671
|
+
parameters: schema8,
|
|
3672
|
+
async execute() {
|
|
3673
|
+
const result = await capture();
|
|
3674
|
+
if (!result.ok) {
|
|
3675
|
+
return { content: `Screenshot failed: ${result.error}`, isError: true };
|
|
3676
|
+
}
|
|
3677
|
+
const image = result.image;
|
|
3678
|
+
const dims = probeImageDimensions(Buffer.from(image.data, "base64"));
|
|
3679
|
+
const sizeKB = Math.round(image.data.length / 1024);
|
|
3680
|
+
const desc = dims ? `${dims.width}x${dims.height}` : "unknown dimensions";
|
|
3681
|
+
return {
|
|
3682
|
+
content: `Screenshot captured (${desc}, ~${sizeKB}KB). The image is attached below \u2014 look at it now.`,
|
|
3683
|
+
images: [image]
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
};
|
|
3687
|
+
}
|
|
3688
|
+
var screenshotTool = createScreenshotTool();
|
|
3689
|
+
|
|
3690
|
+
// src/tools/tasks.ts
|
|
3691
|
+
import { z as z10 } from "zod";
|
|
3692
|
+
|
|
3280
3693
|
// src/agent/agent-tasks.ts
|
|
3281
3694
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
3282
3695
|
var MAX_FINISHED_RECORDS2 = 50;
|
|
@@ -3446,8 +3859,8 @@ function formatTaskFinished(task) {
|
|
|
3446
3859
|
}
|
|
3447
3860
|
|
|
3448
3861
|
// src/tools/tasks.ts
|
|
3449
|
-
var idSchema =
|
|
3450
|
-
id:
|
|
3862
|
+
var idSchema = z10.object({
|
|
3863
|
+
id: z10.string().describe("Task id, e.g. task-1 or agent-1")
|
|
3451
3864
|
});
|
|
3452
3865
|
function truncateOutput(s) {
|
|
3453
3866
|
if (s.length <= MAX_OUTPUT) return s;
|
|
@@ -3464,7 +3877,7 @@ var taskListTool = {
|
|
|
3464
3877
|
name: "task_list",
|
|
3465
3878
|
description: "List background tasks (shell commands and subagents) with status and runtime.",
|
|
3466
3879
|
permission: "read",
|
|
3467
|
-
parameters:
|
|
3880
|
+
parameters: z10.object({}),
|
|
3468
3881
|
execute() {
|
|
3469
3882
|
const shell = formatTaskList(defaultTaskManager.list());
|
|
3470
3883
|
const agents = defaultAgentTasks.list();
|
|
@@ -3523,20 +3936,20 @@ var taskKillTool = {
|
|
|
3523
3936
|
};
|
|
3524
3937
|
|
|
3525
3938
|
// src/tools/todo.ts
|
|
3526
|
-
import { mkdir as mkdir4, readFile as
|
|
3527
|
-
import
|
|
3528
|
-
import { z as
|
|
3529
|
-
var todoItemSchema =
|
|
3530
|
-
id:
|
|
3531
|
-
title:
|
|
3532
|
-
status:
|
|
3939
|
+
import { mkdir as mkdir4, readFile as readFile7, rename, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3940
|
+
import path16 from "path";
|
|
3941
|
+
import { z as z11 } from "zod";
|
|
3942
|
+
var todoItemSchema = z11.object({
|
|
3943
|
+
id: z11.number().int(),
|
|
3944
|
+
title: z11.string(),
|
|
3945
|
+
status: z11.enum(["pending", "in_progress", "done"])
|
|
3533
3946
|
});
|
|
3534
|
-
var writeSchema =
|
|
3535
|
-
todos:
|
|
3947
|
+
var writeSchema = z11.object({
|
|
3948
|
+
todos: z11.array(todoItemSchema).describe("The full todo list, replacing the current one")
|
|
3536
3949
|
});
|
|
3537
|
-
var readSchema =
|
|
3950
|
+
var readSchema = z11.object({});
|
|
3538
3951
|
function fileFor(cwd) {
|
|
3539
|
-
return
|
|
3952
|
+
return path16.join(cwd, ".star", "todos.json");
|
|
3540
3953
|
}
|
|
3541
3954
|
var persistGuard = () => true;
|
|
3542
3955
|
function setTodoPersistGuard(guard) {
|
|
@@ -3556,7 +3969,7 @@ var TodoStore = class {
|
|
|
3556
3969
|
async load(cwd) {
|
|
3557
3970
|
let raw;
|
|
3558
3971
|
try {
|
|
3559
|
-
raw = await
|
|
3972
|
+
raw = await readFile7(fileFor(cwd), "utf8");
|
|
3560
3973
|
} catch {
|
|
3561
3974
|
return;
|
|
3562
3975
|
}
|
|
@@ -3569,15 +3982,22 @@ var TodoStore = class {
|
|
|
3569
3982
|
}
|
|
3570
3983
|
}
|
|
3571
3984
|
// Atomic write (tmp + rename): a crash or failed save can never leave a
|
|
3572
|
-
// truncated todos.json behind.
|
|
3985
|
+
// truncated todos.json behind. An empty or fully completed list has no
|
|
3986
|
+
// resume value — rehydrating it would only restore finished work — so the
|
|
3987
|
+
// file is deleted instead of persisted.
|
|
3573
3988
|
async save(cwd) {
|
|
3574
3989
|
if (!persistGuard()) {
|
|
3575
3990
|
return;
|
|
3576
3991
|
}
|
|
3577
|
-
|
|
3992
|
+
const items = this.list();
|
|
3578
3993
|
const file = fileFor(cwd);
|
|
3994
|
+
if (items.length === 0 || items.every((it) => it.status === "done")) {
|
|
3995
|
+
await rm3(file, { force: true });
|
|
3996
|
+
return;
|
|
3997
|
+
}
|
|
3998
|
+
await mkdir4(path16.join(cwd, ".star"), { recursive: true });
|
|
3579
3999
|
const tmp = `${file}.tmp`;
|
|
3580
|
-
await writeFile6(tmp, `${JSON.stringify(
|
|
4000
|
+
await writeFile6(tmp, `${JSON.stringify(items, null, 2)}
|
|
3581
4001
|
`);
|
|
3582
4002
|
await rename(tmp, file);
|
|
3583
4003
|
}
|
|
@@ -3650,13 +4070,13 @@ function createTodoTools(store = defaultStore) {
|
|
|
3650
4070
|
}
|
|
3651
4071
|
|
|
3652
4072
|
// src/tools/web/fetch.ts
|
|
3653
|
-
import { z as
|
|
4073
|
+
import { z as z12 } from "zod";
|
|
3654
4074
|
var DEFAULT_MAX_CHARS = 2e4;
|
|
3655
4075
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
3656
4076
|
var TIMEOUT_MS = 3e4;
|
|
3657
|
-
var
|
|
3658
|
-
url:
|
|
3659
|
-
maxChars:
|
|
4077
|
+
var schema9 = z12.object({
|
|
4078
|
+
url: z12.string().describe("The http(s) URL to fetch"),
|
|
4079
|
+
maxChars: z12.number().int().positive().optional().describe("Maximum characters of text to return (default 20000)")
|
|
3660
4080
|
});
|
|
3661
4081
|
function safeCodePoint(n, raw) {
|
|
3662
4082
|
if (!Number.isInteger(n) || n < 0 || n > 1114111) {
|
|
@@ -3727,7 +4147,7 @@ var webFetchTool = {
|
|
|
3727
4147
|
name: "web_fetch",
|
|
3728
4148
|
description: "Fetch a URL over http(s) and return its contents as readable text. HTML pages are converted to plain text (tags, scripts and styles removed). Output is truncated to maxChars (default 20000). Can reach internal/intranet addresses, so do not use it for untrusted URLs.",
|
|
3729
4149
|
permission: "read",
|
|
3730
|
-
parameters:
|
|
4150
|
+
parameters: schema9,
|
|
3731
4151
|
async execute(args, ctx) {
|
|
3732
4152
|
let url;
|
|
3733
4153
|
try {
|
|
@@ -3790,15 +4210,15 @@ var webFetchTool = {
|
|
|
3790
4210
|
};
|
|
3791
4211
|
|
|
3792
4212
|
// src/tools/web/search.ts
|
|
3793
|
-
import { z as
|
|
4213
|
+
import { z as z13 } from "zod";
|
|
3794
4214
|
var TIMEOUT_MS2 = 15e3;
|
|
3795
4215
|
var DEFAULT_MAX_RESULTS = 5;
|
|
3796
4216
|
var MAX_RESULTS_LIMIT = 10;
|
|
3797
4217
|
var SEARCH_URL = "https://html.duckduckgo.com/html/?q=";
|
|
3798
4218
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
3799
|
-
var
|
|
3800
|
-
query:
|
|
3801
|
-
maxResults:
|
|
4219
|
+
var schema10 = z13.object({
|
|
4220
|
+
query: z13.string().min(1).describe("The search query"),
|
|
4221
|
+
maxResults: z13.number().int().min(1).max(MAX_RESULTS_LIMIT).optional().describe(
|
|
3802
4222
|
`Maximum number of results to return (default ${DEFAULT_MAX_RESULTS}, max ${MAX_RESULTS_LIMIT})`
|
|
3803
4223
|
)
|
|
3804
4224
|
});
|
|
@@ -3849,7 +4269,7 @@ var webSearchTool = {
|
|
|
3849
4269
|
name: "web_search",
|
|
3850
4270
|
description: "Search the web using DuckDuckGo and return a numbered list of results with title, URL and snippet. No API key required. Use web_fetch to read a result's full page.",
|
|
3851
4271
|
permission: "read",
|
|
3852
|
-
parameters:
|
|
4272
|
+
parameters: schema10,
|
|
3853
4273
|
async execute(args, ctx) {
|
|
3854
4274
|
const maxResults = Math.min(args.maxResults ?? DEFAULT_MAX_RESULTS, MAX_RESULTS_LIMIT);
|
|
3855
4275
|
const url = `${SEARCH_URL}${encodeURIComponent(args.query)}`;
|
|
@@ -3911,8 +4331,11 @@ var ToolRegistry = class {
|
|
|
3911
4331
|
// their own instance so a child's todo_write cannot clobber the parent list.
|
|
3912
4332
|
constructor(todoStore) {
|
|
3913
4333
|
for (const tool of [
|
|
3914
|
-
|
|
4334
|
+
// Fresh per registry (i.e. per agent loop): the unchanged-since-last-
|
|
4335
|
+
// read cache must not leak across loops, whose contexts differ.
|
|
4336
|
+
createReadFileTool(),
|
|
3915
4337
|
readImageTool,
|
|
4338
|
+
screenshotTool,
|
|
3916
4339
|
writeFileTool,
|
|
3917
4340
|
editFileTool,
|
|
3918
4341
|
globTool,
|
|
@@ -3931,6 +4354,12 @@ var ToolRegistry = class {
|
|
|
3931
4354
|
register(tool) {
|
|
3932
4355
|
this.tools.set(tool.name, tool);
|
|
3933
4356
|
}
|
|
4357
|
+
// Drops per-session volatile tool state after a wholesale history rewrite
|
|
4358
|
+
// (compaction, /undo, resume): anything a tool remembers as "already sent
|
|
4359
|
+
// to the model" may no longer be in the context.
|
|
4360
|
+
resetVolatileState() {
|
|
4361
|
+
for (const tool of this.tools.values()) tool.reset?.();
|
|
4362
|
+
}
|
|
3934
4363
|
get(name) {
|
|
3935
4364
|
return this.tools.get(name);
|
|
3936
4365
|
}
|
|
@@ -3944,12 +4373,12 @@ var ToolRegistry = class {
|
|
|
3944
4373
|
|
|
3945
4374
|
// src/agent/project-memory.ts
|
|
3946
4375
|
import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
3947
|
-
import
|
|
4376
|
+
import path17 from "path";
|
|
3948
4377
|
var PROJECT_MEMORY_FILE = "AGENTS.md";
|
|
3949
4378
|
var PROJECT_MEMORY_MAX_CHARS = 32 * 1024;
|
|
3950
4379
|
var cache = /* @__PURE__ */ new Map();
|
|
3951
4380
|
function readProjectMemory(cwd) {
|
|
3952
|
-
const file =
|
|
4381
|
+
const file = path17.join(cwd, PROJECT_MEMORY_FILE);
|
|
3953
4382
|
let mtimeMs;
|
|
3954
4383
|
try {
|
|
3955
4384
|
mtimeMs = statSync2(file).mtimeMs;
|
|
@@ -3979,8 +4408,8 @@ ${body}`;
|
|
|
3979
4408
|
|
|
3980
4409
|
// src/agent/skills.ts
|
|
3981
4410
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "fs";
|
|
3982
|
-
import
|
|
3983
|
-
import { z as
|
|
4411
|
+
import path18 from "path";
|
|
4412
|
+
import { z as z14 } from "zod";
|
|
3984
4413
|
var SKILL_FILE = "SKILL.md";
|
|
3985
4414
|
var SKILL_MAX_CHARS = 32 * 1024;
|
|
3986
4415
|
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
@@ -4010,7 +4439,7 @@ function parseSkillFile(filePath, dirName, scope) {
|
|
|
4010
4439
|
const name = parsed?.meta.name || dirName;
|
|
4011
4440
|
const description = parsed?.meta.description;
|
|
4012
4441
|
if (parsed && NAME_PATTERN.test(name) && description) {
|
|
4013
|
-
skill = { name, description, filePath, dir:
|
|
4442
|
+
skill = { name, description, filePath, dir: path18.dirname(filePath), scope };
|
|
4014
4443
|
}
|
|
4015
4444
|
} catch {
|
|
4016
4445
|
skill = null;
|
|
@@ -4027,14 +4456,14 @@ function scanDir(base, scope, into) {
|
|
|
4027
4456
|
}
|
|
4028
4457
|
for (const entry of entries.sort()) {
|
|
4029
4458
|
if (!NAME_PATTERN.test(entry)) continue;
|
|
4030
|
-
const parsed = parseSkillFile(
|
|
4459
|
+
const parsed = parseSkillFile(path18.join(base, entry, SKILL_FILE), entry, scope);
|
|
4031
4460
|
if (parsed) into.set(parsed.name, parsed);
|
|
4032
4461
|
}
|
|
4033
4462
|
}
|
|
4034
4463
|
function discoverSkills(cwd, home = starHome()) {
|
|
4035
4464
|
const byName = /* @__PURE__ */ new Map();
|
|
4036
|
-
scanDir(
|
|
4037
|
-
scanDir(
|
|
4465
|
+
scanDir(path18.join(home, "skills"), "user", byName);
|
|
4466
|
+
scanDir(path18.join(cwd, ".star", "skills"), "project", byName);
|
|
4038
4467
|
return [...byName.values()];
|
|
4039
4468
|
}
|
|
4040
4469
|
function formatSkillsBlock(skills) {
|
|
@@ -4065,8 +4494,8 @@ function createSkillTool(deps) {
|
|
|
4065
4494
|
name: "skill",
|
|
4066
4495
|
description: "Load the full instructions of a skill by name. Available skills are listed in the system prompt when present; call this before starting work that matches one of them.",
|
|
4067
4496
|
permission: "read",
|
|
4068
|
-
parameters:
|
|
4069
|
-
name:
|
|
4497
|
+
parameters: z14.object({
|
|
4498
|
+
name: z14.string().describe("Name of the skill to load, as listed in the system prompt.")
|
|
4070
4499
|
}),
|
|
4071
4500
|
async execute(args) {
|
|
4072
4501
|
const skills = discoverSkills(deps.cwd, deps.home);
|
|
@@ -4096,7 +4525,7 @@ function createSkillTool(deps) {
|
|
|
4096
4525
|
}
|
|
4097
4526
|
|
|
4098
4527
|
// src/agent/subagent.ts
|
|
4099
|
-
import { z as
|
|
4528
|
+
import { z as z15 } from "zod";
|
|
4100
4529
|
|
|
4101
4530
|
// src/tools/index.ts
|
|
4102
4531
|
function createDefaultRegistry(todoStore) {
|
|
@@ -4137,7 +4566,7 @@ function convertUsagePricing(usage, from, to, config) {
|
|
|
4137
4566
|
return result;
|
|
4138
4567
|
}
|
|
4139
4568
|
async function runSubagent(deps, args, cwd, signal) {
|
|
4140
|
-
const { AgentLoop: AgentLoop2 } = await import("./loop-
|
|
4569
|
+
const { AgentLoop: AgentLoop2 } = await import("./loop-2MOFLXTG.js");
|
|
4141
4570
|
const child = new AgentLoop2({
|
|
4142
4571
|
model: deps.model,
|
|
4143
4572
|
registry: createDefaultRegistry(),
|
|
@@ -4208,10 +4637,10 @@ function createSubagentTool(deps) {
|
|
|
4208
4637
|
name: "subagent",
|
|
4209
4638
|
description: "Spawn a subagent with its own agent loop to handle a focused subtask (research, exploration, or an isolated change). The subagent has the same tools but cannot spawn further subagents. Returns the subagent's final report. Prefer this for self-contained work that would otherwise clutter the main conversation. Set run_in_background to start it without blocking: independent subtasks then run concurrently and their reports are delivered to you automatically when they finish (also inspectable via task_list/task_output/task_kill).",
|
|
4210
4639
|
permission: "exec",
|
|
4211
|
-
parameters:
|
|
4212
|
-
prompt:
|
|
4213
|
-
description:
|
|
4214
|
-
run_in_background:
|
|
4640
|
+
parameters: z15.object({
|
|
4641
|
+
prompt: z15.string().describe("Complete, self-contained instructions for the subagent."),
|
|
4642
|
+
description: z15.string().optional().describe("Short label for the subtask, shown in the result header."),
|
|
4643
|
+
run_in_background: z15.boolean().optional().describe(
|
|
4215
4644
|
"Run concurrently in the background and return a task id immediately (default false). Use for independent subtasks that can run in parallel."
|
|
4216
4645
|
)
|
|
4217
4646
|
}),
|
|
@@ -4238,13 +4667,13 @@ function createSubagentTool(deps) {
|
|
|
4238
4667
|
|
|
4239
4668
|
// src/agent/user-memory.ts
|
|
4240
4669
|
import { appendFileSync as appendFileSync2, mkdirSync, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
4241
|
-
import
|
|
4242
|
-
import { z as
|
|
4670
|
+
import path19 from "path";
|
|
4671
|
+
import { z as z16 } from "zod";
|
|
4243
4672
|
var USER_MEMORY_MAX_CHARS = 32 * 1024;
|
|
4244
4673
|
var REMEMBER_TEXT_MAX_CHARS = 500;
|
|
4245
4674
|
var cache3 = /* @__PURE__ */ new Map();
|
|
4246
4675
|
function readUserMemory(home = starHome()) {
|
|
4247
|
-
const file =
|
|
4676
|
+
const file = path19.join(home, "MEMORY.md");
|
|
4248
4677
|
let mtimeMs;
|
|
4249
4678
|
try {
|
|
4250
4679
|
mtimeMs = statSync4(file).mtimeMs;
|
|
@@ -4276,7 +4705,7 @@ function readUserMemory(home = starHome()) {
|
|
|
4276
4705
|
}
|
|
4277
4706
|
function appendUserMemory(text, home = starHome()) {
|
|
4278
4707
|
mkdirSync(home, { recursive: true });
|
|
4279
|
-
appendFileSync2(
|
|
4708
|
+
appendFileSync2(path19.join(home, "MEMORY.md"), `- ${text}
|
|
4280
4709
|
`, "utf8");
|
|
4281
4710
|
}
|
|
4282
4711
|
function createRememberTool(deps = {}) {
|
|
@@ -4284,8 +4713,8 @@ function createRememberTool(deps = {}) {
|
|
|
4284
4713
|
name: "remember",
|
|
4285
4714
|
description: `Save a note to the user's long-term memory (${userMemoryPath()}), injected into every future session. ONLY use when the user explicitly asks to remember or save something. Never use autonomously.`,
|
|
4286
4715
|
permission: "write",
|
|
4287
|
-
parameters:
|
|
4288
|
-
text:
|
|
4716
|
+
parameters: z16.object({
|
|
4717
|
+
text: z16.string().max(REMEMBER_TEXT_MAX_CHARS).describe("The note to save, as a single concise sentence.")
|
|
4289
4718
|
}),
|
|
4290
4719
|
async execute(args) {
|
|
4291
4720
|
appendUserMemory(args.text, deps.home);
|
|
@@ -4310,6 +4739,24 @@ function finalNudgeWarning(autoContinues, maxAutoContinues) {
|
|
|
4310
4739
|
return autoContinues >= maxAutoContinues ? " This is the final automatic continuation \u2014 if your next reply again has no tool calls, nudging stops and the turn is handed back to the user. Finish the work now or summarize what remains." : "";
|
|
4311
4740
|
}
|
|
4312
4741
|
var COMPLETION_CHECK_TIMEOUT_MS = 3e4;
|
|
4742
|
+
function attachTurnContext(messages, context) {
|
|
4743
|
+
if (!context) return messages;
|
|
4744
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
4745
|
+
const message = messages[i];
|
|
4746
|
+
if (message?.role !== "user") continue;
|
|
4747
|
+
const reminder = {
|
|
4748
|
+
type: "text",
|
|
4749
|
+
text: `<system-reminder>
|
|
4750
|
+
${context}
|
|
4751
|
+
</system-reminder>`
|
|
4752
|
+
};
|
|
4753
|
+
const content = typeof message.content === "string" ? message.content ? [reminder, { type: "text", text: message.content }] : [reminder] : [reminder, ...message.content];
|
|
4754
|
+
const copy = messages.slice();
|
|
4755
|
+
copy[i] = { ...message, content };
|
|
4756
|
+
return copy;
|
|
4757
|
+
}
|
|
4758
|
+
return messages;
|
|
4759
|
+
}
|
|
4313
4760
|
async function checkTaskComplete(model, request, finalReply, signal, temperature) {
|
|
4314
4761
|
const controller = new AbortController();
|
|
4315
4762
|
const timer = setTimeout(() => controller.abort(), COMPLETION_CHECK_TIMEOUT_MS);
|
|
@@ -4417,6 +4864,16 @@ var AgentLoop = class {
|
|
|
4417
4864
|
activeTurnUserIndex = -1;
|
|
4418
4865
|
titleScheduled = false;
|
|
4419
4866
|
titleInput;
|
|
4867
|
+
// Rendered git context for the in-flight turn (null when unavailable);
|
|
4868
|
+
// snapshotted at turn start so every step of the turn sends the same block.
|
|
4869
|
+
turnGitContext = null;
|
|
4870
|
+
// Lazily resolved auxiliary model (config.smallModel) for cheap side calls
|
|
4871
|
+
// — compaction summaries, session titles, completion checks. Undefined =
|
|
4872
|
+
// use the main model; a resolution failure is surfaced once as a notice.
|
|
4873
|
+
auxModelResolved = false;
|
|
4874
|
+
auxModelValue;
|
|
4875
|
+
auxTemperatureValue;
|
|
4876
|
+
auxModelError = null;
|
|
4420
4877
|
confirmHandler;
|
|
4421
4878
|
// Hook failures never block the turn (except an explicit PreToolUse block);
|
|
4422
4879
|
// their stderr surfaces through this callback (REPL system message / stderr
|
|
@@ -4462,6 +4919,29 @@ var AgentLoop = class {
|
|
|
4462
4919
|
getMessages() {
|
|
4463
4920
|
return this.messages;
|
|
4464
4921
|
}
|
|
4922
|
+
// Auxiliary model for cheap side calls: config.smallModel, resolved once.
|
|
4923
|
+
// An unresolvable name/key degrades to the main model instead of breaking
|
|
4924
|
+
// turns; the failure rides out once as a notice at the next turn start.
|
|
4925
|
+
resolveAuxModel() {
|
|
4926
|
+
if (this.auxModelResolved) return;
|
|
4927
|
+
this.auxModelResolved = true;
|
|
4928
|
+
const name = this.opts.config.smallModel;
|
|
4929
|
+
if (!name) return;
|
|
4930
|
+
try {
|
|
4931
|
+
this.auxModelValue = createModel(this.opts.config, name);
|
|
4932
|
+
this.auxTemperatureValue = resolveModelConfig(this.opts.config, name).temperature ?? this.opts.temperature;
|
|
4933
|
+
} catch (error) {
|
|
4934
|
+
this.auxModelError = `smallModel "${name}" could not be resolved (${error instanceof Error ? error.message : String(error)}); using the main model for auxiliary calls.`;
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
getAuxModel() {
|
|
4938
|
+
this.resolveAuxModel();
|
|
4939
|
+
return this.auxModelValue ?? this.opts.model;
|
|
4940
|
+
}
|
|
4941
|
+
getAuxTemperature() {
|
|
4942
|
+
this.resolveAuxModel();
|
|
4943
|
+
return this.auxModelValue ? this.auxTemperatureValue : this.opts.temperature;
|
|
4944
|
+
}
|
|
4465
4945
|
// Rebinds the per-call provider metadata (e.g. after the reasoning effort
|
|
4466
4946
|
// for the current model changed via /model) without recreating the loop.
|
|
4467
4947
|
setProviderMetadata(metadata) {
|
|
@@ -4502,6 +4982,7 @@ var AgentLoop = class {
|
|
|
4502
4982
|
this.messages = reconcileToolCalls(messages);
|
|
4503
4983
|
this.turnMarkers = [];
|
|
4504
4984
|
this.redoStack = [];
|
|
4985
|
+
this.opts.registry?.resetVolatileState();
|
|
4505
4986
|
}
|
|
4506
4987
|
// Read-only counterpart of retractLastTurn: how many messages /undo would
|
|
4507
4988
|
// drop, which snapshot turn's file changes it would revert (undefined =
|
|
@@ -4533,6 +5014,7 @@ var AgentLoop = class {
|
|
|
4533
5014
|
this.lastUndoneLabel = messageText(this.messages[userIndex]).slice(0, REDO_LABEL_MAX);
|
|
4534
5015
|
this.messages = result.messages;
|
|
4535
5016
|
await this.opts.sessionStore?.replaceMessages([...this.messages]);
|
|
5017
|
+
this.opts.registry?.resetVolatileState();
|
|
4536
5018
|
const last = this.turnMarkers[this.turnMarkers.length - 1];
|
|
4537
5019
|
let turn;
|
|
4538
5020
|
let tree;
|
|
@@ -4614,6 +5096,7 @@ var AgentLoop = class {
|
|
|
4614
5096
|
if (removed === 0) return 0;
|
|
4615
5097
|
this.messages = this.messages.slice(0, cut);
|
|
4616
5098
|
await this.opts.sessionStore?.replaceMessages([...this.messages]);
|
|
5099
|
+
this.opts.registry?.resetVolatileState();
|
|
4617
5100
|
this.turnMarkers = this.turnMarkers.filter((m) => m.userIndex < cut);
|
|
4618
5101
|
this.redoStack = [];
|
|
4619
5102
|
return removed;
|
|
@@ -4649,7 +5132,7 @@ var AgentLoop = class {
|
|
|
4649
5132
|
const input = this.titleInput;
|
|
4650
5133
|
if (!input) return;
|
|
4651
5134
|
this.titleScheduled = true;
|
|
4652
|
-
scheduleSessionTitle(store, input, this.
|
|
5135
|
+
scheduleSessionTitle(store, input, this.getAuxModel());
|
|
4653
5136
|
}
|
|
4654
5137
|
// A user interrupt (Esc) ends the turn mid-stream, before the normal
|
|
4655
5138
|
// assistant-message persistence runs. Whatever text and tool calls already
|
|
@@ -4694,6 +5177,13 @@ var AgentLoop = class {
|
|
|
4694
5177
|
}
|
|
4695
5178
|
async *stream(input, signal, opts) {
|
|
4696
5179
|
this.syncSystemMessage();
|
|
5180
|
+
const git = getGitSummaryCached(this.opts.cwd);
|
|
5181
|
+
this.turnGitContext = git ? formatGitSummary(git) : null;
|
|
5182
|
+
this.resolveAuxModel();
|
|
5183
|
+
if (this.auxModelError) {
|
|
5184
|
+
yield { type: "notice", message: this.auxModelError };
|
|
5185
|
+
this.auxModelError = null;
|
|
5186
|
+
}
|
|
4697
5187
|
const inputText = typeof input === "string" ? input : input.text;
|
|
4698
5188
|
const images = typeof input === "string" ? [] : input.images;
|
|
4699
5189
|
if (this.titleInput === void 0 && inputText.trim().length > 0) {
|
|
@@ -4773,6 +5263,7 @@ ${finished.result}`
|
|
|
4773
5263
|
this.messages = await this.applyCompactionSummary(compacted, signal);
|
|
4774
5264
|
await this.opts.sessionStore?.replaceMessages([...this.messages]);
|
|
4775
5265
|
this.turnMarkers = [];
|
|
5266
|
+
this.opts.registry?.resetVolatileState();
|
|
4776
5267
|
}
|
|
4777
5268
|
const maxRetries = Math.max(0, config.streamMaxRetries);
|
|
4778
5269
|
const baseDelay = this.opts.retryDelayMs ?? STREAM_RETRY_BASE_DELAY_MS;
|
|
@@ -4929,11 +5420,11 @@ ${finished.result}`
|
|
|
4929
5420
|
)}. Complete them now with tool calls, or update the list with todo_write if they are no longer needed.`;
|
|
4930
5421
|
} else if (usedToolsThisTurn && !signal.aborted) {
|
|
4931
5422
|
const complete = await checkTaskComplete(
|
|
4932
|
-
this.
|
|
5423
|
+
this.getAuxModel(),
|
|
4933
5424
|
inputText,
|
|
4934
5425
|
text,
|
|
4935
5426
|
signal,
|
|
4936
|
-
this.
|
|
5427
|
+
this.getAuxTemperature()
|
|
4937
5428
|
);
|
|
4938
5429
|
if (complete === false) {
|
|
4939
5430
|
reason = "completion check reports the task unfinished";
|
|
@@ -5061,14 +5552,11 @@ ${finished.result}`
|
|
|
5061
5552
|
// The permission mode can change at runtime, so the system message is
|
|
5062
5553
|
// recomputed at the start of every turn instead of being frozen by the
|
|
5063
5554
|
// constructor. Also restores a system message after loadMessages() (resume,
|
|
5064
|
-
// /model switch) replaced the history with one that has none.
|
|
5065
|
-
//
|
|
5066
|
-
//
|
|
5067
|
-
//
|
|
5068
|
-
//
|
|
5069
|
-
// stays cheap per turn. The
|
|
5070
|
-
// skills listing is refreshed here as well, so skills added mid-session
|
|
5071
|
-
// show up in the next turn's system prompt.
|
|
5555
|
+
// /model switch) replaced the history with one that has none. Everything in
|
|
5556
|
+
// here is stable across turns (base prompt, plan-mode flag, memories, skills
|
|
5557
|
+
// — the latter two cached by mtime), which keeps the prompt-cache prefix
|
|
5558
|
+
// intact; volatile git state rides per-turn on the latest user message
|
|
5559
|
+
// instead (attachTurnContext).
|
|
5072
5560
|
syncSystemMessage() {
|
|
5073
5561
|
const base = this.opts.system;
|
|
5074
5562
|
const plan = this.opts.config.permissionMode === "plan";
|
|
@@ -5081,8 +5569,6 @@ ${finished.result}`
|
|
|
5081
5569
|
if (userMemory) parts.push(userMemory);
|
|
5082
5570
|
const skills = discoverSkills(this.opts.cwd);
|
|
5083
5571
|
if (skills.length > 0) parts.push(formatSkillsBlock(skills));
|
|
5084
|
-
const git = getGitSummaryCached(this.opts.cwd);
|
|
5085
|
-
if (git) parts.push(formatGitSummary(git));
|
|
5086
5572
|
if (parts.length === 0) return;
|
|
5087
5573
|
const content = parts.join("\n\n");
|
|
5088
5574
|
const head = this.messages[0];
|
|
@@ -5093,14 +5579,19 @@ ${finished.result}`
|
|
|
5093
5579
|
}
|
|
5094
5580
|
}
|
|
5095
5581
|
async applyCompactionSummary(compacted, signal) {
|
|
5096
|
-
const { config
|
|
5097
|
-
if (config.contextCompaction !== "summary"
|
|
5582
|
+
const { config } = this.opts;
|
|
5583
|
+
if (config.contextCompaction !== "summary") {
|
|
5098
5584
|
return compacted.messages;
|
|
5099
5585
|
}
|
|
5100
5586
|
const headCount = compacted.messages[0]?.role === "system" ? 1 : 0;
|
|
5101
5587
|
const dropped = this.messages.slice(headCount, headCount + compacted.droppedCount);
|
|
5102
5588
|
try {
|
|
5103
|
-
const summary = await summarizeMessages(
|
|
5589
|
+
const summary = await summarizeMessages(
|
|
5590
|
+
dropped,
|
|
5591
|
+
this.getAuxModel(),
|
|
5592
|
+
signal,
|
|
5593
|
+
this.getAuxTemperature()
|
|
5594
|
+
);
|
|
5104
5595
|
const messages = compacted.messages.slice();
|
|
5105
5596
|
messages[headCount] = {
|
|
5106
5597
|
role: "user",
|
|
@@ -5115,7 +5606,7 @@ ${summary}`
|
|
|
5115
5606
|
async *streamOnce(aiTools, signal, timeoutScale = 1) {
|
|
5116
5607
|
for await (const event of streamChat({
|
|
5117
5608
|
model: this.opts.model,
|
|
5118
|
-
messages: this.messages,
|
|
5609
|
+
messages: attachTurnContext(this.messages, this.turnGitContext),
|
|
5119
5610
|
tools: aiTools,
|
|
5120
5611
|
abortSignal: signal,
|
|
5121
5612
|
providerMetadata: this.opts.providerMetadata,
|
|
@@ -5311,7 +5802,10 @@ export {
|
|
|
5311
5802
|
collectWorkingDiff,
|
|
5312
5803
|
MAX_IMAGE_DIMENSION,
|
|
5313
5804
|
reconcileToolCalls,
|
|
5314
|
-
|
|
5805
|
+
listModels,
|
|
5806
|
+
resolveModelConfig,
|
|
5807
|
+
createModel,
|
|
5808
|
+
reasoningEffortMetadata,
|
|
5315
5809
|
starHome,
|
|
5316
5810
|
globalConfigPath,
|
|
5317
5811
|
sessionsDir,
|
|
@@ -5349,6 +5843,7 @@ export {
|
|
|
5349
5843
|
createDefaultRegistry,
|
|
5350
5844
|
appendUserMemory,
|
|
5351
5845
|
PLAN_MODE_PROMPT,
|
|
5846
|
+
attachTurnContext,
|
|
5352
5847
|
AgentLoop
|
|
5353
5848
|
};
|
|
5354
|
-
//# sourceMappingURL=chunk-
|
|
5849
|
+
//# sourceMappingURL=chunk-OHLA2DP7.js.map
|