@guildai/cli 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/commands/agent/chat.js +29 -62
  2. package/dist/commands/agent/clone.js +1 -1
  3. package/dist/commands/agent/code.js +1 -1
  4. package/dist/commands/agent/fork.js +2 -2
  5. package/dist/commands/agent/get.js +1 -1
  6. package/dist/commands/agent/grep.js +2 -2
  7. package/dist/commands/agent/init.js +11 -4
  8. package/dist/commands/agent/list.js +9 -1
  9. package/dist/commands/agent/publish.js +1 -1
  10. package/dist/commands/agent/revalidate.js +1 -1
  11. package/dist/commands/agent/save.js +23 -2
  12. package/dist/commands/agent/test.js +91 -92
  13. package/dist/commands/agent/unpublish.js +1 -1
  14. package/dist/commands/agent/update.js +1 -1
  15. package/dist/commands/agent/versions.js +1 -1
  16. package/dist/commands/agent/workspaces.js +1 -1
  17. package/dist/commands/chat.js +28 -15
  18. package/dist/commands/config/list.js +2 -2
  19. package/dist/commands/integration/operation/create.js +2 -2
  20. package/dist/commands/integration/operation/list.js +2 -2
  21. package/dist/commands/integration/update.js +1 -1
  22. package/dist/commands/integration/version/get.js +2 -2
  23. package/dist/commands/integration/version/publish.js +2 -2
  24. package/dist/commands/integration/version/test.js +2 -2
  25. package/dist/commands/session/events.js +7 -3
  26. package/dist/commands/session/tasks.js +8 -2
  27. package/dist/commands/workspace/get.js +1 -1
  28. package/dist/commands/workspace/list.js +28 -6
  29. package/dist/commands/workspace/select.js +40 -9
  30. package/dist/components/TaskView.js +2 -2
  31. package/dist/lib/agent-helpers.d.ts +74 -2
  32. package/dist/lib/agent-helpers.js +222 -8
  33. package/dist/lib/alternate-screen.js +2 -0
  34. package/dist/lib/api-client.js +2 -1
  35. package/dist/lib/api-types.d.ts +37 -0
  36. package/dist/lib/config.d.ts +3 -0
  37. package/dist/lib/config.js +33 -0
  38. package/dist/lib/output.d.ts +6 -1
  39. package/dist/lib/output.js +50 -0
  40. package/dist/lib/session-events.d.ts +1 -1
  41. package/dist/lib/session-events.js +5 -3
  42. package/dist/lib/session-polling.d.ts +8 -0
  43. package/dist/lib/session-polling.js +49 -0
  44. package/dist/lib/spinners.js +4 -1
  45. package/package.json +1 -1
