@cryer/star-cli 0.2.0 → 0.2.2
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 +24 -3
- package/dist/{chunk-RLQXGX3N.js → chunk-GN236FD4.js} +394 -93
- package/dist/chunk-GN236FD4.js.map +1 -0
- package/dist/{loop-XIILV7GY.js → loop-ADVWZA62.js} +2 -2
- package/dist/main.js +727 -263
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-RLQXGX3N.js.map +0 -1
- /package/dist/{loop-XIILV7GY.js.map → loop-ADVWZA62.js.map} +0 -0
|
@@ -10,23 +10,31 @@ import { generateText } from "ai";
|
|
|
10
10
|
var CHARS_PER_TOKEN = 4;
|
|
11
11
|
var MESSAGE_OVERHEAD = 4;
|
|
12
12
|
var IMAGE_PART_TOKENS = 1024;
|
|
13
|
+
var CJK_RE = /[\u3000-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
|
|
14
|
+
function textWeight(text) {
|
|
15
|
+
let cjk = 0;
|
|
16
|
+
for (const ch of text) {
|
|
17
|
+
if (CJK_RE.test(ch)) cjk++;
|
|
18
|
+
}
|
|
19
|
+
return text.length + cjk * (CHARS_PER_TOKEN - 1);
|
|
20
|
+
}
|
|
13
21
|
function contentCharLength(message) {
|
|
14
22
|
if (message.role === "tool") {
|
|
15
23
|
let chars2 = 0;
|
|
16
24
|
for (const part of message.content) {
|
|
17
|
-
chars2 += JSON.stringify(part.result ?? null)
|
|
25
|
+
chars2 += textWeight(JSON.stringify(part.result ?? null));
|
|
18
26
|
}
|
|
19
27
|
return chars2;
|
|
20
28
|
}
|
|
21
29
|
if (typeof message.content === "string") {
|
|
22
|
-
return message.content
|
|
30
|
+
return textWeight(message.content);
|
|
23
31
|
}
|
|
24
32
|
let chars = 0;
|
|
25
33
|
for (const part of message.content) {
|
|
26
34
|
if (part.type === "text" || part.type === "reasoning") {
|
|
27
|
-
chars += part.text
|
|
35
|
+
chars += textWeight(part.text);
|
|
28
36
|
} else if (part.type === "tool-call") {
|
|
29
|
-
chars += part.toolName
|
|
37
|
+
chars += textWeight(part.toolName) + textWeight(JSON.stringify(part.args ?? null));
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
40
|
return chars;
|
|
@@ -425,6 +433,12 @@ function matchAllowRule(rule, req) {
|
|
|
425
433
|
return target !== void 0 && globMatch(rule.pattern, target);
|
|
426
434
|
}
|
|
427
435
|
function isAllowedByRules(rules, req) {
|
|
436
|
+
return matchesAnyRule(rules, req);
|
|
437
|
+
}
|
|
438
|
+
function isDeniedByRules(rules, req) {
|
|
439
|
+
return matchesAnyRule(rules, req);
|
|
440
|
+
}
|
|
441
|
+
function matchesAnyRule(rules, req) {
|
|
428
442
|
return rules.some((raw) => {
|
|
429
443
|
const rule = parseAllowRule(raw);
|
|
430
444
|
return rule !== null && matchAllowRule(rule, req);
|
|
@@ -476,7 +490,7 @@ function isSensitivePath(p) {
|
|
|
476
490
|
}
|
|
477
491
|
return base === ".env" || base.startsWith(".env.") || base === "id_rsa" || base.endsWith(".pem");
|
|
478
492
|
}
|
|
479
|
-
function checkPermission(mode, req, ctx, allowRules = []) {
|
|
493
|
+
function checkPermission(mode, req, ctx, allowRules = [], denyRules = []) {
|
|
480
494
|
if (mode === "yolo") {
|
|
481
495
|
return "allow";
|
|
482
496
|
}
|
|
@@ -496,6 +510,9 @@ function checkPermission(mode, req, ctx, allowRules = []) {
|
|
|
496
510
|
return "deny";
|
|
497
511
|
}
|
|
498
512
|
}
|
|
513
|
+
if (isDeniedByRules(denyRules, req)) {
|
|
514
|
+
return "deny";
|
|
515
|
+
}
|
|
499
516
|
if (mode === "auto") {
|
|
500
517
|
return "allow";
|
|
501
518
|
}
|
|
@@ -720,14 +737,153 @@ ${body}`;
|
|
|
720
737
|
return block;
|
|
721
738
|
}
|
|
722
739
|
|
|
740
|
+
// src/agent/skills.ts
|
|
741
|
+
import { readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
742
|
+
import path5 from "path";
|
|
743
|
+
import { z } from "zod";
|
|
744
|
+
|
|
745
|
+
// src/config/paths.ts
|
|
746
|
+
import os from "os";
|
|
747
|
+
import path4 from "path";
|
|
748
|
+
function starHome() {
|
|
749
|
+
return process.env.STAR_HOME ?? path4.join(os.homedir(), ".star-cli");
|
|
750
|
+
}
|
|
751
|
+
function globalConfigPath() {
|
|
752
|
+
return path4.join(starHome(), "config.toml");
|
|
753
|
+
}
|
|
754
|
+
function sessionsDir() {
|
|
755
|
+
return path4.join(starHome(), "sessions");
|
|
756
|
+
}
|
|
757
|
+
function userMemoryPath() {
|
|
758
|
+
return path4.join(starHome(), "MEMORY.md");
|
|
759
|
+
}
|
|
760
|
+
function projectConfigPath(cwd) {
|
|
761
|
+
return path4.join(cwd, ".star", "config.toml");
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/agent/skills.ts
|
|
765
|
+
var SKILL_FILE = "SKILL.md";
|
|
766
|
+
var SKILL_MAX_CHARS = 32 * 1024;
|
|
767
|
+
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
768
|
+
function parseFrontmatter(raw) {
|
|
769
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
770
|
+
if (!match) return null;
|
|
771
|
+
const meta = {};
|
|
772
|
+
for (const line of (match[1] ?? "").split(/\r?\n/)) {
|
|
773
|
+
const kv = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
774
|
+
if (kv?.[1]) meta[kv[1]] = (kv[2] ?? "").trim();
|
|
775
|
+
}
|
|
776
|
+
return { meta, body: raw.slice(match[0].length).trim() };
|
|
777
|
+
}
|
|
778
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
779
|
+
function parseSkillFile(filePath, dirName, scope) {
|
|
780
|
+
let mtimeMs;
|
|
781
|
+
try {
|
|
782
|
+
mtimeMs = statSync2(filePath).mtimeMs;
|
|
783
|
+
} catch {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
const hit = cache2.get(filePath);
|
|
787
|
+
if (hit && hit.mtimeMs === mtimeMs) return hit.skill;
|
|
788
|
+
let skill = null;
|
|
789
|
+
try {
|
|
790
|
+
const parsed = parseFrontmatter(readFileSync2(filePath, "utf8"));
|
|
791
|
+
const name = parsed?.meta.name || dirName;
|
|
792
|
+
const description = parsed?.meta.description;
|
|
793
|
+
if (parsed && NAME_PATTERN.test(name) && description) {
|
|
794
|
+
skill = { name, description, filePath, dir: path5.dirname(filePath), scope };
|
|
795
|
+
}
|
|
796
|
+
} catch {
|
|
797
|
+
skill = null;
|
|
798
|
+
}
|
|
799
|
+
cache2.set(filePath, { mtimeMs, skill });
|
|
800
|
+
return skill;
|
|
801
|
+
}
|
|
802
|
+
function scanDir(base, scope, into) {
|
|
803
|
+
let entries;
|
|
804
|
+
try {
|
|
805
|
+
entries = readdirSync(base);
|
|
806
|
+
} catch {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
for (const entry of entries.sort()) {
|
|
810
|
+
if (!NAME_PATTERN.test(entry)) continue;
|
|
811
|
+
const parsed = parseSkillFile(path5.join(base, entry, SKILL_FILE), entry, scope);
|
|
812
|
+
if (parsed) into.set(parsed.name, parsed);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function discoverSkills(cwd, home = starHome()) {
|
|
816
|
+
const byName = /* @__PURE__ */ new Map();
|
|
817
|
+
scanDir(path5.join(home, "skills"), "user", byName);
|
|
818
|
+
scanDir(path5.join(cwd, ".star", "skills"), "project", byName);
|
|
819
|
+
return [...byName.values()];
|
|
820
|
+
}
|
|
821
|
+
function formatSkillsBlock(skills) {
|
|
822
|
+
const lines = skills.map((s) => `- ${s.name}: ${s.description}`);
|
|
823
|
+
return [
|
|
824
|
+
"# Available skills",
|
|
825
|
+
"When the user's request matches one of these skills, call the skill tool with its name to load the full instructions before proceeding:",
|
|
826
|
+
...lines
|
|
827
|
+
].join("\n");
|
|
828
|
+
}
|
|
829
|
+
function loadSkillBody(skill) {
|
|
830
|
+
let parsed = null;
|
|
831
|
+
try {
|
|
832
|
+
parsed = parseFrontmatter(readFileSync2(skill.filePath, "utf8"));
|
|
833
|
+
} catch {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
if (!parsed?.body) return null;
|
|
837
|
+
if (parsed.body.length > SKILL_MAX_CHARS) {
|
|
838
|
+
return `${parsed.body.slice(0, SKILL_MAX_CHARS)}
|
|
839
|
+
|
|
840
|
+
[skill truncated to ${SKILL_MAX_CHARS} characters]`;
|
|
841
|
+
}
|
|
842
|
+
return parsed.body;
|
|
843
|
+
}
|
|
844
|
+
function createSkillTool(deps) {
|
|
845
|
+
return {
|
|
846
|
+
name: "skill",
|
|
847
|
+
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.",
|
|
848
|
+
permission: "read",
|
|
849
|
+
parameters: z.object({
|
|
850
|
+
name: z.string().describe("Name of the skill to load, as listed in the system prompt.")
|
|
851
|
+
}),
|
|
852
|
+
async execute(args) {
|
|
853
|
+
const skills = discoverSkills(deps.cwd, deps.home);
|
|
854
|
+
const skill = skills.find((s) => s.name === args.name);
|
|
855
|
+
if (!skill) {
|
|
856
|
+
const available = skills.map((s) => s.name).join(", ") || "none";
|
|
857
|
+
return {
|
|
858
|
+
content: `Unknown skill: "${args.name}". Available skills: ${available}`,
|
|
859
|
+
isError: true
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
const body = loadSkillBody(skill);
|
|
863
|
+
if (!body) {
|
|
864
|
+
return { content: `Skill "${skill.name}" is empty or unreadable.`, isError: true };
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
content: [
|
|
868
|
+
`# Skill: ${skill.name}`,
|
|
869
|
+
"",
|
|
870
|
+
body,
|
|
871
|
+
"",
|
|
872
|
+
`Base directory for relative file references in this skill: ${skill.dir}`
|
|
873
|
+
].join("\n")
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
723
879
|
// src/agent/subagent.ts
|
|
724
|
-
import { z as
|
|
880
|
+
import { z as z12 } from "zod";
|
|
725
881
|
|
|
726
882
|
// src/tools/bash.ts
|
|
727
883
|
import { spawn as spawn3 } from "child_process";
|
|
728
884
|
import { existsSync } from "fs";
|
|
729
|
-
import
|
|
730
|
-
import { z } from "zod";
|
|
885
|
+
import path6 from "path";
|
|
886
|
+
import { z as z2 } from "zod";
|
|
731
887
|
|
|
732
888
|
// src/tasks/manager.ts
|
|
733
889
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -846,11 +1002,11 @@ var MAX_OUTPUT = 3e4;
|
|
|
846
1002
|
var DEFAULT_TIMEOUT = 120;
|
|
847
1003
|
var MAX_TIMEOUT = 600;
|
|
848
1004
|
function findOnPath(exe, exclude) {
|
|
849
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
1005
|
+
for (const dir of (process.env.PATH ?? "").split(path6.delimiter)) {
|
|
850
1006
|
if (!dir || exclude?.(dir)) {
|
|
851
1007
|
continue;
|
|
852
1008
|
}
|
|
853
|
-
const full =
|
|
1009
|
+
const full = path6.join(dir, exe);
|
|
854
1010
|
if (existsSync(full)) {
|
|
855
1011
|
return full;
|
|
856
1012
|
}
|
|
@@ -868,7 +1024,7 @@ function resolveShell() {
|
|
|
868
1024
|
}
|
|
869
1025
|
const gitExe = findOnPath("git.exe");
|
|
870
1026
|
if (gitExe) {
|
|
871
|
-
const sibling =
|
|
1027
|
+
const sibling = path6.join(path6.dirname(path6.dirname(gitExe)), "bin", "bash.exe");
|
|
872
1028
|
if (existsSync(sibling)) {
|
|
873
1029
|
return { shell: sibling, wrap: (c) => ["-c", c], label: "bash" };
|
|
874
1030
|
}
|
|
@@ -885,11 +1041,11 @@ function resolveShell() {
|
|
|
885
1041
|
const comspec = process.env.ComSpec ?? "cmd.exe";
|
|
886
1042
|
return { shell: comspec, wrap: (c) => ["/d", "/s", "/c", c], label: "cmd" };
|
|
887
1043
|
}
|
|
888
|
-
var schema =
|
|
889
|
-
command:
|
|
890
|
-
timeout:
|
|
891
|
-
description:
|
|
892
|
-
run_in_background:
|
|
1044
|
+
var schema = z2.object({
|
|
1045
|
+
command: z2.string().describe("Shell command to execute"),
|
|
1046
|
+
timeout: z2.number().positive().max(MAX_TIMEOUT).optional().describe("Timeout in seconds (default 120, max 600)"),
|
|
1047
|
+
description: z2.string().optional().describe("Short description of what the command does"),
|
|
1048
|
+
run_in_background: z2.boolean().optional().describe(
|
|
893
1049
|
"Run the command in the background and return a task id immediately (default false). Use task_output/task_kill to inspect or stop it."
|
|
894
1050
|
)
|
|
895
1051
|
});
|
|
@@ -992,13 +1148,13 @@ Command timed out after ${timeoutSeconds}s`,
|
|
|
992
1148
|
|
|
993
1149
|
// src/tools/fs/edit.ts
|
|
994
1150
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
995
|
-
import
|
|
996
|
-
import { z as
|
|
997
|
-
var schema2 =
|
|
998
|
-
path:
|
|
999
|
-
old_string:
|
|
1000
|
-
new_string:
|
|
1001
|
-
replace_all:
|
|
1151
|
+
import path7 from "path";
|
|
1152
|
+
import { z as z3 } from "zod";
|
|
1153
|
+
var schema2 = z3.object({
|
|
1154
|
+
path: z3.string().describe("File path, absolute or relative to the working directory"),
|
|
1155
|
+
old_string: z3.string().min(1).describe("Exact text to replace"),
|
|
1156
|
+
new_string: z3.string().describe("Replacement text"),
|
|
1157
|
+
replace_all: z3.boolean().optional().describe("Replace every occurrence instead of requiring a unique match")
|
|
1002
1158
|
});
|
|
1003
1159
|
var editFileTool = {
|
|
1004
1160
|
name: "edit_file",
|
|
@@ -1006,7 +1162,7 @@ var editFileTool = {
|
|
|
1006
1162
|
permission: "write",
|
|
1007
1163
|
parameters: schema2,
|
|
1008
1164
|
async execute(args, ctx) {
|
|
1009
|
-
const filePath =
|
|
1165
|
+
const filePath = path7.resolve(ctx.cwd, args.path);
|
|
1010
1166
|
let content;
|
|
1011
1167
|
try {
|
|
1012
1168
|
content = await readFile2(filePath, "utf8");
|
|
@@ -1049,14 +1205,15 @@ var editFileTool = {
|
|
|
1049
1205
|
};
|
|
1050
1206
|
|
|
1051
1207
|
// src/tools/fs/glob.ts
|
|
1052
|
-
import
|
|
1053
|
-
import { z as
|
|
1208
|
+
import path9 from "path";
|
|
1209
|
+
import { z as z4 } from "zod";
|
|
1054
1210
|
|
|
1055
1211
|
// src/tools/fs/util.ts
|
|
1212
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
1056
1213
|
import { readdir, stat } from "fs/promises";
|
|
1057
|
-
import
|
|
1214
|
+
import path8 from "path";
|
|
1058
1215
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
1059
|
-
async function walkFiles(root, skipDirs = SKIP_DIRS) {
|
|
1216
|
+
async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
|
|
1060
1217
|
const out = [];
|
|
1061
1218
|
async function walk(dir, relBase) {
|
|
1062
1219
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
@@ -1064,13 +1221,17 @@ async function walkFiles(root, skipDirs = SKIP_DIRS) {
|
|
|
1064
1221
|
return;
|
|
1065
1222
|
}
|
|
1066
1223
|
for (const entry of entries) {
|
|
1067
|
-
const abs =
|
|
1224
|
+
const abs = path8.join(dir, entry.name);
|
|
1068
1225
|
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
1069
1226
|
if (entry.isDirectory()) {
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1227
|
+
if (skipDirs.has(entry.name) || options.ignore?.(abs, true)) {
|
|
1228
|
+
continue;
|
|
1072
1229
|
}
|
|
1230
|
+
await walk(abs, rel);
|
|
1073
1231
|
} else if (entry.isFile()) {
|
|
1232
|
+
if (options.ignore?.(abs, false)) {
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1074
1235
|
try {
|
|
1075
1236
|
const st = await stat(abs);
|
|
1076
1237
|
out.push({ abs, rel, mtimeMs: st.mtimeMs });
|
|
@@ -1119,8 +1280,71 @@ function matchesGlob(pattern, relPath) {
|
|
|
1119
1280
|
}
|
|
1120
1281
|
return globToRegExp(normalized).test(relPath);
|
|
1121
1282
|
}
|
|
1283
|
+
var IGNORE_FILE = ".starignore";
|
|
1284
|
+
var ignoreCache = /* @__PURE__ */ new Map();
|
|
1285
|
+
function parseIgnorePatterns(body) {
|
|
1286
|
+
const out = [];
|
|
1287
|
+
for (const raw of body.split("\n")) {
|
|
1288
|
+
const line = raw.trim();
|
|
1289
|
+
if (!line || line.startsWith("#")) {
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (line.endsWith("/")) {
|
|
1293
|
+
const pattern = line.slice(0, -1);
|
|
1294
|
+
if (pattern) {
|
|
1295
|
+
out.push({ pattern, dirOnly: true });
|
|
1296
|
+
}
|
|
1297
|
+
} else {
|
|
1298
|
+
out.push({ pattern: line, dirOnly: false });
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return out;
|
|
1302
|
+
}
|
|
1303
|
+
function loadIgnorePatterns(dir) {
|
|
1304
|
+
const file = path8.join(dir, IGNORE_FILE);
|
|
1305
|
+
let mtimeMs;
|
|
1306
|
+
try {
|
|
1307
|
+
mtimeMs = statSync3(file).mtimeMs;
|
|
1308
|
+
} catch {
|
|
1309
|
+
return [];
|
|
1310
|
+
}
|
|
1311
|
+
const hit = ignoreCache.get(file);
|
|
1312
|
+
if (hit && hit.mtimeMs === mtimeMs) {
|
|
1313
|
+
return hit.patterns;
|
|
1314
|
+
}
|
|
1315
|
+
let patterns = [];
|
|
1316
|
+
try {
|
|
1317
|
+
patterns = parseIgnorePatterns(readFileSync3(file, "utf8"));
|
|
1318
|
+
} catch {
|
|
1319
|
+
patterns = [];
|
|
1320
|
+
}
|
|
1321
|
+
ignoreCache.set(file, { mtimeMs, patterns });
|
|
1322
|
+
return patterns;
|
|
1323
|
+
}
|
|
1324
|
+
function createIgnorePredicate(cwd) {
|
|
1325
|
+
const patterns = loadIgnorePatterns(cwd);
|
|
1326
|
+
if (patterns.length === 0) {
|
|
1327
|
+
return void 0;
|
|
1328
|
+
}
|
|
1329
|
+
const root = path8.resolve(cwd);
|
|
1330
|
+
return (absPath, isDir) => {
|
|
1331
|
+
const rel = path8.relative(root, absPath).split(path8.sep).join("/");
|
|
1332
|
+
if (!rel || rel.startsWith("..")) {
|
|
1333
|
+
return false;
|
|
1334
|
+
}
|
|
1335
|
+
for (const { pattern, dirOnly } of patterns) {
|
|
1336
|
+
if (dirOnly && !isDir) {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
if (matchesGlob(pattern, rel)) {
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return false;
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1122
1346
|
function isSensitivePath2(filePath) {
|
|
1123
|
-
const base =
|
|
1347
|
+
const base = path8.basename(filePath);
|
|
1124
1348
|
if (base === ".env.example" || base === ".env.sample" || base === ".env.template") {
|
|
1125
1349
|
return false;
|
|
1126
1350
|
}
|
|
@@ -1135,11 +1359,11 @@ function isSensitivePath2(filePath) {
|
|
|
1135
1359
|
|
|
1136
1360
|
// src/tools/fs/glob.ts
|
|
1137
1361
|
var MAX_RESULTS = 100;
|
|
1138
|
-
var schema3 =
|
|
1139
|
-
pattern:
|
|
1362
|
+
var schema3 = z4.object({
|
|
1363
|
+
pattern: z4.string().describe(
|
|
1140
1364
|
"Glob pattern, e.g. 'src/**/*.ts'. Supports **, * and ?. A bare pattern like '*.ts' matches basenames recursively."
|
|
1141
1365
|
),
|
|
1142
|
-
path:
|
|
1366
|
+
path: z4.string().optional().describe("Directory to search, defaults to the working directory")
|
|
1143
1367
|
});
|
|
1144
1368
|
var globTool = {
|
|
1145
1369
|
name: "glob",
|
|
@@ -1147,8 +1371,8 @@ var globTool = {
|
|
|
1147
1371
|
permission: "read",
|
|
1148
1372
|
parameters: schema3,
|
|
1149
1373
|
async execute(args, ctx) {
|
|
1150
|
-
const root =
|
|
1151
|
-
const files = await walkFiles(root);
|
|
1374
|
+
const root = path9.resolve(ctx.cwd, args.path ?? ".");
|
|
1375
|
+
const files = await walkFiles(root, void 0, { ignore: createIgnorePredicate(ctx.cwd) });
|
|
1152
1376
|
const matched = files.filter((f) => matchesGlob(args.pattern, f.rel)).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RESULTS);
|
|
1153
1377
|
if (matched.length === 0) {
|
|
1154
1378
|
return { content: `No files matched pattern: ${args.pattern}` };
|
|
@@ -1159,14 +1383,14 @@ var globTool = {
|
|
|
1159
1383
|
|
|
1160
1384
|
// src/tools/fs/grep.ts
|
|
1161
1385
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1162
|
-
import
|
|
1163
|
-
import { z as
|
|
1386
|
+
import path10 from "path";
|
|
1387
|
+
import { z as z5 } from "zod";
|
|
1164
1388
|
var MAX_MATCHES = 250;
|
|
1165
|
-
var schema4 =
|
|
1166
|
-
pattern:
|
|
1167
|
-
path:
|
|
1168
|
-
glob:
|
|
1169
|
-
ignoreCase:
|
|
1389
|
+
var schema4 = z5.object({
|
|
1390
|
+
pattern: z5.string().describe("Regular expression to search for"),
|
|
1391
|
+
path: z5.string().optional().describe("File or directory to search, defaults to the working directory"),
|
|
1392
|
+
glob: z5.string().optional().describe("Glob pattern to filter which files are searched, e.g. '*.ts'"),
|
|
1393
|
+
ignoreCase: z5.boolean().optional().describe("Case-insensitive matching")
|
|
1170
1394
|
});
|
|
1171
1395
|
var grepTool = {
|
|
1172
1396
|
name: "grep",
|
|
@@ -1180,14 +1404,14 @@ var grepTool = {
|
|
|
1180
1404
|
} catch (err) {
|
|
1181
1405
|
return { content: `Invalid regular expression: ${err.message}`, isError: true };
|
|
1182
1406
|
}
|
|
1183
|
-
const root =
|
|
1407
|
+
const root = path10.resolve(ctx.cwd, args.path ?? ".");
|
|
1184
1408
|
let files;
|
|
1185
1409
|
try {
|
|
1186
1410
|
const st = await stat2(root);
|
|
1187
1411
|
if (st.isFile()) {
|
|
1188
|
-
files = [{ abs: root, rel:
|
|
1412
|
+
files = [{ abs: root, rel: path10.basename(root), mtimeMs: st.mtimeMs }];
|
|
1189
1413
|
} else {
|
|
1190
|
-
files = await walkFiles(root);
|
|
1414
|
+
files = await walkFiles(root, void 0, { ignore: createIgnorePredicate(ctx.cwd) });
|
|
1191
1415
|
}
|
|
1192
1416
|
} catch {
|
|
1193
1417
|
return { content: `Path not found: ${args.path ?? "."}`, isError: true };
|
|
@@ -1232,14 +1456,14 @@ var grepTool = {
|
|
|
1232
1456
|
|
|
1233
1457
|
// src/tools/fs/read.ts
|
|
1234
1458
|
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
1235
|
-
import
|
|
1236
|
-
import { z as
|
|
1459
|
+
import path11 from "path";
|
|
1460
|
+
import { z as z6 } from "zod";
|
|
1237
1461
|
var DEFAULT_LIMIT = 2e3;
|
|
1238
1462
|
var MAX_CHARS = 100 * 1024;
|
|
1239
|
-
var schema5 =
|
|
1240
|
-
path:
|
|
1241
|
-
offset:
|
|
1242
|
-
limit:
|
|
1463
|
+
var schema5 = z6.object({
|
|
1464
|
+
path: z6.string().describe("File path, absolute or relative to the working directory"),
|
|
1465
|
+
offset: z6.number().int().positive().optional().describe("1-based line number to start reading from"),
|
|
1466
|
+
limit: z6.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
|
|
1243
1467
|
});
|
|
1244
1468
|
var readFileTool = {
|
|
1245
1469
|
name: "read_file",
|
|
@@ -1247,7 +1471,7 @@ var readFileTool = {
|
|
|
1247
1471
|
permission: "read",
|
|
1248
1472
|
parameters: schema5,
|
|
1249
1473
|
async execute(args, ctx) {
|
|
1250
|
-
const filePath =
|
|
1474
|
+
const filePath = path11.resolve(ctx.cwd, args.path);
|
|
1251
1475
|
if (isSensitivePath2(filePath)) {
|
|
1252
1476
|
return { content: `Refused to read sensitive file: ${args.path}`, isError: true };
|
|
1253
1477
|
}
|
|
@@ -1293,11 +1517,11 @@ var readFileTool = {
|
|
|
1293
1517
|
|
|
1294
1518
|
// src/tools/fs/write.ts
|
|
1295
1519
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
1296
|
-
import
|
|
1297
|
-
import { z as
|
|
1298
|
-
var schema6 =
|
|
1299
|
-
path:
|
|
1300
|
-
content:
|
|
1520
|
+
import path12 from "path";
|
|
1521
|
+
import { z as z7 } from "zod";
|
|
1522
|
+
var schema6 = z7.object({
|
|
1523
|
+
path: z7.string().describe("File path, absolute or relative to the working directory"),
|
|
1524
|
+
content: z7.string().describe("Full content to write to the file")
|
|
1301
1525
|
});
|
|
1302
1526
|
var writeFileTool = {
|
|
1303
1527
|
name: "write_file",
|
|
@@ -1305,10 +1529,10 @@ var writeFileTool = {
|
|
|
1305
1529
|
permission: "write",
|
|
1306
1530
|
parameters: schema6,
|
|
1307
1531
|
async execute(args, ctx) {
|
|
1308
|
-
const filePath =
|
|
1532
|
+
const filePath = path12.resolve(ctx.cwd, args.path);
|
|
1309
1533
|
const snapshot = await captureSnapshot(filePath, "write_file");
|
|
1310
1534
|
try {
|
|
1311
|
-
await mkdir2(
|
|
1535
|
+
await mkdir2(path12.dirname(filePath), { recursive: true });
|
|
1312
1536
|
await writeFile3(filePath, args.content, "utf8");
|
|
1313
1537
|
} catch (err) {
|
|
1314
1538
|
return { content: `Failed to write ${args.path}: ${err.message}`, isError: true };
|
|
@@ -1319,7 +1543,7 @@ var writeFileTool = {
|
|
|
1319
1543
|
};
|
|
1320
1544
|
|
|
1321
1545
|
// src/tools/tasks.ts
|
|
1322
|
-
import { z as
|
|
1546
|
+
import { z as z8 } from "zod";
|
|
1323
1547
|
|
|
1324
1548
|
// src/tasks/format.ts
|
|
1325
1549
|
function formatDuration(startedAt, endedAt) {
|
|
@@ -1365,14 +1589,14 @@ function formatTaskFinished(task) {
|
|
|
1365
1589
|
}
|
|
1366
1590
|
|
|
1367
1591
|
// src/tools/tasks.ts
|
|
1368
|
-
var idSchema =
|
|
1369
|
-
id:
|
|
1592
|
+
var idSchema = z8.object({
|
|
1593
|
+
id: z8.string().describe("Task id, e.g. task-1")
|
|
1370
1594
|
});
|
|
1371
1595
|
var taskListTool = {
|
|
1372
1596
|
name: "task_list",
|
|
1373
1597
|
description: "List background shell tasks with status, runtime, and exit code.",
|
|
1374
1598
|
permission: "read",
|
|
1375
|
-
parameters:
|
|
1599
|
+
parameters: z8.object({}),
|
|
1376
1600
|
execute() {
|
|
1377
1601
|
return Promise.resolve({ content: formatTaskList(defaultTaskManager.list()) });
|
|
1378
1602
|
}
|
|
@@ -1415,19 +1639,19 @@ var taskKillTool = {
|
|
|
1415
1639
|
|
|
1416
1640
|
// src/tools/todo.ts
|
|
1417
1641
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
1418
|
-
import
|
|
1419
|
-
import { z as
|
|
1420
|
-
var todoItemSchema =
|
|
1421
|
-
id:
|
|
1422
|
-
title:
|
|
1423
|
-
status:
|
|
1642
|
+
import path13 from "path";
|
|
1643
|
+
import { z as z9 } from "zod";
|
|
1644
|
+
var todoItemSchema = z9.object({
|
|
1645
|
+
id: z9.number().int(),
|
|
1646
|
+
title: z9.string(),
|
|
1647
|
+
status: z9.enum(["pending", "in_progress", "done"])
|
|
1424
1648
|
});
|
|
1425
|
-
var writeSchema =
|
|
1426
|
-
todos:
|
|
1649
|
+
var writeSchema = z9.object({
|
|
1650
|
+
todos: z9.array(todoItemSchema).describe("The full todo list, replacing the current one")
|
|
1427
1651
|
});
|
|
1428
|
-
var readSchema =
|
|
1652
|
+
var readSchema = z9.object({});
|
|
1429
1653
|
function fileFor(cwd) {
|
|
1430
|
-
return
|
|
1654
|
+
return path13.join(cwd, ".star", "todos.json");
|
|
1431
1655
|
}
|
|
1432
1656
|
var TodoStore = class {
|
|
1433
1657
|
items = /* @__PURE__ */ new Map();
|
|
@@ -1456,7 +1680,7 @@ var TodoStore = class {
|
|
|
1456
1680
|
}
|
|
1457
1681
|
}
|
|
1458
1682
|
async save(cwd) {
|
|
1459
|
-
await mkdir3(
|
|
1683
|
+
await mkdir3(path13.join(cwd, ".star"), { recursive: true });
|
|
1460
1684
|
await writeFile4(fileFor(cwd), `${JSON.stringify(this.list(), null, 2)}
|
|
1461
1685
|
`);
|
|
1462
1686
|
}
|
|
@@ -1509,13 +1733,13 @@ function createTodoTools(store = defaultStore) {
|
|
|
1509
1733
|
}
|
|
1510
1734
|
|
|
1511
1735
|
// src/tools/web/fetch.ts
|
|
1512
|
-
import { z as
|
|
1736
|
+
import { z as z10 } from "zod";
|
|
1513
1737
|
var DEFAULT_MAX_CHARS = 2e4;
|
|
1514
1738
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
1515
1739
|
var TIMEOUT_MS = 3e4;
|
|
1516
|
-
var schema7 =
|
|
1517
|
-
url:
|
|
1518
|
-
maxChars:
|
|
1740
|
+
var schema7 = z10.object({
|
|
1741
|
+
url: z10.string().describe("The http(s) URL to fetch"),
|
|
1742
|
+
maxChars: z10.number().int().positive().optional().describe("Maximum characters of text to return (default 20000)")
|
|
1519
1743
|
});
|
|
1520
1744
|
function decodeEntities(s) {
|
|
1521
1745
|
return s.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n))).replace(/&#x([0-9a-fA-F]+);/gi, (_, n) => String.fromCodePoint(Number.parseInt(n, 16))).replace(/&(quot|lt|gt|nbsp);/g, (m) => {
|
|
@@ -1643,15 +1867,15 @@ var webFetchTool = {
|
|
|
1643
1867
|
};
|
|
1644
1868
|
|
|
1645
1869
|
// src/tools/web/search.ts
|
|
1646
|
-
import { z as
|
|
1870
|
+
import { z as z11 } from "zod";
|
|
1647
1871
|
var TIMEOUT_MS2 = 15e3;
|
|
1648
1872
|
var DEFAULT_MAX_RESULTS = 5;
|
|
1649
1873
|
var MAX_RESULTS_LIMIT = 10;
|
|
1650
1874
|
var SEARCH_URL = "https://html.duckduckgo.com/html/?q=";
|
|
1651
1875
|
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";
|
|
1652
|
-
var schema8 =
|
|
1653
|
-
query:
|
|
1654
|
-
maxResults:
|
|
1876
|
+
var schema8 = z11.object({
|
|
1877
|
+
query: z11.string().min(1).describe("The search query"),
|
|
1878
|
+
maxResults: z11.number().int().min(1).max(MAX_RESULTS_LIMIT).optional().describe(
|
|
1655
1879
|
`Maximum number of results to return (default ${DEFAULT_MAX_RESULTS}, max ${MAX_RESULTS_LIMIT})`
|
|
1656
1880
|
)
|
|
1657
1881
|
});
|
|
@@ -1810,12 +2034,12 @@ function createSubagentTool(deps) {
|
|
|
1810
2034
|
name: "subagent",
|
|
1811
2035
|
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.",
|
|
1812
2036
|
permission: "exec",
|
|
1813
|
-
parameters:
|
|
1814
|
-
prompt:
|
|
1815
|
-
description:
|
|
2037
|
+
parameters: z12.object({
|
|
2038
|
+
prompt: z12.string().describe("Complete, self-contained instructions for the subagent."),
|
|
2039
|
+
description: z12.string().optional().describe("Short label for the subtask, shown in the result header.")
|
|
1816
2040
|
}),
|
|
1817
2041
|
async execute(args, ctx) {
|
|
1818
|
-
const { AgentLoop: AgentLoop2 } = await import("./loop-
|
|
2042
|
+
const { AgentLoop: AgentLoop2 } = await import("./loop-ADVWZA62.js");
|
|
1819
2043
|
const child = new AgentLoop2({
|
|
1820
2044
|
model: deps.model,
|
|
1821
2045
|
registry: createDefaultRegistry(),
|
|
@@ -1872,6 +2096,64 @@ ${report || "(subagent produced no text output)"}` };
|
|
|
1872
2096
|
};
|
|
1873
2097
|
}
|
|
1874
2098
|
|
|
2099
|
+
// src/agent/user-memory.ts
|
|
2100
|
+
import { appendFileSync, mkdirSync, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
2101
|
+
import path14 from "path";
|
|
2102
|
+
import { z as z13 } from "zod";
|
|
2103
|
+
var USER_MEMORY_MAX_CHARS = 32 * 1024;
|
|
2104
|
+
var REMEMBER_TEXT_MAX_CHARS = 500;
|
|
2105
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
2106
|
+
function readUserMemory(home = starHome()) {
|
|
2107
|
+
const file = path14.join(home, "MEMORY.md");
|
|
2108
|
+
let mtimeMs;
|
|
2109
|
+
try {
|
|
2110
|
+
mtimeMs = statSync4(file).mtimeMs;
|
|
2111
|
+
} catch {
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
const hit = cache3.get(file);
|
|
2115
|
+
if (hit && hit.mtimeMs === mtimeMs) return hit.block;
|
|
2116
|
+
let block = null;
|
|
2117
|
+
try {
|
|
2118
|
+
let body = readFileSync4(file, "utf8").trim();
|
|
2119
|
+
if (body) {
|
|
2120
|
+
if (body.length > USER_MEMORY_MAX_CHARS) {
|
|
2121
|
+
body = `${body.slice(0, USER_MEMORY_MAX_CHARS)}
|
|
2122
|
+
|
|
2123
|
+
[MEMORY.md truncated to ${USER_MEMORY_MAX_CHARS} characters]`;
|
|
2124
|
+
}
|
|
2125
|
+
block = [
|
|
2126
|
+
"# User memory (MEMORY.md)",
|
|
2127
|
+
"The following notes were saved by the user across sessions. Respect them unless they conflict with the current request.",
|
|
2128
|
+
body
|
|
2129
|
+
].join("\n");
|
|
2130
|
+
}
|
|
2131
|
+
} catch {
|
|
2132
|
+
block = null;
|
|
2133
|
+
}
|
|
2134
|
+
cache3.set(file, { mtimeMs, block });
|
|
2135
|
+
return block;
|
|
2136
|
+
}
|
|
2137
|
+
function appendUserMemory(text, home = starHome()) {
|
|
2138
|
+
mkdirSync(home, { recursive: true });
|
|
2139
|
+
appendFileSync(path14.join(home, "MEMORY.md"), `- ${text}
|
|
2140
|
+
`, "utf8");
|
|
2141
|
+
}
|
|
2142
|
+
function createRememberTool(deps = {}) {
|
|
2143
|
+
return {
|
|
2144
|
+
name: "remember",
|
|
2145
|
+
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.`,
|
|
2146
|
+
permission: "write",
|
|
2147
|
+
parameters: z13.object({
|
|
2148
|
+
text: z13.string().max(REMEMBER_TEXT_MAX_CHARS).describe("The note to save, as a single concise sentence.")
|
|
2149
|
+
}),
|
|
2150
|
+
async execute(args) {
|
|
2151
|
+
appendUserMemory(args.text, deps.home);
|
|
2152
|
+
return { content: `Saved to user memory: ${args.text}` };
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
|
|
1875
2157
|
// src/agent/loop.ts
|
|
1876
2158
|
var PLAN_MODE_PROMPT = "\n\nYou are currently in PLAN MODE. Research the task using only the read-only tools available to you, then present a concrete, step-by-step implementation plan as your final answer. You must not modify files, run shell commands, or otherwise change the system \u2014 write/exec tools are unavailable in this mode. Do not ask the user to run commands for you; note manual steps in the plan instead. The user will review your plan and approve it before any execution begins.";
|
|
1877
2159
|
var AgentLoop = class {
|
|
@@ -1896,6 +2178,8 @@ var AgentLoop = class {
|
|
|
1896
2178
|
if (opts.sessionStore) {
|
|
1897
2179
|
this.bindSnapshotHooks(opts.sessionStore);
|
|
1898
2180
|
}
|
|
2181
|
+
opts.registry?.register(createSkillTool({ cwd: opts.cwd }));
|
|
2182
|
+
opts.registry?.register(createRememberTool());
|
|
1899
2183
|
const depth = opts.subagentDepth ?? 0;
|
|
1900
2184
|
if (opts.registry && depth < MAX_SUBAGENT_DEPTH) {
|
|
1901
2185
|
opts.registry.register(
|
|
@@ -2029,7 +2313,8 @@ var AgentLoop = class {
|
|
|
2029
2313
|
const { config, registry, cwd } = this.opts;
|
|
2030
2314
|
const aiTools = this.buildAiTools();
|
|
2031
2315
|
for (let step = 0; step < config.maxSteps; step++) {
|
|
2032
|
-
const
|
|
2316
|
+
const maxTokens = this.opts.contextMaxTokens ?? config.contextMaxTokens;
|
|
2317
|
+
const compacted = compactMessages([...this.messages], maxTokens);
|
|
2033
2318
|
if (compacted.compacted) {
|
|
2034
2319
|
this.messages = await this.applyCompactionSummary(compacted);
|
|
2035
2320
|
}
|
|
@@ -2139,8 +2424,11 @@ var AgentLoop = class {
|
|
|
2139
2424
|
// /model switch) replaced the history with one that has none. Git context
|
|
2140
2425
|
// (branch, dirty count, recent commits) is refreshed here too, so the model
|
|
2141
2426
|
// always sees the current repo state; any git failure is silently skipped.
|
|
2142
|
-
// Project memory (AGENTS.md in the cwd)
|
|
2143
|
-
//
|
|
2427
|
+
// Project memory (AGENTS.md in the cwd) and user memory (~/.star-cli/
|
|
2428
|
+
// MEMORY.md) are appended as delimited blocks; both cache by mtime, so this
|
|
2429
|
+
// stays cheap per turn. The
|
|
2430
|
+
// skills listing is refreshed here as well, so skills added mid-session
|
|
2431
|
+
// show up in the next turn's system prompt.
|
|
2144
2432
|
syncSystemMessage() {
|
|
2145
2433
|
const base = this.opts.system;
|
|
2146
2434
|
const plan = this.opts.config.permissionMode === "plan";
|
|
@@ -2149,6 +2437,10 @@ var AgentLoop = class {
|
|
|
2149
2437
|
if (plan) parts.push(PLAN_MODE_PROMPT.trim());
|
|
2150
2438
|
const memory = readProjectMemory(this.opts.cwd);
|
|
2151
2439
|
if (memory) parts.push(memory);
|
|
2440
|
+
const userMemory = readUserMemory();
|
|
2441
|
+
if (userMemory) parts.push(userMemory);
|
|
2442
|
+
const skills = discoverSkills(this.opts.cwd);
|
|
2443
|
+
if (skills.length > 0) parts.push(formatSkillsBlock(skills));
|
|
2152
2444
|
const git = getGitSummary(this.opts.cwd);
|
|
2153
2445
|
if (git) parts.push(formatGitSummary(git));
|
|
2154
2446
|
if (parts.length === 0) return;
|
|
@@ -2228,7 +2520,8 @@ ${summary}`
|
|
|
2228
2520
|
config.permissionMode,
|
|
2229
2521
|
{ toolName: call.name, args: call.args, level: tool.permission },
|
|
2230
2522
|
{ cwd },
|
|
2231
|
-
config.permissions.allow
|
|
2523
|
+
config.permissions.allow,
|
|
2524
|
+
config.permissions.deny
|
|
2232
2525
|
);
|
|
2233
2526
|
if (decision === "deny") {
|
|
2234
2527
|
return { content: `Permission denied for tool "${call.name}".`, isError: true };
|
|
@@ -2297,10 +2590,17 @@ export {
|
|
|
2297
2590
|
hydrateSnapshots,
|
|
2298
2591
|
undoTurnSnapshots,
|
|
2299
2592
|
rewindToSnapshot,
|
|
2593
|
+
starHome,
|
|
2594
|
+
globalConfigPath,
|
|
2595
|
+
sessionsDir,
|
|
2596
|
+
userMemoryPath,
|
|
2597
|
+
projectConfigPath,
|
|
2598
|
+
discoverSkills,
|
|
2300
2599
|
defaultTaskManager,
|
|
2301
2600
|
resolveShell,
|
|
2302
2601
|
bashTool,
|
|
2303
2602
|
SKIP_DIRS,
|
|
2603
|
+
createIgnorePredicate,
|
|
2304
2604
|
isSensitivePath2 as isSensitivePath,
|
|
2305
2605
|
formatTaskList,
|
|
2306
2606
|
formatTaskStarted,
|
|
@@ -2308,7 +2608,8 @@ export {
|
|
|
2308
2608
|
TodoStore,
|
|
2309
2609
|
formatTodos,
|
|
2310
2610
|
createDefaultRegistry,
|
|
2611
|
+
appendUserMemory,
|
|
2311
2612
|
PLAN_MODE_PROMPT,
|
|
2312
2613
|
AgentLoop
|
|
2313
2614
|
};
|
|
2314
|
-
//# sourceMappingURL=chunk-
|
|
2615
|
+
//# sourceMappingURL=chunk-GN236FD4.js.map
|