@phren/agent 0.1.4 → 0.1.6

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.
@@ -2,23 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useState, useCallback } from "react";
3
3
  import { Static, Box, Text, useApp } from "ink";
4
4
  import { Banner } from "./Banner.js";
5
- import { UserMessage } from "./UserMessage.js";
6
- import { StreamingText } from "./StreamingText.js";
7
5
  import { ToolCall } from "./ToolCall.js";
8
6
  import { ThinkingIndicator } from "./ThinkingIndicator.js";
9
7
  import { SteerQueue } from "./SteerQueue.js";
10
8
  import { InputArea, PermissionsLine } from "./InputArea.js";
11
9
  import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts.js";
12
- function CompletedItem({ msg }) {
13
- switch (msg.kind) {
14
- case "user":
15
- return _jsx(UserMessage, { text: msg.text });
16
- case "assistant":
17
- return (_jsxs(Box, { flexDirection: "column", children: [msg.text ? _jsx(StreamingText, { text: msg.text }) : null, msg.toolCalls?.map((tc, i) => (_jsx(ToolCall, { ...tc }, i)))] }));
18
- case "status":
19
- return _jsx(Text, { dimColor: true, children: msg.text });
20
- }
21
- }
22
10
  export function App({ state, completedMessages, streamingText, completedToolCalls, thinking, thinkStartTime, thinkElapsed, steerQueue, running, showBanner, inputHistory, onSubmit, onPermissionCycle, onCancelTurn, onExit, }) {
23
11
  const { exit } = useApp();
24
12
  const [inputValue, setInputValue] = useState("");
@@ -53,7 +41,7 @@ export function App({ state, completedMessages, streamingText, completedToolCall
53
41
  onCyclePermissions: onPermissionCycle,
54
42
  onCancelTurn,
55
43
  });
56
- // Build the Static items — banner first if shown, then completed messages
44
+ // Build Static items — banner first, then completed messages
57
45
  const staticItems = [];
58
46
  if (showBanner) {
59
47
  staticItems.push({ id: "banner", kind: "banner" });
@@ -63,8 +51,18 @@ export function App({ state, completedMessages, streamingText, completedToolCall
63
51
  }
64
52
  return (_jsxs(_Fragment, { children: [_jsx(Static, { items: staticItems, children: (item) => {
65
53
  if (item.kind === "banner") {
66
- return (_jsx(Box, { children: _jsx(Banner, { version: state.version }) }, "banner"));
54
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Banner, { version: state.version }) }, "banner"));
55
+ }
56
+ if (item.kind === "user") {
57
+ return (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { bold: true, children: ["\u276f", " ", item.text] }) }, item.id));
58
+ }
59
+ if (item.kind === "assistant") {
60
+ const aMsg = item;
61
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [aMsg.text ? (_jsxs(Box, { children: [_jsxs(Text, { color: "magenta", children: ["\u25c6", " "] }), _jsx(Text, { children: aMsg.text })] })) : null, aMsg.toolCalls?.map((tc, i) => (_jsx(ToolCall, { ...tc }, i)))] }, item.id));
62
+ }
63
+ if (item.kind === "status") {
64
+ return _jsx(Text, { dimColor: true, children: item.text }, item.id);
67
65
  }
