@townco/ui 0.1.28 → 0.1.30

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.
@@ -16,6 +16,7 @@ export declare function useChatMessages(client: AcpClient | null): {
16
16
  title: string;
17
17
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
18
18
  status: "pending" | "in_progress" | "completed" | "failed";
19
+ contentPosition?: number | undefined;
19
20
  locations?: {
20
21
  path: string;
21
22
  line?: number | null | undefined;
@@ -3,7 +3,7 @@ import type { AcpClient } from "../../sdk/client/index.js";
3
3
  * Hook for managing chat session lifecycle
4
4
  */
5
5
  export declare function useChatSession(client: AcpClient | null, initialSessionId?: string | null): {
6
- connectionStatus: "error" | "connecting" | "connected" | "disconnected";
6
+ connectionStatus: "error" | "disconnected" | "connecting" | "connected";
7
7
  sessionId: string | null;
8
8
  connect: () => Promise<void>;
9
9
  loadSession: (sessionIdToLoad: string) => Promise<void>;
@@ -14,6 +14,7 @@ export declare function useToolCalls(client: AcpClient | null): {
14
14
  title: string;
15
15
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
16
16
  status: "pending" | "in_progress" | "completed" | "failed";
17
+ contentPosition?: number | undefined;
17
18
  locations?: {
18
19
  path: string;
19
20
  line?: number | null | undefined;
@@ -38,6 +38,7 @@ export declare const DisplayMessage: z.ZodObject<{
38
38
  completed: "completed";
39
39
  failed: "failed";
40
40
  }>;
41
+ contentPosition: z.ZodOptional<z.ZodNumber>;
41
42
  locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
42
43
  path: z.ZodString;
43
44
  line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
@@ -133,6 +134,7 @@ export declare const ChatSessionState: z.ZodObject<{
133
134
  completed: "completed";
134
135
  failed: "failed";
135
136
  }>;
137
+ contentPosition: z.ZodOptional<z.ZodNumber>;
136
138
  locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
137
139
  path: z.ZodString;
138
140
  line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
@@ -191,8 +193,8 @@ export type ChatSessionState = z.infer<typeof ChatSessionState>;
191
193
  */
192
194
  export declare const ConnectionStatus: z.ZodEnum<{
193
195
  error: "error";
196
+ disconnected: "disconnected";
194
197
  connecting: "connecting";
195
198
  connected: "connected";
196
- disconnected: "disconnected";
197
199
  }>;
198
200
  export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
@@ -89,6 +89,7 @@ export declare const ToolCallSchema: z.ZodObject<{
89
89
  completed: "completed";
90
90
  failed: "failed";
91
91
  }>;
92
+ contentPosition: z.ZodOptional<z.ZodNumber>;
92
93
  locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
93
94
  path: z.ZodString;
94
95
  line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
@@ -79,6 +79,8 @@ export const ToolCallSchema = z.object({
79
79
  kind: ToolCallKindSchema,
80
80
  /** Current execution status */
81
81
  status: ToolCallStatusSchema,
82
+ /** Position in the message content where this tool call was invoked (character index) */
83
+ contentPosition: z.number().optional(),
82
84
  /** Affected file paths with optional line numbers */
83
85
  locations: z.array(FileLocationSchema).optional(),
84
86
  /** Raw parameters passed to the tool */
@@ -162,9 +162,18 @@ export const useChatStore = create((set) => ({
162
162
  const lastAssistantMsg = messages[lastAssistantIndex];
163
163
  if (!lastAssistantMsg)
164
164
  return state;
165
+ // Track the content position where this tool call was invoked
166
+ const contentPosition = lastAssistantMsg.content.length;
167
+ const toolCallWithPosition = {
168
+ ...toolCall,
169
+ contentPosition,
170
+ };
165
171
  messages[lastAssistantIndex] = {
166
172
  ...lastAssistantMsg,
167
- toolCalls: [...(lastAssistantMsg.toolCalls || []), toolCall],
173
+ toolCalls: [
174
+ ...(lastAssistantMsg.toolCalls || []),
175
+ toolCallWithPosition,
176
+ ],
168
177
  };
169
178
  return { messages };
170
179
  }),
@@ -7,7 +7,7 @@ import type { DisplayMessage } from "./MessageList.js";
7
7
  */
8
8
  declare const messageVariants: (props?: ({
9
9
  role?: "user" | "assistant" | "system" | null | undefined;
10
- layout?: "default" | "compact" | "full" | null | undefined;
10
+ layout?: "default" | "full" | "compact" | null | undefined;
11
11
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
12
12
  export interface MessageProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof messageVariants> {
13
13
  /**
@@ -100,9 +100,42 @@ export const MessageContent = React.forwardRef(({ role: roleProp, variant, isStr
100
100
  const hasThinking = !!thinking;
101
101
  // Check if waiting (streaming but no content yet)
102
102
  const isWaiting = message.isStreaming && !message.content && message.role === "assistant";
103
- content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (_jsxs("div", { className: "flex items-center gap-2 opacity-50", children: [_jsx(Loader2Icon, { className: "size-4 animate-spin text-muted-foreground" }), _jsx(WaitingElapsedTime, { startTime: streamingStartTime })] })), message.role === "assistant" &&
104
- message.toolCalls &&
105
- message.toolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-3", children: message.toolCalls.map((toolCall) => (_jsx(ToolCall, { toolCall: toolCall }, toolCall.id))) })), message.role === "user" ? (_jsx("div", { className: "whitespace-pre-wrap", children: message.content })) : (_jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false })), message.role === "assistant" && message.tokenUsage && (_jsx("div", { className: "mt-3 pt-2 border-t border-border/30 text-xs text-muted-foreground/60", children: _jsxs("span", { children: ["Context:", " ", formatTokenPercentage(message.tokenUsage.totalTokens ?? 0, currentModel ?? undefined), " ", "(", (message.tokenUsage.totalTokens ?? 0).toLocaleString(), " ", "tokens)"] }) }))] }));
103
+ content = (_jsxs(_Fragment, { children: [message.role === "assistant" && hasThinking && (_jsx(Reasoning, { content: thinking, isStreaming: message.isStreaming, mode: thinkingDisplayStyle, autoCollapse: true })), isWaiting && streamingStartTime && (_jsxs("div", { className: "flex items-center gap-2 opacity-50", children: [_jsx(Loader2Icon, { className: "size-4 animate-spin text-muted-foreground" }), _jsx(WaitingElapsedTime, { startTime: streamingStartTime })] })), message.role === "assistant" ? ((() => {
104
+ // Sort tool calls by content position
105
+ const sortedToolCalls = (message.toolCalls || [])
106
+ .slice()
107
+ .sort((a, b) => (a.contentPosition ?? Infinity) -
108
+ (b.contentPosition ?? Infinity));
109
+ // If no tool calls or they don't have positions, render old way
110
+ if (sortedToolCalls.length === 0 ||
111
+ !sortedToolCalls.some((tc) => tc.contentPosition !== undefined)) {
112
+ return (_jsxs(_Fragment, { children: [sortedToolCalls.length > 0 && (_jsx("div", { className: "flex flex-col gap-2 mb-3", children: sortedToolCalls.map((toolCall) => (_jsx(ToolCall, { toolCall: toolCall }, toolCall.id))) })), _jsx(Response, { content: message.content, isStreaming: message.isStreaming, showEmpty: false })] }));
113
+ }
114
+ // Render content interleaved with tool calls
115
+ const elements = [];
116
+ let currentPosition = 0;
117
+ sortedToolCalls.forEach((toolCall) => {
118
+ const position = toolCall.contentPosition ?? message.content.length;
119
+ // Add text before this tool call
120
+ if (position > currentPosition) {
121
+ const textChunk = message.content.slice(currentPosition, position);
122
+ if (textChunk) {
123
+ elements.push(_jsx(Response, { content: textChunk, isStreaming: false, showEmpty: false }, `text-before-${toolCall.id}`));
124
+ }
125
+ }
126
+ // Add the tool call
127
+ elements.push(_jsx("div", { className: "my-2", children: _jsx(ToolCall, { toolCall: toolCall }) }, `tool-${toolCall.id}`));
128
+ currentPosition = position;
129
+ });
130
+ // Add remaining text after the last tool call
131
+ if (currentPosition < message.content.length) {
132
+ const remainingText = message.content.slice(currentPosition);
133
+ if (remainingText) {
134
+ elements.push(_jsx(Response, { content: remainingText, isStreaming: message.isStreaming, showEmpty: false }, "text-end"));
135
+ }
136
+ }
137
+ return _jsx(_Fragment, { children: elements });
138
+ })()) : (_jsx("div", { className: "whitespace-pre-wrap", children: message.content })), message.role === "assistant" && message.tokenUsage && (_jsx("div", { className: "mt-3 pt-2 border-t border-border/30 text-xs text-muted-foreground/60", children: _jsxs("span", { children: ["Context:", " ", formatTokenPercentage(message.tokenUsage.totalTokens ?? 0, currentModel ?? undefined), " ", "(", (message.tokenUsage.totalTokens ?? 0).toLocaleString(), " ", "tokens)"] }) }))] }));
106
139
  }
107
140
  return (_jsx("div", { ref: ref, className: cn(messageContentVariants({ role, variant }), isStreaming && "animate-pulse-subtle", className), ...props, children: content }));
108
141
  });
@@ -4,12 +4,12 @@ import { z } from "zod";
4
4
  */
5
5
  export declare const SessionStatus: z.ZodEnum<{
6
6
  error: "error";
7
- idle: "idle";
7
+ disconnected: "disconnected";
8
8
  connecting: "connecting";
9
9
  connected: "connected";
10
+ idle: "idle";
10
11
  active: "active";
11
12
  streaming: "streaming";
12
- disconnected: "disconnected";
13
13
  }>;
14
14
  export type SessionStatus = z.infer<typeof SessionStatus>;
15
15
  /**
@@ -41,12 +41,12 @@ export declare const Session: z.ZodObject<{
41
41
  id: z.ZodString;
42
42
  status: z.ZodEnum<{
43
43
  error: "error";
44
- idle: "idle";
44
+ disconnected: "disconnected";
45
45
  connecting: "connecting";
46
46
  connected: "connected";
47
+ idle: "idle";
47
48
  active: "active";
48
49
  streaming: "streaming";
49
- disconnected: "disconnected";
50
50
  }>;
51
51
  config: z.ZodObject<{
52
52
  agentPath: z.ZodString;
@@ -109,12 +109,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
109
109
  sessionId: z.ZodString;
110
110
  status: z.ZodOptional<z.ZodEnum<{
111
111
  error: "error";
112
- idle: "idle";
112
+ disconnected: "disconnected";
113
113
  connecting: "connecting";
114
114
  connected: "connected";
115
+ idle: "idle";
115
116
  active: "active";
116
117
  streaming: "streaming";
117
- disconnected: "disconnected";
118
118
  }>>;
119
119
  message: z.ZodOptional<z.ZodObject<{
120
120
  id: z.ZodString;
@@ -176,6 +176,7 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
176
176
  completed: "completed";
177
177
  failed: "failed";
178
178
  }>;
179
+ contentPosition: z.ZodOptional<z.ZodNumber>;
179
180
  locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
180
181
  path: z.ZodString;
181
182
  line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
@@ -215,12 +216,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
215
216
  sessionId: z.ZodString;
216
217
  status: z.ZodOptional<z.ZodEnum<{
217
218
  error: "error";
218
- idle: "idle";
219
+ disconnected: "disconnected";
219
220
  connecting: "connecting";
220
221
  connected: "connected";
222
+ idle: "idle";
221
223
  active: "active";
222
224
  streaming: "streaming";
223
- disconnected: "disconnected";
224
225
  }>>;
225
226
  message: z.ZodOptional<z.ZodObject<{
226
227
  id: z.ZodString;
@@ -306,12 +307,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
306
307
  sessionId: z.ZodString;
307
308
  status: z.ZodOptional<z.ZodEnum<{
308
309
  error: "error";
309
- idle: "idle";
310
+ disconnected: "disconnected";
310
311
  connecting: "connecting";
311
312
  connected: "connected";
313
+ idle: "idle";
312
314
  active: "active";
313
315
  streaming: "streaming";
314
- disconnected: "disconnected";
315
316
  }>>;
316
317
  message: z.ZodOptional<z.ZodObject<{
317
318
  id: z.ZodString;
@@ -362,12 +363,12 @@ export declare const SessionUpdate: z.ZodUnion<readonly [z.ZodObject<{
362
363
  sessionId: z.ZodString;
363
364
  status: z.ZodOptional<z.ZodEnum<{
364
365
  error: "error";
365
- idle: "idle";
366
+ disconnected: "disconnected";
366
367
  connecting: "connecting";
367
368
  connected: "connected";
369
+ idle: "idle";
368
370
  active: "active";
369
371
  streaming: "streaming";
370
- disconnected: "disconnected";
371
372
  }>>;
372
373
  message: z.ZodOptional<z.ZodObject<{
373
374
  id: z.ZodString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/ui",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@agentclientprotocol/sdk": "^0.5.1",
43
- "@townco/core": "0.0.6",
43
+ "@townco/core": "0.0.8",
44
44
  "@radix-ui/react-dialog": "^1.1.15",
45
45
  "@radix-ui/react-dropdown-menu": "^2.1.16",
46
46
  "@radix-ui/react-label": "^2.1.8",
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "devDependencies": {
64
64
  "@tailwindcss/postcss": "^4.1.17",
65
- "@townco/tsconfig": "0.1.25",
65
+ "@townco/tsconfig": "0.1.27",
66
66
  "@types/node": "^24.10.0",
67
67
  "@types/react": "^19.2.2",
68
68
  "ink": "^6.4.0",