@hizliemre/horse-code 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-4WN37LZ3.js → app-GJ5V3WJH.js} +32 -18
- package/dist/{chunk-2WXG35EM.js → chunk-2JDMCRRD.js} +71 -28
- package/dist/chunk-C3AOCIS2.js +1945 -0
- package/dist/{chunk-UTHLEW5V.js → chunk-DCK2QQ2S.js} +1 -1
- package/dist/{chunk-372X5HHU.js → chunk-J6P3CXUR.js} +2 -2
- package/dist/{chunk-JR2JLRE3.js → chunk-KFB5H4HX.js} +1 -1
- package/dist/{chunk-BFIZMM4G.js → chunk-LLJWUHI6.js} +1 -1
- package/dist/{chunk-27F44PBD.js → chunk-M2RP2AGC.js} +433 -2200
- package/dist/chunk-MZM24M5M.js +251 -0
- package/dist/{chunk-G45RWL7S.js → chunk-PEAZQPEZ.js} +4 -0
- package/dist/{chunk-CYLPQWIF.js → chunk-PTOFSTLL.js} +1 -1
- package/dist/{chunk-3ACDNDCG.js → chunk-ST6LIEPZ.js} +1 -1
- package/dist/{chunk-YULQ4URQ.js → chunk-UKIIQD6Q.js} +63 -261
- package/dist/cli.js +27 -22
- package/dist/{fix-QCL5AITT.js → fix-UGGMUNZI.js} +9 -7
- package/dist/{ongoing-6NUSPSCV.js → ongoing-J2UQL7FG.js} +3 -3
- package/dist/research-WQYDF53R.js +111 -0
- package/dist/{save-skills-NPKTYNAF.js → save-skills-7M3XUKMK.js} +2 -2
- package/dist/{trace-run-CZWEZ4R6.js → trace-run-YJNCJPU4.js} +6 -3
- package/dist/{triage-IFCVL5MA.js → triage-ELDGP3JC.js} +4 -4
- package/dist/{verify-HWZBTK5X.js → verify-5CSN24AE.js} +21 -18
- package/package.json +1 -1
- package/dist/{chunk-JLWQCA7B.js → chunk-FNUGG2FG.js} +86 -86
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWebFetchTool,
|
|
3
|
+
readOnlyRegistry
|
|
4
|
+
} from "./chunk-C3AOCIS2.js";
|
|
5
|
+
import "./chunk-ZSQ24YDJ.js";
|
|
6
|
+
import "./chunk-MZM24M5M.js";
|
|
7
|
+
import {
|
|
8
|
+
defaultGitRunner
|
|
9
|
+
} from "./chunk-LPQU436C.js";
|
|
10
|
+
import "./chunk-LLJWUHI6.js";
|
|
11
|
+
import "./chunk-PTOFSTLL.js";
|
|
12
|
+
import "./chunk-63E73TGI.js";
|
|
13
|
+
import "./chunk-6S4WWQMN.js";
|
|
14
|
+
import {
|
|
15
|
+
runToCompletion
|
|
16
|
+
} from "./chunk-FNUGG2FG.js";
|
|
17
|
+
import "./chunk-ZPJP2VH5.js";
|
|
18
|
+
import "./chunk-4M6LXNG2.js";
|
|
19
|
+
import "./chunk-6W4UH2BQ.js";
|
|
20
|
+
|
|
21
|
+
// src/engine/research.ts
|
|
22
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
23
|
+
import { dirname, join, relative } from "path";
|
|
24
|
+
import { existsSync } from "fs";
|
|
25
|
+
var RESEARCH_MAX_TURNS = 120;
|
|
26
|
+
function researchDir(trackedFiles2) {
|
|
27
|
+
const known = [/^docs\/research\//i, /^docs\/adr\//i, /^docs\/decisions\//i, /^research\//i, /^\.planning\//i];
|
|
28
|
+
for (const re of known) {
|
|
29
|
+
const hit = trackedFiles2.find((f) => re.test(f.replace(/\\/g, "/")));
|
|
30
|
+
if (hit) return dirname(hit.replace(/\\/g, "/"));
|
|
31
|
+
}
|
|
32
|
+
return "docs/research";
|
|
33
|
+
}
|
|
34
|
+
function reportName(title, now = /* @__PURE__ */ new Date()) {
|
|
35
|
+
const slug = (title || "research").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
36
|
+
const day = now.toISOString().slice(0, 10);
|
|
37
|
+
return `${day}-${slug || "research"}.md`;
|
|
38
|
+
}
|
|
39
|
+
var researchRule = "You are researching a question and writing ONE document. You are not changing this project: you have no tool that writes a file, and the report is your final answer \u2014 horse-code writes it down.\n\nGround the answer in THIS repository before anything else. Read the code that the question is about; a recommendation that ignores what is already here is a recommendation for a different project.\n\nThe document must contain, in this order:\n1. The question, restated in one or two sentences as you understood it.\n2. What this project does today in the area concerned, with file paths \u2014 the starting position.\n3. The options, one section each. For every option: how it would work here, what it costs, and what it rules out. An option nobody would pick is still worth a paragraph saying why not.\n4. A comparison table across the dimensions that actually decide this one. Choose the dimensions from the question rather than from habit.\n5. A recommendation, with the condition that would change it. A recommendation that nothing could overturn is a preference wearing a suit.\n6. What you could not establish, and what it would take to settle. Say it plainly \u2014 an unexamined assumption presented as a finding is the one failure this document cannot recover from.\n\nWrite markdown. Cite file paths for claims about this project and URLs for claims about the world. Where you are reasoning from your own knowledge rather than from something you read, say so.";
|
|
40
|
+
async function runResearch(opts) {
|
|
41
|
+
const { deps, workdir } = opts;
|
|
42
|
+
const dir = researchDir(opts.trackedFiles ?? await trackedFiles(workdir));
|
|
43
|
+
const rel = join(dir, reportName(opts.title));
|
|
44
|
+
const tools = readOnlyRegistry(deps, { remember: true });
|
|
45
|
+
tools.register(createWebFetchTool());
|
|
46
|
+
const language = opts.language ? `
|
|
47
|
+
|
|
48
|
+
Write the document in ${opts.language}. Keep file paths, identifiers and URLs exactly as they are.` : "";
|
|
49
|
+
const rules = deps.roleRegistry.ruleSuffix();
|
|
50
|
+
const last = await runToCompletion({
|
|
51
|
+
...deps.roleRegistry.fallbackOpts("analyst"),
|
|
52
|
+
systemPrompt: researchRule + language + rules,
|
|
53
|
+
messages: [{ role: "user", content: opts.prompt }],
|
|
54
|
+
tools,
|
|
55
|
+
maxTurns: RESEARCH_MAX_TURNS,
|
|
56
|
+
provider: deps.provider,
|
|
57
|
+
permission: deps.permission,
|
|
58
|
+
approve: deps.approve,
|
|
59
|
+
cwd: workdir,
|
|
60
|
+
signal: deps.signal,
|
|
61
|
+
onActivity: deps.onActivity,
|
|
62
|
+
onLiveActivity: deps.onLiveActivity,
|
|
63
|
+
inbox: deps.inbox,
|
|
64
|
+
...opts.note ? { onSay: opts.note } : {}
|
|
65
|
+
});
|
|
66
|
+
const body = (last.content ?? "").trim();
|
|
67
|
+
if (!body) return { reportPath: rel, written: false, committed: false };
|
|
68
|
+
const abs = join(workdir, rel);
|
|
69
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
70
|
+
await writeFile(abs, `${body}
|
|
71
|
+
`, "utf8");
|
|
72
|
+
return { reportPath: rel, written: true, committed: await commitReport(workdir, rel, opts.title) };
|
|
73
|
+
}
|
|
74
|
+
async function trackedFiles(cwd) {
|
|
75
|
+
try {
|
|
76
|
+
const r = await defaultGitRunner(["ls-files"], cwd);
|
|
77
|
+
return r.code === 0 ? r.stdout.split("\n").filter(Boolean) : [];
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function commitReport(workdir, rel, title) {
|
|
83
|
+
if (!existsSync(join(workdir, ".git")) && !existsSync(join(workdir, rel))) return false;
|
|
84
|
+
const add = await defaultGitRunner(["add", "--", rel], workdir);
|
|
85
|
+
if (add.code !== 0) return false;
|
|
86
|
+
const staged = await defaultGitRunner(["diff", "--cached", "--quiet", "--", rel], workdir);
|
|
87
|
+
if (staged.code === 0) return false;
|
|
88
|
+
const r = await defaultGitRunner(["commit", "-m", `docs(research): ${title || "findings"}`, "--", rel], workdir);
|
|
89
|
+
return r.code === 0;
|
|
90
|
+
}
|
|
91
|
+
function describeResearch(r, branch) {
|
|
92
|
+
if (!r.written) return "The research produced no document \u2014 nothing was written.";
|
|
93
|
+
const where = r.committed ? `Committed on \`${branch}\`.` : "Written, but not committed \u2014 it is in the working tree.";
|
|
94
|
+
return `\u{1F4C4} **Research report:** \`${r.reportPath}\`
|
|
95
|
+
${where}
|
|
96
|
+
|
|
97
|
+
_No code was changed: the lane runs without a write tool at all._`;
|
|
98
|
+
}
|
|
99
|
+
function plannedPath(title, trackedFiles2) {
|
|
100
|
+
return relative(".", join(researchDir(trackedFiles2), reportName(title))).replace(/\\/g, "/");
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
RESEARCH_MAX_TURNS,
|
|
104
|
+
commitReport,
|
|
105
|
+
describeResearch,
|
|
106
|
+
plannedPath,
|
|
107
|
+
reportName,
|
|
108
|
+
researchDir,
|
|
109
|
+
researchRule,
|
|
110
|
+
runResearch
|
|
111
|
+
};
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
saveMcpServer,
|
|
4
4
|
saveRoleSkills,
|
|
5
5
|
saveSkillSource
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KFB5H4HX.js";
|
|
7
7
|
import "./chunk-H2FDGPVW.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-PEAZQPEZ.js";
|
|
9
9
|
export {
|
|
10
10
|
removeSkillSource,
|
|
11
11
|
saveMcpServer,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ColdStartGate,
|
|
2
3
|
SpentSources,
|
|
3
4
|
TRACE_CONCURRENCY,
|
|
4
5
|
buildBrief,
|
|
@@ -7,15 +8,17 @@ import {
|
|
|
7
8
|
planFor,
|
|
8
9
|
runTraces,
|
|
9
10
|
traceableFiles
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-UKIIQD6Q.js";
|
|
12
|
+
import "./chunk-MZM24M5M.js";
|
|
13
|
+
import "./chunk-PEAZQPEZ.js";
|
|
12
14
|
import "./chunk-LPQU436C.js";
|
|
13
15
|
import "./chunk-6S4WWQMN.js";
|
|
14
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-FNUGG2FG.js";
|
|
15
17
|
import "./chunk-ZPJP2VH5.js";
|
|
16
18
|
import "./chunk-4M6LXNG2.js";
|
|
17
19
|
import "./chunk-6W4UH2BQ.js";
|
|
18
20
|
export {
|
|
21
|
+
ColdStartGate,
|
|
19
22
|
SpentSources,
|
|
20
23
|
TRACE_CONCURRENCY,
|
|
21
24
|
buildBrief,
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
describeSizeDoubt,
|
|
9
9
|
sizeRequest,
|
|
10
10
|
triageFinding
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-J6P3CXUR.js";
|
|
12
|
+
import "./chunk-LLJWUHI6.js";
|
|
13
|
+
import "./chunk-PTOFSTLL.js";
|
|
13
14
|
import "./chunk-63E73TGI.js";
|
|
14
|
-
import "./chunk-CYLPQWIF.js";
|
|
15
15
|
import "./chunk-6S4WWQMN.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-FNUGG2FG.js";
|
|
17
17
|
import "./chunk-ZPJP2VH5.js";
|
|
18
18
|
import "./chunk-4M6LXNG2.js";
|
|
19
19
|
import "./chunk-6W4UH2BQ.js";
|
|
@@ -3,41 +3,44 @@ import {
|
|
|
3
3
|
describeFix,
|
|
4
4
|
dirtyPaths,
|
|
5
5
|
runFix
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ST6LIEPZ.js";
|
|
7
7
|
import {
|
|
8
8
|
respondIn
|
|
9
9
|
} from "./chunk-M2RKCIGV.js";
|
|
10
|
+
import {
|
|
11
|
+
askInUserLanguage,
|
|
12
|
+
inUserLanguage
|
|
13
|
+
} from "./chunk-DCK2QQ2S.js";
|
|
10
14
|
import {
|
|
11
15
|
buildAskUserTool,
|
|
16
|
+
editFileTool,
|
|
17
|
+
refreshAfterChange,
|
|
18
|
+
shellTool,
|
|
19
|
+
writeFileTool
|
|
20
|
+
} from "./chunk-M2RP2AGC.js";
|
|
21
|
+
import {
|
|
12
22
|
buildRememberTool,
|
|
13
23
|
constitutionNote,
|
|
14
24
|
createWebFetchTool,
|
|
15
25
|
deferMcp,
|
|
16
|
-
editFileTool,
|
|
17
26
|
featureSlugFor,
|
|
18
27
|
gitTool,
|
|
19
|
-
refreshAfterChange,
|
|
20
|
-
shellTool,
|
|
21
28
|
specsDir,
|
|
22
|
-
verifyPaths
|
|
23
|
-
|
|
24
|
-
} from "./chunk-27F44PBD.js";
|
|
25
|
-
import {
|
|
26
|
-
askInUserLanguage,
|
|
27
|
-
inUserLanguage
|
|
28
|
-
} from "./chunk-UTHLEW5V.js";
|
|
29
|
+
verifyPaths
|
|
30
|
+
} from "./chunk-C3AOCIS2.js";
|
|
29
31
|
import "./chunk-ZSQ24YDJ.js";
|
|
32
|
+
import "./chunk-UKIIQD6Q.js";
|
|
30
33
|
import {
|
|
31
34
|
buildSkillTool
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import "./chunk-
|
|
35
|
+
} from "./chunk-MZM24M5M.js";
|
|
36
|
+
import "./chunk-PEAZQPEZ.js";
|
|
34
37
|
import {
|
|
35
38
|
defaultGitRunner
|
|
36
39
|
} from "./chunk-LPQU436C.js";
|
|
37
40
|
import {
|
|
38
41
|
describeEscalation,
|
|
39
42
|
triageFinding
|
|
40
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-J6P3CXUR.js";
|
|
41
44
|
import {
|
|
42
45
|
BATCH_TOOLS_NOTE,
|
|
43
46
|
contextTools,
|
|
@@ -47,16 +50,16 @@ import {
|
|
|
47
50
|
projectToolsNote,
|
|
48
51
|
readFileTool,
|
|
49
52
|
reinforceUsed
|
|
50
|
-
} from "./chunk-
|
|
51
|
-
import "./chunk-63E73TGI.js";
|
|
53
|
+
} from "./chunk-LLJWUHI6.js";
|
|
52
54
|
import {
|
|
53
55
|
ToolRegistry
|
|
54
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-PTOFSTLL.js";
|
|
57
|
+
import "./chunk-63E73TGI.js";
|
|
55
58
|
import "./chunk-6S4WWQMN.js";
|
|
56
59
|
import {
|
|
57
60
|
handedOver,
|
|
58
61
|
runToCompletion
|
|
59
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-FNUGG2FG.js";
|
|
60
63
|
import "./chunk-ZPJP2VH5.js";
|
|
61
64
|
import {
|
|
62
65
|
loadGraphSync
|
package/package.json
CHANGED
|
@@ -2,86 +2,6 @@ import {
|
|
|
2
2
|
sessionBase
|
|
3
3
|
} from "./chunk-6W4UH2BQ.js";
|
|
4
4
|
|
|
5
|
-
// src/tui/format.ts
|
|
6
|
-
function fmtTokens(n) {
|
|
7
|
-
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
8
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
9
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
10
|
-
return String(n);
|
|
11
|
-
}
|
|
12
|
-
function fmtDuration(ms) {
|
|
13
|
-
const s = Math.max(0, Math.round(ms / 1e3));
|
|
14
|
-
if (s < 60) return `${s}s`;
|
|
15
|
-
return `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, "0")}s`;
|
|
16
|
-
}
|
|
17
|
-
function relTime(then, now = Date.now()) {
|
|
18
|
-
const s = Math.max(0, Math.round((now - then) / 1e3));
|
|
19
|
-
if (s < 60) return "just now";
|
|
20
|
-
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
21
|
-
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
22
|
-
return `${Math.floor(s / 86400)}d ago`;
|
|
23
|
-
}
|
|
24
|
-
function stripThinking(text) {
|
|
25
|
-
let out = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
26
|
-
const lastClose = out.toLowerCase().lastIndexOf("</think>");
|
|
27
|
-
if (lastClose !== -1 && !/<think>/i.test(out.slice(0, lastClose))) {
|
|
28
|
-
out = out.slice(lastClose + "</think>".length);
|
|
29
|
-
}
|
|
30
|
-
const open = out.toLowerCase().lastIndexOf("<think>");
|
|
31
|
-
if (open !== -1) out = out.slice(0, open).trimEnd();
|
|
32
|
-
return out.trimStart();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/agent/attach.ts
|
|
36
|
-
import { readFileSync, statSync } from "fs";
|
|
37
|
-
import { isAbsolute, resolve } from "path";
|
|
38
|
-
import { homedir } from "os";
|
|
39
|
-
var IMAGE_EXT = {
|
|
40
|
-
png: "image/png",
|
|
41
|
-
jpg: "image/jpeg",
|
|
42
|
-
jpeg: "image/jpeg",
|
|
43
|
-
gif: "image/gif",
|
|
44
|
-
webp: "image/webp"
|
|
45
|
-
};
|
|
46
|
-
var MAX_IMAGE_BYTES = 5e6;
|
|
47
|
-
var CANDIDATE = /(?:"([^"\n]+?\.(?:png|jpe?g|gif|webp))"|'([^'\n]+?\.(?:png|jpe?g|gif|webp))'|((?:~|\.{0,2}\/|[A-Za-z0-9_.-]+\/)[^\n"'`]*?\.(?:png|jpe?g|gif|webp)))/gi;
|
|
48
|
-
function expand(p, cwd) {
|
|
49
|
-
const t = p.trim();
|
|
50
|
-
if (t.startsWith("~/")) return resolve(homedir(), t.slice(2));
|
|
51
|
-
return isAbsolute(t) ? t : resolve(cwd, t);
|
|
52
|
-
}
|
|
53
|
-
function attachedImages(text, cwd) {
|
|
54
|
-
const out = [];
|
|
55
|
-
const seen = /* @__PURE__ */ new Set();
|
|
56
|
-
for (const m of text.matchAll(CANDIDATE)) {
|
|
57
|
-
const raw = m[1] ?? m[2] ?? m[3];
|
|
58
|
-
if (!raw) continue;
|
|
59
|
-
if (/^[a-z]+:\/\//i.test(raw)) continue;
|
|
60
|
-
const abs = expand(raw, cwd);
|
|
61
|
-
if (seen.has(abs)) continue;
|
|
62
|
-
const ext = abs.slice(abs.lastIndexOf(".") + 1).toLowerCase();
|
|
63
|
-
const mime = IMAGE_EXT[ext];
|
|
64
|
-
if (!mime) continue;
|
|
65
|
-
try {
|
|
66
|
-
const st = statSync(abs);
|
|
67
|
-
if (!st.isFile() || st.size > MAX_IMAGE_BYTES || st.size === 0) continue;
|
|
68
|
-
out.push(`data:${mime};base64,${readFileSync(abs).toString("base64")}`);
|
|
69
|
-
seen.add(abs);
|
|
70
|
-
} catch {
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return out;
|
|
74
|
-
}
|
|
75
|
-
var OWN_PASTE = /\S*[\\/]\.horsecode[\\/]pastes[\\/][^\s"'`)\]]+\.(?:png|jpe?g|gif|webp)/gi;
|
|
76
|
-
var HANDED_OVER = "[screenshot pasted by the developer]";
|
|
77
|
-
function withoutPastePaths(text) {
|
|
78
|
-
return text.replace(OWN_PASTE, HANDED_OVER);
|
|
79
|
-
}
|
|
80
|
-
function handedOver(text, cwd) {
|
|
81
|
-
const images = attachedImages(text, cwd);
|
|
82
|
-
return { content: withoutPastePaths(text), ...images.length ? { images } : {} };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
5
|
// src/obs/telemetry.ts
|
|
86
6
|
import { AsyncLocalStorage } from "async_hooks";
|
|
87
7
|
import { randomBytes } from "crypto";
|
|
@@ -227,6 +147,86 @@ function telemetry() {
|
|
|
227
147
|
return active;
|
|
228
148
|
}
|
|
229
149
|
|
|
150
|
+
// src/tui/format.ts
|
|
151
|
+
function fmtTokens(n) {
|
|
152
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
153
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
154
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
155
|
+
return String(n);
|
|
156
|
+
}
|
|
157
|
+
function fmtDuration(ms) {
|
|
158
|
+
const s = Math.max(0, Math.round(ms / 1e3));
|
|
159
|
+
if (s < 60) return `${s}s`;
|
|
160
|
+
return `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, "0")}s`;
|
|
161
|
+
}
|
|
162
|
+
function relTime(then, now = Date.now()) {
|
|
163
|
+
const s = Math.max(0, Math.round((now - then) / 1e3));
|
|
164
|
+
if (s < 60) return "just now";
|
|
165
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
166
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
167
|
+
return `${Math.floor(s / 86400)}d ago`;
|
|
168
|
+
}
|
|
169
|
+
function stripThinking(text) {
|
|
170
|
+
let out = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
171
|
+
const lastClose = out.toLowerCase().lastIndexOf("</think>");
|
|
172
|
+
if (lastClose !== -1 && !/<think>/i.test(out.slice(0, lastClose))) {
|
|
173
|
+
out = out.slice(lastClose + "</think>".length);
|
|
174
|
+
}
|
|
175
|
+
const open = out.toLowerCase().lastIndexOf("<think>");
|
|
176
|
+
if (open !== -1) out = out.slice(0, open).trimEnd();
|
|
177
|
+
return out.trimStart();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/agent/attach.ts
|
|
181
|
+
import { readFileSync, statSync } from "fs";
|
|
182
|
+
import { isAbsolute, resolve } from "path";
|
|
183
|
+
import { homedir } from "os";
|
|
184
|
+
var IMAGE_EXT = {
|
|
185
|
+
png: "image/png",
|
|
186
|
+
jpg: "image/jpeg",
|
|
187
|
+
jpeg: "image/jpeg",
|
|
188
|
+
gif: "image/gif",
|
|
189
|
+
webp: "image/webp"
|
|
190
|
+
};
|
|
191
|
+
var MAX_IMAGE_BYTES = 5e6;
|
|
192
|
+
var CANDIDATE = /(?:"([^"\n]+?\.(?:png|jpe?g|gif|webp))"|'([^'\n]+?\.(?:png|jpe?g|gif|webp))'|((?:~|\.{0,2}\/|[A-Za-z0-9_.-]+\/)[^\n"'`]*?\.(?:png|jpe?g|gif|webp)))/gi;
|
|
193
|
+
function expand(p, cwd) {
|
|
194
|
+
const t = p.trim();
|
|
195
|
+
if (t.startsWith("~/")) return resolve(homedir(), t.slice(2));
|
|
196
|
+
return isAbsolute(t) ? t : resolve(cwd, t);
|
|
197
|
+
}
|
|
198
|
+
function attachedImages(text, cwd) {
|
|
199
|
+
const out = [];
|
|
200
|
+
const seen = /* @__PURE__ */ new Set();
|
|
201
|
+
for (const m of text.matchAll(CANDIDATE)) {
|
|
202
|
+
const raw = m[1] ?? m[2] ?? m[3];
|
|
203
|
+
if (!raw) continue;
|
|
204
|
+
if (/^[a-z]+:\/\//i.test(raw)) continue;
|
|
205
|
+
const abs = expand(raw, cwd);
|
|
206
|
+
if (seen.has(abs)) continue;
|
|
207
|
+
const ext = abs.slice(abs.lastIndexOf(".") + 1).toLowerCase();
|
|
208
|
+
const mime = IMAGE_EXT[ext];
|
|
209
|
+
if (!mime) continue;
|
|
210
|
+
try {
|
|
211
|
+
const st = statSync(abs);
|
|
212
|
+
if (!st.isFile() || st.size > MAX_IMAGE_BYTES || st.size === 0) continue;
|
|
213
|
+
out.push(`data:${mime};base64,${readFileSync(abs).toString("base64")}`);
|
|
214
|
+
seen.add(abs);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
var OWN_PASTE = /\S*[\\/]\.horsecode[\\/]pastes[\\/][^\s"'`)\]]+\.(?:png|jpe?g|gif|webp)/gi;
|
|
221
|
+
var HANDED_OVER = "[screenshot pasted by the developer]";
|
|
222
|
+
function withoutPastePaths(text) {
|
|
223
|
+
return text.replace(OWN_PASTE, HANDED_OVER);
|
|
224
|
+
}
|
|
225
|
+
function handedOver(text, cwd) {
|
|
226
|
+
const images = attachedImages(text, cwd);
|
|
227
|
+
return { content: withoutPastePaths(text), ...images.length ? { images } : {} };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
230
|
// src/agent/recall.ts
|
|
231
231
|
var RECALLABLE = /* @__PURE__ */ new Set(["read_file", "grep", "glob", "graph_trace", "graph_find", "graph_context"]);
|
|
232
232
|
var INVALIDATING = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
|
|
@@ -1161,12 +1161,6 @@ async function runToCompletion(opts) {
|
|
|
1161
1161
|
}
|
|
1162
1162
|
|
|
1163
1163
|
export {
|
|
1164
|
-
fmtTokens,
|
|
1165
|
-
fmtDuration,
|
|
1166
|
-
relTime,
|
|
1167
|
-
stripThinking,
|
|
1168
|
-
handedOver,
|
|
1169
|
-
truncateSafe,
|
|
1170
1164
|
Telemetry,
|
|
1171
1165
|
sampleMemory,
|
|
1172
1166
|
estimateFreezeSeconds,
|
|
@@ -1174,6 +1168,12 @@ export {
|
|
|
1174
1168
|
clearPerfMarks,
|
|
1175
1169
|
setTelemetry,
|
|
1176
1170
|
telemetry,
|
|
1171
|
+
fmtTokens,
|
|
1172
|
+
fmtDuration,
|
|
1173
|
+
relTime,
|
|
1174
|
+
stripThinking,
|
|
1175
|
+
handedOver,
|
|
1176
|
+
truncateSafe,
|
|
1177
1177
|
redactSecrets,
|
|
1178
1178
|
runRoleAgent,
|
|
1179
1179
|
runToCompletion
|