68
- return (_jsx(Box, { flexDirection: "column", children: _jsx(CompletedItem, { msg: item }) }, item.id));
69
- } }), _jsxs(Box, { flexDirection: "column", children: [completedToolCalls.map((tc, i) => (_jsx(ToolCall, { ...tc }, `tc-${i}`))), streamingText !== "" && _jsx(StreamingText, { text: streamingText }), thinking && _jsx(ThinkingIndicator, { startTime: thinkStartTime }), thinkElapsed !== null && (_jsxs(Text, { dimColor: true, children: [" ", "\u25c6", " thought for ", thinkElapsed, "s"] })), ctrlCCount > 0 && !running && (_jsxs(Text, { dimColor: true, children: [" ", "Press Ctrl+C again to exit."] })), _jsx(SteerQueue, { items: steerQueue }), _jsx(InputArea, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, bashMode: bashMode, focus: !running }), _jsx(PermissionsLine, { mode: state.permMode })] })] }));
66
+ return null;
67
+ } }), _jsxs(Box, { flexDirection: "column", children: [completedToolCalls.map((tc, i) => (_jsx(ToolCall, { ...tc }, `tc-${i}`))), streamingText !== "" && (_jsxs(Box, { paddingLeft: 2, children: [_jsxs(Text, { color: "magenta", children: ["\u25c6", " "] }), _jsx(Text, { children: streamingText })] })), thinking && _jsx(ThinkingIndicator, { startTime: thinkStartTime }), thinkElapsed !== null && (_jsxs(Text, { dimColor: true, children: [" ", "\u25c6", " thought for ", thinkElapsed, "s"] })), ctrlCCount > 0 && !running && (_jsxs(Text, { dimColor: true, children: [" ", "Press Ctrl+C again to exit."] })), _jsx(SteerQueue, { items: steerQueue }), _jsx(InputArea, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, bashMode: bashMode, focus: !running }), _jsx(PermissionsLine, { mode: state.permMode })] })] }));
70
68
  }
@@ -1,8 +1,14 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
3
  import { Text } from "ink";
