mythos-code 1.7.1 → 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 +206 -20
- 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");
|
|
@@ -52,10 +155,24 @@ const goalPrompt = (goal) => goal
|
|
|
52
155
|
"\nDer Auftraggeber ist NICHT am Rechner und liest erst viel später mit, was hier im Chat passiert ist – frage ihn nichts, er kann nicht antworten. Behandle jede Entscheidung, jede Rückfrage und jedes Hindernis als etwas, das du selbst lösen musst:" +
|
|
53
156
|
"\n- Gib bei Fehlern NIEMALS auf. Ein fehlgeschlagener Befehl, eine fehlende Abhängigkeit, ein kaputter Build sind Aufgaben, keine Endstationen: lies die Fehlermeldung genau, versuche eine andere Vorgehensweise, installiere Fehlendes selbst (z. B. per npm/pip/winget), nutze bei Bedarf websearch/fetch um die Lösung nachzuschlagen, und probiere es erneut. Erst wenn du wirklich mehrere grundverschiedene Ansätze erfolglos versucht hast, notierst du das Problem knapp und machst mit dem Rest der Aufgabe weiter." +
|
|
54
157
|
"\n- Triff sinnvolle Annahmen statt zu fragen, und schreib kurz dazu, welche Annahme du getroffen hast." +
|
|
55
|
-
"\n-
|
|
158
|
+
"\n- Beginne SOFORT mit einer kurzen Bestätigung und deinem Plan (2–6 Stichpunkte) als normalen Text, BEVOR du das erste Werkzeug nutzt – so sieht der Auftraggeber direkt, dass du das Ziel verstanden hast." +
|
|
159
|
+
"\n- Teile deinen Fortschritt normal im Chat mit (was du tust und warum), aber warte nicht auf eine Antwort. Schreibe vor jedem größeren Schritt einen kurzen Satz dazu." +
|
|
160
|
+
"\n- Baue große Projekte in vielen kleinen Dateien (je höchstens ca. 300 Zeilen) statt in einer riesigen Datei auf einmal." +
|
|
56
161
|
"\n- Das ist eine große, langlaufende Aufgabe: Plane für 1 bis 5 Stunden durchgehende Arbeit in vielen kleinen Schritten, nicht für ein paar Minuten. Höre nicht zu früh auf – arbeite lieber zu gründlich als zu knapp." +
|
|
57
162
|
"\nArbeite komplett selbstständig weiter, bis das Ziel vollständig erreicht und überprüft ist. Erst dann schreibe in deiner letzten Antwort eine eigene Zeile: ZIEL ERREICHT"
|
|
58
163
|
: "";
|
|
164
|
+
/** Kurzer Live-Status aus der gerade gestreamten Antwort, z. B. „✍ schreibt src/main.js · 12 KB“. */
|
|
165
|
+
function streamActivity(full) {
|
|
166
|
+
if (!full) return "denkt nach…";
|
|
167
|
+
const k = full.lastIndexOf("<tool>");
|
|
168
|
+
if (k < 0 || full.indexOf("</tool>", k) >= 0) return "schreibt Antwort…";
|
|
169
|
+
const t = full.slice(k + 6), kb = (t.length / 1024).toFixed(1).replace(".", ",") + " KB";
|
|
170
|
+
const f = (key) => { const m = t.match(new RegExp('"' + key + '"\\s*:\\s*"([^"\\\\]{0,120})')); return m ? m[1] : ""; };
|
|
171
|
+
const name = f("name"), path = f("path");
|
|
172
|
+
if (name === "write" || name === "edit") return "✍ " + (name === "write" ? "schreibt " : "ändert ") + (path || "eine Datei") + " · " + kb;
|
|
173
|
+
if (name === "run") return "⚙ bereitet Befehl vor: " + f("cmd").slice(0, 60);
|
|
174
|
+
return "🔧 bereitet " + (name || "Werkzeug") + " vor…";
|
|
175
|
+
}
|
|
59
176
|
const GOAL_NUDGE = "Das Ziel ist noch nicht als erreicht gemeldet. Der Auftraggeber ist nicht da – gib bei Problemen nicht auf, sondern versuche einen anderen Weg, nutze websearch/fetch für Lösungen und arbeite selbstständig weiter, ohne Rückfragen. Wenn es wirklich vollständig erledigt und geprüft ist, schreibe ZIEL ERREICHT.";
|
|
60
177
|
const SUMMARY_PROMPT = "Fasse jetzt kurz auf Deutsch zusammen, OHNE Werkzeuge:\n**Geändert:** welche Dateien und was\n**Geklappt:** was funktioniert (und wie geprüft)\n**Offen:** was noch fehlt oder beachtet werden muss\nMaximal 12 Zeilen.";
|
|
61
178
|
|
|
@@ -412,7 +529,7 @@ let goal = "";
|
|
|
412
529
|
const SYSTEM = () => "Du bist Mythos Code, ein autonomer Coding-Agent im Terminal des Nutzers (OS: " + process.platform + ", Projektordner: " + ROOT + ").\n" +
|
|
413
530
|
TOOL_PROMPT + memoryPrompt(readMemory(ROOT)) + goalPrompt(goal);
|
|
414
531
|
|
|
415
|
-
const SITE = process.env.MYTHOS_SITE_URL || "https://ai
|
|
532
|
+
const SITE = process.env.MYTHOS_SITE_URL || "https://mythos-core-ai.lovable.app";
|
|
416
533
|
const FN = process.env.MYTHOS_FN_URL || API.replace(/\/v1-messages$/, "");
|
|
417
534
|
const authPost = async (body) => (await fetch(FN + "/cli-auth", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })).json();
|
|
418
535
|
|
|
@@ -452,10 +569,33 @@ async function login() {
|
|
|
452
569
|
const fmt = (ms) => { const s = Math.floor(ms / 1000); return (s >= 60 ? Math.floor(s / 60) + "m " : "") + (s % 60) + "s"; };
|
|
453
570
|
|
|
454
571
|
// Live-Timer: läuft während der ganzen Aufgabe, pausiert für Ausgaben und Rückfragen.
|
|
455
|
-
let T0 = 0, tick = null;
|
|
572
|
+
let T0 = 0, tick = null, activity = "";
|
|
456
573
|
const clearLine = () => process.stdout.write("\r\x1b[K");
|
|
457
|
-
|
|
574
|
+
// Zeigt live, was Mythos gerade tut (z. B. „✍ schreibt src/main.js · 12 KB“) – auf Terminalbreite gekürzt.
|
|
575
|
+
const tickLine = () => {
|
|
576
|
+
const s = "⏳ Mythos arbeitet… " + fmt(Date.now() - T0) + (activity ? " · " + activity : "") + " (Strg+C = stoppen)";
|
|
577
|
+
const w = (process.stdout.columns || 100) - 2;
|
|
578
|
+
return s.length > w ? s.slice(0, w - 1) + "…" : s;
|
|
579
|
+
};
|
|
580
|
+
const startTick = () => { if (tick || !working) return; tick = setInterval(() => process.stdout.write("\r\x1b[K" + C.d + tickLine() + C.x), 250); };
|
|
458
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
|
+
}
|
|
459
599
|
const say = (s) => { const was = !!tick; stopTick(); console.log(s); if (was) startTick(); };
|
|
460
600
|
|
|
461
601
|
// ---------- Stoppen mit Strg+C ----------
|
|
@@ -503,9 +643,9 @@ function streamPrinter() {
|
|
|
503
643
|
return {
|
|
504
644
|
push(full) {
|
|
505
645
|
let vis = full;
|
|
506
|
-
const k = vis.
|
|
646
|
+
const k = vis.search(/<tool(?:_call|_use)?[\s>]/i);
|
|
507
647
|
if (k >= 0) vis = vis.slice(0, k);
|
|
508
|
-
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); }
|
|
509
649
|
if (vis.length <= printed) return;
|
|
510
650
|
buf += vis.slice(printed); printed = vis.length;
|
|
511
651
|
let i; while ((i = buf.indexOf("\n")) !== -1) { line(buf.slice(0, i)); buf = buf.slice(i + 1); }
|
|
@@ -514,6 +654,20 @@ function streamPrinter() {
|
|
|
514
654
|
};
|
|
515
655
|
}
|
|
516
656
|
|
|
657
|
+
// Sichtbare Gedanken (wie bei base44): laufen gedimmt Wort für Wort, bevor die eigentliche Antwort beginnt.
|
|
658
|
+
function thinkPrinter() {
|
|
659
|
+
let printed = 0, started = false, ended = false;
|
|
660
|
+
return {
|
|
661
|
+
push(full) {
|
|
662
|
+
if (ended) return;
|
|
663
|
+
if (!started) { stopTick(); process.stdout.write(C.d + "💭 "); started = true; }
|
|
664
|
+
if (full.length <= printed) return;
|
|
665
|
+
process.stdout.write(full.slice(printed)); printed = full.length;
|
|
666
|
+
},
|
|
667
|
+
end() { if (ended) return; ended = true; if (started) process.stdout.write(C.x + "\n"); startTick(); },
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
517
671
|
// ---------- Modell ----------
|
|
518
672
|
// Verlauf kürzen, damit lange Aufgaben nicht an zu großem Kontext scheitern. level 0 = mild, 2 = stark.
|
|
519
673
|
const shorten = (s, max) => s.length <= max ? s : s.slice(0, Math.floor(max * 0.7)) + "\n…[gekürzt]…\n" + s.slice(s.length - Math.floor(max * 0.3));
|
|
@@ -529,7 +683,7 @@ function compact(history, level) {
|
|
|
529
683
|
}
|
|
530
684
|
|
|
531
685
|
let tokens = { in: 0, out: 0 };
|
|
532
|
-
async function once(messages, onText) {
|
|
686
|
+
async function once(messages, onText, onThink) {
|
|
533
687
|
const c = ctl = new AbortController(); const to = setTimeout(() => c.abort(), 170000);
|
|
534
688
|
const system = SYSTEM();
|
|
535
689
|
tokens.in += estimateTokens(system, messages);
|
|
@@ -540,7 +694,7 @@ async function once(messages, onText) {
|
|
|
540
694
|
body: JSON.stringify({ model: cfg.model || "mythos-code", max_tokens: 8000, system, messages, stream: true }),
|
|
541
695
|
});
|
|
542
696
|
if (!r.ok) { const j = await r.json().catch(() => ({})); const e = new Error(j?.error?.message || ("HTTP " + r.status)); e.status = r.status; throw e; }
|
|
543
|
-
const reader = r.body.getReader(); const dec = new TextDecoder(); let buf = "", out = "";
|
|
697
|
+
const reader = r.body.getReader(); const dec = new TextDecoder(); let buf = "", out = "", think = "";
|
|
544
698
|
while (true) {
|
|
545
699
|
const { done, value } = await reader.read(); if (done) break;
|
|
546
700
|
buf += dec.decode(value, { stream: true });
|
|
@@ -549,6 +703,8 @@ async function once(messages, onText) {
|
|
|
549
703
|
if (!line.startsWith("data:")) continue;
|
|
550
704
|
let p; try { p = JSON.parse(line.slice(5)); } catch { continue; }
|
|
551
705
|
if (p.type === "error") { const e = new Error(p.error?.message || "Überlastet"); e.status = 529; throw e; }
|
|
706
|
+
// Sichtbare Gedanken: kommen als eigener Block VOR der Antwort.
|
|
707
|
+
if (p.delta?.type === "thinking_delta" && p.delta.thinking) { think += p.delta.thinking; if (onThink) onThink(think); }
|
|
552
708
|
if (p.delta?.text) { out += p.delta.text; if (onText) onText(out); }
|
|
553
709
|
}
|
|
554
710
|
}
|
|
@@ -558,14 +714,16 @@ async function once(messages, onText) {
|
|
|
558
714
|
} finally { clearTimeout(to); }
|
|
559
715
|
}
|
|
560
716
|
|
|
561
|
-
async function call(history, onText) {
|
|
717
|
+
async function call(history, onText, onThink) {
|
|
562
718
|
for (let a = 0; a < 6; a++) {
|
|
563
719
|
if (stopped) throw new Error("Gestoppt");
|
|
564
|
-
try { return await once(compact(history, Math.min(2, Math.floor(a / 2))), onText); }
|
|
720
|
+
try { return await once(compact(history, Math.min(2, Math.floor(a / 2))), onText, onThink); }
|
|
565
721
|
catch (e) {
|
|
566
722
|
if (stopped) throw e;
|
|
567
723
|
if (e.status === 401) throw new Error("Anmeldung ungültig – bitte neu anmelden: mythos login");
|
|
568
724
|
if (/Daily limit/i.test(e.message)) throw new Error("Tageslimit erreicht – mit Pro unbegrenzt.");
|
|
725
|
+
// Konfigurationsproblem (Guthaben leer, kein Ausweich-Schlüssel) statt normaler Überlastung -> nicht sinnlos wiederholen.
|
|
726
|
+
if (/nicht konfiguriert/i.test(e.message)) throw e;
|
|
569
727
|
await new Promise((r) => setTimeout(r, Math.min(8000, 1500 * (a + 1))));
|
|
570
728
|
}
|
|
571
729
|
}
|
|
@@ -599,6 +757,19 @@ function runLive(cmd) {
|
|
|
599
757
|
});
|
|
600
758
|
}
|
|
601
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
|
+
|
|
602
773
|
async function runTool(t) {
|
|
603
774
|
if (t.name === "todo") {
|
|
604
775
|
const items = normalizeTodos(t);
|
|
@@ -612,6 +783,7 @@ async function runTool(t) {
|
|
|
612
783
|
: t.name === "write" ? "write " + t.path + " (" + String(t.content || "").length + " Zeichen)"
|
|
613
784
|
: t.name === "websearch" ? "websearch " + t.query
|
|
614
785
|
: t.name === "fetch" ? "fetch " + t.url
|
|
786
|
+
: t.name === "cloud" ? "cloud verbinden: " + (t.app || "App")
|
|
615
787
|
: t.name + " " + (t.path || "");
|
|
616
788
|
say(C.y + "⚙ " + label + C.x);
|
|
617
789
|
// Sicherheitsnetz: gefährliche Befehle fragen immer nach (auch mit --yes) und werden bei /goal blockiert.
|
|
@@ -624,13 +796,14 @@ async function runTool(t) {
|
|
|
624
796
|
startTick();
|
|
625
797
|
if (a !== "j" && a !== "y") return "Vom Nutzer abgelehnt (Sicherheitsnetz). Finde einen sichereren Weg.";
|
|
626
798
|
}
|
|
627
|
-
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) {
|
|
628
800
|
stopTick();
|
|
629
801
|
const a = (await ask(C.d + " Erlauben? [j/N/a=immer] " + C.x)).trim().toLowerCase();
|
|
630
802
|
startTick();
|
|
631
803
|
if (a === "a") autoYes = true; else if (a !== "j" && a !== "y") return "Vom Nutzer abgelehnt.";
|
|
632
804
|
}
|
|
633
805
|
if (t.name === "run") return runLive(t.cmd);
|
|
806
|
+
if (t.name === "cloud") return cloudConnect(t);
|
|
634
807
|
if (isWebTool(t)) {
|
|
635
808
|
const r = await webTool(t);
|
|
636
809
|
say(C.d + " " + r.result.split("\n").filter(Boolean).slice(0, 3).join(" · ").slice(0, 160) + C.x);
|
|
@@ -647,19 +820,26 @@ async function turnInner(history) {
|
|
|
647
820
|
let nudges = 0;
|
|
648
821
|
for (let i = 0; i < max && !stopped; i++) {
|
|
649
822
|
const pr = streamPrinter();
|
|
650
|
-
|
|
651
|
-
|
|
823
|
+
let think = null;
|
|
824
|
+
activity = "denkt nach…";
|
|
825
|
+
const out = await call(history,
|
|
826
|
+
(full) => { if (think) think.end(); activity = streamActivity(full); pr.push(full); },
|
|
827
|
+
(t) => { if (!think) think = thinkPrinter(); think.push(t); });
|
|
828
|
+
if (think) think.end();
|
|
829
|
+
pr.end(); activity = "";
|
|
652
830
|
if (stopped) break;
|
|
653
831
|
history.push({ role: "assistant", content: out });
|
|
654
|
-
const
|
|
655
|
-
if (!
|
|
832
|
+
const pb = parseToolBlock(out);
|
|
833
|
+
if (!pb.tool && !pb.error) {
|
|
656
834
|
if (!goal || /ZIEL ERREICHT/.test(out)) return goal ? "reached" : "done";
|
|
657
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"; }
|
|
658
836
|
history.push({ role: "user", content: GOAL_NUDGE });
|
|
659
837
|
continue;
|
|
660
838
|
}
|
|
661
839
|
nudges = 0;
|
|
662
|
-
|
|
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; }
|
|
663
843
|
const res = await runTool(t);
|
|
664
844
|
history.push({ role: "user", content: "Werkzeug-Ergebnis:\n" + res });
|
|
665
845
|
}
|
|
@@ -671,7 +851,11 @@ async function summarize(history) {
|
|
|
671
851
|
history.push({ role: "user", content: SUMMARY_PROMPT });
|
|
672
852
|
say(C.m + C.b + "📋 Zusammenfassung" + C.x);
|
|
673
853
|
const pr = streamPrinter();
|
|
674
|
-
|
|
854
|
+
let think = null;
|
|
855
|
+
const out = await call(history,
|
|
856
|
+
(full) => { if (think) think.end(); pr.push(full); },
|
|
857
|
+
(t) => { if (!think) think = thinkPrinter(); think.push(t); });
|
|
858
|
+
if (think) think.end();
|
|
675
859
|
pr.end();
|
|
676
860
|
history.push({ role: "assistant", content: out });
|
|
677
861
|
return out;
|
|
@@ -692,6 +876,7 @@ async function turn(history, input, images) {
|
|
|
692
876
|
const took = fmt(Date.now() - T0);
|
|
693
877
|
console.log((stopped ? C.r + "■ Gestoppt nach " + took : C.g + "✓ Mythos hat " + took + " gearbeitet") + C.x +
|
|
694
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();
|
|
695
880
|
if (status === "reached") { console.log(C.g + "🎯 Ziel erreicht: " + wasGoal + C.x + "\n"); goal = ""; }
|
|
696
881
|
// Handy-Benachrichtigung bei /goal oder längeren Aufgaben.
|
|
697
882
|
if (cfg.notify && (wasGoal || Date.now() - T0 > 60000)) {
|
|
@@ -717,7 +902,7 @@ async function commit(msg) {
|
|
|
717
902
|
stopped = false;
|
|
718
903
|
try {
|
|
719
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) }]))
|
|
720
|
-
.replace(
|
|
905
|
+
.replace(TOOL_BLOCK_RE, "").replace(/^```\w*\n?|```$/g, "").trim();
|
|
721
906
|
} catch (e) { clearLine(); return say(C.r + "⚠ " + e.message + C.x); }
|
|
722
907
|
clearLine();
|
|
723
908
|
}
|
|
@@ -812,6 +997,7 @@ async function command(q, history) {
|
|
|
812
997
|
if (/^(stop|aus|ende|beenden)$/i.test(arg)) { goal = ""; return console.log("🎯 Ziel beendet.\n"); }
|
|
813
998
|
if (!autoYes) { autoYes = true; console.log(C.d + "Vollzugriff für /goal eingeschaltet – Mythos fragt nicht mehr nach." + C.x); }
|
|
814
999
|
goal = arg;
|
|
1000
|
+
console.log(C.m + C.b + "🎯 Ziel angenommen: " + C.x + arg + C.d + "\n Mythos plant jetzt und arbeitet selbstständig – der Fortschritt erscheint hier. Stoppen: Strg+C" + C.x);
|
|
815
1001
|
return turn(history, "Neues Ziel: " + arg + "\nArbeite jetzt komplett selbstständig daran, bis es erreicht und geprüft ist.");
|
|
816
1002
|
}
|
|
817
1003
|
if (cmd === "/weiter") {
|
|
@@ -858,7 +1044,7 @@ async function command(q, history) {
|
|
|
858
1044
|
let sum = "";
|
|
859
1045
|
try {
|
|
860
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 }]))
|
|
861
|
-
.replace(
|
|
1047
|
+
.replace(TOOL_BLOCK_RE, "").trim();
|
|
862
1048
|
} catch (e) { clearLine(); return console.log(C.r + "⚠ " + e.message + C.x + "\n"); }
|
|
863
1049
|
clearLine();
|
|
864
1050
|
history.length = 0;
|
|
@@ -868,7 +1054,7 @@ async function command(q, history) {
|
|
|
868
1054
|
if (cmd === "/stats") { const b = gitBranch(); return console.log(mdLines(projectStats(ROOT) + (b ? "\n**Git:** Branch `" + b + "`" : "")) + "\n"); }
|
|
869
1055
|
if (cmd === "/export") {
|
|
870
1056
|
const file = path.resolve(ROOT, arg || "mythos-chat-" + new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-") + ".md");
|
|
871
|
-
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());
|
|
872
1058
|
fs.writeFileSync(file, chatToMarkdown("Mythos-Chat – " + path.basename(ROOT), msgs));
|
|
873
1059
|
return console.log(C.g + "✓ Gespeichert: " + file + C.x + "\n");
|
|
874
1060
|
}
|