@phren/agent 0.1.4 → 0.1.5
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
|
|
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
|
|
69
|
-
} }), _jsxs(Box, { flexDirection: "column", children: [completedToolCalls.map((tc, i) => (_jsx(ToolCall, { ...tc }, `tc-${i}`))), streamingText !== "" && _jsx(
|
|
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 {
|
|
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: [" ",
|
|
25
|
+
return (_jsxs(Text, { children: [" ", _jsxs(Text, { color: hex, children: ["\u25c6", " ", verb] }), " ", _jsxs(Text, { dimColor: true, children: [elapsed, "s"] })] }));
|
|
20
26
|
}
|
package/dist/tui/ink-entry.js
CHANGED
|
@@ -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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.5",
|
|
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.
|
|
20
|
+
"@phren/cli": "0.1.5"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20.0.0"
|