@guildai/cli 0.9.0 → 0.9.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.d.ts +1 -0
- package/dist/commands/agent/chat.js +24 -1
- package/dist/commands/agent/init.js +1 -1
- package/dist/commands/agent/test.d.ts +1 -0
- package/dist/commands/agent/test.js +92 -34
- package/dist/commands/chat.d.ts +1 -0
- package/dist/commands/chat.js +178 -46
- package/dist/commands/integration/connect.js +1 -1
- package/dist/commands/integration/create.js +36 -36
- package/dist/commands/integration/operation/create.js +2 -1
- package/dist/commands/integration/operation/list.js +23 -15
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/session/events.js +16 -7
- package/dist/commands/session/send.js +1 -1
- package/dist/commands/workspace/agent/add.js +16 -3
- package/dist/commands/workspace/agent/list.js +14 -1
- package/dist/commands/workspace/agent/remove.js +14 -1
- package/dist/commands/workspace/clear.d.ts +3 -0
- package/dist/commands/workspace/clear.js +45 -0
- package/dist/commands/workspace/select.js +3 -1
- package/dist/index.js +53 -6
- package/dist/lib/api-types.d.ts +1 -0
- package/dist/lib/generated-types.d.ts +1 -1
- package/dist/lib/generated-types.js +1 -0
- package/dist/lib/guild-config.d.ts +13 -0
- package/dist/lib/guild-config.js +19 -0
- package/dist/lib/npmrc.js +6 -2
- package/dist/lib/session-events.d.ts +32 -16
- package/dist/lib/session-events.js +22 -0
- package/dist/lib/session-polling.d.ts +4 -3
- package/dist/lib/session-polling.js +75 -17
- package/dist/lib/session-resume.js +4 -1
- package/dist/lib/stdin.d.ts +4 -0
- package/dist/lib/stdin.js +23 -0
- package/dist/lib/validate-input-schema.d.ts +19 -0
- package/dist/lib/validate-input-schema.js +208 -0
- package/dist/lib/workspace-helpers.d.ts +20 -0
- package/dist/lib/workspace-helpers.js +49 -0
- package/dist/mcp/tools.js +8 -52
- package/docs/skills/agent-dev.md +191 -128
- package/package.json +2 -1
|
@@ -19,6 +19,7 @@ import * as readline from 'readline';
|
|
|
19
19
|
import { pollForResponse } from '../../lib/session-polling.js';
|
|
20
20
|
import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
|
|
21
21
|
import { fetchSession, fetchSessionEvents } from '../../lib/session-resume.js';
|
|
22
|
+
import { getWorkspaceId, getWorkspaceSourceLabel } from '../../lib/guild-config.js';
|
|
22
23
|
// ESM equivalent of __dirname
|
|
23
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
// Read version from package.json
|
|
@@ -103,9 +104,26 @@ export function createAgentChatCommand() {
|
|
|
103
104
|
const sessionPrompt = options.mode === 'json' && inputData
|
|
104
105
|
? JSON.stringify(inputData)
|
|
105
106
|
: initialPrompt;
|
|
106
|
-
|
|
107
|
+
// Pre-resolve workspace so we can show the source label in output.
|
|
108
|
+
// When --workspace is given explicitly, no source label is shown.
|
|
109
|
+
let resolvedWorkspaceId = options.workspace;
|
|
110
|
+
let workspaceSourceLabel;
|
|
111
|
+
if (!resolvedWorkspaceId) {
|
|
112
|
+
const wsResolved = await getWorkspaceId(resolvedPath);
|
|
113
|
+
resolvedWorkspaceId = wsResolved?.workspaceId;
|
|
114
|
+
workspaceSourceLabel = wsResolved
|
|
115
|
+
? getWorkspaceSourceLabel(wsResolved.source)
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
118
|
+
const session = await createSession(client, resolvedWorkspaceId, sessionPrompt, version.id);
|
|
107
119
|
if (!quiet) {
|
|
108
120
|
console.log(`✓ Agent: ${config?.name || agentId}`);
|
|
121
|
+
if (session.workspace_id) {
|
|
122
|
+
const workspaceDisplay = workspaceSourceLabel
|
|
123
|
+
? `${session.workspace_id} (from ${workspaceSourceLabel})`
|
|
124
|
+
: session.workspace_id;
|
|
125
|
+
console.log(`✓ Workspace: ${workspaceDisplay}`);
|
|
126
|
+
}
|
|
109
127
|
const sessionLink = session.session_url
|
|
110
128
|
? hyperlink(session.id, session.session_url)
|
|
111
129
|
: session.id;
|
|
@@ -252,4 +270,9 @@ export function createAgentChatCommand() {
|
|
|
252
270
|
});
|
|
253
271
|
return cmd;
|
|
254
272
|
}
|
|
273
|
+
// Thin wrapper for lazy-loading from index.ts (avoids importing React at startup)
|
|
274
|
+
export async function handleAgentChatAction(_promptArgs, _options) {
|
|
275
|
+
const cmd = createAgentChatCommand();
|
|
276
|
+
await cmd.parseAsync(process.argv.slice(3), { from: 'user' });
|
|
277
|
+
}
|
|
255
278
|
//# sourceMappingURL=chat.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Copyright 2026 Guild.ai
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import inquirer from 'inquirer';
|
|
5
4
|
import { GuildAPIClient } from '../../lib/api-client.js';
|
|
6
5
|
import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
|
|
7
6
|
import { createSteps, createSpinner, format } from '../../lib/progress.js';
|
|
@@ -61,6 +60,7 @@ async function promptForName(defaultName) {
|
|
|
61
60
|
return ask();
|
|
62
61
|
}
|
|
63
62
|
async function promptForTemplate() {
|
|
63
|
+
const { default: inquirer } = await import('inquirer');
|
|
64
64
|
const { template } = await inquirer.prompt([
|
|
65
65
|
{
|
|
66
66
|
type: 'list',
|
|
@@ -12,14 +12,14 @@ import { hyperlink } from '../../lib/colors.js';
|
|
|
12
12
|
import { GuildAPIClient } from '../../lib/api-client.js';
|
|
13
13
|
import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
|
|
14
14
|
import { format } from '../../lib/progress.js';
|
|
15
|
-
import * as readline from 'readline';
|
|
16
15
|
import { parseEventFilter } from '../../lib/event-filter.js';
|
|
17
16
|
import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
|
|
18
|
-
import { pollForResponseWithEvents } from '../../lib/session-polling.js';
|
|
19
|
-
import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
|
|
20
|
-
import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
|
|
17
|
+
import { pollForResponse, pollForResponseWithEvents, } from '../../lib/session-polling.js';
|
|
18
|
+
import { readStdinAsJSON, readStdinAsText, ensureInteractiveStdin, } from '../../lib/stdin.js';
|
|
19
|
+
import { loadLocalConfig, getWorkspaceId, getWorkspaceSourceLabel, } from '../../lib/guild-config.js';
|
|
21
20
|
import { GitError, formatGitError } from '../../lib/git.js';
|
|
22
21
|
import { readAgentFiles, buildEphemeralVersion, buildBundledVersion, BundleNotFoundError, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
|
|
22
|
+
import { validateInputSchema } from '../../lib/validate-input-schema.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';
|
|
@@ -36,13 +36,15 @@ export function createAgentTestCommand() {
|
|
|
36
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:
|
|
39
|
+
.option('--events <types>', 'Event types to stream (default: user). Shorthands: none, user, system, all, or comma-separated type names')
|
|
40
40
|
.option('--bundle <file>', 'Path to a pre-built gzip+base64 bundle file')
|
|
41
41
|
.option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
|
|
42
42
|
.action(async (options) => {
|
|
43
43
|
const cwd = process.cwd();
|
|
44
|
-
// Parse --events filter once, before any branching
|
|
45
|
-
const eventFilter =
|
|
44
|
+
// Parse --events filter once, before any branching
|
|
45
|
+
const eventFilter = options.events
|
|
46
|
+
? parseEventFilter(options.events)
|
|
47
|
+
: undefined;
|
|
46
48
|
try {
|
|
47
49
|
// Handle --resume: skip build, fetch existing session, hand off to ChatApp
|
|
48
50
|
if (options.resume) {
|
|
@@ -81,9 +83,10 @@ export function createAgentTestCommand() {
|
|
|
81
83
|
console.error(' guild agent clone <agent-id>');
|
|
82
84
|
process.exit(1);
|
|
83
85
|
}
|
|
84
|
-
// If using JSON input, read and validate it early (before auth/session creation)
|
|
85
|
-
// This provides better UX - fail fast on bad
|
|
86
|
+
// If using JSON/JSONL input, read and validate it early (before auth/session creation)
|
|
87
|
+
// This provides better UX - fail fast on bad input without needing auth
|
|
86
88
|
let inputData;
|
|
89
|
+
let jsonlInputs;
|
|
87
90
|
if (options.mode === 'json') {
|
|
88
91
|
try {
|
|
89
92
|
inputData = await readStdinAsJSON();
|
|
@@ -98,6 +101,57 @@ export function createAgentTestCommand() {
|
|
|
98
101
|
process.exit(1);
|
|
99
102
|
}
|
|
100
103
|
}
|
|
104
|
+
else if (options.mode === 'jsonl') {
|
|
105
|
+
try {
|
|
106
|
+
const stdinContent = await readStdinAsText();
|
|
107
|
+
jsonlInputs = [];
|
|
108
|
+
const lines = stdinContent.split('\n');
|
|
109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
110
|
+
const line = lines[i].trim();
|
|
111
|
+
if (!line)
|
|
112
|
+
continue;
|
|
113
|
+
try {
|
|
114
|
+
jsonlInputs.push(JSON.parse(line));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
console.error(`Error: Invalid JSON on line ${i + 1}`);
|
|
118
|
+
console.error('');
|
|
119
|
+
console.error('Each line must be valid JSON.');
|
|
120
|
+
console.error(' cat inputs.jsonl | guild agent test --mode jsonl');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (jsonlInputs.length === 0) {
|
|
125
|
+
console.error('Error: No JSON input provided');
|
|
126
|
+
console.error('');
|
|
127
|
+
console.error('Example usage:');
|
|
128
|
+
console.error(' cat inputs.jsonl | guild agent test --mode jsonl');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
const err = error;
|
|
134
|
+
console.error(`Error: ${err.message}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Validate input against agent's schema locally (before build)
|
|
139
|
+
// Skip when --agent-version is set since local source may differ from that version
|
|
140
|
+
const inputsToValidate = inputData !== undefined ? [inputData] : (jsonlInputs ?? []);
|
|
141
|
+
if (inputsToValidate.length > 0 && !options.agentVersion) {
|
|
142
|
+
const validationResult = await validateInputSchema(cwd, inputsToValidate);
|
|
143
|
+
if (!validationResult.valid) {
|
|
144
|
+
console.error('Input validation failed:');
|
|
145
|
+
for (const err of validationResult.errors) {
|
|
146
|
+
const pathStr = err.path?.length ? ` (at ${err.path.join('.')})` : '';
|
|
147
|
+
console.error(` - ${err.message}${pathStr}`);
|
|
148
|
+
}
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
if ('skipped' in validationResult && validationResult.skipped) {
|
|
152
|
+
format.warn(`Skipping local validation: ${validationResult.reason}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
101
155
|
// If using bundle, verify the file exists early (before auth/session creation)
|
|
102
156
|
// so we fail fast without needing auth if the path is wrong.
|
|
103
157
|
if (options.bundle) {
|
|
@@ -115,9 +169,13 @@ export function createAgentTestCommand() {
|
|
|
115
169
|
}
|
|
116
170
|
// Determine workspace (priority: flag > local config > global config)
|
|
117
171
|
let workspaceId = options.workspace;
|
|
172
|
+
let workspaceSourceLabel;
|
|
118
173
|
if (!workspaceId) {
|
|
119
174
|
const resolved = await getWorkspaceId(cwd);
|
|
120
175
|
workspaceId = resolved?.workspaceId;
|
|
176
|
+
workspaceSourceLabel = resolved
|
|
177
|
+
? getWorkspaceSourceLabel(resolved.source)
|
|
178
|
+
: undefined;
|
|
121
179
|
if (!workspaceId) {
|
|
122
180
|
console.error('Error: No default workspace configured');
|
|
123
181
|
console.error('');
|
|
@@ -249,7 +307,10 @@ export function createAgentTestCommand() {
|
|
|
249
307
|
? 'ephemeral (cached, no changes)'
|
|
250
308
|
: 'ephemeral (working directory)';
|
|
251
309
|
console.log(`✓ Version: ${versionDisplay}`);
|
|
252
|
-
|
|
310
|
+
const workspaceDisplay = workspaceSourceLabel
|
|
311
|
+
? `${workspaceId} (from ${workspaceSourceLabel})`
|
|
312
|
+
: workspaceId;
|
|
313
|
+
console.log(`✓ Workspace: ${workspaceDisplay}`);
|
|
253
314
|
const sessionLink = session.session_url
|
|
254
315
|
? hyperlink(session.id, session.session_url)
|
|
255
316
|
: session.id;
|
|
@@ -270,7 +331,9 @@ export function createAgentTestCommand() {
|
|
|
270
331
|
});
|
|
271
332
|
// Poll for response (starting from beginning)
|
|
272
333
|
// 3 minutes - allow time for agents that use LLM calls for input parsing
|
|
273
|
-
const { response } =
|
|
334
|
+
const { response } = eventFilter
|
|
335
|
+
? await pollForResponseWithEvents(client, session.id, eventFilter, undefined, 180000)
|
|
336
|
+
: await pollForResponse(client, session.id, undefined, 180000);
|
|
274
337
|
if (!response) {
|
|
275
338
|
console.error('Error: No response received from agent within timeout');
|
|
276
339
|
console.error('');
|
|
@@ -321,41 +384,33 @@ export function createAgentTestCommand() {
|
|
|
321
384
|
process.exit(1);
|
|
322
385
|
}
|
|
323
386
|
}
|
|
324
|
-
else if (options.mode === 'jsonl') {
|
|
325
|
-
// JSONL input mode: line-by-line processing
|
|
326
|
-
const rl = readline.createInterface({
|
|
327
|
-
input: process.stdin,
|
|
328
|
-
output: process.stdout,
|
|
329
|
-
terminal: false,
|
|
330
|
-
});
|
|
331
|
-
let lineNumber = 0;
|
|
387
|
+
else if (options.mode === 'jsonl' && jsonlInputs) {
|
|
388
|
+
// JSONL input mode: line-by-line processing (inputs already parsed and validated)
|
|
332
389
|
let processedCount = 0;
|
|
333
390
|
let lastEventId;
|
|
334
|
-
for
|
|
335
|
-
|
|
336
|
-
if (!
|
|
337
|
-
|
|
391
|
+
for (let inputIndex = 0; inputIndex < jsonlInputs.length; inputIndex++) {
|
|
392
|
+
const jsonInput = jsonlInputs[inputIndex];
|
|
393
|
+
if (!quiet) {
|
|
394
|
+
console.error(`Processing input ${inputIndex + 1}/${jsonlInputs.length}...`);
|
|
395
|
+
}
|
|
338
396
|
try {
|
|
339
|
-
const jsonInput = JSON.parse(line);
|
|
340
|
-
if (!quiet) {
|
|
341
|
-
console.error(`Processing line ${lineNumber}...`);
|
|
342
|
-
}
|
|
343
397
|
// Send message
|
|
344
398
|
await client.post(`/sessions/${session.id}/events`, {
|
|
345
399
|
mode: 'json',
|
|
346
400
|
content: jsonInput,
|
|
347
401
|
});
|
|
348
402
|
// Wait for response (looking for events after last seen)
|
|
349
|
-
const result =
|
|
403
|
+
const result = eventFilter
|
|
404
|
+
? await pollForResponseWithEvents(client, session.id, eventFilter, lastEventId, 180000)
|
|
405
|
+
: await pollForResponse(client, session.id, lastEventId, 180000);
|
|
350
406
|
lastEventId = result.lastEventId;
|
|
351
407
|
if (!result.response) {
|
|
352
|
-
console.error(`Timeout: No response for
|
|
408
|
+
console.error(`Timeout: No response for input ${inputIndex + 1}`);
|
|
353
409
|
continue;
|
|
354
410
|
}
|
|
355
411
|
const response = result.response;
|
|
356
412
|
// Output response
|
|
357
413
|
if (quiet) {
|
|
358
|
-
// In quiet mode, output just the message content (or as JSON if --json is used)
|
|
359
414
|
console.log(response);
|
|
360
415
|
}
|
|
361
416
|
else {
|
|
@@ -365,10 +420,8 @@ export function createAgentTestCommand() {
|
|
|
365
420
|
processedCount++;
|
|
366
421
|
}
|
|
367
422
|
catch (error) {
|
|
368
|
-
const
|
|
369
|
-
console.error(`Error
|
|
370
|
-
console.error(`Failed to parse JSON: ${err.message}`);
|
|
371
|
-
console.error('Skipping line and continuing...');
|
|
423
|
+
const formattedErr = handleAxiosError(error);
|
|
424
|
+
console.error(`Error processing input ${inputIndex + 1}: ${formattedErr.details}`);
|
|
372
425
|
}
|
|
373
426
|
}
|
|
374
427
|
if (!quiet) {
|
|
@@ -425,4 +478,9 @@ export function createAgentTestCommand() {
|
|
|
425
478
|
});
|
|
426
479
|
return cmd;
|
|
427
480
|
}
|
|
481
|
+
// Thin wrapper for lazy-loading from index.ts (avoids importing React at startup)
|
|
482
|
+
export async function handleAgentTestAction(_options) {
|
|
483
|
+
const cmd = createAgentTestCommand();
|
|
484
|
+
await cmd.parseAsync(process.argv.slice(3), { from: 'user' });
|
|
485
|
+
}
|
|
428
486
|
//# sourceMappingURL=test.js.map
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -23,4 +23,5 @@ export declare function ChatApp({ initialPrompt, version, workspaceId, versionId
|
|
|
23
23
|
export declare function ensureAuthenticated(): Promise<string>;
|
|
24
24
|
export declare function createSession(client: GuildAPIClient, workspaceId: string | undefined, initialPrompt: string, versionId?: string, onProgress?: (status: string) => void): Promise<Session>;
|
|
25
25
|
export declare function createChatCommand(): Command;
|
|
26
|
+
export declare function handleChatAction(_promptArgs: string[], _options: Record<string, unknown>): Promise<void>;
|
|
26
27
|
//# sourceMappingURL=chat.d.ts.map
|
package/dist/commands/chat.js
CHANGED
|
@@ -13,12 +13,12 @@ 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, getAgentName, } from '../lib/session-events.js';
|
|
16
|
+
import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayName, getAgentName, getAgentNotificationText, isDoneResponseStreamEvent, isResponseStreamEvent, isRootTaskEvent, } 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';
|
|
20
20
|
import { AgentInstallPrompt } from '../components/AgentInstallPrompt.js';
|
|
21
|
-
import { getWorkspaceId } from '../lib/guild-config.js';
|
|
21
|
+
import { getWorkspaceId, getWorkspaceSourceLabel } from '../lib/guild-config.js';
|
|
22
22
|
import { ensureInteractiveStdin } from '../lib/stdin.js';
|
|
23
23
|
import { brand, BRAND_COLOR, code as codeColor, hyperlink } from '../lib/colors.js';
|
|
24
24
|
import { SplashAnimation } from '../components/SplashAnimation.js';
|
|
@@ -48,6 +48,48 @@ function fixListItemMarkdown(text) {
|
|
|
48
48
|
text = text.replace(/(?<![\\w])_([^_]+)_(?![\\w])/g, (_, content) => chalk.italic(content));
|
|
49
49
|
return text;
|
|
50
50
|
}
|
|
51
|
+
function extractMessageText(text) {
|
|
52
|
+
const trimmed = text.trim();
|
|
53
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
|
|
54
|
+
return text;
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(trimmed);
|
|
57
|
+
if (typeof parsed === 'object' && parsed !== null && 'message' in parsed) {
|
|
58
|
+
return typeof parsed.message === 'string' ? parsed.message : text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// If parsing fails, use the content as-is.
|
|
63
|
+
}
|
|
64
|
+
return text;
|
|
65
|
+
}
|
|
66
|
+
function extractRuntimeDoneText(content) {
|
|
67
|
+
if (content === null || content === undefined)
|
|
68
|
+
return null;
|
|
69
|
+
if (typeof content === 'string')
|
|
70
|
+
return content.trim() ? content : null;
|
|
71
|
+
if (typeof content !== 'object' || Array.isArray(content)) {
|
|
72
|
+
return JSON.stringify(content);
|
|
73
|
+
}
|
|
74
|
+
const record = content;
|
|
75
|
+
if (Object.keys(record).length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
if (record.type === 'text') {
|
|
78
|
+
if (typeof record.text === 'string')
|
|
79
|
+
return record.text;
|
|
80
|
+
if (typeof record.data === 'string')
|
|
81
|
+
return record.data;
|
|
82
|
+
}
|
|
83
|
+
if (typeof record.message === 'string')
|
|
84
|
+
return record.message;
|
|
85
|
+
// runtime_done content is agent output, not notification content. Preserve
|
|
86
|
+
// structured outputs rather than trying to render notification-only shapes.
|
|
87
|
+
return JSON.stringify(content);
|
|
88
|
+
}
|
|
89
|
+
function renderAssistantMessage(text, taskName) {
|
|
90
|
+
const rendered = fixListItemMarkdown(marked.parse(text));
|
|
91
|
+
return `${chalk.green('●')} ${chalk.bold(taskName)}\n${rendered.trim()}`;
|
|
92
|
+
}
|
|
51
93
|
/**
|
|
52
94
|
* Output the result of a --once mode session.
|
|
53
95
|
* Handles both JSON and human-readable output formats.
|
|
@@ -57,21 +99,29 @@ async function outputOnceResult(sessionId, events, mode) {
|
|
|
57
99
|
console.log(JSON.stringify({ session_id: sessionId, events }, null, 2));
|
|
58
100
|
}
|
|
59
101
|
else {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
const finalAgentMessages = events.filter((e) => e.type === 'agent_notification_message' &&
|
|
103
|
+
isRootTaskEvent(e) &&
|
|
104
|
+
!isResponseStreamEvent(e));
|
|
105
|
+
const streamFallbacks = events.filter((e) => e.type === 'agent_notification_message' &&
|
|
106
|
+
isRootTaskEvent(e) &&
|
|
107
|
+
isDoneResponseStreamEvent(e));
|
|
108
|
+
if (finalAgentMessages.length > 0) {
|
|
109
|
+
const messageContent = extractMessageText(getAgentNotificationText(finalAgentMessages[finalAgentMessages.length - 1]));
|
|
110
|
+
const rendered = fixListItemMarkdown(await marked(messageContent));
|
|
111
|
+
console.log(rendered.trim());
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const runtimeDoneEvents = events.filter((e) => e.type === 'runtime_done' && isRootTaskEvent(e) && e.content);
|
|
115
|
+
const runtimeDoneWithContent = runtimeDoneEvents[runtimeDoneEvents.length - 1];
|
|
116
|
+
if (runtimeDoneWithContent?.type === 'runtime_done') {
|
|
117
|
+
const runtimeDoneText = extractRuntimeDoneText(runtimeDoneWithContent.content);
|
|
118
|
+
if (runtimeDoneText) {
|
|
119
|
+
console.log(runtimeDoneText);
|
|
120
|
+
return;
|
|
74
121
|
}
|
|
122
|
+
}
|
|
123
|
+
if (streamFallbacks.length > 0) {
|
|
124
|
+
const messageContent = extractMessageText(getAgentNotificationText(streamFallbacks[streamFallbacks.length - 1]));
|
|
75
125
|
const rendered = fixListItemMarkdown(await marked(messageContent));
|
|
76
126
|
console.log(rendered.trim());
|
|
77
127
|
}
|
|
@@ -427,6 +477,83 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
427
477
|
const isPolling = useRef(false);
|
|
428
478
|
const receivedResponseSinceLastInput = useRef(false);
|
|
429
479
|
const firstMessageNotified = useRef(!!resumeEvents);
|
|
480
|
+
const responseStreamKeys = useRef(new Map());
|
|
481
|
+
const responseStreamTimestamps = useRef(new Map());
|
|
482
|
+
const responseStreamKeysByTask = useRef(new Map());
|
|
483
|
+
const clearResponseStreamsForTask = (taskId) => {
|
|
484
|
+
if (!taskId)
|
|
485
|
+
return;
|
|
486
|
+
const keys = responseStreamKeysByTask.current.get(taskId);
|
|
487
|
+
if (!keys?.size)
|
|
488
|
+
return;
|
|
489
|
+
for (const [streamId, key] of responseStreamKeys.current.entries()) {
|
|
490
|
+
if (keys.has(key)) {
|
|
491
|
+
responseStreamKeys.current.delete(streamId);
|
|
492
|
+
responseStreamTimestamps.current.delete(streamId);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
responseStreamKeysByTask.current.delete(taskId);
|
|
496
|
+
setMessages((prev) => prev.filter((message) => !keys.has(message.key)));
|
|
497
|
+
};
|
|
498
|
+
const upsertResponseStreamMessage = (event) => {
|
|
499
|
+
if (!isResponseStreamEvent(event))
|
|
500
|
+
return;
|
|
501
|
+
if (!isRootTaskEvent(event))
|
|
502
|
+
return;
|
|
503
|
+
const streamId = event.content.stream_id;
|
|
504
|
+
const taskId = event.task?.id;
|
|
505
|
+
const existingKey = responseStreamKeys.current.get(streamId);
|
|
506
|
+
if (event.content.status === 'aborted') {
|
|
507
|
+
if (existingKey) {
|
|
508
|
+
responseStreamKeys.current.delete(streamId);
|
|
509
|
+
responseStreamTimestamps.current.delete(streamId);
|
|
510
|
+
if (taskId) {
|
|
511
|
+
const keys = responseStreamKeysByTask.current.get(taskId);
|
|
512
|
+
keys?.delete(existingKey);
|
|
513
|
+
if (keys?.size === 0)
|
|
514
|
+
responseStreamKeysByTask.current.delete(taskId);
|
|
515
|
+
}
|
|
516
|
+
setMessages((prev) => prev.filter((message) => message.key !== existingKey));
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const text = event.content.text;
|
|
521
|
+
if (!text.trim())
|
|
522
|
+
return;
|
|
523
|
+
const key = existingKey ?? `response-stream-${streamId}`;
|
|
524
|
+
responseStreamKeys.current.set(streamId, key);
|
|
525
|
+
if (taskId) {
|
|
526
|
+
const keys = responseStreamKeysByTask.current.get(taskId) ?? new Set();
|
|
527
|
+
keys.add(key);
|
|
528
|
+
responseStreamKeysByTask.current.set(taskId, keys);
|
|
529
|
+
}
|
|
530
|
+
const taskName = agentName || 'assistant';
|
|
531
|
+
const messageContent = renderAssistantMessage(text, taskName);
|
|
532
|
+
const timestamp = responseStreamTimestamps.current.get(streamId) ?? new Date().toLocaleTimeString();
|
|
533
|
+
responseStreamTimestamps.current.set(streamId, timestamp);
|
|
534
|
+
setMessages((prev) => {
|
|
535
|
+
const index = prev.findIndex((message) => message.key === key);
|
|
536
|
+
const message = {
|
|
537
|
+
key,
|
|
538
|
+
content: messageContent,
|
|
539
|
+
type: 'assistant',
|
|
540
|
+
timestamp,
|
|
541
|
+
};
|
|
542
|
+
if (index === -1)
|
|
543
|
+
return [...prev, message];
|
|
544
|
+
const next = [...prev];
|
|
545
|
+
next[index] = message;
|
|
546
|
+
return next;
|
|
547
|
+
});
|
|
548
|
+
if (!firstMessageNotified.current && onFirstMessage) {
|
|
549
|
+
firstMessageNotified.current = true;
|
|
550
|
+
onFirstMessage();
|
|
551
|
+
}
|
|
552
|
+
if (event.content.status === 'done') {
|
|
553
|
+
receivedResponseSinceLastInput.current = true;
|
|
554
|
+
setCurrentOperation('');
|
|
555
|
+
}
|
|
556
|
+
};
|
|
430
557
|
// Mark initial prompt as sent (skip for resume — we already have the events)
|
|
431
558
|
useEffect(() => {
|
|
432
559
|
if (!resumeEvents) {
|
|
@@ -526,6 +653,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
526
653
|
}
|
|
527
654
|
// Process events that affect the chat UI (task state comes from tasks poll)
|
|
528
655
|
if (event.type === 'runtime_error') {
|
|
656
|
+
clearResponseStreamsForTask(taskInfo?.id);
|
|
529
657
|
// Always clear the spinner on runtime errors so the UI doesn't get stuck
|
|
530
658
|
setCurrentOperation('');
|
|
531
659
|
// Show runtime errors in the chat (gated on --events filter)
|
|
@@ -639,32 +767,15 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
639
767
|
}
|
|
640
768
|
}
|
|
641
769
|
else if (event.type === 'agent_notification_message') {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const
|
|
647
|
-
if (
|
|
648
|
-
|
|
649
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
650
|
-
try {
|
|
651
|
-
const parsed = JSON.parse(trimmed);
|
|
652
|
-
// If JSON has a "message" field, extract it (some agents wrap responses this way)
|
|
653
|
-
if (typeof parsed === 'object' &&
|
|
654
|
-
parsed !== null &&
|
|
655
|
-
'message' in parsed) {
|
|
656
|
-
text = parsed.message;
|
|
657
|
-
}
|
|
658
|
-
// Otherwise use full JSON content as-is (legitimate JSON responses)
|
|
659
|
-
}
|
|
660
|
-
catch {
|
|
661
|
-
// If parsing fails, use the content as-is
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
const rendered = fixListItemMarkdown(marked.parse(text));
|
|
665
|
-
// Add attribution with task name
|
|
770
|
+
if (isResponseStreamEvent(event)) {
|
|
771
|
+
upsertResponseStreamMessage(event);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const text = extractMessageText(getAgentNotificationText(event));
|
|
775
|
+
if (text.trim()) {
|
|
776
|
+
clearResponseStreamsForTask(taskInfo?.id);
|
|
666
777
|
const taskName = agentName || 'assistant';
|
|
667
|
-
const messageContent =
|
|
778
|
+
const messageContent = renderAssistantMessage(text, taskName);
|
|
668
779
|
setMessages((prev) => [
|
|
669
780
|
...prev,
|
|
670
781
|
{
|
|
@@ -684,6 +795,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
684
795
|
setCurrentOperation('');
|
|
685
796
|
}
|
|
686
797
|
else if (event.type === 'agent_notification_error') {
|
|
798
|
+
clearResponseStreamsForTask(taskInfo?.id);
|
|
687
799
|
// Show error in chat (task status is updated via tasks poll)
|
|
688
800
|
const errorText = typeof event.content === 'string'
|
|
689
801
|
? event.content
|
|
@@ -730,6 +842,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
730
842
|
event.content !== undefined &&
|
|
731
843
|
taskInfo &&
|
|
732
844
|
'agent' in taskInfo) {
|
|
845
|
+
clearResponseStreamsForTask(taskInfo.id);
|
|
733
846
|
// One-shot agents may complete with runtime_done without sending
|
|
734
847
|
// agent_notification_message. Display the output if we haven't
|
|
735
848
|
// already shown a response for this input cycle.
|
|
@@ -759,6 +872,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
759
872
|
}
|
|
760
873
|
}
|
|
761
874
|
else if (event.type === 'interrupted') {
|
|
875
|
+
clearResponseStreamsForTask(taskInfo?.id);
|
|
762
876
|
// Session was interrupted — interrupted sessions are terminal on the backend
|
|
763
877
|
setMessages((prev) => [
|
|
764
878
|
...prev,
|
|
@@ -987,6 +1101,10 @@ export async function createSession(client, workspaceId, initialPrompt, versionI
|
|
|
987
1101
|
const resolved = await getWorkspaceId();
|
|
988
1102
|
if (resolved) {
|
|
989
1103
|
workspaceId = resolved.workspaceId;
|
|
1104
|
+
const sourceLabel = getWorkspaceSourceLabel(resolved.source);
|
|
1105
|
+
if (sourceLabel) {
|
|
1106
|
+
progress(`Using workspace from ${sourceLabel}`);
|
|
1107
|
+
}
|
|
990
1108
|
}
|
|
991
1109
|
}
|
|
992
1110
|
if (!workspaceId) {
|
|
@@ -1045,7 +1163,9 @@ export function createChatCommand() {
|
|
|
1045
1163
|
try {
|
|
1046
1164
|
await ensureAuthenticated();
|
|
1047
1165
|
const client = new GuildAPIClient();
|
|
1048
|
-
const session = await createSession(client, options.workspace, initialPrompt, options.agent)
|
|
1166
|
+
const session = await createSession(client, options.workspace, initialPrompt, options.agent, (status) => {
|
|
1167
|
+
spinner.text = status;
|
|
1168
|
+
});
|
|
1049
1169
|
spinner.succeed('Connected');
|
|
1050
1170
|
if (session.session_url) {
|
|
1051
1171
|
const sessionLink = hyperlink(session.id, session.session_url);
|
|
@@ -1084,12 +1204,18 @@ export function createChatCommand() {
|
|
|
1084
1204
|
else {
|
|
1085
1205
|
inactivityCounter++;
|
|
1086
1206
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1207
|
+
// Check if we got a completion response from the root agent.
|
|
1208
|
+
// Stream done events are only a rendering fallback; wait for the
|
|
1209
|
+
// final root message or runtime completion so child drafts cannot
|
|
1210
|
+
// terminate --once early.
|
|
1211
|
+
const hasRootTaskDone = allEvents.some((e) => e.type === 'runtime_done' && isRootTaskEvent(e));
|
|
1212
|
+
const hasAgentMessage = allEvents.some((e) => e.type === 'agent_notification_message' &&
|
|
1213
|
+
isRootTaskEvent(e) &&
|
|
1214
|
+
!isResponseStreamEvent(e));
|
|
1215
|
+
const hasRootTaskError = allEvents.some((e) => e.type === 'runtime_error' && isRootTaskEvent(e));
|
|
1091
1216
|
// Check for a ui_prompt request... that ends the game.
|
|
1092
1217
|
const hasUIPromptMessage = allEvents.some((e) => e.type === 'agent_notification_message' &&
|
|
1218
|
+
!isResponseStreamEvent(e) &&
|
|
1093
1219
|
e.task?.tool_name === 'ui_prompt');
|
|
1094
1220
|
if (hasRootTaskError) {
|
|
1095
1221
|
debug('Found error event from root agent, exiting --once mode');
|
|
@@ -1177,4 +1303,10 @@ export function createChatCommand() {
|
|
|
1177
1303
|
});
|
|
1178
1304
|
return cmd;
|
|
1179
1305
|
}
|
|
1306
|
+
// Thin wrapper for lazy-loading from index.ts (avoids importing React at startup)
|
|
1307
|
+
export async function handleChatAction(_promptArgs, _options) {
|
|
1308
|
+
const cmd = createChatCommand();
|
|
1309
|
+
// Re-parse the original argv so the command's own action handler runs
|
|
1310
|
+
await cmd.parseAsync(process.argv.slice(2), { from: 'user' });
|
|
1311
|
+
}
|
|
1180
1312
|
//# sourceMappingURL=chat.js.map
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import inquirer from 'inquirer';
|
|
6
5
|
import { GuildAPIClient } from '../../lib/api-client.js';
|
|
7
6
|
import { getAuthToken } from '../../lib/auth.js';
|
|
8
7
|
import { handleAxiosError } from '../../lib/errors.js';
|
|
@@ -40,6 +39,7 @@ export function createIntegrationConnectCommand() {
|
|
|
40
39
|
}
|
|
41
40
|
let apiKey = options.token;
|
|
42
41
|
if (!apiKey) {
|
|
42
|
+
const { default: inquirer } = await import('inquirer');
|
|
43
43
|
const answer = await inquirer.prompt([
|
|
44
44
|
{
|
|
45
45
|
type: 'password',
|