Befunde aus dem Testbetrieb auf der Linux-VM behoben (#572)

- #578: Abgebrochene Laeufe (Schrittgrenze, Zeitueberschreitung, Abbruch,
  Ausnahme) tragen ihre bezahlten Token - vorher 0 in RunUsage, fuer das
  Budget unsichtbar.
- #580: WebFetch prueft die aufgeloeste Zieladresse beim Verbinden; ein
  erlaubter Name, der auf 127.0.0.1 aufloest, wird gesperrt.
- #575: Dateien entstehen unter Unix mit 0600 (AtomicFile, Protokolle,
  FileRW-Anhaengen, state.db), Protokollverzeichnisse mit 0700.
- #576: SIGTERM/SIGINT beenden auch mit Fenster geordnet.
- #593: OpenRouter-Statusdienst wird gestartet.
- #589: Auftragsnachricht nennt aktuelle Zeit, Zeitzone und Termin.
- #581: Unvollstaendiges Argument-JSON erreicht das Werkzeug nicht mehr;
  das Modell bekommt eine verstaendliche Rueckmeldung statt Parsertext.
- #591: Weboberflaeche liest die Felder, die API und Ereignisstrom liefern
  (Freigaben, Protokoll, Taskboard mit Termin, Werkzeug-Einstellungen,
  Tokennutzung); Ereignisstrom in camelCase.
- #592: Chat zeigt Nachrichten nicht mehr doppelt.
- #536 (teilweise): A2A-Antworten tragen Absender und Empfaenger, Parser
  wirft nicht mehr; Datums- und Volltextfilter in Agenten-Chats.
- deploy/publish.py: Packager-Pfad net10.0, Schalter --agent-file.
- Version 0.1.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-14 15:40:56 +02:00
co-authored by Claude Opus 5
parent e2567a466f
commit 6581fd0187
29 changed files with 1128 additions and 190 deletions
+6 -1
View File
@@ -139,8 +139,13 @@ public static class AtomicFile
return (fullPath, tempPath);
}
/// <summary>
/// Die Nebendatei ersetzt die Zieldatei — und mit ihr deren Rechte. Deshalb entsteht sie
/// unter Unix mit <c>0600</c> (#575); mit der Standardmaske wurde aus einer geschützten
/// <c>InstanceSettings.json</c> beim ersten Speichern eine für die Gruppe lesbare.
/// </summary>
private static FileStream CreateTempStream(string tempPath)
=> new(tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
=> new(tempPath, OwnerOnlyFile.Options(FileMode.CreateNew, FileAccess.Write, FileShare.None));
/// <summary>Versuche für das Ersetzen — siehe <see cref="Commit"/>.</summary>
private const int CommitAttempts = 20;
@@ -0,0 +1,35 @@
namespace ClawdDotNet.Core.Storage;
/// <summary>
/// Legt Dateien so an, dass sie unter Unix von Anfang an nur dem Besitzer gehören (#575).
///
/// <para><b>Warum beim Anlegen und nicht hinterher.</b> .NET erzeugt neue Dateien mit
/// <c>0666 &amp; ~umask</c> — auf Ubuntu mit der Maske <c>0002</c> also <c>0664</c>. Ein
/// nachträgliches <see cref="AppPaths.RestrictToOwner"/> lässt ein Fenster offen und
/// wird an jeder neuen Schreibstelle vergessen: <c>InstanceSettings.json</c> ging auf der
/// Test-VM beim ersten Speichern von <c>0600</c> auf <c>0664</c>.</para>
///
/// <para>Unter Windows wirkungslos; dort regelt die Vererbung aus dem Profil die Rechte.</para>
/// </summary>
public static class OwnerOnlyFile
{
private const UnixFileMode OwnerReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite;
/// <summary>Öffnungsangaben, die eine neu entstehende Datei auf <c>0600</c> setzen.</summary>
public static FileStreamOptions Options(FileMode mode, FileAccess access, FileShare share)
{
var options = new FileStreamOptions { Mode = mode, Access = access, Share = share };
if (!OperatingSystem.IsWindows())
options.UnixCreateMode = OwnerReadWrite;
return options;
}
/// <summary>
/// Öffnet zum Anhängen. Eine vorhandene Datei behält ihre Rechte — nur was hier neu
/// entsteht, gehört dem Besitzer allein.
/// </summary>
public static FileStream OpenAppend(string path)
=> new(path, Options(FileMode.Append, FileAccess.Write, FileShare.Read));
}
@@ -39,6 +39,16 @@ public sealed class SqliteStorage
}.ToString();
Initialize();
// #575: SQLite legt die Datenbank mit der Standardmaske an (auf der Test-VM 0644),
// und WAL- und Shared-Memory-Datei erben deren Rechte. Darin stehen Chatverläufe,
// Gedächtnis und die Argumente jedes Werkzeugaufrufs. Auch für bestehende Dateien,
// die noch aus der Zeit vor dieser Zeile stammen.
foreach (var datei in new[] { databasePath, databasePath + "-wal", databasePath + "-shm" })
{
if (File.Exists(datei))
AppPaths.RestrictToOwner(datei);
}
}
public SqliteConnection OpenConnection()