Werkzeugkasten: Anhaenge lesen - Word, Excel, Text (#692)

- Core/Documents/DocumentText: text of .docx (paragraphs, tables as rows),
  .xlsx (sheet by sheet, every cell at its column, stored values not
  formulas) and plain text/CSV. No DTD, no external references, every part
  bounded against zip bombs. PDF is named as not readable yet.
- Mail: read_message lists attachments; read_attachment returns the text;
  save_attachment keeps the file in the agent's workspace (never over an
  existing file, never outside).
- FileRW read/read_many return Word and Excel files as text; writing them
  stays refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-10-01 13:06:23 +02:00
co-authored by Claude Opus 5.5
parent edf3ff19ea
commit 72d855efcf
7 changed files with 723 additions and 7 deletions
+22 -3
View File
@@ -592,7 +592,12 @@ public sealed class FileRWTool : IAgentTool, IToolStateProbe
? je.EnumerateArray().Select(x => x.GetString()?.ToLowerInvariant()).ToList()
: new List<string?> { ".txt", ".json", ".md", ".html", ".js", ".css" }; // Default
if (!allowedExtensions.Contains(extension))
// #692: A Word file or a spreadsheet is read as its extracted text, never raw and never
// written by this tool — so reading it needs no place on the list.
var reading = input.ValueKind == JsonValueKind.Object && input.TryGetProperty("action", out var act)
&& act.ValueKind == JsonValueKind.String && act.GetString() is "read" or "read_many";
if (!allowedExtensions.Contains(extension)
&& !(reading && ClawdDotNet.Core.Documents.DocumentText.IsOfficeDocument(fullPath)))
{
throw new UnauthorizedAccessException($"File extension '{extension}' is not allowed in workspace '{area.Name}'.");
}
@@ -668,7 +673,7 @@ public sealed class FileRWTool : IAgentTool, IToolStateProbe
await fileLock.WaitAsync(ct);
try
{
var content = await File.ReadAllTextAsync(path, ct);
var content = await ReadContentAsync(path, ct);
NoteRead(path, area, context);
var relativePath = Path.GetRelativePath(rootPath, path);
return ToolResult.Ok($"[{area.Name}:/{relativePath}]\n{content}");
@@ -679,6 +684,20 @@ public sealed class FileRWTool : IAgentTool, IToolStateProbe
}
}
/// <summary>
/// #692: A Word file or a spreadsheet is read as its text — a price list saved from a mail is
/// a table, not a heap of zip bytes. Everything else as before.
/// </summary>
private static async Task<string> ReadContentAsync(string path, CancellationToken ct)
{
if (!ClawdDotNet.Core.Documents.DocumentText.IsOfficeDocument(path))
return await File.ReadAllTextAsync(path, ct);
var extracted = ClawdDotNet.Core.Documents.DocumentText.Extract(await File.ReadAllBytesAsync(path, ct), path, MaxZeichenJeAufruf);
return extracted.Ok
? $"(text of a {extracted.Kind}{(extracted.Truncated ? $", first {MaxZeichenJeAufruf} characters" : "")})\n{extracted.Text}"
: $"({extracted.Problem})";
}
/// <summary>Hoechstzahl Dateien je <c>read_many</c>.</summary>
public const int MaxDateienJeAufruf = 50;
@@ -730,7 +749,7 @@ public sealed class FileRWTool : IAgentTool, IToolStateProbe
string inhalt;
try
{
inhalt = await File.ReadAllTextAsync(pfad, ct);
inhalt = await ReadContentAsync(pfad, ct);
NoteRead(pfad, area, context);
}
finally { sperre.Release(); }