@agentuity/cli 2.0.11 → 2.0.12

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 (220) hide show
  1. package/dist/cache/resource-region.d.ts.map +1 -1
  2. package/dist/cache/resource-region.js +48 -25
  3. package/dist/cache/resource-region.js.map +1 -1
  4. package/dist/cmd/build/vite/bun-dev-server.d.ts +20 -0
  5. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  6. package/dist/cmd/build/vite/bun-dev-server.js +62 -4
  7. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  8. package/dist/cmd/build/vite/index.d.ts +0 -1
  9. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  10. package/dist/cmd/build/vite/index.js +0 -1
  11. package/dist/cmd/build/vite/index.js.map +1 -1
  12. package/dist/cmd/build/vite/static-renderer.d.ts +17 -0
  13. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/static-renderer.js +18 -6
  15. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  16. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  17. package/dist/cmd/build/vite/vite-asset-server-config.js +34 -27
  18. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  19. package/dist/cmd/build/vite/vite-asset-server.d.ts +9 -0
  20. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/vite-asset-server.js +5 -1
  22. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  23. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  24. package/dist/cmd/build/vite/vite-builder.js +12 -1
  25. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  26. package/dist/cmd/build/vite/ws-proxy.d.ts +15 -1
  27. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/ws-proxy.js +33 -0
  29. package/dist/cmd/build/vite/ws-proxy.js.map +1 -1
  30. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  31. package/dist/cmd/cloud/deploy.js +98 -39
  32. package/dist/cmd/cloud/deploy.js.map +1 -1
  33. package/dist/cmd/cloud/sandbox/checkpoint/create.d.ts.map +1 -1
  34. package/dist/cmd/cloud/sandbox/checkpoint/create.js +3 -4
  35. package/dist/cmd/cloud/sandbox/checkpoint/create.js.map +1 -1
  36. package/dist/cmd/cloud/sandbox/checkpoint/delete.d.ts.map +1 -1
  37. package/dist/cmd/cloud/sandbox/checkpoint/delete.js +3 -4
  38. package/dist/cmd/cloud/sandbox/checkpoint/delete.js.map +1 -1
  39. package/dist/cmd/cloud/sandbox/checkpoint/list.d.ts.map +1 -1
  40. package/dist/cmd/cloud/sandbox/checkpoint/list.js +3 -4
  41. package/dist/cmd/cloud/sandbox/checkpoint/list.js.map +1 -1
  42. package/dist/cmd/cloud/sandbox/checkpoint/restore.d.ts.map +1 -1
  43. package/dist/cmd/cloud/sandbox/checkpoint/restore.js +3 -4
  44. package/dist/cmd/cloud/sandbox/checkpoint/restore.js.map +1 -1
  45. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  46. package/dist/cmd/cloud/sandbox/create.js +13 -4
  47. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  48. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
  49. package/dist/cmd/cloud/sandbox/delete.js +3 -4
  50. package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
  51. package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -1
  52. package/dist/cmd/cloud/sandbox/env.js +3 -5
  53. package/dist/cmd/cloud/sandbox/env.js.map +1 -1
  54. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  55. package/dist/cmd/cloud/sandbox/exec.js +114 -41
  56. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  57. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -1
  58. package/dist/cmd/cloud/sandbox/execution/list.js +3 -5
  59. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -1
  60. package/dist/cmd/cloud/sandbox/fs/cp.d.ts.map +1 -1
  61. package/dist/cmd/cloud/sandbox/fs/cp.js +61 -113
  62. package/dist/cmd/cloud/sandbox/fs/cp.js.map +1 -1
  63. package/dist/cmd/cloud/sandbox/fs/download.d.ts.map +1 -1
  64. package/dist/cmd/cloud/sandbox/fs/download.js +11 -22
  65. package/dist/cmd/cloud/sandbox/fs/download.js.map +1 -1
  66. package/dist/cmd/cloud/sandbox/fs/ls.d.ts.map +1 -1
  67. package/dist/cmd/cloud/sandbox/fs/ls.js +3 -5
  68. package/dist/cmd/cloud/sandbox/fs/ls.js.map +1 -1
  69. package/dist/cmd/cloud/sandbox/fs/mkdir.d.ts.map +1 -1
  70. package/dist/cmd/cloud/sandbox/fs/mkdir.js +3 -5
  71. package/dist/cmd/cloud/sandbox/fs/mkdir.js.map +1 -1
  72. package/dist/cmd/cloud/sandbox/fs/rm.d.ts.map +1 -1
  73. package/dist/cmd/cloud/sandbox/fs/rm.js +3 -5
  74. package/dist/cmd/cloud/sandbox/fs/rm.js.map +1 -1
  75. package/dist/cmd/cloud/sandbox/fs/rmdir.d.ts.map +1 -1
  76. package/dist/cmd/cloud/sandbox/fs/rmdir.js +3 -5
  77. package/dist/cmd/cloud/sandbox/fs/rmdir.js.map +1 -1
  78. package/dist/cmd/cloud/sandbox/fs/upload.d.ts.map +1 -1
  79. package/dist/cmd/cloud/sandbox/fs/upload.js +7 -8
  80. package/dist/cmd/cloud/sandbox/fs/upload.js.map +1 -1
  81. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  82. package/dist/cmd/cloud/sandbox/get.js +21 -7
  83. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  84. package/dist/cmd/cloud/sandbox/job/create.d.ts.map +1 -1
  85. package/dist/cmd/cloud/sandbox/job/create.js +3 -4
  86. package/dist/cmd/cloud/sandbox/job/create.js.map +1 -1
  87. package/dist/cmd/cloud/sandbox/job/destroy.d.ts.map +1 -1
  88. package/dist/cmd/cloud/sandbox/job/destroy.js +3 -4
  89. package/dist/cmd/cloud/sandbox/job/destroy.js.map +1 -1
  90. package/dist/cmd/cloud/sandbox/job/get.d.ts.map +1 -1
  91. package/dist/cmd/cloud/sandbox/job/get.js +3 -4
  92. package/dist/cmd/cloud/sandbox/job/get.js.map +1 -1
  93. package/dist/cmd/cloud/sandbox/job/list.d.ts.map +1 -1
  94. package/dist/cmd/cloud/sandbox/job/list.js +3 -4
  95. package/dist/cmd/cloud/sandbox/job/list.js.map +1 -1
  96. package/dist/cmd/cloud/sandbox/job/logs.d.ts.map +1 -1
  97. package/dist/cmd/cloud/sandbox/job/logs.js +4 -4
  98. package/dist/cmd/cloud/sandbox/job/logs.js.map +1 -1
  99. package/dist/cmd/cloud/sandbox/pause.d.ts.map +1 -1
  100. package/dist/cmd/cloud/sandbox/pause.js +21 -5
  101. package/dist/cmd/cloud/sandbox/pause.js.map +1 -1
  102. package/dist/cmd/cloud/sandbox/resume.d.ts.map +1 -1
  103. package/dist/cmd/cloud/sandbox/resume.js +3 -4
  104. package/dist/cmd/cloud/sandbox/resume.js.map +1 -1
  105. package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -1
  106. package/dist/cmd/cloud/sandbox/run.js +36 -7
  107. package/dist/cmd/cloud/sandbox/run.js.map +1 -1
  108. package/dist/cmd/cloud/sandbox/util.d.ts +19 -0
  109. package/dist/cmd/cloud/sandbox/util.d.ts.map +1 -1
  110. package/dist/cmd/cloud/sandbox/util.js +40 -2
  111. package/dist/cmd/cloud/sandbox/util.js.map +1 -1
  112. package/dist/cmd/coder/create.js +7 -7
  113. package/dist/cmd/coder/create.js.map +1 -1
  114. package/dist/cmd/coder/start.d.ts.map +1 -1
  115. package/dist/cmd/coder/start.js +3 -0
  116. package/dist/cmd/coder/start.js.map +1 -1
  117. package/dist/cmd/coder/tui-init.js +1 -1
  118. package/dist/cmd/coder/tui-init.js.map +1 -1
  119. package/dist/cmd/coder/update.js +8 -8
  120. package/dist/cmd/coder/update.js.map +1 -1
  121. package/dist/cmd/coder/workspace/create.d.ts.map +1 -1
  122. package/dist/cmd/coder/workspace/create.js +49 -21
  123. package/dist/cmd/coder/workspace/create.js.map +1 -1
  124. package/dist/cmd/coder/workspace/index.d.ts.map +1 -1
  125. package/dist/cmd/coder/workspace/index.js +1 -1
  126. package/dist/cmd/coder/workspace/index.js.map +1 -1
  127. package/dist/cmd/dev/dev-lock.d.ts.map +1 -1
  128. package/dist/cmd/dev/dev-lock.js +43 -17
  129. package/dist/cmd/dev/dev-lock.js.map +1 -1
  130. package/dist/cmd/dev/index.d.ts.map +1 -1
  131. package/dist/cmd/dev/index.js +211 -125
  132. package/dist/cmd/dev/index.js.map +1 -1
  133. package/dist/cmd/dev/process-manager.d.ts +41 -1
  134. package/dist/cmd/dev/process-manager.d.ts.map +1 -1
  135. package/dist/cmd/dev/process-manager.js +160 -31
  136. package/dist/cmd/dev/process-manager.js.map +1 -1
  137. package/dist/cmd/project/create.d.ts.map +1 -1
  138. package/dist/cmd/project/create.js +0 -2
  139. package/dist/cmd/project/create.js.map +1 -1
  140. package/dist/cmd/project/index.d.ts.map +1 -1
  141. package/dist/cmd/project/index.js +0 -3
  142. package/dist/cmd/project/index.js.map +1 -1
  143. package/dist/cmd/project/template-flow.d.ts +0 -1
  144. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  145. package/dist/cmd/project/template-flow.js +1 -124
  146. package/dist/cmd/project/template-flow.js.map +1 -1
  147. package/package.json +7 -7
  148. package/src/cache/resource-region.ts +68 -44
  149. package/src/cmd/ai/prompt/web.md +43 -17
  150. package/src/cmd/build/vite/bun-dev-server.ts +92 -6
  151. package/src/cmd/build/vite/index.ts +0 -1
  152. package/src/cmd/build/vite/static-renderer.ts +18 -7
  153. package/src/cmd/build/vite/vite-asset-server-config.ts +37 -27
  154. package/src/cmd/build/vite/vite-asset-server.ts +5 -1
  155. package/src/cmd/build/vite/vite-builder.ts +12 -1
  156. package/src/cmd/build/vite/ws-proxy.ts +52 -3
  157. package/src/cmd/cloud/deploy.ts +117 -49
  158. package/src/cmd/cloud/sandbox/checkpoint/create.ts +10 -4
  159. package/src/cmd/cloud/sandbox/checkpoint/delete.ts +10 -4
  160. package/src/cmd/cloud/sandbox/checkpoint/list.ts +10 -4
  161. package/src/cmd/cloud/sandbox/checkpoint/restore.ts +10 -4
  162. package/src/cmd/cloud/sandbox/create.ts +14 -4
  163. package/src/cmd/cloud/sandbox/delete.ts +10 -4
  164. package/src/cmd/cloud/sandbox/env.ts +10 -5
  165. package/src/cmd/cloud/sandbox/exec.ts +157 -42
  166. package/src/cmd/cloud/sandbox/execution/list.ts +10 -5
  167. package/src/cmd/cloud/sandbox/fs/cp.ts +94 -126
  168. package/src/cmd/cloud/sandbox/fs/download.ts +18 -25
  169. package/src/cmd/cloud/sandbox/fs/ls.ts +10 -5
  170. package/src/cmd/cloud/sandbox/fs/mkdir.ts +10 -5
  171. package/src/cmd/cloud/sandbox/fs/rm.ts +10 -5
  172. package/src/cmd/cloud/sandbox/fs/rmdir.ts +10 -5
  173. package/src/cmd/cloud/sandbox/fs/upload.ts +14 -8
  174. package/src/cmd/cloud/sandbox/get.ts +28 -7
  175. package/src/cmd/cloud/sandbox/job/create.ts +10 -4
  176. package/src/cmd/cloud/sandbox/job/destroy.ts +10 -4
  177. package/src/cmd/cloud/sandbox/job/get.ts +10 -4
  178. package/src/cmd/cloud/sandbox/job/list.ts +10 -4
  179. package/src/cmd/cloud/sandbox/job/logs.ts +11 -4
  180. package/src/cmd/cloud/sandbox/pause.ts +31 -5
  181. package/src/cmd/cloud/sandbox/resume.ts +10 -4
  182. package/src/cmd/cloud/sandbox/run.ts +49 -11
  183. package/src/cmd/cloud/sandbox/util.ts +63 -2
  184. package/src/cmd/coder/create.ts +8 -8
  185. package/src/cmd/coder/start.ts +3 -0
  186. package/src/cmd/coder/tui-init.ts +1 -1
  187. package/src/cmd/coder/update.ts +7 -7
  188. package/src/cmd/coder/workspace/create.ts +77 -26
  189. package/src/cmd/coder/workspace/index.ts +3 -1
  190. package/src/cmd/dev/dev-lock.ts +50 -16
  191. package/src/cmd/dev/index.ts +249 -134
  192. package/src/cmd/dev/process-manager.ts +173 -33
  193. package/src/cmd/project/create.ts +0 -2
  194. package/src/cmd/project/index.ts +0 -3
  195. package/src/cmd/project/template-flow.ts +0 -147
  196. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts +0 -45
  197. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts.map +0 -1
  198. package/dist/cmd/build/vite/public-asset-path-plugin.js +0 -166
  199. package/dist/cmd/build/vite/public-asset-path-plugin.js.map +0 -1
  200. package/dist/cmd/project/auth/generate.d.ts +0 -5
  201. package/dist/cmd/project/auth/generate.d.ts.map +0 -1
  202. package/dist/cmd/project/auth/generate.js +0 -102
  203. package/dist/cmd/project/auth/generate.js.map +0 -1
  204. package/dist/cmd/project/auth/index.d.ts +0 -2
  205. package/dist/cmd/project/auth/index.d.ts.map +0 -1
  206. package/dist/cmd/project/auth/index.js +0 -21
  207. package/dist/cmd/project/auth/index.js.map +0 -1
  208. package/dist/cmd/project/auth/init.d.ts +0 -2
  209. package/dist/cmd/project/auth/init.d.ts.map +0 -1
  210. package/dist/cmd/project/auth/init.js +0 -213
  211. package/dist/cmd/project/auth/init.js.map +0 -1
  212. package/dist/cmd/project/auth/shared.d.ts +0 -93
  213. package/dist/cmd/project/auth/shared.d.ts.map +0 -1
  214. package/dist/cmd/project/auth/shared.js +0 -475
  215. package/dist/cmd/project/auth/shared.js.map +0 -1
  216. package/src/cmd/build/vite/public-asset-path-plugin.ts +0 -209
  217. package/src/cmd/project/auth/generate.ts +0 -116
  218. package/src/cmd/project/auth/index.ts +0 -21
  219. package/src/cmd/project/auth/init.ts +0 -256
  220. package/src/cmd/project/auth/shared.ts +0 -591
