@diff-review-system/drs 4.0.0-rc.4 → 4.0.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/describe/pr-describer.md +14 -0
- package/.pi/agents/review/unified-reviewer.md +31 -1
- package/.pi/agents/task/agents-md-updater.md +3 -1
- package/.pi/agents/task/review-issue-fixer.md +18 -1
- package/.pi/agents/visual/pr-explainer.md +205 -0
- package/.pi/workflows/github-pr-describe.yaml +10 -7
- package/.pi/workflows/github-pr-fix-review-issues-stacked.yaml +148 -0
- package/.pi/workflows/github-pr-post-comment.yaml +10 -10
- package/.pi/workflows/github-pr-review-post.yaml +19 -8
- package/.pi/workflows/github-pr-review.yaml +348 -7
- package/.pi/workflows/github-pr-show-changes.yaml +8 -8
- package/.pi/workflows/github-pr-update-agents-md-stacked.yaml +103 -0
- package/.pi/workflows/github-pr-visual-explain.yaml +35 -0
- package/.pi/workflows/gitlab-mr-describe.yaml +8 -5
- package/.pi/workflows/gitlab-mr-fix-review-issues-stacked.yaml +144 -0
- package/.pi/workflows/gitlab-mr-post-comment.yaml +8 -8
- package/.pi/workflows/gitlab-mr-review.yaml +348 -5
- package/.pi/workflows/gitlab-mr-show-changes.yaml +6 -6
- package/.pi/workflows/gitlab-mr-update-agents-md-stacked.yaml +100 -0
- package/.pi/workflows/gitlab-mr-visual-explain.yaml +33 -0
- package/.pi/workflows/local-fix-review-issues.yaml +82 -13
- package/.pi/workflows/local-review.yaml +9 -2
- package/.pi/workflows/local-update-agents-md.yaml +1 -1
- package/.pi/workflows/local-visual-explain.yaml +31 -0
- package/.pi/workflows/release-changelog-finalize.yaml +47 -0
- package/.pi/workflows/tag-changelog-update.yaml +4 -4
- package/README.md +91 -27
- package/dist/ci/runner.d.ts.map +1 -1
- package/dist/ci/runner.js +3 -1
- package/dist/ci/runner.js.map +1 -1
- package/dist/cli/index.js +48 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/run-agent.d.ts +2 -0
- package/dist/cli/run-agent.d.ts.map +1 -1
- package/dist/cli/run-agent.js +4 -0
- package/dist/cli/run-agent.js.map +1 -1
- package/dist/cli/workflow.d.ts +56 -2
- package/dist/cli/workflow.d.ts.map +1 -1
- package/dist/cli/workflow.js +2165 -85
- package/dist/cli/workflow.js.map +1 -1
- package/dist/github/client.d.ts +12 -0
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +27 -0
- package/dist/github/client.js.map +1 -1
- package/dist/github/platform-adapter.d.ts +6 -1
- package/dist/github/platform-adapter.d.ts.map +1 -1
- package/dist/github/platform-adapter.js +84 -8
- package/dist/github/platform-adapter.js.map +1 -1
- package/dist/gitlab/client.d.ts +11 -0
- package/dist/gitlab/client.d.ts.map +1 -1
- package/dist/gitlab/client.js +11 -0
- package/dist/gitlab/client.js.map +1 -1
- package/dist/gitlab/platform-adapter.d.ts +3 -1
- package/dist/gitlab/platform-adapter.d.ts.map +1 -1
- package/dist/gitlab/platform-adapter.js +32 -1
- package/dist/gitlab/platform-adapter.js.map +1 -1
- package/dist/lib/comment-formatter.d.ts +8 -0
- package/dist/lib/comment-formatter.d.ts.map +1 -1
- package/dist/lib/comment-formatter.js +12 -4
- package/dist/lib/comment-formatter.js.map +1 -1
- package/dist/lib/comment-poster.d.ts.map +1 -1
- package/dist/lib/comment-poster.js +28 -1
- package/dist/lib/comment-poster.js.map +1 -1
- package/dist/lib/config.d.ts +50 -11
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +163 -28
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/context-compression.d.ts +10 -0
- package/dist/lib/context-compression.d.ts.map +1 -1
- package/dist/lib/context-compression.js +101 -13
- package/dist/lib/context-compression.js.map +1 -1
- package/dist/lib/context-loader.d.ts +2 -1
- package/dist/lib/context-loader.d.ts.map +1 -1
- package/dist/lib/context-loader.js +70 -1
- package/dist/lib/context-loader.js.map +1 -1
- package/dist/lib/describe-core.d.ts.map +1 -1
- package/dist/lib/describe-core.js +3 -2
- package/dist/lib/describe-core.js.map +1 -1
- package/dist/lib/diff-lines.d.ts +9 -0
- package/dist/lib/diff-lines.d.ts.map +1 -1
- package/dist/lib/diff-lines.js +17 -9
- package/dist/lib/diff-lines.js.map +1 -1
- package/dist/lib/exit.js +4 -4
- package/dist/lib/exit.js.map +1 -1
- package/dist/lib/html-artifact.d.ts +14 -0
- package/dist/lib/html-artifact.d.ts.map +1 -0
- package/dist/lib/html-artifact.js +59 -0
- package/dist/lib/html-artifact.js.map +1 -0
- package/dist/lib/issue-parser.js +3 -3
- package/dist/lib/issue-parser.js.map +1 -1
- package/dist/lib/json-output-schema.d.ts +70 -0
- package/dist/lib/json-output-schema.d.ts.map +1 -1
- package/dist/lib/json-output-schema.js +40 -0
- package/dist/lib/json-output-schema.js.map +1 -1
- package/dist/lib/platform-client.d.ts +26 -0
- package/dist/lib/platform-client.d.ts.map +1 -1
- package/dist/lib/review-artifact.d.ts +69 -0
- package/dist/lib/review-artifact.d.ts.map +1 -0
- package/dist/lib/review-artifact.js +171 -0
- package/dist/lib/review-artifact.js.map +1 -0
- package/dist/lib/review-core.d.ts +6 -4
- package/dist/lib/review-core.d.ts.map +1 -1
- package/dist/lib/review-core.js +71 -151
- package/dist/lib/review-core.js.map +1 -1
- package/dist/lib/review-orchestrator.d.ts +23 -0
- package/dist/lib/review-orchestrator.d.ts.map +1 -1
- package/dist/lib/review-orchestrator.js +20 -13
- package/dist/lib/review-orchestrator.js.map +1 -1
- package/dist/lib/review-usage.d.ts +4 -0
- package/dist/lib/review-usage.d.ts.map +1 -1
- package/dist/lib/review-usage.js +25 -0
- package/dist/lib/review-usage.js.map +1 -1
- package/dist/lib/trace-collector.d.ts +105 -0
- package/dist/lib/trace-collector.d.ts.map +1 -0
- package/dist/lib/trace-collector.js +255 -0
- package/dist/lib/trace-collector.js.map +1 -0
- package/dist/lib/trace-html.d.ts +3 -0
- package/dist/lib/trace-html.d.ts.map +1 -0
- package/dist/lib/trace-html.js +349 -0
- package/dist/lib/trace-html.js.map +1 -0
- package/dist/lib/workflow-artifacts.d.ts +54 -0
- package/dist/lib/workflow-artifacts.d.ts.map +1 -0
- package/dist/lib/workflow-artifacts.js +150 -0
- package/dist/lib/workflow-artifacts.js.map +1 -0
- package/dist/pi/sdk.d.ts.map +1 -1
- package/dist/pi/sdk.js +570 -6
- package/dist/pi/sdk.js.map +1 -1
- package/dist/runtime/agent-loader.js +2 -2
- package/dist/runtime/client.d.ts +2 -0
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +11 -5
- package/dist/runtime/client.js.map +1 -1
- package/package.json +21 -15
- package/.pi/workflows/github-pr-describe-post.yaml +0 -24
- package/.pi/workflows/gitlab-mr-describe-post.yaml +0 -22
- package/.pi/workflows/gitlab-mr-review-code-quality.yaml +0 -31
- package/.pi/workflows/gitlab-mr-review-post-code-quality.yaml +0 -40
- package/.pi/workflows/gitlab-mr-review-post.yaml +0 -30
- package/.pi/workflows/local-staged-review.yaml +0 -17
- package/dist/cli/run-agent.test.d.ts +0 -2
- package/dist/cli/run-agent.test.d.ts.map +0 -1
- package/dist/cli/run-agent.test.js +0 -204
- package/dist/cli/run-agent.test.js.map +0 -1
- package/dist/cli/workflow.test.d.ts +0 -2
- package/dist/cli/workflow.test.d.ts.map +0 -1
- package/dist/cli/workflow.test.js +0 -1410
- package/dist/cli/workflow.test.js.map +0 -1
- package/dist/github/client.test.d.ts +0 -2
- package/dist/github/client.test.d.ts.map +0 -1
- package/dist/github/client.test.js +0 -206
- package/dist/github/client.test.js.map +0 -1
- package/dist/github/platform-adapter.test.d.ts +0 -2
- package/dist/github/platform-adapter.test.d.ts.map +0 -1
- package/dist/github/platform-adapter.test.js +0 -40
- package/dist/github/platform-adapter.test.js.map +0 -1
- package/dist/gitlab/diff-parser.test.d.ts +0 -2
- package/dist/gitlab/diff-parser.test.d.ts.map +0 -1
- package/dist/gitlab/diff-parser.test.js +0 -315
- package/dist/gitlab/diff-parser.test.js.map +0 -1
- package/dist/gitlab/platform-adapter.test.d.ts +0 -2
- package/dist/gitlab/platform-adapter.test.d.ts.map +0 -1
- package/dist/gitlab/platform-adapter.test.js +0 -21
- package/dist/gitlab/platform-adapter.test.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -7
- package/dist/index.test.js.map +0 -1
- package/dist/lib/code-quality-report.test.d.ts +0 -2
- package/dist/lib/code-quality-report.test.d.ts.map +0 -1
- package/dist/lib/code-quality-report.test.js +0 -327
- package/dist/lib/code-quality-report.test.js.map +0 -1
- package/dist/lib/comment-formatter.test.d.ts +0 -2
- package/dist/lib/comment-formatter.test.d.ts.map +0 -1
- package/dist/lib/comment-formatter.test.js +0 -727
- package/dist/lib/comment-formatter.test.js.map +0 -1
- package/dist/lib/comment-manager.test.d.ts +0 -2
- package/dist/lib/comment-manager.test.d.ts.map +0 -1
- package/dist/lib/comment-manager.test.js +0 -680
- package/dist/lib/comment-manager.test.js.map +0 -1
- package/dist/lib/comment-poster.test.d.ts +0 -5
- package/dist/lib/comment-poster.test.d.ts.map +0 -1
- package/dist/lib/comment-poster.test.js +0 -255
- package/dist/lib/comment-poster.test.js.map +0 -1
- package/dist/lib/config-model-overrides.test.d.ts +0 -2
- package/dist/lib/config-model-overrides.test.d.ts.map +0 -1
- package/dist/lib/config-model-overrides.test.js +0 -218
- package/dist/lib/config-model-overrides.test.js.map +0 -1
- package/dist/lib/config.test.d.ts +0 -2
- package/dist/lib/config.test.d.ts.map +0 -1
- package/dist/lib/config.test.js +0 -353
- package/dist/lib/config.test.js.map +0 -1
- package/dist/lib/context-compression.test.d.ts +0 -2
- package/dist/lib/context-compression.test.d.ts.map +0 -1
- package/dist/lib/context-compression.test.js +0 -337
- package/dist/lib/context-compression.test.js.map +0 -1
- package/dist/lib/context-loader.test.d.ts +0 -2
- package/dist/lib/context-loader.test.d.ts.map +0 -1
- package/dist/lib/context-loader.test.js +0 -212
- package/dist/lib/context-loader.test.js.map +0 -1
- package/dist/lib/cursor-fix-link.test.d.ts +0 -2
- package/dist/lib/cursor-fix-link.test.d.ts.map +0 -1
- package/dist/lib/cursor-fix-link.test.js +0 -70
- package/dist/lib/cursor-fix-link.test.js.map +0 -1
- package/dist/lib/describe-core.test.d.ts +0 -2
- package/dist/lib/describe-core.test.d.ts.map +0 -1
- package/dist/lib/describe-core.test.js +0 -208
- package/dist/lib/describe-core.test.js.map +0 -1
- package/dist/lib/describe-output-path.test.d.ts +0 -2
- package/dist/lib/describe-output-path.test.d.ts.map +0 -1
- package/dist/lib/describe-output-path.test.js +0 -51
- package/dist/lib/describe-output-path.test.js.map +0 -1
- package/dist/lib/describe-parser.test.d.ts +0 -2
- package/dist/lib/describe-parser.test.d.ts.map +0 -1
- package/dist/lib/describe-parser.test.js +0 -282
- package/dist/lib/describe-parser.test.js.map +0 -1
- package/dist/lib/description-executor.test.d.ts +0 -2
- package/dist/lib/description-executor.test.d.ts.map +0 -1
- package/dist/lib/description-executor.test.js +0 -135
- package/dist/lib/description-executor.test.js.map +0 -1
- package/dist/lib/description-formatter.test.d.ts +0 -2
- package/dist/lib/description-formatter.test.d.ts.map +0 -1
- package/dist/lib/description-formatter.test.js +0 -57
- package/dist/lib/description-formatter.test.js.map +0 -1
- package/dist/lib/diff-lines.test.d.ts +0 -2
- package/dist/lib/diff-lines.test.d.ts.map +0 -1
- package/dist/lib/diff-lines.test.js +0 -13
- package/dist/lib/diff-lines.test.js.map +0 -1
- package/dist/lib/diff-parser.test.d.ts +0 -2
- package/dist/lib/diff-parser.test.d.ts.map +0 -1
- package/dist/lib/diff-parser.test.js +0 -335
- package/dist/lib/diff-parser.test.js.map +0 -1
- package/dist/lib/error-comment-poster.test.d.ts +0 -2
- package/dist/lib/error-comment-poster.test.d.ts.map +0 -1
- package/dist/lib/error-comment-poster.test.js +0 -128
- package/dist/lib/error-comment-poster.test.js.map +0 -1
- package/dist/lib/exit.test.d.ts +0 -2
- package/dist/lib/exit.test.d.ts.map +0 -1
- package/dist/lib/exit.test.js +0 -120
- package/dist/lib/exit.test.js.map +0 -1
- package/dist/lib/issue-parser.test.d.ts +0 -2
- package/dist/lib/issue-parser.test.d.ts.map +0 -1
- package/dist/lib/issue-parser.test.js +0 -281
- package/dist/lib/issue-parser.test.js.map +0 -1
- package/dist/lib/json-output-schema.test.d.ts +0 -2
- package/dist/lib/json-output-schema.test.d.ts.map +0 -1
- package/dist/lib/json-output-schema.test.js +0 -92
- package/dist/lib/json-output-schema.test.js.map +0 -1
- package/dist/lib/json-output.test.d.ts +0 -2
- package/dist/lib/json-output.test.d.ts.map +0 -1
- package/dist/lib/json-output.test.js +0 -141
- package/dist/lib/json-output.test.js.map +0 -1
- package/dist/lib/logger.test.d.ts +0 -2
- package/dist/lib/logger.test.d.ts.map +0 -1
- package/dist/lib/logger.test.js +0 -324
- package/dist/lib/logger.test.js.map +0 -1
- package/dist/lib/position-validator.test.d.ts +0 -2
- package/dist/lib/position-validator.test.d.ts.map +0 -1
- package/dist/lib/position-validator.test.js +0 -128
- package/dist/lib/position-validator.test.js.map +0 -1
- package/dist/lib/prompt-budget.test.d.ts +0 -2
- package/dist/lib/prompt-budget.test.d.ts.map +0 -1
- package/dist/lib/prompt-budget.test.js +0 -55
- package/dist/lib/prompt-budget.test.js.map +0 -1
- package/dist/lib/repository-validator.test.d.ts +0 -5
- package/dist/lib/repository-validator.test.d.ts.map +0 -1
- package/dist/lib/repository-validator.test.js +0 -341
- package/dist/lib/repository-validator.test.js.map +0 -1
- package/dist/lib/review-core.test.d.ts +0 -2
- package/dist/lib/review-core.test.d.ts.map +0 -1
- package/dist/lib/review-core.test.js +0 -614
- package/dist/lib/review-core.test.js.map +0 -1
- package/dist/lib/review-orchestrator.test.d.ts +0 -2
- package/dist/lib/review-orchestrator.test.d.ts.map +0 -1
- package/dist/lib/review-orchestrator.test.js +0 -552
- package/dist/lib/review-orchestrator.test.js.map +0 -1
- package/dist/lib/review-output-path.test.d.ts +0 -2
- package/dist/lib/review-output-path.test.d.ts.map +0 -1
- package/dist/lib/review-output-path.test.js +0 -83
- package/dist/lib/review-output-path.test.js.map +0 -1
- package/dist/lib/review-parser.test.d.ts +0 -2
- package/dist/lib/review-parser.test.d.ts.map +0 -1
- package/dist/lib/review-parser.test.js +0 -130
- package/dist/lib/review-parser.test.js.map +0 -1
- package/dist/lib/review-usage.test.d.ts +0 -2
- package/dist/lib/review-usage.test.d.ts.map +0 -1
- package/dist/lib/review-usage.test.js +0 -83
- package/dist/lib/review-usage.test.js.map +0 -1
- package/dist/lib/unified-review-executor.d.ts +0 -58
- package/dist/lib/unified-review-executor.d.ts.map +0 -1
- package/dist/lib/unified-review-executor.js +0 -201
- package/dist/lib/unified-review-executor.js.map +0 -1
- package/dist/lib/unified-review-executor.test.d.ts +0 -5
- package/dist/lib/unified-review-executor.test.d.ts.map +0 -1
- package/dist/lib/unified-review-executor.test.js +0 -472
- package/dist/lib/unified-review-executor.test.js.map +0 -1
- package/dist/lib/write-json-output.test.d.ts +0 -2
- package/dist/lib/write-json-output.test.d.ts.map +0 -1
- package/dist/lib/write-json-output.test.js +0 -259
- package/dist/lib/write-json-output.test.js.map +0 -1
- package/dist/pi/sdk.test.d.ts +0 -2
- package/dist/pi/sdk.test.d.ts.map +0 -1
- package/dist/pi/sdk.test.js +0 -488
- package/dist/pi/sdk.test.js.map +0 -1
- package/dist/runtime/agent-loader.test.d.ts +0 -2
- package/dist/runtime/agent-loader.test.d.ts.map +0 -1
- package/dist/runtime/agent-loader.test.js +0 -277
- package/dist/runtime/agent-loader.test.js.map +0 -1
- package/dist/runtime/client.test.d.ts +0 -2
- package/dist/runtime/client.test.d.ts.map +0 -1
- package/dist/runtime/client.test.js +0 -772
- package/dist/runtime/client.test.js.map +0 -1
- package/dist/runtime/path-config.test.d.ts +0 -2
- package/dist/runtime/path-config.test.d.ts.map +0 -1
- package/dist/runtime/path-config.test.js +0 -112
- package/dist/runtime/path-config.test.js.map +0 -1
package/dist/pi/sdk.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
2
3
|
import { isAbsolute, join, resolve } from 'path';
|
|
3
4
|
import { Type } from '@sinclair/typebox';
|
|
4
|
-
import { AuthStorage, createAgentSession, DefaultResourceLoader, getAgentDir, ModelRegistry, SettingsManager, SessionManager, } from '@
|
|
5
|
+
import { AuthStorage, createAgentSession, DefaultResourceLoader, getAgentDir, ModelRegistry, SettingsManager, SessionManager, } from '@earendil-works/pi-coding-agent';
|
|
5
6
|
import { writeJsonOutput } from '../lib/write-json-output.js';
|
|
7
|
+
import { writeArtifactOutput } from '../lib/html-artifact.js';
|
|
8
|
+
import { isReviewArtifactPayload } from '../lib/review-artifact.js';
|
|
9
|
+
import { resolveWithinWorkingDir } from '../lib/path-utils.js';
|
|
10
|
+
const DEFAULT_GIT_DIFF_MAX_BYTES = 120_000;
|
|
11
|
+
const HARD_GIT_DIFF_MAX_BYTES = 500_000;
|
|
12
|
+
const GIT_DIFF_STDERR_MAX_BYTES = 16_384;
|
|
6
13
|
function asRecord(value) {
|
|
7
14
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
8
15
|
? value
|
|
@@ -148,6 +155,162 @@ function asPositiveInt(value) {
|
|
|
148
155
|
const rounded = Math.round(value);
|
|
149
156
|
return rounded > 0 ? rounded : undefined;
|
|
150
157
|
}
|
|
158
|
+
function formatUnknownError(error) {
|
|
159
|
+
if (error instanceof Error) {
|
|
160
|
+
return error.message;
|
|
161
|
+
}
|
|
162
|
+
if (typeof error === 'string') {
|
|
163
|
+
return error;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
return JSON.stringify(error);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return 'Unknown error';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function normalizeGitDiffPath(file) {
|
|
173
|
+
const trimmed = file.trim();
|
|
174
|
+
if (!trimmed) {
|
|
175
|
+
throw new Error('git_diff requires a non-empty file path');
|
|
176
|
+
}
|
|
177
|
+
if (trimmed.includes('\0')) {
|
|
178
|
+
throw new Error('git_diff file path must not contain NUL bytes');
|
|
179
|
+
}
|
|
180
|
+
if (isAbsolute(trimmed)) {
|
|
181
|
+
throw new Error('git_diff only accepts repository-relative file paths');
|
|
182
|
+
}
|
|
183
|
+
if (trimmed.split(/[\\/]+/).includes('..')) {
|
|
184
|
+
throw new Error('git_diff file path must stay inside the repository');
|
|
185
|
+
}
|
|
186
|
+
const resolved = resolve('/', trimmed);
|
|
187
|
+
const relativePath = resolved.slice(1);
|
|
188
|
+
if (!relativePath || relativePath === '..' || relativePath.startsWith('../')) {
|
|
189
|
+
throw new Error('git_diff file path must stay inside the repository');
|
|
190
|
+
}
|
|
191
|
+
return relativePath;
|
|
192
|
+
}
|
|
193
|
+
function normalizeGitRev(value, label) {
|
|
194
|
+
const trimmed = value?.trim();
|
|
195
|
+
if (!trimmed) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
if (trimmed.startsWith('-') || trimmed.includes('\0') || /\s/.test(trimmed)) {
|
|
199
|
+
throw new Error(`git_diff ${label} revision is not allowed`);
|
|
200
|
+
}
|
|
201
|
+
return trimmed;
|
|
202
|
+
}
|
|
203
|
+
function normalizeGitDiffMaxBytes(maxBytes) {
|
|
204
|
+
if (!maxBytes || !Number.isFinite(maxBytes)) {
|
|
205
|
+
return DEFAULT_GIT_DIFF_MAX_BYTES;
|
|
206
|
+
}
|
|
207
|
+
return Math.max(1, Math.min(Math.floor(maxBytes), HARD_GIT_DIFF_MAX_BYTES));
|
|
208
|
+
}
|
|
209
|
+
function appendCappedChunk(chunks, chunk, maxBytes) {
|
|
210
|
+
const currentBytes = chunks.reduce((sum, existing) => sum + existing.length, 0);
|
|
211
|
+
const remaining = maxBytes - currentBytes;
|
|
212
|
+
if (remaining <= 0) {
|
|
213
|
+
return chunk.length > 0;
|
|
214
|
+
}
|
|
215
|
+
if (chunk.length <= remaining) {
|
|
216
|
+
chunks.push(chunk);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
function runGitDiff(workingDir, args, maxBytes) {
|
|
223
|
+
return new Promise((resolvePromise, reject) => {
|
|
224
|
+
const child = spawn('git', ['diff', '--no-ext-diff', '-M', ...args], {
|
|
225
|
+
cwd: workingDir,
|
|
226
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
227
|
+
});
|
|
228
|
+
const stdoutChunks = [];
|
|
229
|
+
const stderrChunks = [];
|
|
230
|
+
let truncated = false;
|
|
231
|
+
child.stdout.on('data', (chunk) => {
|
|
232
|
+
truncated = appendCappedChunk(stdoutChunks, chunk, maxBytes) || truncated;
|
|
233
|
+
});
|
|
234
|
+
child.stderr.on('data', (chunk) => {
|
|
235
|
+
appendCappedChunk(stderrChunks, chunk, GIT_DIFF_STDERR_MAX_BYTES);
|
|
236
|
+
});
|
|
237
|
+
child.on('error', reject);
|
|
238
|
+
child.on('close', (code) => {
|
|
239
|
+
const stderr = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
240
|
+
if (code !== 0) {
|
|
241
|
+
reject(new Error(stderr || `git diff exited with code ${code ?? 'unknown'}`));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
resolvePromise({
|
|
245
|
+
stdout: Buffer.concat(stdoutChunks).toString('utf8'),
|
|
246
|
+
truncated,
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function runGitNameStatus(workingDir, args) {
|
|
252
|
+
return new Promise((resolvePromise, reject) => {
|
|
253
|
+
const child = spawn('git', ['diff', '--name-status', '-M', ...args], {
|
|
254
|
+
cwd: workingDir,
|
|
255
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
256
|
+
});
|
|
257
|
+
const stdoutChunks = [];
|
|
258
|
+
const stderrChunks = [];
|
|
259
|
+
child.stdout.on('data', (chunk) => {
|
|
260
|
+
stdoutChunks.push(chunk);
|
|
261
|
+
});
|
|
262
|
+
child.stderr.on('data', (chunk) => {
|
|
263
|
+
appendCappedChunk(stderrChunks, chunk, GIT_DIFF_STDERR_MAX_BYTES);
|
|
264
|
+
});
|
|
265
|
+
child.on('error', reject);
|
|
266
|
+
child.on('close', (code) => {
|
|
267
|
+
const stderr = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
268
|
+
if (code !== 0) {
|
|
269
|
+
reject(new Error(stderr || `git diff --name-status exited with code ${code ?? 'unknown'}`));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
resolvePromise(Buffer.concat(stdoutChunks).toString('utf8'));
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function extractGitDiffMetadata(diff) {
|
|
277
|
+
const lines = diff.split('\n');
|
|
278
|
+
const oldPath = lines
|
|
279
|
+
.find((line) => line.startsWith('rename from '))
|
|
280
|
+
?.slice('rename from '.length);
|
|
281
|
+
const newPath = lines.find((line) => line.startsWith('rename to '))?.slice('rename to '.length);
|
|
282
|
+
return {
|
|
283
|
+
binary: lines.some((line) => line.startsWith('Binary files ') || line.startsWith('GIT binary patch')),
|
|
284
|
+
deleted: lines.some((line) => line.startsWith('deleted file mode')),
|
|
285
|
+
renamed: oldPath !== undefined || newPath !== undefined,
|
|
286
|
+
oldPath,
|
|
287
|
+
newPath,
|
|
288
|
+
empty: diff.trim().length === 0,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function mergeGitNameStatusMetadata(metadata, nameStatus, file) {
|
|
292
|
+
for (const line of nameStatus.split('\n')) {
|
|
293
|
+
const parts = line.split('\t');
|
|
294
|
+
const status = parts[0];
|
|
295
|
+
if (!status)
|
|
296
|
+
continue;
|
|
297
|
+
if (status.startsWith('R') && parts[2] === file) {
|
|
298
|
+
return {
|
|
299
|
+
...metadata,
|
|
300
|
+
renamed: true,
|
|
301
|
+
oldPath: parts[1],
|
|
302
|
+
newPath: parts[2],
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (status === 'D' && parts[1] === file) {
|
|
306
|
+
return {
|
|
307
|
+
...metadata,
|
|
308
|
+
deleted: true,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return metadata;
|
|
313
|
+
}
|
|
151
314
|
function normalizeAgentSkills(value) {
|
|
152
315
|
const normalized = {};
|
|
153
316
|
for (const [agentName, skills] of Object.entries(value)) {
|
|
@@ -158,6 +321,188 @@ function normalizeAgentSkills(value) {
|
|
|
158
321
|
}
|
|
159
322
|
return normalized;
|
|
160
323
|
}
|
|
324
|
+
function parseFixChecks(value) {
|
|
325
|
+
if (!Array.isArray(value)) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const checks = [];
|
|
329
|
+
for (const entry of value) {
|
|
330
|
+
const record = asRecord(entry);
|
|
331
|
+
const name = asString(record.name);
|
|
332
|
+
const command = asString(record.command);
|
|
333
|
+
if (!name || !command) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const check = { name, command };
|
|
337
|
+
const matchPaths = asStringArray(record.matchPaths);
|
|
338
|
+
if (matchPaths && matchPaths.length > 0) {
|
|
339
|
+
check.matchPaths = matchPaths;
|
|
340
|
+
}
|
|
341
|
+
const timeoutMs = asNumber(record.timeoutMs);
|
|
342
|
+
if (timeoutMs !== undefined) {
|
|
343
|
+
check.timeoutMs = timeoutMs;
|
|
344
|
+
}
|
|
345
|
+
checks.push(check);
|
|
346
|
+
}
|
|
347
|
+
return checks.length > 0 ? checks : undefined;
|
|
348
|
+
}
|
|
349
|
+
const DEFAULT_CHECK_TIMEOUT_MS = 120_000;
|
|
350
|
+
const HARD_CHECK_TIMEOUT_MS = 600_000;
|
|
351
|
+
const CHECK_OUTPUT_MAX_BYTES = 64_000;
|
|
352
|
+
function normalizeCheckTimeout(timeoutMs) {
|
|
353
|
+
if (!timeoutMs || !Number.isFinite(timeoutMs)) {
|
|
354
|
+
return DEFAULT_CHECK_TIMEOUT_MS;
|
|
355
|
+
}
|
|
356
|
+
return Math.max(1_000, Math.min(Math.floor(timeoutMs), HARD_CHECK_TIMEOUT_MS));
|
|
357
|
+
}
|
|
358
|
+
function globToRegex(pattern) {
|
|
359
|
+
let regex = '';
|
|
360
|
+
let i = 0;
|
|
361
|
+
while (i < pattern.length) {
|
|
362
|
+
const char = pattern[i];
|
|
363
|
+
if (char === '*' && pattern[i + 1] === '*') {
|
|
364
|
+
regex += '.*';
|
|
365
|
+
i += 2;
|
|
366
|
+
if (pattern[i] === '/') {
|
|
367
|
+
i++;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else if (char === '*') {
|
|
371
|
+
regex += '[^/]*';
|
|
372
|
+
i++;
|
|
373
|
+
}
|
|
374
|
+
else if (char === '?') {
|
|
375
|
+
regex += '[^/]';
|
|
376
|
+
i++;
|
|
377
|
+
}
|
|
378
|
+
else if ('.+^${}()|[]\\'.includes(char)) {
|
|
379
|
+
regex += '\\' + char;
|
|
380
|
+
i++;
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
regex += char;
|
|
384
|
+
i++;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return new RegExp('^' + regex + '$');
|
|
388
|
+
}
|
|
389
|
+
function fileMatchesGlobs(filePath, patterns) {
|
|
390
|
+
if (!patterns || patterns.length === 0) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
return patterns.some((pattern) => globToRegex(pattern).test(filePath));
|
|
394
|
+
}
|
|
395
|
+
async function getChangedFiles(workingDir) {
|
|
396
|
+
return new Promise((resolvePromise) => {
|
|
397
|
+
const child = spawn('git', ['status', '--porcelain', '--no-renames'], {
|
|
398
|
+
cwd: workingDir,
|
|
399
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
400
|
+
});
|
|
401
|
+
const stdoutChunks = [];
|
|
402
|
+
child.stdout.on('data', (chunk) => stdoutChunks.push(chunk));
|
|
403
|
+
child.on('error', () => resolvePromise([]));
|
|
404
|
+
child.on('close', () => {
|
|
405
|
+
const output = Buffer.concat(stdoutChunks).toString('utf8').trim();
|
|
406
|
+
resolvePromise(output
|
|
407
|
+
? output
|
|
408
|
+
.split('\n')
|
|
409
|
+
.map((line) => line.slice(3).trim())
|
|
410
|
+
.filter(Boolean)
|
|
411
|
+
: []);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function runCheckCommand(workingDir, check) {
|
|
416
|
+
return new Promise((resolvePromise) => {
|
|
417
|
+
const timeoutMs = normalizeCheckTimeout(check.timeoutMs);
|
|
418
|
+
const child = spawn(check.command, {
|
|
419
|
+
cwd: workingDir,
|
|
420
|
+
shell: true,
|
|
421
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
422
|
+
});
|
|
423
|
+
const stdoutChunks = [];
|
|
424
|
+
const stderrChunks = [];
|
|
425
|
+
let truncated = false;
|
|
426
|
+
let timedOut = false;
|
|
427
|
+
const startTime = Date.now();
|
|
428
|
+
const timer = setTimeout(() => {
|
|
429
|
+
timedOut = true;
|
|
430
|
+
child.kill('SIGTERM');
|
|
431
|
+
}, timeoutMs);
|
|
432
|
+
child.stdout.on('data', (chunk) => {
|
|
433
|
+
truncated = appendCappedChunk(stdoutChunks, chunk, CHECK_OUTPUT_MAX_BYTES) || truncated;
|
|
434
|
+
});
|
|
435
|
+
child.stderr.on('data', (chunk) => {
|
|
436
|
+
appendCappedChunk(stderrChunks, chunk, CHECK_OUTPUT_MAX_BYTES);
|
|
437
|
+
});
|
|
438
|
+
child.on('error', () => {
|
|
439
|
+
clearTimeout(timer);
|
|
440
|
+
resolvePromise({
|
|
441
|
+
name: check.name,
|
|
442
|
+
command: check.command,
|
|
443
|
+
exitCode: null,
|
|
444
|
+
stdout: '',
|
|
445
|
+
stderr: `Failed to spawn: ${check.command}`,
|
|
446
|
+
truncated: false,
|
|
447
|
+
skipped: false,
|
|
448
|
+
durationMs: Date.now() - startTime,
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
child.on('close', (code) => {
|
|
452
|
+
clearTimeout(timer);
|
|
453
|
+
resolvePromise({
|
|
454
|
+
name: check.name,
|
|
455
|
+
command: check.command,
|
|
456
|
+
exitCode: timedOut ? null : code,
|
|
457
|
+
stdout: Buffer.concat(stdoutChunks).toString('utf8'),
|
|
458
|
+
stderr: (timedOut ? `Timed out after ${timeoutMs}ms\n` : '') +
|
|
459
|
+
Buffer.concat(stderrChunks).toString('utf8'),
|
|
460
|
+
truncated,
|
|
461
|
+
skipped: false,
|
|
462
|
+
durationMs: Date.now() - startTime,
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function findingIdParamProvided(params) {
|
|
468
|
+
return typeof params.findingId === 'string' && params.findingId.trim().length > 0;
|
|
469
|
+
}
|
|
470
|
+
function extractFindingFromArtifact(raw, findingId) {
|
|
471
|
+
const envelope = raw;
|
|
472
|
+
const payload = envelope?.payload ?? raw;
|
|
473
|
+
if (!isReviewArtifactPayload(payload)) {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
return payload.findings.find((f) => f.id === findingId);
|
|
477
|
+
}
|
|
478
|
+
function buildArtifactManifest(raw, artifactPath) {
|
|
479
|
+
const envelope = raw;
|
|
480
|
+
const payload = envelope?.payload ?? raw;
|
|
481
|
+
if (!isReviewArtifactPayload(payload)) {
|
|
482
|
+
return { artifactPath, ok: false, totalFindings: 0, actionableFindings: 0, findings: [] };
|
|
483
|
+
}
|
|
484
|
+
const findings = payload.findings.map((f) => ({
|
|
485
|
+
id: f.id,
|
|
486
|
+
severity: f.issue.severity,
|
|
487
|
+
state: f.state,
|
|
488
|
+
disposition: f.disposition,
|
|
489
|
+
file: f.issue.file,
|
|
490
|
+
line: f.issue.line,
|
|
491
|
+
title: f.issue.title,
|
|
492
|
+
}));
|
|
493
|
+
const actionableFindings = findings.filter((f) => f.state === 'open' &&
|
|
494
|
+
(f.disposition === 'still_open' ||
|
|
495
|
+
f.disposition === 'partial' ||
|
|
496
|
+
f.disposition === 'regression')).length;
|
|
497
|
+
return {
|
|
498
|
+
artifactPath,
|
|
499
|
+
ok: true,
|
|
500
|
+
reviewId: payload.reviewId,
|
|
501
|
+
totalFindings: findings.length,
|
|
502
|
+
actionableFindings,
|
|
503
|
+
findings,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
161
506
|
function normalizeSkillPath(cwd, skillPath) {
|
|
162
507
|
return isAbsolute(skillPath) ? resolve(skillPath) : resolve(cwd, skillPath);
|
|
163
508
|
}
|
|
@@ -255,6 +600,8 @@ class PiSessionRuntime {
|
|
|
255
600
|
skillSearchPaths: asStringArray(config.skillSearchPaths),
|
|
256
601
|
agentSkills: normalizeAgentSkills(asRecord(config.agentSkills)),
|
|
257
602
|
thinkingLevel: asString(config.thinkingLevel),
|
|
603
|
+
fixChecks: parseFixChecks(config.fixChecks),
|
|
604
|
+
traceCollector: config.traceCollector,
|
|
258
605
|
retry: asRecord(config.retry),
|
|
259
606
|
};
|
|
260
607
|
this.authStorage = AuthStorage.create();
|
|
@@ -416,9 +763,9 @@ class PiSessionRuntime {
|
|
|
416
763
|
resolveAgentSkills(agentName) {
|
|
417
764
|
return this.runtimeConfig.agentSkills?.[agentName] ?? [];
|
|
418
765
|
}
|
|
419
|
-
resolveCustomTools(workingDir) {
|
|
766
|
+
resolveCustomTools(workingDir, agentTools) {
|
|
420
767
|
const customTools = [];
|
|
421
|
-
if (this.isToolEnabled('write_json_output', true)) {
|
|
768
|
+
if (this.isToolEnabled('write_json_output', true, agentTools)) {
|
|
422
769
|
customTools.push({
|
|
423
770
|
name: 'write_json_output',
|
|
424
771
|
label: 'write_json_output',
|
|
@@ -444,6 +791,210 @@ class PiSessionRuntime {
|
|
|
444
791
|
},
|
|
445
792
|
});
|
|
446
793
|
}
|
|
794
|
+
if (this.isToolEnabled('write_artifact_output', false, agentTools)) {
|
|
795
|
+
customTools.push({
|
|
796
|
+
name: 'write_artifact_output',
|
|
797
|
+
label: 'write_artifact_output',
|
|
798
|
+
description: 'Validate and write a self-contained HTML artifact for DRS agents.',
|
|
799
|
+
parameters: Type.Object({
|
|
800
|
+
outputPath: Type.String({ minLength: 1 }),
|
|
801
|
+
content: Type.String({ minLength: 1 }),
|
|
802
|
+
}),
|
|
803
|
+
execute: async (_toolCallId, params) => {
|
|
804
|
+
const pointer = await writeArtifactOutput({
|
|
805
|
+
outputPath: params.outputPath,
|
|
806
|
+
content: params.content,
|
|
807
|
+
workingDir,
|
|
808
|
+
});
|
|
809
|
+
return {
|
|
810
|
+
content: [{ type: 'text', text: JSON.stringify(pointer) }],
|
|
811
|
+
details: pointer,
|
|
812
|
+
};
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
if (this.isToolEnabled('git_diff', false, agentTools)) {
|
|
817
|
+
customTools.push({
|
|
818
|
+
name: 'git_diff',
|
|
819
|
+
label: 'git_diff',
|
|
820
|
+
description: 'Read a unified git diff for one repository-relative file path.',
|
|
821
|
+
parameters: Type.Object({
|
|
822
|
+
file: Type.String({ minLength: 1 }),
|
|
823
|
+
base: Type.Optional(Type.String()),
|
|
824
|
+
head: Type.Optional(Type.String()),
|
|
825
|
+
maxBytes: Type.Optional(Type.Number({ minimum: 1, maximum: HARD_GIT_DIFF_MAX_BYTES })),
|
|
826
|
+
}),
|
|
827
|
+
execute: async (_toolCallId, params) => {
|
|
828
|
+
const file = normalizeGitDiffPath(params.file);
|
|
829
|
+
const base = normalizeGitRev(params.base, 'base');
|
|
830
|
+
const head = normalizeGitRev(params.head, 'head');
|
|
831
|
+
const maxBytes = normalizeGitDiffMaxBytes(params.maxBytes);
|
|
832
|
+
const range = base && head ? [`${base}...${head}`] : base ? [base] : [];
|
|
833
|
+
let diff = '';
|
|
834
|
+
let truncated = false;
|
|
835
|
+
let error;
|
|
836
|
+
try {
|
|
837
|
+
const result = await runGitDiff(workingDir, [...range, '--', file], maxBytes);
|
|
838
|
+
diff = result.stdout;
|
|
839
|
+
truncated = result.truncated;
|
|
840
|
+
}
|
|
841
|
+
catch (caught) {
|
|
842
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
843
|
+
}
|
|
844
|
+
let metadata = extractGitDiffMetadata(diff);
|
|
845
|
+
if (error === undefined && range.length > 0) {
|
|
846
|
+
try {
|
|
847
|
+
const nameStatus = await runGitNameStatus(workingDir, range);
|
|
848
|
+
metadata = mergeGitNameStatusMetadata(metadata, nameStatus, file);
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// Diff text remains authoritative; name-status only enriches metadata.
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const details = {
|
|
855
|
+
file,
|
|
856
|
+
base,
|
|
857
|
+
head,
|
|
858
|
+
ok: error === undefined,
|
|
859
|
+
error,
|
|
860
|
+
truncated,
|
|
861
|
+
bytes: Buffer.byteLength(diff, 'utf8'),
|
|
862
|
+
metadata,
|
|
863
|
+
diff,
|
|
864
|
+
};
|
|
865
|
+
return {
|
|
866
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
867
|
+
details,
|
|
868
|
+
};
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
if (this.isToolEnabled('read_artifact', false, agentTools)) {
|
|
873
|
+
customTools.push({
|
|
874
|
+
name: 'read_artifact',
|
|
875
|
+
label: 'read_artifact',
|
|
876
|
+
description: 'Read a DRS review artifact from a file path. ' +
|
|
877
|
+
'Without findingId: returns a compact manifest of all findings (id, severity, state, disposition, file, line, title). ' +
|
|
878
|
+
'With findingId: returns the full finding detail including issue, verification rationale, and fingerprint.',
|
|
879
|
+
parameters: Type.Object({
|
|
880
|
+
artifactPath: Type.String({ minLength: 1 }),
|
|
881
|
+
findingId: Type.Optional(Type.String({ minLength: 1 })),
|
|
882
|
+
}),
|
|
883
|
+
execute: async (_toolCallId, params) => {
|
|
884
|
+
const { readFile } = await import('fs/promises');
|
|
885
|
+
try {
|
|
886
|
+
const fullPath = resolveWithinWorkingDir(workingDir, params.artifactPath, 'read');
|
|
887
|
+
const raw = JSON.parse(await readFile(fullPath, 'utf-8'));
|
|
888
|
+
if (findingIdParamProvided(params)) {
|
|
889
|
+
const finding = extractFindingFromArtifact(raw, params.findingId);
|
|
890
|
+
if (!finding) {
|
|
891
|
+
const details = {
|
|
892
|
+
artifactPath: params.artifactPath,
|
|
893
|
+
findingId: params.findingId,
|
|
894
|
+
ok: false,
|
|
895
|
+
error: `Finding "${params.findingId}" not found in artifact`,
|
|
896
|
+
};
|
|
897
|
+
return {
|
|
898
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
899
|
+
details,
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
const details = {
|
|
903
|
+
artifactPath: params.artifactPath,
|
|
904
|
+
findingId: params.findingId,
|
|
905
|
+
ok: true,
|
|
906
|
+
finding,
|
|
907
|
+
};
|
|
908
|
+
return {
|
|
909
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
910
|
+
details,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
const manifest = buildArtifactManifest(raw, params.artifactPath);
|
|
914
|
+
return {
|
|
915
|
+
content: [{ type: 'text', text: JSON.stringify(manifest) }],
|
|
916
|
+
details: manifest,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
catch (caught) {
|
|
920
|
+
const error = formatUnknownError(caught);
|
|
921
|
+
const details = {
|
|
922
|
+
artifactPath: params.artifactPath,
|
|
923
|
+
findingId: params.findingId,
|
|
924
|
+
ok: false,
|
|
925
|
+
error,
|
|
926
|
+
};
|
|
927
|
+
return {
|
|
928
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
929
|
+
details,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
if (this.isToolEnabled('drs_check', false, agentTools) && this.runtimeConfig.fixChecks) {
|
|
936
|
+
const configuredChecks = this.runtimeConfig.fixChecks;
|
|
937
|
+
customTools.push({
|
|
938
|
+
name: 'drs_check',
|
|
939
|
+
label: 'drs_check',
|
|
940
|
+
description: 'Run configured DRS fix checks (type-check, lint, tests, etc.). ' +
|
|
941
|
+
'Without name: runs all applicable checks (filtered by matchPaths against changed files). ' +
|
|
942
|
+
'With name: runs a single named check. ' +
|
|
943
|
+
'Returns exit code, stdout, and stderr for each check.',
|
|
944
|
+
parameters: Type.Object({
|
|
945
|
+
name: Type.Optional(Type.String({ minLength: 1 })),
|
|
946
|
+
}),
|
|
947
|
+
execute: async (_toolCallId, params) => {
|
|
948
|
+
const changedFiles = await getChangedFiles(workingDir);
|
|
949
|
+
let checksToRun;
|
|
950
|
+
if (params.name) {
|
|
951
|
+
const match = configuredChecks.find((c) => c.name === params.name);
|
|
952
|
+
if (!match) {
|
|
953
|
+
const details = {
|
|
954
|
+
ok: false,
|
|
955
|
+
error: `No check named "${params.name}" is configured`,
|
|
956
|
+
availableChecks: configuredChecks.map((c) => c.name),
|
|
957
|
+
};
|
|
958
|
+
return {
|
|
959
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
960
|
+
details,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
checksToRun = [match];
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
checksToRun = configuredChecks.filter((check) => !check.matchPaths ||
|
|
967
|
+
check.matchPaths.length === 0 ||
|
|
968
|
+
changedFiles.some((f) => fileMatchesGlobs(f, check.matchPaths)));
|
|
969
|
+
}
|
|
970
|
+
if (checksToRun.length === 0) {
|
|
971
|
+
const details = {
|
|
972
|
+
ok: true,
|
|
973
|
+
checks: [],
|
|
974
|
+
changedFiles,
|
|
975
|
+
skipped: 'No applicable checks for changed files',
|
|
976
|
+
};
|
|
977
|
+
return {
|
|
978
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
979
|
+
details,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const results = [];
|
|
983
|
+
for (const check of checksToRun) {
|
|
984
|
+
results.push(await runCheckCommand(workingDir, check));
|
|
985
|
+
}
|
|
986
|
+
const details = {
|
|
987
|
+
ok: results.every((r) => r.exitCode === 0),
|
|
988
|
+
checks: results,
|
|
989
|
+
changedFiles,
|
|
990
|
+
};
|
|
991
|
+
return {
|
|
992
|
+
content: [{ type: 'text', text: JSON.stringify(details) }],
|
|
993
|
+
details,
|
|
994
|
+
};
|
|
995
|
+
},
|
|
996
|
+
});
|
|
997
|
+
}
|
|
447
998
|
return customTools;
|
|
448
999
|
}
|
|
449
1000
|
async createAgentSession(cwd, agentName) {
|
|
@@ -487,6 +1038,11 @@ class PiSessionRuntime {
|
|
|
487
1038
|
},
|
|
488
1039
|
})
|
|
489
1040
|
: undefined;
|
|
1041
|
+
const customTools = this.resolveCustomTools(cwd, settings.tools);
|
|
1042
|
+
const tools = [
|
|
1043
|
+
...this.resolveTools(cwd, settings.tools),
|
|
1044
|
+
...customTools.map((tool) => tool.name),
|
|
1045
|
+
];
|
|
490
1046
|
const { session } = await createAgentSession({
|
|
491
1047
|
cwd,
|
|
492
1048
|
authStorage: this.authStorage,
|
|
@@ -495,8 +1051,8 @@ class PiSessionRuntime {
|
|
|
495
1051
|
resourceLoader,
|
|
496
1052
|
sessionManager: SessionManager.inMemory(),
|
|
497
1053
|
settingsManager,
|
|
498
|
-
tools
|
|
499
|
-
customTools
|
|
1054
|
+
tools,
|
|
1055
|
+
customTools,
|
|
500
1056
|
thinkingLevel: this.runtimeConfig.thinkingLevel,
|
|
501
1057
|
});
|
|
502
1058
|
return session;
|
|
@@ -534,6 +1090,9 @@ class PiSessionRuntime {
|
|
|
534
1090
|
record.session = await this.createAgentSession(cwd, input.body.agent);
|
|
535
1091
|
record.agent = input.body.agent;
|
|
536
1092
|
record.cwd = cwd;
|
|
1093
|
+
if (this.runtimeConfig.traceCollector) {
|
|
1094
|
+
this.runtimeConfig.traceCollector.attachSession(record.session, record.id);
|
|
1095
|
+
}
|
|
537
1096
|
}
|
|
538
1097
|
record.error = undefined;
|
|
539
1098
|
await record.session.prompt(promptText);
|
|
@@ -543,6 +1102,11 @@ class PiSessionRuntime {
|
|
|
543
1102
|
record.error = error;
|
|
544
1103
|
return { ok: false };
|
|
545
1104
|
}
|
|
1105
|
+
finally {
|
|
1106
|
+
if (this.runtimeConfig.traceCollector && record.session) {
|
|
1107
|
+
this.runtimeConfig.traceCollector.finalizeSession(record.id);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
546
1110
|
}
|
|
547
1111
|
readMessages(input) {
|
|
548
1112
|
const record = this.getSessionRecord(input.path.id);
|
|
@@ -563,7 +1127,7 @@ class PiSessionRuntime {
|
|
|
563
1127
|
id: `${record.id}-error`,
|
|
564
1128
|
role: 'assistant',
|
|
565
1129
|
time: { completed: Date.now() },
|
|
566
|
-
error:
|
|
1130
|
+
error: formatUnknownError(record.error),
|
|
567
1131
|
},
|
|
568
1132
|
parts: [{ text: '' }],
|
|
569
1133
|
});
|