@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.
Files changed (111) hide show
  1. package/dist/apply-patch.js +67 -64
  2. package/dist/cli.js +243 -4
  3. package/dist/config.js +6 -9
  4. package/dist/db/migrations.js +34 -0
  5. package/dist/db/schema.js +19 -0
  6. package/dist/local-agent-adapters.js +653 -0
  7. package/dist/local-agent-availability.js +119 -0
  8. package/dist/local-agent-path.js +24 -0
  9. package/dist/local-agent-profiles.js +135 -0
  10. package/dist/local-agent-runtime.js +48 -0
  11. package/dist/local-agent-store.js +145 -0
  12. package/dist/local-agent-targets.js +80 -0
  13. package/dist/process-sessions.js +11 -3
  14. package/dist/server.js +88 -50
  15. package/dist/skills.js +27 -3
  16. package/dist/ui/.vite/manifest.json +281 -281
  17. package/dist/ui/assets/{angular-html-D6nc7PYw.js → angular-html-CKeFzaPY.js} +1 -1
  18. package/dist/ui/assets/{angular-ts-BAIStLeI.js → angular-ts-0wcM_Zxf.js} +1 -1
  19. package/dist/ui/assets/{apl-CtEv5pop.js → apl-C7UdkNb2.js} +1 -1
  20. package/dist/ui/assets/{astro-CVFIMXc6.js → astro-DMqju4MJ.js} +1 -1
  21. package/dist/ui/assets/{blade-BgSlZ6jY.js → blade-DHanNgN5.js} +1 -1
  22. package/dist/ui/assets/{c-bxLlCV26.js → c-_iPKYPcx.js} +1 -1
  23. package/dist/ui/assets/{cobol-BuZ-r51r.js → cobol-CagdLJsE.js} +1 -1
  24. package/dist/ui/assets/{coffee-_JP0Joa7.js → coffee-CkNGriPi.js} +1 -1
  25. package/dist/ui/assets/{cpp-CP_eaz-H.js → cpp-CqmKOWIQ.js} +1 -1
  26. package/dist/ui/assets/{crystal-DZ-sEZd0.js → crystal-DbAbDEO1.js} +1 -1
  27. package/dist/ui/assets/{css-CZnyti9s.js → css-xwljL6Kw.js} +1 -1
  28. package/dist/ui/assets/{edge-DjsoDMTR.js → edge-CX3sYz15.js} +1 -1
  29. package/dist/ui/assets/{elixir-C5b04AkY.js → elixir-B3qL9hqd.js} +1 -1
  30. package/dist/ui/assets/{elm-Ddx-rgVY.js → elm-CH4vrjBp.js} +1 -1
  31. package/dist/ui/assets/{erb-cjF7oPcT.js → erb-CupHqynN.js} +1 -1
  32. package/dist/ui/assets/{git-rebase-DN5exBTl.js → git-rebase-CDxsU2LC.js} +1 -1
  33. package/dist/ui/assets/{glimmer-js-CEq0hNwa.js → glimmer-js-emkga3g1.js} +1 -1
  34. package/dist/ui/assets/{glimmer-ts-4uYGFkon.js → glimmer-ts-D8kQDnUw.js} +1 -1
  35. package/dist/ui/assets/{glsl-DEJ9tTWm.js → glsl-BtfTaPhi.js} +1 -1
  36. package/dist/ui/assets/{graphql-Bim5WuQh.js → graphql-DEus8iSO.js} +1 -1
  37. package/dist/ui/assets/{hack-BcyVIXhE.js → hack-DI8qpBqE.js} +1 -1
  38. package/dist/ui/assets/{haml-D9YaukDg.js → haml-Br1i1gmi.js} +1 -1
  39. package/dist/ui/assets/{handlebars-CFaMw9zH.js → handlebars-Ce23mu9x.js} +1 -1
  40. package/dist/ui/assets/{heavy-payload-B9aTqgfs.js → heavy-payload-BcZ_nGfF.js} +1 -1
  41. package/dist/ui/assets/{html-CgO_zx4D.js → html-RXVmfixf.js} +1 -1
  42. package/dist/ui/assets/{html-derivative-BxqkysJT.js → html-derivative-D1aqeobb.js} +1 -1
  43. package/dist/ui/assets/{http-CO5dv0RA.js → http-BebG00CE.js} +1 -1
  44. package/dist/ui/assets/{hurl-DRHtrK78.js → hurl-kHMuvenL.js} +1 -1
  45. package/dist/ui/assets/{java-BvHC_DgR.js → java-DYc2V5RJ.js} +1 -1
  46. package/dist/ui/assets/{javascript-C-3payQx.js → javascript-BcfzTzr3.js} +1 -1
  47. package/dist/ui/assets/{jinja-CAOPvEOH.js → jinja-D2RhihUZ.js} +1 -1
  48. package/dist/ui/assets/{jison-totCB1L-.js → jison-BuoFEePN.js} +1 -1
  49. package/dist/ui/assets/{json-D0J9rEe1.js → json-BIDkCuJA.js} +1 -1
  50. package/dist/ui/assets/{jsx-na-XQD_B.js → jsx-DHCL2HA0.js} +1 -1
  51. package/dist/ui/assets/{julia-CqrTtZ4-.js → julia-CvUQXscF.js} +1 -1
  52. package/dist/ui/assets/{just-pmrVSkMc.js → just-etc9R2qw.js} +1 -1
  53. package/dist/ui/assets/{latex-Cd_geX3E.js → latex-BB3zRnxC.js} +1 -1
  54. package/dist/ui/assets/{liquid-C6ky5sJ-.js → liquid-CIi5Mvpv.js} +1 -1
  55. package/dist/ui/assets/{lua-dgyBbeof.js → lua-Cox7XDiW.js} +1 -1
  56. package/dist/ui/assets/{marko-Dy5mfXY9.js → marko-ruBIRV0n.js} +1 -1
  57. package/dist/ui/assets/{mdc-7NUvxgI1.js → mdc-CcbvB9U5.js} +1 -1
  58. package/dist/ui/assets/{nginx-C-KiEXbI.js → nginx-DtWz6ZYE.js} +1 -1
  59. package/dist/ui/assets/{nim-BBw8kfl9.js → nim-BV5BR4P7.js} +1 -1
  60. package/dist/ui/assets/{perl-CyxhJgbO.js → perl-BvzoSaJ4.js} +1 -1
  61. package/dist/ui/assets/{php-m4LAOpam.js → php-C-clrAOy.js} +1 -1
  62. package/dist/ui/assets/{pug-Cpl0baYI.js → pug-E07_GXAr.js} +1 -1
  63. package/dist/ui/assets/{qml-BoSGNgIb.js → qml-dqt33dti.js} +1 -1
  64. package/dist/ui/assets/{r-C0JhqQd5.js → r-BcVQxsAw.js} +1 -1
  65. package/dist/ui/assets/{razor-CntsBNoh.js → razor-BW5T_UZ-.js} +1 -1
  66. package/dist/ui/assets/{regexp-BNZmOCRl.js → regexp-Ed_DEsuU.js} +1 -1
  67. package/dist/ui/assets/{review-payload-BgdWHWiv.js → review-payload-Dsmx33pY.js} +1 -1
  68. package/dist/ui/assets/{rst-BZ2XHIxM.js → rst-BkcGqLs8.js} +1 -1
  69. package/dist/ui/assets/{ruby-BIthfCTP.js → ruby-Sf_X_YID.js} +1 -1
  70. package/dist/ui/assets/{sas-5aT3fOla.js → sas-COsombsA.js} +1 -1
  71. package/dist/ui/assets/{scss-DZPfdkDy.js → scss-K6Ylfa26.js} +1 -1
  72. package/dist/ui/assets/{shellscript-DbhKc5Xj.js → shellscript-D2EdqkgV.js} +1 -1
  73. package/dist/ui/assets/{shellsession-X9-bcyHh.js → shellsession-jRXgXrnI.js} +1 -1
  74. package/dist/ui/assets/{soy-Bd4tecrW.js → soy-BGspKQWW.js} +1 -1
  75. package/dist/ui/assets/{sql-Chf5JrDn.js → sql-C4lAmYnM.js} +1 -1
  76. package/dist/ui/assets/{stata-DvMMIc_T.js → stata-DQWvRqxp.js} +1 -1
  77. package/dist/ui/assets/{surrealql-D5FMzVF5.js → surrealql-OmKt9XJ_.js} +1 -1
  78. package/dist/ui/assets/{svelte-CTiVXZrv.js → svelte-D0rUmwDI.js} +1 -1
  79. package/dist/ui/assets/{templ-ldnfq-7w.js → templ-B-U0RMc0.js} +1 -1
  80. package/dist/ui/assets/{tex-QW0_7T2r.js → tex-BIHD-btS.js} +1 -1
  81. package/dist/ui/assets/{ts-tags-CAKiWIBx.js → ts-tags-CGuDWfPC.js} +1 -1
  82. package/dist/ui/assets/{tsx-Bu9xJJmq.js → tsx-B0-h_HlD.js} +1 -1
  83. package/dist/ui/assets/{twig-Dp1752_V.js → twig-BN2qDJWN.js} +1 -1
  84. package/dist/ui/assets/{typescript-RfO03Kmm.js → typescript-BRnwPzY3.js} +1 -1
  85. package/dist/ui/assets/{useFileDiffInstance-DjqU4y1a.js → useFileDiffInstance-5ivdkUYO.js} +3 -3
  86. package/dist/ui/assets/{vue-Dlasv57G.js → vue-DekNU1qt.js} +1 -1
  87. package/dist/ui/assets/{vue-html-BHuJ7anu.js → vue-html-Bjlv-uvH.js} +1 -1
  88. package/dist/ui/assets/{vue-vine-CavE0PSk.js → vue-vine-C2Doytjq.js} +1 -1
  89. package/dist/ui/assets/{workspace-app-DGD9R89D.css → workspace-app-DASlX26z.css} +1 -1
  90. package/dist/ui/assets/{workspace-app-DDV_qdjx.js → workspace-app-DYi7VJam.js} +5 -5
  91. package/dist/ui/assets/{xml-Bbv64l4W.js → xml-pXbxxgI3.js} +1 -1
  92. package/dist/ui/assets/{xsl-Sro4gyQY.js → xsl-BEAlgDI2.js} +1 -1
  93. package/dist/ui/assets/{yaml-Du68c8sA.js → yaml-C1O-To71.js} +1 -1
  94. package/dist/ui/workspace-app.html +2 -2
  95. package/dist/user-config.js +21 -1
  96. package/dist/workspaces.js +3 -0
  97. package/docs/agent-profile-schema.md +165 -0
  98. package/docs/chatgpt-coding-workflow.md +24 -10
  99. package/docs/configuration.md +20 -8
  100. package/docs/gotchas.md +13 -0
  101. package/examples/agents/claude-implementer.md +30 -0
  102. package/examples/agents/codex-explorer.md +27 -0
  103. package/examples/agents/codex-qa-tester.md +29 -0
  104. package/examples/agents/codex-worker.md +25 -0
  105. package/examples/agents/copilot-reviewer.md +25 -0
  106. package/examples/agents/cursor-agent-worker.md +26 -0
  107. package/examples/agents/opencode-explorer.md +27 -0
  108. package/examples/agents/pi-reviewer.md +27 -0
  109. package/package.json +11 -3
  110. package/skills/subagent-delegation/SKILL.md +132 -0
  111. package/docs/codex-tool-mode-qa.md +0 -73
