@diff-review-system/drs 4.0.1 → 5.0.0-rc.1
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/.pi/agents/review/unified-reviewer.md +2 -6
- package/.pi/agents/task/okf-wiki-maintainer.md +72 -0
- package/.pi/agents/task/review-assistant.md +22 -0
- package/.pi/workflows/github-pr-fix-review-issues-stacked.yaml +12 -4
- package/.pi/workflows/github-pr-review-post.yaml +29 -20
- package/.pi/workflows/github-pr-review.yaml +37 -8
- package/.pi/workflows/github-pr-update-agents-md-stacked.yaml +2 -0
- package/.pi/workflows/github-pr-visual-explain.yaml +46 -1
- package/.pi/workflows/gitlab-mr-fix-review-issues-stacked.yaml +12 -4
- package/.pi/workflows/gitlab-mr-review.yaml +20 -8
- package/.pi/workflows/gitlab-mr-update-agents-md-stacked.yaml +2 -0
- package/.pi/workflows/gitlab-mr-visual-explain.yaml +46 -1
- package/.pi/workflows/local-fix-review-issues.yaml +13 -6
- package/.pi/workflows/local-review.yaml +7 -1
- package/.pi/workflows/local-visual-explain.yaml +46 -1
- package/.pi/workflows/release-changelog-finalize.yaml +10 -0
- package/.pi/workflows/repository-wiki-check.yaml +40 -0
- package/.pi/workflows/repository-wiki-sync.yaml +187 -0
- package/.pi/workflows/temporal-control-smoke.yaml +63 -0
- package/.wiki-site/.vitepress/config.mts +452 -0
- package/.wiki-site/.vitepress/theme/PageLead.vue +122 -0
- package/.wiki-site/.vitepress/theme/index.ts +12 -0
- package/.wiki-site/.vitepress/theme/style.css +270 -0
- package/README.md +77 -4
- package/dist/cli/index.js +87 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +6 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +26 -10
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/run-agent.d.ts +5 -7
- package/dist/cli/run-agent.d.ts.map +1 -1
- package/dist/cli/run-agent.js +56 -5
- package/dist/cli/run-agent.js.map +1 -1
- package/dist/cli/wiki.d.ts +3 -0
- package/dist/cli/wiki.d.ts.map +1 -0
- package/dist/cli/wiki.js +174 -0
- package/dist/cli/wiki.js.map +1 -0
- package/dist/cli/workflow-executor-selection.d.ts +3 -0
- package/dist/cli/workflow-executor-selection.d.ts.map +1 -0
- package/dist/cli/workflow-executor-selection.js +15 -0
- package/dist/cli/workflow-executor-selection.js.map +1 -0
- package/dist/cli/workflow.d.ts +40 -42
- package/dist/cli/workflow.d.ts.map +1 -1
- package/dist/cli/workflow.js +533 -868
- package/dist/cli/workflow.js.map +1 -1
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +2 -2
- package/dist/github/client.js.map +1 -1
- package/dist/github/platform-adapter.d.ts.map +1 -1
- package/dist/github/platform-adapter.js +15 -1
- package/dist/github/platform-adapter.js.map +1 -1
- package/dist/gitlab/client.d.ts +4 -0
- package/dist/gitlab/client.d.ts.map +1 -1
- package/dist/gitlab/client.js +17 -1
- package/dist/gitlab/client.js.map +1 -1
- package/dist/gitlab/platform-adapter.d.ts.map +1 -1
- package/dist/gitlab/platform-adapter.js +17 -1
- package/dist/gitlab/platform-adapter.js.map +1 -1
- package/dist/lib/agent-permissions.d.ts +44 -0
- package/dist/lib/agent-permissions.d.ts.map +1 -0
- package/dist/lib/agent-permissions.js +358 -0
- package/dist/lib/agent-permissions.js.map +1 -0
- package/dist/lib/agent-result.d.ts +10 -0
- package/dist/lib/agent-result.d.ts.map +1 -0
- package/dist/lib/agent-result.js +2 -0
- package/dist/lib/agent-result.js.map +1 -0
- package/dist/lib/comment-poster.d.ts +1 -1
- package/dist/lib/comment-poster.d.ts.map +1 -1
- package/dist/lib/comment-poster.js +38 -16
- package/dist/lib/comment-poster.js.map +1 -1
- package/dist/lib/config.d.ts +27 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +17 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/error-comment-poster.d.ts +1 -1
- package/dist/lib/error-comment-poster.d.ts.map +1 -1
- package/dist/lib/error-comment-poster.js +15 -7
- package/dist/lib/error-comment-poster.js.map +1 -1
- package/dist/lib/okf-wiki.d.ts +65 -0
- package/dist/lib/okf-wiki.d.ts.map +1 -0
- package/dist/lib/okf-wiki.js +647 -0
- package/dist/lib/okf-wiki.js.map +1 -0
- package/dist/lib/output-paths.d.ts +0 -1
- package/dist/lib/output-paths.d.ts.map +1 -1
- package/dist/lib/output-paths.js +0 -1
- package/dist/lib/output-paths.js.map +1 -1
- package/dist/lib/platform-client.d.ts +2 -0
- package/dist/lib/platform-client.d.ts.map +1 -1
- package/dist/lib/project-setup.d.ts +7 -0
- package/dist/lib/project-setup.d.ts.map +1 -0
- package/dist/lib/project-setup.js +11 -0
- package/dist/lib/project-setup.js.map +1 -0
- package/dist/lib/review-artifact-store.d.ts +16 -0
- package/dist/lib/review-artifact-store.d.ts.map +1 -0
- package/dist/lib/review-artifact-store.js +84 -0
- package/dist/lib/review-artifact-store.js.map +1 -0
- package/dist/lib/review-artifact.d.ts +11 -1
- package/dist/lib/review-artifact.d.ts.map +1 -1
- package/dist/lib/review-artifact.js +203 -0
- package/dist/lib/review-artifact.js.map +1 -1
- package/dist/lib/review-core.d.ts +1 -0
- package/dist/lib/review-core.d.ts.map +1 -1
- package/dist/lib/review-core.js +11 -14
- package/dist/lib/review-core.js.map +1 -1
- package/dist/lib/review-orchestrator.d.ts +6 -1
- package/dist/lib/review-orchestrator.d.ts.map +1 -1
- package/dist/lib/review-orchestrator.js +3 -1
- package/dist/lib/review-orchestrator.js.map +1 -1
- package/dist/lib/review-parser.d.ts.map +1 -1
- package/dist/lib/review-parser.js +15 -12
- package/dist/lib/review-parser.js.map +1 -1
- package/dist/lib/wiki-delta.d.ts +71 -0
- package/dist/lib/wiki-delta.d.ts.map +1 -0
- package/dist/lib/wiki-delta.js +524 -0
- package/dist/lib/wiki-delta.js.map +1 -0
- package/dist/lib/wiki-run-summary.d.ts +45 -0
- package/dist/lib/wiki-run-summary.d.ts.map +1 -0
- package/dist/lib/wiki-run-summary.js +168 -0
- package/dist/lib/wiki-run-summary.js.map +1 -0
- package/dist/lib/wiki-search.d.ts +24 -0
- package/dist/lib/wiki-search.d.ts.map +1 -0
- package/dist/lib/wiki-search.js +200 -0
- package/dist/lib/wiki-search.js.map +1 -0
- package/dist/lib/wiki-site-graph.d.ts +33 -0
- package/dist/lib/wiki-site-graph.d.ts.map +1 -0
- package/dist/lib/wiki-site-graph.js +446 -0
- package/dist/lib/wiki-site-graph.js.map +1 -0
- package/dist/lib/wiki-site-safety.d.ts +6 -0
- package/dist/lib/wiki-site-safety.d.ts.map +1 -0
- package/dist/lib/wiki-site-safety.js +83 -0
- package/dist/lib/wiki-site-safety.js.map +1 -0
- package/dist/lib/wiki-site-smoke.d.ts +16 -0
- package/dist/lib/wiki-site-smoke.d.ts.map +1 -0
- package/dist/lib/wiki-site-smoke.js +270 -0
- package/dist/lib/wiki-site-smoke.js.map +1 -0
- package/dist/lib/wiki-site.d.ts +26 -0
- package/dist/lib/wiki-site.d.ts.map +1 -0
- package/dist/lib/wiki-site.js +221 -0
- package/dist/lib/wiki-site.js.map +1 -0
- package/dist/lib/workflow/artifact-store.d.ts +71 -0
- package/dist/lib/workflow/artifact-store.d.ts.map +1 -0
- package/dist/lib/workflow/artifact-store.js +121 -0
- package/dist/lib/workflow/artifact-store.js.map +1 -0
- package/dist/lib/workflow/compiled-plan.d.ts +87 -0
- package/dist/lib/workflow/compiled-plan.d.ts.map +1 -0
- package/dist/lib/workflow/compiled-plan.js +62 -0
- package/dist/lib/workflow/compiled-plan.js.map +1 -0
- package/dist/lib/workflow/executor.d.ts +18 -0
- package/dist/lib/workflow/executor.d.ts.map +1 -0
- package/dist/lib/workflow/executor.js +2 -0
- package/dist/lib/workflow/executor.js.map +1 -0
- package/dist/lib/workflow/graph.d.ts +30 -0
- package/dist/lib/workflow/graph.d.ts.map +1 -0
- package/dist/lib/workflow/graph.js +139 -0
- package/dist/lib/workflow/graph.js.map +1 -0
- package/dist/lib/workflow/input.d.ts +4 -0
- package/dist/lib/workflow/input.d.ts.map +1 -0
- package/dist/lib/workflow/input.js +4 -0
- package/dist/lib/workflow/input.js.map +1 -0
- package/dist/lib/workflow/node-executor.d.ts +19 -0
- package/dist/lib/workflow/node-executor.d.ts.map +1 -0
- package/dist/lib/workflow/node-executor.js +2 -0
- package/dist/lib/workflow/node-executor.js.map +1 -0
- package/dist/lib/workflow/planning.d.ts +52 -0
- package/dist/lib/workflow/planning.d.ts.map +1 -0
- package/dist/lib/workflow/planning.js +865 -0
- package/dist/lib/workflow/planning.js.map +1 -0
- package/dist/lib/workflow/types.d.ts +71 -0
- package/dist/lib/workflow/types.d.ts.map +1 -0
- package/dist/lib/workflow/types.js +2 -0
- package/dist/lib/workflow/types.js.map +1 -0
- package/dist/lib/workflow-artifacts.d.ts.map +1 -1
- package/dist/lib/workflow-artifacts.js +23 -1
- package/dist/lib/workflow-artifacts.js.map +1 -1
- package/dist/lib/write-json-output.d.ts.map +1 -1
- package/dist/lib/write-json-output.js +3 -8
- package/dist/lib/write-json-output.js.map +1 -1
- package/dist/pi/sdk.d.ts.map +1 -1
- package/dist/pi/sdk.js +276 -19
- package/dist/pi/sdk.js.map +1 -1
- package/dist/runtime/client.d.ts +3 -0
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +4 -0
- package/dist/runtime/client.js.map +1 -1
- package/dist/temporal/activities.d.ts +26 -0
- package/dist/temporal/activities.d.ts.map +1 -0
- package/dist/temporal/activities.js +219 -0
- package/dist/temporal/activities.js.map +1 -0
- package/dist/temporal/config.d.ts +4 -0
- package/dist/temporal/config.d.ts.map +1 -0
- package/dist/temporal/config.js +36 -0
- package/dist/temporal/config.js.map +1 -0
- package/dist/temporal/executor.d.ts +7 -0
- package/dist/temporal/executor.d.ts.map +1 -0
- package/dist/temporal/executor.js +240 -0
- package/dist/temporal/executor.js.map +1 -0
- package/dist/temporal/retry-policy.d.ts +28 -0
- package/dist/temporal/retry-policy.d.ts.map +1 -0
- package/dist/temporal/retry-policy.js +61 -0
- package/dist/temporal/retry-policy.js.map +1 -0
- package/dist/temporal/types.d.ts +86 -0
- package/dist/temporal/types.d.ts.map +1 -0
- package/dist/temporal/types.js +2 -0
- package/dist/temporal/types.js.map +1 -0
- package/dist/temporal/worker.d.ts +3 -0
- package/dist/temporal/worker.d.ts.map +1 -0
- package/dist/temporal/worker.js +18 -0
- package/dist/temporal/worker.js.map +1 -0
- package/dist/temporal/workflow-id.d.ts +27 -0
- package/dist/temporal/workflow-id.d.ts.map +1 -0
- package/dist/temporal/workflow-id.js +53 -0
- package/dist/temporal/workflow-id.js.map +1 -0
- package/dist/temporal/workflows.d.ts +6 -0
- package/dist/temporal/workflows.d.ts.map +1 -0
- package/dist/temporal/workflows.js +355 -0
- package/dist/temporal/workflows.js.map +1 -0
- package/package.json +24 -9
package/dist/pi/sdk.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
|
-
import {
|
|
3
|
+
import { constants } from 'fs';
|
|
4
|
+
import { access, mkdir, readFile as readFsFile, unlink, writeFile as writeFsFile, } from 'fs/promises';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { isAbsolute, join, relative, resolve, sep } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
4
8
|
import { Type } from '@sinclair/typebox';
|
|
5
|
-
import { AuthStorage, createAgentSession, DefaultResourceLoader, getAgentDir, ModelRegistry, SettingsManager, SessionManager, } from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import { AuthStorage, createAgentSession, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultResourceLoader, getAgentDir, ModelRegistry, SettingsManager, SessionManager, } from '@earendil-works/pi-coding-agent';
|
|
6
10
|
import { writeJsonOutput } from '../lib/write-json-output.js';
|
|
7
|
-
import { writeArtifactOutput } from '../lib/html-artifact.js';
|
|
11
|
+
import { extractHtmlDocument, writeArtifactOutput } from '../lib/html-artifact.js';
|
|
8
12
|
import { isReviewArtifactPayload } from '../lib/review-artifact.js';
|
|
9
13
|
import { resolveWithinWorkingDir } from '../lib/path-utils.js';
|
|
14
|
+
import { AgentFilesystemAuthorizer, validateAgentPermissions, validateAgentValidation, } from '../lib/agent-permissions.js';
|
|
15
|
+
import { formatOkfValidationErrors, validateOkfBundle, validateOkfDocument, } from '../lib/okf-wiki.js';
|
|
16
|
+
import { OUTPUT_PATHS } from '../lib/output-paths.js';
|
|
10
17
|
const DEFAULT_GIT_DIFF_MAX_BYTES = 120_000;
|
|
11
18
|
const HARD_GIT_DIFF_MAX_BYTES = 500_000;
|
|
12
19
|
const GIT_DIFF_STDERR_MAX_BYTES = 16_384;
|
|
@@ -15,6 +22,44 @@ function asRecord(value) {
|
|
|
15
22
|
? value
|
|
16
23
|
: {};
|
|
17
24
|
}
|
|
25
|
+
function parseAgentPermissions(value) {
|
|
26
|
+
if (value === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
validateAgentPermissions(value, 'Pi runtime permissions');
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function parseAgentValidation(value) {
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
validateAgentValidation(value, 'Pi runtime validation');
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function toPosixPath(value) {
|
|
38
|
+
return sep === '/' ? value : value.split(sep).join('/');
|
|
39
|
+
}
|
|
40
|
+
function resolvePiToolPath(workingDir, requestedPath) {
|
|
41
|
+
let normalized = requestedPath.replace(/[\u00A0\u2000-\u200A\u202F\u205F\u3000]/gu, ' ');
|
|
42
|
+
if (normalized.startsWith('@'))
|
|
43
|
+
normalized = normalized.slice(1);
|
|
44
|
+
if (normalized === '~')
|
|
45
|
+
normalized = homedir();
|
|
46
|
+
else if (normalized.startsWith('~/') ||
|
|
47
|
+
(process.platform === 'win32' && normalized.startsWith('~\\'))) {
|
|
48
|
+
normalized = join(homedir(), normalized.slice(2));
|
|
49
|
+
}
|
|
50
|
+
if (/^file:\/\//u.test(normalized))
|
|
51
|
+
normalized = fileURLToPath(normalized);
|
|
52
|
+
return isAbsolute(normalized) ? resolve(normalized) : resolve(workingDir, normalized);
|
|
53
|
+
}
|
|
54
|
+
function allowsAggregateReadTools(permissions) {
|
|
55
|
+
return Boolean(permissions &&
|
|
56
|
+
(permissions.deny?.length ?? 0) === 0 &&
|
|
57
|
+
permissions.allow.some((pattern) => pattern.replaceAll('\\', '/').replace(/^\.\//u, '') === '**'));
|
|
58
|
+
}
|
|
59
|
+
function allowsFullRepositoryRead(permissions) {
|
|
60
|
+
return Boolean(allowsAggregateReadTools(permissions) &&
|
|
61
|
+
permissions?.roots.some((root) => root.replaceAll('\\', '/').replace(/\/$/u, '') === '.'));
|
|
62
|
+
}
|
|
18
63
|
function asString(value) {
|
|
19
64
|
return typeof value === 'string' ? value : undefined;
|
|
20
65
|
}
|
|
@@ -593,6 +638,11 @@ class PiSessionRuntime {
|
|
|
593
638
|
sessions = new Map();
|
|
594
639
|
sessionApi;
|
|
595
640
|
constructor(config) {
|
|
641
|
+
const permissions = parseAgentPermissions(config.permissions);
|
|
642
|
+
const validation = parseAgentValidation(config.validation);
|
|
643
|
+
if (validation && !permissions?.filesystem?.write && !permissions?.filesystem?.delete) {
|
|
644
|
+
throw new Error('Pi runtime validation requires filesystem write or delete permissions.');
|
|
645
|
+
}
|
|
596
646
|
this.runtimeConfig = {
|
|
597
647
|
tools: asRecord(config.tools),
|
|
598
648
|
agent: asRecord(config.agent),
|
|
@@ -602,6 +652,8 @@ class PiSessionRuntime {
|
|
|
602
652
|
thinkingLevel: asString(config.thinkingLevel),
|
|
603
653
|
fixChecks: parseFixChecks(config.fixChecks),
|
|
604
654
|
traceCollector: config.traceCollector,
|
|
655
|
+
permissions,
|
|
656
|
+
validation,
|
|
605
657
|
retry: asRecord(config.retry),
|
|
606
658
|
};
|
|
607
659
|
this.authStorage = AuthStorage.create();
|
|
@@ -729,34 +781,211 @@ class PiSessionRuntime {
|
|
|
729
781
|
}
|
|
730
782
|
resolveTools(_cwd, agentTools) {
|
|
731
783
|
const tools = [];
|
|
784
|
+
const filesystemPermissions = this.runtimeConfig.permissions?.filesystem;
|
|
785
|
+
const aggregateReadsAllowed = !filesystemPermissions?.read || allowsAggregateReadTools(filesystemPermissions.read);
|
|
732
786
|
if (this.isToolEnabled('Read', true, agentTools)) {
|
|
733
787
|
tools.push('read');
|
|
734
788
|
}
|
|
735
|
-
if (this.
|
|
789
|
+
if (this.runtimeConfig.permissions?.shell !== false &&
|
|
790
|
+
this.isToolEnabled('Bash', true, agentTools)) {
|
|
736
791
|
tools.push('bash');
|
|
737
792
|
}
|
|
738
|
-
if (
|
|
793
|
+
if ((!filesystemPermissions || filesystemPermissions.write !== undefined) &&
|
|
794
|
+
this.isToolEnabled('Edit', false, agentTools)) {
|
|
739
795
|
tools.push('edit');
|
|
740
796
|
}
|
|
741
|
-
if (
|
|
797
|
+
if ((!filesystemPermissions || filesystemPermissions.write !== undefined) &&
|
|
798
|
+
this.isToolEnabled('Write', false, agentTools)) {
|
|
742
799
|
tools.push('write');
|
|
743
800
|
}
|
|
744
|
-
if (this.isToolEnabled('Grep', true, agentTools)) {
|
|
801
|
+
if (aggregateReadsAllowed && this.isToolEnabled('Grep', true, agentTools)) {
|
|
745
802
|
tools.push('grep');
|
|
746
803
|
}
|
|
747
|
-
if (this.isToolEnabled('Glob', true, agentTools)) {
|
|
804
|
+
if (aggregateReadsAllowed && this.isToolEnabled('Glob', true, agentTools)) {
|
|
748
805
|
tools.push('find');
|
|
749
806
|
tools.push('ls');
|
|
750
807
|
}
|
|
751
|
-
|
|
752
|
-
|
|
808
|
+
return tools;
|
|
809
|
+
}
|
|
810
|
+
resolvePermissionTools(workingDir, agentTools) {
|
|
811
|
+
const filesystemPermissions = this.runtimeConfig.permissions?.filesystem;
|
|
812
|
+
if (!filesystemPermissions)
|
|
813
|
+
return [];
|
|
814
|
+
const authorizer = new AgentFilesystemAuthorizer(workingDir, filesystemPermissions);
|
|
815
|
+
const tools = [];
|
|
816
|
+
if (filesystemPermissions.read && this.isToolEnabled('Read', true, agentTools)) {
|
|
817
|
+
const readTool = createReadToolDefinition(workingDir, {
|
|
818
|
+
operations: {
|
|
819
|
+
access: async (filePath) => {
|
|
820
|
+
await authorizer.authorize('read', filePath);
|
|
821
|
+
await access(filePath, constants.R_OK);
|
|
822
|
+
},
|
|
823
|
+
readFile: async (filePath) => {
|
|
824
|
+
await authorizer.authorize('read', filePath);
|
|
825
|
+
return readFsFile(filePath);
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
});
|
|
829
|
+
const executeRead = readTool.execute.bind(readTool);
|
|
830
|
+
readTool.execute = async (toolCallId, params, signal, onUpdate, context) => {
|
|
831
|
+
await authorizer.authorize('read', resolvePiToolPath(workingDir, params.path));
|
|
832
|
+
return executeRead(toolCallId, params, signal, onUpdate, context);
|
|
833
|
+
};
|
|
834
|
+
tools.push(readTool);
|
|
835
|
+
}
|
|
836
|
+
if (filesystemPermissions.read && allowsAggregateReadTools(filesystemPermissions.read)) {
|
|
837
|
+
if (this.isToolEnabled('Grep', true, agentTools)) {
|
|
838
|
+
const grepTool = createGrepToolDefinition(workingDir);
|
|
839
|
+
const executeGrep = grepTool.execute.bind(grepTool);
|
|
840
|
+
grepTool.execute = async (toolCallId, params, signal, onUpdate, context) => {
|
|
841
|
+
await authorizer.authorize('read', resolvePiToolPath(workingDir, params.path ?? '.'));
|
|
842
|
+
return executeGrep(toolCallId, params, signal, onUpdate, context);
|
|
843
|
+
};
|
|
844
|
+
tools.push(grepTool);
|
|
845
|
+
}
|
|
846
|
+
if (this.isToolEnabled('Glob', true, agentTools)) {
|
|
847
|
+
const findTool = createFindToolDefinition(workingDir);
|
|
848
|
+
const executeFind = findTool.execute.bind(findTool);
|
|
849
|
+
findTool.execute = async (toolCallId, params, signal, onUpdate, context) => {
|
|
850
|
+
if (isAbsolute(params.pattern) ||
|
|
851
|
+
params.pattern.replaceAll('\\', '/').split('/').includes('..')) {
|
|
852
|
+
throw new Error(`Agent find pattern must stay within its search root: ${params.pattern}`);
|
|
853
|
+
}
|
|
854
|
+
await authorizer.authorize('read', resolvePiToolPath(workingDir, params.path ?? '.'));
|
|
855
|
+
return executeFind(toolCallId, params, signal, onUpdate, context);
|
|
856
|
+
};
|
|
857
|
+
tools.push(findTool);
|
|
858
|
+
const lsTool = createLsToolDefinition(workingDir);
|
|
859
|
+
const executeLs = lsTool.execute.bind(lsTool);
|
|
860
|
+
lsTool.execute = async (toolCallId, params, signal, onUpdate, context) => {
|
|
861
|
+
await authorizer.authorize('read', resolvePiToolPath(workingDir, params.path ?? '.'));
|
|
862
|
+
return executeLs(toolCallId, params, signal, onUpdate, context);
|
|
863
|
+
};
|
|
864
|
+
tools.push(lsTool);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (filesystemPermissions.write && this.isToolEnabled('Write', false, agentTools)) {
|
|
868
|
+
const writeTool = createWriteToolDefinition(workingDir, {
|
|
869
|
+
operations: {
|
|
870
|
+
mkdir: async (directory) => mkdir(directory, { recursive: true }).then(() => undefined),
|
|
871
|
+
writeFile: async (filePath, content) => {
|
|
872
|
+
await authorizer.authorize('write', filePath);
|
|
873
|
+
this.validateProposedWrite(workingDir, filePath, content);
|
|
874
|
+
await writeFsFile(filePath, content, 'utf-8');
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
const executeWrite = writeTool.execute.bind(writeTool);
|
|
879
|
+
writeTool.execute = async (toolCallId, params, signal, onUpdate, context) => {
|
|
880
|
+
const filePath = await authorizer.authorize('write', resolvePiToolPath(workingDir, params.path));
|
|
881
|
+
this.validateProposedWrite(workingDir, filePath, params.content);
|
|
882
|
+
const result = await executeWrite(toolCallId, params, signal, onUpdate, context);
|
|
883
|
+
return this.appendAfterMutationValidation(workingDir, filePath, result);
|
|
884
|
+
};
|
|
885
|
+
tools.push(writeTool);
|
|
886
|
+
}
|
|
887
|
+
if (filesystemPermissions.write && this.isToolEnabled('Edit', false, agentTools)) {
|
|
888
|
+
const editTool = createEditToolDefinition(workingDir, {
|
|
889
|
+
operations: {
|
|
890
|
+
access: async (filePath) => {
|
|
891
|
+
if (filesystemPermissions.read)
|
|
892
|
+
await authorizer.authorize('read', filePath);
|
|
893
|
+
await access(filePath, constants.R_OK | constants.W_OK);
|
|
894
|
+
},
|
|
895
|
+
readFile: async (filePath) => {
|
|
896
|
+
if (filesystemPermissions.read)
|
|
897
|
+
await authorizer.authorize('read', filePath);
|
|
898
|
+
return readFsFile(filePath);
|
|
899
|
+
},
|
|
900
|
+
writeFile: async (filePath, content) => {
|
|
901
|
+
await authorizer.authorize('write', filePath);
|
|
902
|
+
this.validateProposedWrite(workingDir, filePath, content);
|
|
903
|
+
await writeFsFile(filePath, content, 'utf-8');
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
const executeEdit = editTool.execute.bind(editTool);
|
|
908
|
+
editTool.execute = async (toolCallId, params, signal, onUpdate, context) => {
|
|
909
|
+
const resolvedPath = resolvePiToolPath(workingDir, params.path);
|
|
910
|
+
const filePath = await authorizer.authorize('write', resolvedPath);
|
|
911
|
+
if (filesystemPermissions.read)
|
|
912
|
+
await authorizer.authorize('read', resolvedPath);
|
|
913
|
+
const result = await executeEdit(toolCallId, params, signal, onUpdate, context);
|
|
914
|
+
return this.appendAfterMutationValidation(workingDir, filePath, result);
|
|
915
|
+
};
|
|
916
|
+
tools.push(editTool);
|
|
917
|
+
}
|
|
918
|
+
if (filesystemPermissions.delete && this.isToolEnabled('delete_file', false, agentTools)) {
|
|
919
|
+
tools.push({
|
|
920
|
+
name: 'delete_file',
|
|
921
|
+
label: 'delete_file',
|
|
922
|
+
description: 'Delete one file permitted by the current workflow policy.',
|
|
923
|
+
parameters: Type.Object({ path: Type.String({ minLength: 1 }) }),
|
|
924
|
+
execute: async (_toolCallId, params) => {
|
|
925
|
+
const filePath = await authorizer.authorize('delete', params.path);
|
|
926
|
+
await unlink(filePath);
|
|
927
|
+
const result = {
|
|
928
|
+
content: [{ type: 'text', text: `Deleted ${params.path}` }],
|
|
929
|
+
details: { path: params.path },
|
|
930
|
+
};
|
|
931
|
+
return this.appendAfterMutationValidation(workingDir, filePath, result);
|
|
932
|
+
},
|
|
933
|
+
});
|
|
753
934
|
}
|
|
754
935
|
return tools;
|
|
755
936
|
}
|
|
937
|
+
validateProposedWrite(workingDir, filePath, content) {
|
|
938
|
+
for (const validator of this.runtimeConfig.validation?.afterMutation ?? []) {
|
|
939
|
+
const root = resolveWithinWorkingDir(workingDir, validator.root, 'access');
|
|
940
|
+
const relativePath = relative(root, filePath);
|
|
941
|
+
if (relativePath === '' ||
|
|
942
|
+
relativePath === '..' ||
|
|
943
|
+
relativePath.startsWith(`..${sep}`) ||
|
|
944
|
+
isAbsolute(relativePath))
|
|
945
|
+
continue;
|
|
946
|
+
if (validator.name === 'okf-document') {
|
|
947
|
+
const validation = validateOkfDocument(content, toPosixPath(relativePath));
|
|
948
|
+
if (!validation.valid) {
|
|
949
|
+
const errors = validation.errors
|
|
950
|
+
.map((issue) => `- ${issue.path ?? relativePath}: [${issue.code}] ${issue.message}`)
|
|
951
|
+
.join('\n');
|
|
952
|
+
throw new Error(`Proposed write failed ${validator.name} validation:\n${errors}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async appendAfterMutationValidation(workingDir, filePath, result) {
|
|
958
|
+
const feedback = [];
|
|
959
|
+
for (const validator of this.runtimeConfig.validation?.afterMutation ?? []) {
|
|
960
|
+
const root = resolveWithinWorkingDir(workingDir, validator.root, 'access');
|
|
961
|
+
const relativePath = relative(root, filePath);
|
|
962
|
+
if (relativePath === '' ||
|
|
963
|
+
relativePath === '..' ||
|
|
964
|
+
relativePath.startsWith(`..${sep}`) ||
|
|
965
|
+
isAbsolute(relativePath))
|
|
966
|
+
continue;
|
|
967
|
+
if (validator.name === 'okf-document') {
|
|
968
|
+
const validation = await validateOkfBundle(workingDir, validator.root);
|
|
969
|
+
feedback.push(validation.valid
|
|
970
|
+
? `OKF bundle validation passed with ${validation.warnings.length} warning(s).`
|
|
971
|
+
: `OKF bundle validation found ${validation.errors.length} error(s):\n${formatOkfValidationErrors(validation)}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (feedback.length === 0)
|
|
975
|
+
return result;
|
|
976
|
+
return {
|
|
977
|
+
...result,
|
|
978
|
+
content: [...result.content, { type: 'text', text: feedback.join('\n') }],
|
|
979
|
+
};
|
|
980
|
+
}
|
|
756
981
|
resolveSkillSearchPaths(cwd) {
|
|
757
982
|
const configuredPaths = this.runtimeConfig.skillSearchPaths ?? [];
|
|
758
983
|
if (configuredPaths.length === 0) {
|
|
759
|
-
return [
|
|
984
|
+
return [
|
|
985
|
+
join(cwd, '.agents', 'skills'),
|
|
986
|
+
join(cwd, '.drs', 'skills'),
|
|
987
|
+
join(cwd, '.pi', 'skills'),
|
|
988
|
+
];
|
|
760
989
|
}
|
|
761
990
|
return configuredPaths.map((skillPath) => normalizeSkillPath(cwd, skillPath));
|
|
762
991
|
}
|
|
@@ -765,18 +994,30 @@ class PiSessionRuntime {
|
|
|
765
994
|
}
|
|
766
995
|
resolveCustomTools(workingDir, agentTools) {
|
|
767
996
|
const customTools = [];
|
|
768
|
-
|
|
997
|
+
const filesystemPermissions = this.runtimeConfig.permissions?.filesystem;
|
|
998
|
+
const authorizer = filesystemPermissions
|
|
999
|
+
? new AgentFilesystemAuthorizer(workingDir, filesystemPermissions)
|
|
1000
|
+
: undefined;
|
|
1001
|
+
if ((!filesystemPermissions || filesystemPermissions.write !== undefined) &&
|
|
1002
|
+
this.isToolEnabled('write_json_output', true, agentTools)) {
|
|
769
1003
|
customTools.push({
|
|
770
1004
|
name: 'write_json_output',
|
|
771
1005
|
label: 'write_json_output',
|
|
772
1006
|
description: 'Validate and write structured JSON output for DRS agents.',
|
|
773
1007
|
parameters: Type.Object({
|
|
774
|
-
outputType: Type.
|
|
1008
|
+
outputType: Type.Literal('describe_output'),
|
|
775
1009
|
payload: Type.Any(),
|
|
776
1010
|
pretty: Type.Optional(Type.Boolean()),
|
|
777
1011
|
indent: Type.Optional(Type.Number({ minimum: 2, maximum: 8 })),
|
|
778
1012
|
}),
|
|
779
1013
|
execute: async (_toolCallId, params) => {
|
|
1014
|
+
const outputPath = OUTPUT_PATHS[params.outputType];
|
|
1015
|
+
const filePath = authorizer
|
|
1016
|
+
? await authorizer.authorize('write', outputPath)
|
|
1017
|
+
: resolveWithinWorkingDir(workingDir, outputPath, 'write');
|
|
1018
|
+
const jsonValue = typeof params.payload === 'string' ? JSON.parse(params.payload) : params.payload;
|
|
1019
|
+
const spacing = params.pretty === false ? undefined : (params.indent ?? 2);
|
|
1020
|
+
this.validateProposedWrite(workingDir, filePath, JSON.stringify(jsonValue, null, spacing));
|
|
780
1021
|
const pointer = await writeJsonOutput({
|
|
781
1022
|
outputType: params.outputType,
|
|
782
1023
|
payload: params.payload,
|
|
@@ -784,14 +1025,16 @@ class PiSessionRuntime {
|
|
|
784
1025
|
indent: params.indent,
|
|
785
1026
|
workingDir,
|
|
786
1027
|
});
|
|
787
|
-
|
|
1028
|
+
const result = {
|
|
788
1029
|
content: [{ type: 'text', text: JSON.stringify(pointer) }],
|
|
789
1030
|
details: pointer,
|
|
790
1031
|
};
|
|
1032
|
+
return this.appendAfterMutationValidation(workingDir, filePath, result);
|
|
791
1033
|
},
|
|
792
1034
|
});
|
|
793
1035
|
}
|
|
794
|
-
if (
|
|
1036
|
+
if ((!filesystemPermissions || filesystemPermissions.write !== undefined) &&
|
|
1037
|
+
this.isToolEnabled('write_artifact_output', false, agentTools)) {
|
|
795
1038
|
customTools.push({
|
|
796
1039
|
name: 'write_artifact_output',
|
|
797
1040
|
label: 'write_artifact_output',
|
|
@@ -801,19 +1044,26 @@ class PiSessionRuntime {
|
|
|
801
1044
|
content: Type.String({ minLength: 1 }),
|
|
802
1045
|
}),
|
|
803
1046
|
execute: async (_toolCallId, params) => {
|
|
1047
|
+
const filePath = authorizer
|
|
1048
|
+
? await authorizer.authorize('write', params.outputPath)
|
|
1049
|
+
: resolveWithinWorkingDir(workingDir, params.outputPath, 'write');
|
|
1050
|
+
const html = extractHtmlDocument(params.content);
|
|
1051
|
+
this.validateProposedWrite(workingDir, filePath, html);
|
|
804
1052
|
const pointer = await writeArtifactOutput({
|
|
805
1053
|
outputPath: params.outputPath,
|
|
806
1054
|
content: params.content,
|
|
807
1055
|
workingDir,
|
|
808
1056
|
});
|
|
809
|
-
|
|
1057
|
+
const result = {
|
|
810
1058
|
content: [{ type: 'text', text: JSON.stringify(pointer) }],
|
|
811
1059
|
details: pointer,
|
|
812
1060
|
};
|
|
1061
|
+
return this.appendAfterMutationValidation(workingDir, filePath, result);
|
|
813
1062
|
},
|
|
814
1063
|
});
|
|
815
1064
|
}
|
|
816
|
-
if (
|
|
1065
|
+
if ((!filesystemPermissions?.read || allowsFullRepositoryRead(filesystemPermissions.read)) &&
|
|
1066
|
+
this.isToolEnabled('git_diff', false, agentTools)) {
|
|
817
1067
|
customTools.push({
|
|
818
1068
|
name: 'git_diff',
|
|
819
1069
|
label: 'git_diff',
|
|
@@ -883,6 +1133,7 @@ class PiSessionRuntime {
|
|
|
883
1133
|
execute: async (_toolCallId, params) => {
|
|
884
1134
|
const { readFile } = await import('fs/promises');
|
|
885
1135
|
try {
|
|
1136
|
+
await authorizer?.authorize('read', params.artifactPath);
|
|
886
1137
|
const fullPath = resolveWithinWorkingDir(workingDir, params.artifactPath, 'read');
|
|
887
1138
|
const raw = JSON.parse(await readFile(fullPath, 'utf-8'));
|
|
888
1139
|
if (findingIdParamProvided(params)) {
|
|
@@ -932,7 +1183,9 @@ class PiSessionRuntime {
|
|
|
932
1183
|
},
|
|
933
1184
|
});
|
|
934
1185
|
}
|
|
935
|
-
if (this.
|
|
1186
|
+
if (this.runtimeConfig.permissions?.shell !== false &&
|
|
1187
|
+
this.isToolEnabled('drs_check', false, agentTools) &&
|
|
1188
|
+
this.runtimeConfig.fixChecks) {
|
|
936
1189
|
const configuredChecks = this.runtimeConfig.fixChecks;
|
|
937
1190
|
customTools.push({
|
|
938
1191
|
name: 'drs_check',
|
|
@@ -1004,6 +1257,7 @@ class PiSessionRuntime {
|
|
|
1004
1257
|
const resourceLoader = new DefaultResourceLoader({
|
|
1005
1258
|
cwd,
|
|
1006
1259
|
agentDir: getAgentDir(),
|
|
1260
|
+
noExtensions: this.runtimeConfig.permissions !== undefined,
|
|
1007
1261
|
noSkills: true,
|
|
1008
1262
|
additionalSkillPaths: skillSearchPaths,
|
|
1009
1263
|
skillsOverride: configuredSkillNames.size > 0
|
|
@@ -1038,7 +1292,10 @@ class PiSessionRuntime {
|
|
|
1038
1292
|
},
|
|
1039
1293
|
})
|
|
1040
1294
|
: undefined;
|
|
1041
|
-
const customTools =
|
|
1295
|
+
const customTools = [
|
|
1296
|
+
...this.resolveCustomTools(cwd, settings.tools),
|
|
1297
|
+
...this.resolvePermissionTools(cwd, settings.tools),
|
|
1298
|
+
];
|
|
1042
1299
|
const tools = [
|
|
1043
1300
|
...this.resolveTools(cwd, settings.tools),
|
|
1044
1301
|
...customTools.map((tool) => tool.name),
|