4
+ // Phren's own thinking verbs — memory-oriented, not generic
5
+ const THINKING_VERBS = [
6
+ "thinking", "reasoning", "recalling", "connecting", "processing",
7
+ ];
4
8
  export function ThinkingIndicator({ startTime }) {
5
9
  const [frame, setFrame] = useState(0);
10
+ // Pick a random verb once per mount
11
+ const verb = useMemo(() => THINKING_VERBS[Math.floor(Math.random() * THINKING_VERBS.length)], []);
6
12
  useEffect(() => {
7
13
  const timer = setInterval(() => {
8
14
  setFrame((f) => f + 1);
@@ -16,5 +22,5 @@ export function ThinkingIndicator({ startTime }) {
16
22
  const g = Math.round(140 * (1 - t) + 211 * t);
17
23
  const b = Math.round(250 * (1 - t) + 242 * t);
18
24
  const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
19
- return (_jsxs(Text, { children: [" ", _jsx(Text, { color: hex, children: "\u25C6 thinking" }), " ", _jsxs(Text, { dimColor: true, children: [elapsed, "s"] })] }));
25
+ return (_jsxs(Text, { children: [" ", _jsxs(Text, { color: hex, children: ["\u25c6", " ", verb] }), " ", _jsxs(Text, { dimColor: true, children: [elapsed, "s"] })] }));
20
26
  }
@@ -99,6 +99,7 @@ export async function startInkTui(config, spawner) {
99
99
  },
100
100
  });
101
101
  function handleSubmit(input) {
102
+ console.error(`[BRIDGE] handleSubmit: ${JSON.stringify(input.slice(0, 40))} running=${running}`);
102
103
  const line = input.trim();
103
104
  if (!line)
104
105
  return;
@@ -172,42 +173,53 @@ export async function startInkTui(config, spawner) {
172
173
  // TurnHooks bridge — updates mutable state, calls update()
173
174
  const tuiHooks = {
174
175
  onTextDelta: (text) => {
176
+ console.error(`[BRIDGE] onTextDelta: ${JSON.stringify(text.slice(0, 40))}`);
175
177
  thinking = false;
176
178
  streamingText += text;
177
179
  update();
178
180
  },
179
181
  onTextDone: () => {
182
+ console.error("[BRIDGE] onTextDone");
180
183
  // streaming complete — finalized in runAgentTurn
181
184
  },
182
185
  onTextBlock: (text) => {
186
+ console.error(`[BRIDGE] onTextBlock: ${JSON.stringify(text.slice(0, 40))}`);
183
187
  thinking = false;
184
188
  streamingText += text;
185
189
  update();
186
190
  },
187
191
  onToolStart: (_name, _input, _count) => {
192
+ console.error(`[BRIDGE] onToolStart: ${_name} (${_count} tools)`);
188
193
  thinking = false;
189
194
  update();
190
195
  },
191
196
  onToolEnd: (name, input, output, isError, dur) => {
197
+ console.error(`[BRIDGE] onToolEnd: ${name} isError=${isError} dur=${dur}ms`);
192
198
  const diffData = (name === "edit_file" || name === "write_file") ? decodeDiffPayload(output) : null;
193
199
  const cleanOutput = diffData ? output.slice(0, output.indexOf(DIFF_MARKER)) : output;
194
200
  currentToolCalls.push({ name, input, output: cleanOutput, isError, durationMs: dur });
195
201
  update();
196
202
  },
197
- onStatus: () => { update(); },
203
+ onStatus: (msg) => { console.error(`[BRIDGE] onStatus: ${msg.slice(0, 60)}`); update(); },
198
204
  getSteeringInput: () => {
199
- if (steerQueueBuf.length > 0 && inputMode === "steering") {
200
- return steerQueueBuf.shift();
201
- }
202
- if (pendingInput && inputMode === "steering") {
203
- const steer = pendingInput;
204
- pendingInput = null;
205
- return steer;
206
- }
207
- return null;
205
+ const result = (() => {
206
+ if (steerQueueBuf.length > 0 && inputMode === "steering") {
207
+ return steerQueueBuf.shift();
208
+ }
209
+ if (pendingInput && inputMode === "steering") {
210
+ const steer = pendingInput;
211
+ pendingInput = null;
212
+ return steer;
213
+ }
214
+ return null;
215
+ })();
216
+ if (result)
217
+ console.error(`[BRIDGE] getSteeringInput: ${JSON.stringify(result.slice(0, 40))}`);
218
+ return result;
208
219
  },
209
220
  };
210
221
  async function runAgentTurn(userInput) {
222
+ console.error(`[BRIDGE] runAgentTurn START: ${JSON.stringify(userInput.slice(0, 40))}`);
211
223
  running = true;
212
224
  thinking = true;
213
225
  thinkStartTime = Date.now();
@@ -220,10 +232,12 @@ export async function startInkTui(config, spawner) {
220
232
  }
221
233
  catch (err) {
222
234
  const msg = err instanceof Error ? err.message : String(err);
235
+ console.error(`[BRIDGE] runAgentTurn ERROR: ${msg}`);
223
236
  streamingText += `\nError: ${msg}`;
224
237
  }
225
238
  // Compute elapsed time
226
239
  const elapsed = ((Date.now() - thinkStartTime) / 1000).toFixed(1);
240
+ console.error(`[BRIDGE] runAgentTurn END: elapsed=${elapsed}s streamingText=${streamingText.length}chars toolCalls=${currentToolCalls.length}`);
227
241
  // Finalize: move streaming content + tool calls to completed messages
228
242
  thinking = false;
229
243
  if (streamingText || currentToolCalls.length > 0) {
@@ -259,6 +273,8 @@ export async function startInkTui(config, spawner) {
259
273
  runAgentTurn(queued);
260
274
  }
261
275
  }
276
+ // Clear screen before initial render — start clean
277
+ process.stdout.write("\x1b[2J\x1b[H");
262
278
  // Initial render
263
279
  const app = render(_jsx(App, { state: getAppState(), completedMessages: [], streamingText: "", completedToolCalls: [], thinking: false, thinkStartTime: 0, thinkElapsed: null, steerQueue: [], running: false, showBanner: true, inputHistory: [], onSubmit: handleSubmit, onPermissionCycle: handlePermissionCycle, onCancelTurn: handleCancelTurn, onExit: handleExit }), { exitOnCtrlC: false });
264
280
  rerender = app.rerender;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Coding agent with persistent memory — powered by phren",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "ink-spinner": "^5.0.0",
18
18
  "ink-text-input": "^6.0.0",
19
19
  "react": "^19.2.4",
20
- "@phren/cli": "0.1.4"
20
+ "@phren/cli": "0.1.6"
21
21
  },
22
22
  "engines": {
23
23
  "node": ">=20.0.0"