@@ -0,0 +1,119 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { delimiter, resolve } from "node:path";
3
+ import { removeDevspaceNodeModulesBinFromPath } from "./local-agent-path.js";
4
+ import { LOCAL_AGENT_PROVIDERS, } from "./local-agent-profiles.js";
5
+ export function getLocalAgentProviderAvailabilitySnapshot(env = process.env) {
6
+ return LOCAL_AGENT_PROVIDERS.map((provider) => checkLocalAgentProviderAvailability(provider, env));
7
+ }
8
+ export function checkLocalAgentProviderAvailability(provider, env = process.env) {
9
+ switch (provider) {
10
+ case "codex":
11
+ return packageAvailability(provider, "@openai/codex-sdk");
12
+ case "claude":
13
+ return packageAvailability(provider, "@anthropic-ai/claude-agent-sdk");
14
+ case "opencode":
15
+ return packageAvailability(provider, "@opencode-ai/sdk/v2");
16
+ case "pi":
17
+ return commandAvailability(provider, env.PI_COMMAND ?? "pi", {
18
+ env: piAvailabilityEnvironment(env),
19
+ });
20
+ case "cursor":
21
+ return commandAvailability(provider, "cursor-agent");
22
+ case "copilot":
23
+ return commandAvailability(provider, "copilot");
24
+ }
25
+ }
26
+ export function assertLocalAgentProviderAvailable(provider, env = process.env) {
27
+ const availability = checkLocalAgentProviderAvailability(provider, env);
28
+ if (availability.available)
29
+ return;
30
+ throw new Error(`${provider} provider is not available: ${availability.reason ?? "provider preflight failed"}`);
31
+ }
32
+ export function formatLocalAgentProviderAvailabilitySummary(providers) {
33
+ const available = providers
34
+ .filter((provider) => provider.available)
35
+ .map((provider) => provider.name);
36
+ const unavailable = providers
37
+ .filter((provider) => !provider.available)
38
+ .map((provider) => `${provider.name} (${provider.reason ?? "unavailable"})`);
39
+ return [
40
+ available.length > 0 ? `available: ${available.join(", ")}` : undefined,
41
+ unavailable.length > 0 ? `unavailable: ${unavailable.join(", ")}` : undefined,
42
+ ].filter(Boolean).join("; ");
43
+ }
44
+ function packageAvailability(provider, packageName) {
45
+ try {
46
+ import.meta.resolve(packageName);
47
+ return { name: provider, available: true };
48
+ }
49
+ catch {
50
+ return {
51
+ name: provider,
52
+ available: false,
53
+ reason: `${packageName} package not found`,
54
+ };
55
+ }
56
+ }
57
+ function commandAvailability(provider, command, options = {}) {
58
+ const executable = resolveCommand(command, options.env);
59
+ if (!executable) {
60
+ return {
61
+ name: provider,
62
+ available: false,
63
+ reason: `${command} executable not found`,
64
+ };
65
+ }
66
+ return { name: provider, available: true };
67
+ }
68
+ function resolveCommand(command, env = process.env) {
69
+ const commandHasPath = command.includes("/") || command.includes("\\");
70
+ if (commandHasPath)
71
+ return executableExists(command, env) ? command : undefined;
72
+ for (const candidate of candidateCommandPaths(command, env)) {
73
+ if (executableExists(candidate, env))
74
+ return candidate;
75
+ }
76
+ return undefined;
77
+ }
78
+ function candidateCommandPaths(command, env) {
79
+ const path = env.PATH;
80
+ if (!path)
81
+ return [];
82
+ const extensions = process.platform === "win32"
83
+ ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
84
+ .split(";")
85
+ .filter(Boolean)
86
+ : [""];
87
+ const candidates = [];
88
+ for (const directory of path.split(delimiter)) {
89
+ if (!directory)
90
+ continue;
91
+ for (const extension of extensions) {
92
+ candidates.push(resolve(directory, `${command}${extension}`));
93
+ }
94
+ }
95
+ return candidates;
96
+ }
97
+ function executableExists(command, env) {
98
+ const result = spawnSync(command, ["--version"], {
99
+ encoding: "utf8",
100
+ env,
101
+ windowsHide: true,
102
+ timeout: 5_000,
103
+ });
104
+ const code = typeof result.error === "object" && result.error && "code" in result.error
105
+ ? result.error.code
106
+ : undefined;
107
+ return code !== "ENOENT";
108
+ }
109
+ function piAvailabilityEnvironment(env) {
110
+ if (env.PI_COMMAND)
111
+ return env;
112
+ const path = env.PATH;
113
+ if (!path)
114
+ return env;
115
+ return {
116
+ ...env,
117
+ PATH: removeDevspaceNodeModulesBinFromPath(path),
118
+ };
119
+ }
@@ -0,0 +1,24 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { delimiter, resolve, sep } from "node:path";
3
+ export function removeDevspaceNodeModulesBinFromPath(pathValue) {
4
+ return pathValue
5
+ .split(delimiter)
6
+ .filter((entry) => entry && !isDevspaceNodeModulesBin(entry))
7
+ .join(delimiter);
8
+ }
9
+ function isDevspaceNodeModulesBin(pathEntry) {
10
+ const resolvedEntry = resolve(pathEntry);
11
+ if (!resolvedEntry.endsWith(`${sep}node_modules${sep}.bin`)) {
12
+ return false;
13
+ }
14
+ const packageJson = resolve(resolvedEntry, "..", "..", "package.json");
15
+ if (!existsSync(packageJson))
16
+ return false;
17
+ try {
18
+ const packageInfo = JSON.parse(readFileSync(packageJson, "utf8"));
19
+ return packageInfo.name === "@waishnav/devspace";
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
@@ -0,0 +1,135 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { basename, join, resolve } from "node:path";
4
+ import { parse as parseYaml } from "yaml";
5
+ export const LOCAL_AGENT_PROVIDERS = [
6
+ "codex",
7
+ "claude",
8
+ "opencode",
9
+ "pi",
10
+ "cursor",
11
+ "copilot",
12
+ ];
13
+ const FRONTMATTER_DELIMITER = "---";
14
+ const PROVIDERS = new Set(LOCAL_AGENT_PROVIDERS);
15
+ export async function loadLocalAgentProfiles(config, workspaceRoot) {
16
+ if (!config.subagents)
17
+ return [];
18
+ const profileDirs = [
19
+ config.devspaceAgentsDir,
20
+ join(workspaceRoot, ".devspace", "agents"),
21
+ ];
22
+ const profilesByName = new Map();
23
+ for (const directory of profileDirs) {
24
+ for (const profile of await loadProfilesFromDirectory(directory)) {
25
+ profilesByName.set(profile.name, profile);
26
+ }
27
+ }
28
+ return Array.from(profilesByName.values())
29
+ .filter((profile) => !profile.disabled)
30
+ .sort((a, b) => a.name.localeCompare(b.name));
31
+ }
32
+ export function summarizeLocalAgentProfile(profile) {
33
+ return {
34
+ name: profile.name,
35
+ description: profile.description,
36
+ provider: profile.provider,
37
+ model: profile.model,
38
+ thinking: profile.thinking,
39
+ };
40
+ }
41
+ async function loadProfilesFromDirectory(directory) {
42
+ const resolvedDirectory = resolve(directory);
43
+ if (!existsSync(resolvedDirectory))
44
+ return [];
45
+ const entries = await readdir(resolvedDirectory, { withFileTypes: true });
46
+ const profiles = [];
47
+ for (const entry of entries) {
48
+ if (!entry.isFile())
49
+ continue;
50
+ if (!entry.name.endsWith(".md"))
51
+ continue;
52
+ const filePath = join(resolvedDirectory, entry.name);
53
+ try {
54
+ profiles.push(await loadProfileFile(filePath));
55
+ }
56
+ catch (error) {
57
+ console.warn(`Skipping invalid subagent profile ${filePath}: ${errorMessage(error)}`);
58
+ }
59
+ }
60
+ return profiles;
61
+ }
62
+ async function loadProfileFile(filePath) {
63
+ const content = await readFile(filePath, "utf8");
64
+ const parsed = parseFrontmatter(content, filePath);
65
+ return profileFromFrontmatter(parsed.frontmatter, parsed.body, filePath);
66
+ }
67
+ function parseFrontmatter(content, filePath) {
68
+ const normalized = content.replace(/^\uFEFF/, "");
69
+ const lines = normalized.split(/\r?\n/);
70
+ if (lines[0]?.trim() !== FRONTMATTER_DELIMITER) {
71
+ throw new Error(`Subagent profile is missing frontmatter: ${filePath}`);
72
+ }
73
+ const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === FRONTMATTER_DELIMITER);
74
+ if (endIndex === -1) {
75
+ throw new Error(`Subagent profile frontmatter is not closed: ${filePath}`);
76
+ }
77
+ return {
78
+ frontmatter: parseProfileYaml(lines.slice(1, endIndex).join("\n"), filePath),
79
+ body: lines.slice(endIndex + 1).join("\n").trim(),
80
+ };
81
+ }
82
+ function parseProfileYaml(source, filePath) {
83
+ let parsed;
84
+ try {
85
+ parsed = parseYaml(source) ?? {};
86
+ }
87
+ catch (error) {
88
+ throw new Error(`Unable to parse subagent profile frontmatter: ${filePath}: ${errorMessage(error)}`);
89
+ }
90
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
91
+ throw new Error(`Subagent profile frontmatter must be a mapping: ${filePath}`);
92
+ }
93
+ return parsed;
94
+ }
95
+ function profileFromFrontmatter(frontmatter, body, filePath) {
96
+ const name = readString(frontmatter, "name") ?? basename(filePath, ".md");
97
+ const description = readString(frontmatter, "description");
98
+ const provider = readProvider(frontmatter, filePath);
99
+ if (!description) {
100
+ throw new Error(`Subagent profile is missing description: ${filePath}`);
101
+ }
102
+ return {
103
+ name,
104
+ description,
105
+ provider,
106
+ model: readString(frontmatter, "model"),
107
+ thinking: readString(frontmatter, "thinking"),
108
+ filePath,
109
+ body,
110
+ disabled: frontmatter.disabled === true,
111
+ };
112
+ }
113
+ function readProvider(frontmatter, filePath) {
114
+ const provider = readString(frontmatter, "provider");
115
+ if (!provider) {
116
+ throw new Error(`Subagent profile is missing provider: ${filePath}`);
117
+ }
118
+ if (!PROVIDERS.has(provider)) {
119
+ throw new Error(`Subagent profile provider must be codex, claude, opencode, pi, cursor, or copilot: ${filePath}`);
120
+ }
121
+ return provider;
122
+ }
123
+ export function isLocalAgentProvider(value) {
124
+ return PROVIDERS.has(value);
125
+ }
126
+ function readString(frontmatter, key) {
127
+ const value = frontmatter[key];
128
+ if (typeof value !== "string")
129
+ return undefined;
130
+ const trimmed = value.trim();
131
+ return trimmed || undefined;
132
+ }
133
+ function errorMessage(error) {
134
+ return error instanceof Error ? error.message : String(error);
135
+ }
@@ -0,0 +1,48 @@
1
+ function sandboxModeFor(writeMode) {
2
+ switch (writeMode) {
3
+ case "allowed":
4
+ return "workspace-write";
5
+ case "full_access":
6
+ return "danger-full-access";
7
+ case "read_only":
8
+ case undefined:
9
+ return "read-only";
10
+ }
11
+ }
12
+ function threadOptionsFor(input) {
13
+ return {
14
+ workingDirectory: input.workspace,
15
+ sandboxMode: sandboxModeFor(input.writeMode),
16
+ approvalPolicy: "never",
17
+ model: input.model,
18
+ modelReasoningEffort: input.thinking,
19
+ };
20
+ }
21
+ export class CodexSdkLocalAgentRuntime {
22
+ provider = "codex";
23
+ codex;
24
+ constructor(codex) {
25
+ this.codex = codex;
26
+ }
27
+ async run(input) {
28
+ const options = threadOptionsFor(input);
29
+ const thread = input.providerSessionId
30
+ ? this.codex.resumeThread(input.providerSessionId, options)
31
+ : this.codex.startThread(options);
32
+ const turn = await thread.run(input.prompt);
33
+ return {
34
+ provider: this.provider,
35
+ providerSessionId: thread.id,
36
+ finalResponse: turn.finalResponse,
37
+ items: turn.items,
38
+ };
39
+ }
40
+ }
41
+ export async function createCodexSdkLocalAgentRuntime(options, codexFactory) {
42
+ const factory = codexFactory ?? (await defaultCodexFactory());
43
+ return new CodexSdkLocalAgentRuntime(factory(options));
44
+ }
45
+ async function defaultCodexFactory() {
46
+ const module = await import("@openai/codex-sdk");
47
+ return (options) => new module.Codex(options);
48
+ }
@@ -0,0 +1,145 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { openDatabase } from "./db/client.js";
4
+ export class LocalAgentStore {
5
+ database;
6
+ constructor(stateDir) {
7
+ this.database = openDatabase(stateDir);
8
+ }
9
+ list(scope = {}) {
10
+ let rows;
11
+ if (scope.workspaceId) {
12
+ rows = this.database.sqlite
13
+ .prepare(`select * from local_agent_sessions
14
+ where workspace_id = ?
15
+ order by updated_at desc`)
16
+ .all(scope.workspaceId);
17
+ }
18
+ else if (scope.workspaceRoot) {
19
+ rows = this.database.sqlite
20
+ .prepare(`select * from local_agent_sessions
21
+ where workspace_root = ?
22
+ order by updated_at desc`)
23
+ .all(resolve(scope.workspaceRoot));
24
+ }
25
+ else {
26
+ rows = this.database.sqlite
27
+ .prepare("select * from local_agent_sessions order by updated_at desc")
28
+ .all();
29
+ }
30
+ return rows.map(rowToLocalAgentRecord);
31
+ }
32
+ create(input) {
33
+ const now = new Date().toISOString();
34
+ const record = {
35
+ id: `agt_${randomUUID().replaceAll("-", "").slice(0, 8)}`,
36
+ workspaceId: input.workspaceId,
37
+ workspaceRoot: resolve(input.workspaceRoot),
38
+ profileName: input.profileName,
39
+ provider: input.provider,
40
+ model: input.model,
41
+ thinking: input.thinking,
42
+ status: "starting",
43
+ createdAt: now,
44
+ updatedAt: now,
45
+ };
46
+ this.database.sqlite
47
+ .prepare(`insert into local_agent_sessions (
48
+ id,
49
+ workspace_id,
50
+ workspace_root,
51
+ profile_name,
52
+ provider,
53
+ model,
54
+ thinking,
55
+ status,
56
+ created_at,
57
+ updated_at
58
+ ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
59
+ .run(record.id, record.workspaceId ?? null, record.workspaceRoot, record.profileName, record.provider, record.model ?? null, record.thinking ?? null, record.status, record.createdAt, record.updatedAt);
60
+ return record;
61
+ }
62
+ get(idOrPrefix) {
63
+ const exact = this.database.sqlite
64
+ .prepare(`select * from local_agent_sessions
65
+ where id = ? or provider_session_id = ?
66
+ limit 1`)
67
+ .get(idOrPrefix, idOrPrefix);
68
+ if (exact)
69
+ return rowToLocalAgentRecord(exact);
70
+ const matches = this.database.sqlite
71
+ .prepare(`select * from local_agent_sessions
72
+ where id like ? escape '\\' or provider_session_id like ? escape '\\'
73
+ order by updated_at desc`)
74
+ .all(`${escapeLike(idOrPrefix)}%`, `${escapeLike(idOrPrefix)}%`);
75
+ return matches.length === 1 ? rowToLocalAgentRecord(matches[0]) : undefined;
76
+ }
77
+ update(id, patch) {
78
+ const current = this.getById(id);
79
+ if (!current)
80
+ throw new Error(`Unknown subagent id: ${id}`);
81
+ const updated = {
82
+ ...current,
83
+ ...patch,
84
+ updatedAt: new Date().toISOString(),
85
+ };
86
+ this.database.sqlite
87
+ .prepare(`update local_agent_sessions set
88
+ workspace_id = ?,
89
+ workspace_root = ?,
90
+ profile_name = ?,
91
+ provider = ?,
92
+ model = ?,
93
+ thinking = ?,
94
+ provider_session_id = ?,
95
+ status = ?,
96
+ latest_response = ?,
97
+ error = ?,
98
+ updated_at = ?
99
+ where id = ?`)
100
+ .run(updated.workspaceId ?? null, resolve(updated.workspaceRoot), updated.profileName, updated.provider, updated.model ?? null, updated.thinking ?? null, updated.providerSessionId ?? null, updated.status, updated.latestResponse ?? null, updated.error ?? null, updated.updatedAt, updated.id);
101
+ return updated;
102
+ }
103
+ close() {
104
+ this.database.close();
105
+ }
106
+ getById(id) {
107
+ const row = this.database.sqlite
108
+ .prepare("select * from local_agent_sessions where id = ?")
109
+ .get(id);
110
+ return row ? rowToLocalAgentRecord(row) : undefined;
111
+ }
112
+ }
113
+ export function createLocalAgentStore(config) {
114
+ return new LocalAgentStore(config.stateDir);
115
+ }
116
+ function rowToLocalAgentRecord(row) {
117
+ return {
118
+ id: row.id,
119
+ workspaceId: row.workspace_id ?? undefined,
120
+ workspaceRoot: row.workspace_root,
121
+ profileName: row.profile_name,
122
+ provider: row.provider,
123
+ model: row.model ?? undefined,
124
+ thinking: row.thinking ?? undefined,
125
+ providerSessionId: row.provider_session_id ?? undefined,
126
+ status: readStatus(row.status),
127
+ latestResponse: row.latest_response ?? undefined,
128
+ error: row.error ?? undefined,
129
+ createdAt: row.created_at,
130
+ updatedAt: row.updated_at,
131
+ };
132
+ }
133
+ function readStatus(status) {
134
+ if (status === "starting" ||
135
+ status === "running" ||
136
+ status === "idle" ||
137
+ status === "error" ||
138
+ status === "stopped") {
139
+ return status;
140
+ }
141
+ return "error";
142
+ }
143
+ function escapeLike(value) {
144
+ return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
145
+ }
@@ -0,0 +1,80 @@
1
+ import { isLocalAgentProvider, LOCAL_AGENT_PROVIDERS, } from "./local-agent-profiles.js";
2
+ export function parseLocalAgentRunArgs(args) {
3
+ const [target, ...rest] = args;
4
+ if (!target) {
5
+ throw new Error('Usage: devspace agents run <profile-or-provider-or-id> [--model <model>] [--thinking <level>] "<prompt>"');
6
+ }
7
+ let model;
8
+ let thinking;
9
+ const promptParts = [];
10
+ for (let index = 0; index < rest.length; index += 1) {
11
+ const part = rest[index];
12
+ if (part === "--model") {
13
+ const value = rest[index + 1]?.trim();
14
+ if (!value)
15
+ throw new Error("Missing value for --model.");
16
+ model = value;
17
+ index += 1;
18
+ continue;
19
+ }
20
+ if (part?.startsWith("--model=")) {
21
+ const value = part.slice("--model=".length).trim();
22
+ if (!value)
23
+ throw new Error("Missing value for --model.");
24
+ model = value;
25
+ continue;
26
+ }
27
+ if (part === "--thinking") {
28
+ const value = rest[index + 1]?.trim();
29
+ if (!value)
30
+ throw new Error("Missing value for --thinking.");
31
+ thinking = value;
32
+ index += 1;
33
+ continue;
34
+ }
35
+ if (part?.startsWith("--thinking=")) {
36
+ const value = part.slice("--thinking=".length).trim();
37
+ if (!value)
38
+ throw new Error("Missing value for --thinking.");
39
+ thinking = value;
40
+ continue;
41
+ }
42
+ promptParts.push(part ?? "");
43
+ }
44
+ const prompt = promptParts.join(" ").trim();
45
+ if (!prompt) {
46
+ throw new Error('Usage: devspace agents run <profile-or-provider-or-id> [--model <model>] [--thinking <level>] "<prompt>"');
47
+ }
48
+ return { target, prompt, model, thinking };
49
+ }
50
+ export function resolveLocalAgentTarget(target, profiles, modelOverride, thinkingOverride) {
51
+ const profile = profiles.find((candidate) => candidate.name === target);
52
+ if (profile) {
53
+ return {
54
+ kind: "profile",
55
+ name: profile.name,
56
+ provider: profile.provider,
57
+ model: modelOverride ?? profile.model,
58
+ thinking: thinkingOverride ?? profile.thinking,
59
+ profile,
60
+ };
61
+ }
62
+ if (isLocalAgentProvider(target)) {
63
+ return {
64
+ kind: "provider",
65
+ name: target,
66
+ provider: target,
67
+ model: modelOverride,
68
+ thinking: thinkingOverride,
69
+ };
70
+ }
71
+ return undefined;
72
+ }
73
+ export function formatAvailableLocalAgentTargets(profiles) {
74
+ const profileNames = profiles.map((profile) => profile.name);
75
+ const parts = [
76
+ profileNames.length > 0 ? `profiles: ${profileNames.join(", ")}` : undefined,
77
+ `providers: ${LOCAL_AGENT_PROVIDERS.join(", ")}`,
78
+ ].filter(Boolean);
79
+ return parts.join("; ");
80
+ }
@@ -26,7 +26,7 @@ function terminalSize(value, fallback) {
26
26
  }
27
27
  return value;
28
28
  }
29
- function processEnvironment() {
29
+ function processEnvironment(input) {
30
30
  return {
31
31
  ...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)),
32
32
  NO_COLOR: "1",
@@ -37,6 +37,8 @@ function processEnvironment() {
37
37
  CODEX_CI: "1",
38
38
  LANG: process.env.LANG ?? "C.UTF-8",
39
39
  LC_ALL: process.env.LC_ALL ?? "C.UTF-8",
40
+ ...(input?.workspaceId ? { DEVSPACE_WORKSPACE_ID: input.workspaceId } : {}),
41
+ ...(input?.workspaceRoot ? { DEVSPACE_WORKSPACE_ROOT: input.workspaceRoot } : {}),
40
42
  };
41
43
  }
42
44
  function codePointLength(value) {
@@ -236,7 +238,10 @@ export class ProcessSessionManager {
236
238
  const detached = process.platform !== "win32";
237
239
  const child = spawn(input.command, {
238
240
  cwd: input.cwd,
239
- env: processEnvironment(),
241
+ env: processEnvironment({
242
+ workspaceId: input.workspaceId,
243
+ workspaceRoot: input.workspaceRoot,
244
+ }),
240
245
  stdio: "pipe",
241
246
  windowsHide: true,
242
247
  detached,
@@ -265,7 +270,10 @@ export class ProcessSessionManager {
265
270
  try {
266
271
  pty = nodePty.spawn(shell.executable, shell.args, {
267
272
  cwd: input.cwd,
268
- env: processEnvironment(),
273
+ env: processEnvironment({
274
+ workspaceId: input.workspaceId,
275
+ workspaceRoot: input.workspaceRoot,
276
+ }),
269
277
  name: "xterm-256color",
270
278
  cols: session.columns,
271
279
  rows: session.rows,