feat: Workspace-Umbau Schritt 5 — Live-Code-Editor + code_file-Strom

Durchgaengiger Live-Editor fuer Code-Projekte:
- App: selbstenthaltener Highlight-Editor in einer WebView (editorHtml.ts,
  Textarea + Regex-Highlight-Layer, voll offline). CodeEditorTile mit Datei-Tabs,
  verdrahtet mit dem codeFile-Spiegel; Bridge-Protokoll setContent/applyPatch/
  setReadOnly ↔ onEditFromUser.
- Proxy-Hook (routes.js): faengt ARIAs Write/Edit/MultiEdit unter
  /shared/projects/<pid>/ ab und postet den Volltext bei Erfolg an
  /internal/code-file. tool_use_id→file_path-Korrelation, liest die Datei aus
  dem gemounteten /shared.
- Bridge: /internal/code-file relayt als RVS code_file an die App; eingehende
  code_file_edit schreiben den Volltext pfad-sicher nach /shared/projects/<pid>/
  (_write_project_file, kein Ausbruch via ..).

Arbeitsverzeichnis fuer Code-Projekte = /shared/projects/<projectId>/ (in proxy/
bridge/brain gemountet, kein SSH noetig). tsc/py/js clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 23:53:48 +02:00
co-authored by Claude Opus 4.8
parent 85363b1014
commit a6cb152f55
4 changed files with 388 additions and 12 deletions
+49
View File
@@ -28,6 +28,43 @@ const TOOL_HOOK_URL = process.env.ARIA_TOOL_HOOK_URL
|| "http://aria-bridge:8090/internal/agent-activity";
const STREAM_HOOK_URL = process.env.ARIA_STREAM_HOOK_URL
|| "http://aria-bridge:8090/internal/agent-stream";
const CODE_FILE_HOOK_URL = process.env.ARIA_CODE_FILE_HOOK_URL
|| "http://aria-bridge:8090/internal/code-file";
// Code-Projekte leben unter /shared/projects/<projectId>/ (Volume in proxy +
// bridge + brain gemountet). Schreibt/aendert ARIA hier eine Datei, spiegeln
// wir den Volltext live in den Code-Editor der App. Nur Dateien unter diesem
// Praefix — ARIAs sonstige Datei-Ops (Skills, Configs) bleiben unberuehrt.
const PROJECTS_ROOT = "/shared/projects/";
const CODE_FILE_MAX_BYTES = 512 * 1024;
/** Zerlegt einen absoluten Pfad unter /shared/projects/<pid>/<rel> → {pid, rel}
* oder null wenn er nicht darunter liegt. */
function _parseProjectPath(filePath) {
if (typeof filePath !== "string" || !filePath.startsWith(PROJECTS_ROOT)) return null;
const rest = filePath.slice(PROJECTS_ROOT.length);
const slash = rest.indexOf("/");
if (slash <= 0) return null;
return { pid: rest.slice(0, slash), rel: rest.slice(slash + 1) };
}
/** Liest die (frisch geschriebene) Datei und pusht sie als code_file an die
* Bridge. Fire-and-forget, fail-open. */
function _emitCodeFile(filePath) {
try {
const parsed = _parseProjectPath(filePath);
if (!parsed || !parsed.rel) return;
const st = fs.statSync(filePath);
if (!st.isFile() || st.size > CODE_FILE_MAX_BYTES) return;
const content = fs.readFileSync(filePath, "utf8");
_postJson(CODE_FILE_HOOK_URL, {
projectId: parsed.pid,
path: parsed.rel,
content,
version: Date.now(),
});
} catch (_) { /* fail-open */ }
}
// Tool-Output kann sehr lang werden (git log -p, find /). Wir truncaten
// hart auf 4 KB pro Event — der User sieht weiterhin den Anfang und einen
@@ -153,6 +190,9 @@ function _attachIdleWatchdog(subprocess, requestId) {
* - Neu-API: voller Stream (text/tool_use/tool_result) an /internal/agent-stream
*/
function _attachToolHook(subprocess, requestId, projectId) {
// tool_use_id → file_path fuer Write/Edit, damit wir beim (erfolgreichen)
// tool_result die frisch geschriebene Datei aus /shared lesen koennen.
const _pendingFileWrites = new Map();
subprocess.on("assistant", (message) => {
try {
const blocks = message?.message?.content || [];
@@ -160,6 +200,10 @@ function _attachToolHook(subprocess, requestId, projectId) {
if (!b) continue;
if (b.type === "tool_use") {
if (b.name) _emitToolEvent(b.name, projectId);
if ((b.name === "Write" || b.name === "Edit" || b.name === "MultiEdit")
&& b.id && b.input && typeof b.input.file_path === "string") {
_pendingFileWrites.set(b.id, b.input.file_path);
}
const inputStr = b.input ? JSON.stringify(b.input) : "";
const inp = _truncate(inputStr, TOOL_INPUT_MAX_CHARS);
_emitStreamEvent(requestId, "tool_use", {
@@ -203,6 +247,11 @@ function _attachToolHook(subprocess, requestId, projectId) {
truncatedBytes: out.truncatedBytes,
isError: b.is_error === true,
});
// Write/Edit erfolgreich → Datei live in den Code-Editor spiegeln.
if (b.tool_use_id && b.is_error !== true && _pendingFileWrites.has(b.tool_use_id)) {
_emitCodeFile(_pendingFileWrites.get(b.tool_use_id));
_pendingFileWrites.delete(b.tool_use_id);
}
}
}
} catch (_) { /* fail-open */ }