@agent-relay/dashboard 2.0.82 → 2.0.84
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/out/404.html +1 -1
- package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
- package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
- package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
- package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
- package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
- package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
- package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
- package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
- package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
- package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
- package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
- package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
- package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
- package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
- package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
- package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
- package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
- package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
- package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
- package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
- package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
- package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
- package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
- package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
- package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
- package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
- package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
- package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
- package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
- package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
- package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
- package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
- package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
- package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
- package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
- package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
- package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
- package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
- package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
- package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
- package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
- package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
- package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
- package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
- package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
- package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
- package/out/about.html +2 -2
- package/out/about.txt +2 -2
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +2 -2
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +2 -2
- package/out/changelog.html +2 -2
- package/out/changelog.txt +2 -2
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +2 -2
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +2 -2
- package/out/dev/cli-tools.html +1 -0
- package/out/dev/cli-tools.txt +7 -0
- package/out/dev/log-viewer.html +23 -0
- package/out/dev/log-viewer.txt +7 -0
- package/out/docs.html +2 -2
- package/out/docs.txt +2 -2
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +2 -2
- package/out/privacy.html +2 -2
- package/out/privacy.txt +2 -2
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +2 -2
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +2 -2
- package/package.json +5 -1
- package/src/adapters/DashboardConfigProvider.tsx +56 -0
- package/src/adapters/cloudFetchAdapter.ts +278 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/types.ts +508 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
- package/src/app/app/onboarding/page.tsx +870 -170
- package/src/app/cloud/link/page.tsx +14 -6
- package/src/app/connect-repos/page.tsx +9 -3
- package/src/app/dev/cli-tools/page.tsx +130 -0
- package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
- package/src/app/dev/log-viewer/fixtures.ts +110 -0
- package/src/app/dev/log-viewer/page.tsx +288 -0
- package/src/app/history/page.tsx +28 -12
- package/src/app/page.tsx +1 -1
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
- package/src/components/AgentCard.tsx +4 -4
- package/src/components/AgentLogPreview.tsx +2 -38
- package/src/components/App.tsx +441 -2624
- package/src/components/CliToolHarness.test.tsx +83 -0
- package/src/components/CliToolHarness.tsx +292 -0
- package/src/components/CoordinatorPanel.tsx +13 -6
- package/src/components/LogViewer.tsx +2 -42
- package/src/components/ProviderAuthFlow.tsx +201 -81
- package/src/components/ProvisioningProgress.tsx +1 -1
- package/src/components/ReactionChips.tsx +2 -1
- package/src/components/SpawnModal.test.tsx +51 -18
- package/src/components/SpawnModal.tsx +175 -207
- package/src/components/TerminalProviderSetup.tsx +1 -1
- package/src/components/ThreadPanel.tsx +2 -0
- package/src/components/WorkspaceContext.tsx +7 -19
- package/src/components/XTermLogViewer.tsx +190 -27
- package/src/components/channels/ChannelMessageList.tsx +94 -4
- package/src/components/channels/ChannelViewV1.tsx +35 -11
- package/src/components/channels/api.ts +21 -20
- package/src/components/channels/types.ts +16 -0
- package/src/components/hooks/index.ts +0 -19
- package/src/components/hooks/useMessages.test.ts +80 -0
- package/src/components/hooks/useMessages.ts +13 -4
- package/src/components/hooks/useOrchestrator.ts +1 -1
- package/src/components/hooks/usePresence.ts +45 -6
- package/src/components/hooks/useThread.ts +83 -46
- package/src/components/hooks/useTrajectory.ts +62 -5
- package/src/components/hooks/useWebSocket.test.ts +358 -0
- package/src/components/hooks/useWebSocket.ts +243 -5
- package/src/components/index.ts +2 -14
- package/src/components/layout/Header.tsx +9 -15
- package/src/components/layout/Sidebar.tsx +1 -8
- package/src/components/settings/SettingsPage.tsx +108 -47
- package/src/components/settings/index.ts +0 -3
- package/src/landing/blogData.ts +1 -1
- package/src/lib/agent-merge.test.ts +2 -2
- package/src/lib/api.ts +8 -38
- package/src/lib/identity.test.ts +139 -0
- package/src/lib/identity.ts +48 -0
- package/src/lib/relaycastMessageAdapters.test.ts +182 -0
- package/src/lib/relaycastMessageAdapters.ts +105 -0
- package/src/lib/sanitize-logs.test.ts +227 -0
- package/src/lib/sanitize-logs.ts +202 -0
- package/src/providers/AgentProvider.tsx +799 -0
- package/src/providers/ChannelProvider.tsx +528 -0
- package/src/providers/CloudWorkspaceProvider.tsx +402 -0
- package/src/providers/MessageProvider.tsx +875 -0
- package/src/providers/RelayConfigProvider.tsx +94 -0
- package/src/providers/SendProvider.tsx +497 -0
- package/src/providers/SettingsProvider.tsx +247 -0
- package/src/providers/index.ts +26 -0
- package/src/types/index.ts +10 -10
- package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
- package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
- package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
- package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
- package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
- package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
- package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
- package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
- package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
- package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
- package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
- package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
- package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
- package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
- package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
- package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
- package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
- package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
- package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
- package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
- package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
- package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
- package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
- package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
- package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
- package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
- package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
- package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
- package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
- package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
- package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
- package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
- package/src/components/BillingResult.tsx +0 -447
- package/src/components/CloudSessionProvider.tsx +0 -130
- package/src/components/SessionExpiredModal.tsx +0 -128
- package/src/components/WorkspaceStatusIndicator.tsx +0 -396
- package/src/components/hooks/useSession.ts +0 -209
- package/src/components/hooks/useWorkspaceMembers.ts +0 -132
- package/src/components/hooks/useWorkspaceStatus.ts +0 -237
- package/src/components/settings/BillingSettingsPanel.tsx +0 -564
- package/src/components/settings/TeamSettingsPanel.tsx +0 -560
- package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
- package/src/lib/cloudApi.ts +0 -893
- /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for CliToolHarness
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Launching a real tool harness flow (spawn -> log viewer input path)
|
|
6
|
+
* - Releasing tool session
|
|
7
|
+
* - Error handling on launch failure
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// @vitest-environment jsdom
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
13
|
+
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
|
|
14
|
+
import { CliToolHarness, type CliToolHarnessConfig } from './CliToolHarness';
|
|
15
|
+
import { api } from '../lib/api';
|
|
16
|
+
|
|
17
|
+
vi.mock('./XTermLogViewer', () => ({
|
|
18
|
+
XTermLogViewer: ({ agentName }: { agentName: string }) => (
|
|
19
|
+
<div data-testid="xterm-log-viewer">{agentName}</div>
|
|
20
|
+
),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const TOOL: CliToolHarnessConfig = {
|
|
24
|
+
id: 'claude',
|
|
25
|
+
name: 'Claude',
|
|
26
|
+
command: 'claude',
|
|
27
|
+
description: 'Test harness entry',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('CliToolHarness', () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.restoreAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
cleanup();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('launches a CLI tool and renders a log viewer with the spawned agent', async () => {
|
|
40
|
+
const spawnSpy = vi
|
|
41
|
+
.spyOn(api, 'spawnAgent')
|
|
42
|
+
.mockResolvedValue({ success: true, name: 'claude-tool-1' });
|
|
43
|
+
const releaseSpy = vi
|
|
44
|
+
.spyOn(api, 'releaseAgent')
|
|
45
|
+
.mockResolvedValue({ success: true });
|
|
46
|
+
|
|
47
|
+
render(<CliToolHarness tool={TOOL} nameGenerator={() => 'claude-tool-1'} />);
|
|
48
|
+
|
|
49
|
+
fireEvent.click(screen.getByRole('button', { name: 'Launch Claude' }));
|
|
50
|
+
|
|
51
|
+
await waitFor(() => {
|
|
52
|
+
expect(spawnSpy).toHaveBeenCalledTimes(1);
|
|
53
|
+
expect(spawnSpy).toHaveBeenCalledWith({
|
|
54
|
+
name: 'claude-tool-1',
|
|
55
|
+
cli: 'claude',
|
|
56
|
+
task: undefined,
|
|
57
|
+
});
|
|
58
|
+
expect(screen.getByTestId('xterm-log-viewer')).toHaveTextContent('claude-tool-1');
|
|
59
|
+
expect(screen.getByRole('button', { name: 'Stop Claude' })).toBeTruthy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(screen.getByText('Claude')).toBeTruthy();
|
|
63
|
+
fireEvent.click(screen.getByRole('button', { name: 'Stop Claude' }));
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(releaseSpy).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(releaseSpy).toHaveBeenCalledWith('claude-tool-1');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('shows a friendly error message when launch fails', async () => {
|
|
72
|
+
vi
|
|
73
|
+
.spyOn(api, 'spawnAgent')
|
|
74
|
+
.mockResolvedValue({ success: false, name: 'ignored', error: 'Tool unavailable' });
|
|
75
|
+
|
|
76
|
+
render(<CliToolHarness tool={TOOL} />);
|
|
77
|
+
|
|
78
|
+
fireEvent.click(screen.getByRole('button', { name: 'Launch Claude' }));
|
|
79
|
+
|
|
80
|
+
expect(await screen.findByText('Tool unavailable')).toBeTruthy();
|
|
81
|
+
expect(screen.queryByTestId('xterm-log-viewer')).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CliToolHarness
|
|
3
|
+
*
|
|
4
|
+
* Isolated per-CLI-tool harness used for manual and integration-style testing.
|
|
5
|
+
* Spawns one real CLI tool instance and renders only the CLI metadata + log stream.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
9
|
+
import { api } from '../lib/api';
|
|
10
|
+
import { XTermLogViewer } from './XTermLogViewer';
|
|
11
|
+
|
|
12
|
+
export interface CliToolHarnessConfig {
|
|
13
|
+
/** Unique identifier used for CLI and agent name generation */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Display name in the UI */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Command string sent to /api/spawn as `cli` */
|
|
18
|
+
command: string;
|
|
19
|
+
/** Optional task string sent to /api/spawn */
|
|
20
|
+
task?: string;
|
|
21
|
+
/** Optional short description */
|
|
22
|
+
description?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CliToolHarnessProps {
|
|
26
|
+
/** Tool definition */
|
|
27
|
+
tool: CliToolHarnessConfig;
|
|
28
|
+
/** Optional class name for the wrapper card */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Optional deterministic agent-name generator for tests */
|
|
31
|
+
nameGenerator?: (tool: CliToolHarnessConfig) => string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type HarnessState = 'idle' | 'spawning' | 'running' | 'stopping' | 'error';
|
|
35
|
+
|
|
36
|
+
let harnessCounter = 0;
|
|
37
|
+
|
|
38
|
+
const defaultNameGenerator = (tool: CliToolHarnessConfig): string => {
|
|
39
|
+
harnessCounter += 1;
|
|
40
|
+
return `${tool.id}-${Date.now().toString(36)}-${harnessCounter.toString().padStart(3, '0')}`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function getStatusLabel(state: HarnessState): string {
|
|
44
|
+
switch (state) {
|
|
45
|
+
case 'spawning':
|
|
46
|
+
return 'starting';
|
|
47
|
+
case 'stopping':
|
|
48
|
+
return 'stopping';
|
|
49
|
+
case 'running':
|
|
50
|
+
return 'running';
|
|
51
|
+
case 'error':
|
|
52
|
+
return 'error';
|
|
53
|
+
case 'idle':
|
|
54
|
+
default:
|
|
55
|
+
return 'idle';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getButtonLabel(state: HarnessState, toolName: string): string {
|
|
60
|
+
if (state === 'running') {
|
|
61
|
+
return `Stop ${toolName}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (state === 'spawning' || state === 'stopping') {
|
|
65
|
+
return `${state === 'spawning' ? 'Starting' : 'Stopping'} ${toolName}...`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return `Launch ${toolName}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function CliToolHarness({
|
|
72
|
+
tool,
|
|
73
|
+
className = '',
|
|
74
|
+
nameGenerator = defaultNameGenerator,
|
|
75
|
+
}: CliToolHarnessProps) {
|
|
76
|
+
const [state, setState] = useState<HarnessState>('idle');
|
|
77
|
+
const [error, setError] = useState<string | null>(null);
|
|
78
|
+
const [agentName, setAgentName] = useState<string | null>(null);
|
|
79
|
+
const [chatInput, setChatInput] = useState('');
|
|
80
|
+
const [isSending, setIsSending] = useState(false);
|
|
81
|
+
const [sendError, setSendError] = useState<string | null>(null);
|
|
82
|
+
const activeAgentNameRef = useRef<string | null>(null);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
activeAgentNameRef.current = agentName;
|
|
86
|
+
}, [agentName]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
return () => {
|
|
90
|
+
const runningAgentName = activeAgentNameRef.current;
|
|
91
|
+
if (!runningAgentName) return;
|
|
92
|
+
void api.releaseAgent(runningAgentName);
|
|
93
|
+
};
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const isBusy = state === 'spawning' || state === 'stopping';
|
|
97
|
+
|
|
98
|
+
const handleToggle = useCallback(async () => {
|
|
99
|
+
if (isBusy) return;
|
|
100
|
+
|
|
101
|
+
if (state === 'running' && agentName) {
|
|
102
|
+
setState('stopping');
|
|
103
|
+
setError(null);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const result = await api.releaseAgent(agentName);
|
|
107
|
+
if (!result.success) {
|
|
108
|
+
setState('error');
|
|
109
|
+
setError(result.error || `Failed to stop ${tool.name}`);
|
|
110
|
+
setSendError(null);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setAgentName(null);
|
|
115
|
+
setChatInput('');
|
|
116
|
+
setSendError(null);
|
|
117
|
+
setState('idle');
|
|
118
|
+
} catch {
|
|
119
|
+
setState('error');
|
|
120
|
+
setError(`Failed to stop ${tool.name}`);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setState('spawning');
|
|
126
|
+
setError(null);
|
|
127
|
+
|
|
128
|
+
const name = nameGenerator(tool);
|
|
129
|
+
try {
|
|
130
|
+
const result = await api.spawnAgent({
|
|
131
|
+
name,
|
|
132
|
+
cli: tool.command,
|
|
133
|
+
task: tool.task,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
setState('error');
|
|
138
|
+
setError(result.error || `Failed to launch ${tool.name}`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setAgentName(result.name);
|
|
143
|
+
setSendError(null);
|
|
144
|
+
setState('running');
|
|
145
|
+
} catch {
|
|
146
|
+
setState('error');
|
|
147
|
+
setError(`Failed to launch ${tool.name}`);
|
|
148
|
+
}
|
|
149
|
+
}, [agentName, isBusy, nameGenerator, state, tool]);
|
|
150
|
+
|
|
151
|
+
const handleSendMessage = useCallback(
|
|
152
|
+
async (event: React.FormEvent<HTMLFormElement>) => {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
|
|
155
|
+
if (!agentName) return;
|
|
156
|
+
const message = chatInput.trim();
|
|
157
|
+
if (!message || isSending) return;
|
|
158
|
+
|
|
159
|
+
setIsSending(true);
|
|
160
|
+
setSendError(null);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const result = await api.sendMessage({
|
|
164
|
+
to: agentName,
|
|
165
|
+
message,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
setSendError(result.error || `Failed to send message to ${tool.name}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setChatInput('');
|
|
174
|
+
} catch {
|
|
175
|
+
setSendError(`Failed to send message to ${tool.name}`);
|
|
176
|
+
} finally {
|
|
177
|
+
setIsSending(false);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
[agentName, chatInput, isSending, tool.name],
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const statusLabel = getStatusLabel(state);
|
|
184
|
+
const buttonLabel = getButtonLabel(state, tool.name);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<section
|
|
188
|
+
className={`rounded-xl border border-[#2a2d35] bg-gradient-to-b from-[#0d0f14] to-[#0a0c10] p-4 min-w-0 ${className}`}
|
|
189
|
+
data-tool-id={tool.id}
|
|
190
|
+
>
|
|
191
|
+
<div className="mb-3 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4">
|
|
192
|
+
<div className="min-w-0">
|
|
193
|
+
<h2 className="text-sm font-semibold text-[#e6edf3]">{tool.name}</h2>
|
|
194
|
+
<p className="mt-1 text-xs text-[#8b949e]">Command: {tool.command}</p>
|
|
195
|
+
{tool.description && (
|
|
196
|
+
<p className="mt-2 text-xs text-[#8b949e]">{tool.description}</p>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
<div className="text-xs text-[#8b949e]">
|
|
200
|
+
<span
|
|
201
|
+
className={`rounded-full px-2 py-1 uppercase tracking-wider ${
|
|
202
|
+
state === 'running'
|
|
203
|
+
? 'bg-[#3fb950]/20 text-[#3fb950]'
|
|
204
|
+
: state === 'error'
|
|
205
|
+
? 'bg-[#f85149]/20 text-[#f85149]'
|
|
206
|
+
: 'bg-[#30363d]/50 text-[#8b949e]'
|
|
207
|
+
}`}
|
|
208
|
+
>
|
|
209
|
+
{statusLabel}
|
|
210
|
+
</span>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div className="mb-3 flex flex-wrap items-center gap-2">
|
|
215
|
+
<button
|
|
216
|
+
type="button"
|
|
217
|
+
onClick={handleToggle}
|
|
218
|
+
disabled={isBusy}
|
|
219
|
+
className={`rounded-lg px-3 py-2 text-xs font-semibold transition-all ${
|
|
220
|
+
state === 'running'
|
|
221
|
+
? 'bg-[#f85149]/20 text-[#fba8a8] hover:bg-[#f85149]/30'
|
|
222
|
+
: 'bg-accent-cyan/20 text-accent-cyan hover:bg-accent-cyan/30'
|
|
223
|
+
}`}
|
|
224
|
+
>
|
|
225
|
+
{buttonLabel}
|
|
226
|
+
</button>
|
|
227
|
+
|
|
228
|
+
{agentName && (
|
|
229
|
+
<span className="text-xs text-[#8b949e]">
|
|
230
|
+
Agent: <span className="text-[#c9d1d9]">{agentName}</span>
|
|
231
|
+
</span>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{error && (
|
|
236
|
+
<div
|
|
237
|
+
className="mb-3 rounded-md border border-[#f85149]/40 bg-[#3d1d20] px-3 py-2 text-xs text-[#f85149]"
|
|
238
|
+
role="status"
|
|
239
|
+
aria-live="polite"
|
|
240
|
+
>
|
|
241
|
+
{error}
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{state === 'running' && agentName ? (
|
|
246
|
+
<div className="mb-3">
|
|
247
|
+
<form className="flex gap-2" onSubmit={handleSendMessage}>
|
|
248
|
+
<input
|
|
249
|
+
type="text"
|
|
250
|
+
value={chatInput}
|
|
251
|
+
onChange={(event) => setChatInput(event.target.value)}
|
|
252
|
+
placeholder={`Message ${agentName}`}
|
|
253
|
+
className="min-w-0 flex-1 rounded-lg border border-[#30363d] bg-[#0d1117] px-2 py-2 text-xs text-[#c9d1d9] placeholder:text-[#6e7681] focus:border-[#58a6ff] focus:outline-none focus:ring-1 focus:ring-[#58a6ff]/40"
|
|
254
|
+
disabled={isSending}
|
|
255
|
+
autoComplete="off"
|
|
256
|
+
/>
|
|
257
|
+
<button
|
|
258
|
+
type="submit"
|
|
259
|
+
className="rounded-lg bg-[#58a6ff]/20 px-3 py-2 text-xs font-semibold text-[#79c0ff] transition-all hover:bg-[#58a6ff]/30 disabled:cursor-not-allowed disabled:opacity-50"
|
|
260
|
+
disabled={!chatInput.trim() || isSending}
|
|
261
|
+
>
|
|
262
|
+
{isSending ? 'Sending…' : 'Send'}
|
|
263
|
+
</button>
|
|
264
|
+
</form>
|
|
265
|
+
{sendError && (
|
|
266
|
+
<div className="mt-2 rounded-md border border-[#f85149]/40 bg-[#3d1d20] px-3 py-2 text-xs text-[#f85149]">
|
|
267
|
+
{sendError}
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
) : (
|
|
272
|
+
<div className="mb-3 rounded-lg border border-dashed border-[#30363d] px-3 py-2 text-xs text-[#8b949e]">
|
|
273
|
+
Start the session to send a message.
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{state === 'running' && agentName ? (
|
|
278
|
+
<XTermLogViewer
|
|
279
|
+
agentName={agentName}
|
|
280
|
+
maxHeight="320px"
|
|
281
|
+
showHeader={true}
|
|
282
|
+
key={`log-viewer-${tool.id}-${agentName}`}
|
|
283
|
+
suppressNoisyOutput={false}
|
|
284
|
+
/>
|
|
285
|
+
) : (
|
|
286
|
+
<div className="rounded-lg border border-dashed border-[#30363d] px-3 py-4 text-xs text-[#8b949e]">
|
|
287
|
+
No active session for this tool. Launch it to start a real log stream.
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
</section>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, { useState, useEffect } from 'react';
|
|
9
|
-
import {
|
|
9
|
+
import { useDashboardConfig } from '../adapters';
|
|
10
|
+
import { getCsrfToken } from '../lib/api';
|
|
10
11
|
import type { Project } from '../types';
|
|
11
12
|
|
|
12
13
|
export interface RepositoryInfo {
|
|
@@ -40,7 +41,6 @@ export interface CoordinatorPanelProps {
|
|
|
40
41
|
isOpen: boolean;
|
|
41
42
|
onClose: () => void;
|
|
42
43
|
projects: Project[];
|
|
43
|
-
isCloudMode?: boolean;
|
|
44
44
|
/** Whether an Architect agent is already running */
|
|
45
45
|
hasArchitect?: boolean;
|
|
46
46
|
/** Callback when Architect is spawned */
|
|
@@ -51,10 +51,13 @@ export function CoordinatorPanel({
|
|
|
51
51
|
isOpen,
|
|
52
52
|
onClose,
|
|
53
53
|
projects,
|
|
54
|
-
isCloudMode = false,
|
|
55
54
|
hasArchitect = false,
|
|
56
55
|
onArchitectSpawned,
|
|
57
56
|
}: CoordinatorPanelProps) {
|
|
57
|
+
const { features } = useDashboardConfig();
|
|
58
|
+
const hasWorkspaceFeature = features.workspaces;
|
|
59
|
+
const shouldUseWorkspaceCoordinator = hasWorkspaceFeature;
|
|
60
|
+
|
|
58
61
|
const [projectGroups, setProjectGroups] = useState<ProjectGroup[]>([]);
|
|
59
62
|
const [ungroupedRepos, setUngroupedRepos] = useState<RepositoryInfo[]>([]);
|
|
60
63
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -76,12 +79,16 @@ export function CoordinatorPanel({
|
|
|
76
79
|
|
|
77
80
|
// Fetch project groups on open
|
|
78
81
|
useEffect(() => {
|
|
79
|
-
if (isOpen &&
|
|
82
|
+
if (isOpen && shouldUseWorkspaceCoordinator) {
|
|
80
83
|
fetchProjectGroups();
|
|
81
84
|
}
|
|
82
|
-
}, [isOpen,
|
|
85
|
+
}, [isOpen, shouldUseWorkspaceCoordinator]);
|
|
83
86
|
|
|
84
87
|
const fetchProjectGroups = async () => {
|
|
88
|
+
if (!shouldUseWorkspaceCoordinator) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
setIsLoading(true);
|
|
86
93
|
setError(null);
|
|
87
94
|
try {
|
|
@@ -341,7 +348,7 @@ export function CoordinatorPanel({
|
|
|
341
348
|
};
|
|
342
349
|
|
|
343
350
|
// Local mode: show spawn architect UI
|
|
344
|
-
if (!
|
|
351
|
+
if (!shouldUseWorkspaceCoordinator) {
|
|
345
352
|
const isInBridgeMode = projects.length > 1;
|
|
346
353
|
|
|
347
354
|
return (
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
10
10
|
import { useAgentLogs, type LogLine } from './hooks/useAgentLogs';
|
|
11
11
|
import { getAgentColor } from '../lib/colors';
|
|
12
|
+
import { sanitizeLogContent, isSpinnerFragment } from '../lib/sanitize-logs';
|
|
12
13
|
import { XTermLogViewer } from './XTermLogViewer';
|
|
13
14
|
|
|
14
15
|
export type LogViewerMode = 'inline' | 'panel';
|
|
@@ -59,13 +60,8 @@ export function LogViewer({
|
|
|
59
60
|
return logs.filter((log) => {
|
|
60
61
|
const stripped = sanitizeLogContent(log.content).trim();
|
|
61
62
|
|
|
62
|
-
// Filter out empty lines
|
|
63
63
|
if (stripped.length === 0) return false;
|
|
64
|
-
|
|
65
|
-
// Filter out likely spinner fragments (single char or very short non-word content)
|
|
66
|
-
// Common spinner chars: ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ | - \ / * . etc.
|
|
67
|
-
const spinnerPattern = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷◐◓◑◒●○◉◎|\\\/\-*.\u2800-\u28FF]+$/;
|
|
68
|
-
if (stripped.length <= 2 && spinnerPattern.test(stripped)) return false;
|
|
64
|
+
if (isSpinnerFragment(stripped)) return false;
|
|
69
65
|
|
|
70
66
|
return true;
|
|
71
67
|
});
|
|
@@ -233,42 +229,6 @@ function ConnectionBadge({
|
|
|
233
229
|
);
|
|
234
230
|
}
|
|
235
231
|
|
|
236
|
-
/**
|
|
237
|
-
* Strip ANSI escape codes (including degraded sequences like "[38;5;216m")
|
|
238
|
-
* and control characters so logs render as clean text.
|
|
239
|
-
*/
|
|
240
|
-
function sanitizeLogContent(text: string): string {
|
|
241
|
-
if (!text) return '';
|
|
242
|
-
|
|
243
|
-
let result = text;
|
|
244
|
-
|
|
245
|
-
// Remove OSC sequences (like window title): \x1b]...(\x07|\x1b\\)
|
|
246
|
-
result = result.replace(/\x1b\].*?(?:\x07|\x1b\\)/gs, '');
|
|
247
|
-
|
|
248
|
-
// Remove DCS (Device Control String) sequences: \x1bP...\x1b\\
|
|
249
|
-
result = result.replace(/\x1bP.*?\x1b\\/gs, '');
|
|
250
|
-
|
|
251
|
-
// Remove standard ANSI escape sequences (CSI, SGR, etc.)
|
|
252
|
-
result = result.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '');
|
|
253
|
-
|
|
254
|
-
// Remove single-character escapes
|
|
255
|
-
result = result.replace(/\x1b[@-Z\\-_]/g, '');
|
|
256
|
-
|
|
257
|
-
// Remove orphaned CSI sequences that lost their escape byte
|
|
258
|
-
result = result.replace(/^\[\??\d+[hlKJHfABCDGPXsu]/gm, '');
|
|
259
|
-
|
|
260
|
-
// Remove literal SGR sequences that show up without ESC (e.g. "[38;5;216m")
|
|
261
|
-
result = result.replace(/\[\d+(?:;\d+)*m/g, '');
|
|
262
|
-
|
|
263
|
-
// Remove carriage returns/backspaces and other control chars (except newline/tab)
|
|
264
|
-
result = result.replace(/\r/g, '');
|
|
265
|
-
result = result.replace(/.\x08/g, '');
|
|
266
|
-
result = result.replace(/\x08+/g, '');
|
|
267
|
-
result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
|
|
268
|
-
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
232
|
// Icon components
|
|
273
233
|
function TerminalIcon({ size = 16 }: { size?: number }) {
|
|
274
234
|
return (
|