@ouro.bot/cli 0.1.0-alpha.243 → 0.1.0-alpha.244

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 CHANGED
@@ -1,6 +1,14 @@
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.244",
6
+ "changes": [
7
+ "refactor(sync): git-status-based bundle sync \u2014 postTurnPush discovers dirty files via `git status --porcelain` instead of broken explicit trackSyncWrite tracking (only 3/9 writers used it). Removed dead infrastructure. Remote push optional and non-fatal.",
8
+ "feat(tui): queued input steer \u2014 messages typed while agent is thinking appear dimmed above input area. UP/ESC pops all queued messages back into input for editing. Placeholder hint. Multiple messages supported, each sent as separate turn.",
9
+ "chore: deleted legacy InkCliApp adapter (776 lines dead code)."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.243",
6
14
  "changes": [
@@ -38,7 +38,6 @@ exports.readRecentEpisodes = readRecentEpisodes;
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../nerves/runtime");
41
- const sync_1 = require("../heart/sync");
42
41
  function episodesDir(agentRoot) {
43
42
  return path.join(agentRoot, "arc", "episodes");
44
43
  }
@@ -64,7 +63,6 @@ function emitEpisode(agentRoot, input) {
64
63
  fs.mkdirSync(dir, { recursive: true });
65
64
  const filePath = path.join(dir, `${id}.json`);
66
65
  fs.writeFileSync(filePath, JSON.stringify(episode, null, 2), "utf-8");
67
- (0, sync_1.trackSyncWrite)(filePath);
68
66
  (0, runtime_1.emitNervesEvent)({
69
67
  component: "mind",
70
68
  event: "mind.episode_emitted",
@@ -47,7 +47,6 @@ exports.readJsonFileOrThrow = readJsonFileOrThrow;
47
47
  exports.writeJsonFile = writeJsonFile;
48
48
  const fs = __importStar(require("fs"));
49
49
  const path = __importStar(require("path"));
50
- const sync_1 = require("../heart/sync");
51
50
  /**
52
51
  * Generate a timestamped random ID.
53
52
  * @param prefix Optional prefix (e.g. "care", "int"). Omit for plain timestamp IDs.
@@ -110,11 +109,9 @@ function readJsonFileOrThrow(dir, id, label) {
110
109
  }
111
110
  /**
112
111
  * Write a record to a JSON file, creating the directory if needed.
113
- * Automatically calls trackSyncWrite.
114
112
  */
115
113
  function writeJsonFile(dir, id, record) {
116
114
  fs.mkdirSync(dir, { recursive: true });
117
115
  const filePath = path.join(dir, `${id}.json`);
118
116
  fs.writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
119
- (0, sync_1.trackSyncWrite)(filePath);
120
117
  }
@@ -33,48 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.runWithSyncContext = runWithSyncContext;
37
- exports.trackSyncWrite = trackSyncWrite;
38
- exports.drainSyncWrites = drainSyncWrites;
39
36
  exports.preTurnPull = preTurnPull;
40
37
  exports.postTurnPush = postTurnPush;
41
- const node_async_hooks_1 = require("node:async_hooks");
42
38
  const child_process_1 = require("child_process");
43
39
  const fs = __importStar(require("fs"));
44
40
  const path = __importStar(require("path"));
45
41
  const runtime_1 = require("../nerves/runtime");
46
- /**
47
- * Turn-scoped write tracker using AsyncLocalStorage.
48
- * Each turn runs inside its own async context (via runWithSyncContext),
49
- * so concurrent turns from different senses never share or clobber each
50
- * other's tracked writes.
51
- *
52
- * Continuity writers (episodes, obligations, cares, intentions, diary)
53
- * call trackSyncWrite() after every fs write. drainSyncWrites() returns
54
- * the current turn's accumulated set and clears it.
55
- */
56
- const syncContext = new node_async_hooks_1.AsyncLocalStorage();
57
- /**
58
- * Run a function inside its own sync-write tracking context.
59
- * All trackSyncWrite / drainSyncWrites calls within `fn` (including
60
- * across awaits) operate on a private Set scoped to this invocation.
61
- */
62
- function runWithSyncContext(fn) {
63
- return syncContext.run(new Set(), fn);
64
- }
65
- /** Register an absolute file path written during this turn. No-op if called outside a sync context. */
66
- function trackSyncWrite(absolutePath) {
67
- syncContext.getStore()?.add(absolutePath);
68
- }
69
- /** Return all paths written this turn and clear the set. Returns [] if called outside a sync context. */
70
- function drainSyncWrites() {
71
- const store = syncContext.getStore();
72
- if (!store)
73
- return [];
74
- const paths = [...store];
75
- store.clear();
76
- return paths;
77
- }
78
42
  /**
79
43
  * Pre-turn pull: sync the agent bundle from remote before assembling the start-of-turn packet.
80
44
  */
@@ -111,42 +75,47 @@ function preTurnPull(agentRoot, config) {
111
75
  }
112
76
  }
113
77
  /**
114
- * Post-turn push: commit and push files written during this turn.
115
- * Receives an explicit list of absolute paths (from drainSyncWrites) instead of
116
- * discovering changes via git status. Empty list = no changes = skip.
78
+ * Post-turn push: discover dirty files via `git status`, commit, and push.
79
+ * Uses git-status-based discovery instead of explicit path tracking, ensuring
80
+ * all file writers are captured regardless of whether they call a tracking API.
117
81
  */
118
- function postTurnPush(agentRoot, config, writtenPaths) {
82
+ function postTurnPush(agentRoot, config) {
119
83
  (0, runtime_1.emitNervesEvent)({
120
84
  component: "heart",
121
85
  event: "heart.sync_push_start",
122
86
  message: "post-turn push starting",
123
87
  meta: { agentRoot, remote: config.remote },
124
88
  });
125
- if (writtenPaths.length === 0) {
89
+ let statusOutput;
90
+ try {
91
+ statusOutput = (0, child_process_1.execFileSync)("git", ["status", "--porcelain"], {
92
+ cwd: agentRoot,
93
+ stdio: "pipe",
94
+ timeout: 10000,
95
+ }).toString().trim();
96
+ }
97
+ catch (err) {
98
+ const error = err instanceof Error ? err.message : String(err);
126
99
  (0, runtime_1.emitNervesEvent)({
127
100
  component: "heart",
128
- event: "heart.sync_push_end",
129
- message: "post-turn push: no changes to sync",
130
- meta: { agentRoot },
101
+ event: "heart.sync_push_error",
102
+ message: "post-turn push: git status failed",
103
+ meta: { agentRoot, error },
131
104
  });
132
- return { ok: true };
105
+ return { ok: false, error };
133
106
  }
134
- // Convert absolute paths to relative (for git add)
135
- const relativePaths = writtenPaths
136
- .map((p) => path.relative(agentRoot, p))
137
- .filter((p) => p.length > 0 && !p.startsWith(".."));
138
- if (relativePaths.length === 0) {
107
+ if (statusOutput.length === 0) {
139
108
  (0, runtime_1.emitNervesEvent)({
140
109
  component: "heart",
141
110
  event: "heart.sync_push_end",
142
- message: "post-turn push: no in-bundle paths to sync",
111
+ message: "post-turn push: no changes to sync",
143
112
  meta: { agentRoot },
144
113
  });
145
114
  return { ok: true };
146
115
  }
116
+ const changedCount = statusOutput.split("\n").length;
147
117
  try {
148
- // Stage only the specific files written during this turn
149
- (0, child_process_1.execFileSync)("git", ["add", "--", ...relativePaths], {
118
+ (0, child_process_1.execFileSync)("git", ["add", "-A"], {
150
119
  cwd: agentRoot,
151
120
  stdio: "pipe",
152
121
  timeout: 10000,
@@ -156,6 +125,21 @@ function postTurnPush(agentRoot, config, writtenPaths) {
156
125
  stdio: "pipe",
157
126
  timeout: 10000,
158
127
  });
128
+ // Check if a remote exists
129
+ const remoteOutput = (0, child_process_1.execFileSync)("git", ["remote"], {
130
+ cwd: agentRoot,
131
+ stdio: "pipe",
132
+ timeout: 5000,
133
+ }).toString().trim();
134
+ if (remoteOutput.length === 0) {
135
+ (0, runtime_1.emitNervesEvent)({
136
+ component: "heart",
137
+ event: "heart.sync_push_end",
138
+ message: "post-turn push: committed locally, no remote configured",
139
+ meta: { agentRoot, changedCount },
140
+ });
141
+ return { ok: true };
142
+ }
159
143
  try {
160
144
  (0, child_process_1.execFileSync)("git", ["push", config.remote], {
161
145
  cwd: agentRoot,
@@ -185,7 +169,6 @@ function postTurnPush(agentRoot, config, writtenPaths) {
185
169
  fs.writeFileSync(pendingSyncPath, JSON.stringify({
186
170
  error: retryError,
187
171
  failedAt: new Date().toISOString(),
188
- paths: relativePaths,
189
172
  }, null, 2), "utf-8");
190
173
  (0, runtime_1.emitNervesEvent)({
191
174
  component: "heart",
@@ -200,7 +183,7 @@ function postTurnPush(agentRoot, config, writtenPaths) {
200
183
  component: "heart",
201
184
  event: "heart.sync_push_end",
202
185
  message: "post-turn push complete",
203
- meta: { agentRoot, changedCount: relativePaths.length },
186
+ meta: { agentRoot, changedCount },
204
187
  });
205
188
  return { ok: true };
206
189
  }
@@ -45,7 +45,6 @@ const path = __importStar(require("path"));
45
45
  const crypto_1 = require("crypto");
46
46
  const identity_1 = require("../heart/identity");
47
47
  const runtime_1 = require("../nerves/runtime");
48
- const sync_1 = require("../heart/sync");
49
48
  const associative_recall_1 = require("./associative-recall");
50
49
  const embedding_provider_1 = require("./embedding-provider");
51
50
  const DEDUP_THRESHOLD = 0.6;
@@ -175,11 +174,8 @@ function appendEntriesWithDedup(stores, incoming, options) {
175
174
  all.push(fact);
176
175
  added++;
177
176
  fs.appendFileSync(stores.factsPath, `${JSON.stringify(fact)}\n`, "utf8");
178
- (0, sync_1.trackSyncWrite)(stores.factsPath);
179
177
  updateEntityIndex(stores.entitiesPath, fact);
180
- (0, sync_1.trackSyncWrite)(stores.entitiesPath);
181
178
  appendDailyFact(stores.dailyDir, fact);
182
- (0, sync_1.trackSyncWrite)(path.join(stores.dailyDir, `${(fact.createdAt.slice(0, 10) || "unknown")}.jsonl`));
183
179
  }
184
180
  (0, runtime_1.emitNervesEvent)({
185
181
  component: "mind",
@@ -295,7 +291,6 @@ async function backfillEmbeddings(options) {
295
291
  // Rewrite facts file with updated embeddings
296
292
  const lines = facts.map((f) => JSON.stringify(f)).join("\n") + "\n";
297
293
  fs.writeFileSync(factsPath, lines, "utf8");
298
- (0, sync_1.trackSyncWrite)(factsPath);
299
294
  (0, runtime_1.emitNervesEvent)({
300
295
  component: "mind",
301
296
  event: "mind.diary_backfill_complete",
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QueuedMessages = QueuedMessages;
3
4
  exports.OuroTui = OuroTui;
4
5
  const jsx_runtime_1 = require("react/jsx-runtime");
5
6
  /**
@@ -141,8 +142,14 @@ function ActiveToolLine({ tool }) {
141
142
  function LiveArea({ live, elapsed }) {
142
143
  return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [live.streamingText ? ((0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: live.streamingText, maxWidth: safeWidth() })) : null, live.activeTool ? ((0, jsx_runtime_1.jsx)(ActiveToolLine, { tool: live.activeTool })) : null, live.loading ? ((0, jsx_runtime_1.jsx)(Spinner, { phrasePool: live.spinnerPhrasePool, elapsed: elapsed })) : null, live.errorMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.fang, children: ["✗ ", live.errorMessage] })) : null, live.kickMessage ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.gold, children: ["↻ ", live.kickMessage] })) : null] }));
143
144
  }
145
+ // ─── Queued Messages ───────────────────────────────────────────────
146
+ function QueuedMessages({ items }) {
147
+ if (items.length === 0)
148
+ return (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" });
149
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: items.map((text, i) => ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO.shadow, children: "\u231B queued: " }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: OURO.mist, children: ["\"", text, "\""] })] }, i))) }));
150
+ }
144
151
  // ─── Input ──────────────────────────────────────────────────────────
145
- function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
152
+ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agentName, model }) {
146
153
  const [input, setInput] = (0, react_1.useState)("");
147
154
  const [cursorPos, setCursorPos] = (0, react_1.useState)(0); // cursor position within input
148
155
  const [tooltip, setTooltip] = (0, react_1.useState)("");
@@ -202,6 +209,12 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
202
209
  clearTimeout(tooltipTimerRef.current);
203
210
  tooltipTimerRef.current = setTimeout(() => setTooltip(""), 2000);
204
211
  }
212
+ else if (queuedInputs.length > 0) {
213
+ // Pop all queued messages into input for editing
214
+ const items = onPopQueue();
215
+ updateInput(items.join("\n"));
216
+ historyIdx.current = -1;
217
+ }
205
218
  else {
206
219
  setTooltip("");
207
220
  }
@@ -271,16 +284,25 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
271
284
  }
272
285
  return;
273
286
  }
274
- // Up/Down: history
275
- if (key.upArrow && history.length > 0) {
276
- if (historyIdx.current === -1) {
277
- savedInput.current = inputRef.current;
278
- historyIdx.current = history.length - 1;
287
+ // Up/Down: queue pop takes priority over history
288
+ if (key.upArrow) {
289
+ // If not already browsing history and queue has items, pop queue into input
290
+ if (historyIdx.current === -1 && queuedInputs.length > 0) {
291
+ const items = onPopQueue();
292
+ updateInput(items.join("\n"));
293
+ return;
279
294
  }
280
- else if (historyIdx.current > 0) {
281
- historyIdx.current--;
295
+ // Otherwise, browse history
296
+ if (history.length > 0) {
297
+ if (historyIdx.current === -1) {
298
+ savedInput.current = inputRef.current;
299
+ historyIdx.current = history.length - 1;
300
+ }
301
+ else if (historyIdx.current > 0) {
302
+ historyIdx.current--;
303
+ }
304
+ updateInput(history[historyIdx.current]);
282
305
  }
283
- updateInput(history[historyIdx.current]);
284
306
  return;
285
307
  }
286
308
  if (key.downArrow) {
@@ -345,14 +367,14 @@ function InputArea({ onSubmit, onCtrlC, history, agentName, model }) {
345
367
  const cols = safeWidth();
346
368
  const isMultiline = input.includes("\n");
347
369
  const inputLines = input.split("\n");
348
- return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), 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: ") " }), (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(cols) }), (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 }) : null] })] }));
370
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "─".repeat(cols) }), 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(cols) }), (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 }) : null] })] }));
349
371
  }
350
372
  // ─── Main TUI Component ─────────────────────────────────────────────
351
- function OuroTui({ agentName, model, completedMessages, inputHistory, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, cwd, }) {
373
+ function OuroTui({ agentName, model, completedMessages, inputHistory, queuedInputs, live, elapsedSeconds, contextPercent, onSubmit, onCtrlC, onPopQueue, cwd, }) {
352
374
  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) => {
353
375
  if (index === 0) {
354
376
  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 Alt+Enter newline" })] }, "header"));
355
377
  }
356
378
  return (0, jsx_runtime_1.jsx)(MessageBlock, { msg: item }, item.id);
357
- } }), (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, (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, agentName: agentName, model: model })] }));
379
+ } }), (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 })] }));
358
380
  }
@@ -32,10 +32,12 @@ class TuiStore {
32
32
  _listeners = new Set();
33
33
  _headerShown = false;
34
34
  _inputHistory = [];
35
+ _queuedInputs = [];
35
36
  get completedMessages() { return this._completed; }
36
37
  get live() { return this._live; }
37
38
  get headerShown() { return this._headerShown; }
38
39
  get inputHistory() { return this._inputHistory; }
40
+ get queuedInputs() { return this._queuedInputs; }
39
41
  /** Elapsed seconds since spinner started — computed on demand, not stored */
40
42
  getElapsed() {
41
43
  if (this._live.loading && this._spinnerStart > 0) {
@@ -171,6 +173,28 @@ class TuiStore {
171
173
  seedHistory(texts) {
172
174
  this._inputHistory.push(...texts);
173
175
  }
176
+ // ─── Queued input display ────────────────────────────────────────
177
+ enqueueInput(text) {
178
+ this._queuedInputs.push(text);
179
+ this.notify();
180
+ }
181
+ dequeueInput(text) {
182
+ const idx = this._queuedInputs.indexOf(text);
183
+ if (idx !== -1) {
184
+ this._queuedInputs.splice(idx, 1);
185
+ }
186
+ this.notify();
187
+ }
188
+ popAllQueuedForEditing() {
189
+ const copy = [...this._queuedInputs];
190
+ this._queuedInputs = [];
191
+ this.notify();
192
+ return copy;
193
+ }
194
+ clearQueue() {
195
+ this._queuedInputs = [];
196
+ this.notify();
197
+ }
174
198
  }
175
199
  exports.TuiStore = TuiStore;
176
200
  // ─── Callbacks factory ──────────────────────────────────────────────
@@ -464,6 +464,12 @@ class InputQueue {
464
464
  r({ value: undefined, done: true });
465
465
  }
466
466
  }
467
+ /** Drain all buffered items, leaving any pending async awaiter untouched. */
468
+ drainAll() {
469
+ const items = [...this.queue];
470
+ this.queue = [];
471
+ return items;
472
+ }
467
473
  [Symbol.asyncIterator]() {
468
474
  return {
469
475
  next: () => {
@@ -566,11 +572,13 @@ async function runCliSession(options) {
566
572
  model: (0, identity_1.loadAgentConfig)().humanFacing?.model ?? "",
567
573
  completedMessages: storeRef.completedMessages,
568
574
  inputHistory: storeRef.inputHistory,
575
+ queuedInputs: storeRef.queuedInputs,
569
576
  live: storeRef.live,
570
577
  elapsedSeconds: elapsed,
571
578
  contextPercent: 0,
572
- onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); },
579
+ onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); storeRef.enqueueInput(text); },
573
580
  onCtrlC: handleCtrlC,
581
+ onPopQueue: () => { const items = storeRef.popAllQueuedForEditing(); inputQueue.drainAll(); return items; },
574
582
  headerShown: storeRef.headerShown,
575
583
  cwd: process.cwd().replace(process.env.HOME ?? "", "~"),
576
584
  });
@@ -729,6 +737,8 @@ async function runCliSession(options) {
729
737
  break;
730
738
  if (!input.trim())
731
739
  continue;
740
+ // Remove from TUI queue display as the agent picks up this message
741
+ tuiStore?.dequeueInput(input);
732
742
  // Optional input gate (e.g. trust gate in main)
733
743
  if (options.onInput) {
734
744
  const gate = options.onInput(input);