@townco/ui 0.1.83 → 0.1.96

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.
Files changed (63) hide show
  1. package/dist/core/hooks/use-chat-input.js +13 -6
  2. package/dist/core/hooks/use-chat-messages.d.ts +17 -0
  3. package/dist/core/hooks/use-chat-messages.js +294 -10
  4. package/dist/core/schemas/chat.d.ts +20 -0
  5. package/dist/core/schemas/chat.js +4 -0
  6. package/dist/core/schemas/index.d.ts +1 -0
  7. package/dist/core/schemas/index.js +1 -0
  8. package/dist/core/schemas/source.d.ts +22 -0
  9. package/dist/core/schemas/source.js +45 -0
  10. package/dist/core/store/chat-store.d.ts +4 -0
  11. package/dist/core/store/chat-store.js +54 -0
  12. package/dist/gui/components/Actions.d.ts +15 -0
  13. package/dist/gui/components/Actions.js +22 -0
  14. package/dist/gui/components/ChatInput.d.ts +9 -1
  15. package/dist/gui/components/ChatInput.js +24 -6
  16. package/dist/gui/components/ChatInputCommandMenu.d.ts +1 -0
  17. package/dist/gui/components/ChatInputCommandMenu.js +22 -5
  18. package/dist/gui/components/ChatInputParameters.d.ts +13 -0
  19. package/dist/gui/components/ChatInputParameters.js +67 -0
  20. package/dist/gui/components/ChatLayout.d.ts +2 -0
  21. package/dist/gui/components/ChatLayout.js +183 -61
  22. package/dist/gui/components/ChatPanelTabContent.d.ts +7 -0
  23. package/dist/gui/components/ChatPanelTabContent.js +17 -7
  24. package/dist/gui/components/ChatView.js +105 -15
  25. package/dist/gui/components/CitationChip.d.ts +15 -0
  26. package/dist/gui/components/CitationChip.js +72 -0
  27. package/dist/gui/components/EditableUserMessage.d.ts +18 -0
  28. package/dist/gui/components/EditableUserMessage.js +109 -0
  29. package/dist/gui/components/MessageActions.d.ts +16 -0
  30. package/dist/gui/components/MessageActions.js +97 -0
  31. package/dist/gui/components/MessageContent.js +22 -7
  32. package/dist/gui/components/Response.d.ts +3 -0
  33. package/dist/gui/components/Response.js +30 -3
  34. package/dist/gui/components/Sidebar.js +1 -1
  35. package/dist/gui/components/TodoSubline.js +1 -1
  36. package/dist/gui/components/WorkProgress.js +7 -0
  37. package/dist/gui/components/index.d.ts +6 -1
  38. package/dist/gui/components/index.js +6 -1
  39. package/dist/gui/hooks/index.d.ts +1 -0
  40. package/dist/gui/hooks/index.js +1 -0
  41. package/dist/gui/hooks/use-favicon.d.ts +6 -0
  42. package/dist/gui/hooks/use-favicon.js +47 -0
  43. package/dist/gui/hooks/use-scroll-to-bottom.d.ts +14 -0
  44. package/dist/gui/hooks/use-scroll-to-bottom.js +317 -1
  45. package/dist/gui/index.d.ts +1 -1
  46. package/dist/gui/index.js +1 -1
  47. package/dist/gui/lib/motion.js +6 -6
  48. package/dist/gui/lib/remark-citations.d.ts +28 -0
  49. package/dist/gui/lib/remark-citations.js +70 -0
  50. package/dist/sdk/client/acp-client.d.ts +38 -1
  51. package/dist/sdk/client/acp-client.js +67 -3
  52. package/dist/sdk/schemas/message.d.ts +40 -0
  53. package/dist/sdk/schemas/message.js +20 -0
  54. package/dist/sdk/transports/http.d.ts +24 -1
  55. package/dist/sdk/transports/http.js +189 -1
  56. package/dist/sdk/transports/stdio.d.ts +1 -0
  57. package/dist/sdk/transports/stdio.js +39 -0
  58. package/dist/sdk/transports/types.d.ts +46 -1
  59. package/dist/sdk/transports/websocket.d.ts +1 -0
  60. package/dist/sdk/transports/websocket.js +4 -0
  61. package/dist/tui/components/ChatView.js +3 -4
  62. package/package.json +5 -3
  63. package/src/styles/global.css +71 -0
