@waishnav/devspace 1.0.3 → 1.0.4
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/dist/apply-patch.js +67 -64
- package/dist/cli.js +243 -4
- package/dist/config.js +6 -9
- package/dist/db/migrations.js +34 -0
- package/dist/db/schema.js +19 -0
- package/dist/local-agent-adapters.js +653 -0
- package/dist/local-agent-availability.js +119 -0
- package/dist/local-agent-path.js +24 -0
- package/dist/local-agent-profiles.js +135 -0
- package/dist/local-agent-runtime.js +48 -0
- package/dist/local-agent-store.js +145 -0
- package/dist/local-agent-targets.js +80 -0
- package/dist/process-sessions.js +11 -3
- package/dist/server.js +88 -50
- package/dist/skills.js +27 -3
- package/dist/ui/.vite/manifest.json +281 -281
- package/dist/ui/assets/{angular-html-D6nc7PYw.js → angular-html-CKeFzaPY.js} +1 -1
- package/dist/ui/assets/{angular-ts-BAIStLeI.js → angular-ts-0wcM_Zxf.js} +1 -1
- package/dist/ui/assets/{apl-CtEv5pop.js → apl-C7UdkNb2.js} +1 -1
- package/dist/ui/assets/{astro-CVFIMXc6.js → astro-DMqju4MJ.js} +1 -1
- package/dist/ui/assets/{blade-BgSlZ6jY.js → blade-DHanNgN5.js} +1 -1
- package/dist/ui/assets/{c-bxLlCV26.js → c-_iPKYPcx.js} +1 -1
- package/dist/ui/assets/{cobol-BuZ-r51r.js → cobol-CagdLJsE.js} +1 -1
- package/dist/ui/assets/{coffee-_JP0Joa7.js → coffee-CkNGriPi.js} +1 -1
- package/dist/ui/assets/{cpp-CP_eaz-H.js → cpp-CqmKOWIQ.js} +1 -1
- package/dist/ui/assets/{crystal-DZ-sEZd0.js → crystal-DbAbDEO1.js} +1 -1
- package/dist/ui/assets/{css-CZnyti9s.js → css-xwljL6Kw.js} +1 -1
- package/dist/ui/assets/{edge-DjsoDMTR.js → edge-CX3sYz15.js} +1 -1
- package/dist/ui/assets/{elixir-C5b04AkY.js → elixir-B3qL9hqd.js} +1 -1
- package/dist/ui/assets/{elm-Ddx-rgVY.js → elm-CH4vrjBp.js} +1 -1
- package/dist/ui/assets/{erb-cjF7oPcT.js → erb-CupHqynN.js} +1 -1
- package/dist/ui/assets/{git-rebase-DN5exBTl.js → git-rebase-CDxsU2LC.js} +1 -1
- package/dist/ui/assets/{glimmer-js-CEq0hNwa.js → glimmer-js-emkga3g1.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-4uYGFkon.js → glimmer-ts-D8kQDnUw.js} +1 -1
- package/dist/ui/assets/{glsl-DEJ9tTWm.js → glsl-BtfTaPhi.js} +1 -1
- package/dist/ui/assets/{graphql-Bim5WuQh.js → graphql-DEus8iSO.js} +1 -1
- package/dist/ui/assets/{hack-BcyVIXhE.js → hack-DI8qpBqE.js} +1 -1
- package/dist/ui/assets/{haml-D9YaukDg.js → haml-Br1i1gmi.js} +1 -1
- package/dist/ui/assets/{handlebars-CFaMw9zH.js → handlebars-Ce23mu9x.js} +1 -1
- package/dist/ui/assets/{heavy-payload-B9aTqgfs.js → heavy-payload-BcZ_nGfF.js} +1 -1
- package/dist/ui/assets/{html-CgO_zx4D.js → html-RXVmfixf.js} +1 -1
- package/dist/ui/assets/{html-derivative-BxqkysJT.js → html-derivative-D1aqeobb.js} +1 -1
- package/dist/ui/assets/{http-CO5dv0RA.js → http-BebG00CE.js} +1 -1
- package/dist/ui/assets/{hurl-DRHtrK78.js → hurl-kHMuvenL.js} +1 -1
- package/dist/ui/assets/{java-BvHC_DgR.js → java-DYc2V5RJ.js} +1 -1
- package/dist/ui/assets/{javascript-C-3payQx.js → javascript-BcfzTzr3.js} +1 -1
- package/dist/ui/assets/{jinja-CAOPvEOH.js → jinja-D2RhihUZ.js} +1 -1
- package/dist/ui/assets/{jison-totCB1L-.js → jison-BuoFEePN.js} +1 -1
- package/dist/ui/assets/{json-D0J9rEe1.js → json-BIDkCuJA.js} +1 -1
- package/dist/ui/assets/{jsx-na-XQD_B.js → jsx-DHCL2HA0.js} +1 -1
- package/dist/ui/assets/{julia-CqrTtZ4-.js → julia-CvUQXscF.js} +1 -1
- package/dist/ui/assets/{just-pmrVSkMc.js → just-etc9R2qw.js} +1 -1
- package/dist/ui/assets/{latex-Cd_geX3E.js → latex-BB3zRnxC.js} +1 -1
- package/dist/ui/assets/{liquid-C6ky5sJ-.js → liquid-CIi5Mvpv.js} +1 -1
- package/dist/ui/assets/{lua-dgyBbeof.js → lua-Cox7XDiW.js} +1 -1
- package/dist/ui/assets/{marko-Dy5mfXY9.js → marko-ruBIRV0n.js} +1 -1
- package/dist/ui/assets/{mdc-7NUvxgI1.js → mdc-CcbvB9U5.js} +1 -1
- package/dist/ui/assets/{nginx-C-KiEXbI.js → nginx-DtWz6ZYE.js} +1 -1
- package/dist/ui/assets/{nim-BBw8kfl9.js → nim-BV5BR4P7.js} +1 -1
- package/dist/ui/assets/{perl-CyxhJgbO.js → perl-BvzoSaJ4.js} +1 -1
- package/dist/ui/assets/{php-m4LAOpam.js → php-C-clrAOy.js} +1 -1
- package/dist/ui/assets/{pug-Cpl0baYI.js → pug-E07_GXAr.js} +1 -1
- package/dist/ui/assets/{qml-BoSGNgIb.js → qml-dqt33dti.js} +1 -1
- package/dist/ui/assets/{r-C0JhqQd5.js → r-BcVQxsAw.js} +1 -1
- package/dist/ui/assets/{razor-CntsBNoh.js → razor-BW5T_UZ-.js} +1 -1
- package/dist/ui/assets/{regexp-BNZmOCRl.js → regexp-Ed_DEsuU.js} +1 -1
- package/dist/ui/assets/{review-payload-BgdWHWiv.js → review-payload-Dsmx33pY.js} +1 -1
- package/dist/ui/assets/{rst-BZ2XHIxM.js → rst-BkcGqLs8.js} +1 -1
- package/dist/ui/assets/{ruby-BIthfCTP.js → ruby-Sf_X_YID.js} +1 -1
- package/dist/ui/assets/{sas-5aT3fOla.js → sas-COsombsA.js} +1 -1
- package/dist/ui/assets/{scss-DZPfdkDy.js → scss-K6Ylfa26.js} +1 -1
- package/dist/ui/assets/{shellscript-DbhKc5Xj.js → shellscript-D2EdqkgV.js} +1 -1
- package/dist/ui/assets/{shellsession-X9-bcyHh.js → shellsession-jRXgXrnI.js} +1 -1
- package/dist/ui/assets/{soy-Bd4tecrW.js → soy-BGspKQWW.js} +1 -1
- package/dist/ui/assets/{sql-Chf5JrDn.js → sql-C4lAmYnM.js} +1 -1
- package/dist/ui/assets/{stata-DvMMIc_T.js → stata-DQWvRqxp.js} +1 -1
- package/dist/ui/assets/{surrealql-D5FMzVF5.js → surrealql-OmKt9XJ_.js} +1 -1
- package/dist/ui/assets/{svelte-CTiVXZrv.js → svelte-D0rUmwDI.js} +1 -1
- package/dist/ui/assets/{templ-ldnfq-7w.js → templ-B-U0RMc0.js} +1 -1
- package/dist/ui/assets/{tex-QW0_7T2r.js → tex-BIHD-btS.js} +1 -1
- package/dist/ui/assets/{ts-tags-CAKiWIBx.js → ts-tags-CGuDWfPC.js} +1 -1
- package/dist/ui/assets/{tsx-Bu9xJJmq.js → tsx-B0-h_HlD.js} +1 -1
- package/dist/ui/assets/{twig-Dp1752_V.js → twig-BN2qDJWN.js} +1 -1
- package/dist/ui/assets/{typescript-RfO03Kmm.js → typescript-BRnwPzY3.js} +1 -1
- package/dist/ui/assets/{useFileDiffInstance-DjqU4y1a.js → useFileDiffInstance-5ivdkUYO.js} +3 -3
- package/dist/ui/assets/{vue-Dlasv57G.js → vue-DekNU1qt.js} +1 -1
- package/dist/ui/assets/{vue-html-BHuJ7anu.js → vue-html-Bjlv-uvH.js} +1 -1
- package/dist/ui/assets/{vue-vine-CavE0PSk.js → vue-vine-C2Doytjq.js} +1 -1
- package/dist/ui/assets/{workspace-app-DGD9R89D.css → workspace-app-DASlX26z.css} +1 -1
- package/dist/ui/assets/{workspace-app-DDV_qdjx.js → workspace-app-DYi7VJam.js} +5 -5
- package/dist/ui/assets/{xml-Bbv64l4W.js → xml-pXbxxgI3.js} +1 -1
- package/dist/ui/assets/{xsl-Sro4gyQY.js → xsl-BEAlgDI2.js} +1 -1
- package/dist/ui/assets/{yaml-Du68c8sA.js → yaml-C1O-To71.js} +1 -1
- package/dist/ui/workspace-app.html +2 -2
- package/dist/user-config.js +21 -1
- package/dist/workspaces.js +3 -0
- package/docs/agent-profile-schema.md +165 -0
- package/docs/chatgpt-coding-workflow.md +24 -10
- package/docs/configuration.md +20 -8
- package/docs/gotchas.md +13 -0
- package/examples/agents/claude-implementer.md +30 -0
- package/examples/agents/codex-explorer.md +27 -0
- package/examples/agents/codex-qa-tester.md +29 -0
- package/examples/agents/codex-worker.md +25 -0
- package/examples/agents/copilot-reviewer.md +25 -0
- package/examples/agents/cursor-agent-worker.md +26 -0
- package/examples/agents/opencode-explorer.md +27 -0
- package/examples/agents/pi-reviewer.md +27 -0
- package/package.json +11 -3
- package/skills/subagent-delegation/SKILL.md +132 -0
- package/docs/codex-tool-mode-qa.md +0 -73
package/dist/apply-patch.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
|
-
import { access, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, lstat, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
5
|
import { TextDecoder } from "node:util";
|
|
6
|
+
import { createTwoFilesPatch, FILE_HEADERS_ONLY } from "diff";
|
|
6
7
|
function patchError(message) {
|
|
7
8
|
return new Error(`Invalid patch: ${message}`);
|
|
8
9
|
}
|
|
@@ -249,23 +250,56 @@ export async function replaceFile(temporary, destination, destinationExists, pla
|
|
|
249
250
|
}
|
|
250
251
|
await rm(backup, { force: true });
|
|
251
252
|
}
|
|
253
|
+
export async function isSamePatchFile(source, destination, readIdentity = lstat) {
|
|
254
|
+
if (source === destination)
|
|
255
|
+
return true;
|
|
256
|
+
if (source.toLowerCase() !== destination.toLowerCase())
|
|
257
|
+
return false;
|
|
258
|
+
try {
|
|
259
|
+
const [sourceIdentity, destinationIdentity] = await Promise.all([
|
|
260
|
+
readIdentity(source),
|
|
261
|
+
readIdentity(destination),
|
|
262
|
+
]);
|
|
263
|
+
return sourceIdentity.dev === destinationIdentity.dev && sourceIdentity.ino === destinationIdentity.ino;
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
const code = error.code;
|
|
267
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
268
|
+
return false;
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
252
272
|
export async function applyPatch(root, patch) {
|
|
253
273
|
const actions = parsePatch(patch);
|
|
254
274
|
const results = [];
|
|
255
275
|
const patches = [];
|
|
276
|
+
const staged = new Map();
|
|
277
|
+
const readStagedOptional = async (absolute, displayPath) => {
|
|
278
|
+
if (staged.has(absolute))
|
|
279
|
+
return staged.get(absolute) ?? null;
|
|
280
|
+
const file = await readOptionalTextFile(absolute, displayPath);
|
|
281
|
+
staged.set(absolute, file);
|
|
282
|
+
return file;
|
|
283
|
+
};
|
|
284
|
+
const readStagedRequired = async (absolute, displayPath) => {
|
|
285
|
+
const file = await readStagedOptional(absolute, displayPath);
|
|
286
|
+
if (!file)
|
|
287
|
+
throw patchError(`file does not exist: ${displayPath}`);
|
|
288
|
+
return file;
|
|
289
|
+
};
|
|
256
290
|
for (const action of actions) {
|
|
257
291
|
if (action.kind === "add") {
|
|
258
292
|
const absolute = await resolveConfinedPath(root, action.path);
|
|
259
|
-
const original = await
|
|
260
|
-
|
|
293
|
+
const original = await readStagedOptional(absolute, action.path);
|
|
294
|
+
staged.set(absolute, { content: action.content, mode: original?.mode });
|
|
261
295
|
patches.push(unifiedFilePatch(action.path, action.path, original?.content ?? null, action.content));
|
|
262
296
|
results.push({ path: action.path, operation: "add" });
|
|
263
297
|
continue;
|
|
264
298
|
}
|
|
265
299
|
const absolute = await resolveConfinedPath(root, action.path);
|
|
266
|
-
const file = await
|
|
300
|
+
const file = await readStagedRequired(absolute, action.path);
|
|
267
301
|
if (action.kind === "delete") {
|
|
268
|
-
|
|
302
|
+
staged.set(absolute, null);
|
|
269
303
|
patches.push(unifiedFilePatch(action.path, action.path, file.content, null));
|
|
270
304
|
results.push({ path: action.path, operation: "delete" });
|
|
271
305
|
continue;
|
|
@@ -273,32 +307,35 @@ export async function applyPatch(root, patch) {
|
|
|
273
307
|
const updated = applyHunks(action.path, file.content, action.hunks);
|
|
274
308
|
if (action.moveTo) {
|
|
275
309
|
const destination = await resolveConfinedPath(root, action.moveTo);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
310
|
+
const samePatchFile = await isSamePatchFile(absolute, destination);
|
|
311
|
+
if (!samePatchFile)
|
|
312
|
+
await readStagedOptional(destination, action.moveTo);
|
|
313
|
+
if (samePatchFile)
|
|
314
|
+
staged.delete(absolute);
|
|
315
|
+
staged.set(destination, { content: updated, mode: file.mode });
|
|
316
|
+
if (!samePatchFile)
|
|
317
|
+
staged.set(absolute, null);
|
|
281
318
|
patches.push(unifiedFilePatch(action.path, action.moveTo, file.content, updated));
|
|
282
319
|
results.push({ path: action.moveTo, previousPath: action.path, operation: "move" });
|
|
283
320
|
}
|
|
284
321
|
else {
|
|
285
|
-
|
|
322
|
+
staged.set(absolute, { content: updated, mode: file.mode });
|
|
286
323
|
patches.push(unifiedFilePatch(action.path, action.path, file.content, updated));
|
|
287
324
|
results.push({ path: action.path, operation: "update" });
|
|
288
325
|
}
|
|
289
326
|
}
|
|
327
|
+
for (const [absolute, file] of staged) {
|
|
328
|
+
if (file)
|
|
329
|
+
await writeTextFile(absolute, file.content, file.mode);
|
|
330
|
+
}
|
|
331
|
+
for (const [absolute, file] of staged) {
|
|
332
|
+
if (!file)
|
|
333
|
+
await rm(absolute, { force: true });
|
|
334
|
+
}
|
|
290
335
|
const unifiedPatch = patches.filter(Boolean).join("\n");
|
|
291
336
|
const stats = countPatchStats(unifiedPatch);
|
|
292
337
|
return { files: results, patch: unifiedPatch, ...stats };
|
|
293
338
|
}
|
|
294
|
-
async function readRequiredTextFile(absolute, displayPath) {
|
|
295
|
-
if (!(await fileExists(absolute)))
|
|
296
|
-
throw patchError(`file does not exist: ${displayPath}`);
|
|
297
|
-
const metadata = await stat(absolute);
|
|
298
|
-
if (!metadata.isFile())
|
|
299
|
-
throw patchError(`path is not a regular file: ${displayPath}`);
|
|
300
|
-
return { content: await readUtf8Text(absolute, displayPath), mode: metadata.mode };
|
|
301
|
-
}
|
|
302
339
|
async function readOptionalTextFile(absolute, displayPath) {
|
|
303
340
|
if (!(await fileExists(absolute)))
|
|
304
341
|
return null;
|
|
@@ -332,60 +369,26 @@ async function writeTextFile(destination, content, mode) {
|
|
|
332
369
|
throw error;
|
|
333
370
|
}
|
|
334
371
|
}
|
|
335
|
-
function fileLines(content) {
|
|
336
|
-
if (content.length === 0)
|
|
337
|
-
return [];
|
|
338
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
339
|
-
const lines = normalized.split("\n");
|
|
340
|
-
if (normalized.endsWith("\n"))
|
|
341
|
-
lines.pop();
|
|
342
|
-
return lines;
|
|
343
|
-
}
|
|
344
|
-
function hunkRange(start, count) {
|
|
345
|
-
return count === 0 ? "0,0" : `${start},${count}`;
|
|
346
|
-
}
|
|
347
372
|
function unifiedFilePatch(oldPath, newPath, oldContent, newContent) {
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
while (prefix < oldLines.length &&
|
|
352
|
-
prefix < newLines.length &&
|
|
353
|
-
oldLines[prefix] === newLines[prefix]) {
|
|
354
|
-
prefix += 1;
|
|
355
|
-
}
|
|
356
|
-
let suffix = 0;
|
|
357
|
-
while (suffix < oldLines.length - prefix &&
|
|
358
|
-
suffix < newLines.length - prefix &&
|
|
359
|
-
oldLines[oldLines.length - 1 - suffix] === newLines[newLines.length - 1 - suffix]) {
|
|
360
|
-
suffix += 1;
|
|
361
|
-
}
|
|
362
|
-
const contextBefore = Math.min(3, prefix);
|
|
363
|
-
const contextAfter = Math.min(3, suffix);
|
|
364
|
-
const oldChanged = oldLines.slice(prefix, oldLines.length - suffix);
|
|
365
|
-
const newChanged = newLines.slice(prefix, newLines.length - suffix);
|
|
366
|
-
const before = oldLines.slice(prefix - contextBefore, prefix);
|
|
367
|
-
const after = oldLines.slice(oldLines.length - suffix, oldLines.length - suffix + contextAfter);
|
|
368
|
-
const oldCount = contextBefore + oldChanged.length + contextAfter;
|
|
369
|
-
const newCount = contextBefore + newChanged.length + contextAfter;
|
|
370
|
-
const oldStart = oldContent === null ? 0 : prefix - contextBefore + 1;
|
|
371
|
-
const newStart = newContent === null ? 0 : prefix - contextBefore + 1;
|
|
372
|
-
const displayOld = oldContent === null ? "/dev/null" : `a/${oldPath}`;
|
|
373
|
-
const displayNew = newContent === null ? "/dev/null" : `b/${newPath}`;
|
|
373
|
+
const oldFileName = oldContent === null ? "/dev/null" : `a/${oldPath}`;
|
|
374
|
+
const newFileName = newContent === null ? "/dev/null" : `b/${newPath}`;
|
|
375
|
+
const body = createTwoFilesPatch(oldFileName, newFileName, oldContent ?? "", newContent ?? "", "", "", { context: 3, headerOptions: FILE_HEADERS_ONLY });
|
|
374
376
|
return [
|
|
375
377
|
`diff --git a/${oldPath} b/${newPath}`,
|
|
376
378
|
oldContent === null ? "new file mode 100644" : undefined,
|
|
377
379
|
newContent === null ? "deleted file mode 100644" : undefined,
|
|
378
|
-
|
|
379
|
-
`+++ ${displayNew}`,
|
|
380
|
-
`@@ -${hunkRange(oldStart, oldCount)} +${hunkRange(newStart, newCount)} @@`,
|
|
381
|
-
...before.map((line) => ` ${line}`),
|
|
382
|
-
...oldChanged.map((line) => `-${line}`),
|
|
383
|
-
...newChanged.map((line) => `+${line}`),
|
|
384
|
-
...after.map((line) => ` ${line}`),
|
|
380
|
+
stripFinalNewline(body),
|
|
385
381
|
]
|
|
386
382
|
.filter((line) => line !== undefined)
|
|
387
383
|
.join("\n");
|
|
388
384
|
}
|
|
385
|
+
function stripFinalNewline(value) {
|
|
386
|
+
if (value.endsWith("\r\n"))
|
|
387
|
+
return value.slice(0, -2);
|
|
388
|
+
if (value.endsWith("\n"))
|
|
389
|
+
return value.slice(0, -1);
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
389
392
|
function countPatchStats(patch) {
|
|
390
393
|
let additions = 0;
|
|
391
394
|
let removals = 0;
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
|
-
import {
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
5
10
|
import * as prompts from "@clack/prompts";
|
|
6
11
|
import { getShellConfig } from "@earendil-works/pi-coding-agent";
|
|
7
12
|
import { satisfies } from "semver";
|
|
8
13
|
import { loadConfig } from "./config.js";
|
|
9
|
-
import {
|
|
14
|
+
import { runLocalAgentProvider } from "./local-agent-adapters.js";
|
|
15
|
+
import { isLocalAgentProvider, loadLocalAgentProfiles, } from "./local-agent-profiles.js";
|
|
16
|
+
import { assertLocalAgentProviderAvailable, formatLocalAgentProviderAvailabilitySummary, } from "./local-agent-availability.js";
|
|
17
|
+
import { formatAvailableLocalAgentTargets, parseLocalAgentRunArgs, resolveLocalAgentTarget, } from "./local-agent-targets.js";
|
|
18
|
+
import { createLocalAgentStore } from "./local-agent-store.js";
|
|
19
|
+
import { ensureDevspaceDefaultSkills, generateOwnerToken, loadDevspaceFiles, resolveSubagentsFlag, writeDevspaceAuth, writeDevspaceConfig, } from "./user-config.js";
|
|
10
20
|
import { expandHomePath } from "./roots.js";
|
|
11
21
|
const require = createRequire(import.meta.url);
|
|
12
22
|
const SUPPORTED_NODE_RANGE = ">=20.12 <27";
|
|
@@ -28,6 +38,9 @@ async function main(argv) {
|
|
|
28
38
|
case "config":
|
|
29
39
|
runConfigCommand(args);
|
|
30
40
|
return;
|
|
41
|
+
case "agents":
|
|
42
|
+
await runAgentsCommand(args);
|
|
43
|
+
return;
|
|
31
44
|
case "help":
|
|
32
45
|
printHelp();
|
|
33
46
|
return;
|
|
@@ -39,7 +52,7 @@ async function main(argv) {
|
|
|
39
52
|
function normalizeCommand(command) {
|
|
40
53
|
if (!command || command === "serve" || command === "start")
|
|
41
54
|
return "serve";
|
|
42
|
-
if (command === "init" || command === "doctor" || command === "config")
|
|
55
|
+
if (command === "init" || command === "doctor" || command === "config" || command === "agents")
|
|
43
56
|
return command;
|
|
44
57
|
if (command === "help" || command === "--help" || command === "-h")
|
|
45
58
|
return "help";
|
|
@@ -113,15 +126,18 @@ async function runInit({ force }) {
|
|
|
113
126
|
port,
|
|
114
127
|
allowedRoots,
|
|
115
128
|
publicBaseUrl,
|
|
129
|
+
subagents: resolveSubagentsFlag(files.config),
|
|
116
130
|
};
|
|
117
131
|
const auth = {
|
|
118
132
|
ownerToken: files.auth.ownerToken ?? generateOwnerToken(),
|
|
119
133
|
};
|
|
120
134
|
const configPath = writeDevspaceConfig(config);
|
|
121
135
|
const authPath = writeDevspaceAuth(auth);
|
|
136
|
+
const seededSkillPaths = config.subagents ? ensureDevspaceDefaultSkills() : [];
|
|
122
137
|
const lines = [
|
|
123
138
|
`Config: ${configPath}`,
|
|
124
139
|
`Auth: ${authPath}`,
|
|
140
|
+
...seededSkillPaths.map((path) => `Default skill: ${path}`),
|
|
125
141
|
`Local MCP URL: http://${config.host}:${config.port}/mcp`,
|
|
126
142
|
...(publicBaseUrl ? [`Public MCP URL: ${publicBaseUrl}/mcp`] : []),
|
|
127
143
|
];
|
|
@@ -154,7 +170,7 @@ async function serve() {
|
|
|
154
170
|
}
|
|
155
171
|
const { createServer } = await import("./server.js");
|
|
156
172
|
const config = loadConfig();
|
|
157
|
-
const { app, close } = createServer(config);
|
|
173
|
+
const { app, close, localAgentProviders } = createServer(config);
|
|
158
174
|
const httpServer = app.listen(config.port, config.host, () => {
|
|
159
175
|
console.log(`devspace listening on http://${config.host}:${config.port}/mcp`);
|
|
160
176
|
console.log(`public base url: ${config.publicBaseUrl}`);
|
|
@@ -165,6 +181,9 @@ async function serve() {
|
|
|
165
181
|
}
|
|
166
182
|
console.log("auth: Owner password approval required");
|
|
167
183
|
console.log(`logging: ${config.logging.level} ${config.logging.format}`);
|
|
184
|
+
if (config.subagents) {
|
|
185
|
+
console.log(`subagent providers: ${formatLocalAgentProviderAvailabilitySummary(localAgentProviders)}`);
|
|
186
|
+
}
|
|
168
187
|
});
|
|
169
188
|
const shutdown = () => {
|
|
170
189
|
httpServer.close(() => {
|
|
@@ -231,12 +250,232 @@ function printHelp() {
|
|
|
231
250
|
" devspace doctor Show config, runtime, and native dependency status",
|
|
232
251
|
" devspace config get Print persisted config",
|
|
233
252
|
" devspace config set publicBaseUrl <url|null>",
|
|
253
|
+
" devspace agents ls List subagent sessions",
|
|
254
|
+
" devspace agents run <profile-or-provider-or-id> [--model <model>] <prompt>",
|
|
255
|
+
" devspace agents show <id>",
|
|
234
256
|
" devspace -v, --version Print the installed version",
|
|
235
257
|
"",
|
|
236
258
|
"For temporary tunnels:",
|
|
237
259
|
" DEVSPACE_PUBLIC_BASE_URL=https://example.trycloudflare.com devspace serve",
|
|
238
260
|
].join("\n"));
|
|
239
261
|
}
|
|
262
|
+
async function runAgentsCommand(args) {
|
|
263
|
+
const [subcommand, ...rest] = args;
|
|
264
|
+
switch (subcommand) {
|
|
265
|
+
case "ls":
|
|
266
|
+
case "list":
|
|
267
|
+
await runAgentsList();
|
|
268
|
+
return;
|
|
269
|
+
case "run":
|
|
270
|
+
await runAgentsRun(rest);
|
|
271
|
+
return;
|
|
272
|
+
case "show":
|
|
273
|
+
await runAgentsShow(rest);
|
|
274
|
+
return;
|
|
275
|
+
case "__worker":
|
|
276
|
+
await runAgentsWorker(rest);
|
|
277
|
+
return;
|
|
278
|
+
case undefined:
|
|
279
|
+
case "help":
|
|
280
|
+
case "--help":
|
|
281
|
+
case "-h":
|
|
282
|
+
printAgentsHelp();
|
|
283
|
+
return;
|
|
284
|
+
default:
|
|
285
|
+
throw new Error(`Unknown agents command: ${subcommand}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function runAgentsList() {
|
|
289
|
+
const config = loadConfig();
|
|
290
|
+
const store = createLocalAgentStore(config);
|
|
291
|
+
const agents = store.list(resolveCurrentWorkspaceScope());
|
|
292
|
+
if (agents.length === 0) {
|
|
293
|
+
console.log("No subagent sessions found for this workspace.");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
for (const agent of agents) {
|
|
297
|
+
console.log(formatAgentLine(agent));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function runAgentsRun(args) {
|
|
301
|
+
const parsed = parseLocalAgentRunArgs(args);
|
|
302
|
+
const config = loadConfig();
|
|
303
|
+
const workspaceRoot = resolveCurrentWorkspaceRoot();
|
|
304
|
+
const store = createLocalAgentStore(config);
|
|
305
|
+
const existing = store.get(parsed.target);
|
|
306
|
+
if (existing) {
|
|
307
|
+
if (!isLocalAgentProvider(existing.provider)) {
|
|
308
|
+
throw new Error(`Unknown subagent provider for existing session: ${existing.provider}`);
|
|
309
|
+
}
|
|
310
|
+
assertLocalAgentProviderAvailable(existing.provider);
|
|
311
|
+
const promptFile = writeAgentPromptFile(parsed.prompt);
|
|
312
|
+
store.update(existing.id, {
|
|
313
|
+
status: "starting",
|
|
314
|
+
model: parsed.model ?? existing.model,
|
|
315
|
+
thinking: parsed.thinking ?? existing.thinking,
|
|
316
|
+
latestResponse: undefined,
|
|
317
|
+
error: undefined,
|
|
318
|
+
});
|
|
319
|
+
spawnAgentWorker(existing.id, promptFile);
|
|
320
|
+
console.log(formatAgentLine({
|
|
321
|
+
...existing,
|
|
322
|
+
status: "running",
|
|
323
|
+
model: parsed.model ?? existing.model,
|
|
324
|
+
thinking: parsed.thinking ?? existing.thinking,
|
|
325
|
+
}));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const profiles = await loadLocalAgentProfiles(config, workspaceRoot);
|
|
329
|
+
const target = resolveLocalAgentTarget(parsed.target, profiles, parsed.model, parsed.thinking);
|
|
330
|
+
if (!target) {
|
|
331
|
+
throw new Error(`Unknown subagent profile, provider, or id: ${parsed.target}. Available ${formatAvailableLocalAgentTargets(profiles)}`);
|
|
332
|
+
}
|
|
333
|
+
assertLocalAgentProviderAvailable(target.provider);
|
|
334
|
+
const promptFile = writeAgentPromptFile(parsed.prompt);
|
|
335
|
+
const record = store.create({
|
|
336
|
+
workspaceId: process.env.DEVSPACE_WORKSPACE_ID,
|
|
337
|
+
workspaceRoot,
|
|
338
|
+
profileName: target.name,
|
|
339
|
+
provider: target.provider,
|
|
340
|
+
model: target.model,
|
|
341
|
+
thinking: target.thinking,
|
|
342
|
+
});
|
|
343
|
+
spawnAgentWorker(record.id, promptFile);
|
|
344
|
+
console.log(formatAgentLine({ ...record, status: "running" }));
|
|
345
|
+
}
|
|
346
|
+
async function runAgentsShow(args) {
|
|
347
|
+
const [id] = args;
|
|
348
|
+
if (!id)
|
|
349
|
+
throw new Error("Usage: devspace agents show <id>");
|
|
350
|
+
const config = loadConfig();
|
|
351
|
+
const store = createLocalAgentStore(config);
|
|
352
|
+
let record = store.get(id);
|
|
353
|
+
if (!record)
|
|
354
|
+
throw new Error(`Unknown subagent id: ${id}`);
|
|
355
|
+
const deadline = Date.now() + 15_000;
|
|
356
|
+
while ((record.status === "starting" || record.status === "running") && Date.now() < deadline) {
|
|
357
|
+
await sleep(500);
|
|
358
|
+
record = store.get(id) ?? record;
|
|
359
|
+
}
|
|
360
|
+
console.log(formatAgentLine(record));
|
|
361
|
+
if (record.latestResponse) {
|
|
362
|
+
console.log(record.latestResponse);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (record.error) {
|
|
366
|
+
console.log(record.error);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (record.status === "starting" || record.status === "running") {
|
|
370
|
+
console.log(`No final response yet. Call \`devspace agents show ${record.id}\` again later.`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function runAgentsWorker(args) {
|
|
374
|
+
const [id, promptFileFlag, promptFile] = args;
|
|
375
|
+
if (!id || promptFileFlag !== "--prompt-file" || !promptFile) {
|
|
376
|
+
throw new Error("Usage: devspace agents __worker <id> --prompt-file <path>");
|
|
377
|
+
}
|
|
378
|
+
const config = loadConfig();
|
|
379
|
+
const store = createLocalAgentStore(config);
|
|
380
|
+
const record = store.get(id);
|
|
381
|
+
if (!record)
|
|
382
|
+
throw new Error(`Unknown subagent id: ${id}`);
|
|
383
|
+
store.update(record.id, { status: "running", error: undefined });
|
|
384
|
+
try {
|
|
385
|
+
const profiles = await loadLocalAgentProfiles(config, record.workspaceRoot);
|
|
386
|
+
const profile = profiles.find((candidate) => candidate.name === record.profileName);
|
|
387
|
+
const prompt = await readFile(promptFile, "utf8");
|
|
388
|
+
const result = profile
|
|
389
|
+
? await runLocalAgentProfile(profile, record, prompt)
|
|
390
|
+
: await runRawLocalAgentProvider(record, prompt);
|
|
391
|
+
store.update(record.id, {
|
|
392
|
+
providerSessionId: result.providerSessionId ?? undefined,
|
|
393
|
+
status: "idle",
|
|
394
|
+
latestResponse: result.finalResponse,
|
|
395
|
+
error: undefined,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
store.update(record.id, {
|
|
400
|
+
status: "error",
|
|
401
|
+
error: error instanceof Error ? error.message : String(error),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function runLocalAgentProfile(profile, record, prompt) {
|
|
406
|
+
const body = profile.body.trim();
|
|
407
|
+
const fullPrompt = body ? `${body}\n\nTask:\n${prompt}` : prompt;
|
|
408
|
+
return runLocalAgentProvider(profile.provider, {
|
|
409
|
+
prompt: fullPrompt,
|
|
410
|
+
workspace: record.workspaceRoot,
|
|
411
|
+
providerSessionId: record.providerSessionId,
|
|
412
|
+
writeMode: "allowed",
|
|
413
|
+
model: record.model ?? profile.model,
|
|
414
|
+
thinking: record.thinking ?? profile.thinking,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
async function runRawLocalAgentProvider(record, prompt) {
|
|
418
|
+
if (record.profileName !== record.provider || !isLocalAgentProvider(record.provider)) {
|
|
419
|
+
throw new Error(`Subagent profile not found: ${record.profileName}`);
|
|
420
|
+
}
|
|
421
|
+
return runLocalAgentProvider(record.provider, {
|
|
422
|
+
prompt,
|
|
423
|
+
workspace: record.workspaceRoot,
|
|
424
|
+
providerSessionId: record.providerSessionId,
|
|
425
|
+
writeMode: "allowed",
|
|
426
|
+
model: record.model,
|
|
427
|
+
thinking: record.thinking,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function spawnAgentWorker(agentId, promptFile) {
|
|
431
|
+
const child = spawn(process.execPath, [
|
|
432
|
+
...process.execArgv,
|
|
433
|
+
fileURLToPath(import.meta.url),
|
|
434
|
+
"agents",
|
|
435
|
+
"__worker",
|
|
436
|
+
agentId,
|
|
437
|
+
"--prompt-file",
|
|
438
|
+
promptFile,
|
|
439
|
+
], {
|
|
440
|
+
detached: true,
|
|
441
|
+
stdio: "ignore",
|
|
442
|
+
env: process.env,
|
|
443
|
+
});
|
|
444
|
+
child.unref();
|
|
445
|
+
}
|
|
446
|
+
function writeAgentPromptFile(prompt) {
|
|
447
|
+
const directory = mkdtempSync(join(tmpdir(), "devspace-agent-prompt-"));
|
|
448
|
+
const filePath = join(directory, "prompt.txt");
|
|
449
|
+
writeFileSync(filePath, prompt, { mode: 0o600 });
|
|
450
|
+
return filePath;
|
|
451
|
+
}
|
|
452
|
+
function resolveCurrentWorkspaceRoot() {
|
|
453
|
+
return resolve(process.env.DEVSPACE_WORKSPACE_ROOT || process.cwd());
|
|
454
|
+
}
|
|
455
|
+
function resolveCurrentWorkspaceScope() {
|
|
456
|
+
return {
|
|
457
|
+
workspaceId: process.env.DEVSPACE_WORKSPACE_ID,
|
|
458
|
+
workspaceRoot: resolveCurrentWorkspaceRoot(),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function formatAgentLine(agent) {
|
|
462
|
+
const model = agent.model ? ` ${agent.model}` : "";
|
|
463
|
+
const thinking = agent.thinking ? ` thinking=${agent.thinking}` : "";
|
|
464
|
+
return `${agent.id} ${agent.status} ${agent.profileName} ${agent.provider}${model}${thinking}`;
|
|
465
|
+
}
|
|
466
|
+
function sleep(ms) {
|
|
467
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
468
|
+
}
|
|
469
|
+
function printAgentsHelp() {
|
|
470
|
+
console.log([
|
|
471
|
+
"DevSpace agents",
|
|
472
|
+
"",
|
|
473
|
+
"Usage:",
|
|
474
|
+
" devspace agents ls",
|
|
475
|
+
" devspace agents run <profile-or-provider-or-id> [--model <model>] [--thinking <level>] <prompt>",
|
|
476
|
+
" devspace agents show <id>",
|
|
477
|
+
].join("\n"));
|
|
478
|
+
}
|
|
240
479
|
function printVersion() {
|
|
241
480
|
const packageJson = require("../package.json");
|
|
242
481
|
if (typeof packageJson.version !== "string") {
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { expandHomePath } from "./roots.js";
|
|
4
|
-
import { loadDevspaceFiles } from "./user-config.js";
|
|
4
|
+
import { devspaceAgentsDir, devspaceSkillsDir, loadDevspaceFiles } from "./user-config.js";
|
|
5
5
|
const DEFAULT_OAUTH_ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
|
|
6
6
|
const DEFAULT_OAUTH_REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
7
7
|
function parsePort(value) {
|
|
@@ -91,13 +91,6 @@ function parsePositiveInteger(value, fallback, name) {
|
|
|
91
91
|
}
|
|
92
92
|
return parsed;
|
|
93
93
|
}
|
|
94
|
-
function parseToolNaming(value) {
|
|
95
|
-
if (!value || value === "short")
|
|
96
|
-
return "short";
|
|
97
|
-
if (value === "legacy")
|
|
98
|
-
return "legacy";
|
|
99
|
-
throw new Error(`Invalid DEVSPACE_TOOL_NAMING: ${value}`);
|
|
100
|
-
}
|
|
101
94
|
function parseLoggingConfig(env) {
|
|
102
95
|
return {
|
|
103
96
|
level: parseLogLevel(env.DEVSPACE_LOG_LEVEL),
|
|
@@ -169,12 +162,16 @@ export function loadConfig(env = process.env) {
|
|
|
169
162
|
allowedHosts: parseAllowedHosts(env.DEVSPACE_ALLOWED_HOSTS, derivedAllowedHosts),
|
|
170
163
|
publicBaseUrl,
|
|
171
164
|
toolMode: parseToolMode(env),
|
|
172
|
-
toolNaming: parseToolNaming(env.DEVSPACE_TOOL_NAMING),
|
|
173
165
|
widgets: parseWidgetMode(env.DEVSPACE_WIDGETS),
|
|
174
166
|
stateDir: resolve(expandHomePath(env.DEVSPACE_STATE_DIR ?? files.config.stateDir ?? defaultStateDir())),
|
|
175
167
|
worktreeRoot: resolve(expandHomePath(env.DEVSPACE_WORKTREE_ROOT ?? files.config.worktreeRoot ?? defaultWorktreeRoot())),
|
|
176
168
|
skillsEnabled: env.DEVSPACE_SKILLS === undefined ? true : parseBoolean(env.DEVSPACE_SKILLS),
|
|
177
169
|
skillPaths: parsePathList(env.DEVSPACE_SKILL_PATHS),
|
|
170
|
+
devspaceSkillsDir: devspaceSkillsDir(env),
|
|
171
|
+
devspaceAgentsDir: devspaceAgentsDir(env),
|
|
172
|
+
subagents: env.DEVSPACE_SUBAGENTS === undefined
|
|
173
|
+
? files.config.subagents === true
|
|
174
|
+
: parseBoolean(env.DEVSPACE_SUBAGENTS),
|
|
178
175
|
agentDir: resolve(expandHomePath(env.DEVSPACE_AGENT_DIR ?? files.config.agentDir ?? defaultAgentDir())),
|
|
179
176
|
logging: parseLoggingConfig(env),
|
|
180
177
|
};
|
package/dist/db/migrations.js
CHANGED
|
@@ -9,6 +9,11 @@ const migrations = [
|
|
|
9
9
|
name: "oauth-state",
|
|
10
10
|
up: migrateOAuthState,
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
version: 3,
|
|
14
|
+
name: "local-agent-sessions",
|
|
15
|
+
up: migrateLocalAgentSessions,
|
|
16
|
+
},
|
|
12
17
|
];
|
|
13
18
|
export function migrateDatabase(sqlite) {
|
|
14
19
|
const migrate = sqlite.transaction(() => {
|
|
@@ -115,6 +120,35 @@ function migrateOAuthState(sqlite) {
|
|
|
115
120
|
on oauth_refresh_tokens(expires_at);
|
|
116
121
|
`);
|
|
117
122
|
}
|
|
123
|
+
function migrateLocalAgentSessions(sqlite) {
|
|
124
|
+
sqlite.exec(`
|
|
125
|
+
create table if not exists local_agent_sessions (
|
|
126
|
+
id text primary key,
|
|
127
|
+
workspace_id text,
|
|
128
|
+
workspace_root text not null,
|
|
129
|
+
profile_name text not null,
|
|
130
|
+
provider text not null,
|
|
131
|
+
model text,
|
|
132
|
+
thinking text,
|
|
133
|
+
provider_session_id text,
|
|
134
|
+
status text not null,
|
|
135
|
+
latest_response text,
|
|
136
|
+
error text,
|
|
137
|
+
created_at text not null,
|
|
138
|
+
updated_at text not null
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
create index if not exists local_agent_sessions_workspace_id_idx
|
|
142
|
+
on local_agent_sessions(workspace_id, updated_at desc);
|
|
143
|
+
|
|
144
|
+
create index if not exists local_agent_sessions_workspace_root_idx
|
|
145
|
+
on local_agent_sessions(workspace_root, updated_at desc);
|
|
146
|
+
|
|
147
|
+
create index if not exists local_agent_sessions_provider_session_id_idx
|
|
148
|
+
on local_agent_sessions(provider_session_id);
|
|
149
|
+
`);
|
|
150
|
+
addColumnIfMissing(sqlite, "local_agent_sessions", "thinking", "text");
|
|
151
|
+
}
|
|
118
152
|
function addColumnIfMissing(sqlite, table, column, definition) {
|
|
119
153
|
const columns = sqlite.prepare(`pragma table_info(${table})`).all();
|
|
120
154
|
if (columns.some((existingColumn) => existingColumn.name === column))
|
package/dist/db/schema.js
CHANGED
|
@@ -50,3 +50,22 @@ export const oauthRefreshTokens = sqliteTable("oauth_refresh_tokens", {
|
|
|
50
50
|
expiresAt: integer("expires_at").notNull(),
|
|
51
51
|
resource: text("resource"),
|
|
52
52
|
});
|
|
53
|
+
export const localAgentSessions = sqliteTable("local_agent_sessions", {
|
|
54
|
+
id: text("id").primaryKey(),
|
|
55
|
+
workspaceId: text("workspace_id"),
|
|
56
|
+
workspaceRoot: text("workspace_root").notNull(),
|
|
57
|
+
profileName: text("profile_name").notNull(),
|
|
58
|
+
provider: text("provider").notNull(),
|
|
59
|
+
model: text("model"),
|
|
60
|
+
thinking: text("thinking"),
|
|
61
|
+
providerSessionId: text("provider_session_id"),
|
|
62
|
+
status: text("status").notNull(),
|
|
63
|
+
latestResponse: text("latest_response"),
|
|
64
|
+
error: text("error"),
|
|
65
|
+
createdAt: text("created_at").notNull(),
|
|
66
|
+
updatedAt: text("updated_at").notNull(),
|
|
67
|
+
}, (table) => [
|
|
68
|
+
index("local_agent_sessions_workspace_id_idx").on(table.workspaceId, table.updatedAt),
|
|
69
|
+
index("local_agent_sessions_workspace_root_idx").on(table.workspaceRoot, table.updatedAt),
|
|
70
|
+
index("local_agent_sessions_provider_session_id_idx").on(table.providerSessionId),
|
|
71
|
+
]);
|