patchwarden 0.4.0 → 0.6.0
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/PatchWarden.cmd +51 -0
- package/README.en.md +1022 -0
- package/README.md +897 -358
- package/dist/assessments/agentAssessor.d.ts +15 -0
- package/dist/assessments/agentAssessor.js +293 -0
- package/dist/assessments/assessmentStore.d.ts +133 -0
- package/dist/assessments/assessmentStore.js +238 -0
- package/dist/assessments/confirmCli.d.ts +9 -0
- package/dist/assessments/confirmCli.js +26 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.js +156 -3
- package/dist/direct/directAudit.d.ts +23 -0
- package/dist/direct/directAudit.js +309 -0
- package/dist/direct/directGuards.d.ts +20 -0
- package/dist/direct/directGuards.js +137 -0
- package/dist/direct/directPatch.d.ts +17 -0
- package/dist/direct/directPatch.js +113 -0
- package/dist/direct/directSessionStore.d.ts +63 -0
- package/dist/direct/directSessionStore.js +192 -0
- package/dist/direct/directVerification.d.ts +12 -0
- package/dist/direct/directVerification.js +96 -0
- package/dist/doctor.js +73 -5
- package/dist/runner/agentInvocation.d.ts +23 -0
- package/dist/runner/agentInvocation.js +106 -0
- package/dist/runner/changeCapture.d.ts +29 -0
- package/dist/runner/changeCapture.js +107 -5
- package/dist/runner/runTask.js +70 -44
- package/dist/runner/simpleProcess.d.ts +21 -0
- package/dist/runner/simpleProcess.js +206 -0
- package/dist/runner/watch.js +17 -2
- package/dist/security/commandGuard.d.ts +2 -1
- package/dist/security/commandGuard.js +25 -3
- package/dist/security/riskEngine.d.ts +27 -0
- package/dist/security/riskEngine.js +118 -0
- package/dist/security/runtimeGuard.d.ts +6 -0
- package/dist/security/runtimeGuard.js +28 -0
- package/dist/smoke-test.js +1353 -7
- package/dist/tools/applyPatch.d.ts +16 -0
- package/dist/tools/applyPatch.js +41 -0
- package/dist/tools/auditSession.d.ts +5 -0
- package/dist/tools/auditSession.js +12 -0
- package/dist/tools/auditTask.d.ts +7 -0
- package/dist/tools/auditTask.js +94 -2
- package/dist/tools/createDirectSession.d.ts +14 -0
- package/dist/tools/createDirectSession.js +54 -0
- package/dist/tools/createTask.d.ts +48 -1
- package/dist/tools/createTask.js +298 -47
- package/dist/tools/finalizeDirectSession.d.ts +19 -0
- package/dist/tools/finalizeDirectSession.js +84 -0
- package/dist/tools/getTaskSummary.d.ts +47 -0
- package/dist/tools/getTaskSummary.js +64 -1
- package/dist/tools/healthCheck.d.ts +22 -12
- package/dist/tools/healthCheck.js +23 -6
- package/dist/tools/readWorkspaceFile.d.ts +7 -1
- package/dist/tools/readWorkspaceFile.js +48 -2
- package/dist/tools/registry.js +217 -19
- package/dist/tools/runVerification.d.ts +16 -0
- package/dist/tools/runVerification.js +32 -0
- package/dist/tools/savePlan.d.ts +1 -0
- package/dist/tools/savePlan.js +38 -7
- package/dist/tools/searchWorkspace.d.ts +19 -0
- package/dist/tools/searchWorkspace.js +205 -0
- package/dist/tools/taskTemplates.js +2 -1
- package/dist/tools/toolCatalog.d.ts +3 -2
- package/dist/tools/toolCatalog.js +32 -5
- package/dist/tools/waitForTask.d.ts +2 -2
- package/dist/tools/waitForTask.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/docs/chatgpt-usage.md +101 -0
- package/docs/release-checklist.md +14 -0
- package/docs/release-v0.6.0.md +71 -0
- package/examples/config.example.json +5 -0
- package/examples/openai-tunnel/README.md +11 -4
- package/examples/openai-tunnel/chatgpt-test-prompt.md +18 -14
- package/examples/openai-tunnel/tunnel-client.example.yaml +8 -0
- package/package.json +12 -8
- package/scripts/brand-check.js +58 -12
- package/scripts/control-smoke.js +206 -0
- package/scripts/launchers/Check-PatchWarden-Health.cmd +6 -0
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
- package/scripts/launchers/Restart-PatchWarden.cmd +6 -0
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +7 -0
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +7 -0
- package/scripts/lifecycle-smoke.js +66 -6
- package/scripts/manage-patchwarden.ps1 +639 -0
- package/scripts/mcp-manifest-check.js +100 -58
- package/scripts/mcp-smoke.js +157 -2
- package/scripts/pack-clean.js +3 -4
- package/scripts/package-manifest-check.js +78 -0
- package/scripts/patchwarden-mcp-direct.cmd +7 -0
- package/scripts/patchwarden-mcp-stdio.cmd +1 -1
- package/scripts/restart-patchwarden.ps1 +5 -4
- package/scripts/start-patchwarden-tunnel.ps1 +261 -30
- package/scripts/tunnel-supervisor-smoke.js +36 -4
- package/scripts/watcher-supervisor-smoke.js +10 -6
- package/src/assessments/agentAssessor.ts +324 -0
- package/src/assessments/assessmentStore.ts +426 -0
- package/src/assessments/confirmCli.ts +29 -0
- package/src/config.ts +178 -4
- package/src/direct/directAudit.ts +400 -0
- package/src/direct/directGuards.ts +279 -0
- package/src/direct/directPatch.ts +258 -0
- package/src/direct/directSessionStore.ts +345 -0
- package/src/direct/directVerification.ts +138 -0
- package/src/doctor.ts +103 -7
- package/src/runner/agentInvocation.ts +125 -0
- package/src/runner/changeCapture.ts +140 -5
- package/src/runner/runTask.ts +61 -43
- package/src/runner/simpleProcess.ts +223 -0
- package/src/runner/watch.ts +18 -2
- package/src/security/commandGuard.ts +46 -4
- package/src/security/riskEngine.ts +160 -0
- package/src/security/runtimeGuard.ts +41 -0
- package/src/smoke-test.ts +1291 -4
- package/src/tools/applyPatch.ts +86 -0
- package/src/tools/auditSession.ts +28 -0
- package/src/tools/auditTask.ts +100 -2
- package/src/tools/createDirectSession.ts +113 -0
- package/src/tools/createTask.ts +405 -55
- package/src/tools/finalizeDirectSession.ts +144 -0
- package/src/tools/getTaskSummary.ts +111 -1
- package/src/tools/healthCheck.ts +23 -6
- package/src/tools/readWorkspaceFile.ts +85 -2
- package/src/tools/registry.ts +242 -19
- package/src/tools/runVerification.ts +58 -0
- package/src/tools/savePlan.ts +57 -7
- package/src/tools/searchWorkspace.ts +275 -0
- package/src/tools/taskTemplates.ts +2 -1
- package/src/tools/toolCatalog.ts +35 -6
- package/src/tools/waitForTask.ts +3 -3
- package/src/version.ts +2 -2
- package/tsconfig.json +18 -17
- package/Check-PatchWarden-Health.cmd +0 -6
- package/Reset-PatchWarden-Tunnel-Key.cmd +0 -6
- package/Restart-PatchWarden.cmd +0 -19
- package/Start-PatchWarden-Tunnel.cmd +0 -7
- package/docs/release-v0.3.0.md +0 -43
- package/docs/release-v0.4.0.md +0 -74
|
@@ -12,7 +12,7 @@ const TERMINAL_STATUSES = new Set([
|
|
|
12
12
|
"failed_policy_violation",
|
|
13
13
|
"canceled",
|
|
14
14
|
]);
|
|
15
|
-
export function getTaskSummary(taskId) {
|
|
15
|
+
export function getTaskSummary(taskId, options = {}) {
|
|
16
16
|
const config = getConfig();
|
|
17
17
|
const taskDir = resolve(getTasksDir(config), taskId);
|
|
18
18
|
const statusFile = join(taskDir, "status.json");
|
|
@@ -92,6 +92,10 @@ export function getTaskSummary(taskId) {
|
|
|
92
92
|
? Math.max(0, (Number.isFinite(finishedAt) ? finishedAt : Date.now()) - startedAt)
|
|
93
93
|
: 0;
|
|
94
94
|
const changedFiles = asArray(result.changed_files ?? status.changed_files);
|
|
95
|
+
const changedFilesRead = tryReadJson(join(taskDir, "changed-files.json"));
|
|
96
|
+
const artifactHygiene = asRecord(result.artifact_hygiene ?? changedFilesRead.data.artifact_hygiene ?? {
|
|
97
|
+
counts: status.artifact_hygiene_counts || {},
|
|
98
|
+
});
|
|
95
99
|
const verifyCommands = asArray(verify.commands ?? result.verify_commands ?? result.verify?.commands);
|
|
96
100
|
const testLogSummary = summarizeTestLog(join(taskDir, "test.log"));
|
|
97
101
|
const verificationSummary = buildVerificationSummary(verifyStatus, verifyCommands, testLogSummary);
|
|
@@ -156,7 +160,11 @@ export function getTaskSummary(taskId) {
|
|
|
156
160
|
watcher: status.watcher,
|
|
157
161
|
pending_reason: status.pending_reason || null,
|
|
158
162
|
execution_blocked: Boolean(status.execution_blocked),
|
|
163
|
+
artifact_hygiene: artifactHygiene,
|
|
159
164
|
};
|
|
165
|
+
if ((options.view || "standard") === "compact") {
|
|
166
|
+
return buildCompactSummary(output, normalizeMaxItems(options.max_items));
|
|
167
|
+
}
|
|
160
168
|
const safe = redactSensitiveValue(output);
|
|
161
169
|
return {
|
|
162
170
|
...safe.value,
|
|
@@ -164,6 +172,58 @@ export function getTaskSummary(taskId) {
|
|
|
164
172
|
redaction_categories: safe.redaction_categories,
|
|
165
173
|
};
|
|
166
174
|
}
|
|
175
|
+
function buildCompactSummary(output, maxItems) {
|
|
176
|
+
const hygiene = asRecord(output.artifact_hygiene);
|
|
177
|
+
const groupNames = [
|
|
178
|
+
"source_changes",
|
|
179
|
+
"tracked_build_artifacts",
|
|
180
|
+
"ignored_untracked_artifacts",
|
|
181
|
+
"runtime_generated_files",
|
|
182
|
+
"suspicious_changes",
|
|
183
|
+
];
|
|
184
|
+
const groups = Object.fromEntries(groupNames.map((name) => [name, asArray(hygiene[name]).slice(0, maxItems)]));
|
|
185
|
+
const truncated = groupNames.some((name) => asArray(hygiene[name]).length > maxItems);
|
|
186
|
+
const compact = {
|
|
187
|
+
view: "compact",
|
|
188
|
+
task_id: String(output.task_id),
|
|
189
|
+
status: String(output.status),
|
|
190
|
+
terminal: Boolean(output.terminal),
|
|
191
|
+
acceptance_status: output.acceptance_status,
|
|
192
|
+
phase: String(output.phase),
|
|
193
|
+
repo_path: String(output.repo_path),
|
|
194
|
+
changed_files_total: asArray(output.changed_files).length,
|
|
195
|
+
out_of_scope_changes_total: asArray(output.out_of_scope_changes).length,
|
|
196
|
+
artifact_hygiene: {
|
|
197
|
+
counts: asRecord(hygiene.counts),
|
|
198
|
+
...groups,
|
|
199
|
+
max_items: maxItems,
|
|
200
|
+
truncated,
|
|
201
|
+
},
|
|
202
|
+
verification_summary: output.verification_summary,
|
|
203
|
+
summary: String(output.summary).slice(0, 1000),
|
|
204
|
+
warnings: asArray(output.warnings).slice(0, maxItems),
|
|
205
|
+
errors: asArray(output.errors).slice(0, maxItems),
|
|
206
|
+
failure_reason: output.failure_reason || null,
|
|
207
|
+
failed_command: output.failed_command || null,
|
|
208
|
+
suggested_next_action: String(output.suggested_next_action),
|
|
209
|
+
execution_blocked: Boolean(output.execution_blocked),
|
|
210
|
+
pending_reason: output.pending_reason || null,
|
|
211
|
+
};
|
|
212
|
+
const safe = redactSensitiveValue(compact);
|
|
213
|
+
return {
|
|
214
|
+
...safe.value,
|
|
215
|
+
redacted: safe.redacted,
|
|
216
|
+
redaction_categories: safe.redaction_categories,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function normalizeMaxItems(value) {
|
|
220
|
+
if (value === undefined)
|
|
221
|
+
return 8;
|
|
222
|
+
if (!Number.isInteger(value) || value < 1 || value > 50) {
|
|
223
|
+
throw new Error("max_items must be an integer from 1 to 50.");
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
167
227
|
function tryReadJson(path) {
|
|
168
228
|
if (!existsSync(path))
|
|
169
229
|
return { data: {} };
|
|
@@ -177,6 +237,9 @@ function tryReadJson(path) {
|
|
|
177
237
|
function asArray(value) {
|
|
178
238
|
return Array.isArray(value) ? value : [];
|
|
179
239
|
}
|
|
240
|
+
function asRecord(value) {
|
|
241
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
242
|
+
}
|
|
180
243
|
function summarizeTestLog(path) {
|
|
181
244
|
if (!existsSync(path))
|
|
182
245
|
return "test.log missing";
|
|
@@ -20,18 +20,6 @@ export declare function healthCheck(catalog?: ToolCatalogSnapshot, input?: Healt
|
|
|
20
20
|
error: string | null;
|
|
21
21
|
}[];
|
|
22
22
|
} | undefined;
|
|
23
|
-
status: string;
|
|
24
|
-
server_version: string;
|
|
25
|
-
schema_epoch: string;
|
|
26
|
-
tool_profile: string;
|
|
27
|
-
tool_count: number | null;
|
|
28
|
-
tool_names: string[];
|
|
29
|
-
tool_manifest_sha256: string | null;
|
|
30
|
-
profile_consistent: boolean;
|
|
31
|
-
profile_errors: string[];
|
|
32
|
-
catalog_consistent: boolean;
|
|
33
|
-
mismatch_report: string[];
|
|
34
|
-
tunnel_catalog_comparison: string;
|
|
35
23
|
connector_visibility: {
|
|
36
24
|
status: string;
|
|
37
25
|
verification: string;
|
|
@@ -76,4 +64,26 @@ export declare function healthCheck(catalog?: ToolCatalogSnapshot, input?: Healt
|
|
|
76
64
|
agent_status: {
|
|
77
65
|
[k: string]: string;
|
|
78
66
|
};
|
|
67
|
+
direct_tool_count?: number | undefined;
|
|
68
|
+
status: string;
|
|
69
|
+
server_version: string;
|
|
70
|
+
schema_epoch: string;
|
|
71
|
+
tool_profile: string;
|
|
72
|
+
tool_count: number | null;
|
|
73
|
+
tool_names: string[];
|
|
74
|
+
tool_manifest_sha256: string | null;
|
|
75
|
+
profile_consistent: boolean;
|
|
76
|
+
profile_errors: string[];
|
|
77
|
+
catalog_consistent: boolean;
|
|
78
|
+
mismatch_report: string[];
|
|
79
|
+
tunnel_catalog_comparison: string;
|
|
80
|
+
direct_profile_enabled: boolean;
|
|
81
|
+
direct_sessions_dir: {
|
|
82
|
+
available: boolean;
|
|
83
|
+
path: string;
|
|
84
|
+
readable: boolean;
|
|
85
|
+
writable: boolean;
|
|
86
|
+
reason: string | null;
|
|
87
|
+
};
|
|
88
|
+
direct_session_ttl_seconds: number;
|
|
79
89
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { accessSync, constants, existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { getConfig, getTasksDir, resolveWorkspaceRoot } from "../config.js";
|
|
4
|
+
import { getConfig, getTasksDir, getDirectSessionsDir, resolveWorkspaceRoot } from "../config.js";
|
|
5
5
|
import { listAgents } from "./listAgents.js";
|
|
6
6
|
import { redactSensitiveContent, redactSensitiveValue } from "../security/contentRedaction.js";
|
|
7
7
|
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
8
|
-
import { CHATGPT_CORE_TOOL_NAMES, resolveToolProfile } from "./toolCatalog.js";
|
|
8
|
+
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, resolveToolProfile } from "./toolCatalog.js";
|
|
9
9
|
import { readWatcherStatus } from "../watcherStatus.js";
|
|
10
10
|
import { listTasks } from "./listTasks.js";
|
|
11
11
|
const SERVER_STARTED_AT = Date.now();
|
|
@@ -26,9 +26,19 @@ export function healthCheck(catalog, input = {}) {
|
|
|
26
26
|
let profileConsistent = true;
|
|
27
27
|
if (catalog) {
|
|
28
28
|
const activeProfile = resolveToolProfile(config.toolProfile);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
let expectedNames = null;
|
|
30
|
+
if (activeProfile === "chatgpt_core") {
|
|
31
|
+
expectedNames = [...CHATGPT_CORE_TOOL_NAMES];
|
|
32
|
+
}
|
|
33
|
+
else if (activeProfile === "chatgpt_direct" && config.enableDirectProfile) {
|
|
34
|
+
expectedNames = [...CHATGPT_DIRECT_TOOL_NAMES];
|
|
35
|
+
}
|
|
36
|
+
else if (activeProfile === "chatgpt_direct" && !config.enableDirectProfile) {
|
|
37
|
+
// Degraded mode: only health_check should be exposed
|
|
38
|
+
expectedNames = ["health_check"];
|
|
39
|
+
profileErrors.push("Direct profile is disabled (enableDirectProfile=false). Only health_check is available. Set enableDirectProfile: true to enable Direct session tools.");
|
|
40
|
+
profileConsistent = false;
|
|
41
|
+
}
|
|
32
42
|
if (expectedNames) {
|
|
33
43
|
const catalogNames = new Set(catalog.tool_names);
|
|
34
44
|
for (const name of expectedNames) {
|
|
@@ -65,6 +75,9 @@ export function healthCheck(catalog, input = {}) {
|
|
|
65
75
|
const status = watcher.available && workspace.available && tasks.available && agentReady && catalogConsistent
|
|
66
76
|
? "healthy"
|
|
67
77
|
: "degraded";
|
|
78
|
+
const directSessionsDir = getDirectSessionsDir(config);
|
|
79
|
+
const directSessions = directoryStatus(directSessionsDir, true, workspaceRoot);
|
|
80
|
+
const activeProfile = resolveToolProfile(config.toolProfile);
|
|
68
81
|
return {
|
|
69
82
|
status,
|
|
70
83
|
server_version: catalog?.server_version || PATCHWARDEN_VERSION,
|
|
@@ -78,11 +91,15 @@ export function healthCheck(catalog, input = {}) {
|
|
|
78
91
|
catalog_consistent: catalogConsistent,
|
|
79
92
|
mismatch_report: mismatchReport,
|
|
80
93
|
tunnel_catalog_comparison: tunnelCatalogComparison,
|
|
94
|
+
direct_profile_enabled: config.enableDirectProfile ?? false,
|
|
95
|
+
direct_sessions_dir: directSessions,
|
|
96
|
+
direct_session_ttl_seconds: config.directSessionTtlSeconds,
|
|
97
|
+
...(activeProfile === "chatgpt_direct" ? { direct_tool_count: config.enableDirectProfile ? CHATGPT_DIRECT_TOOL_NAMES.length : 1 } : {}),
|
|
81
98
|
connector_visibility: {
|
|
82
99
|
status: "not_observable_server_side",
|
|
83
100
|
verification: "Refresh or reconnect the Connector and verify tools/list from a new ChatGPT conversation.",
|
|
84
101
|
refresh_steps: [
|
|
85
|
-
"1. Run
|
|
102
|
+
"1. Run PatchWarden.cmd health to confirm the active profile, tool count, and catalog consistency.",
|
|
86
103
|
"2. In ChatGPT Platform, refresh or reconnect the Connector (do not reuse an old session).",
|
|
87
104
|
"3. Open a NEW ChatGPT conversation (old conversations retain their cached tool catalog).",
|
|
88
105
|
"4. Call health_check in the new conversation; verify tool_manifest_sha256 matches the local report.",
|
|
@@ -4,5 +4,11 @@ export interface ReadWorkspaceFileOutput {
|
|
|
4
4
|
size: number;
|
|
5
5
|
redacted?: boolean;
|
|
6
6
|
redaction_categories?: string[];
|
|
7
|
+
relative_path?: string;
|
|
8
|
+
sha256?: string;
|
|
7
9
|
}
|
|
8
|
-
export
|
|
10
|
+
export interface ReadWorkspaceFileInput {
|
|
11
|
+
path: string;
|
|
12
|
+
session_id?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function readWorkspaceFile(input: string | ReadWorkspaceFileInput): ReadWorkspaceFileOutput;
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import { readFileSync, statSync } from "node:fs";
|
|
1
|
+
import { readFileSync, statSync, existsSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { getConfig } from "../config.js";
|
|
3
4
|
import { guardReadPath } from "../security/pathGuard.js";
|
|
4
5
|
import { guardSensitivePath } from "../security/sensitiveGuard.js";
|
|
5
6
|
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
6
|
-
|
|
7
|
+
import { PatchWardenError } from "../errors.js";
|
|
8
|
+
import { readDirectSession } from "../direct/directSessionStore.js";
|
|
9
|
+
import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "../direct/directGuards.js";
|
|
10
|
+
import { resolveToolProfile } from "./toolCatalog.js";
|
|
11
|
+
export function readWorkspaceFile(input) {
|
|
7
12
|
const config = getConfig();
|
|
13
|
+
// Support both old string API and new object API
|
|
14
|
+
const relativePath = typeof input === "string" ? input : input.path;
|
|
15
|
+
const sessionId = typeof input === "string" ? undefined : input.session_id;
|
|
16
|
+
// Direct mode: session_id provided
|
|
17
|
+
if (sessionId) {
|
|
18
|
+
return readWorkspaceFileDirect(relativePath, sessionId);
|
|
19
|
+
}
|
|
20
|
+
// In chatgpt_direct profile, session_id is required
|
|
21
|
+
const profile = resolveToolProfile(config.toolProfile);
|
|
22
|
+
if (profile === "chatgpt_direct") {
|
|
23
|
+
throw new PatchWardenError("session_id_required", "session_id is required when using the chatgpt_direct profile.", "Provide a session_id from create_direct_session.", true, { operation: "read_workspace_file" });
|
|
24
|
+
}
|
|
25
|
+
// Compatibility mode: no session_id, original behavior
|
|
8
26
|
const safePath = guardReadPath(relativePath, config.workspaceRoot);
|
|
9
27
|
guardSensitivePath(safePath);
|
|
10
28
|
const stat = statSync(safePath);
|
|
@@ -24,3 +42,31 @@ export function readWorkspaceFile(relativePath) {
|
|
|
24
42
|
redaction_categories: redaction.redaction_categories,
|
|
25
43
|
};
|
|
26
44
|
}
|
|
45
|
+
function readWorkspaceFileDirect(relativePath, sessionId) {
|
|
46
|
+
const config = getConfig();
|
|
47
|
+
const session = readDirectSession(sessionId);
|
|
48
|
+
guardDirectSessionActive(session);
|
|
49
|
+
// Resolve path within session's repo
|
|
50
|
+
const safePath = guardDirectReadPath(relativePath, session.resolved_repo_path, config.workspaceRoot);
|
|
51
|
+
if (!existsSync(safePath)) {
|
|
52
|
+
throw new PatchWardenError("file_not_found", `File "${relativePath}" not found in session repo.`, "Use search_workspace to find available files.", true, { path: relativePath, session_id: sessionId });
|
|
53
|
+
}
|
|
54
|
+
const stat = statSync(safePath);
|
|
55
|
+
guardDirectFileSize(stat.size);
|
|
56
|
+
// Block binary files
|
|
57
|
+
if (isBinaryFile(safePath)) {
|
|
58
|
+
throw new PatchWardenError("binary_file_blocked", `File "${relativePath}" appears to be a binary file.`, "Binary files cannot be read in Direct mode.", true, { path: relativePath });
|
|
59
|
+
}
|
|
60
|
+
const content = readFileSync(safePath, "utf-8");
|
|
61
|
+
const redaction = redactSensitiveContent(content);
|
|
62
|
+
const sha256 = createHash("sha256").update(content, "utf-8").digest("hex");
|
|
63
|
+
return {
|
|
64
|
+
path: safePath,
|
|
65
|
+
relative_path: relativePath,
|
|
66
|
+
content: redaction.content,
|
|
67
|
+
size: stat.size,
|
|
68
|
+
sha256,
|
|
69
|
+
redacted: redaction.redacted,
|
|
70
|
+
redaction_categories: redaction.redaction_categories,
|
|
71
|
+
};
|
|
72
|
+
}
|