@guildai/cli 0.7.0 → 0.7.1
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/commands/agent/chat.js +29 -62
- package/dist/commands/agent/clone.js +1 -1
- package/dist/commands/agent/code.js +1 -1
- package/dist/commands/agent/fork.js +2 -2
- package/dist/commands/agent/get.js +1 -1
- package/dist/commands/agent/grep.js +2 -2
- package/dist/commands/agent/init.js +10 -3
- package/dist/commands/agent/list.js +9 -1
- package/dist/commands/agent/publish.js +1 -1
- package/dist/commands/agent/revalidate.js +1 -1
- package/dist/commands/agent/save.js +23 -2
- package/dist/commands/agent/test.js +59 -92
- package/dist/commands/agent/unpublish.js +1 -1
- package/dist/commands/agent/update.js +1 -1
- package/dist/commands/agent/versions.js +1 -1
- package/dist/commands/agent/workspaces.js +1 -1
- package/dist/commands/chat.js +28 -15
- package/dist/commands/config/list.js +2 -2
- package/dist/commands/integration/operation/create.js +2 -2
- package/dist/commands/integration/operation/list.js +2 -2
- package/dist/commands/integration/update.js +1 -1
- package/dist/commands/integration/version/get.js +2 -2
- package/dist/commands/integration/version/publish.js +2 -2
- package/dist/commands/integration/version/test.js +2 -2
- package/dist/commands/session/events.js +7 -3
- package/dist/commands/workspace/get.js +1 -1
- package/dist/commands/workspace/list.js +28 -6
- package/dist/commands/workspace/select.js +40 -9
- package/dist/components/TaskView.js +2 -2
- package/dist/lib/agent-helpers.d.ts +59 -2
- package/dist/lib/agent-helpers.js +153 -8
- package/dist/lib/alternate-screen.js +2 -0
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.js +33 -0
- package/dist/lib/session-events.d.ts +1 -1
- package/dist/lib/session-events.js +5 -3
- package/dist/lib/session-polling.d.ts +8 -0
- package/dist/lib/session-polling.js +49 -0
- package/dist/lib/spinners.js +4 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { render } from 'ink';
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import open from 'open';
|
|
7
7
|
import { hyperlink } from '../../lib/colors.js';
|
|
8
|
-
import { getAgentId, readAgentFiles } from '../../lib/agent-helpers.js';
|
|
8
|
+
import { getAgentId, readAgentFiles, buildEphemeralVersion, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
|
|
9
9
|
import { ChatApp, createSession, ensureAuthenticated } from '../chat.js';
|
|
10
10
|
import { readFileSync } from 'fs';
|
|
11
11
|
import path from 'path';
|
|
@@ -15,7 +15,6 @@ import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
|
|
|
15
15
|
import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
|
|
16
16
|
import { format } from '../../lib/progress.js';
|
|
17
17
|
import { showBetaGuidance } from '../../lib/auth.js';
|
|
18
|
-
import { pollUntilComplete } from '../../lib/polling.js';
|
|
19
18
|
import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
|
|
20
19
|
import * as readline from 'readline';
|
|
21
20
|
import { pollForResponse } from '../../lib/session-polling.js';
|
|
@@ -31,13 +30,14 @@ export function createAgentChatCommand() {
|
|
|
31
30
|
.description('Chat with the agent in current directory')
|
|
32
31
|
.argument('[prompt...]', 'Optional initial prompt for the agent')
|
|
33
32
|
.option('--path <dir>', 'Path to agent directory (defaults to current directory)')
|
|
34
|
-
.option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner
|
|
33
|
+
.option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner~workspace-name)')
|
|
35
34
|
.option('--mode <format>', 'Input mode: json (one-shot) or jsonl (line-by-line)')
|
|
36
|
-
.option('--version <id>', 'Chat with a specific version (UUID or version number)')
|
|
35
|
+
.option('--agent-version <id>', 'Chat with a specific version (UUID or version number)')
|
|
37
36
|
.option('--no-splash', 'Skip the splash screen animation')
|
|
38
37
|
.option('--resume <session-id>', 'Resume an existing session')
|
|
39
38
|
.option('--open', 'Open session in web dashboard')
|
|
40
|
-
.
|
|
39
|
+
.option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
|
|
40
|
+
.addHelpText('after', '\nTo chat with a published agent by name: guild chat --agent owner~agent-name')
|
|
41
41
|
.action(async (promptArgs, options) => {
|
|
42
42
|
try {
|
|
43
43
|
// Get agent ID from guild.json in the specified path
|
|
@@ -69,12 +69,14 @@ export function createAgentChatCommand() {
|
|
|
69
69
|
// Default: create ephemeral version from working directory.
|
|
70
70
|
// --version: use a specific existing version.
|
|
71
71
|
let version;
|
|
72
|
-
|
|
72
|
+
let ephemeralCached = false;
|
|
73
|
+
if (options.agentVersion) {
|
|
73
74
|
// Explicit version: look it up by ID or version number
|
|
74
75
|
const existingVersions = (await client.get(`/agents/${agentId}/versions`));
|
|
75
|
-
const match = existingVersions.items.find((v) => v.id === options.
|
|
76
|
+
const match = existingVersions.items.find((v) => v.id === options.agentVersion ||
|
|
77
|
+
v.version_number === options.agentVersion);
|
|
76
78
|
if (!match) {
|
|
77
|
-
console.error(`Error: Version not found: ${options.
|
|
79
|
+
console.error(`Error: Version not found: ${options.agentVersion}`);
|
|
78
80
|
console.error('');
|
|
79
81
|
console.error('List available versions:');
|
|
80
82
|
console.error(` guild agent versions ${agentId}`);
|
|
@@ -87,61 +89,13 @@ export function createAgentChatCommand() {
|
|
|
87
89
|
console.error('Building agent...');
|
|
88
90
|
}
|
|
89
91
|
const agentFiles = await readAgentFiles(resolvedPath);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Wait for build to complete (skip if version already validated)
|
|
97
|
-
if (version.validation_status === 'PENDING' ||
|
|
98
|
-
version.validation_status === 'RUNNING') {
|
|
99
|
-
const pollResult = await pollUntilComplete({
|
|
100
|
-
resourceId: version.id,
|
|
101
|
-
endpoint: `/versions/${version.id}`,
|
|
102
|
-
isComplete: (response) => response.validation_status !== 'PENDING' &&
|
|
103
|
-
response.validation_status !== 'RUNNING',
|
|
104
|
-
message: 'Building...',
|
|
105
|
-
successMessage: 'Build finished',
|
|
106
|
-
timeoutMessage: 'Build timed out',
|
|
107
|
-
maxAttempts: 120,
|
|
108
|
-
delayMs: 1000,
|
|
109
|
-
});
|
|
110
|
-
if (!pollResult.success || !pollResult.response) {
|
|
111
|
-
console.error('Error: Agent build did not complete');
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
version = pollResult.response;
|
|
115
|
-
}
|
|
116
|
-
if (version.validation_status === 'FAILED') {
|
|
117
|
-
console.error('Error: Agent build failed');
|
|
118
|
-
console.error('');
|
|
119
|
-
// Fetch validation steps to show the actual error
|
|
120
|
-
try {
|
|
121
|
-
const stepsResponse = await client.get(`/versions/${version.id}/validation/steps`);
|
|
122
|
-
const failedSteps = stepsResponse.steps.filter((step) => step.status === 'FAILED');
|
|
123
|
-
if (failedSteps.length > 0) {
|
|
124
|
-
for (const step of failedSteps) {
|
|
125
|
-
console.error(`Step "${step.name}" failed:`);
|
|
126
|
-
if (step.content) {
|
|
127
|
-
console.error(step.content);
|
|
128
|
-
}
|
|
129
|
-
console.error('');
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
console.error('No failed steps found. Check validation logs for details.');
|
|
134
|
-
console.error('');
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
// If we can't fetch steps, just show generic message
|
|
139
|
-
console.error('Could not fetch validation details.');
|
|
140
|
-
console.error('');
|
|
92
|
+
const noCache = options.cache === false;
|
|
93
|
+
const result = await buildEphemeralVersion(client, agentId, agentFiles, resolvedPath, '[Chat] Ephemeral development version', { noCache });
|
|
94
|
+
version = result.version;
|
|
95
|
+
ephemeralCached = result.cached;
|
|
96
|
+
if (ephemeralCached && !quiet) {
|
|
97
|
+
console.error('No changes detected, reusing cached build.');
|
|
141
98
|
}
|
|
142
|
-
console.error('Fix the issues and retry:');
|
|
143
|
-
console.error(` guild agent chat ${agentId}`);
|
|
144
|
-
process.exit(1);
|
|
145
99
|
}
|
|
146
100
|
// Branch: JSON/JSONL modes vs interactive
|
|
147
101
|
if (options.mode === 'json' || options.mode === 'jsonl') {
|
|
@@ -274,6 +228,19 @@ export function createAgentChatCommand() {
|
|
|
274
228
|
}
|
|
275
229
|
}
|
|
276
230
|
catch (error) {
|
|
231
|
+
if (error instanceof BuildTimeoutError) {
|
|
232
|
+
console.error('Error: Build did not complete');
|
|
233
|
+
console.error('');
|
|
234
|
+
console.error(error.message);
|
|
235
|
+
console.error('');
|
|
236
|
+
console.error('Check build status:');
|
|
237
|
+
console.error(' guild agent versions');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
if (error instanceof BuildFailedError) {
|
|
241
|
+
console.error(`Error: ${error.message}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
277
244
|
const formattedError = handleAxiosError(error);
|
|
278
245
|
if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
|
|
279
246
|
formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
|
|
@@ -23,7 +23,7 @@ export function createAgentCloneCommand() {
|
|
|
23
23
|
const cmd = new Command('clone');
|
|
24
24
|
cmd
|
|
25
25
|
.description('Clone an existing agent repository')
|
|
26
|
-
.argument('<identifier>', 'Agent ID or full name to clone (e.g., owner
|
|
26
|
+
.argument('<identifier>', 'Agent ID or full name to clone (e.g., owner~agent-name)')
|
|
27
27
|
.option('--directory <path>', 'Target directory for clone')
|
|
28
28
|
.option('--force', 'Clone even if directory is not empty', false)
|
|
29
29
|
.action(async (agentId, options) => {
|
|
@@ -11,7 +11,7 @@ export function createAgentCodeCommand() {
|
|
|
11
11
|
const cmd = new Command('code');
|
|
12
12
|
cmd
|
|
13
13
|
.description('Fetch the latest code for an agent')
|
|
14
|
-
.argument('[identifier]', 'Agent ID or full name (e.g., owner
|
|
14
|
+
.argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
|
|
15
15
|
.option('--draft', 'Include draft versions (default: only published)', false)
|
|
16
16
|
.option('--output <directory>', 'Write files to directory instead of printing JSON')
|
|
17
17
|
.action(async (agentIdArg, options) => {
|
|
@@ -48,7 +48,7 @@ export function createAgentForkCommand() {
|
|
|
48
48
|
const cmd = new Command('fork');
|
|
49
49
|
cmd
|
|
50
50
|
.description('Fork an existing agent version to create a new agent')
|
|
51
|
-
.argument('[identifier]', 'Agent ID, full name, or agent:version (e.g., owner
|
|
51
|
+
.argument('[identifier]', 'Agent ID, full name, or agent:version (e.g., owner~agent-name:version_xyz)')
|
|
52
52
|
.option('--name <name>', 'Name for the forked agent')
|
|
53
53
|
.option('--description <desc>', 'Description for the forked agent')
|
|
54
54
|
.option('--directory <path>', 'Target directory for clone')
|
|
@@ -65,7 +65,7 @@ export function createAgentForkCommand() {
|
|
|
65
65
|
const agentPart = identifierArg.substring(0, colonIndex);
|
|
66
66
|
sourceVersionId = identifierArg.substring(colonIndex + 1);
|
|
67
67
|
if (!agentPart || !sourceVersionId) {
|
|
68
|
-
output.error('Error: Invalid argument format', 'Expected: [identifier] or [identifier]:[version-id]\nExample: guild agent fork owner
|
|
68
|
+
output.error('Error: Invalid argument format', 'Expected: [identifier] or [identifier]:[version-id]\nExample: guild agent fork owner~agent-name:version_xyz\n\nTo find versions:\n guild agent versions <agent-id>');
|
|
69
69
|
process.exit(1);
|
|
70
70
|
}
|
|
71
71
|
const resolved = await getAgentId(agentPart);
|
|
@@ -10,7 +10,7 @@ export function createAgentGetCommand() {
|
|
|
10
10
|
const cmd = new Command('get');
|
|
11
11
|
cmd
|
|
12
12
|
.description('Get agent details')
|
|
13
|
-
.argument('[identifier]', 'Agent ID or full name (e.g., owner
|
|
13
|
+
.argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
|
|
14
14
|
.action(async (idArg) => {
|
|
15
15
|
const output = createOutputWriter();
|
|
16
16
|
try {
|
|
@@ -94,10 +94,10 @@ async function grepAllAgents(client, patternRE, publishedOnly, output) {
|
|
|
94
94
|
}
|
|
95
95
|
catch (ex) {
|
|
96
96
|
const formattedError = handleAxiosError(ex);
|
|
97
|
-
output.error(`${agent.owner?.name}
|
|
97
|
+
output.error(`${agent.owner?.name}~${agent.name}: ${formattedError.details}`);
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
|
-
searchFiles(files, patternRE, `${agent.owner?.name}
|
|
100
|
+
searchFiles(files, patternRE, `${agent.owner?.name}~${agent.name}/`, output);
|
|
101
101
|
}));
|
|
102
102
|
offset += response.pagination.limit;
|
|
103
103
|
if (!response.pagination.has_more)
|
|
@@ -319,17 +319,24 @@ export function createAgentInitCommand() {
|
|
|
319
319
|
};
|
|
320
320
|
await fs.writeFile(guildJsonPath, JSON.stringify(guildConfig, null, 2) + '\n', 'utf-8');
|
|
321
321
|
steps.succeed('Create guild.json');
|
|
322
|
-
// Add guild.json to .gitignore if not already present
|
|
322
|
+
// Add guild.json and .guild/cache/ to .gitignore if not already present
|
|
323
323
|
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
324
324
|
try {
|
|
325
325
|
const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
326
|
+
let additions = '';
|
|
326
327
|
if (!gitignoreContent.includes('guild.json')) {
|
|
327
|
-
|
|
328
|
+
additions += '\nguild.json\n';
|
|
329
|
+
}
|
|
330
|
+
if (!gitignoreContent.includes('.guild/cache/')) {
|
|
331
|
+
additions += '.guild/cache/\n';
|
|
332
|
+
}
|
|
333
|
+
if (additions) {
|
|
334
|
+
await fs.appendFile(gitignorePath, additions);
|
|
328
335
|
}
|
|
329
336
|
}
|
|
330
337
|
catch {
|
|
331
338
|
// .gitignore doesn't exist (backend should have created it), create it
|
|
332
|
-
await fs.writeFile(gitignorePath, 'guild.json\n');
|
|
339
|
+
await fs.writeFile(gitignorePath, 'guild.json\n.guild/cache/\n');
|
|
333
340
|
}
|
|
334
341
|
// Install pre-push hook to block direct git push
|
|
335
342
|
await installPrePushHook(targetDir);
|
|
@@ -3,7 +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 { handleAxiosError } from '../../lib/errors.js';
|
|
6
|
+
import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
|
|
7
7
|
import { getOutputMode } from '../../lib/output-mode.js';
|
|
8
8
|
import { createOutputWriter, formatAgentTable } from '../../lib/output.js';
|
|
9
9
|
const SORT_MAP = {
|
|
@@ -19,6 +19,7 @@ export function createAgentListCommand() {
|
|
|
19
19
|
.option('--search <query>', 'Search agents by name or description')
|
|
20
20
|
.option('--sort <field>', 'Sort by: updated, newest, name, popular (default: updated)', 'updated')
|
|
21
21
|
.option('--published', 'Only show published agents')
|
|
22
|
+
.option('--workspace <id>', 'Filter agents by workspace ID or name')
|
|
22
23
|
.option('--limit <number>', 'Number of results to return', '20')
|
|
23
24
|
.option('--offset <number>', 'Offset for pagination', '0')
|
|
24
25
|
.action(async (options) => {
|
|
@@ -39,6 +40,9 @@ export function createAgentListCommand() {
|
|
|
39
40
|
if (options.published) {
|
|
40
41
|
params.append('published_only', 'true');
|
|
41
42
|
}
|
|
43
|
+
if (options.workspace) {
|
|
44
|
+
params.append('for_workspace', options.workspace);
|
|
45
|
+
}
|
|
42
46
|
const sortField = SORT_MAP[options.sort];
|
|
43
47
|
if (sortField) {
|
|
44
48
|
params.append('sort_by', sortField);
|
|
@@ -53,6 +57,10 @@ export function createAgentListCommand() {
|
|
|
53
57
|
}
|
|
54
58
|
catch (error) {
|
|
55
59
|
const formattedError = handleAxiosError(error);
|
|
60
|
+
if (formattedError.code === ErrorCodes.NOT_FOUND) {
|
|
61
|
+
output.error('Workspace not found');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
56
64
|
output.error(`Failed to list agents: ${formattedError.details}`);
|
|
57
65
|
process.exit(1);
|
|
58
66
|
}
|
|
@@ -10,7 +10,7 @@ export function createAgentPublishCommand() {
|
|
|
10
10
|
const cmd = new Command('publish');
|
|
11
11
|
cmd
|
|
12
12
|
.description('Publish the latest draft version of an agent')
|
|
13
|
-
.argument('[identifier]', 'Agent ID or full name (e.g., owner
|
|
13
|
+
.argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
|
|
14
14
|
.option('--wait', 'Wait for validation to complete before publishing')
|
|
15
15
|
.action(async (agentIdArg, options) => {
|
|
16
16
|
const output = createOutputWriter();
|
|
@@ -10,7 +10,7 @@ export function createAgentRevalidateCommand() {
|
|
|
10
10
|
const cmd = new Command('revalidate');
|
|
11
11
|
cmd
|
|
12
12
|
.description('Revalidate an agent version')
|
|
13
|
-
.argument('[identifier]', 'Agent ID or full name (e.g., owner
|
|
13
|
+
.argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
|
|
14
14
|
.argument('[version-id]', 'ID of the version to revalidate (uses latest if omitted)')
|
|
15
15
|
.action(async (agentIdArg, versionIdArg) => {
|
|
16
16
|
const output = createOutputWriter();
|
|
@@ -44,6 +44,7 @@ export function createAgentSaveCommand() {
|
|
|
44
44
|
output.error('Commit message is required with --all', 'Provide a message describing your changes:\n guild agent save -A --message "Add new feature"');
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
|
+
let versionNumber;
|
|
47
48
|
try {
|
|
48
49
|
// Check for guild.json
|
|
49
50
|
const guildJsonPath = path.join(cwd, 'guild.json');
|
|
@@ -88,8 +89,22 @@ export function createAgentSaveCommand() {
|
|
|
88
89
|
}
|
|
89
90
|
packageJson.version = newVersion;
|
|
90
91
|
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
92
|
+
versionNumber = newVersion;
|
|
91
93
|
output.progress(`✓ Bumped version: ${currentVersion} → ${newVersion}`);
|
|
92
94
|
}
|
|
95
|
+
else {
|
|
96
|
+
// No bump — read version from package.json if it exists
|
|
97
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
98
|
+
try {
|
|
99
|
+
const pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
100
|
+
if (pkg.version) {
|
|
101
|
+
versionNumber = pkg.version;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// No package.json or unreadable — version stays undefined
|
|
106
|
+
}
|
|
107
|
+
}
|
|
93
108
|
// Check for uncommitted changes and unpushed commits
|
|
94
109
|
const { stdout: statusOutput } = await runGit(['status', '--porcelain'], {
|
|
95
110
|
cwd,
|
|
@@ -249,13 +264,19 @@ export function createAgentSaveCommand() {
|
|
|
249
264
|
const commitSha = shaOutput.trim();
|
|
250
265
|
// Create version in guildcore (always as DRAFT, publish happens separately)
|
|
251
266
|
const client = new GuildAPIClient();
|
|
252
|
-
|
|
267
|
+
const agentId = guildConfig?.agent_id;
|
|
268
|
+
if (!agentId) {
|
|
269
|
+
output.error('Not in an agent directory');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
let version = await client.post(`/agents/${agentId}/versions`, {
|
|
253
273
|
commit_sha: commitSha,
|
|
254
274
|
summary: versionMessage,
|
|
255
275
|
version_type: 'COMMITTED',
|
|
276
|
+
...(versionNumber ? { version_number: versionNumber } : {}),
|
|
256
277
|
});
|
|
257
278
|
output.progress(`✓ Created version (${version.id})`);
|
|
258
|
-
if (options.wait) {
|
|
279
|
+
if (options.wait || options.publish) {
|
|
259
280
|
version = await waitForValidation(version.id, output);
|
|
260
281
|
}
|
|
261
282
|
if (options.publish) {
|
|
@@ -13,13 +13,13 @@ import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
|
|
|
13
13
|
import { format } from '../../lib/progress.js';
|
|
14
14
|
import { showBetaGuidance } from '../../lib/auth.js';
|
|
15
15
|
import * as readline from 'readline';
|
|
16
|
+
import { parseEventFilter } from '../../lib/event-filter.js';
|
|
16
17
|
import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
|
|
17
|
-
import { pollForResponse } from '../../lib/session-polling.js';
|
|
18
|
+
import { pollForResponse, pollForResponseWithEvents, } from '../../lib/session-polling.js';
|
|
18
19
|
import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
|
|
19
20
|
import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
|
|
20
21
|
import { GitError, formatGitError } from '../../lib/git.js';
|
|
21
|
-
import {
|
|
22
|
-
import { readAgentFiles } from '../../lib/agent-helpers.js';
|
|
22
|
+
import { readAgentFiles, buildEphemeralVersion, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
|
|
23
23
|
import { fetchSession, fetchSessionEvents } from '../../lib/session-resume.js';
|
|
24
24
|
import { ChatApp, ensureAuthenticated } from '../chat.js';
|
|
25
25
|
import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
|
|
@@ -33,11 +33,17 @@ export function createAgentTestCommand() {
|
|
|
33
33
|
.description('Test agent in interactive REPL session')
|
|
34
34
|
.option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
|
|
35
35
|
.option('--mode <format>', 'Input mode: json (one-shot) or jsonl (line-by-line)')
|
|
36
|
-
.option('--version <id>', 'Test a specific version (UUID or version number)')
|
|
36
|
+
.option('--agent-version <id>', 'Test a specific version (UUID or version number)')
|
|
37
37
|
.option('--resume <session-id>', 'Resume an existing test session')
|
|
38
38
|
.option('--open', 'Open session in web dashboard')
|
|
39
|
+
.option('--events <types>', 'Event types to stream (default: user). Shorthands: none, user, system, all, or comma-separated type names')
|
|
40
|
+
.option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
|
|
39
41
|
.action(async (options) => {
|
|
40
42
|
const cwd = process.cwd();
|
|
43
|
+
// Parse --events filter once, before any branching
|
|
44
|
+
const eventFilter = options.events
|
|
45
|
+
? parseEventFilter(options.events)
|
|
46
|
+
: undefined;
|
|
41
47
|
try {
|
|
42
48
|
// Handle --resume: skip build, fetch existing session, hand off to ChatApp
|
|
43
49
|
if (options.resume) {
|
|
@@ -57,6 +63,7 @@ export function createAgentTestCommand() {
|
|
|
57
63
|
resumeSession,
|
|
58
64
|
resumeEvents,
|
|
59
65
|
resumeCommand,
|
|
66
|
+
eventFilter,
|
|
60
67
|
}), { exitOnCtrlC: false });
|
|
61
68
|
await waitUntilExit();
|
|
62
69
|
return;
|
|
@@ -75,25 +82,6 @@ export function createAgentTestCommand() {
|
|
|
75
82
|
console.error(' guild agent clone <agent-id>');
|
|
76
83
|
process.exit(1);
|
|
77
84
|
}
|
|
78
|
-
// Read agent files from disk for ephemeral version creation.
|
|
79
|
-
// Skipped when --version is provided (testing a specific existing version).
|
|
80
|
-
let agentFiles = null;
|
|
81
|
-
if (!options.version) {
|
|
82
|
-
try {
|
|
83
|
-
agentFiles = await readAgentFiles(cwd);
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
const err = error;
|
|
87
|
-
console.error('Error: Could not read agent files');
|
|
88
|
-
console.error('');
|
|
89
|
-
console.error(err.message);
|
|
90
|
-
console.error('');
|
|
91
|
-
console.error('Ensure you are in an agent directory with:');
|
|
92
|
-
console.error(' - agent.ts (required)');
|
|
93
|
-
console.error(' - package.json (required)');
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
85
|
// If using JSON input, read and validate it early (before auth/session creation)
|
|
98
86
|
// This provides better UX - fail fast on bad JSON without needing auth
|
|
99
87
|
let inputData;
|
|
@@ -135,13 +123,15 @@ export function createAgentTestCommand() {
|
|
|
135
123
|
// Resolve version for testing
|
|
136
124
|
const client = new GuildAPIClient();
|
|
137
125
|
let version;
|
|
126
|
+
let ephemeralCached = false;
|
|
138
127
|
try {
|
|
139
|
-
if (options.
|
|
128
|
+
if (options.agentVersion) {
|
|
140
129
|
// Explicit version: look it up by ID or version number
|
|
141
130
|
const existingVersions = (await client.get(`/agents/${guildConfig.agent_id}/versions`));
|
|
142
|
-
const match = existingVersions.items.find((v) => v.id === options.
|
|
131
|
+
const match = existingVersions.items.find((v) => v.id === options.agentVersion ||
|
|
132
|
+
v.version_number === options.agentVersion);
|
|
143
133
|
if (!match) {
|
|
144
|
-
console.error(`Error: Version not found: ${options.
|
|
134
|
+
console.error(`Error: Version not found: ${options.agentVersion}`);
|
|
145
135
|
console.error('');
|
|
146
136
|
console.error('List available versions:');
|
|
147
137
|
console.error(` guild agent versions ${guildConfig.agent_id}`);
|
|
@@ -150,15 +140,40 @@ export function createAgentTestCommand() {
|
|
|
150
140
|
version = match;
|
|
151
141
|
}
|
|
152
142
|
else {
|
|
153
|
-
// Default:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
143
|
+
// Default: build ephemeral version from working directory,
|
|
144
|
+
// reusing the last build if files haven't changed.
|
|
145
|
+
const agentFiles = await readAgentFiles(cwd);
|
|
146
|
+
const noCache = options.cache === false;
|
|
147
|
+
const result = await buildEphemeralVersion(client, guildConfig.agent_id, agentFiles, cwd, '[Test] Ephemeral development version', { noCache });
|
|
148
|
+
version = result.version;
|
|
149
|
+
ephemeralCached = result.cached;
|
|
159
150
|
}
|
|
160
151
|
}
|
|
161
152
|
catch (error) {
|
|
153
|
+
if (error instanceof BuildTimeoutError) {
|
|
154
|
+
console.error('Error: Build did not complete');
|
|
155
|
+
console.error('');
|
|
156
|
+
console.error(error.message);
|
|
157
|
+
console.error('');
|
|
158
|
+
console.error('Check build status:');
|
|
159
|
+
console.error(' guild agent versions');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
if (error instanceof BuildFailedError) {
|
|
163
|
+
console.error(`Error: ${error.message}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
if (error instanceof Error &&
|
|
167
|
+
error.message.startsWith('Missing required')) {
|
|
168
|
+
console.error('Error: Could not read agent files');
|
|
169
|
+
console.error('');
|
|
170
|
+
console.error(error.message);
|
|
171
|
+
console.error('');
|
|
172
|
+
console.error('Ensure you are in an agent directory with:');
|
|
173
|
+
console.error(' - agent.ts (required)');
|
|
174
|
+
console.error(' - package.json (required)');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
162
177
|
const formattedError = handleAxiosError(error);
|
|
163
178
|
if (formattedError.code === ErrorCodes.NOT_FOUND) {
|
|
164
179
|
console.error(`Error: Agent not found: ${guildConfig.agent_id}`);
|
|
@@ -174,61 +189,6 @@ export function createAgentTestCommand() {
|
|
|
174
189
|
}
|
|
175
190
|
throw error;
|
|
176
191
|
}
|
|
177
|
-
// Wait for validation to complete
|
|
178
|
-
const pollResult = await pollUntilComplete({
|
|
179
|
-
resourceId: version.id,
|
|
180
|
-
endpoint: `/versions/${version.id}`,
|
|
181
|
-
isComplete: (response) => response.validation_status !== 'PENDING' &&
|
|
182
|
-
response.validation_status !== 'RUNNING',
|
|
183
|
-
message: 'Building...',
|
|
184
|
-
successMessage: 'Build finished',
|
|
185
|
-
timeoutMessage: 'Build timed out',
|
|
186
|
-
maxAttempts: 120,
|
|
187
|
-
delayMs: 1000,
|
|
188
|
-
});
|
|
189
|
-
if (!pollResult.success || !pollResult.response) {
|
|
190
|
-
console.error('Error: Build did not complete');
|
|
191
|
-
console.error('');
|
|
192
|
-
console.error('The agent version build timed out or failed to report status.');
|
|
193
|
-
console.error('');
|
|
194
|
-
console.error('Check build status:');
|
|
195
|
-
console.error(` guild agent versions ${guildConfig.agent_id}`);
|
|
196
|
-
console.error('');
|
|
197
|
-
console.error('Retry the test:');
|
|
198
|
-
console.error(' guild agent test');
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
version = pollResult.response;
|
|
202
|
-
if (version.validation_status === 'FAILED') {
|
|
203
|
-
console.error('Error: Build failed');
|
|
204
|
-
console.error('');
|
|
205
|
-
// Fetch validation steps to show the actual error
|
|
206
|
-
try {
|
|
207
|
-
const stepsResponse = await client.get(`/versions/${version.id}/validation/steps`);
|
|
208
|
-
const failedSteps = stepsResponse.steps.filter((step) => step.status === 'FAILED');
|
|
209
|
-
if (failedSteps.length > 0) {
|
|
210
|
-
for (const step of failedSteps) {
|
|
211
|
-
console.error(`Step "${step.name}" failed:`);
|
|
212
|
-
if (step.content) {
|
|
213
|
-
console.error(step.content);
|
|
214
|
-
}
|
|
215
|
-
console.error('');
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
console.error('No failed steps found. Check validation logs for details.');
|
|
220
|
-
console.error('');
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
// If we can't fetch steps, just show generic message
|
|
225
|
-
console.error('Could not fetch validation details.');
|
|
226
|
-
console.error('');
|
|
227
|
-
}
|
|
228
|
-
console.error('Fix the issues and retry:');
|
|
229
|
-
console.error(' guild agent test');
|
|
230
|
-
process.exit(1);
|
|
231
|
-
}
|
|
232
192
|
// Create test session
|
|
233
193
|
let session;
|
|
234
194
|
try {
|
|
@@ -254,9 +214,11 @@ export function createAgentTestCommand() {
|
|
|
254
214
|
const quiet = isQuietMode();
|
|
255
215
|
if (!quiet) {
|
|
256
216
|
console.log(`✓ Agent: ${guildConfig.name} (${guildConfig.agent_id})`);
|
|
257
|
-
const versionDisplay = options.
|
|
258
|
-
? options.
|
|
259
|
-
:
|
|
217
|
+
const versionDisplay = options.agentVersion
|
|
218
|
+
? options.agentVersion
|
|
219
|
+
: ephemeralCached
|
|
220
|
+
? 'ephemeral (cached, no changes)'
|
|
221
|
+
: 'ephemeral (working directory)';
|
|
260
222
|
console.log(`✓ Version: ${versionDisplay}`);
|
|
261
223
|
console.log(`✓ Workspace: ${workspaceId}`);
|
|
262
224
|
const sessionLink = session.session_url
|
|
@@ -279,7 +241,9 @@ export function createAgentTestCommand() {
|
|
|
279
241
|
});
|
|
280
242
|
// Poll for response (starting from beginning)
|
|
281
243
|
// 3 minutes - allow time for agents that use LLM calls for input parsing
|
|
282
|
-
const { response } =
|
|
244
|
+
const { response } = eventFilter
|
|
245
|
+
? await pollForResponseWithEvents(client, session.id, eventFilter, undefined, 180000)
|
|
246
|
+
: await pollForResponse(client, session.id, undefined, 180000);
|
|
283
247
|
if (!response) {
|
|
284
248
|
console.error('Error: No response received from agent within timeout');
|
|
285
249
|
console.error('');
|
|
@@ -355,7 +319,9 @@ export function createAgentTestCommand() {
|
|
|
355
319
|
content: jsonInput,
|
|
356
320
|
});
|
|
357
321
|
// Wait for response (looking for events after last seen)
|
|
358
|
-
const result =
|
|
322
|
+
const result = eventFilter
|
|
323
|
+
? await pollForResponseWithEvents(client, session.id, eventFilter, lastEventId, 180000)
|
|
324
|
+
: await pollForResponse(client, session.id, lastEventId, 180000);
|
|
359
325
|
lastEventId = result.lastEventId;
|
|
360
326
|
if (!result.response) {
|
|
361
327
|
console.error(`Timeout: No response for line ${lineNumber}`);
|
|
@@ -404,6 +370,7 @@ export function createAgentTestCommand() {
|
|
|
404
370
|
resumeSession: session,
|
|
405
371
|
resumeEvents: [],
|
|
406
372
|
resumeCommand,
|
|
373
|
+
eventFilter,
|
|
407
374
|
}), { exitOnCtrlC: false });
|
|
408
375
|
await waitUntilExit();
|
|
409
376
|
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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) => {
|