@ouro.bot/cli 0.1.0-alpha.251 → 0.1.0-alpha.252
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/changelog.json +9 -3
- package/dist/senses/cli/ouro-tui.js +38 -12
- package/dist/senses/cli.js +1 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.252",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(tui): polished history preview (spacing, shadow color, showing-recent label), full-width separators, backslash+enter and ctrl+j for newlines."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.251",
|
|
6
12
|
"changes": [
|
|
7
|
-
"fix(bluebubbles): images sent via iMessage now reach the model
|
|
13
|
+
"fix(bluebubbles): images sent via iMessage now reach the model \u2014 adds capability-gated image hydration with a MiniMax VLM fallback. Reasoning MiniMax chat models (M2/M2.1/M2.5/M2.7) silently drop OpenAI-style `image_url` content parts, so previously slugger answered image questions from fabricated context. Now, when the active chat model lacks the new `vision: true` capability flag, inbound screenshots are auto-described at ingestion via `/v1/coding_plan/vlm` and the description text replaces the `image_url` part before the turn reaches the model. Vision-capable models (claude-opus/sonnet-4-6, gpt-5.4, MiniMax-Text-01, MiniMax-VL-01) continue to see images natively via pass-through.",
|
|
8
14
|
"feat(tools): new `describe_image` agent tool registered into the BlueBubbles tool set when the chat model lacks vision. Lets the agent re-interrogate an attachment with a targeted prompt (e.g. 'what's the flight number in the bottom-right?') after ingestion. Backed by a bounded in-memory attachment cache populated during hydration; handler re-downloads bytes and calls the same VLM client.",
|
|
9
15
|
"fix(bluebubbles): `formatMessageText` now preserves the attachment marker when a message has BOTH text and attachments (B2). Previously the marker was dropped whenever text was present, hiding attachments from the agent's view of the message.",
|
|
10
16
|
"feat(heart): `ModelCapabilities` gains `vision?: boolean` and `audio?: boolean` flags (B4). Vision rows populated for claude-opus-4-6, claude-sonnet-4-6, claude-opus-4.6, claude-sonnet-4.6, gpt-5.4, MiniMax-Text-01, MiniMax-VL-01. M2.1/M2.5/M2.7 intentionally left unset.",
|
|
@@ -14,7 +20,7 @@
|
|
|
14
20
|
{
|
|
15
21
|
"version": "0.1.0-alpha.250",
|
|
16
22
|
"changes": [
|
|
17
|
-
"fix(sync): surface 'bundle is not a git repo' as an actionable error instead of silently failing. Previously, enabling `sync.enabled` on a bundle that had never been `git init`'d produced a generic `git status` failure buried in nerves logs; the agent saw nothing in its start-of-turn packet and the user saw nothing in `ouro status`. Now: (1) `preTurnPull` and `postTurnPush` detect the missing `.git` directory before touching git and return an actionable error with the bundle path and the `git init` hint; (2) this error propagates via `ctx.syncFailure` into the agent's Sync warning, so the agent can offer to run `git init` or just do it; (3) `ouro status` shows a red `error` state with `not a git repo
|
|
23
|
+
"fix(sync): surface 'bundle is not a git repo' as an actionable error instead of silently failing. Previously, enabling `sync.enabled` on a bundle that had never been `git init`'d produced a generic `git status` failure buried in nerves logs; the agent saw nothing in its start-of-turn packet and the user saw nothing in `ouro status`. Now: (1) `preTurnPull` and `postTurnPush` detect the missing `.git` directory before touching git and return an actionable error with the bundle path and the `git init` hint; (2) this error propagates via `ctx.syncFailure` into the agent's Sync warning, so the agent can offer to run `git init` or just do it; (3) `ouro status` shows a red `error` state with `not a git repo \u2014 run \\`git init\\` to enable sync` next to the offending bundle. New nerves event: `heart.sync_not_a_repo`."
|
|
18
24
|
]
|
|
19
25
|
},
|
|
20
26
|
{
|
|
@@ -27,7 +33,7 @@
|
|
|
27
33
|
{
|
|
28
34
|
"version": "0.1.0-alpha.248",
|
|
29
35
|
"changes": [
|
|
30
|
-
"feat(daemon): `ouro status` Git Sync now resolves and shows the actual remote URL via `git remote get-url`. Three states: `origin
|
|
36
|
+
"feat(daemon): `ouro status` Git Sync now resolves and shows the actual remote URL via `git remote get-url`. Three states: `origin \u2192 git@github.com:me/foo.git` when the remote resolves, `local only` when sync is enabled but no remote is configured, `disabled` when sync is off. Previously you had to `cd` into the bundle and run `git remote -v` to find out where it pushes.",
|
|
31
37
|
"fix(heart/sync): `preTurnPull` now skips the pull when no git remote is configured, mirroring the existing `postTurnPush` behavior. Closes the half-implemented 'no-remote sync' (local-only commit log) story \u2014 previously, enabling sync without a remote produced a `syncFailure` on every turn from the failing `git pull` call."
|
|
32
38
|
]
|
|
33
39
|
},
|
|
@@ -41,10 +41,14 @@ function ringColor(elapsedSec) {
|
|
|
41
41
|
return OURO.scale;
|
|
42
42
|
}
|
|
43
43
|
// ─── Header ─────────────────────────────────────────────────────────
|
|
44
|
-
/**
|
|
44
|
+
/** Terminal width for content (capped at 200, with 2-char margin) */
|
|
45
45
|
function safeWidth() {
|
|
46
46
|
return Math.min(process.stdout.columns || 80, 200) - 2;
|
|
47
47
|
}
|
|
48
|
+
/** Full terminal width for edge-to-edge elements (separators) */
|
|
49
|
+
function termWidth() {
|
|
50
|
+
return process.stdout.columns || 80;
|
|
51
|
+
}
|
|
48
52
|
function Header({ agentName, model, contextPercent, cwd }) {
|
|
49
53
|
const showCtx = contextPercent > 0;
|
|
50
54
|
const info = [agentName, model, cwd, showCtx ? `ctx ${contextPercent}%` : ""].filter(Boolean).join(" · ");
|
|
@@ -81,18 +85,18 @@ function MessageBlock({ msg }) {
|
|
|
81
85
|
return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
|
|
82
86
|
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: visibleCalls.map((tc, i) => (0, jsx_runtime_1.jsx)(ToolResultLine, { tc: tc }, i)) }));
|
|
83
87
|
}
|
|
84
|
-
// ── Session history (dimmed
|
|
88
|
+
// ── Session history (dimmed recap of recent exchanges) ──
|
|
85
89
|
if (msg.role === "history-summary") {
|
|
86
|
-
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, {
|
|
90
|
+
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: msg.content }) }));
|
|
87
91
|
}
|
|
88
92
|
if (msg.role === "history-user") {
|
|
89
|
-
return ((0, jsx_runtime_1.
|
|
93
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [(0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, bold: true, children: msg.content })] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
|
|
90
94
|
}
|
|
91
95
|
if (msg.role === "history-assistant") {
|
|
92
|
-
return ((0, jsx_runtime_1.
|
|
96
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginBottom: 1, children: [msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
|
|
93
97
|
}
|
|
94
98
|
if (msg.role === "history-end") {
|
|
95
|
-
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: (0, jsx_runtime_1.jsx)(ink_1.Text, {
|
|
99
|
+
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.separator, children: "─".repeat(termWidth()) }) }));
|
|
96
100
|
}
|
|
97
101
|
if (msg.role === "user") {
|
|
98
102
|
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginTop: 1, children: [msg.content ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, bold: true, children: msg.content }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) })] }));
|
|
@@ -171,6 +175,23 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
171
175
|
const cursorRef = (0, react_1.useRef)(0);
|
|
172
176
|
const historyIdx = (0, react_1.useRef)(-1);
|
|
173
177
|
const savedInput = (0, react_1.useRef)("");
|
|
178
|
+
// Raw stdin handler for Alt+Enter (ESC + CR/LF) — Ink 3.2's useInput splits
|
|
179
|
+
// \x1b\r into separate events, so we catch the combined sequence here.
|
|
180
|
+
const { stdin } = (0, ink_1.useStdin)();
|
|
181
|
+
(0, react_1.useEffect)(() => {
|
|
182
|
+
if (!stdin)
|
|
183
|
+
return;
|
|
184
|
+
const onData = (data) => {
|
|
185
|
+
const str = data.toString("utf-8");
|
|
186
|
+
if (str === "\x1b\r" || str === "\x1b\n") {
|
|
187
|
+
const before = inputRef.current.slice(0, cursorRef.current);
|
|
188
|
+
const after = inputRef.current.slice(cursorRef.current);
|
|
189
|
+
updateInput(before + "\n" + after, cursorRef.current + 1);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
stdin.on("data", onData);
|
|
193
|
+
return () => { stdin.off("data", onData); };
|
|
194
|
+
}, [stdin]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
174
195
|
const tooltipTimerRef = (0, react_1.useRef)(null);
|
|
175
196
|
(0, react_1.useEffect)(() => () => {
|
|
176
197
|
if (tooltipTimerRef.current)
|
|
@@ -234,13 +255,20 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
234
255
|
return;
|
|
235
256
|
}
|
|
236
257
|
if (key.return) {
|
|
237
|
-
|
|
238
|
-
|
|
258
|
+
// Alt+Enter or Shift+Enter: insert newline
|
|
259
|
+
if (key.meta || key.shift) {
|
|
239
260
|
const before = inputRef.current.slice(0, cursorRef.current);
|
|
240
261
|
const after = inputRef.current.slice(cursorRef.current);
|
|
241
262
|
updateInput(before + "\n" + after, cursorRef.current + 1);
|
|
242
263
|
return;
|
|
243
264
|
}
|
|
265
|
+
// Backslash+Enter: insert newline (like Claude Code)
|
|
266
|
+
if (cursorRef.current > 0 && inputRef.current[cursorRef.current - 1] === "\\") {
|
|
267
|
+
const before = inputRef.current.slice(0, cursorRef.current - 1);
|
|
268
|
+
const after = inputRef.current.slice(cursorRef.current);
|
|
269
|
+
updateInput(before + "\n" + after, cursorRef.current);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
244
272
|
const text = inputRef.current;
|
|
245
273
|
if (text.trim())
|
|
246
274
|
onSubmit(text);
|
|
@@ -376,17 +404,15 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
376
404
|
historyIdx.current = -1;
|
|
377
405
|
}
|
|
378
406
|
});
|
|
379
|
-
// Get terminal width (capped for sanity)
|
|
380
|
-
const cols = safeWidth();
|
|
381
407
|
const isMultiline = input.includes("\n");
|
|
382
408
|
const inputLines = input.split("\n");
|
|
383
|
-
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(
|
|
409
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), isMultiline ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: inputLines.map((line, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: i === 0 ? ") " : "· " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: line }), i === inputLines.length - 1 ? (cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " })) : null] }, i))) })) : ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.teal, bold: true, children: ") " }), !input && queuedInputs.length > 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "Press up to edit queued messages" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(0, cursorPos) }), cursorVisible ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.scale, children: "█" }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input[cursorPos] ?? " " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.bone, children: input.slice(cursorPos + (cursorVisible ? 0 : 1)) })] }))] })), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(termWidth()) }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", agentName, model ? ` · ${model}` : "", " \u00B7 /help"] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexGrow: 1 }), tooltip ? (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: tooltip }) : (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "opt+enter for newline" })] })] }));
|
|
384
410
|
}
|
|
385
411
|
// ─── Main TUI Component ─────────────────────────────────────────────
|
|
386
412
|
function OuroTui({ agentName, model, completedMessages, inputHistory, queuedInputs, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, onPopQueue, cwd, }) {
|
|
387
413
|
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Static, { items: [{ id: "__header__" }, ...completedMessages], children: (item, index) => {
|
|
388
414
|
if (index === 0) {
|
|
389
|
-
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginBottom: 2, children: [(0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(Header, { agentName: agentName, model: model, contextPercent: contextPercent, cwd: cwd }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, dimColor: true, children: " Ctrl-C twice to exit \u00b7 \u2191\u2193 history \u00b7 Esc clear \u00b7
|
|
415
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", marginBottom: 2, children: [(0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(Header, { agentName: agentName, model: model, contextPercent: contextPercent, cwd: cwd }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, dimColor: true, children: " Ctrl-C twice to exit \u00b7 \u2191\u2193 history \u00b7 Esc clear \u00b7 opt+Enter newline" })] }, "header"));
|
|
390
416
|
}
|
|
391
417
|
return (0, jsx_runtime_1.jsx)(MessageBlock, { msg: item }, item.id);
|
|
392
418
|
} }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, (0, jsx_runtime_1.jsx)(LiveArea, { live: live, elapsed: elapsedSeconds }), (live.loading || live.streamingText || live.activeTool) ? (0, jsx_runtime_1.jsx)(ink_1.Box, { marginBottom: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }) : null, queuedInputs.length > 0 ? (0, jsx_runtime_1.jsx)(QueuedMessages, { items: queuedInputs }) : null, (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }) }), (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: onSubmit, onCtrlC: onCtrlC, history: inputHistory, queuedInputs: queuedInputs, onPopQueue: onPopQueue, agentName: agentName, model: model })] }));
|
package/dist/senses/cli.js
CHANGED
|
@@ -546,7 +546,7 @@ async function runCliSession(options) {
|
|
|
546
546
|
}
|
|
547
547
|
const msgCount = messages.filter(m => m.role === "user" || m.role === "assistant").length;
|
|
548
548
|
const timeAgo = options.lastActivityAt ? formatTimeAgo(new Date(options.lastActivityAt)) : null;
|
|
549
|
-
const summary = ` resuming session
|
|
549
|
+
const summary = ` resuming session · ${msgCount} messages${timeAgo ? ` · last active ${timeAgo}` : ""} · showing recent`;
|
|
550
550
|
tuiStore.addSessionHistory(summary, lastExchanges);
|
|
551
551
|
}
|
|
552
552
|
/* v8 ignore stop */
|