@@ -9,7 +9,7 @@ export function createAgentUnpublishCommand() {
9
9
  const cmd = new Command('unpublish');
10
10
  cmd
11
11
  .description('Unpublish the latest published version of an agent')
12
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
12
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
13
13
  .action(async (agentIdArg) => {
14
14
  const output = createOutputWriter();
15
15
  try {
@@ -26,7 +26,7 @@ export function createAgentUpdateCommand() {
26
26
  const cmd = new Command('update');
27
27
  cmd
28
28
  .description('Update agent properties (visibility)')
29
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
29
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
30
30
  .option('--public', 'Make the agent public (visible to everyone)')
31
31
  .option('--private', 'Make the agent private (only visible to owner)')
32
32
  .option('--yes', 'Skip confirmation prompt for visibility changes')
@@ -11,7 +11,7 @@ export function createAgentVersionsCommand() {
11
11
  const cmd = new Command('versions');
12
12
  cmd
13
13
  .description('List all versions of an agent')
14
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
14
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
15
15
  .option('--limit <number>', 'Maximum number of versions to return', '20')
16
16
  .option('--offset <number>', 'Number of versions to skip', '0')
17
17
  .action(async (agentIdArg, options) => {
@@ -11,7 +11,7 @@ export function createAgentWorkspacesCommand() {
11
11
  const cmd = new Command('workspaces');
12
12
  cmd
13
13
  .description('List workspaces that use an agent')
14
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
14
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
15
15
  .option('--limit <number>', 'Maximum number of workspaces to return', '20')
16
16
  .option('--offset <number>', 'Number of workspaces to skip', '0')
17
17
  .action(async (agentIdArg, options) => {
@@ -13,7 +13,7 @@ import chalk from 'chalk';
13
13
  import { readFileSync } from 'fs';
14
14
  import path from 'path';
15
15
  import { fileURLToPath } from 'url';
16
- import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayName, matchesAgent, getAgentName, } from '../lib/session-events.js';
16
+ import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayName, getAgentName, } from '../lib/session-events.js';
17
17
  import { printResumeHint, fetchSession, fetchSessionEvents, eventsToDisplayMessages, } from '../lib/session-resume.js';
18
18
  import { DEFAULT_EVENT_TYPES, parseEventFilter, shouldShowEvent, } from '../lib/event-filter.js';
19
19
  import { fetchEvents, fetchTasks } from '../lib/session-events-fetch.js';
@@ -1087,24 +1087,37 @@ export function createChatCommand() {
1087
1087
  else {
1088
1088
  inactivityCounter++;
1089
1089
  }
1090
- // Check if we got a completion response
1091
- // Look for runtime_done events from the ROOT task (the agent we're chatting with)
1092
- // For orchestrating agents like agent-builder that spawn child tasks, we need to
1093
- // wait for the root agent's completion, not just any runtime_done event.
1094
- const targetAgent = options.agent || 'assistant';
1095
- const hasRootTaskDone = allEvents.some((e) => e.type === 'runtime_done' &&
1096
- matchesAgent(e.task?.agent, targetAgent) &&
1097
- !e.task?.parent_task_id // Root task has no parent
1098
- );
1099
- // Also check for agent_notification_message events from the root agent
1100
- const hasAgentMessage = allEvents.some((e) => e.type === 'agent_notification_message' &&
1101
- matchesAgent(e.task?.agent, targetAgent) &&
1102
- !e.task?.parent_task_id);
1090
+ const isRootTask = (e) => !e.task?.parent_task;
1091
+ const hasRootTaskDone = allEvents.some((e) => e.type === 'runtime_done' && isRootTask(e));
1092
+ const hasAgentMessage = allEvents.some((e) => e.type === 'agent_notification_message' && isRootTask(e));
1093
+ const hasRootTaskError = allEvents.some((e) => e.type === 'runtime_error' && isRootTask(e));
1103
1094
  // Check for a ui_prompt request... that ends the game.
1104
1095
  const hasUIPromptMessage = allEvents.some((e) => e.type === 'agent_notification_message' &&
1105
1096
  e.task?.tool_name === 'ui_prompt');
1097
+ if (hasRootTaskError) {
1098
+ debug('Found error event from root agent, exiting --once mode');
1099
+ const errorEvents = allEvents.filter((e) => e.type === 'runtime_error' || e.type === 'agent_notification_error');
1100
+ if (errorEvents.length > 0 && !options.mode) {
1101
+ const lastError = errorEvents[errorEvents.length - 1];
1102
+ const content = lastError.content;
1103
+ if (content?.data) {
1104
+ console.error(chalk.red(`Error: ${content.data}`));
1105
+ }
1106
+ else {
1107
+ console.error(chalk.red('Agent failed to start'));
1108
+ }
1109
+ }
1110
+ else if (options.mode === 'json') {
1111
+ console.log(JSON.stringify({
1112
+ session_id: session.id,
1113
+ events: allEvents,
1114
+ error: true,
1115
+ }, null, 2));
1116
+ }
1117
+ process.exit(1);
1118
+ }
1106
1119
  if (hasRootTaskDone || hasAgentMessage || hasUIPromptMessage) {
1107
- debug(`Found completion event from root agent (${targetAgent}), exiting --once mode`);
1120
+ debug('Found completion event from root agent, exiting --once mode');
1108
1121
  await outputOnceResult(session.id, allEvents, options.mode);
1109
1122
  process.exit(0);
1110
1123
  }
@@ -27,13 +27,13 @@ export function createConfigListCommand() {
27
27
  output.progress(chalk.dim(' guild workspace select'));
28
28
  return;
29
29
  }
30
- if (hasGlobal) {
30
+ if (config.global && hasGlobal) {
31
31
  output.progress(chalk.bold('Global config') + chalk.dim(' (~/.guild/config.json):'));
32
32
  for (const [key, value] of Object.entries(config.global)) {
33
33
  output.progress(` ${key}: ${chalk.cyan(String(value))}`);
34
34
  }
35
35
  }
36
- if (hasLocal) {
36
+ if (config.local && hasLocal) {
37
37
  if (hasGlobal)
38
38
  output.progress('');
39
39
  output.progress(chalk.bold('Local config') + chalk.dim(' (guild.json):'));
@@ -17,7 +17,7 @@ export function createIntegrationOperationCreateCommand() {
17
17
  cmd
18
18
  .description('Create operation(s) for an integration version')
19
19
  .argument('<id_or_name>', 'Integration ID or name (owner~name)')
20
- .option('--version <semver>', 'Specific version, e.g. 1.0.0')
20
+ .option('--version-number <semver>', 'Specific version, e.g. 1.0.0')
21
21
  .option('--operation <name>', 'Operation identifier, e.g. list_users')
22
22
  .option('--method <method>', 'HTTP method: GET, POST, PUT, PATCH, DELETE')
23
23
  .option('--path <path>', 'REST path, e.g. /{owner}/{repo}/issues')
@@ -35,7 +35,7 @@ export function createIntegrationOperationCreateCommand() {
35
35
  process.exit(1);
36
36
  }
37
37
  const client = new GuildAPIClient();
38
- const versionId = await resolveVersionId(client, identifier, options.version);
38
+ const versionId = await resolveVersionId(client, identifier, options.versionNumber);
39
39
  if (options.openapi) {
40
40
  // OpenAPI mode
41
41
  if (!existsSync(options.openapi)) {
@@ -14,7 +14,7 @@ export function createIntegrationOperationListCommand() {
14
14
  cmd
15
15
  .description('List operations for an integration version')
16
16
  .argument('<id_or_name>', 'Integration ID or name (owner~name)')
17
- .option('--version <semver>', 'Specific version, e.g. 1.0.0')
17
+ .option('--version-number <semver>', 'Specific version, e.g. 1.0.0')
18
18
  .option('--limit <number>', 'Number of results to return', '100')
19
19
  .option('--offset <number>', 'Offset for pagination', '0')
20
20
  .action(async (identifier, options) => {
@@ -26,7 +26,7 @@ export function createIntegrationOperationListCommand() {
26
26
  process.exit(1);
27
27
  }
28
28
  const client = new GuildAPIClient();
29
- const versionId = await resolveVersionId(client, identifier, options.version);
29
+ const versionId = await resolveVersionId(client, identifier, options.versionNumber);
30
30
  const params = new URLSearchParams();
31
31
  params.append('limit', options.limit);
32
32
  params.append('offset', options.offset);
@@ -63,7 +63,7 @@ export function createIntegrationUpdateCommand() {
63
63
  hasUpdates = true;
64
64
  }
65
65
  if (options.baseUrl !== undefined) {
66
- body.protocol_config = { base_url: options.baseUrl };
66
+ body.protocol_config = { protocol: 'REST', base_url: options.baseUrl };
67
67
  hasUpdates = true;
68
68
  }
69
69
  // Auth config updates need the discriminator
@@ -34,7 +34,7 @@ export function createIntegrationVersionGetCommand() {
34
34
  cmd
35
35
  .description('Get version details')
36
36
  .argument('<id_or_name>', 'Integration ID or name (owner~name)')
37
- .option('--version <semver>', 'Specific version, e.g. 1.0.0')
37
+ .option('--version-number <semver>', 'Specific version, e.g. 1.0.0')
38
38
  .action(async (identifier, options) => {
39
39
  const output = createOutputWriter();
40
40
  try {
@@ -44,7 +44,7 @@ export function createIntegrationVersionGetCommand() {
44
44
  process.exit(1);
45
45
  }
46
46
  const client = new GuildAPIClient();
47
- const versionId = await resolveVersionId(client, identifier, options.version);
47
+ const versionId = await resolveVersionId(client, identifier, options.versionNumber);
48
48
  const response = await client.get(`/integration_versions/${versionId}`);
49
49
  if (getOutputMode() === 'json') {
50
50
  output.data(response);
@@ -23,7 +23,7 @@ export function createIntegrationVersionPublishCommand() {
23
23
  cmd
24
24
  .description('Publish a built version')
25
25
  .argument('<id_or_name>', 'Integration ID or name (owner~name)')
26
- .option('--version <semver>', 'Specific version to publish, e.g. 1.0.0')
26
+ .option('--version-number <semver>', 'Specific version to publish, e.g. 1.0.0')
27
27
  .action(async (identifier, options) => {
28
28
  const output = createOutputWriter();
29
29
  try {
@@ -33,7 +33,7 @@ export function createIntegrationVersionPublishCommand() {
33
33
  process.exit(1);
34
34
  }
35
35
  const client = new GuildAPIClient();
36
- const versionId = await resolveVersionId(client, identifier, options.version);
36
+ const versionId = await resolveVersionId(client, identifier, options.versionNumber);
37
37
  // Get current version info for display
38
38
  const currentVersion = await client.get(`/integration_versions/${versionId}`);
39
39
  const versionDisplay = currentVersion.version_number || versionId;
@@ -22,7 +22,7 @@ export function createIntegrationVersionTestCommand() {
22
22
  cmd
23
23
  .description('Test an endpoint invocation')
24
24
  .argument('<id_or_name>', 'Integration ID or name (owner~name)')
25
- .option('--version <semver>', 'Specific version, e.g. 1.0.0')
25
+ .option('--version-number <semver>', 'Specific version, e.g. 1.0.0')
26
26
  .requiredOption('--operation <name>', 'Operation to test, e.g. list_users')
27
27
  .option('--account <name>', 'Account name to resolve credential from')
28
28
  .option('--input-path <json>', 'JSON object of path parameters')
@@ -50,7 +50,7 @@ export function createIntegrationVersionTestCommand() {
50
50
  body.input_body = parseJsonFlag(options.inputBody, '--input-body');
51
51
  }
52
52
  const client = new GuildAPIClient();
53
- const versionId = await resolveVersionId(client, identifier, options.version);
53
+ const versionId = await resolveVersionId(client, identifier, options.versionNumber);
54
54
  if (options.account) {
55
55
  const creds = await client.get(`/accounts/${options.account}/credentials?integration=${encodeURIComponent(identifier)}`);
56
56
  if (creds.items.length === 0) {
@@ -3,6 +3,7 @@
3
3
  import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getAuthToken } from '../../lib/auth.js';
6
+ import { parseEventFilter } from '../../lib/event-filter.js';
6
7
  import { handleAxiosError } from '../../lib/errors.js';
7
8
  import { createOutputWriter } from '../../lib/output.js';
8
9
  export function createSessionEventsCommand() {
@@ -10,7 +11,7 @@ export function createSessionEventsCommand() {
10
11
  cmd
11
12
  .description('List events in a session')
12
13
  .argument('<session-id>', 'Session ID')
13
- .option('--types <types>', 'Comma-separated event types to filter (e.g., user_message,agent_notification_message)')
14
+ .option('--events <types>', 'Event types to show. Shorthands: none, user, system, all, or comma-separated type names')
14
15
  .option('--limit <number>', 'Number of results to return', '100')
15
16
  .option('--offset <number>', 'Offset for pagination', '0')
16
17
  .action(async (sessionId, options) => {
@@ -25,8 +26,11 @@ export function createSessionEventsCommand() {
25
26
  const params = new URLSearchParams();
26
27
  params.append('limit', options.limit);
27
28
  params.append('offset', options.offset);
28
- if (options.types) {
29
- params.append('types', options.types);
29
+ if (options.events) {
30
+ const filter = parseEventFilter(options.events);
31
+ if (filter.size > 0) {
32
+ params.append('types', [...filter].join(','));
33
+ }
30
34
  }
31
35
  const response = await client.get(`/sessions/${sessionId}/events?${params.toString()}`);
32
36
  output.data(response);
@@ -4,7 +4,8 @@ import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getAuthToken } from '../../lib/auth.js';
6
6
  import { handleAxiosError } from '../../lib/errors.js';
7
- import { createOutputWriter } from '../../lib/output.js';
7
+ import { getOutputMode } from '../../lib/output-mode.js';
8
+ import { createOutputWriter, formatTaskTable } from '../../lib/output.js';
8
9
  export function createSessionTasksCommand() {
9
10
  const cmd = new Command('tasks');
10
11
  cmd
@@ -25,7 +26,12 @@ export function createSessionTasksCommand() {
25
26
  params.append('limit', options.limit);
26
27
  params.append('offset', options.offset);
27
28
  const response = await client.get(`/sessions/${sessionId}/tasks?${params.toString()}`);
28
- output.data(response);
29
+ if (getOutputMode() === 'json') {
30
+ console.log(JSON.stringify(response, null, 2));
31
+ }
32
+ else {
33
+ formatTaskTable(response.items, response.pagination);
34
+ }
29
35
  }
30
36
  catch (error) {
31
37
  const formattedError = handleAxiosError(error);
@@ -8,7 +8,7 @@ export function createWorkspaceGetCommand() {
8
8
  const cmd = new Command('get');
9
9
  cmd
10
10
  .description('Get workspace details')
11
- .argument('<identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
11
+ .argument('<identifier>', 'Workspace ID or full name (e.g., owner~workspace-name)')
12
12
  .action(async (id) => {
13
13
  const output = createOutputWriter();
14
14
  try {
@@ -11,16 +11,38 @@ export function createWorkspaceListCommand() {
11
11
  .description('List workspaces')
12
12
  .option('--limit <number>', 'Number of results to return', '20')
13
13
  .option('--offset <number>', 'Offset for pagination', '0')
14
+ .option('--owner <name>', 'Filter workspaces by owner name (case-insensitive)')
14
15
  .action(async (options) => {
15
16
  const output = createOutputWriter();
16
17
  try {
17
18
  const client = new GuildAPIClient();
18
- const params = new URLSearchParams();
19
- // Use filter=all to get workspaces from all orgs user is a member of
20
- params.append('filter', 'all');
21
- params.append('limit', options.limit);
22
- params.append('offset', options.offset);
23
- const response = await client.get(`/me/workspaces?${params.toString()}`);
19
+ let response;
20
+ if (options.owner) {
21
+ // When filtering by owner, fetch ALL workspaces first so the
22
+ // client-side filter is applied to the complete dataset, not just
23
+ // one page. The backend's `filter` param only recognises "all" vs
24
+ // personal; owner-name filtering is not supported server-side.
25
+ const allWorkspaces = await client.fetchAll('/me/workspaces?filter=all');
26
+ const ownerLower = options.owner.toLowerCase();
27
+ const filtered = allWorkspaces.filter((w) => w.owner?.name?.toLowerCase() === ownerLower);
28
+ response = {
29
+ items: filtered,
30
+ pagination: {
31
+ total_count: filtered.length,
32
+ limit: filtered.length,
33
+ offset: 0,
34
+ has_more: false,
35
+ },
36
+ };
37
+ }
38
+ else {
39
+ const params = new URLSearchParams();
40
+ // Use filter=all to get workspaces from all orgs the user is a member of.
41
+ params.append('filter', 'all');
42
+ params.append('limit', options.limit);
43
+ params.append('offset', options.offset);
44
+ response = await client.get(`/me/workspaces?${params.toString()}`);
45
+ }
24
46
  if (getOutputMode() === 'json') {
25
47
  console.log(JSON.stringify(response, null, 2));
26
48
  }
@@ -35,8 +35,10 @@ export function createWorkspaceSelectCommand() {
35
35
  cmd
36
36
  .description('Select default workspace for agent testing')
37
37
  .argument('[workspace]', 'Workspace name or ID to select directly')
38
- .action(async (workspaceArg) => {
38
+ .option('--owner <name>', 'Filter workspaces by owner name (case-insensitive)')
39
+ .action(async (workspaceArg, options = {}) => {
39
40
  const output = createOutputWriter();
41
+ const ownerFilter = options.owner;
40
42
  try {
41
43
  const client = new GuildAPIClient();
42
44
  // If a workspace argument was provided, use server-side search to find it
@@ -45,19 +47,34 @@ export function createWorkspaceSelectCommand() {
45
47
  // The backend searches only the "name" column via ILIKE, so full_name (owner/name)
46
48
  // and ID lookups may not return results. Extract just the name part for search,
47
49
  // and if still no match, fall back to fetching the full list.
50
+ // Always use filter=all — the backend's `filter` param only recognises "all"
51
+ // vs personal; owner-name filtering is applied client-side below.
48
52
  const searchTerm = workspaceArg.includes('/')
49
- ? workspaceArg.split('/').pop()
53
+ ? (workspaceArg.split('/').pop() ?? workspaceArg)
50
54
  : workspaceArg;
51
55
  const searchResults = await client.get(`/me/workspaces?filter=all&search=${encodeURIComponent(searchTerm)}&limit=100`);
52
- let workspace = searchResults.items.find((w) => matchesWorkspaceArg(w, workspaceArg));
56
+ let candidates = searchResults.items;
57
+ // Apply client-side owner filter on search results
58
+ if (ownerFilter) {
59
+ candidates = candidates.filter((w) => w.owner?.name?.toLowerCase() === ownerFilter.toLowerCase());
60
+ }
53
61
  // Search didn't find it (could be an ID lookup, or search term didn't match).
54
62
  // Fall back to fetching all workspaces via pagination.
55
- if (!workspace) {
63
+ const directMatch = candidates.find((w) => matchesWorkspaceArg(w, workspaceArg));
64
+ if (!directMatch) {
56
65
  const allWorkspaces = await client.fetchAll('/me/workspaces?filter=all');
57
- workspace = allWorkspaces.find((w) => matchesWorkspaceArg(w, workspaceArg));
66
+ candidates = ownerFilter
67
+ ? allWorkspaces.filter((w) => w.owner?.name?.toLowerCase() === ownerFilter.toLowerCase())
68
+ : allWorkspaces;
58
69
  }
70
+ const workspace = candidates.find((w) => matchesWorkspaceArg(w, workspaceArg));
59
71
  if (!workspace) {
60
- output.error(`Workspace "${workspaceArg}" not found.`, 'Run: guild workspace list');
72
+ if (ownerFilter) {
73
+ output.error(`Workspace "${workspaceArg}" not found for owner "${ownerFilter}".`, 'Run: guild workspace list');
74
+ }
75
+ else {
76
+ output.error(`Workspace "${workspaceArg}" not found.`, 'Run: guild workspace list');
77
+ }
61
78
  process.exit(1);
62
79
  }
63
80
  const target = await saveWorkspaceConfig(workspace.id, workspace.name);
@@ -73,10 +90,20 @@ export function createWorkspaceSelectCommand() {
73
90
  output.error('Interactive mode requires a terminal.', 'Provide a workspace argument:\n guild workspace select <name|id>');
74
91
  process.exit(1);
75
92
  }
76
- // Interactive mode: fetch all workspaces across all pages
77
- const workspaces = await client.fetchAll('/me/workspaces?filter=all');
93
+ // Interactive mode: fetch all workspaces across all pages.
94
+ // Always use filter=all; owner-name filtering is applied client-side
95
+ // (the backend's `filter` param only recognises "all" vs personal).
96
+ let workspaces = await client.fetchAll('/me/workspaces?filter=all');
97
+ if (ownerFilter) {
98
+ workspaces = workspaces.filter((w) => w.owner?.name?.toLowerCase() === ownerFilter.toLowerCase());
99
+ }
78
100
  if (workspaces.length === 0) {
79
- output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
101
+ if (ownerFilter) {
102
+ output.error(`No workspaces found for owner "${ownerFilter}".`, 'Run: guild workspace list');
103
+ }
104
+ else {
105
+ output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
106
+ }
80
107
  process.exit(1);
81
108
  }
82
109
  // Resolve the currently selected workspace (if any)
@@ -101,6 +128,10 @@ export function createWorkspaceSelectCommand() {
101
128
  },
102
129
  });
103
130
  const selectedWorkspace = workspaces.find((w) => w.id === selectedId);
131
+ if (!selectedWorkspace) {
132
+ output.error('Selected workspace not found');
133
+ process.exit(1);
134
+ }
104
135
  const target = await saveWorkspaceConfig(selectedWorkspace.id, selectedWorkspace.name);
105
136
  if (target === 'local') {
106
137
  output.success(`Workspace set for this agent: ${formatWorkspaceDisplay(selectedWorkspace)}`);
@@ -87,7 +87,7 @@ function calculateTaskDepths(tasks) {
87
87
  const taskMap = new Map(tasks.map((t) => [t.id, t]));
88
88
  function getDepth(taskId) {
89
89
  if (depthMap.has(taskId))
90
- return depthMap.get(taskId);
90
+ return depthMap.get(taskId) ?? 0;
91
91
  const task = taskMap.get(taskId);
92
92
  const parentId = task?.parent_task?.id;
93
93
  if (!parentId) {
@@ -167,7 +167,7 @@ export function TaskView({ tasks }) {
167
167
  if (!tasksByParent.has(parentId)) {
168
168
  tasksByParent.set(parentId, []);
169
169
  }
170
- tasksByParent.get(parentId).push(task);
170
+ tasksByParent.get(parentId)?.push(task);
171
171
  });
172
172
  // Process root tasks first, then children
173
173
  const processedIds = new Set();
@@ -1,10 +1,26 @@
1
1
  import { LocalConfig } from './guild-config.js';
2
2
  import { GuildAPIClient } from './api-client.js';
3
+ import type { AgentVersion } from './api-types.js';
4
+ /** Thrown when the ephemeral version build times out or polling fails. */
5
+ export declare class BuildTimeoutError extends Error {
6
+ constructor(message?: string);
7
+ }
8
+ /** Thrown when the ephemeral version build fails validation. */
9
+ export declare class BuildFailedError extends Error {
10
+ readonly failedSteps: {
11
+ name: string;
12
+ content?: string;
13
+ }[];
14
+ constructor(failedSteps: {
15
+ name: string;
16
+ content?: string;
17
+ }[]);
18
+ }
3
19
  /**
4
20
  * Resolve an agent identifier to a fully-qualified form.
5
21
  *
6
- * If the identifier already contains `/` or is a UUID, return it verbatim.
7
- * Otherwise, resolve the default owner and prepend `owner.name/`.
22
+ * If the identifier already contains `~` or is a UUID, return it verbatim.
23
+ * Otherwise, resolve the default owner and prepend `owner.name~`.
8
24
  */
9
25
  export declare function resolveAgentRef(client: GuildAPIClient, identifier: string): Promise<string>;
10
26
  /**
@@ -27,4 +43,60 @@ export declare function readAgentFiles(cwd: string): Promise<{
27
43
  path: string;
28
44
  content: string;
29
45
  }[]>;
46
+ /**
47
+ * Compute a deterministic hash of agent files.
48
+ * Sorts by path to ensure consistent ordering.
49
+ */
50
+ export declare function hashAgentFiles(files: {
51
+ path: string;
52
+ content: string;
53
+ }[]): string;
54
+ /**
55
+ * Get or create an ephemeral version, skipping the build if files haven't
56
+ * changed since the last successful ephemeral build.
57
+ *
58
+ * Returns the version (cached or newly created), a cache hit flag, and the
59
+ * computed hash (used by buildEphemeralVersion to write the cache on success).
60
+ */
61
+ export declare function getOrCreateEphemeral(client: GuildAPIClient, agentId: string, files: {
62
+ path: string;
63
+ content: string;
64
+ }[], cwd: string, summary: string, options?: {
65
+ noCache?: boolean;
66
+ }): Promise<{
67
+ version: AgentVersion;
68
+ cached: boolean;
69
+ hash: string;
70
+ }>;
71
+ /**
72
+ * Build an ephemeral version end-to-end: get-or-create, poll for validation,
73
+ * cache on success, and report build failures.
74
+ *
75
+ * Consolidates the build/poll/cache pattern used by both `guild agent test`
76
+ * and `guild agent chat`.
77
+ */
78
+ export declare function buildEphemeralVersion(client: GuildAPIClient, agentId: string, files: {
79
+ path: string;
80
+ content: string;
81
+ }[], cwd: string, summary: string, options?: {
82
+ noCache?: boolean;
83
+ }): Promise<{
84
+ version: AgentVersion;
85
+ cached: boolean;
86
+ }>;
87
+ /** Thrown when the specified bundle file cannot be found on disk. */
88
+ export declare class BundleNotFoundError extends Error {
89
+ constructor(filePath: string);
90
+ }
91
+ /**
92
+ * Upload a pre-built bundle as an ephemeral version.
93
+ *
94
+ * The bundle file must be gzip+base64 encoded (the output of
95
+ * `esbuild ... | gzip | base64`). Source files are included for
96
+ * dashboard viewing, but the server skips its own build step because
97
+ * the ready-to-run artifact is already provided.
98
+ */
99
+ export declare function buildBundledVersion(client: GuildAPIClient, agentId: string, bundlePath: string, cwd: string, summary: string): Promise<{
100
+ version: AgentVersion;
101
+ }>;
30
102
  //# sourceMappingURL=agent-helpers.d.ts.map