@@ -21,6 +21,7 @@ export declare class StdioTransport implements Transport {
21
21
  connect(): Promise<void>;
22
22
  disconnect(): Promise<void>;
23
23
  send(message: Message): Promise<void>;
24
+ cancel(sessionId: string): Promise<void>;
24
25
  receive(): AsyncIterableIterator<MessageChunk>;
25
26
  isConnected(): boolean;
26
27
  onSessionUpdate(callback: (update: SessionUpdate) => void): () => void;
@@ -525,6 +525,45 @@ export class StdioTransport {
525
525
  throw err;
526
526
  }
527
527
  }
528
+ async cancel(sessionId) {
529
+ if (!this.connected || !this.connection) {
530
+ logger.warn("Cannot cancel: transport not connected");
531
+ return;
532
+ }
533
+ const targetSessionId = sessionId || this.currentSessionId;
534
+ if (!targetSessionId) {
535
+ logger.warn("Cannot cancel: no session ID");
536
+ return;
537
+ }
538
+ logger.info("Cancelling session", { sessionId: targetSessionId });
539
+ try {
540
+ // Send cancel request through the ACP connection
541
+ // The ACP SDK connection has a cancel method
542
+ await this.connection.cancel({ sessionId: targetSessionId });
543
+ logger.debug("Cancel request sent successfully");
544
+ }
545
+ catch (error) {
546
+ // Log but don't throw - cancellation should be best-effort
547
+ logger.error("Error sending cancel request", { error });
548
+ }
549
+ // Mark stream as complete to exit receive() loop
550
+ this.streamComplete = true;
551
+ // Resolve any pending chunk resolvers with a completion marker
552
+ const completionChunk = {
553
+ type: "content",
554
+ id: targetSessionId,
555
+ role: "assistant",
556
+ contentDelta: { type: "text", text: "" },
557
+ isComplete: true,
558
+ };
559
+ // Drain all pending resolvers
560
+ while (this.chunkResolvers.length > 0) {
561
+ const resolver = this.chunkResolvers.shift();
562
+ if (resolver) {
563
+ resolver(completionChunk);
564
+ }
565
+ }
566
+ }
528
567
  async *receive() {
529
568
  // Keep yielding chunks until stream is complete
530
569
  while (!this.streamComplete) {
@@ -44,6 +44,30 @@ export interface AgentUiConfig {
44
44
  /** Whether to hide the top bar (session switcher, debugger link, settings) */
45
45
  hideTopBar?: boolean;
46
46
  }
47
+ /**
48
+ * A single option within a prompt parameter
49
+ */
50
+ export interface PromptParameterOption {
51
+ /** Unique identifier for this option */
52
+ id: string;
53
+ /** Human-readable label shown in the UI */
54
+ label: string;
55
+ }
56
+ /**
57
+ * A configurable prompt parameter that users can select per-message
58
+ */
59
+ export interface PromptParameter {
60
+ /** Unique identifier for this parameter */
61
+ id: string;
62
+ /** Human-readable label shown in the UI dropdown */
63
+ label: string;
64
+ /** Optional description explaining what this parameter controls */
65
+ description?: string;
66
+ /** Available options for this parameter */
67
+ options: PromptParameterOption[];
68
+ /** The default option ID to use if none is selected */
69
+ defaultOptionId?: string;
70
+ }
47
71
  /**
48
72
  * Session summary for listing
49
73
  */
@@ -70,14 +94,33 @@ export interface Transport {
70
94
  * List available sessions (optional, not all transports support this)
71
95
  */
72
96
  listSessions?(): Promise<SessionSummary[]>;
97
+ /**
98
+ * Edit and resend a message from a specific point in the conversation.
99
+ * Truncates history to the specified message index and sends the new content.
100
+ * (optional, not all transports support this)
101
+ */
102
+ editAndResend?(sessionId: string, messageIndex: number, prompt: Array<{
103
+ type: string;
104
+ text?: string;
105
+ [key: string]: unknown;
106
+ }>): Promise<void>;
73
107
  /**
74
108
  * Close the transport connection
75
109
  */
76
110
  disconnect(): Promise<void>;
77
111
  /**
78
112
  * Send a message through the transport
113
+ * @param message - The message to send
114
+ * @param options - Optional parameters including promptParameters for per-message configuration
115
+ */
116
+ send(message: Message, options?: {
117
+ promptParameters?: Record<string, string>;
118
+ }): Promise<void>;
119
+ /**
120
+ * Cancel the current agent turn for a session
121
+ * Stops streaming, tool execution, and sub-agent processes
79
122
  */
80
- send(message: Message): Promise<void>;
123
+ cancel(sessionId: string): Promise<void>;
81
124
  /**
82
125
  * Receive messages from the transport
83
126
  * Returns an async iterator for streaming support
@@ -104,6 +147,7 @@ export interface Transport {
104
147
  * - subagents: List of subagents available via Task tool
105
148
  * - initialMessage: Configuration for agent's initial message on session start
106
149
  * - uiConfig: UI configuration for controlling interface appearance
150
+ * - promptParameters: Configurable parameters users can select per-message
107
151
  */
108
152
  getAgentInfo?(): {
109
153
  name?: string;
@@ -116,6 +160,7 @@ export interface Transport {
116
160
  tools?: AgentToolInfo[];
117
161
  mcps?: AgentMcpInfo[];
118
162
  subagents?: AgentSubagentInfo[];
163
+ promptParameters?: PromptParameter[];
119
164
  };
120
165
  }
121
166
  /**
@@ -14,6 +14,7 @@ export declare class WebSocketTransport implements Transport {
14
14
  disconnect(): Promise<void>;
15
15
  send(_message: Message): Promise<void>;
16
16
  receive(): AsyncIterableIterator<MessageChunk>;
17
+ cancel(_sessionId: string): Promise<void>;
17
18
  isConnected(): boolean;
18
19
  onSessionUpdate(callback: (update: SessionUpdate) => void): () => void;
19
20
  onError(callback: (error: Error) => void): () => void;
@@ -34,6 +34,10 @@ export class WebSocketTransport {
34
34
  // TODO: Implement WebSocket message receiving
35
35
  throw new Error("WebSocketTransport not yet implemented. Waiting for HTTP ACP server.");
36
36
  }
37
+ async cancel(_sessionId) {
38
+ // TODO: Implement WebSocket cancel
39
+ throw new Error("WebSocketTransport not yet implemented. Waiting for HTTP ACP server.");
40
+ }
37
41
  isConnected() {
38
42
  return this.connected;
39
43
  }
@@ -5,7 +5,6 @@ import { InputBox } from "./InputBox.js";
5
5
  import { MessageList } from "./MessageList.js";
6
6
  import { StatusBar } from "./StatusBar.js";
7
7
  export function ChatView({ client }) {
8
- const setIsStreaming = useChatStore((state) => state.setIsStreaming);
9
8
  const _streamingStartTime = useChatStore((state) => state.streamingStartTime);
10
9
  const _totalBilled = useChatStore((state) => state.totalBilled);
11
10
  const _currentContext = useChatStore((state) => state.currentContext);
@@ -13,7 +12,7 @@ export function ChatView({ client }) {
13
12
  const _tokenDisplayMode = useChatStore((state) => state.tokenDisplayMode);
14
13
  // Use headless hooks for business logic
15
14
  const { startSession } = useChatSession(client); // Subscribe to session changes
16
- const { messages, isStreaming } = useChatMessages(client, startSession);
15
+ const { messages, isStreaming, cancel } = useChatMessages(client, startSession);
17
16
  useToolCalls(client); // Still need to subscribe to tool call events
18
17
  const { value, isSubmitting, attachedFiles, onChange, onSubmit } = useChatInput(client, startSession);
19
18
  // Check if we're actively receiving content (hide waiting indicator)
@@ -21,8 +20,8 @@ export function ChatView({ client }) {
21
20
  // Callbacks for keyboard shortcuts
22
21
  const handleEscape = () => {
23
22
  if (isStreaming) {
24
- // TODO: Implement proper cancellation when SDK supports it
25
- setIsStreaming(false);
23
+ // Cancel the current agent turn
24
+ cancel();
26
25
  }
27
26
  };
28
27
  return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.83",
3
+ "version": "0.1.96",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -49,7 +49,8 @@
49
49
  "@radix-ui/react-slot": "^1.2.4",
50
50
  "@radix-ui/react-tabs": "^1.1.13",
51
51
  "@radix-ui/react-tooltip": "^1.2.8",
52
- "@townco/core": "0.0.61",
52
+ "@townco/core": "0.0.74",
53
+ "@types/mdast": "^4.0.4",
53
54
  "@uiw/react-json-view": "^2.0.0-alpha.39",
54
55
  "class-variance-authority": "^0.7.1",
55
56
  "clsx": "^2.1.1",
@@ -61,11 +62,12 @@
61
62
  "sonner": "^2.0.7",
62
63
  "streamdown": "^1.6.9",
63
64
  "tailwind-merge": "^3.3.1",
65
+ "unist-util-visit": "^5.0.0",
64
66
  "zod": "^4.1.12",
65
67
  "zustand": "^5.0.8"
66
68
  },
67
69
  "devDependencies": {
68
- "@townco/tsconfig": "0.1.80",
70
+ "@townco/tsconfig": "0.1.93",
69
71
  "@types/node": "^24.10.0",
70
72
  "@types/react": "^19.2.2",
71
73
  "ink": "^6.4.0",
@@ -233,6 +233,77 @@
233
233
  }
234
234
  }
235
235
 
236
+ /* Streamdown list styling */
237
+ /* Ordered and unordered lists need explicit styling since Tailwind resets them */
238
+ [data-streamdown] ol,
239
+ [data-streamdown] ul {
240
+ padding-left: 1.5rem;
241
+ margin: 1rem 0;
242
+ }
243
+
244
+ [data-streamdown] ol {
245
+ list-style-type: decimal;
246
+ }
247
+
248
+ [data-streamdown] ul {
249
+ list-style-type: disc;
250
+ }
251
+
252
+ [data-streamdown] li {
253
+ margin: 0.5rem 0;
254
+ padding-left: 0.25rem;
255
+ }
256
+
257
+ [data-streamdown] li > p {
258
+ margin: 0;
259
+ display: inline;
260
+ }
261
+
262
+ /* Nested lists */
263
+ [data-streamdown] ol ol,
264
+ [data-streamdown] ul ul,
265
+ [data-streamdown] ol ul,
266
+ [data-streamdown] ul ol {
267
+ margin: 0.5rem 0;
268
+ }
269
+
270
+ [data-streamdown] ol ol {
271
+ list-style-type: lower-alpha;
272
+ }
273
+
274
+ [data-streamdown] ol ol ol {
275
+ list-style-type: lower-roman;
276
+ }
277
+
278
+ /* Table styling */
279
+ [data-streamdown] table {
280
+ width: 100%;
281
+ border-collapse: collapse;
282
+ margin: 1rem 0;
283
+ font-size: 0.875rem;
284
+ }
285
+
286
+ [data-streamdown] th,
287
+ [data-streamdown] td {
288
+ border: 1px solid var(--border);
289
+ padding: 0.5rem 0.75rem;
290
+ text-align: left;
291
+ }
292
+
293
+ [data-streamdown] th {
294
+ background-color: var(--muted);
295
+ font-weight: 600;
296
+ }
297
+
298
+ [data-streamdown] tr:nth-child(even) {
299
+ background-color: var(--muted);
300
+ opacity: 0.5;
301
+ }
302
+
303
+ [data-streamdown] table code {
304
+ font-size: 0.8125rem;
305
+ }
306
+
236
307
  /* Streamdown code block dark mode styling */
237
308
  /* Ensure Shiki dark theme variables are applied in dark mode */
238
309
  .dark [data-streamdown="code-block"] {