pi-onedev-toolkit 0.1.0 → 0.2.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/CHANGELOG.md +29 -1
- package/README.md +23 -7
- package/package.json +9 -5
- package/skills/onedev-issue-to-pr/SKILL.md +9 -4
- package/skills/onedev-pr-review/SKILL.md +12 -6
- package/src/config-storage.ts +105 -0
- package/src/context.ts +204 -38
- package/src/extension.ts +98 -8
- package/src/mutation-approvals.ts +130 -0
- package/src/source-watch.ts +521 -0
- package/src/tod.ts +33 -3
- package/src/tools/build.ts +143 -21
- package/src/tools/common.ts +27 -12
- package/src/tools/index.ts +91 -63
- package/src/tools/issue.ts +50 -26
- package/src/tools/pull.ts +116 -67
- package/src/tools/watch.ts +70 -0
package/src/tools/build.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { copyFile, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
1
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
5
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
3
6
|
import { Type, type Static } from "typebox";
|
|
4
7
|
import {
|
|
8
|
+
confirmMutation,
|
|
5
9
|
pushRepeated,
|
|
6
10
|
QUERY_DESCRIPTION,
|
|
7
11
|
REF_DESCRIPTION,
|
|
@@ -11,13 +15,20 @@ import {
|
|
|
11
15
|
withFooter,
|
|
12
16
|
type ToolDeps,
|
|
13
17
|
} from "./common.js";
|
|
14
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
BUILD_RUN_TIMEOUT_MS,
|
|
20
|
+
runTod,
|
|
21
|
+
TodError,
|
|
22
|
+
type TodOutput,
|
|
23
|
+
} from "../tod.js";
|
|
24
|
+
import type { MutationOperation } from "../mutation-approvals.js";
|
|
15
25
|
|
|
16
26
|
const BUILD_ACTIONS = [
|
|
17
27
|
"list",
|
|
18
28
|
"get",
|
|
19
29
|
"get_log",
|
|
20
30
|
"get_code_problems",
|
|
31
|
+
"get_unit_test_report",
|
|
21
32
|
"get_changes_since_success",
|
|
22
33
|
"run",
|
|
23
34
|
"check_spec",
|
|
@@ -26,13 +37,18 @@ const BUILD_ACTIONS = [
|
|
|
26
37
|
] as const;
|
|
27
38
|
export type BuildAction = (typeof BUILD_ACTIONS)[number];
|
|
28
39
|
|
|
40
|
+
const BUILD_MUTATIONS: Partial<Record<BuildAction, MutationOperation>> = {
|
|
41
|
+
run: "build.run",
|
|
42
|
+
check_spec: "build.check_spec",
|
|
43
|
+
};
|
|
44
|
+
|
|
29
45
|
export const SEVERITY_LEVELS = ["CRITICAL", "HIGH", "MEDIUM", "LOW"] as const;
|
|
30
46
|
export const RUN_MODES = ["local", "branch", "tag"] as const;
|
|
31
47
|
|
|
32
48
|
export interface BuildParams {
|
|
33
49
|
action?: BuildAction;
|
|
34
50
|
ref?: string;
|
|
35
|
-
|
|
51
|
+
report_name?: string;
|
|
36
52
|
severity?: (typeof SEVERITY_LEVELS)[number];
|
|
37
53
|
job?: string;
|
|
38
54
|
mode?: (typeof RUN_MODES)[number];
|
|
@@ -67,23 +83,42 @@ export function buildBuildArgs(params: BuildParams): string[] {
|
|
|
67
83
|
...args,
|
|
68
84
|
"get-code-problems",
|
|
69
85
|
ref(),
|
|
70
|
-
requireParam(params.
|
|
86
|
+
requireParam(params.report_name, action, "report_name"),
|
|
71
87
|
requireParam(params.severity, action, "severity"),
|
|
72
88
|
];
|
|
89
|
+
case "get_unit_test_report":
|
|
90
|
+
return [
|
|
91
|
+
...args,
|
|
92
|
+
"get-unit-test-report",
|
|
93
|
+
ref(),
|
|
94
|
+
requireParam(params.report_name, action, "report_name"),
|
|
95
|
+
];
|
|
73
96
|
case "get_changes_since_success":
|
|
74
97
|
return [...args, "get-changes-since-success", ref()];
|
|
75
98
|
case "run":
|
|
76
99
|
args.push("run", requireParam(params.job, action, "job"));
|
|
100
|
+
if (params.branch !== undefined && params.tag !== undefined) {
|
|
101
|
+
throw new Error("run accepts either branch or tag, not both");
|
|
102
|
+
}
|
|
103
|
+
if (
|
|
104
|
+
params.mode === "local" &&
|
|
105
|
+
(params.branch !== undefined || params.tag !== undefined)
|
|
106
|
+
) {
|
|
107
|
+
throw new Error("local run mode does not accept branch or tag");
|
|
108
|
+
}
|
|
109
|
+
if (params.mode === "branch" && params.tag !== undefined) {
|
|
110
|
+
throw new Error("branch run mode does not accept tag");
|
|
111
|
+
}
|
|
112
|
+
if (params.mode === "tag" && params.branch !== undefined) {
|
|
113
|
+
throw new Error("tag run mode does not accept branch");
|
|
114
|
+
}
|
|
77
115
|
if (
|
|
78
116
|
params.mode === "local" ||
|
|
79
117
|
(params.mode === undefined && !params.branch && !params.tag)
|
|
80
118
|
) {
|
|
81
119
|
args.push("--local");
|
|
82
120
|
} else if (params.mode === "branch" || params.branch !== undefined) {
|
|
83
|
-
args.push(
|
|
84
|
-
"--branch",
|
|
85
|
-
requireParam(params.branch ?? params.job, action, "branch"),
|
|
86
|
-
);
|
|
121
|
+
args.push("--branch", requireParam(params.branch, action, "branch"));
|
|
87
122
|
} else {
|
|
88
123
|
args.push("--tag", requireParam(params.tag, action, "tag"));
|
|
89
124
|
}
|
|
@@ -95,6 +130,72 @@ export function buildBuildArgs(params: BuildParams): string[] {
|
|
|
95
130
|
return [...args, "get-spec-schema"];
|
|
96
131
|
case "query_description":
|
|
97
132
|
return [...args, "get-query-description"];
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`unsupported build action: ${String(action)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function ensureUnitTestReportSupport(
|
|
139
|
+
deps: ToolDeps,
|
|
140
|
+
signal?: AbortSignal,
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
const help = await runInContext(deps, ["build", "--help"], {
|
|
143
|
+
signal,
|
|
144
|
+
maxOutputBytes: 12_000,
|
|
145
|
+
});
|
|
146
|
+
if (!/^\s+get-unit-test-report\s/m.test(help.text)) {
|
|
147
|
+
throw new TodError(
|
|
148
|
+
"the installed tod does not support build get-unit-test-report; update tod from https://code.onedev.io/onedev/tod",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function runLocalBuildWithIsolatedIndex(
|
|
154
|
+
deps: ToolDeps,
|
|
155
|
+
args: string[],
|
|
156
|
+
signal?: AbortSignal,
|
|
157
|
+
): Promise<TodOutput> {
|
|
158
|
+
if (process.platform === "win32") {
|
|
159
|
+
throw new TodError(
|
|
160
|
+
"local build mode is disabled on Windows because TOD modifies the git index; use branch or tag mode",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const context = deps.context();
|
|
164
|
+
const indexResult = await deps.exec(
|
|
165
|
+
"git",
|
|
166
|
+
["rev-parse", "--git-path", "index"],
|
|
167
|
+
{ cwd: context.cwd, timeout: 10_000, signal },
|
|
168
|
+
);
|
|
169
|
+
if (indexResult.killed || indexResult.code !== 0) {
|
|
170
|
+
throw new TodError(
|
|
171
|
+
`could not isolate the git index for local build mode: ${(indexResult.stderr || indexResult.stdout).trim() || "git rev-parse failed"}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const indexPath = indexResult.stdout.trim();
|
|
175
|
+
const sourceIndex = isAbsolute(indexPath)
|
|
176
|
+
? indexPath
|
|
177
|
+
: resolve(context.cwd, indexPath);
|
|
178
|
+
const directory = await mkdtemp(join(tmpdir(), "pi-onedev-index-"));
|
|
179
|
+
const isolatedIndex = join(directory, "index");
|
|
180
|
+
try {
|
|
181
|
+
await copyFile(sourceIndex, isolatedIndex);
|
|
182
|
+
return await runTod(deps.exec, args, {
|
|
183
|
+
cwd: context.cwd,
|
|
184
|
+
signal,
|
|
185
|
+
timeoutMs: BUILD_RUN_TIMEOUT_MS,
|
|
186
|
+
maxOutputBytes: 24_000,
|
|
187
|
+
environment: { GIT_INDEX_FILE: isolatedIndex },
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
191
|
+
throw new TodError(
|
|
192
|
+
"local build mode requires an existing git index and at least one commit",
|
|
193
|
+
{ cause: error },
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
} finally {
|
|
198
|
+
await rm(directory, { recursive: true, force: true });
|
|
98
199
|
}
|
|
99
200
|
}
|
|
100
201
|
|
|
@@ -102,10 +203,10 @@ export function registerBuildTool(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
|
102
203
|
const parameters = Type.Object({
|
|
103
204
|
action: StringEnum(BUILD_ACTIONS, {
|
|
104
205
|
description:
|
|
105
|
-
"list, get, get_log, get_code_problems
|
|
206
|
+
"list, get, get_log, get_code_problems, get_unit_test_report, get_changes_since_success, run, check_spec (may upgrade .onedev-buildspec.yml), get_spec_schema, query_description",
|
|
106
207
|
}),
|
|
107
208
|
ref: Type.Optional(Type.String({ description: REF_DESCRIPTION })),
|
|
108
|
-
|
|
209
|
+
report_name: Type.Optional(
|
|
109
210
|
Type.String({ description: "Report name quoted in the build log" }),
|
|
110
211
|
),
|
|
111
212
|
severity: Type.Optional(StringEnum(SEVERITY_LEVELS)),
|
|
@@ -137,7 +238,7 @@ export function registerBuildTool(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
|
137
238
|
query: Type.Optional(Type.String({ description: QUERY_DESCRIPTION })),
|
|
138
239
|
count: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
|
|
139
240
|
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
140
|
-
});
|
|
241
|
+
}, { additionalProperties: false });
|
|
141
242
|
type Params = Static<typeof parameters>;
|
|
142
243
|
|
|
143
244
|
pi.registerTool({
|
|
@@ -145,19 +246,40 @@ export function registerBuildTool(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
|
145
246
|
label: "OneDev Builds",
|
|
146
247
|
description:
|
|
147
248
|
"Inspect OneDev CI/CD: query builds, read logs and code-problem reports, diff changes since the last success, validate the build spec, and run jobs (including against uncommitted local changes).",
|
|
148
|
-
promptGuidelines: [
|
|
149
|
-
"Use onedev_build get_log before guessing at build failures.",
|
|
150
|
-
"run with mode=local pushes uncommitted changes to a temporary server ref; prefer it over committing just to test.",
|
|
151
|
-
],
|
|
152
249
|
parameters,
|
|
153
|
-
async execute(_toolCallId, params: Params, signal) {
|
|
250
|
+
async execute(_toolCallId, params: Params, signal, _onUpdate, ctx) {
|
|
154
251
|
const isRun = params.action === "run";
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
signal
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
252
|
+
const args = buildBuildArgs(params);
|
|
253
|
+
if (params.action === "get_unit_test_report") {
|
|
254
|
+
await ensureUnitTestReportSupport(deps, signal);
|
|
255
|
+
}
|
|
256
|
+
const operation = BUILD_MUTATIONS[params.action];
|
|
257
|
+
if (operation) {
|
|
258
|
+
await confirmMutation(deps, ctx, {
|
|
259
|
+
operation,
|
|
260
|
+
title: `Approve OneDev build action: ${params.action}`,
|
|
261
|
+
details:
|
|
262
|
+
params.action === "check_spec"
|
|
263
|
+
? "TOD may upgrade .onedev-buildspec.yml in the working tree."
|
|
264
|
+
: `Job: ${params.job ?? "?"} · mode: ${params.mode ?? "local"}`,
|
|
265
|
+
signal,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// ponytail: build run streams the full log; preserve bounded metadata and the failure tail
|
|
269
|
+
const isLocalRun =
|
|
270
|
+
isRun &&
|
|
271
|
+
(params.mode === "local" ||
|
|
272
|
+
(params.mode === undefined && !params.branch && !params.tag));
|
|
273
|
+
let output: TodOutput;
|
|
274
|
+
if (isLocalRun) {
|
|
275
|
+
output = await runLocalBuildWithIsolatedIndex(deps, args, signal);
|
|
276
|
+
} else {
|
|
277
|
+
output = await runInContext(deps, args, {
|
|
278
|
+
signal,
|
|
279
|
+
timeoutMs: isRun ? BUILD_RUN_TIMEOUT_MS : undefined,
|
|
280
|
+
maxOutputBytes: isRun ? 24_000 : undefined,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
161
283
|
return toolResult(
|
|
162
284
|
withFooter(output.text, deps.context(), output.truncated),
|
|
163
285
|
{
|
package/src/tools/common.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type {
|
|
3
|
+
ContextRefreshOptions,
|
|
4
|
+
OneDevSessionContext,
|
|
5
|
+
} from "../context.js";
|
|
6
|
+
import type {
|
|
7
|
+
MutationApprovalManager,
|
|
8
|
+
MutationApprovalRequest,
|
|
9
|
+
} from "../mutation-approvals.js";
|
|
2
10
|
import {
|
|
3
11
|
TodError,
|
|
4
12
|
runTod,
|
|
@@ -10,6 +18,10 @@ export interface ToolDeps {
|
|
|
10
18
|
exec: CommandExecutor;
|
|
11
19
|
/** Session OneDev context; throws a friendly error before initialization. */
|
|
12
20
|
context: () => OneDevSessionContext;
|
|
21
|
+
refreshContext: (
|
|
22
|
+
options?: ContextRefreshOptions,
|
|
23
|
+
) => Promise<OneDevSessionContext>;
|
|
24
|
+
approvals: MutationApprovalManager;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
export const REF_DESCRIPTION =
|
|
@@ -43,6 +55,12 @@ export async function runInContext(
|
|
|
43
55
|
} = {},
|
|
44
56
|
): Promise<TodOutput> {
|
|
45
57
|
const context = deps.context();
|
|
58
|
+
if (context.status !== "ready" || !context.project) {
|
|
59
|
+
throw new TodError(
|
|
60
|
+
context.problem ??
|
|
61
|
+
"OneDev tools require a repository whose project can be inferred from a OneDev remote",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
46
64
|
return runTod(deps.exec, args, {
|
|
47
65
|
cwd: context.cwd,
|
|
48
66
|
signal: options.signal,
|
|
@@ -71,22 +89,19 @@ export function pushRepeated(
|
|
|
71
89
|
for (const value of values ?? []) args.push(flag, value);
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
export function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`action '${action}' changes remote state; re-run with confirm: true once the user has approved it`,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
92
|
+
export function confirmMutation(
|
|
93
|
+
deps: ToolDeps,
|
|
94
|
+
ctx: ExtensionContext,
|
|
95
|
+
request: MutationApprovalRequest,
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
return deps.approvals.confirm(deps.context(), ctx, request);
|
|
83
98
|
}
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
function contextFooter(context: OneDevSessionContext): string {
|
|
86
101
|
if (context.status === "ready" && context.project) {
|
|
87
102
|
return `Context: project ${context.project} · server ${context.serverUrl}`;
|
|
88
103
|
}
|
|
89
|
-
return `Context:
|
|
104
|
+
return `Context unavailable: ${context.problem ?? "no OneDev project inferred from git remotes"}`;
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
export function withFooter(
|
package/src/tools/index.ts
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import { formatContext } from "../context.js";
|
|
5
|
-
import {
|
|
4
|
+
import { formatContext, setupGuidance } from "../context.js";
|
|
5
|
+
import type { OneDevSourceWatchManager } from "../source-watch.js";
|
|
6
|
+
import { toolResult, type ToolDeps } from "./common.js";
|
|
6
7
|
import { registerBuildTool } from "./build.js";
|
|
7
8
|
import { registerIssueTool } from "./issue.js";
|
|
8
9
|
import { registerPullTool } from "./pull.js";
|
|
10
|
+
import { registerWatchTool } from "./watch.js";
|
|
9
11
|
|
|
10
|
-
const
|
|
11
|
-
type OneDevToolDomain = (typeof
|
|
12
|
+
const ONEDEV_TOOL_DOMAINS = ["issue", "pull", "build", "watch"] as const;
|
|
13
|
+
type OneDevToolDomain = (typeof ONEDEV_TOOL_DOMAINS)[number];
|
|
12
14
|
|
|
15
|
+
const ALWAYS_ACTIVE_TOOLS = ["onedev_context", "onedev_tools"] as const;
|
|
13
16
|
const LAZY_TOOL_NAMES = [
|
|
14
17
|
"onedev_issue",
|
|
15
18
|
"onedev_pull",
|
|
16
19
|
"onedev_build",
|
|
20
|
+
"onedev_watch",
|
|
17
21
|
] as const;
|
|
18
22
|
const LAZY_TOOLS = new Set<string>(LAZY_TOOL_NAMES);
|
|
19
23
|
const DOMAIN_TOOL_NAMES = {
|
|
20
24
|
issue: ["onedev_issue"],
|
|
21
25
|
pull: ["onedev_pull"],
|
|
22
26
|
build: ["onedev_build"],
|
|
27
|
+
watch: ["onedev_watch"],
|
|
23
28
|
} as const satisfies Record<OneDevToolDomain, readonly string[]>;
|
|
24
29
|
|
|
25
30
|
export interface OnedevToolController {
|
|
26
|
-
/**
|
|
31
|
+
/** Restore only the small context/loader surface at session boundaries. */
|
|
27
32
|
reset(): void;
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -34,93 +39,116 @@ function supportsDynamicTools(pi: ExtensionAPI): boolean {
|
|
|
34
39
|
);
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
function registerContextTool(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
43
|
+
pi.registerTool({
|
|
44
|
+
name: "onedev_context",
|
|
45
|
+
label: "OneDev Context",
|
|
46
|
+
description:
|
|
47
|
+
"Inspect OneDev readiness without loading domain tools. Use health for an authenticated re-check and setup for actionable configuration guidance.",
|
|
48
|
+
parameters: Type.Object(
|
|
49
|
+
{
|
|
50
|
+
action: StringEnum(["current", "health", "login_name", "setup"]),
|
|
51
|
+
},
|
|
52
|
+
{ additionalProperties: false },
|
|
53
|
+
),
|
|
54
|
+
async execute(_toolCallId, params, signal) {
|
|
55
|
+
if (params.action === "current") {
|
|
56
|
+
return toolResult(formatContext(deps.context()));
|
|
57
|
+
}
|
|
58
|
+
if (params.action === "setup") {
|
|
59
|
+
return toolResult(setupGuidance(deps.context()));
|
|
60
|
+
}
|
|
61
|
+
const context = await deps.refreshContext({
|
|
62
|
+
verifyAuth: true,
|
|
63
|
+
force: true,
|
|
64
|
+
signal,
|
|
65
|
+
});
|
|
66
|
+
if (params.action === "login_name" && context.status === "ready") {
|
|
67
|
+
return toolResult(context.loginName ?? "(no login name)");
|
|
68
|
+
}
|
|
69
|
+
return toolResult(formatContext(context));
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
37
74
|
export function registerOnedevTools(
|
|
38
75
|
pi: ExtensionAPI,
|
|
39
76
|
deps: ToolDeps,
|
|
77
|
+
watches: OneDevSourceWatchManager,
|
|
40
78
|
): OnedevToolController {
|
|
79
|
+
registerContextTool(pi, deps);
|
|
41
80
|
registerIssueTool(pi, deps);
|
|
42
81
|
registerPullTool(pi, deps);
|
|
43
82
|
registerBuildTool(pi, deps);
|
|
83
|
+
registerWatchTool(pi, watches);
|
|
44
84
|
|
|
45
85
|
pi.registerTool({
|
|
46
86
|
name: "onedev_tools",
|
|
47
87
|
label: "OneDev Tools",
|
|
48
88
|
description:
|
|
49
|
-
"Activate OneDev
|
|
89
|
+
"Activate only the OneDev domains needed for the current task. Returns authenticated server, project, and login context with the activation result.",
|
|
50
90
|
promptGuidelines: [
|
|
51
|
-
"Use onedev_tools before an unavailable OneDev operation.",
|
|
91
|
+
"Use onedev_tools before an unavailable OneDev domain operation.",
|
|
52
92
|
],
|
|
53
|
-
parameters: Type.Object(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}),
|
|
58
|
-
domains: Type.Optional(
|
|
59
|
-
Type.Array(StringEnum(ONEDev_TOOL_DOMAINS), {
|
|
93
|
+
parameters: Type.Object(
|
|
94
|
+
{
|
|
95
|
+
action: StringEnum(["activate"]),
|
|
96
|
+
domains: Type.Array(StringEnum(ONEDEV_TOOL_DOMAINS), {
|
|
60
97
|
minItems: 1,
|
|
61
|
-
maxItems:
|
|
98
|
+
maxItems: 4,
|
|
62
99
|
uniqueItems: true,
|
|
63
|
-
description: "issue, pull, build
|
|
100
|
+
description: "Activate issue, pull, build, and/or watch in one call",
|
|
64
101
|
}),
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
description:
|
|
69
|
-
"download: resource URL exactly as it appears in the markdown",
|
|
70
|
-
}),
|
|
71
|
-
),
|
|
72
|
-
outputFile: Type.Optional(
|
|
73
|
-
Type.String({
|
|
74
|
-
description: "download: local path to write the resource to",
|
|
75
|
-
}),
|
|
76
|
-
),
|
|
77
|
-
}),
|
|
102
|
+
},
|
|
103
|
+
{ additionalProperties: false },
|
|
104
|
+
),
|
|
78
105
|
async execute(_toolCallId, params, signal) {
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
const requested = params.domains ?? [...ONEDev_TOOL_DOMAINS];
|
|
82
|
-
if (!supportsDynamicTools(pi)) {
|
|
83
|
-
return toolResult(
|
|
84
|
-
"Dynamic OneDev tool activation is unavailable in this Pi version; registered tools remain unchanged.",
|
|
85
|
-
{ requested },
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
const selected = [
|
|
89
|
-
...new Set(requested.flatMap((domain) => DOMAIN_TOOL_NAMES[domain])),
|
|
90
|
-
];
|
|
91
|
-
const active = pi.getActiveTools();
|
|
92
|
-
const added = selected.filter((name) => !active.includes(name));
|
|
93
|
-
if (added.length > 0) pi.setActiveTools([...active, ...added]);
|
|
106
|
+
const context = await deps.refreshContext({ verifyAuth: true, signal });
|
|
107
|
+
if (context.status !== "ready") {
|
|
94
108
|
return toolResult(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
: `Requested OneDev tools already active: ${selected.join(", ")}`,
|
|
98
|
-
{ requested, added },
|
|
109
|
+
`${formatContext(context)}\nNo domain tools were activated. Use onedev_context setup for the next step.`,
|
|
110
|
+
{ requested: params.domains, added: [] },
|
|
99
111
|
);
|
|
100
112
|
}
|
|
101
|
-
if (
|
|
102
|
-
return toolResult(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return toolResult(output.text || "(no login name)");
|
|
107
|
-
}
|
|
108
|
-
if (!params.url || !params.outputFile) {
|
|
109
|
-
throw new Error("download requires url and output_file");
|
|
113
|
+
if (!supportsDynamicTools(pi)) {
|
|
114
|
+
return toolResult(
|
|
115
|
+
`Dynamic OneDev tool activation is unavailable in this Pi version.\n${formatContext(context)}`,
|
|
116
|
+
{ requested: params.domains, added: [] },
|
|
117
|
+
);
|
|
110
118
|
}
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
const selected = [
|
|
120
|
+
...new Set(
|
|
121
|
+
params.domains.flatMap((domain) => DOMAIN_TOOL_NAMES[domain]),
|
|
122
|
+
),
|
|
123
|
+
];
|
|
124
|
+
const active = pi.getActiveTools();
|
|
125
|
+
const added = selected.filter((name) => !active.includes(name));
|
|
126
|
+
if (added.length > 0) pi.setActiveTools([...active, ...added]);
|
|
127
|
+
const activation =
|
|
128
|
+
added.length > 0
|
|
129
|
+
? `Enabled OneDev tools: ${added.join(", ")}`
|
|
130
|
+
: `Requested OneDev tools already active: ${selected.join(", ")}`;
|
|
131
|
+
return toolResult(`${activation}\n${formatContext(context)}`, {
|
|
132
|
+
requested: params.domains,
|
|
133
|
+
added,
|
|
113
134
|
});
|
|
114
|
-
return toolResult(`Downloaded ${params.url} to ${params.outputFile}`);
|
|
115
135
|
},
|
|
116
136
|
});
|
|
117
137
|
|
|
118
138
|
return {
|
|
119
139
|
reset() {
|
|
120
140
|
if (!supportsDynamicTools(pi)) return;
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
const current = pi.getActiveTools();
|
|
142
|
+
const next = current.filter((name) => !LAZY_TOOLS.has(name));
|
|
143
|
+
for (const name of ALWAYS_ACTIVE_TOOLS) {
|
|
144
|
+
if (!next.includes(name)) next.push(name);
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
next.length !== current.length ||
|
|
148
|
+
next.some((name, index) => name !== current[index])
|
|
149
|
+
) {
|
|
150
|
+
pi.setActiveTools(next);
|
|
151
|
+
}
|
|
124
152
|
},
|
|
125
153
|
};
|
|
126
154
|
}
|