@guildai/cli 0.6.2 → 0.7.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/README.md CHANGED
@@ -229,6 +229,8 @@ guild version # Show version info
229
229
  ```bash
230
230
  guild setup # Install skills + configure MCP server
231
231
  guild setup --claude-md # Also create a CLAUDE.md template
232
+ guild setup --codex # Install Codex skills + configure MCP server
233
+ guild setup --codex --agents-md # Also create an AGENTS.md template
232
234
  guild setup --no-mcp # Install skills only, skip MCP configuration
233
235
  guild mcp # Start MCP server (used by Claude Code, etc.)
234
236
  ```
@@ -243,7 +245,7 @@ The CLI includes an [MCP](https://modelcontextprotocol.io/) server that exposes
243
245
  guild setup
244
246
  ```
245
247
 
246
- This installs Claude Code skills and adds a `guild` entry to `.mcp.json` in your project. Claude Code (and other MCP-compatible tools) will automatically connect when they detect it.
248
+ This installs Claude Code skills and adds a `guild` entry to `.mcp.json` in your project. Use `guild setup --codex` to install Codex skills instead. Claude Code and other MCP-compatible tools will automatically connect when they detect `.mcp.json`.
247
249
 
248
250
  **What it provides:**
249
251
 
@@ -16,7 +16,6 @@ import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
16
16
  import { format } from '../../lib/progress.js';
17
17
  import { showBetaGuidance } from '../../lib/auth.js';
18
18
  import { pollUntilComplete } from '../../lib/polling.js';
19
- import { runGit } from '../../lib/git.js';
20
19
  import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
21
20
  import * as readline from 'readline';
22
21
  import { pollForResponse } from '../../lib/session-polling.js';
@@ -34,7 +33,7 @@ export function createAgentChatCommand() {
34
33
  .option('--path <dir>', 'Path to agent directory (defaults to current directory)')
35
34
  .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
36
35
  .option('--mode <format>', 'Input mode: json (one-shot) or jsonl (line-by-line)')
37
- .option('--ephemeral', 'Chat using unsaved changes (no guild agent save required)')
36
+ .option('--version <id>', 'Chat with a specific version (UUID or version number)')
38
37
  .option('--no-splash', 'Skip the splash screen animation')
39
38
  .option('--resume <session-id>', 'Resume an existing session')
40
39
  .option('--open', 'Open session in web dashboard')
@@ -67,10 +66,23 @@ export function createAgentChatCommand() {
67
66
  const quiet = isQuietMode();
68
67
  const resolvedPath = path.resolve(agentPath);
69
68
  // Resolve the version to chat with.
70
- // Default: use the latest saved version (requires clean working tree).
71
- // --ephemeral: read local files and create an ephemeral version.
69
+ // Default: create ephemeral version from working directory.
70
+ // --version: use a specific existing version.
72
71
  let version;
73
- if (options.ephemeral) {
72
+ if (options.version) {
73
+ // Explicit version: look it up by ID or version number
74
+ const existingVersions = (await client.get(`/agents/${agentId}/versions`));
75
+ const match = existingVersions.items.find((v) => v.id === options.version || v.version_number === options.version);
76
+ if (!match) {
77
+ console.error(`Error: Version not found: ${options.version}`);
78
+ console.error('');
79
+ console.error('List available versions:');
80
+ console.error(` guild agent versions ${agentId}`);
81
+ process.exit(1);
82
+ }
83
+ version = match;
84
+ }
85
+ else {
74
86
  if (!quiet) {
75
87
  console.error('Building agent...');
76
88
  }
@@ -81,43 +93,6 @@ export function createAgentChatCommand() {
81
93
  version_type: 'EPHEMERAL',
82
94
  }));
83
95
  }
84
- else {
85
- // Require clean working tree
86
- const { stdout: statusOutput } = await runGit(['status', '--porcelain'], {
87
- cwd: resolvedPath,
88
- });
89
- if (statusOutput.trim().length > 0) {
90
- console.error('Error: Working tree has unsaved changes');
91
- console.error('');
92
- console.error('Options:');
93
- console.error(' 1. Save your changes first:');
94
- console.error(' guild agent save --message "your changes"');
95
- console.error('');
96
- console.error(' 2. Chat with unsaved changes:');
97
- console.error(' guild agent chat --ephemeral');
98
- process.exit(1);
99
- }
100
- // Get commit SHA and look for an existing version
101
- const { stdout: shaOutput } = await runGit(['rev-parse', 'HEAD'], {
102
- cwd: resolvedPath,
103
- });
104
- const commitSha = shaOutput.trim();
105
- const existingVersions = (await client.get(`/agents/${agentId}/versions`));
106
- const existing = existingVersions.items.find((v) => v.sha === commitSha);
107
- if (existing) {
108
- version = existing;
109
- }
110
- else {
111
- if (!quiet) {
112
- console.error('Building agent...');
113
- }
114
- version = (await client.post(`/agents/${agentId}/versions`, {
115
- commit_sha: commitSha,
116
- summary: '[Chat] Development version',
117
- version_type: 'COMMITTED',
118
- }));
119
- }
120
- }
121
96
  // Wait for build to complete (skip if version already validated)
122
97
  if (version.validation_status === 'PENDING' ||
123
98
  version.validation_status === 'RUNNING') {
@@ -17,7 +17,7 @@ import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
17
17
  import { pollForResponse } from '../../lib/session-polling.js';
18
18
  import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
19
19
  import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
20
- import { runGit, GitError, formatGitError } from '../../lib/git.js';
20
+ import { GitError, formatGitError } from '../../lib/git.js';
21
21
  import { pollUntilComplete } from '../../lib/polling.js';
22
22
  import { readAgentFiles } from '../../lib/agent-helpers.js';
23
23
  import { fetchSession, fetchSessionEvents } from '../../lib/session-resume.js';
@@ -33,7 +33,7 @@ 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('--ephemeral', 'Test unsaved changes via ephemeral version (no guild agent save required)')
36
+ .option('--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
39
  .action(async (options) => {
@@ -75,12 +75,10 @@ export function createAgentTestCommand() {
75
75
  console.error(' guild agent clone <agent-id>');
76
76
  process.exit(1);
77
77
  }
78
- // For ephemeral mode, we skip git checks and read files directly
79
- // For committed mode, we require a clean working tree
80
- let commitSha = null;
78
+ // Read agent files from disk for ephemeral version creation.
79
+ // Skipped when --version is provided (testing a specific existing version).
81
80
  let agentFiles = null;
82
- if (options.ephemeral) {
83
- // Ephemeral mode: read files from disk
81
+ if (!options.version) {
84
82
  try {
85
83
  agentFiles = await readAgentFiles(cwd);
86
84
  }
@@ -96,28 +94,6 @@ export function createAgentTestCommand() {
96
94
  process.exit(1);
97
95
  }
98
96
  }
99
- else {
100
- // Committed mode: require clean working tree
101
- const { stdout: statusOutput } = await runGit(['status', '--porcelain'], {
102
- cwd,
103
- });
104
- if (statusOutput.trim().length > 0) {
105
- console.error('Error: Working tree has unsaved changes');
106
- console.error('');
107
- console.error('Options:');
108
- console.error(' 1. Save your changes first:');
109
- console.error(' guild agent save --message "your changes"');
110
- console.error('');
111
- console.error(' 2. Test unsaved changes:');
112
- console.error(' guild agent test --ephemeral');
113
- process.exit(1);
114
- }
115
- // Get commit SHA
116
- const { stdout: shaOutput } = await runGit(['rev-parse', 'HEAD'], {
117
- cwd,
118
- });
119
- commitSha = shaOutput.trim();
120
- }
121
97
  // If using JSON input, read and validate it early (before auth/session creation)
122
98
  // This provides better UX - fail fast on bad JSON without needing auth
123
99
  let inputData;
@@ -156,33 +132,31 @@ export function createAgentTestCommand() {
156
132
  }
157
133
  // Validate auth before any API calls or UI rendering
158
134
  await ensureAuthenticated();
159
- // Create version for testing
135
+ // Resolve version for testing
160
136
  const client = new GuildAPIClient();
161
137
  let version;
162
138
  try {
163
- if (options.ephemeral && agentFiles) {
164
- // Ephemeral version: send file contents directly
139
+ if (options.version) {
140
+ // Explicit version: look it up by ID or version number
141
+ const existingVersions = (await client.get(`/agents/${guildConfig.agent_id}/versions`));
142
+ const match = existingVersions.items.find((v) => v.id === options.version || v.version_number === options.version);
143
+ if (!match) {
144
+ console.error(`Error: Version not found: ${options.version}`);
145
+ console.error('');
146
+ console.error('List available versions:');
147
+ console.error(` guild agent versions ${guildConfig.agent_id}`);
148
+ process.exit(1);
149
+ }
150
+ version = match;
151
+ }
152
+ else {
153
+ // Default: create ephemeral version from working directory
165
154
  version = (await client.post(`/agents/${guildConfig.agent_id}/versions`, {
166
155
  files: agentFiles,
167
156
  summary: '[Test] Ephemeral development version',
168
157
  version_type: 'EPHEMERAL',
169
158
  }));
170
159
  }
171
- else {
172
- // Committed mode: check if a version for this commit already exists
173
- const existingVersions = (await client.get(`/agents/${guildConfig.agent_id}/versions`));
174
- const existing = existingVersions.items.find((v) => v.sha === commitSha);
175
- if (existing) {
176
- version = existing;
177
- }
178
- else {
179
- version = (await client.post(`/agents/${guildConfig.agent_id}/versions`, {
180
- commit_sha: commitSha,
181
- summary: '[Test] Development version',
182
- version_type: 'COMMITTED',
183
- }));
184
- }
185
- }
186
160
  }
187
161
  catch (error) {
188
162
  const formattedError = handleAxiosError(error);
@@ -280,9 +254,9 @@ export function createAgentTestCommand() {
280
254
  const quiet = isQuietMode();
281
255
  if (!quiet) {
282
256
  console.log(`✓ Agent: ${guildConfig.name} (${guildConfig.agent_id})`);
283
- const versionDisplay = options.ephemeral
284
- ? 'ephemeral (unsaved changes)'
285
- : commitSha?.substring(0, 12) || 'unknown';
257
+ const versionDisplay = options.version
258
+ ? options.version
259
+ : 'ephemeral (working directory)';
286
260
  console.log(`✓ Version: ${versionDisplay}`);
287
261
  console.log(`✓ Workspace: ${workspaceId}`);
288
262
  const sessionLink = session.session_url
@@ -452,7 +426,6 @@ export function createAgentTestCommand() {
452
426
  console.error('Failed to start test session. Troubleshooting:');
453
427
  console.error(' - Verify the agent exists: guild agent get');
454
428
  console.error(' - Check your workspace: guild workspace list');
455
- console.error(' - Test unsaved changes: guild agent test --ephemeral');
456
429
  if (formattedError.code) {
457
430
  console.error(` - Error code: ${formattedError.code}`);
458
431
  }
@@ -17,8 +17,9 @@ export interface ChatAppProps {
17
17
  resumeEvents?: SessionEvent[];
18
18
  resumeCommand?: string;
19
19
  openDashboard?: boolean;
20
+ eventFilter?: Set<string>;
20
21
  }
21
- export declare function ChatApp({ initialPrompt, version, workspaceId, versionId, agentName, showSplash, resumeSession, resumeEvents, resumeCommand, openDashboard, }: ChatAppProps): React.JSX.Element;
22
+ export declare function ChatApp({ initialPrompt, version, workspaceId, versionId, agentName, showSplash, resumeSession, resumeEvents, resumeCommand, openDashboard, eventFilter, }: ChatAppProps): React.JSX.Element;
22
23
  export declare function ensureAuthenticated(): Promise<string>;
23
24
  export declare function createSession(client: GuildAPIClient, workspaceId: string | undefined, initialPrompt: string, versionId?: string, onProgress?: (status: string) => void): Promise<Session>;
24
25
  export declare function createChatCommand(): Command;
@@ -15,6 +15,7 @@ import path from 'path';
15
15
  import { fileURLToPath } from 'url';
16
16
  import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayName, matchesAgent, getAgentName, } from '../lib/session-events.js';
17
17
  import { printResumeHint, fetchSession, fetchSessionEvents, eventsToDisplayMessages, } from '../lib/session-resume.js';
18
+ import { DEFAULT_EVENT_TYPES, parseEventFilter, shouldShowEvent, } from '../lib/event-filter.js';
18
19
  import { fetchEvents, fetchTasks } from '../lib/session-events-fetch.js';
19
20
  import { AgentInstallPrompt } from '../components/AgentInstallPrompt.js';
20
21
  import { getWorkspaceId } from '../lib/guild-config.js';
@@ -117,7 +118,7 @@ function InputWrapper({ isReady, isInterrupted, input, setInput, handleSubmit, t
117
118
  React.createElement(Text, { color: isReady ? BRAND_COLOR : 'gray' }, "> "),
118
119
  isReady && isActive ? (React.createElement(CustomInput, { value: input, onChange: setInput, onSubmit: handleSubmit, trackedTasksSize: trackedTasksSize, setShowTaskPanel: setShowTaskPanel, isActive: isActive })) : isReady ? (React.createElement(Text, null, input)) : (React.createElement(Text, null, chalk.dim('(connecting...)')))));
119
120
  }
120
- export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentName, showSplash = true, resumeSession, resumeEvents, resumeCommand, openDashboard, }) {
121
+ export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentName, showSplash = true, resumeSession, resumeEvents, resumeCommand, openDashboard, eventFilter, }) {
121
122
  const { exit } = useApp();
122
123
  const isResuming = !!resumeSession;
123
124
  const [phase, setPhase] = useState(isResuming || !showSplash ? 'chat' : 'splash');
@@ -241,13 +242,14 @@ export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentN
241
242
  }
242
243
  // If not connected yet, ignore escape (let connection complete)
243
244
  } })),
244
- phase === 'chat' && (React.createElement(ChatUIWithConnection, { initialPrompt: initialPrompt, version: version, versionId: versionId, agentName: agentName, client: connectedClient, session: connectedSession, onFirstMessage: () => setFirstMessageReceived(true), resumeEvents: resumeEvents, resumeCommand: resumeCommand })),
245
+ phase === 'chat' && (React.createElement(ChatUIWithConnection, { initialPrompt: initialPrompt, version: version, versionId: versionId, agentName: agentName, client: connectedClient, session: connectedSession, onFirstMessage: () => setFirstMessageReceived(true), resumeEvents: resumeEvents, resumeCommand: resumeCommand, eventFilter: eventFilter })),
245
246
  (phase === 'splash' || phase === 'finalizing') &&
246
247
  connectedSession &&
247
248
  connectedClient && (React.createElement(Box, { display: "none" },
248
- React.createElement(ChatUIWithConnection, { initialPrompt: initialPrompt, version: version, versionId: versionId, agentName: agentName, client: connectedClient, session: connectedSession, onFirstMessage: () => setFirstMessageReceived(true), isActive: false })))));
249
+ React.createElement(ChatUIWithConnection, { initialPrompt: initialPrompt, version: version, versionId: versionId, agentName: agentName, client: connectedClient, session: connectedSession, onFirstMessage: () => setFirstMessageReceived(true), isActive: false, eventFilter: eventFilter })))));
249
250
  }
250
- function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _versionId, agentName, client: preConnectedClient, session: preConnectedSession, onFirstMessage, isActive = true, resumeEvents, resumeCommand, }) {
251
+ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _versionId, agentName, client: preConnectedClient, session: preConnectedSession, onFirstMessage, isActive = true, resumeEvents, resumeCommand, eventFilter, }) {
252
+ const activeFilter = eventFilter ?? DEFAULT_EVENT_TYPES;
251
253
  // Note: We handle SIGINT directly via process.on, not using useApp().exit
252
254
  // Task panel state - managed at this level to handle keyboard input before TextInput
253
255
  // Default to showing task panel (Ctrl-T to toggle)
@@ -525,18 +527,107 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
525
527
  }
526
528
  // Process events that affect the chat UI (task state comes from tasks poll)
527
529
  if (event.type === 'runtime_error') {
528
- // Show runtime errors in the chat
529
- const errorText = typeof event.content === 'string' ? event.content : 'Unknown error';
530
- const taskName = agentName || 'assistant';
531
- setMessages((prev) => [
532
- ...prev,
533
- {
534
- key: `error-${Date.now()}`,
535
- content: `${chalk.red('●')} ${chalk.bold(taskName)}\n${chalk.red(`Error: ${errorText}`)}`,
536
- type: 'assistant',
537
- },
538
- ]);
530
+ // Always clear the spinner on runtime errors so the UI doesn't get stuck
539
531
  setCurrentOperation('');
532
+ // Show runtime errors in the chat (gated on --events filter)
533
+ if (shouldShowEvent('runtime_error', activeFilter)) {
534
+ const errorText = typeof event.content === 'string' ? event.content : 'Unknown error';
535
+ const taskName = agentName || 'assistant';
536
+ setMessages((prev) => [
537
+ ...prev,
538
+ {
539
+ key: `error-${Date.now()}`,
540
+ content: `${chalk.red('●')} ${chalk.bold(taskName)}\n${chalk.red(`Error: ${errorText}`)}`,
541
+ type: 'assistant',
542
+ },
543
+ ]);
544
+ }
545
+ }
546
+ else if (event.type === 'runtime_start') {
547
+ if (shouldShowEvent('runtime_start', activeFilter)) {
548
+ setMessages((prev) => [
549
+ ...prev,
550
+ {
551
+ key: `runtime-start-${Date.now()}`,
552
+ content: chalk.dim('[runtime/start]'),
553
+ type: 'assistant',
554
+ },
555
+ ]);
556
+ }
557
+ }
558
+ else if (event.type === 'runtime_running') {
559
+ if (shouldShowEvent('runtime_running', activeFilter)) {
560
+ setMessages((prev) => [
561
+ ...prev,
562
+ {
563
+ key: `runtime-running-${Date.now()}`,
564
+ content: chalk.dim('[runtime/running]'),
565
+ type: 'assistant',
566
+ },
567
+ ]);
568
+ }
569
+ }
570
+ else if (event.type === 'runtime_waiting') {
571
+ if (shouldShowEvent('runtime_waiting', activeFilter)) {
572
+ setMessages((prev) => [
573
+ ...prev,
574
+ {
575
+ key: `runtime-waiting-${Date.now()}`,
576
+ content: chalk.dim('[runtime/waiting]'),
577
+ type: 'assistant',
578
+ },
579
+ ]);
580
+ }
581
+ }
582
+ else if (event.type === 'trigger_message') {
583
+ if (shouldShowEvent('trigger_message', activeFilter)) {
584
+ const triggerText = typeof event.content === 'object' ? event.content?.data || '' : '';
585
+ setMessages((prev) => [
586
+ ...prev,
587
+ {
588
+ key: `trigger-${Date.now()}`,
589
+ content: `${chalk.cyan('[trigger]')} ${triggerText}`,
590
+ type: 'assistant',
591
+ },
592
+ ]);
593
+ }
594
+ }
595
+ else if (event.type === 'system_error') {
596
+ if (shouldShowEvent('system_error', activeFilter)) {
597
+ const errText = typeof event.content === 'object' ? event.content?.data || '' : '';
598
+ setMessages((prev) => [
599
+ ...prev,
600
+ {
601
+ key: `system-error-${Date.now()}`,
602
+ content: `${chalk.red('[system_error]')} ${errText}`,
603
+ type: 'assistant',
604
+ },
605
+ ]);
606
+ }
607
+ }
608
+ else if (event.type === 'llm_start') {
609
+ if (shouldShowEvent('llm_start', activeFilter)) {
610
+ setMessages((prev) => [
611
+ ...prev,
612
+ {
613
+ key: `llm-start-${Date.now()}`,
614
+ content: chalk.dim(`[llm_start] provider:${event.provider}`),
615
+ type: 'assistant',
616
+ },
617
+ ]);
618
+ }
619
+ }
620
+ else if (event.type === 'llm_done') {
621
+ if (shouldShowEvent('llm_done', activeFilter)) {
622
+ setMessages((prev) => [
623
+ ...prev,
624
+ {
625
+ key: `llm-done-${Date.now()}`,
626
+ content: chalk.dim(`[llm_done] HTTP ${event.status_code}`),
627
+ type: 'assistant',
628
+ },
629
+ ]);
630
+ }
540
631
  }
541
632
  else if (event.type === 'agent_notification_progress') {
542
633
  // Update status line with progress text (task tracking is done by tasks poll)
@@ -609,48 +700,63 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
609
700
  ]);
610
701
  setCurrentOperation('');
611
702
  }
612
- else if (event.type === 'agent_console' && isDebugMode()) {
613
- // Show console logs when debug mode is enabled (matches www debug checkbox behavior)
614
- const content = typeof event.content === 'string' ? event.content : '';
615
- setMessages((prev) => [
616
- ...prev,
617
- {
618
- key: `console-${Date.now()}-${Math.random()}`,
619
- content: chalk.dim(`[console.${event.level}] ${content}`),
620
- type: 'assistant',
621
- },
622
- ]);
703
+ else if (event.type === 'agent_console') {
704
+ // Show console logs when enabled via --events or --debug
705
+ // --debug continues to show console logs for backwards compatibility
706
+ if (shouldShowEvent('agent_console', activeFilter) || isDebugMode()) {
707
+ const content = typeof event.content === 'string' ? event.content : '';
708
+ setMessages((prev) => [
709
+ ...prev,
710
+ {
711
+ key: `console-${Date.now()}-${Math.random()}`,
712
+ content: chalk.dim(`[console.${event.level}] ${content}`),
713
+ type: 'assistant',
714
+ },
715
+ ]);
716
+ }
623
717
  }
624
- else if (event.type === 'runtime_done' &&
625
- !receivedResponseSinceLastInput.current &&
626
- event.content !== undefined &&
627
- taskInfo &&
628
- 'agent' in taskInfo) {
629
- // One-shot agents may complete with runtime_done without sending
630
- // agent_notification_message. Display the output if we haven't
631
- // already shown a response for this input cycle.
632
- const contentStr = typeof event.content === 'string'
633
- ? event.content
634
- : JSON.stringify(event.content);
635
- if (contentStr && contentStr !== '{}' && contentStr !== 'null') {
636
- const rendered = fixListItemMarkdown(marked.parse(contentStr));
637
- const taskName = agentName || 'assistant';
638
- const messageContent = `${chalk.green('●')} ${chalk.bold(taskName)}\n${rendered.trim()}`;
718
+ else if (event.type === 'runtime_done') {
719
+ if (shouldShowEvent('runtime_done', activeFilter)) {
720
+ // Show runtime_done as a system event when enabled via --events
639
721
  setMessages((prev) => [
640
722
  ...prev,
641
723
  {
642
- key: `msg-${Date.now()}-${Math.random()}`,
643
- content: messageContent,
724
+ key: `runtime-done-${Date.now()}`,
725
+ content: chalk.dim('[runtime/done]'),
644
726
  type: 'assistant',
645
- timestamp: new Date().toLocaleTimeString(),
646
727
  },
647
728
  ]);
648
- if (!firstMessageNotified.current && onFirstMessage) {
649
- firstMessageNotified.current = true;
650
- onFirstMessage();
729
+ }
730
+ if (!receivedResponseSinceLastInput.current &&
731
+ event.content !== undefined &&
732
+ taskInfo &&
733
+ 'agent' in taskInfo) {
734
+ // One-shot agents may complete with runtime_done without sending
735
+ // agent_notification_message. Display the output if we haven't
736
+ // already shown a response for this input cycle.
737
+ const contentStr = typeof event.content === 'string'
738
+ ? event.content
739
+ : JSON.stringify(event.content);
740
+ if (contentStr && contentStr !== '{}' && contentStr !== 'null') {
741
+ const rendered = fixListItemMarkdown(marked.parse(contentStr));
742
+ const taskName = agentName || 'assistant';
743
+ const messageContent = `${chalk.green('●')} ${chalk.bold(taskName)}\n${rendered.trim()}`;
744
+ setMessages((prev) => [
745
+ ...prev,
746
+ {
747
+ key: `msg-${Date.now()}-${Math.random()}`,
748
+ content: messageContent,
749
+ type: 'assistant',
750
+ timestamp: new Date().toLocaleTimeString(),
751
+ },
752
+ ]);
753
+ if (!firstMessageNotified.current && onFirstMessage) {
754
+ firstMessageNotified.current = true;
755
+ onFirstMessage();
756
+ }
757
+ receivedResponseSinceLastInput.current = true;
758
+ setCurrentOperation('');
651
759
  }
652
- receivedResponseSinceLastInput.current = true;
653
- setCurrentOperation('');
654
760
  }
655
761
  }
656
762
  else if (event.type === 'interrupted') {
@@ -887,29 +993,7 @@ export async function createSession(client, workspaceId, initialPrompt, versionI
887
993
  }
888
994
  }
889
995
  if (!workspaceId) {
890
- // Fall back to fetching workspaces from API
891
- progress('Fetching workspaces');
892
- const response = await client.get('/me/workspaces');
893
- if (!response) {
894
- throw new Error('Failed to fetch workspaces');
895
- }
896
- // Handle both array and paginated object responses
897
- const workspaces = (Array.isArray(response)
898
- ? response
899
- : response?.items || []);
900
- if (workspaces.length === 0) {
901
- // Get current user to use as workspace owner
902
- progress('Creating workspace');
903
- const me = (await client.get('/me'));
904
- const newWorkspace = (await client.post('/workspaces', {
905
- name: 'My Workspace',
906
- owner_id: me.id,
907
- }));
908
- workspaceId = newWorkspace.id;
909
- }
910
- else {
911
- workspaceId = workspaces[0].id;
912
- }
996
+ throw new Error('No workspace configured. Run: guild workspace select, or pass --workspace <id>');
913
997
  }
914
998
  progress('Creating session');
915
999
  const sessionData = {
@@ -950,9 +1034,13 @@ export function createChatCommand() {
950
1034
  .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
951
1035
  .option('--no-splash', 'Skip the splash screen animation')
952
1036
  .option('--resume <session-id>', 'Resume an existing session')
1037
+ .option('--events <types>', 'Event types to show (default: user). Shorthands: none, user, system, all, or comma-separated type names (e.g. agent_console,llm_start)')
953
1038
  .addHelpText('after', '\nTo chat with a local agent under development: guild agent chat')
954
1039
  .action(async (promptArgs, options) => {
955
1040
  const initialPrompt = promptArgs.length > 0 ? promptArgs.join(' ') : 'Hello';
1041
+ const eventFilter = options.events
1042
+ ? parseEventFilter(options.events)
1043
+ : DEFAULT_EVENT_TYPES;
956
1044
  if (options.once) {
957
1045
  // --once mode: use old spinner-based approach
958
1046
  const spinner = createSpinner('Connecting to Guild servers...');
@@ -1075,7 +1163,7 @@ export function createChatCommand() {
1075
1163
  if (shouldShowSplash) {
1076
1164
  suppressScrollbackClear();
1077
1165
  }
1078
- const { waitUntilExit } = render(React.createElement(ChatApp, { initialPrompt: initialPrompt, version: packageJson.version, workspaceId: options.workspace, versionId: options.agent, showSplash: shouldShowSplash, resumeSession: resumeSession, resumeEvents: resumeSessionEvents, resumeCommand: resumeCommand }), {
1166
+ const { waitUntilExit } = render(React.createElement(ChatApp, { initialPrompt: initialPrompt, version: packageJson.version, workspaceId: options.workspace, versionId: options.agent, showSplash: shouldShowSplash, resumeSession: resumeSession, resumeEvents: resumeSessionEvents, resumeCommand: resumeCommand, eventFilter: eventFilter }), {
1079
1167
  exitOnCtrlC: false, // We handle Ctrl-C in useInput (raw mode)
1080
1168
  });
1081
1169
  await waitUntilExit();
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createSessionInterruptCommand(): Command;
3
+ //# sourceMappingURL=interrupt.d.ts.map
@@ -0,0 +1,33 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Command } from 'commander';
4
+ import { GuildAPIClient } from '../../lib/api-client.js';
5
+ import { getAuthToken } from '../../lib/auth.js';
6
+ import { handleAxiosError } from '../../lib/errors.js';
7
+ import { createOutputWriter } from '../../lib/output.js';
8
+ export function createSessionInterruptCommand() {
9
+ const cmd = new Command('interrupt');
10
+ cmd
11
+ .description('Interrupt a running session')
12
+ .argument('<session-id>', 'Session ID')
13
+ .action(async (sessionId) => {
14
+ const output = createOutputWriter();
15
+ try {
16
+ const token = await getAuthToken();
17
+ if (!token) {
18
+ output.error('Not authenticated. Run: guild auth login');
19
+ process.exit(1);
20
+ }
21
+ const client = new GuildAPIClient();
22
+ const response = await client.post(`/sessions/${sessionId}/interrupt`, {});
23
+ output.data(response);
24
+ }
25
+ catch (error) {
26
+ const formattedError = handleAxiosError(error);
27
+ output.error(`Failed to interrupt session: ${formattedError.details}`);
28
+ process.exit(1);
29
+ }
30
+ });
31
+ return cmd;
32
+ }
33
+ //# sourceMappingURL=interrupt.js.map