@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.
- 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 +11 -4
- 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 +91 -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/session/tasks.js +8 -2
- 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 +74 -2
- package/dist/lib/agent-helpers.js +222 -8
- package/dist/lib/alternate-screen.js +2 -0
- package/dist/lib/api-client.js +2 -1
- package/dist/lib/api-types.d.ts +37 -0
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.js +33 -0
- package/dist/lib/output.d.ts +6 -1
- package/dist/lib/output.js +50 -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
|
@@ -1,23 +1,52 @@
|
|
|
1
1
|
// Copyright 2026 Guild.ai
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { createHash } from 'crypto';
|
|
3
4
|
import * as fs from 'fs/promises';
|
|
4
5
|
import * as path from 'path';
|
|
5
6
|
import { loadLocalConfig } from './guild-config.js';
|
|
6
7
|
import { runGit } from './git.js';
|
|
7
8
|
import { resolveOwnerId } from './owner-helpers.js';
|
|
9
|
+
import { pollUntilComplete } from './polling.js';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Build error types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/** Thrown when the ephemeral version build times out or polling fails. */
|
|
14
|
+
export class BuildTimeoutError extends Error {
|
|
15
|
+
constructor(message = 'The agent version build timed out or failed to report status.') {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'BuildTimeoutError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Thrown when the ephemeral version build fails validation. */
|
|
21
|
+
export class BuildFailedError extends Error {
|
|
22
|
+
failedSteps;
|
|
23
|
+
constructor(failedSteps) {
|
|
24
|
+
const stepSummary = failedSteps.length > 0
|
|
25
|
+
? failedSteps
|
|
26
|
+
.map((s) => `Step "${s.name}" failed:${s.content ? `\n${s.content}` : ''}`)
|
|
27
|
+
.join('\n')
|
|
28
|
+
: 'No failed steps found. Check validation logs for details.';
|
|
29
|
+
super(`Build failed\n\n${stepSummary}\n\nFix the issues and retry.`);
|
|
30
|
+
this.name = 'BuildFailedError';
|
|
31
|
+
this.failedSteps = failedSteps;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
8
34
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
35
|
/**
|
|
10
36
|
* Resolve an agent identifier to a fully-qualified form.
|
|
11
37
|
*
|
|
12
|
-
* If the identifier already contains
|
|
13
|
-
* Otherwise, resolve the default owner and prepend `owner.name
|
|
38
|
+
* If the identifier already contains `~` or is a UUID, return it verbatim.
|
|
39
|
+
* Otherwise, resolve the default owner and prepend `owner.name~`.
|
|
14
40
|
*/
|
|
15
41
|
export async function resolveAgentRef(client, identifier) {
|
|
16
|
-
if (identifier.includes('
|
|
42
|
+
if (identifier.includes('~') || UUID_RE.test(identifier)) {
|
|
17
43
|
return identifier;
|
|
18
44
|
}
|
|
45
|
+
if (identifier.includes('/')) {
|
|
46
|
+
return identifier.replace('/', '~');
|
|
47
|
+
}
|
|
19
48
|
const owner = await resolveOwnerId({ client, interactive: false });
|
|
20
|
-
return `${owner.name}
|
|
49
|
+
return `${owner.name}~${identifier}`;
|
|
21
50
|
}
|
|
22
51
|
/**
|
|
23
52
|
* Get agent ID from argument or guild.json in current directory
|
|
@@ -72,10 +101,7 @@ const REQUIRED_AGENT_FILES = ['agent.ts', 'package.json'];
|
|
|
72
101
|
export async function readAgentFiles(cwd) {
|
|
73
102
|
const { stdout } = await runGit(['ls-files', '--cached', '--others', '--exclude-standard'], { cwd });
|
|
74
103
|
const gitFiles = stdout.split('\n').filter((f) => f.trim().length > 0);
|
|
75
|
-
const relevantFiles = gitFiles.filter((f) =>
|
|
76
|
-
const ext = path.extname(f);
|
|
77
|
-
return ['.ts', '.json', '.md'].includes(ext);
|
|
78
|
-
});
|
|
104
|
+
const relevantFiles = gitFiles.filter((f) => !f.startsWith('.guild/'));
|
|
79
105
|
const files = [];
|
|
80
106
|
for (const filePath of relevantFiles) {
|
|
81
107
|
const fullPath = path.join(cwd, filePath);
|
|
@@ -89,4 +115,192 @@ export async function readAgentFiles(cwd) {
|
|
|
89
115
|
}
|
|
90
116
|
return files;
|
|
91
117
|
}
|
|
118
|
+
const CACHE_DIR = path.join('.guild', 'cache');
|
|
119
|
+
const CACHE_FILE = 'last-ephemeral.json';
|
|
120
|
+
/**
|
|
121
|
+
* Compute a deterministic hash of agent files.
|
|
122
|
+
* Sorts by path to ensure consistent ordering.
|
|
123
|
+
*/
|
|
124
|
+
export function hashAgentFiles(files) {
|
|
125
|
+
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
126
|
+
const hash = createHash('sha256');
|
|
127
|
+
for (const file of sorted) {
|
|
128
|
+
hash.update(file.path);
|
|
129
|
+
hash.update('\0');
|
|
130
|
+
hash.update(file.content);
|
|
131
|
+
hash.update('\0');
|
|
132
|
+
}
|
|
133
|
+
return hash.digest('hex');
|
|
134
|
+
}
|
|
135
|
+
async function readEphemeralCache(cwd) {
|
|
136
|
+
try {
|
|
137
|
+
const cachePath = path.join(cwd, CACHE_DIR, CACHE_FILE);
|
|
138
|
+
const raw = await fs.readFile(cachePath, 'utf-8');
|
|
139
|
+
const parsed = JSON.parse(raw);
|
|
140
|
+
if (typeof parsed.hash === 'string' && typeof parsed.version_id === 'string') {
|
|
141
|
+
return { hash: parsed.hash, version_id: parsed.version_id };
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function writeEphemeralCache(cwd, cache) {
|
|
150
|
+
const dir = path.join(cwd, CACHE_DIR);
|
|
151
|
+
await fs.mkdir(dir, { recursive: true });
|
|
152
|
+
await fs.writeFile(path.join(dir, CACHE_FILE), JSON.stringify(cache, null, 2) + '\n');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get or create an ephemeral version, skipping the build if files haven't
|
|
156
|
+
* changed since the last successful ephemeral build.
|
|
157
|
+
*
|
|
158
|
+
* Returns the version (cached or newly created), a cache hit flag, and the
|
|
159
|
+
* computed hash (used by buildEphemeralVersion to write the cache on success).
|
|
160
|
+
*/
|
|
161
|
+
export async function getOrCreateEphemeral(client, agentId, files, cwd, summary, options) {
|
|
162
|
+
const hash = hashAgentFiles(files);
|
|
163
|
+
if (options?.noCache) {
|
|
164
|
+
const version = (await client.post(`/agents/${agentId}/versions`, {
|
|
165
|
+
files,
|
|
166
|
+
summary,
|
|
167
|
+
version_type: 'EPHEMERAL',
|
|
168
|
+
}));
|
|
169
|
+
return { version, cached: false, hash };
|
|
170
|
+
}
|
|
171
|
+
const cache = await readEphemeralCache(cwd);
|
|
172
|
+
if (cache && cache.hash === hash) {
|
|
173
|
+
// Verify the cached version still exists on the server
|
|
174
|
+
try {
|
|
175
|
+
const version = (await client.get(`/versions/${cache.version_id}`));
|
|
176
|
+
if (version.validation_status === 'PASSED') {
|
|
177
|
+
return { version, cached: true, hash };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Version gone or inaccessible — fall through to create new one
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const version = (await client.post(`/agents/${agentId}/versions`, {
|
|
185
|
+
files,
|
|
186
|
+
summary,
|
|
187
|
+
version_type: 'EPHEMERAL',
|
|
188
|
+
}));
|
|
189
|
+
return { version, cached: false, hash };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Build an ephemeral version end-to-end: get-or-create, poll for validation,
|
|
193
|
+
* cache on success, and report build failures.
|
|
194
|
+
*
|
|
195
|
+
* Consolidates the build/poll/cache pattern used by both `guild agent test`
|
|
196
|
+
* and `guild agent chat`.
|
|
197
|
+
*/
|
|
198
|
+
export async function buildEphemeralVersion(client, agentId, files, cwd, summary, options) {
|
|
199
|
+
const { version: initial, cached, hash, } = await getOrCreateEphemeral(client, agentId, files, cwd, summary, options);
|
|
200
|
+
if (cached) {
|
|
201
|
+
return { version: initial, cached: true };
|
|
202
|
+
}
|
|
203
|
+
// Poll for validation to complete
|
|
204
|
+
const pollResult = await pollUntilComplete({
|
|
205
|
+
resourceId: initial.id,
|
|
206
|
+
endpoint: `/versions/${initial.id}`,
|
|
207
|
+
isComplete: (r) => r.validation_status !== 'PENDING' && r.validation_status !== 'RUNNING',
|
|
208
|
+
message: 'Building...',
|
|
209
|
+
successMessage: 'Build finished',
|
|
210
|
+
timeoutMessage: 'Build timed out',
|
|
211
|
+
maxAttempts: 120,
|
|
212
|
+
delayMs: 1000,
|
|
213
|
+
});
|
|
214
|
+
if (!pollResult.success || !pollResult.response) {
|
|
215
|
+
throw new BuildTimeoutError();
|
|
216
|
+
}
|
|
217
|
+
const version = pollResult.response;
|
|
218
|
+
// Cache the successful build so we can skip it next time
|
|
219
|
+
if (version.validation_status === 'PASSED') {
|
|
220
|
+
await writeEphemeralCache(cwd, { hash, version_id: version.id });
|
|
221
|
+
}
|
|
222
|
+
if (version.validation_status === 'FAILED') {
|
|
223
|
+
let failedSteps = [];
|
|
224
|
+
try {
|
|
225
|
+
const stepsResponse = await client.get(`/versions/${version.id}/validation/steps`);
|
|
226
|
+
failedSteps = stepsResponse.steps
|
|
227
|
+
.filter((step) => step.status === 'FAILED')
|
|
228
|
+
.map((step) => ({ name: step.name, content: step.content ?? undefined }));
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Could not fetch validation details — throw with empty steps
|
|
232
|
+
}
|
|
233
|
+
throw new BuildFailedError(failedSteps);
|
|
234
|
+
}
|
|
235
|
+
return { version, cached: false };
|
|
236
|
+
}
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Bundle version
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
/** Thrown when the specified bundle file cannot be found on disk. */
|
|
241
|
+
export class BundleNotFoundError extends Error {
|
|
242
|
+
constructor(filePath) {
|
|
243
|
+
super(`Bundle file not found: ${filePath}`);
|
|
244
|
+
this.name = 'BundleNotFoundError';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Upload a pre-built bundle as an ephemeral version.
|
|
249
|
+
*
|
|
250
|
+
* The bundle file must be gzip+base64 encoded (the output of
|
|
251
|
+
* `esbuild ... | gzip | base64`). Source files are included for
|
|
252
|
+
* dashboard viewing, but the server skips its own build step because
|
|
253
|
+
* the ready-to-run artifact is already provided.
|
|
254
|
+
*/
|
|
255
|
+
export async function buildBundledVersion(client, agentId, bundlePath, cwd, summary) {
|
|
256
|
+
try {
|
|
257
|
+
await fs.access(bundlePath);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
throw new BundleNotFoundError(bundlePath);
|
|
261
|
+
}
|
|
262
|
+
const bundle = (await fs.readFile(bundlePath, 'utf-8')).trim();
|
|
263
|
+
let files = [];
|
|
264
|
+
try {
|
|
265
|
+
files = await readAgentFiles(cwd);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// No git or missing required files — proceed with bundle-only upload
|
|
269
|
+
}
|
|
270
|
+
const initial = (await client.post(`/agents/${agentId}/versions`, {
|
|
271
|
+
version_type: 'EPHEMERAL',
|
|
272
|
+
bundle,
|
|
273
|
+
encoding: 'gzip;base64',
|
|
274
|
+
files,
|
|
275
|
+
summary,
|
|
276
|
+
}));
|
|
277
|
+
const pollResult = await pollUntilComplete({
|
|
278
|
+
resourceId: initial.id,
|
|
279
|
+
endpoint: `/versions/${initial.id}`,
|
|
280
|
+
isComplete: (r) => r.validation_status !== 'PENDING' && r.validation_status !== 'RUNNING',
|
|
281
|
+
message: 'Validating bundle...',
|
|
282
|
+
successMessage: 'Bundle validated',
|
|
283
|
+
timeoutMessage: 'Bundle validation timed out',
|
|
284
|
+
maxAttempts: 120,
|
|
285
|
+
delayMs: 1000,
|
|
286
|
+
});
|
|
287
|
+
if (!pollResult.success || !pollResult.response) {
|
|
288
|
+
throw new BuildTimeoutError('Bundle validation timed out.');
|
|
289
|
+
}
|
|
290
|
+
const version = pollResult.response;
|
|
291
|
+
if (version.validation_status === 'FAILED') {
|
|
292
|
+
let failedSteps = [];
|
|
293
|
+
try {
|
|
294
|
+
const stepsResponse = await client.get(`/versions/${version.id}/validation/steps`);
|
|
295
|
+
failedSteps = stepsResponse.steps
|
|
296
|
+
.filter((step) => step.status === 'FAILED')
|
|
297
|
+
.map((step) => ({ name: step.name, content: step.content ?? undefined }));
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Could not fetch validation details — throw with empty steps
|
|
301
|
+
}
|
|
302
|
+
throw new BuildFailedError(failedSteps);
|
|
303
|
+
}
|
|
304
|
+
return { version };
|
|
305
|
+
}
|
|
92
306
|
//# sourceMappingURL=agent-helpers.js.map
|
package/dist/lib/api-client.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
import { getAuthToken, clearAuthToken } from './auth.js';
|
|
5
5
|
import { retry, debug, GuildCLIError, ErrorCodes } from './errors.js';
|
|
6
|
-
import { getGuildcoreUrl } from './config.js';
|
|
6
|
+
import { getUserAgent, getGuildcoreUrl } from './config.js';
|
|
7
7
|
import { getIapHeaders } from './iap.js';
|
|
8
8
|
/**
|
|
9
9
|
* HTTP client for Guild API
|
|
@@ -24,6 +24,7 @@ export class GuildAPIClient {
|
|
|
24
24
|
headers: {
|
|
25
25
|
'Content-Type': 'application/json',
|
|
26
26
|
Accept: 'application/json',
|
|
27
|
+
'User-Agent': getUserAgent(),
|
|
27
28
|
},
|
|
28
29
|
});
|
|
29
30
|
debug(`API Client initialized: ${this.baseUrl}, retry: ${this.enableRetry}, max retries: ${this.maxRetries}`);
|
package/dist/lib/api-types.d.ts
CHANGED
|
@@ -361,6 +361,43 @@ export interface CredentialsPolicy {
|
|
|
361
361
|
updated_at: string;
|
|
362
362
|
}
|
|
363
363
|
export type CredentialsPolicyListResponse = PaginatedResponse<CredentialsPolicy>;
|
|
364
|
+
export type TaskStatus = 'CREATED' | 'DISPATCHED' | 'STARTED' | 'RUNNING' | 'WAITING' | 'ERROR' | 'DONE' | 'INTERRUPTED';
|
|
365
|
+
export interface TokenUsage {
|
|
366
|
+
input_tokens: number;
|
|
367
|
+
output_tokens: number;
|
|
368
|
+
cache_read_tokens: number;
|
|
369
|
+
cache_write_tokens: number;
|
|
370
|
+
total_tokens: number;
|
|
371
|
+
llm_call_count: number;
|
|
372
|
+
}
|
|
373
|
+
interface TaskBase {
|
|
374
|
+
id: string;
|
|
375
|
+
entity_type: string;
|
|
376
|
+
status: TaskStatus;
|
|
377
|
+
token_usage: TokenUsage | null;
|
|
378
|
+
created_at: string;
|
|
379
|
+
updated_at: string;
|
|
380
|
+
}
|
|
381
|
+
export interface TaskAgent extends TaskBase {
|
|
382
|
+
entity_type: 'EntTaskAgent';
|
|
383
|
+
agent: {
|
|
384
|
+
name: string;
|
|
385
|
+
full_name?: string;
|
|
386
|
+
} | null;
|
|
387
|
+
version: {
|
|
388
|
+
id: string;
|
|
389
|
+
version_number?: string;
|
|
390
|
+
} | null;
|
|
391
|
+
}
|
|
392
|
+
export interface TaskTool extends TaskBase {
|
|
393
|
+
entity_type: 'EntTaskTool';
|
|
394
|
+
tool_name: string;
|
|
395
|
+
tool_call_id: string;
|
|
396
|
+
request_bytes: number;
|
|
397
|
+
response_bytes: number;
|
|
398
|
+
http_status_code: number;
|
|
399
|
+
}
|
|
400
|
+
export type Task = TaskAgent | TaskTool;
|
|
364
401
|
/**
|
|
365
402
|
* Session entity from API responses.
|
|
366
403
|
* Used by: session list
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare function getCliVersion(): string;
|
|
2
|
+
export declare function isDevBuild(): boolean;
|
|
3
|
+
export declare function getUserAgent(): string;
|
|
1
4
|
/**
|
|
2
5
|
* IAP (Identity-Aware Proxy) configuration for internal *.guildai.dev hosts.
|
|
3
6
|
* Users must run `gcloud auth login` with an authorized Google account to access.
|
package/dist/lib/config.js
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
// Copyright 2026 Guild.ai
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
const FALLBACK_VERSION = 'unknown';
|
|
6
|
+
let cachedVersion;
|
|
7
|
+
export function getCliVersion() {
|
|
8
|
+
if (!cachedVersion) {
|
|
9
|
+
try {
|
|
10
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
11
|
+
cachedVersion = pkg.version ?? FALLBACK_VERSION;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
cachedVersion = FALLBACK_VERSION;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return cachedVersion;
|
|
18
|
+
}
|
|
19
|
+
export function isDevBuild() {
|
|
20
|
+
try {
|
|
21
|
+
return !__dirname.includes('node_modules');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function getUserAgent() {
|
|
28
|
+
try {
|
|
29
|
+
const base = `GuildCLI/${getCliVersion()}`;
|
|
30
|
+
return isDevBuild() ? `${base}/dev` : base;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return 'GuildCLI';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
3
36
|
/**
|
|
4
37
|
* IAP configuration for shared.guildai.dev
|
|
5
38
|
*/
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Spinner } from './progress.js';
|
|
2
|
-
import type { Agent, AgentVersion, Credentials, CredentialsPolicy, Context, Integration, IntegrationVersion, JobStep, Pagination, Session, Workspace, WorkspaceAgent, Trigger } from './api-types.js';
|
|
2
|
+
import type { Agent, AgentVersion, Credentials, CredentialsPolicy, Context, Integration, IntegrationVersion, JobStep, Pagination, Session, Task, Workspace, WorkspaceAgent, Trigger } from './api-types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Format an agent list as a human-readable table.
|
|
5
5
|
* Shared by agent list and agent search commands.
|
|
@@ -40,6 +40,11 @@ export declare function formatWorkspaceTable(workspaces: Workspace[], pagination
|
|
|
40
40
|
* Used by session list command.
|
|
41
41
|
*/
|
|
42
42
|
export declare function formatSessionTable(sessions: Session[], pagination: Pagination): void;
|
|
43
|
+
/**
|
|
44
|
+
* Format a task list as a human-readable table.
|
|
45
|
+
* Used by session tasks command.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatTaskTable(tasks: Task[], pagination: Pagination): void;
|
|
43
48
|
/**
|
|
44
49
|
* Format a trigger list as a human-readable table.
|
|
45
50
|
* Used by trigger list command.
|
package/dist/lib/output.js
CHANGED
|
@@ -359,6 +359,56 @@ export function formatSessionTable(sessions, pagination) {
|
|
|
359
359
|
console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} sessions`));
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Format a task list as a human-readable table.
|
|
364
|
+
* Used by session tasks command.
|
|
365
|
+
*/
|
|
366
|
+
export function formatTaskTable(tasks, pagination) {
|
|
367
|
+
if (tasks.length === 0) {
|
|
368
|
+
console.log(chalk.dim('No tasks found'));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const table = new Table({
|
|
372
|
+
columns: [
|
|
373
|
+
{ name: 'id', title: 'ID', alignment: 'left' },
|
|
374
|
+
{ name: 'name', title: 'NAME', alignment: 'left' },
|
|
375
|
+
{ name: 'status', title: 'STATUS', alignment: 'left' },
|
|
376
|
+
{ name: 'tokens', title: 'TOKENS', alignment: 'left' },
|
|
377
|
+
{ name: 'created', title: 'CREATED', alignment: 'left' },
|
|
378
|
+
],
|
|
379
|
+
});
|
|
380
|
+
tasks.forEach((task) => {
|
|
381
|
+
const name = task.entity_type === 'EntTaskAgent'
|
|
382
|
+
? task.agent?.full_name || task.agent?.name || '-'
|
|
383
|
+
: task.tool_name;
|
|
384
|
+
const statusColor = task.status === 'DONE'
|
|
385
|
+
? chalk.green
|
|
386
|
+
: task.status === 'ERROR'
|
|
387
|
+
? chalk.red
|
|
388
|
+
: task.status === 'RUNNING' ||
|
|
389
|
+
task.status === 'STARTED' ||
|
|
390
|
+
task.status === 'WAITING'
|
|
391
|
+
? chalk.yellow
|
|
392
|
+
: chalk.dim;
|
|
393
|
+
table.addRow({
|
|
394
|
+
id: truncate(task.id, 13),
|
|
395
|
+
name,
|
|
396
|
+
status: statusColor(task.status),
|
|
397
|
+
tokens: task.token_usage ? task.token_usage.total_tokens.toLocaleString() : '-',
|
|
398
|
+
created: task.created_at ? formatRelativeTime(task.created_at) : '',
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
table.printTable();
|
|
402
|
+
const showing = Math.min(pagination.limit, tasks.length);
|
|
403
|
+
if (pagination.has_more) {
|
|
404
|
+
const nextOffset = pagination.offset + pagination.limit;
|
|
405
|
+
console.log(`\nShowing ${showing} of ${pagination.total_count} tasks. ` +
|
|
406
|
+
chalk.dim(`Use --offset ${nextOffset} to see more.`));
|
|
407
|
+
}
|
|
408
|
+
else if (pagination.total_count > showing) {
|
|
409
|
+
console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} tasks`));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
362
412
|
/**
|
|
363
413
|
* Format a trigger list as a human-readable table.
|
|
364
414
|
* Used by trigger list command.
|
|
@@ -54,7 +54,7 @@ export declare function getAgentName(agent: AgentRef | null | undefined): string
|
|
|
54
54
|
* Check if a task's agent matches a target agent identifier.
|
|
55
55
|
*
|
|
56
56
|
* Target format: "@scope/owner~name" (e.g. "@guildai/guildai~agent-builder") or simple name ("assistant")
|
|
57
|
-
* AgentRef format: { name: "agent-builder", full_name: "guildai
|
|
57
|
+
* AgentRef format: { name: "agent-builder", full_name: "guildai~agent-builder", ... }
|
|
58
58
|
*/
|
|
59
59
|
export declare function matchesAgent(taskAgent: AgentRef | null | undefined, targetAgent: string): boolean;
|
|
60
60
|
/** Get display name for a task - agent name or tool name */
|
|
@@ -41,7 +41,7 @@ export function getAgentName(agent) {
|
|
|
41
41
|
* Check if a task's agent matches a target agent identifier.
|
|
42
42
|
*
|
|
43
43
|
* Target format: "@scope/owner~name" (e.g. "@guildai/guildai~agent-builder") or simple name ("assistant")
|
|
44
|
-
* AgentRef format: { name: "agent-builder", full_name: "guildai
|
|
44
|
+
* AgentRef format: { name: "agent-builder", full_name: "guildai~agent-builder", ... }
|
|
45
45
|
*/
|
|
46
46
|
export function matchesAgent(taskAgent, targetAgent) {
|
|
47
47
|
if (!taskAgent)
|
|
@@ -49,9 +49,11 @@ export function matchesAgent(taskAgent, targetAgent) {
|
|
|
49
49
|
// Direct name match for simple identifiers like "assistant"
|
|
50
50
|
if (taskAgent.name === targetAgent)
|
|
51
51
|
return true;
|
|
52
|
-
// Normalize
|
|
52
|
+
// Normalize target to match full_name format:
|
|
53
|
+
// 1. Strip npm scope prefix: "@scope/owner~name" -> "owner~name"
|
|
54
|
+
// 2. Convert slash separator to tilde: "owner/name" -> "owner~name"
|
|
53
55
|
if (taskAgent.full_name) {
|
|
54
|
-
const normalized = targetAgent.replace(/^@[^/]+\//, '').replace('
|
|
56
|
+
const normalized = targetAgent.replace(/^@[^/]+\//, '').replace('/', '~');
|
|
55
57
|
if (taskAgent.full_name === normalized)
|
|
56
58
|
return true;
|
|
57
59
|
}
|
|
@@ -16,4 +16,12 @@ export interface PollResult {
|
|
|
16
16
|
* 3. runtime_error from agent tasks — fail fast
|
|
17
17
|
*/
|
|
18
18
|
export declare function pollForResponse(client: GuildAPIClient, sessionId: string, afterEventId: string | undefined, maxWaitTime?: number): Promise<PollResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Poll for agent response while streaming matching events to stdout as JSONL.
|
|
21
|
+
*
|
|
22
|
+
* Same completion logic as pollForResponse(), but writes each event that
|
|
23
|
+
* passes the filter to stdout so callers get intermediate output (console.log,
|
|
24
|
+
* progress, etc.) in JSON/JSONL automation modes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function pollForResponseWithEvents(client: GuildAPIClient, sessionId: string, eventFilter: Set<string>, afterEventId: string | undefined, maxWaitTime?: number): Promise<PollResult>;
|
|
19
27
|
//# sourceMappingURL=session-polling.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright 2026 Guild.ai
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { debug, isDebugMode } from './errors.js';
|
|
4
|
+
import { shouldShowEvent } from './event-filter.js';
|
|
4
5
|
import { fetchEvents } from './session-events-fetch.js';
|
|
5
6
|
/**
|
|
6
7
|
* Poll for agent response using from_id cursor.
|
|
@@ -50,4 +51,52 @@ export async function pollForResponse(client, sessionId, afterEventId, maxWaitTi
|
|
|
50
51
|
}
|
|
51
52
|
return { response: null, lastEventId: fromId };
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Poll for agent response while streaming matching events to stdout as JSONL.
|
|
56
|
+
*
|
|
57
|
+
* Same completion logic as pollForResponse(), but writes each event that
|
|
58
|
+
* passes the filter to stdout so callers get intermediate output (console.log,
|
|
59
|
+
* progress, etc.) in JSON/JSONL automation modes.
|
|
60
|
+
*/
|
|
61
|
+
export async function pollForResponseWithEvents(client, sessionId, eventFilter, afterEventId, maxWaitTime = 60000) {
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
let fromId = afterEventId;
|
|
64
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
65
|
+
const events = await fetchEvents(client, sessionId, { fromId });
|
|
66
|
+
let lastAgentRuntimeDone = null;
|
|
67
|
+
for (const event of events) {
|
|
68
|
+
debug(`pollForResponseWithEvents event: ${event.type}`);
|
|
69
|
+
// Stream matching events to stdout as JSONL
|
|
70
|
+
if (shouldShowEvent(event.type, eventFilter)) {
|
|
71
|
+
process.stdout.write(JSON.stringify(event) + '\n');
|
|
72
|
+
}
|
|
73
|
+
if (event.type === 'agent_notification_message') {
|
|
74
|
+
return { response: event.content.data, lastEventId: event.id };
|
|
75
|
+
}
|
|
76
|
+
if (event.type === 'runtime_done' &&
|
|
77
|
+
event.content !== undefined &&
|
|
78
|
+
event.task &&
|
|
79
|
+
'agent' in event.task) {
|
|
80
|
+
lastAgentRuntimeDone = JSON.stringify(event.content);
|
|
81
|
+
}
|
|
82
|
+
if (event.type === 'runtime_error' && event.task && 'agent' in event.task) {
|
|
83
|
+
return {
|
|
84
|
+
response: JSON.stringify({ error: event.content }),
|
|
85
|
+
lastEventId: event.id,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (event.type === 'agent_console' && isDebugMode()) {
|
|
89
|
+
process.stderr.write(`[console.${event.level}] ${event.content}\n`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (events.length > 0) {
|
|
93
|
+
fromId = events[events.length - 1].id;
|
|
94
|
+
}
|
|
95
|
+
if (lastAgentRuntimeDone !== null) {
|
|
96
|
+
return { response: lastAgentRuntimeDone, lastEventId: fromId };
|
|
97
|
+
}
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
99
|
+
}
|
|
100
|
+
return { response: null, lastEventId: fromId };
|
|
101
|
+
}
|
|
53
102
|
//# sourceMappingURL=session-polling.js.map
|
package/dist/lib/spinners.js
CHANGED
|
@@ -751,7 +751,10 @@ export function getSpinnerThemes() {
|
|
|
751
751
|
* @deprecated Use createSpinner() instead
|
|
752
752
|
*/
|
|
753
753
|
export function getActiveTheme() {
|
|
754
|
-
|
|
754
|
+
const theme = getSpinnerThemes().get('classic');
|
|
755
|
+
if (!theme)
|
|
756
|
+
throw new Error('Missing classic spinner theme');
|
|
757
|
+
return theme;
|
|
755
758
|
}
|
|
756
759
|
/**
|
|
757
760
|
* @deprecated Use createSpinner() instead
|