FTP und RocketChat kennen die benannten Arbeitsbereiche (#718)
Gemeinsamer Helfer NamedWorkspaces in Core statt dreier Kopien: unit: und Bereichs-Id als Praefix, Lesen braucht Read, Herunterladen braucht Write, Unbekanntes und Verbotenes antworten gleich. Ein Dateiname aus einer RocketChat-Nachricht waehlt keinen Bereich. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1bf26e9ff3
commit
88c42b244d
@@ -13,4 +13,8 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\ClawdDotNet.Core\\ClawdDotNet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ClawdDotNet.Tools.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.Config;
|
||||
using ClawdDotNet.Core.Organisation;
|
||||
using ClawdDotNet.Core.Storage;
|
||||
using ClawdDotNet.Core.Tools;
|
||||
using FluentFTP;
|
||||
@@ -20,7 +22,9 @@ public sealed class FTPTool : IAgentTool
|
||||
For localPath, the same workspace prefixes as in the FileRW tool are supported:
|
||||
- "personal:website/index.html" → file in the agent's personal workspace
|
||||
- "shared:website/index.html" → file in the shared SharedWorkspace
|
||||
- "unit:report.pdf" → your unit's workspace; "<workspace id>:file" → a workspace you are a member of
|
||||
- Without prefix, the personal workspace is used.
|
||||
Uploading from a workspace needs read access to it, downloading into one needs write access.
|
||||
|
||||
Example upload: { "action": "upload", "localPath": "shared:website/index.html", "remotePath": "/index.html" }
|
||||
""";
|
||||
@@ -36,7 +40,7 @@ public sealed class FTPTool : IAgentTool
|
||||
},
|
||||
"localPath": {
|
||||
"type": "string",
|
||||
"description": "Path to local file with optional workspace prefix: 'personal:file.txt' (default) or 'shared:file.txt' for the SharedWorkspace. Without prefix, personal workspace is used."
|
||||
"description": "Path to local file with optional workspace prefix: 'personal:file.txt' (default), 'shared:file.txt' for the SharedWorkspace, 'unit:file.txt' for your unit's workspace or '<workspace id>:file.txt'. Without prefix, personal workspace is used."
|
||||
},
|
||||
"remotePath": {
|
||||
"type": "string",
|
||||
@@ -114,33 +118,37 @@ public sealed class FTPTool : IAgentTool
|
||||
/// "personal:datei.txt" → persönlicher Workspace + "datei.txt"
|
||||
/// "datei.txt" → persönlicher Workspace + "datei.txt" (Standard)
|
||||
/// </summary>
|
||||
private (string fullPath, string workspaceLabel) ResolveLocalPath(string localPath, AgentToolContext context)
|
||||
/// <param name="write">
|
||||
/// #718: The file is written into the workspace (download). A workspace with a member list
|
||||
/// then needs Write; reading from it (upload) needs Read.
|
||||
/// </param>
|
||||
internal static (string fullPath, string workspaceLabel) ResolveLocalPath(string localPath, AgentToolContext context, bool write)
|
||||
{
|
||||
string basePath;
|
||||
string relativePath;
|
||||
string label;
|
||||
var (label, relativePath) = NamedWorkspaces.Split(localPath);
|
||||
|
||||
if (localPath.StartsWith("shared:", StringComparison.OrdinalIgnoreCase))
|
||||
if (label == WorkspaceLayout.Shared)
|
||||
{
|
||||
relativePath = localPath[7..]; // after "shared:"
|
||||
basePath = context.SharedWorkspacePath
|
||||
?? throw new ArgumentException("No SharedWorkspace configured.");
|
||||
label = "shared";
|
||||
}
|
||||
else if (localPath.StartsWith("personal:", StringComparison.OrdinalIgnoreCase))
|
||||
else if (label == WorkspaceLayout.Personal)
|
||||
{
|
||||
relativePath = localPath[9..]; // after "personal:"
|
||||
basePath = context.WorkspacePath
|
||||
?? throw new ArgumentException("No personal workspace configured.");
|
||||
label = "personal";
|
||||
}
|
||||
else
|
||||
{
|
||||
// No prefix → personal workspace as default
|
||||
relativePath = localPath;
|
||||
basePath = context.WorkspacePath
|
||||
?? throw new ArgumentException("No personal workspace configured.");
|
||||
label = "personal";
|
||||
// #718: "unit:" or a workspace id. Unknown and not allowed answer the same.
|
||||
var named = NamedWorkspaces.Resolve(label, context.AgentId, context.Organisation, context.SharedWorkspacePath);
|
||||
if (named is null || named.Access < (write ? WorkspaceAccess.Write : WorkspaceAccess.Read))
|
||||
throw new ArgumentException(named is null
|
||||
? NamedWorkspaces.Unknown(label, context.AgentId, context.Organisation, withShared: true)
|
||||
: $"Access denied: this agent may not {(write ? "write to" : "read from")} workspace '{named.Workspace.Id}'.");
|
||||
basePath = named.Root;
|
||||
label = named.Workspace.Id;
|
||||
if (write)
|
||||
Directory.CreateDirectory(basePath);
|
||||
}
|
||||
|
||||
// Config-rootPath als Override (nur wenn kein Prefix verwendet)
|
||||
@@ -182,7 +190,7 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
try
|
||||
{
|
||||
var (localFull, wsLabel) = ResolveLocalPath(localRel, context);
|
||||
var (localFull, wsLabel) = ResolveLocalPath(localRel, context, write: false);
|
||||
|
||||
if (!File.Exists(localFull))
|
||||
return ToolResult.Fail($"Local file not found: {localRel} (resolved: {localFull})");
|
||||
@@ -307,7 +315,7 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
try
|
||||
{
|
||||
var (localFull, wsLabel) = ResolveLocalPath(localRel, context);
|
||||
var (localFull, wsLabel) = ResolveLocalPath(localRel, context, write: true);
|
||||
|
||||
var localDir = Path.GetDirectoryName(localFull);
|
||||
if (localDir != null && !Directory.Exists(localDir)) Directory.CreateDirectory(localDir);
|
||||
|
||||
Reference in New Issue
Block a user