@@ -1,17 +1,69 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { fstatSync, readFileSync, statSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import type { FileToWrite, Logger } from '@agentuity/core';
4
- import { APIClient, getServiceUrls, sandboxGet } from '@agentuity/server';
4
+ import { APIClient, getServiceUrls, sandboxGet, sandboxResolve } from '@agentuity/server';
5
5
  import { deleteResourceRegion, getResourceInfo, setResourceInfo } from '../../../cache';
6
6
  import { getGlobalCatalystAPIClient } from '../../../config';
7
7
  import { ErrorCode } from '../../../errors';
8
8
  import * as tui from '../../../tui';
9
9
  import type { AuthData, Config } from '../../../types';
10
10
 
11
+ /**
12
+ * Detect if a file descriptor is redirected to /dev/null (or NUL on Windows).
13
+ * Used to optimize stream creation - when output goes to /dev/null, we can
14
+ * skip creating the stream entirely on the server.
15
+ *
16
+ * @param fd - File descriptor (1 for stdout, 2 for stderr)
17
+ * @returns true if the fd points to /dev/null
18
+ */
19
+ export function detectNullStream(fd: number): boolean {
20
+ try {
21
+ const fdStat = fstatSync(fd);
22
+ const nullPath = process.platform === 'win32' ? 'NUL' : '/dev/null';
23
+ const nullStat = statSync(nullPath);
24
+ return fdStat.dev === nullStat.dev && fdStat.ino === nullStat.ino;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
11
30
  export function createSandboxClient(logger: Logger, auth: AuthData, region: string): APIClient {
12
31
  return new APIClient(getServiceUrls(region).catalyst, logger, auth.apiKey);
13
32
  }
14
33
 
34
+ export interface ResolvedSandboxTarget {
35
+ region: string;
36
+ orgId: string;
37
+ }
38
+
39
+ /**
40
+ * Resolve sandbox routing context using cache-first lookup.
41
+ * Falls back to the CLI resolve endpoint on cache miss or partial cache.
42
+ */
43
+ export async function resolveSandboxTarget(
44
+ logger: Logger,
45
+ auth: AuthData,
46
+ apiClient: APIClient | null = null,
47
+ sandboxId: string,
48
+ profileName = 'production',
49
+ config?: Config | null
50
+ ): Promise<ResolvedSandboxTarget> {
51
+ const cachedInfo = await getResourceInfo('sandbox', profileName, sandboxId);
52
+ if (cachedInfo?.region && cachedInfo?.orgId) {
53
+ logger.trace(
54
+ `[sandbox] Found cached target for ${sandboxId}: ${cachedInfo.region}/${cachedInfo.orgId}`
55
+ );
56
+ return { region: cachedInfo.region, orgId: cachedInfo.orgId };
57
+ }
58
+
59
+ logger.trace(`[sandbox] Cache miss for target ${sandboxId}, resolving via CLI API`);
60
+ const globalClient =
61
+ apiClient ?? (await getGlobalCatalystAPIClient(logger, auth, profileName, undefined, config));
62
+ const sandbox = await sandboxResolve(globalClient, sandboxId);
63
+ await setResourceInfo('sandbox', profileName, sandboxId, sandbox.region, sandbox.orgId);
64
+ return { region: sandbox.region, orgId: sandbox.orgId };
65
+ }
66
+
15
67
  /**
16
68
  * Look up the region for a sandbox, using cache-first strategy.
17
69
  * Falls back to API lookup if not in cache.
@@ -64,6 +116,15 @@ export async function cacheSandboxRegion(
64
116
  await setResourceInfo('sandbox', profileName, sandboxId, region);
65
117
  }
66
118
 
119
+ export async function cacheSandboxTarget(
120
+ profileName = 'production',
121
+ sandboxId: string,
122
+ region: string,
123
+ orgId?: string
124
+ ): Promise<void> {
125
+ await setResourceInfo('sandbox', profileName, sandboxId, region, orgId);
126
+ }
127
+
67
128
  /**
68
129
  * Clear cached region for a sandbox after delete
69
130
  */
@@ -46,9 +46,9 @@ export const createCoderSubcommand = createSubcommand({
46
46
  },
47
47
  {
48
48
  command: getCommand(
49
- 'coder create "Review this change" --default-agent code-review --agent-slugs code-review'
49
+ 'coder create "Review this change" --default-agent code-review --enabled-agents code-review'
50
50
  ),
51
- description: 'Create with published custom agents and a custom default route target',
51
+ description: 'Create with a selected agent roster and a custom default route target',
52
52
  },
53
53
  ],
54
54
  schema: {
@@ -96,10 +96,10 @@ export const createCoderSubcommand = createSubcommand({
96
96
  // Resources
97
97
  workspaceId: z.string().optional().describe('Workspace ID to use'),
98
98
  tags: z.string().optional().describe('Comma-separated tags'),
99
- agentSlugs: z
99
+ enabledAgents: z
100
100
  .string()
101
101
  .optional()
102
- .describe('Comma-separated published custom agent slugs to include'),
102
+ .describe('Comma-separated built-in/custom agents to include'),
103
103
  env: z
104
104
  .string()
105
105
  .optional()
@@ -121,7 +121,7 @@ export const createCoderSubcommand = createSubcommand({
121
121
  // Build the create session request body from flags
122
122
  const body: CoderCreateSessionRequest & {
123
123
  defaultAgent?: string;
124
- agentSlugs?: string[];
124
+ enabledAgents?: string[];
125
125
  } = {
126
126
  task: args.task,
127
127
  ...(opts?.label && { label: opts.label }),
@@ -167,10 +167,10 @@ export const createCoderSubcommand = createSubcommand({
167
167
  .split(',')
168
168
  .map((t) => t.trim())
169
169
  .filter(Boolean);
170
- if (opts?.agentSlugs)
171
- body.agentSlugs = opts.agentSlugs
170
+ if (opts?.enabledAgents)
171
+ body.enabledAgents = opts.enabledAgents
172
172
  .split(',')
173
- .map((slug) => slug.trim())
173
+ .map((name) => name.trim())
174
174
  .filter(Boolean);
175
175
  if (opts?.savedSkillIds)
176
176
  body.savedSkillIds = opts.savedSkillIds
@@ -128,6 +128,8 @@ export const startSubcommand = createSubcommand({
128
128
  },
129
129
  async handler(ctx) {
130
130
  const { opts, options } = ctx;
131
+ // Keep Pi's interactive install telemetry disabled for Agentuity CLI sessions.
132
+ process.env.PI_TELEMETRY = '0';
131
133
 
132
134
  // Resolve working directory from optional --dir option
133
135
  let cwd = process.cwd();
@@ -388,6 +390,7 @@ export const startSubcommand = createSubcommand({
388
390
  AGENTUITY_CODER_HUB_URL: hubWsUrl,
389
391
  };
390
392
  env.AGENTUITY_CODER_API_KEY = ctx.auth.apiKey;
393
+ env.AGENTUITY_ORGID = ctx.orgId;
391
394
 
392
395
  if (opts?.agent) {
393
396
  env.AGENTUITY_CODER_AGENT = opts.agent;
@@ -43,7 +43,7 @@ export async function probeHubInitAccess(
43
43
  try {
44
44
  const response = await fetchImpl(`${hubHttpUrl}/api/hub/init`, {
45
45
  headers,
46
- signal: AbortSignal.timeout(5_000),
46
+ signal: AbortSignal.timeout(10_000),
47
47
  });
48
48
 
49
49
  let payload: unknown;
@@ -54,10 +54,10 @@ export const updateSubcommand = createSubcommand({
54
54
  loopAutoContinue: z.boolean().optional().describe('Auto-continue loop'),
55
55
  loopAllowDetached: z.boolean().optional().describe('Allow detached loop execution'),
56
56
  tags: z.string().optional().describe('Comma-separated tags (replaces existing)'),
57
- agentSlugs: z
57
+ enabledAgents: z
58
58
  .string()
59
59
  .optional()
60
- .describe('Comma-separated published custom agent slugs (replaces existing)'),
60
+ .describe('Comma-separated built-in/custom agents (replaces existing)'),
61
61
  }),
62
62
  },
63
63
  async handler(ctx) {
@@ -81,10 +81,10 @@ export const updateSubcommand = createSubcommand({
81
81
  .map((t) => t.trim())
82
82
  .filter(Boolean);
83
83
  }
84
- if (opts?.agentSlugs) {
85
- body.agentSlugs = opts.agentSlugs
84
+ if (opts?.enabledAgents) {
85
+ body.enabledAgents = opts.enabledAgents
86
86
  .split(',')
87
- .map((slug) => slug.trim())
87
+ .map((name) => name.trim())
88
88
  .filter(Boolean);
89
89
  }
90
90
 
@@ -105,7 +105,7 @@ export const updateSubcommand = createSubcommand({
105
105
 
106
106
  if (Object.keys(body).length === 0) {
107
107
  tui.fatal(
108
- 'No update fields provided. Use --label, --visibility, --tags, --agent, --default-agent, --agent-slugs, --workflow-mode, or loop options.',
108
+ 'No update fields provided. Use --label, --visibility, --tags, --agent, --default-agent, --enabled-agents, --workflow-mode, or loop options.',
109
109
  ErrorCode.VALIDATION_FAILED
110
110
  );
111
111
  }
@@ -125,7 +125,7 @@ export const updateSubcommand = createSubcommand({
125
125
  if (opts?.tags) fields.push(`Tags: ${(body.tags as string[]).join(', ')}`);
126
126
  if (opts?.agent) fields.push(`Agent: ${opts.agent}`);
127
127
  if (opts?.defaultAgent) fields.push(`Default agent: ${opts.defaultAgent}`);
128
- if (opts?.agentSlugs) fields.push(`Custom agents: ${opts.agentSlugs}`);
128
+ if (opts?.enabledAgents) fields.push(`Enabled agents: ${opts.enabledAgents}`);
129
129
  if (opts?.workflowMode || body.loop) fields.push(`Workflow: ${body.workflowMode}`);
130
130
 
131
131
  for (const f of fields) {
@@ -1,37 +1,66 @@
1
1
  import { z } from 'zod';
2
- import { CoderClient, type CoderCreateWorkspaceRequest } from '@agentuity/core/coder';
3
- import { ValidationOutputError } from '@agentuity/core';
2
+ import { APIError, ValidationInputError, ValidationOutputError } from '@agentuity/core';
3
+ import {
4
+ CoderClient,
5
+ CoderCreateWorkspaceRequestSchema,
6
+ type CoderCreateWorkspaceRequest,
7
+ } from '@agentuity/core/coder';
4
8
  import { createSubcommand } from '../../../types';
5
9
  import * as tui from '../../../tui';
6
10
  import { getCommand } from '../../../command-prefix';
7
11
  import { ErrorCode } from '../../../errors';
8
12
  import { resolveGitHubRepo } from '../resolve-repo';
9
13
 
14
+ const EMPTY_WORKSPACE_ERROR =
15
+ 'A workspace needs at least one repo, saved skill, skill bucket, or agent';
16
+
17
+ function hasWorkspaceSelections(input: CoderCreateWorkspaceRequest): boolean {
18
+ return (
19
+ (input.repos?.length ?? 0) > 0 ||
20
+ (input.savedSkillIds?.length ?? 0) > 0 ||
21
+ (input.skillBucketIds?.length ?? 0) > 0 ||
22
+ (input.enabledAgents?.length ?? 0) > 0
23
+ );
24
+ }
25
+
26
+ function formatWorkspaceValidationMessage(issues: Array<{ message: string }>): string {
27
+ const messages = [...new Set(issues.map((issue) => issue.message).filter(Boolean))];
28
+ if (messages.length === 0) {
29
+ return 'Invalid workspace configuration';
30
+ }
31
+ if (messages.includes(EMPTY_WORKSPACE_ERROR)) {
32
+ return `${EMPTY_WORKSPACE_ERROR}. Use --repo or --enabled-agents.`;
33
+ }
34
+ return messages.join('; ');
35
+ }
36
+
10
37
  export const createWorkspaceSubcommand = createSubcommand({
11
38
  name: 'create',
12
39
  aliases: ['new', 'add'],
13
- description: 'Create a new Coder workspace',
40
+ description: 'Create a new Coder workspace with at least one repo or agent',
14
41
  tags: ['mutating', 'requires-auth'],
15
42
  requires: { auth: true, org: true },
16
43
  examples: [
17
- {
18
- command: getCommand('coder workspace create "My Workspace"'),
19
- description: 'Create a workspace with a name',
20
- },
21
44
  {
22
45
  command: getCommand(
23
- 'coder workspace create "My Workspace" --description "For frontend work" --scope org'
46
+ 'coder workspace create "My Workspace" --repo https://github.com/org/repo --repo-branch main'
24
47
  ),
25
- description: 'Create an org-scoped workspace with description',
48
+ description: 'Create a workspace with a repository',
26
49
  },
27
50
  {
28
51
  command: getCommand(
29
- 'coder workspace create "My Workspace" --repo https://github.com/org/repo --repo-branch main'
52
+ 'coder workspace create "My Workspace" --enabled-agents code-review --description "For frontend work" --scope org'
30
53
  ),
31
- description: 'Create a workspace with a repository',
54
+ description: 'Create an org-scoped workspace with description and agents',
32
55
  },
33
56
  {
34
- command: getCommand('coder workspace create "My Workspace" --json'),
57
+ command: getCommand('coder workspace create "My Workspace" --enabled-agents code-review'),
58
+ description: 'Create a workspace with an agent roster',
59
+ },
60
+ {
61
+ command: getCommand(
62
+ 'coder workspace create "My Workspace" --enabled-agents code-review --json'
63
+ ),
35
64
  description: 'Create a workspace and return JSON output',
36
65
  },
37
66
  ],
@@ -45,10 +74,10 @@ export const createWorkspaceSubcommand = createSubcommand({
45
74
  scope: z.string().optional().describe('Workspace scope: user or org'),
46
75
  repo: z.string().optional().describe('Repository URL to add'),
47
76
  repoBranch: z.string().optional().describe('Branch for the repository'),
48
- agentSlugs: z
77
+ enabledAgents: z
49
78
  .string()
50
79
  .optional()
51
- .describe('Comma-separated published custom agent slugs to add'),
80
+ .describe('Comma-separated built-in/custom agents to include'),
52
81
  }),
53
82
  },
54
83
  async handler(ctx) {
@@ -59,9 +88,7 @@ export const createWorkspaceSubcommand = createSubcommand({
59
88
  orgId: ctx.orgId,
60
89
  });
61
90
 
62
- const body: CoderCreateWorkspaceRequest & {
63
- agentSlugs?: string[];
64
- } = {
91
+ const body: CoderCreateWorkspaceRequest = {
65
92
  name: args.name,
66
93
  ...(opts?.description && { description: opts.description }),
67
94
  ...(opts?.scope && { scope: opts.scope as 'user' | 'org' }),
@@ -78,17 +105,35 @@ export const createWorkspaceSubcommand = createSubcommand({
78
105
  return;
79
106
  }
80
107
  }
81
- if (opts?.agentSlugs) {
82
- body.agentSlugs = opts.agentSlugs
108
+ if (opts?.enabledAgents) {
109
+ body.enabledAgents = opts.enabledAgents
83
110
  .split(',')
84
- .map((slug) => slug.trim())
111
+ .map((name) => name.trim())
85
112
  .filter(Boolean);
86
113
  }
114
+ if (!hasWorkspaceSelections(body)) {
115
+ tui.fatal(
116
+ `Failed to create workspace: ${EMPTY_WORKSPACE_ERROR}. Use --repo or --enabled-agents.`,
117
+ ErrorCode.VALIDATION_FAILED
118
+ );
119
+ }
120
+
121
+ const validationResult = CoderCreateWorkspaceRequestSchema.safeParse(body);
122
+ if (!validationResult.success) {
123
+ ctx.logger.trace(
124
+ 'Validation issues: %s',
125
+ JSON.stringify(validationResult.error.issues, null, 2)
126
+ );
127
+ tui.fatal(
128
+ `Failed to create workspace: ${formatWorkspaceValidationMessage(validationResult.error.issues)}`,
129
+ ErrorCode.VALIDATION_FAILED
130
+ );
131
+ }
87
132
 
88
133
  try {
89
- const created = await client.createWorkspace(body);
90
- const createdAgentSlugs = Array.isArray(created.agentSlugs)
91
- ? created.agentSlugs.filter((slug): slug is string => typeof slug === 'string')
134
+ const created = await client.createWorkspace(validationResult.data);
135
+ const createdEnabledAgents = Array.isArray(created.enabledAgents)
136
+ ? created.enabledAgents.filter((name): name is string => typeof name === 'string')
92
137
  : [];
93
138
 
94
139
  if (options.json) {
@@ -104,15 +149,21 @@ export const createWorkspaceSubcommand = createSubcommand({
104
149
  tui.output(` Scope: ${created.scope}`);
105
150
  tui.output(` Repos: ${created.repoCount}`);
106
151
  tui.output(` Selections: ${created.selectionCount}`);
107
- if (createdAgentSlugs.length > 0) {
108
- tui.output(` Agents: ${createdAgentSlugs.join(', ')}`);
152
+ if (createdEnabledAgents.length > 0) {
153
+ tui.output(` Agents: ${createdEnabledAgents.join(', ')}`);
109
154
  }
110
155
 
111
156
  return created;
112
157
  } catch (err) {
113
- if (err instanceof ValidationOutputError) {
158
+ if (err instanceof ValidationInputError || err instanceof ValidationOutputError) {
114
159
  ctx.logger.trace('Validation response URL: %s', err.url ?? 'unknown');
115
160
  ctx.logger.trace('Validation issues: %s', JSON.stringify(err.issues, null, 2));
161
+ tui.fatal(
162
+ `Failed to create workspace: ${formatWorkspaceValidationMessage(err.issues)}`,
163
+ ErrorCode.VALIDATION_FAILED
164
+ );
165
+ }
166
+ if (err instanceof APIError && err.status >= 400 && err.status < 500) {
116
167
  tui.fatal(`Failed to create workspace: ${err.message}`, ErrorCode.VALIDATION_FAILED);
117
168
  }
118
169
  const msg = err instanceof Error ? err.message : String(err);
@@ -17,7 +17,9 @@ export const workspaceCommand = createCommand({
17
17
  description: 'List all workspaces',
18
18
  },
19
19
  {
20
- command: getCommand('coder workspace create "My Workspace"'),
20
+ command: getCommand(
21
+ 'coder workspace create "My Workspace" --repo https://github.com/org/repo'
22
+ ),
21
23
  description: 'Create a new workspace',
22
24
  },
23
25
  {
@@ -71,35 +71,69 @@ function pidExists(pid: number): boolean {
71
71
  }
72
72
 
73
73
  /**
74
- * Kill a process by PID with SIGTERM, then SIGKILL if still alive
74
+ * Send a signal to a process group (negative PID) with fallback to direct PID.
75
+ * Returns true if the signal was sent successfully.
75
76
  */
76
- async function killPid(pid: number, logger: LoggerLike): Promise<void> {
77
- if (!pidExists(pid)) return;
77
+ function killProcessTree(pid: number, signal: NodeJS.Signals, logger: LoggerLike): boolean {
78
+ // Safety: never send signals to PID 0 (own process group), PID 1 (init/systemd),
79
+ // or other dangerously low PIDs. process.kill(-1) would signal every process
80
+ // the user owns, which would crash the entire desktop session.
81
+ if (pid <= 1) {
82
+ logger.debug('Refusing to kill dangerous pid %d, skipping process tree kill', pid);
83
+ return false;
84
+ }
78
85
 
86
+ // Try process group kill first (kills all children too)
79
87
  try {
80
- process.kill(pid, 'SIGTERM');
81
- logger.debug('Sent SIGTERM to pid %d', pid);
88
+ process.kill(-pid, signal);
89
+ logger.debug('Sent %s to process group -%d', signal, pid);
90
+ return true;
82
91
  } catch (err: unknown) {
83
92
  const error = err as NodeJS.ErrnoException;
84
- if (error.code === 'ESRCH') return;
85
- logger.debug('Error sending SIGTERM to pid %d: %s', pid, error.message);
93
+ if (error.code !== 'ESRCH') {
94
+ logger.debug(
95
+ 'Process group kill failed for pid %d (%s), falling back to direct',
96
+ pid,
97
+ error.code
98
+ );
99
+ }
86
100
  }
87
101
 
88
- // Give it a moment to exit gracefully
89
- await new Promise((r) => setTimeout(r, 500));
90
-
91
- if (!pidExists(pid)) return;
92
-
93
- // Force kill
102
+ // Fall back to direct PID kill
94
103
  try {
95
- process.kill(pid, 'SIGKILL');
96
- logger.debug('Sent SIGKILL to pid %d', pid);
104
+ process.kill(pid, signal);
105
+ logger.debug('Sent %s to pid %d (direct)', signal, pid);
106
+ return true;
97
107
  } catch (err: unknown) {
98
108
  const error = err as NodeJS.ErrnoException;
99
109
  if (error.code !== 'ESRCH') {
100
- logger.debug('Error sending SIGKILL to pid %d: %s', pid, error.message);
110
+ logger.debug('Direct kill failed for pid %d: %s', pid, error.code);
101
111
  }
112
+ return false;
102
113
  }
114
+ }
115
+
116
+ /**
117
+ * Kill a process by PID with SIGTERM, then SIGKILL if still alive.
118
+ * Targets the entire process tree to prevent orphaned child processes.
119
+ */
120
+ async function killPid(pid: number, logger: LoggerLike): Promise<void> {
121
+ if (!pidExists(pid)) return;
122
+
123
+ // Send SIGTERM to process tree
124
+ const sent = killProcessTree(pid, 'SIGTERM', logger);
125
+ if (!sent) return;
126
+
127
+ // Give it a moment to exit gracefully
128
+ await new Promise((r) => setTimeout(r, 500));
129
+
130
+ // Always attempt SIGKILL on the process tree even if the leader has exited.
131
+ // On Unix, process groups persist after the leader exits and signaling via
132
+ // negative PGID still reaches remaining members. killProcessTree() handles
133
+ // ESRCH gracefully if the group no longer exists.
134
+
135
+ // Force kill the entire process tree
136
+ killProcessTree(pid, 'SIGKILL', logger);
103
137
 
104
138
  // Wait for process to fully terminate
105
139
  await new Promise((r) => setTimeout(r, 100));