mythos-code 1.7.3 → 1.7.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/bin/mythos.js +148 -10
- package/package.json +1 -1
package/bin/mythos.js
CHANGED
|
@@ -10,6 +10,107 @@ import { exec, spawn, execFileSync } from "node:child_process";
|
|
|
10
10
|
// Wird roh in bin/mythos.js (CLI, ESM) und main.js (App, CommonJS) eingefügt.
|
|
11
11
|
// Erwartet, dass `fs` und `path` im umgebenden Code schon importiert sind.
|
|
12
12
|
|
|
13
|
+
// ---------- Werkzeug-Blöcke robust lesen (CLI, Desktop-App: Haupt- und Fensterprozess) ----------
|
|
14
|
+
// Wird roh über den Platzhalter TOOL_PARSE eingefügt. Keine Abhängigkeiten.
|
|
15
|
+
// Modelle schreiben Aufrufe nicht immer sauber: fehlendes </tool>, <tool_call> statt <tool>, ```json-Zäune,
|
|
16
|
+
// {"name":…,"arguments":{…}} statt flacher Felder, echte Zeilenumbrüche in Strings oder abgeschnittenes JSON.
|
|
17
|
+
// Nichts davon darf als Rohtext im Chat landen.
|
|
18
|
+
|
|
19
|
+
const TOOL_NAMES = ["run", "read", "edit", "write", "search", "ls", "websearch", "fetch", "cloud", "todo"];
|
|
20
|
+
const TOOL_OPEN_RE = /<(tool_call|tool_use|tool)(?:\s[^>]*)?>/i;
|
|
21
|
+
// Ein ganzer Block – auch nicht geschlossen (dann bis zum Textende).
|
|
22
|
+
const TOOL_BLOCK_RE = /<(tool_call|tool_use|tool)(?:\s[^>]*)?>[\s\S]*?(?:<\/\1\s*>|$)/gi;
|
|
23
|
+
|
|
24
|
+
/** Alle Werkzeug-Blöcke und verwaiste End-Tags entfernen. */
|
|
25
|
+
function stripToolBlocks(text) {
|
|
26
|
+
return String(text || "").replace(TOOL_BLOCK_RE, "").replace(/<\/(?:tool_call|tool_use|tool)\s*>/gi, "");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Erstes JSON-Objekt im Text herausschneiden. Rohe Zeilenumbrüche in Strings werden escaped,
|
|
30
|
+
* abgeschnittenes JSON wird geschlossen. → { json, complete } oder null. */
|
|
31
|
+
function jsonObjectAt(s) {
|
|
32
|
+
const start = s.indexOf("{");
|
|
33
|
+
if (start < 0) return null;
|
|
34
|
+
let out = "", inStr = false, esc = false;
|
|
35
|
+
const stack = [];
|
|
36
|
+
for (let i = start; i < s.length; i++) {
|
|
37
|
+
const ch = s[i];
|
|
38
|
+
if (inStr) {
|
|
39
|
+
if (esc) { esc = false; out += ch; continue; }
|
|
40
|
+
if (ch === "\\") { esc = true; out += ch; continue; }
|
|
41
|
+
if (ch === '"') { inStr = false; out += ch; continue; }
|
|
42
|
+
out += ch === "\n" ? "\\n" : ch === "\r" ? "\\r" : ch === "\t" ? "\\t" : ch;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === '"') { inStr = true; out += ch; continue; }
|
|
46
|
+
if (ch === "{" || ch === "[") stack.push(ch === "{" ? "}" : "]");
|
|
47
|
+
else if (ch === "}" || ch === "]") { stack.pop(); out += ch; if (!stack.length) return { json: out, complete: true }; continue; }
|
|
48
|
+
out += ch;
|
|
49
|
+
}
|
|
50
|
+
// Abgeschnitten: offenen String und Klammern schließen, hängendes Komma/Doppelpunkt weg.
|
|
51
|
+
if (esc) out = out.slice(0, -1);
|
|
52
|
+
if (inStr) out += '"';
|
|
53
|
+
out = out.replace(/[,:]\s*$/, "");
|
|
54
|
+
while (stack.length) out += stack.pop();
|
|
55
|
+
return { json: out, complete: false };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Ersten Werkzeug-Aufruf aus einer Antwort lesen.
|
|
59
|
+
* → { tool: {name, …} | null, error: string | "", text: sichtbarer Text ohne Werkzeug-Blöcke } */
|
|
60
|
+
function parseToolBlock(out) {
|
|
61
|
+
out = String(out || "");
|
|
62
|
+
const text = stripToolBlocks(out).trim();
|
|
63
|
+
const m = TOOL_OPEN_RE.exec(out);
|
|
64
|
+
if (!m) return { tool: null, error: "", text };
|
|
65
|
+
let body = out.slice(m.index + m[0].length);
|
|
66
|
+
const end = body.search(/<\/(?:tool_call|tool_use|tool)\s*>/i);
|
|
67
|
+
if (end >= 0) body = body.slice(0, end);
|
|
68
|
+
body = body.replace(/```[a-z]*\s*/gi, "");
|
|
69
|
+
const raw = jsonObjectAt(body);
|
|
70
|
+
if (!raw) return { tool: null, error: "Im Werkzeug-Block steht kein JSON-Objekt.", text };
|
|
71
|
+
let t;
|
|
72
|
+
try { t = JSON.parse(raw.json); } catch (e) { return { tool: null, error: "Werkzeug-JSON ungültig (" + e.message + ").", text }; }
|
|
73
|
+
if (!t || typeof t !== "object" || Array.isArray(t)) return { tool: null, error: "Werkzeug-JSON muss ein Objekt sein.", text };
|
|
74
|
+
// Name auch aus <tool name="…"> oder anderen üblichen Feldern.
|
|
75
|
+
const attr = m[0].match(/\bname\s*=\s*["']([^"']+)["']/i);
|
|
76
|
+
if (typeof t.name !== "string") t.name = String(t.tool_name || t.function || (attr && attr[1]) || "");
|
|
77
|
+
// {"name":"x","arguments":{…}} / "input" / "parameters" / "args" flach machen (bei MCP: nach "args").
|
|
78
|
+
for (const k of ["arguments", "input", "parameters", "args"]) {
|
|
79
|
+
const a = t[k];
|
|
80
|
+
if (!a || typeof a !== "object" || Array.isArray(a)) continue;
|
|
81
|
+
if (t.name === "mcp") { if (k !== "args") { delete t[k]; if (!t.args) t.args = a; } continue; }
|
|
82
|
+
delete t[k]; for (const x of Object.keys(a)) if (!(x in t)) t[x] = a[x];
|
|
83
|
+
}
|
|
84
|
+
// mcp__server__tool (Hook-Schreibweise) → MCP-Aufruf.
|
|
85
|
+
const mm = t.name.match(/^mcp__(.+?)__(.+)$/);
|
|
86
|
+
if (mm) { const { name, ...rest } = t; t = { name: "mcp", server: mm[1], tool: mm[2], args: rest.args || rest }; }
|
|
87
|
+
t.name = t.name.trim();
|
|
88
|
+
if (!t.name) return { tool: null, error: "Im Werkzeug-Block fehlt \"name\".", text };
|
|
89
|
+
// Abgeschnittene Aufrufe nicht ausführen (sonst z. B. halbe Dateien) – außer harmlose wie todo.
|
|
90
|
+
if (!raw.complete && t.name !== "todo") return { tool: null, error: "Der Werkzeug-Aufruf " + t.name + " ist abgeschnitten.", text };
|
|
91
|
+
return { tool: t, error: "", text };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Prüfen, ob es das Werkzeug wirklich gibt. extra = { mcp: [{ server, tools: [Namen] }], browser: bool }.
|
|
95
|
+
* → "" wenn ok, sonst eine Erklärung fürs Modell (mit der Liste, was es gibt). */
|
|
96
|
+
function toolProblem(t, extra) {
|
|
97
|
+
extra = extra || {};
|
|
98
|
+
const mcp = extra.mcp || [];
|
|
99
|
+
const avail = TOOL_NAMES.concat(mcp.length ? ["mcp"] : [], extra.browser ? ["browser"] : []);
|
|
100
|
+
const list = "Verfügbar sind nur: " + avail.join(", ") + (mcp.length ? " · MCP: " + mcp.map((s) => s.server + " (" + s.tools.join(", ") + ")").join("; ") : " · keine MCP-Connectoren verbunden") + ".";
|
|
101
|
+
const tail = " Erfinde keine Werkzeuge. Wenn etwas damit nicht geht, sag das dem Nutzer kurz und mach mit dem Rest weiter.";
|
|
102
|
+
if (t.name === "mcp") {
|
|
103
|
+
const s = mcp.find((x) => x.server === t.server) || mcp.find((x) => String(x.server).toLowerCase() === String(t.server || "").toLowerCase());
|
|
104
|
+
if (!s) return "FEHLER: MCP-Server „" + (t.server || "?") + "“ ist nicht verbunden. " + list + tail;
|
|
105
|
+
t.server = s.server;
|
|
106
|
+
if (!s.tools.includes(t.tool)) return "FEHLER: MCP-Werkzeug „" + t.tool + "“ gibt es bei " + s.server + " nicht. " + list + tail;
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
if (t.name === "browser") return extra.browser ? "" : "FEHLER: Das Browser-Plugin ist nicht verbunden. " + list + tail;
|
|
110
|
+
return TOOL_NAMES.includes(t.name) ? "" : "FEHLER: Werkzeug „" + t.name + "“ gibt es nicht. " + list + tail;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
13
114
|
const TOOL_PROMPT = [
|
|
14
115
|
"Werkzeuge – antworte pro Schritt mit GENAU EINEM Block:",
|
|
15
116
|
'<tool>{"name":"run","cmd":"..."}</tool> Shell-Befehl ausführen (Ausgabe läuft live mit)',
|
|
@@ -20,7 +121,9 @@ const TOOL_PROMPT = [
|
|
|
20
121
|
'<tool>{"name":"ls","path":"."}</tool> Ordner auflisten',
|
|
21
122
|
'<tool>{"name":"websearch","query":"..."}</tool> Im Internet suchen (z. B. Doku, Fehlermeldungen)',
|
|
22
123
|
'<tool>{"name":"fetch","url":"https://..."}</tool> Webseite als Text lesen',
|
|
124
|
+
'<tool>{"name":"cloud","app":"Name der App"}</tool> Mythos Cloud verbinden: eigenes Backend (Datenbank) + KI-Gateway (Chat, Streaming, Bilder) für die App, die du baust. Nutze das IMMER, sobald die App Daten speichern oder KI nutzen soll – statt Supabase/Firebase/OpenAI-Keys o. Ä. Legt mythos-cloud.js im Projekt an und erklärt die Nutzung.',
|
|
23
125
|
'<tool>{"name":"todo","items":[{"text":"Schritt","done":false}]}</tool> Aufgabenliste anlegen/aktualisieren (bei größeren Aufgaben zuerst planen, dann Punkte abhaken; immer die komplette Liste senden)',
|
|
126
|
+
"Es gibt NUR diese Werkzeuge (plus die unten genannten MCP-/Browser-Werkzeuge, falls welche aufgeführt sind). Erfinde keine Werkzeuge, Connectoren oder MCP-Server. Fehlt etwas (z. B. ein nicht verbundener Connector), sag das dem Nutzer kurz und mach mit dem Rest weiter. Schließe jeden Block mit </tool>.",
|
|
24
127
|
"Nach jedem Werkzeug bekommst du das Ergebnis. Suche erst, statt Dateien blind zu lesen. Ändere bestehende Dateien mit edit statt write.",
|
|
25
128
|
"Arbeite Schritt für Schritt, bis die Aufgabe erledigt ist, dann antworte normal ohne <tool> (Markdown erlaubt).",
|
|
26
129
|
].join("\n");
|
|
@@ -426,7 +529,7 @@ let goal = "";
|
|
|
426
529
|
const SYSTEM = () => "Du bist Mythos Code, ein autonomer Coding-Agent im Terminal des Nutzers (OS: " + process.platform + ", Projektordner: " + ROOT + ").\n" +
|
|
427
530
|
TOOL_PROMPT + memoryPrompt(readMemory(ROOT)) + goalPrompt(goal);
|
|
428
531
|
|
|
429
|
-
const SITE = process.env.MYTHOS_SITE_URL || "https://ai
|
|
532
|
+
const SITE = process.env.MYTHOS_SITE_URL || "https://mythos-core-ai.lovable.app";
|
|
430
533
|
const FN = process.env.MYTHOS_FN_URL || API.replace(/\/v1-messages$/, "");
|
|
431
534
|
const authPost = async (body) => (await fetch(FN + "/cli-auth", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })).json();
|
|
432
535
|
|
|
@@ -476,6 +579,23 @@ const tickLine = () => {
|
|
|
476
579
|
};
|
|
477
580
|
const startTick = () => { if (tick || !working) return; tick = setInterval(() => process.stdout.write("\r\x1b[K" + C.d + tickLine() + C.x), 250); };
|
|
478
581
|
const stopTick = () => { if (tick) { clearInterval(tick); tick = null; } clearLine(); };
|
|
582
|
+
let adN = 0, adTier = null, adAdminDay = "";
|
|
583
|
+
async function maybeAd() {
|
|
584
|
+
try {
|
|
585
|
+
if (adTier == null) { const j = await (await fetch(API.replace(/\/v1-messages.*$/, "") + "/cli-auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "usage", api_key: cfg.key }) })).json(); adTier = j.admin ? "admin" : (j.tier || "free"); }
|
|
586
|
+
let due = false; const today = new Date().toDateString();
|
|
587
|
+
if (adTier === "admin") { due = adAdminDay !== today; adAdminDay = today; }
|
|
588
|
+
else { const every = adTier === "free" ? 4 : adTier === "light" ? 10 : 0; due = every > 0 && (++adN % every === 0); }
|
|
589
|
+
if (due) {
|
|
590
|
+
const fnBase = API.replace(/\/v1-messages.*$/, "");
|
|
591
|
+
let ads = []; try { ads = (await (await fetch(fnBase + "/ad-review", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "list" }) })).json()).ads || []; } catch {}
|
|
592
|
+
const pool = [{ house: true, weight: 3 }].concat(ads); let r = Math.random() * pool.reduce((t, a) => t + Math.max(1, a.weight), 0); let ad = pool[0];
|
|
593
|
+
for (const a of pool) { r -= Math.max(1, a.weight); if (r <= 0) { ad = a; break; } }
|
|
594
|
+
if (!ad.house) { console.log("\x1b[2m📣 Werbung\x1b[0m \x1b[1m" + ad.product + "\x1b[0m – " + ad.ad_text + ": " + ad.link + "\n\x1b[2m Buche deine eigene Werbung: https://mythos-core-ai.lovable.app/werbung\x1b[0m\n"); return; }
|
|
595
|
+
}
|
|
596
|
+
if (due) console.log("\x1b[2m📣 Werbung\x1b[0m \x1b[1m@at\x1b[0m – dein Feed voller Vibes: https://at-feed-vibes.lovable.app/\n\x1b[2m Buche deine eigene Werbung: https://mythos-core-ai.lovable.app/werbung\x1b[0m\n");
|
|
597
|
+
} catch {}
|
|
598
|
+
}
|
|
479
599
|
const say = (s) => { const was = !!tick; stopTick(); console.log(s); if (was) startTick(); };
|
|
480
600
|
|
|
481
601
|
// ---------- Stoppen mit Strg+C ----------
|
|
@@ -523,9 +643,9 @@ function streamPrinter() {
|
|
|
523
643
|
return {
|
|
524
644
|
push(full) {
|
|
525
645
|
let vis = full;
|
|
526
|
-
const k = vis.
|
|
646
|
+
const k = vis.search(/<tool(?:_call|_use)?[\s>]/i);
|
|
527
647
|
if (k >= 0) vis = vis.slice(0, k);
|
|
528
|
-
else
|
|
648
|
+
else { const p = vis.match(/<(?:t(?:o(?:o(?:l(?:_(?:c(?:a(?:l(?:l)?)?)?|u(?:s(?:e)?)?)?)?)?)?)?)?$/i); if (p) vis = vis.slice(0, p.index); }
|
|
529
649
|
if (vis.length <= printed) return;
|
|
530
650
|
buf += vis.slice(printed); printed = vis.length;
|
|
531
651
|
let i; while ((i = buf.indexOf("\n")) !== -1) { line(buf.slice(0, i)); buf = buf.slice(i + 1); }
|
|
@@ -637,6 +757,19 @@ function runLive(cmd) {
|
|
|
637
757
|
});
|
|
638
758
|
}
|
|
639
759
|
|
|
760
|
+
// Mythos Cloud: Backend + KI-Gateway für die gebaute App verbinden und mythos-cloud.js anlegen.
|
|
761
|
+
async function cloudConnect(t) {
|
|
762
|
+
try {
|
|
763
|
+
const r = await fetch(FN + "/app-cloud", { method: "POST", headers: { "content-type": "application/json" },
|
|
764
|
+
body: JSON.stringify({ action: "connect", api_key: cfg.key, name: String(t.app || path.basename(ROOT)) }) });
|
|
765
|
+
const j = await r.json().catch(() => ({}));
|
|
766
|
+
if (!r.ok || !j.client_js) return "FEHLER: Mythos Cloud: " + (j.error || "HTTP " + r.status);
|
|
767
|
+
const res = fileTool({ name: "write", path: (t.path = t.path || j.file), content: j.client_js }, ROOT);
|
|
768
|
+
say(C.g + " ☁ Mythos Cloud verbunden (" + j.app + ")" + C.x);
|
|
769
|
+
return res.result + "\n\n" + j.guide;
|
|
770
|
+
} catch (e) { return "FEHLER: Mythos Cloud nicht erreichbar: " + e.message; }
|
|
771
|
+
}
|
|
772
|
+
|
|
640
773
|
async function runTool(t) {
|
|
641
774
|
if (t.name === "todo") {
|
|
642
775
|
const items = normalizeTodos(t);
|
|
@@ -650,6 +783,7 @@ async function runTool(t) {
|
|
|
650
783
|
: t.name === "write" ? "write " + t.path + " (" + String(t.content || "").length + " Zeichen)"
|
|
651
784
|
: t.name === "websearch" ? "websearch " + t.query
|
|
652
785
|
: t.name === "fetch" ? "fetch " + t.url
|
|
786
|
+
: t.name === "cloud" ? "cloud verbinden: " + (t.app || "App")
|
|
653
787
|
: t.name + " " + (t.path || "");
|
|
654
788
|
say(C.y + "⚙ " + label + C.x);
|
|
655
789
|
// Sicherheitsnetz: gefährliche Befehle fragen immer nach (auch mit --yes) und werden bei /goal blockiert.
|
|
@@ -662,13 +796,14 @@ async function runTool(t) {
|
|
|
662
796
|
startTick();
|
|
663
797
|
if (a !== "j" && a !== "y") return "Vom Nutzer abgelehnt (Sicherheitsnetz). Finde einen sichereren Weg.";
|
|
664
798
|
}
|
|
665
|
-
if ((t.name === "run" || t.name === "write" || t.name === "edit") && !autoYes && !why) {
|
|
799
|
+
if ((t.name === "run" || t.name === "write" || t.name === "edit" || t.name === "cloud") && !autoYes && !why) {
|
|
666
800
|
stopTick();
|
|
667
801
|
const a = (await ask(C.d + " Erlauben? [j/N/a=immer] " + C.x)).trim().toLowerCase();
|
|
668
802
|
startTick();
|
|
669
803
|
if (a === "a") autoYes = true; else if (a !== "j" && a !== "y") return "Vom Nutzer abgelehnt.";
|
|
670
804
|
}
|
|
671
805
|
if (t.name === "run") return runLive(t.cmd);
|
|
806
|
+
if (t.name === "cloud") return cloudConnect(t);
|
|
672
807
|
if (isWebTool(t)) {
|
|
673
808
|
const r = await webTool(t);
|
|
674
809
|
say(C.d + " " + r.result.split("\n").filter(Boolean).slice(0, 3).join(" · ").slice(0, 160) + C.x);
|
|
@@ -694,15 +829,17 @@ async function turnInner(history) {
|
|
|
694
829
|
pr.end(); activity = "";
|
|
695
830
|
if (stopped) break;
|
|
696
831
|
history.push({ role: "assistant", content: out });
|
|
697
|
-
const
|
|
698
|
-
if (!
|
|
832
|
+
const pb = parseToolBlock(out);
|
|
833
|
+
if (!pb.tool && !pb.error) {
|
|
699
834
|
if (!goal || /ZIEL ERREICHT/.test(out)) return goal ? "reached" : "done";
|
|
700
835
|
if (++nudges > 40) { say(C.y + "🎯 Mythos kommt beim Ziel nicht weiter – schau es dir bitte an (/weiter macht weiter)." + C.x); return "stuck"; }
|
|
701
836
|
history.push({ role: "user", content: GOAL_NUDGE });
|
|
702
837
|
continue;
|
|
703
838
|
}
|
|
704
839
|
nudges = 0;
|
|
705
|
-
|
|
840
|
+
if (pb.error) { history.push({ role: "user", content: pb.error + " Sende den Block vollständig als gültiges JSON in <tool>…</tool>." }); continue; }
|
|
841
|
+
const t = pb.tool, bad = toolProblem(t);
|
|
842
|
+
if (bad) { history.push({ role: "user", content: "Werkzeug-Ergebnis:\n" + bad }); continue; }
|
|
706
843
|
const res = await runTool(t);
|
|
707
844
|
history.push({ role: "user", content: "Werkzeug-Ergebnis:\n" + res });
|
|
708
845
|
}
|
|
@@ -739,6 +876,7 @@ async function turn(history, input, images) {
|
|
|
739
876
|
const took = fmt(Date.now() - T0);
|
|
740
877
|
console.log((stopped ? C.r + "■ Gestoppt nach " + took : C.g + "✓ Mythos hat " + took + " gearbeitet") + C.x +
|
|
741
878
|
C.d + " · ≈ " + fmtTokens(tokens.in + tokens.out - tok0) + " Tokens (Sitzung ≈ " + fmtTokens(tokens.in + tokens.out) + ")" + C.x + "\n");
|
|
879
|
+
if (!stopped && status !== "error") await maybeAd();
|
|
742
880
|
if (status === "reached") { console.log(C.g + "🎯 Ziel erreicht: " + wasGoal + C.x + "\n"); goal = ""; }
|
|
743
881
|
// Handy-Benachrichtigung bei /goal oder längeren Aufgaben.
|
|
744
882
|
if (cfg.notify && (wasGoal || Date.now() - T0 > 60000)) {
|
|
@@ -764,7 +902,7 @@ async function commit(msg) {
|
|
|
764
902
|
stopped = false;
|
|
765
903
|
try {
|
|
766
904
|
msg = (await call([{ role: "user", content: "Schreibe eine kurze Git-Commit-Nachricht auf Deutsch für diese Änderungen: erste Zeile max. 72 Zeichen, optional Leerzeile + Stichpunkte. Antworte NUR mit der Nachricht, ohne Werkzeuge, ohne Codeblock.\n\n" + st.out + "\n" + diff.slice(0, 20000) }]))
|
|
767
|
-
.replace(
|
|
905
|
+
.replace(TOOL_BLOCK_RE, "").replace(/^```\w*\n?|```$/g, "").trim();
|
|
768
906
|
} catch (e) { clearLine(); return say(C.r + "⚠ " + e.message + C.x); }
|
|
769
907
|
clearLine();
|
|
770
908
|
}
|
|
@@ -906,7 +1044,7 @@ async function command(q, history) {
|
|
|
906
1044
|
let sum = "";
|
|
907
1045
|
try {
|
|
908
1046
|
sum = (await call([{ role: "user", content: "Fasse den bisherigen Verlauf dieses Coding-Chats so zusammen, dass ein Agent nahtlos weiterarbeiten kann: **Ziel**, **Stand**, **Wichtige Dateien & Entscheidungen**, **Offene Punkte**. Maximal 25 Zeilen, ohne Werkzeuge.\n\nVERLAUF:\n" + transcript }]))
|
|
909
|
-
.replace(
|
|
1047
|
+
.replace(TOOL_BLOCK_RE, "").trim();
|
|
910
1048
|
} catch (e) { clearLine(); return console.log(C.r + "⚠ " + e.message + C.x + "\n"); }
|
|
911
1049
|
clearLine();
|
|
912
1050
|
history.length = 0;
|
|
@@ -916,7 +1054,7 @@ async function command(q, history) {
|
|
|
916
1054
|
if (cmd === "/stats") { const b = gitBranch(); return console.log(mdLines(projectStats(ROOT) + (b ? "\n**Git:** Branch `" + b + "`" : "")) + "\n"); }
|
|
917
1055
|
if (cmd === "/export") {
|
|
918
1056
|
const file = path.resolve(ROOT, arg || "mythos-chat-" + new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-") + ".md");
|
|
919
|
-
const msgs = history.map((m) => ({ role: /^Werkzeug-Ergebnis/.test(m.content) ? "tool" : m.role, text: String(m.content).replace(
|
|
1057
|
+
const msgs = history.map((m) => ({ role: /^Werkzeug-Ergebnis/.test(m.content) ? "tool" : m.role, text: String(m.content).replace(TOOL_BLOCK_RE, "").slice(0, 20000) })).filter((m) => m.text.trim());
|
|
920
1058
|
fs.writeFileSync(file, chatToMarkdown("Mythos-Chat – " + path.basename(ROOT), msgs));
|
|
921
1059
|
return console.log(C.g + "✓ Gespeichert: " + file + C.x + "\n");
|
|
922
1060
|
}
|