@shuind/dsh-codex-harness 0.1.7
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/LICENSE +21 -0
- package/README.md +100 -0
- package/README.zh.md +100 -0
- package/cordis.patch.yml +5 -0
- package/lib/index.js +843 -0
- package/lib/installer.js +59 -0
- package/lib/invariant.js +23 -0
- package/lib/types/exec.d.ts +43 -0
- package/lib/types/exec.js +251 -0
- package/lib/types/index.d.ts +28 -0
- package/lib/types/index.js +321 -0
- package/lib/types/installer.d.ts +23 -0
- package/lib/types/installer.js +64 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +22 -0
- package/lib/types/patch.d.ts +36 -0
- package/lib/types/patch.js +186 -0
- package/package.json +114 -0
- package/presets/codex/agent.cordis.yml +44 -0
- package/presets/codex/preset.yml +3 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
String.raw`start: begin_patch hunk+ end_patch
|
|
6
|
+
begin_patch: "*** Begin Patch" LF
|
|
7
|
+
end_patch: "*** End Patch" LF?
|
|
8
|
+
hunk: add_hunk | delete_hunk | update_hunk
|
|
9
|
+
add_hunk: "*** Add File: " filename LF add_line+
|
|
10
|
+
delete_hunk: "*** Delete File: " filename LF
|
|
11
|
+
update_hunk: "*** Update File: " filename LF change_move? change?
|
|
12
|
+
filename: /(.+)/
|
|
13
|
+
add_line: "+" /(.*)/ LF -> line
|
|
14
|
+
change_move: "*** Move to: " filename LF
|
|
15
|
+
change: (change_context | change_line)+ eof_line?
|
|
16
|
+
change_context: ("@@" | "@@ " /(.+)/) LF
|
|
17
|
+
change_line: ("+" | "-" | " ") /(.*)/ LF
|
|
18
|
+
eof_line: "*** End of File" LF
|
|
19
|
+
%import common.LF
|
|
20
|
+
`;
|
|
21
|
+
function invalid(message) {
|
|
22
|
+
throw new Error(`apply_patch: ${message}`);
|
|
23
|
+
}
|
|
24
|
+
function isFileHeader(line) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
return trimmed.startsWith("*** Add File: ") || trimmed.startsWith("*** Delete File: ") || trimmed.startsWith("*** Update File: ");
|
|
27
|
+
}
|
|
28
|
+
function pathFrom(line, prefix) {
|
|
29
|
+
const path = line.slice(prefix.length).trim();
|
|
30
|
+
if (path.length === 0) invalid(`${prefix.trim()} requires a file path`);
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
function isChangeMarker(line) {
|
|
34
|
+
const marker = line.trimEnd();
|
|
35
|
+
return marker === "@@" || marker.startsWith("@@ ");
|
|
36
|
+
}
|
|
37
|
+
function isPotentialChangeMarker(line) {
|
|
38
|
+
return line.trimEnd().startsWith("@@");
|
|
39
|
+
}
|
|
40
|
+
/** Parse one complete Codex patch after normalizing CRLF input to LF. */
|
|
41
|
+
function parsePatch(input) {
|
|
42
|
+
const lines = input.replaceAll("\r\n", "\n").trim().split("\n");
|
|
43
|
+
if (lines[0]?.trim() !== "*** Begin Patch") invalid("input must start with \"*** Begin Patch\"");
|
|
44
|
+
if (lines.at(-1)?.trim() !== "*** End Patch") invalid("input must end with \"*** End Patch\"");
|
|
45
|
+
const files = [];
|
|
46
|
+
let index = 1;
|
|
47
|
+
const end = lines.length - 1;
|
|
48
|
+
while (index < end) {
|
|
49
|
+
const header = lines[index++]?.trim();
|
|
50
|
+
if (header === void 0) invalid("unexpected end of input");
|
|
51
|
+
if (header.startsWith("*** Add File: ")) {
|
|
52
|
+
const path = pathFrom(header, "*** Add File: ");
|
|
53
|
+
const content = [];
|
|
54
|
+
while (index < end && lines[index]?.startsWith("+") === true) {
|
|
55
|
+
content.push(lines[index].slice(1));
|
|
56
|
+
index++;
|
|
57
|
+
}
|
|
58
|
+
if (content.length === 0) invalid(`add file ${JSON.stringify(path)} needs at least one content line`);
|
|
59
|
+
files.push({
|
|
60
|
+
kind: "add",
|
|
61
|
+
path,
|
|
62
|
+
content: `${content.join("\n")}\n`
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (header.startsWith("*** Delete File: ")) {
|
|
67
|
+
files.push({
|
|
68
|
+
kind: "delete",
|
|
69
|
+
path: pathFrom(header, "*** Delete File: ")
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!header.startsWith("*** Update File: ")) invalid(`unexpected directive ${JSON.stringify(header)}`);
|
|
74
|
+
const path = pathFrom(header, "*** Update File: ");
|
|
75
|
+
let moveTo;
|
|
76
|
+
if (lines[index]?.trim().startsWith("*** Move to: ") === true) {
|
|
77
|
+
moveTo = pathFrom(lines[index].trim(), "*** Move to: ");
|
|
78
|
+
index++;
|
|
79
|
+
}
|
|
80
|
+
const hunks = [];
|
|
81
|
+
while (index < end && !isFileHeader(lines[index])) {
|
|
82
|
+
const patchLines = [];
|
|
83
|
+
let context;
|
|
84
|
+
if (isPotentialChangeMarker(lines[index])) {
|
|
85
|
+
const marker = lines[index].trimEnd();
|
|
86
|
+
if (!isChangeMarker(marker)) invalid(`invalid update hunk marker ${JSON.stringify(lines[index])}`);
|
|
87
|
+
index++;
|
|
88
|
+
context = marker.length === 2 ? void 0 : marker.slice(3);
|
|
89
|
+
}
|
|
90
|
+
while (index < end && !isFileHeader(lines[index]) && !isPotentialChangeMarker(lines[index]) && lines[index].trimEnd() !== "*** End of File") {
|
|
91
|
+
const line = lines[index++];
|
|
92
|
+
const kind = line[0];
|
|
93
|
+
if (kind !== " " && kind !== "+" && kind !== "-") invalid(`unexpected update line ${JSON.stringify(line)}`);
|
|
94
|
+
patchLines.push({
|
|
95
|
+
kind: kind === " " ? "context" : kind === "+" ? "add" : "delete",
|
|
96
|
+
text: line.slice(1)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const endOfFile = lines[index]?.trimEnd() === "*** End of File";
|
|
100
|
+
if (endOfFile) index++;
|
|
101
|
+
if (patchLines.length === 0) {
|
|
102
|
+
if (context !== void 0 || endOfFile) invalid("an update hunk needs context or changed lines");
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
hunks.push({
|
|
106
|
+
...context === void 0 ? {} : { context },
|
|
107
|
+
lines: patchLines,
|
|
108
|
+
endOfFile
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (hunks.length === 0 && moveTo === void 0) invalid(`update file ${JSON.stringify(path)} has no changes`);
|
|
112
|
+
files.push({
|
|
113
|
+
kind: "update",
|
|
114
|
+
path,
|
|
115
|
+
...moveTo === void 0 ? {} : { moveTo },
|
|
116
|
+
hunks
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (files.length === 0) invalid("no files were modified");
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
122
|
+
function splitText(text) {
|
|
123
|
+
if (text.length === 0) return {
|
|
124
|
+
lines: [],
|
|
125
|
+
trailingNewline: false
|
|
126
|
+
};
|
|
127
|
+
const trailingNewline = text.endsWith("\n");
|
|
128
|
+
const lines = text.split("\n");
|
|
129
|
+
if (trailingNewline) lines.pop();
|
|
130
|
+
return {
|
|
131
|
+
lines,
|
|
132
|
+
trailingNewline
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function joinText(value) {
|
|
136
|
+
const body = value.lines.join("\n");
|
|
137
|
+
return value.trailingNewline ? `${body}\n` : body;
|
|
138
|
+
}
|
|
139
|
+
function findSequence(lines, expected, from, endOfFile) {
|
|
140
|
+
if (expected.length === 0) return Math.min(from, lines.length);
|
|
141
|
+
if (expected.length > lines.length) return -1;
|
|
142
|
+
const first = endOfFile ? Math.max(from, lines.length - expected.length) : from;
|
|
143
|
+
const last = lines.length - expected.length;
|
|
144
|
+
const matchers = [
|
|
145
|
+
(actual, wanted) => actual === wanted,
|
|
146
|
+
(actual, wanted) => actual.trimEnd() === wanted.trimEnd(),
|
|
147
|
+
(actual, wanted) => actual.trim() === wanted.trim()
|
|
148
|
+
];
|
|
149
|
+
for (const matchesLine of matchers) for (let index = first; index <= last; index++) {
|
|
150
|
+
let matches = true;
|
|
151
|
+
for (let offset = 0; offset < expected.length; offset++) if (!matchesLine(lines[index + offset], expected[offset])) {
|
|
152
|
+
matches = false;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
if (matches) return index;
|
|
156
|
+
}
|
|
157
|
+
return -1;
|
|
158
|
+
}
|
|
159
|
+
/** Apply parsed update hunks and return LF-normalized text. */
|
|
160
|
+
function applyPatchHunks(original, hunks) {
|
|
161
|
+
const value = splitText(original.replaceAll("\r\n", "\n"));
|
|
162
|
+
let cursor = 0;
|
|
163
|
+
for (const hunk of hunks) {
|
|
164
|
+
const expected = hunk.lines.filter((line) => line.kind !== "add").map((line) => line.text);
|
|
165
|
+
const start = findSequence(value.lines, expected, cursor, hunk.endOfFile);
|
|
166
|
+
if (start < 0) {
|
|
167
|
+
const detail = expected.join("\n");
|
|
168
|
+
invalid(`could not find expected lines${detail.length === 0 ? "" : `:\n${detail}`}`);
|
|
169
|
+
}
|
|
170
|
+
const replacement = hunk.lines.filter((line) => line.kind !== "delete").map((line) => line.text);
|
|
171
|
+
value.lines.splice(start, expected.length, ...replacement);
|
|
172
|
+
cursor = start + replacement.length;
|
|
173
|
+
value.trailingNewline = !hunk.endOfFile;
|
|
174
|
+
}
|
|
175
|
+
return joinText(value);
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region lib/types/exec.js
|
|
179
|
+
/** Codex `exec_command` and `write_stdin` execution over dsh capability seams. */
|
|
180
|
+
const STATES = /* @__PURE__ */ new WeakMap();
|
|
181
|
+
function stateFor(agent) {
|
|
182
|
+
const current = STATES.get(agent);
|
|
183
|
+
if (current !== void 0) return current;
|
|
184
|
+
const created = {
|
|
185
|
+
nextId: 0,
|
|
186
|
+
sessions: /* @__PURE__ */ new Map()
|
|
187
|
+
};
|
|
188
|
+
STATES.set(agent, created);
|
|
189
|
+
return created;
|
|
190
|
+
}
|
|
191
|
+
function positiveFinite(name, value) {
|
|
192
|
+
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new Error(`${name} must be a non-negative finite number`);
|
|
193
|
+
}
|
|
194
|
+
function waitMs(value, fallback) {
|
|
195
|
+
return Math.max(0, Math.min(3e4, Math.trunc(value ?? fallback)));
|
|
196
|
+
}
|
|
197
|
+
function outputLimit(maxOutputBytes, maxOutputTokens) {
|
|
198
|
+
if (maxOutputTokens === void 0) return maxOutputBytes;
|
|
199
|
+
positiveFinite("max_output_tokens", maxOutputTokens);
|
|
200
|
+
return Math.max(1, Math.min(maxOutputBytes, Math.trunc(maxOutputTokens * 4)));
|
|
201
|
+
}
|
|
202
|
+
function limitOutput(text, maxBytes) {
|
|
203
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
204
|
+
let end = Math.min(text.length, maxBytes);
|
|
205
|
+
while (end > 0 && Buffer.byteLength(text.slice(0, end), "utf8") > maxBytes) end--;
|
|
206
|
+
return `${text.slice(0, end)}\n[output truncated]`;
|
|
207
|
+
}
|
|
208
|
+
function newChunkId() {
|
|
209
|
+
return randomBytes(3).toString("hex");
|
|
210
|
+
}
|
|
211
|
+
function withChunkId(result) {
|
|
212
|
+
return {
|
|
213
|
+
chunk_id: newChunkId(),
|
|
214
|
+
...result
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Render the ordinary Responses tool result text used by Codex's unified exec tools. */
|
|
218
|
+
function renderExecResult(result) {
|
|
219
|
+
const sections = [];
|
|
220
|
+
if (result.chunk_id !== void 0) sections.push(`Chunk ID: ${result.chunk_id}`);
|
|
221
|
+
sections.push(`Wall time: ${result.wall_time_seconds.toFixed(4)} seconds`);
|
|
222
|
+
if (result.exit_code !== void 0) sections.push(`Process exited with code ${result.exit_code}`);
|
|
223
|
+
if (result.session_id !== void 0) sections.push(`Process running with session ID ${result.session_id}`);
|
|
224
|
+
if (result.original_token_count !== void 0) sections.push(`Original token count: ${result.original_token_count}`);
|
|
225
|
+
sections.push("Output:", result.output);
|
|
226
|
+
return sections.join("\n");
|
|
227
|
+
}
|
|
228
|
+
function sessionCwd$1(exec, workdir) {
|
|
229
|
+
const base = exec.agent?.session.header.cwd ?? process.cwd();
|
|
230
|
+
if (workdir === void 0) return exec.agent?.session.header.cwd;
|
|
231
|
+
return resolve(base, workdir);
|
|
232
|
+
}
|
|
233
|
+
function readShellOutput(read, maxBytes) {
|
|
234
|
+
return limitOutput(read.delta, maxBytes);
|
|
235
|
+
}
|
|
236
|
+
function terminalResult(result, maxBytes, startedAt) {
|
|
237
|
+
const output = limitOutput(result.viewport, maxBytes);
|
|
238
|
+
return withChunkId({
|
|
239
|
+
wall_time_seconds: (performance.now() - startedAt) / 1e3,
|
|
240
|
+
output,
|
|
241
|
+
...result.sessionStatus.kind === "exited" ? { ...result.sessionStatus.exitCode === null ? {} : { exit_code: result.sessionStatus.exitCode } } : {}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function sleep(ms, signal) {
|
|
245
|
+
return new Promise((resolve) => {
|
|
246
|
+
let timer;
|
|
247
|
+
const finish = (result) => {
|
|
248
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
249
|
+
signal.removeEventListener("abort", onAbort);
|
|
250
|
+
resolve(result);
|
|
251
|
+
};
|
|
252
|
+
const onAbort = () => finish("aborted");
|
|
253
|
+
if (signal.aborted) {
|
|
254
|
+
finish("aborted");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
258
|
+
timer = setTimeout(() => finish("elapsed"), ms);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async function waitForShell(process, ms, signal) {
|
|
262
|
+
const timer = sleep(ms, signal);
|
|
263
|
+
const completed = await Promise.race([process.done.then(() => true), timer.then((result) => result === "aborted" ? false : void 0)]);
|
|
264
|
+
if (signal.aborted) signal.throwIfAborted();
|
|
265
|
+
return completed === true || process.status !== "running";
|
|
266
|
+
}
|
|
267
|
+
function allocateSession(agent, session) {
|
|
268
|
+
const state = stateFor(agent);
|
|
269
|
+
const id = ++state.nextId;
|
|
270
|
+
state.sessions.set(id, session);
|
|
271
|
+
return id;
|
|
272
|
+
}
|
|
273
|
+
function storedSession(agent, id) {
|
|
274
|
+
const session = stateFor(agent).sessions.get(id);
|
|
275
|
+
if (session === void 0) throw new Error(`unknown unified exec session ${id}`);
|
|
276
|
+
return session;
|
|
277
|
+
}
|
|
278
|
+
function forgetSession(agent, id) {
|
|
279
|
+
stateFor(agent).sessions.delete(id);
|
|
280
|
+
}
|
|
281
|
+
function commandFor(args) {
|
|
282
|
+
return args.cmd;
|
|
283
|
+
}
|
|
284
|
+
/** Execute one Codex command through the configured pipe or PTY capability. */
|
|
285
|
+
async function runExecCommand(ctx, args, exec, config) {
|
|
286
|
+
if (args.cmd.trim().length === 0) throw new Error("cmd must be a non-empty string");
|
|
287
|
+
positiveFinite("yield_time_ms", args.yield_time_ms);
|
|
288
|
+
const maxBytes = outputLimit(config.maxOutputBytes, args.max_output_tokens);
|
|
289
|
+
const workdir = sessionCwd$1(exec, args.workdir);
|
|
290
|
+
const startedAt = performance.now();
|
|
291
|
+
if (args.tty === true) {
|
|
292
|
+
const agent = exec.agent;
|
|
293
|
+
const terminals = ctx.get("terminals");
|
|
294
|
+
if (agent === void 0 || terminals === void 0) throw new Error("exec_command with tty=true requires the dsh terminal capability and an owning agent session");
|
|
295
|
+
const spawnRequest = {
|
|
296
|
+
type: "shell",
|
|
297
|
+
...args.shell === void 0 ? {} : { shell: args.shell },
|
|
298
|
+
login: args.login ?? true,
|
|
299
|
+
...workdir === void 0 ? {} : { cwd: workdir }
|
|
300
|
+
};
|
|
301
|
+
const spawned = await terminals.spawn(agent, spawnRequest, exec.signal);
|
|
302
|
+
const sendRequest = {
|
|
303
|
+
text: commandFor(args),
|
|
304
|
+
submit: true,
|
|
305
|
+
waitMs: waitMs(args.yield_time_ms, config.defaultYieldTimeMs),
|
|
306
|
+
signal: exec.signal
|
|
307
|
+
};
|
|
308
|
+
const result = await terminals.startSend(agent, spawned.sessionId, sendRequest).done;
|
|
309
|
+
const output = terminalResult(result, maxBytes, startedAt);
|
|
310
|
+
if (result.sessionStatus.kind === "running") output.session_id = allocateSession(agent, {
|
|
311
|
+
kind: "terminal",
|
|
312
|
+
owner: agent,
|
|
313
|
+
id: spawned.sessionId
|
|
314
|
+
});
|
|
315
|
+
else await terminals.kill(agent, spawned.sessionId, "Codex command exited");
|
|
316
|
+
return output;
|
|
317
|
+
}
|
|
318
|
+
const policy = ctx.get("sandboxPolicy")?.resolve(exec.agent === void 0 ? {} : { session: exec.agent.session });
|
|
319
|
+
const dshEnv = ctx.get("shellEnv")?.collect(exec);
|
|
320
|
+
const shellRequest = {
|
|
321
|
+
command: commandFor(args),
|
|
322
|
+
...args.shell === void 0 ? {} : { shell: args.shell },
|
|
323
|
+
login: args.login ?? true,
|
|
324
|
+
...workdir === void 0 ? {} : { workdir },
|
|
325
|
+
stdoutMaxBytes: maxBytes,
|
|
326
|
+
...dshEnv === void 0 ? {} : { dshEnv },
|
|
327
|
+
...policy === void 0 ? {} : { sandboxPolicy: policy }
|
|
328
|
+
};
|
|
329
|
+
const process = ctx.shell.start(ctx.shell.resolve(shellRequest));
|
|
330
|
+
const completed = await waitForShell(process, waitMs(args.yield_time_ms, config.defaultYieldTimeMs), exec.signal);
|
|
331
|
+
const output = readShellOutput(process.readOutput(), maxBytes);
|
|
332
|
+
if (!completed || process.status === "running") {
|
|
333
|
+
if (exec.agent === void 0) {
|
|
334
|
+
process.kill();
|
|
335
|
+
await process.done;
|
|
336
|
+
return withChunkId({
|
|
337
|
+
wall_time_seconds: (performance.now() - startedAt) / 1e3,
|
|
338
|
+
output: readShellOutput(process.readOutput(), maxBytes)
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return withChunkId({
|
|
342
|
+
wall_time_seconds: (performance.now() - startedAt) / 1e3,
|
|
343
|
+
output,
|
|
344
|
+
session_id: allocateSession(exec.agent, {
|
|
345
|
+
kind: "shell",
|
|
346
|
+
process
|
|
347
|
+
})
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return withChunkId({
|
|
351
|
+
wall_time_seconds: (performance.now() - startedAt) / 1e3,
|
|
352
|
+
output,
|
|
353
|
+
...process.exitCode === null ? {} : { exit_code: process.exitCode }
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/** Poll or write to one session returned by {@link runExecCommand}. */
|
|
357
|
+
async function runWriteStdin(ctx, args, exec, config) {
|
|
358
|
+
positiveFinite("yield_time_ms", args.yield_time_ms);
|
|
359
|
+
if (!Number.isSafeInteger(args.session_id) || args.session_id <= 0) throw new Error("session_id must be a positive integer");
|
|
360
|
+
const agent = exec.agent;
|
|
361
|
+
if (agent === void 0) throw new Error("write_stdin requires an owning agent session");
|
|
362
|
+
const session = storedSession(agent, args.session_id);
|
|
363
|
+
const maxBytes = outputLimit(config.maxOutputBytes, args.max_output_tokens);
|
|
364
|
+
const startedAt = performance.now();
|
|
365
|
+
const chars = args.chars ?? "";
|
|
366
|
+
if (session.kind === "shell") {
|
|
367
|
+
if (chars.length > 0) throw new Error("this unified exec session uses pipes and does not accept stdin; rerun exec_command with tty=true");
|
|
368
|
+
const completed = await waitForShell(session.process, waitMs(args.yield_time_ms, config.pollYieldTimeMs), exec.signal);
|
|
369
|
+
const output = readShellOutput(session.process.readOutput(), maxBytes);
|
|
370
|
+
if (completed && session.process.status !== "running") {
|
|
371
|
+
forgetSession(agent, args.session_id);
|
|
372
|
+
return withChunkId({
|
|
373
|
+
wall_time_seconds: (performance.now() - startedAt) / 1e3,
|
|
374
|
+
output,
|
|
375
|
+
...session.process.exitCode === null ? {} : { exit_code: session.process.exitCode }
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return withChunkId({
|
|
379
|
+
wall_time_seconds: (performance.now() - startedAt) / 1e3,
|
|
380
|
+
output,
|
|
381
|
+
session_id: args.session_id
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const terminals = ctx.get("terminals");
|
|
385
|
+
if (terminals === void 0) throw new Error("the dsh terminal capability is no longer available");
|
|
386
|
+
const sendRequest = {
|
|
387
|
+
text: chars,
|
|
388
|
+
submit: false,
|
|
389
|
+
waitMs: waitMs(args.yield_time_ms, chars.length > 0 ? config.writeYieldTimeMs : config.pollYieldTimeMs),
|
|
390
|
+
signal: exec.signal
|
|
391
|
+
};
|
|
392
|
+
const result = await terminals.startSend(agent, session.id, sendRequest).done;
|
|
393
|
+
const output = terminalResult(result, maxBytes, startedAt);
|
|
394
|
+
if (result.sessionStatus.kind === "running") output.session_id = args.session_id;
|
|
395
|
+
else {
|
|
396
|
+
forgetSession(agent, args.session_id);
|
|
397
|
+
await terminals.kill(agent, session.id, "Codex command exited");
|
|
398
|
+
}
|
|
399
|
+
return output;
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region lib/types/index.js
|
|
403
|
+
/** Codex-compatible prompt overlay and core tools for a dsh agent preset. */
|
|
404
|
+
const name = "codex";
|
|
405
|
+
const inject = [
|
|
406
|
+
"tools",
|
|
407
|
+
"systemPrompt",
|
|
408
|
+
"shell",
|
|
409
|
+
"fs"
|
|
410
|
+
];
|
|
411
|
+
/** Runtime configuration schema for the Codex tool bridge. */
|
|
412
|
+
const Config = z.object({
|
|
413
|
+
defaultYieldTimeMs: z.number().step(1).min(0).default(1e4),
|
|
414
|
+
pollYieldTimeMs: z.number().step(1).min(0).default(5e3),
|
|
415
|
+
writeYieldTimeMs: z.number().step(1).min(0).default(250),
|
|
416
|
+
maxOutputBytes: z.number().step(1).min(1).default(64e3)
|
|
417
|
+
});
|
|
418
|
+
const CODEX_BASE_PROMPT = String.raw`You are Codex, based on {{model}}. You are running as a coding agent in dsh Web on a user's computer.
|
|
419
|
+
|
|
420
|
+
## General
|
|
421
|
+
|
|
422
|
+
- When searching for text or files, prefer using rg or rg --files respectively because rg is much faster than alternatives like grep. If rg is not available, use the next best alternative.
|
|
423
|
+
|
|
424
|
+
## Editing constraints
|
|
425
|
+
|
|
426
|
+
- Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them.
|
|
427
|
+
- Add succinct code comments that explain non-obvious code. Do not add comments that merely narrate assignments or control flow.
|
|
428
|
+
- Use apply_patch for single-file edits when practical. The apply_patch tool accepts its freeform patch language; do not wrap that patch in JSON.
|
|
429
|
+
- You may be in a dirty git worktree. Never revert existing changes you did not make unless the user explicitly requests it. If unrelated files are changed, leave them alone.
|
|
430
|
+
|
|
431
|
+
## Planning
|
|
432
|
+
|
|
433
|
+
- Use update_plan for work with multiple meaningful steps. Keep the plan current as the task progresses.
|
|
434
|
+
- Do not use a plan for a trivial one-step request.
|
|
435
|
+
|
|
436
|
+
## dsh session
|
|
437
|
+
|
|
438
|
+
- The user and you share one workspace. Inspect the repository and every applicable AGENTS.md before editing.
|
|
439
|
+
- This session's preset was selected when the session was created and stays fixed for its lifetime. Do not attempt to switch the preset or replace its tool catalog while the session is running.
|
|
440
|
+
- dsh provides the execution, filesystem, session, policy, and Skills capabilities behind these tools. Use those extension points as supplied; do not invent a second harness or bypass the filesystem service for file edits.
|
|
441
|
+
- The core Codex tool names, arguments, and result formats are fixed: use exec_command for terminal work, write_stdin for an existing interactive command, apply_patch for file changes, and update_plan for multi-step tasks.
|
|
442
|
+
|
|
443
|
+
## Task execution
|
|
444
|
+
|
|
445
|
+
- Keep the user informed with concise progress updates and lead with the result.
|
|
446
|
+
- Prefer existing functions and extension points over new machinery.
|
|
447
|
+
- Do not claim that a command, edit, or test succeeded unless it actually succeeded.
|
|
448
|
+
- Use the exact tool names and argument formats supplied by this session; do not invent replacement editing tools.
|
|
449
|
+
|
|
450
|
+
## Presenting your work
|
|
451
|
+
|
|
452
|
+
- Be concise, direct, friendly, and actionable.
|
|
453
|
+
- For substantial work, explain what changed and why, then mention relevant verification and next steps.
|
|
454
|
+
- Do not dump large files into the conversation; refer to their paths.
|
|
455
|
+
- Use plain text with short sections only when they improve scanability.
|
|
456
|
+
`;
|
|
457
|
+
const EXEC_COMMAND_DESCRIPTION = "Runs a command in a PTY, returning output or a session ID for ongoing interaction.";
|
|
458
|
+
const WRITE_STDIN_DESCRIPTION = "Writes characters to an existing unified exec session and returns recent output.";
|
|
459
|
+
const APPLY_PATCH_DESCRIPTION = "The `apply_patch` tool can be used to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.";
|
|
460
|
+
const UPDATE_PLAN_DESCRIPTION = "Updates the task plan.\nProvide an optional explanation and a list of plan items, each with a step and status.\nAt most one step can be in_progress at a time.";
|
|
461
|
+
const PLAN_STATUSES = [
|
|
462
|
+
"pending",
|
|
463
|
+
"in_progress",
|
|
464
|
+
"completed"
|
|
465
|
+
];
|
|
466
|
+
function sessionCwd(exec) {
|
|
467
|
+
return exec.agent?.session.header.cwd;
|
|
468
|
+
}
|
|
469
|
+
function resolvePolicy(ctx, exec) {
|
|
470
|
+
return ctx.get("sandboxPolicy")?.resolve(exec.agent === void 0 ? {} : { session: exec.agent.session });
|
|
471
|
+
}
|
|
472
|
+
async function resolveTarget(ctx, path, exec) {
|
|
473
|
+
const cwd = sessionCwd(exec);
|
|
474
|
+
return ctx.fs.resolve(path, cwd === void 0 ? { signal: exec.signal } : {
|
|
475
|
+
cwd,
|
|
476
|
+
signal: exec.signal
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
async function observedTarget(ctx, target, exec) {
|
|
480
|
+
const info = await ctx.fs.stat(target, exec.signal);
|
|
481
|
+
if (info === void 0) throw new Error(`apply_patch: file not found: ${target.displayPath}`);
|
|
482
|
+
if (info.type !== "file") throw new Error(`apply_patch: not a regular file: ${target.displayPath}`);
|
|
483
|
+
ctx.emit("fs/observed", target, {
|
|
484
|
+
kind: "present",
|
|
485
|
+
version: info.version
|
|
486
|
+
}, exec);
|
|
487
|
+
return info;
|
|
488
|
+
}
|
|
489
|
+
async function writePatchedFile(ctx, target, content, fallback, exec, policy) {
|
|
490
|
+
const intent = await ctx.waterfall("fs/write-intent", target, exec, () => fallback);
|
|
491
|
+
const outcome = await ctx.fs.writeText(target, content, intent, exec.signal, policy);
|
|
492
|
+
ctx.emit("fs/observed", target, {
|
|
493
|
+
kind: "present",
|
|
494
|
+
version: outcome.version
|
|
495
|
+
}, exec);
|
|
496
|
+
return outcome.operation === "create" ? "created" : "updated";
|
|
497
|
+
}
|
|
498
|
+
async function deletePatchedFile(ctx, target, version, exec, policy) {
|
|
499
|
+
const remove = ctx.fs.remove;
|
|
500
|
+
if (typeof remove !== "function") throw new Error("apply_patch: the configured dsh filesystem does not support file deletion");
|
|
501
|
+
await remove.call(ctx.fs, target, { version }, exec.signal, policy);
|
|
502
|
+
ctx.emit("fs/observed", target, { kind: "absent" }, exec);
|
|
503
|
+
}
|
|
504
|
+
async function applyOnePatch(ctx, file, exec, policy) {
|
|
505
|
+
const target = await resolveTarget(ctx, file.path, exec);
|
|
506
|
+
if (file.kind === "add") {
|
|
507
|
+
if (await ctx.fs.stat(target, exec.signal) !== void 0) throw new Error(`apply_patch: file already exists: ${target.displayPath}`);
|
|
508
|
+
ctx.emit("fs/observed", target, { kind: "absent" }, exec);
|
|
509
|
+
await writePatchedFile(ctx, target, file.content, { kind: "createIfAbsent" }, exec, policy);
|
|
510
|
+
return {
|
|
511
|
+
path: file.path,
|
|
512
|
+
operation: "created"
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const sourceInfo = await observedTarget(ctx, target, exec);
|
|
516
|
+
const original = await ctx.fs.readText(target, exec.signal);
|
|
517
|
+
const updated = file.kind === "delete" ? void 0 : applyPatchHunks(original, file.hunks);
|
|
518
|
+
if (file.kind === "delete") {
|
|
519
|
+
await deletePatchedFile(ctx, target, sourceInfo.version, exec, policy);
|
|
520
|
+
return {
|
|
521
|
+
path: file.path,
|
|
522
|
+
operation: "deleted"
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (file.moveTo === void 0) {
|
|
526
|
+
await writePatchedFile(ctx, target, updated, {
|
|
527
|
+
kind: "replaceIfVersion",
|
|
528
|
+
version: sourceInfo.version
|
|
529
|
+
}, exec, policy);
|
|
530
|
+
return {
|
|
531
|
+
path: file.path,
|
|
532
|
+
operation: "updated"
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
const destination = await resolveTarget(ctx, file.moveTo, exec);
|
|
536
|
+
if (destination.targetKey === target.targetKey) {
|
|
537
|
+
await writePatchedFile(ctx, target, updated, {
|
|
538
|
+
kind: "replaceIfVersion",
|
|
539
|
+
version: sourceInfo.version
|
|
540
|
+
}, exec, policy);
|
|
541
|
+
return {
|
|
542
|
+
path: file.path,
|
|
543
|
+
operation: "updated",
|
|
544
|
+
moveTo: file.moveTo
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if (await ctx.fs.stat(destination, exec.signal) !== void 0) throw new Error(`apply_patch: move destination already exists: ${destination.displayPath}`);
|
|
548
|
+
ctx.emit("fs/observed", destination, { kind: "absent" }, exec);
|
|
549
|
+
await writePatchedFile(ctx, destination, updated, { kind: "createIfAbsent" }, exec, policy);
|
|
550
|
+
await deletePatchedFile(ctx, target, sourceInfo.version, exec, policy);
|
|
551
|
+
return {
|
|
552
|
+
path: file.path,
|
|
553
|
+
operation: "moved",
|
|
554
|
+
moveTo: file.moveTo
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function patchSummary(value) {
|
|
558
|
+
const letter = (operation) => {
|
|
559
|
+
switch (operation) {
|
|
560
|
+
case "created": return "A";
|
|
561
|
+
case "updated": return "M";
|
|
562
|
+
case "deleted": return "D";
|
|
563
|
+
case "moved": return "M";
|
|
564
|
+
default: return operation;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
return `Success. Updated the following files:\n${value.files.map((file) => `${letter(file.operation)} ${file.operation === "moved" ? file.moveTo : file.path}`).join("\n")}\n`;
|
|
568
|
+
}
|
|
569
|
+
function planTodos(args) {
|
|
570
|
+
const seen = /* @__PURE__ */ new Set();
|
|
571
|
+
let active = 0;
|
|
572
|
+
const todos = [];
|
|
573
|
+
for (const item of args.plan) {
|
|
574
|
+
const content = item.step.trim();
|
|
575
|
+
if (content.length === 0) throw new Error("update_plan: every step must be non-empty");
|
|
576
|
+
if (seen.has(content)) throw new Error(`update_plan: duplicate step ${JSON.stringify(content)}`);
|
|
577
|
+
seen.add(content);
|
|
578
|
+
if (item.status === "in_progress") active++;
|
|
579
|
+
todos.push({
|
|
580
|
+
content,
|
|
581
|
+
status: item.status
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (active > 1) throw new Error("update_plan: at most one step may be in_progress");
|
|
585
|
+
return todos;
|
|
586
|
+
}
|
|
587
|
+
function registerExecTools(ctx, config) {
|
|
588
|
+
ctx.tools.register(defineTool({
|
|
589
|
+
name: "exec_command",
|
|
590
|
+
description: EXEC_COMMAND_DESCRIPTION,
|
|
591
|
+
parameters: {
|
|
592
|
+
cmd: {
|
|
593
|
+
type: "string",
|
|
594
|
+
required: true,
|
|
595
|
+
description: "Shell command to execute."
|
|
596
|
+
},
|
|
597
|
+
workdir: {
|
|
598
|
+
type: "string",
|
|
599
|
+
description: "Working directory for the command. Defaults to the turn cwd."
|
|
600
|
+
},
|
|
601
|
+
tty: {
|
|
602
|
+
type: "boolean",
|
|
603
|
+
description: "True allocates a PTY for the command; false or omitted uses plain pipes."
|
|
604
|
+
},
|
|
605
|
+
yield_time_ms: {
|
|
606
|
+
type: "number",
|
|
607
|
+
description: "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms."
|
|
608
|
+
},
|
|
609
|
+
max_output_tokens: {
|
|
610
|
+
type: "number",
|
|
611
|
+
description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy."
|
|
612
|
+
},
|
|
613
|
+
shell: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: "Shell binary to launch. Defaults to the user's default shell."
|
|
616
|
+
},
|
|
617
|
+
login: {
|
|
618
|
+
type: "boolean",
|
|
619
|
+
description: "True runs the shell with -l/-i semantics; false disables them. Defaults to true."
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
output: {
|
|
623
|
+
schema: {
|
|
624
|
+
type: "object",
|
|
625
|
+
additionalProperties: false,
|
|
626
|
+
properties: {
|
|
627
|
+
chunk_id: { type: "string" },
|
|
628
|
+
wall_time_seconds: {
|
|
629
|
+
type: "number",
|
|
630
|
+
required: true
|
|
631
|
+
},
|
|
632
|
+
exit_code: { type: "number" },
|
|
633
|
+
session_id: { type: "number" },
|
|
634
|
+
original_token_count: { type: "number" },
|
|
635
|
+
output: {
|
|
636
|
+
type: "string",
|
|
637
|
+
required: true
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
render: (_args, value) => [{
|
|
642
|
+
type: "text",
|
|
643
|
+
text: renderExecResult(value)
|
|
644
|
+
}]
|
|
645
|
+
},
|
|
646
|
+
async execute(args, exec) {
|
|
647
|
+
return runExecCommand(ctx, args, exec, config);
|
|
648
|
+
},
|
|
649
|
+
presentCall: (args) => ({
|
|
650
|
+
card: "terminal",
|
|
651
|
+
title: args.cmd,
|
|
652
|
+
...args.workdir === void 0 ? {} : { cwd: args.workdir }
|
|
653
|
+
})
|
|
654
|
+
}));
|
|
655
|
+
ctx.tools.register(defineTool({
|
|
656
|
+
name: "write_stdin",
|
|
657
|
+
description: WRITE_STDIN_DESCRIPTION,
|
|
658
|
+
parameters: {
|
|
659
|
+
session_id: {
|
|
660
|
+
type: "number",
|
|
661
|
+
required: true,
|
|
662
|
+
description: "Identifier of the running unified exec session."
|
|
663
|
+
},
|
|
664
|
+
chars: {
|
|
665
|
+
type: "string",
|
|
666
|
+
description: "Bytes to write to stdin. Defaults to empty, which polls without writing."
|
|
667
|
+
},
|
|
668
|
+
yield_time_ms: {
|
|
669
|
+
type: "number",
|
|
670
|
+
description: "Wait before yielding output. Non-empty writes default to 250 ms and cap at 30000 ms; empty polls wait 5000-300000 ms by default."
|
|
671
|
+
},
|
|
672
|
+
max_output_tokens: {
|
|
673
|
+
type: "number",
|
|
674
|
+
description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy."
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
output: {
|
|
678
|
+
schema: {
|
|
679
|
+
type: "object",
|
|
680
|
+
additionalProperties: false,
|
|
681
|
+
properties: {
|
|
682
|
+
chunk_id: { type: "string" },
|
|
683
|
+
wall_time_seconds: {
|
|
684
|
+
type: "number",
|
|
685
|
+
required: true
|
|
686
|
+
},
|
|
687
|
+
exit_code: { type: "number" },
|
|
688
|
+
session_id: { type: "number" },
|
|
689
|
+
original_token_count: { type: "number" },
|
|
690
|
+
output: {
|
|
691
|
+
type: "string",
|
|
692
|
+
required: true
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
render: (_args, value) => [{
|
|
697
|
+
type: "text",
|
|
698
|
+
text: renderExecResult(value)
|
|
699
|
+
}]
|
|
700
|
+
},
|
|
701
|
+
async execute(args, exec) {
|
|
702
|
+
return runWriteStdin(ctx, args, exec, config);
|
|
703
|
+
}
|
|
704
|
+
}));
|
|
705
|
+
}
|
|
706
|
+
function registerPatchTool(ctx) {
|
|
707
|
+
ctx.tools.register(defineTool({
|
|
708
|
+
name: "apply_patch",
|
|
709
|
+
description: APPLY_PATCH_DESCRIPTION,
|
|
710
|
+
parameters: { input: {
|
|
711
|
+
type: "string",
|
|
712
|
+
required: true,
|
|
713
|
+
description: "The complete patch text."
|
|
714
|
+
} },
|
|
715
|
+
output: {
|
|
716
|
+
schema: {
|
|
717
|
+
type: "object",
|
|
718
|
+
additionalProperties: false,
|
|
719
|
+
properties: { files: {
|
|
720
|
+
type: "array",
|
|
721
|
+
required: true,
|
|
722
|
+
items: {
|
|
723
|
+
type: "object",
|
|
724
|
+
additionalProperties: false,
|
|
725
|
+
properties: {
|
|
726
|
+
path: {
|
|
727
|
+
type: "string",
|
|
728
|
+
required: true
|
|
729
|
+
},
|
|
730
|
+
operation: {
|
|
731
|
+
type: "string",
|
|
732
|
+
required: true,
|
|
733
|
+
enum: [
|
|
734
|
+
"created",
|
|
735
|
+
"updated",
|
|
736
|
+
"deleted",
|
|
737
|
+
"moved"
|
|
738
|
+
]
|
|
739
|
+
},
|
|
740
|
+
moveTo: { type: "string" }
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
} }
|
|
744
|
+
},
|
|
745
|
+
render: (_args, value) => [{
|
|
746
|
+
type: "text",
|
|
747
|
+
text: patchSummary(value)
|
|
748
|
+
}]
|
|
749
|
+
},
|
|
750
|
+
async execute(args, exec) {
|
|
751
|
+
const files = parsePatch(args.input);
|
|
752
|
+
const policy = resolvePolicy(ctx, exec);
|
|
753
|
+
const applied = [];
|
|
754
|
+
for (const file of files) applied.push(await applyOnePatch(ctx, file, exec, policy));
|
|
755
|
+
return { files: applied };
|
|
756
|
+
},
|
|
757
|
+
presentCall(args) {
|
|
758
|
+
return {
|
|
759
|
+
card: "generic",
|
|
760
|
+
title: "Apply patch",
|
|
761
|
+
kind: "edit",
|
|
762
|
+
rawInput: args.input
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
function registerPlanTool(ctx) {
|
|
768
|
+
ctx.tools.register(defineTool({
|
|
769
|
+
name: "update_plan",
|
|
770
|
+
description: UPDATE_PLAN_DESCRIPTION,
|
|
771
|
+
parameters: {
|
|
772
|
+
explanation: {
|
|
773
|
+
type: "string",
|
|
774
|
+
description: "Optional explanation for this plan update."
|
|
775
|
+
},
|
|
776
|
+
plan: {
|
|
777
|
+
type: "array",
|
|
778
|
+
required: true,
|
|
779
|
+
description: "The list of steps",
|
|
780
|
+
items: {
|
|
781
|
+
type: "object",
|
|
782
|
+
additionalProperties: false,
|
|
783
|
+
properties: {
|
|
784
|
+
step: {
|
|
785
|
+
type: "string",
|
|
786
|
+
required: true,
|
|
787
|
+
description: "Task step text."
|
|
788
|
+
},
|
|
789
|
+
status: {
|
|
790
|
+
type: "string",
|
|
791
|
+
required: true,
|
|
792
|
+
enum: [...PLAN_STATUSES],
|
|
793
|
+
description: "Step status."
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
output: {
|
|
800
|
+
schema: {
|
|
801
|
+
type: "object",
|
|
802
|
+
additionalProperties: false,
|
|
803
|
+
properties: {}
|
|
804
|
+
},
|
|
805
|
+
render: () => [{
|
|
806
|
+
type: "text",
|
|
807
|
+
text: "Plan updated"
|
|
808
|
+
}]
|
|
809
|
+
},
|
|
810
|
+
execute(args, exec) {
|
|
811
|
+
const agent = exec.agent;
|
|
812
|
+
if (agent === void 0) throw new Error("update_plan requires an owning agent session");
|
|
813
|
+
agent.session.append("todo/write", { todos: planTodos(args) });
|
|
814
|
+
return Promise.resolve({});
|
|
815
|
+
}
|
|
816
|
+
}));
|
|
817
|
+
}
|
|
818
|
+
/** Mount the Codex prompt/tool layer inside one fixed agent preset. */
|
|
819
|
+
function apply(ctx, config = {}) {
|
|
820
|
+
const resolved = {
|
|
821
|
+
defaultYieldTimeMs: config.defaultYieldTimeMs ?? 1e4,
|
|
822
|
+
pollYieldTimeMs: config.pollYieldTimeMs ?? 5e3,
|
|
823
|
+
writeYieldTimeMs: config.writeYieldTimeMs ?? 250,
|
|
824
|
+
maxOutputBytes: config.maxOutputBytes ?? 64e3
|
|
825
|
+
};
|
|
826
|
+
if (ctx.fs.sandboxMode !== void 0 && ctx.get("sandboxPolicy") === void 0) throw new Error("codex: a sandboxing filesystem requires ctx.sandboxPolicy");
|
|
827
|
+
ctx.systemPrompt.section({
|
|
828
|
+
name: "codex:base",
|
|
829
|
+
order: 10,
|
|
830
|
+
text: CODEX_BASE_PROMPT
|
|
831
|
+
});
|
|
832
|
+
registerExecTools(ctx, resolved);
|
|
833
|
+
registerPatchTool(ctx);
|
|
834
|
+
registerPlanTool(ctx);
|
|
835
|
+
}
|
|
836
|
+
var types_default = {
|
|
837
|
+
name,
|
|
838
|
+
inject,
|
|
839
|
+
Config,
|
|
840
|
+
apply
|
|
841
|
+
};
|
|
842
|
+
//#endregion
|
|
843
|
+
export { Config, apply, types_default as default, inject, name };
|