@gorlitzer-labs/apiary 1.13.9 → 1.14.0
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/README.md +15 -3
- package/dist/chunk-36WX2UAF.js +663 -0
- package/dist/chunk-36WX2UAF.js.map +1 -0
- package/dist/{chunk-7JXPJO3R.js → chunk-LNSDH2V5.js} +6 -6
- package/dist/{chunk-BCQQJR5E.js → chunk-LOONM4G3.js} +7 -176
- package/dist/chunk-LOONM4G3.js.map +1 -0
- package/dist/{chunk-4A7MREYX.js → chunk-N2LPKMPO.js} +185 -2
- package/dist/chunk-N2LPKMPO.js.map +1 -0
- package/dist/{chunk-MEWZT5KA.js → chunk-XP5F5NCQ.js} +2 -2
- package/dist/cli/index.js +37 -379
- package/dist/cli/index.js.map +1 -1
- package/dist/{invites-GRROUSCE.js → invites-ID5SRM3D.js} +4 -4
- package/dist/{join-H62VB4HU.js → join-W53OPEHR.js} +4 -4
- package/dist/{room-ID4DQDPT.js → room-XC546NTX.js} +10 -10
- package/dist/run-6JRFWMAQ.js +164 -0
- package/dist/run-6JRFWMAQ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-4A7MREYX.js.map +0 -1
- package/dist/chunk-BCQQJR5E.js.map +0 -1
- package/dist/chunk-XLUTCAY6.js +0 -288
- package/dist/chunk-XLUTCAY6.js.map +0 -1
- package/dist/run-C22WQZVK.js +0 -91
- package/dist/run-C22WQZVK.js.map +0 -1
- /package/dist/{chunk-7JXPJO3R.js.map → chunk-LNSDH2V5.js.map} +0 -0
- /package/dist/{chunk-MEWZT5KA.js.map → chunk-XP5F5NCQ.js.map} +0 -0
- /package/dist/{invites-GRROUSCE.js.map → invites-ID5SRM3D.js.map} +0 -0
- /package/dist/{join-H62VB4HU.js.map → join-W53OPEHR.js.map} +0 -0
- /package/dist/{room-ID4DQDPT.js.map → room-XC546NTX.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
join
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-LNSDH2V5.js";
|
|
5
5
|
import {
|
|
6
6
|
deliverInvite,
|
|
7
7
|
sameApiaryServer
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-XP5F5NCQ.js";
|
|
9
9
|
import {
|
|
10
10
|
checkForUpdate,
|
|
11
11
|
printUpdateNotice
|
|
@@ -16,27 +16,16 @@ import {
|
|
|
16
16
|
isAgentAlive,
|
|
17
17
|
listAgentSessions,
|
|
18
18
|
listRoomSessions,
|
|
19
|
-
resetTerminal,
|
|
20
19
|
resolveBindAddress,
|
|
21
20
|
saveAgentSession,
|
|
22
|
-
serve
|
|
23
|
-
|
|
24
|
-
tmuxAvailable,
|
|
25
|
-
tmuxCapturePane,
|
|
26
|
-
tmuxCreateSession,
|
|
27
|
-
tmuxInjectPaste,
|
|
28
|
-
tmuxInjectText,
|
|
29
|
-
tmuxKillSession,
|
|
30
|
-
tmuxPaneIsShell,
|
|
31
|
-
tmuxSendCommand,
|
|
32
|
-
tmuxSendEnter,
|
|
33
|
-
tmuxSendKey,
|
|
34
|
-
tmuxSessionExists
|
|
35
|
-
} from "../chunk-BCQQJR5E.js";
|
|
21
|
+
serve
|
|
22
|
+
} from "../chunk-LOONM4G3.js";
|
|
36
23
|
import "../chunk-PEB6HEFT.js";
|
|
37
24
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
TmuxBridge,
|
|
26
|
+
buildRuntimeMcpOptions,
|
|
27
|
+
composerStillHolds
|
|
28
|
+
} from "../chunk-36WX2UAF.js";
|
|
40
29
|
import {
|
|
41
30
|
EventProcessor,
|
|
42
31
|
RUNTIME_TOOL_NAMES,
|
|
@@ -46,8 +35,20 @@ import {
|
|
|
46
35
|
shouldReportActivity
|
|
47
36
|
} from "../chunk-WJD5EKO6.js";
|
|
48
37
|
import {
|
|
49
|
-
buildShareUrl
|
|
50
|
-
|
|
38
|
+
buildShareUrl,
|
|
39
|
+
resetTerminal,
|
|
40
|
+
tmuxAttach,
|
|
41
|
+
tmuxAvailable,
|
|
42
|
+
tmuxCapturePane,
|
|
43
|
+
tmuxCreateSession,
|
|
44
|
+
tmuxInjectPaste,
|
|
45
|
+
tmuxKillSession,
|
|
46
|
+
tmuxPaneIsShell,
|
|
47
|
+
tmuxSendCommand,
|
|
48
|
+
tmuxSendEnter,
|
|
49
|
+
tmuxSendKey,
|
|
50
|
+
tmuxSessionExists
|
|
51
|
+
} from "../chunk-N2LPKMPO.js";
|
|
51
52
|
import {
|
|
52
53
|
randomName
|
|
53
54
|
} from "../chunk-D7DXZQWT.js";
|
|
@@ -61,349 +62,6 @@ import { writeFileSync as writeFileSync2, mkdtempSync, mkdirSync, rmSync, chmodS
|
|
|
61
62
|
import { join as join4 } from "path";
|
|
62
63
|
import { tmpdir, homedir as homedir3 } from "os";
|
|
63
64
|
|
|
64
|
-
// src/cli/composer.ts
|
|
65
|
-
var PROMPT_MARKER = /^\s*[›❯>]\s?/;
|
|
66
|
-
function composerBlock(lines) {
|
|
67
|
-
const tail = lines.slice(-25);
|
|
68
|
-
let start = -1;
|
|
69
|
-
for (let i = tail.length - 1; i >= 0; i--) {
|
|
70
|
-
if (PROMPT_MARKER.test(tail[i])) {
|
|
71
|
-
start = i;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (start === -1) return "";
|
|
76
|
-
return tail.slice(start).join(" ");
|
|
77
|
-
}
|
|
78
|
-
function submissionProbe(text) {
|
|
79
|
-
return text.replace(/\s+/g, " ").trim().slice(0, 40);
|
|
80
|
-
}
|
|
81
|
-
function composerStillHolds(lines, text) {
|
|
82
|
-
const probe = submissionProbe(text);
|
|
83
|
-
if (probe.length < 8) return false;
|
|
84
|
-
const composer = composerBlock(lines).replace(/\s+/g, " ");
|
|
85
|
-
return composer.includes(probe);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// src/cli/claude/tmux-bridge.ts
|
|
89
|
-
var SPINNER_CHARS = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
90
|
-
var BUSY_PATTERNS = [
|
|
91
|
-
// "← 1 agent" / "← 3 agents"
|
|
92
|
-
/←\s*\d+\s+agents?\b/
|
|
93
|
-
];
|
|
94
|
-
var DIALOG_PATTERNS = [
|
|
95
|
-
"Enter to select",
|
|
96
|
-
"to navigate",
|
|
97
|
-
"Esc to cancel",
|
|
98
|
-
"Ready to code?",
|
|
99
|
-
"Review your answers",
|
|
100
|
-
"ctrl+g to edit in"
|
|
101
|
-
];
|
|
102
|
-
var PERMISSION_PATTERNS = [
|
|
103
|
-
"(Y)",
|
|
104
|
-
"Allow ",
|
|
105
|
-
"Deny ",
|
|
106
|
-
"approve",
|
|
107
|
-
"Yes / No",
|
|
108
|
-
// Current Claude Code wording, captured from a live prompt. Without these a
|
|
109
|
-
// permission dialog reads as idle and the bridge pastes a room event into
|
|
110
|
-
// it, answering on the agent's behalf.
|
|
111
|
-
"Do you want to proceed?",
|
|
112
|
-
"don't ask again",
|
|
113
|
-
// The folder-trust modal, which no flag skips.
|
|
114
|
-
"Yes, I trust this folder"
|
|
115
|
-
];
|
|
116
|
-
var BLOCKING_PATTERNS = [
|
|
117
|
-
"Do you want to proceed?",
|
|
118
|
-
"don't ask again",
|
|
119
|
-
"Yes, I trust this folder",
|
|
120
|
-
"Ready to code?",
|
|
121
|
-
"Enter to select",
|
|
122
|
-
/❯\s*1\.\s*Yes/,
|
|
123
|
-
/\(y\s*\/\s*n\)/i,
|
|
124
|
-
// The arrow glyphs claude renders beside it. Bare "to navigate" is prose.
|
|
125
|
-
/[↑↓⬆⬇]\s*(?:[↑↓⬆⬇]\s*)?to navigate/
|
|
126
|
-
];
|
|
127
|
-
function matchesAny(text, patterns) {
|
|
128
|
-
const haystack = text.toLowerCase();
|
|
129
|
-
for (const pattern of patterns) {
|
|
130
|
-
if (typeof pattern === "string") {
|
|
131
|
-
if (haystack.includes(pattern.toLowerCase())) return true;
|
|
132
|
-
} else if (pattern.test(text)) {
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
function defaultUndeliveredWarning(text) {
|
|
139
|
-
const preview = text.replace(/\s+/g, " ").slice(0, 80);
|
|
140
|
-
process.stderr.write(`apiary: message NOT delivered to the agent \u2014 still in its composer: ${preview}
|
|
141
|
-
`);
|
|
142
|
-
}
|
|
143
|
-
var TmuxBridge = class _TmuxBridge {
|
|
144
|
-
session;
|
|
145
|
-
queue = [];
|
|
146
|
-
pollTimer = null;
|
|
147
|
-
pollIntervalMs;
|
|
148
|
-
keystrokeDelayMs;
|
|
149
|
-
onUndelivered;
|
|
150
|
-
stopped = false;
|
|
151
|
-
blockedCount = 0;
|
|
152
|
-
static BLOCKED_THRESHOLD = 150;
|
|
153
|
-
// ~30s at 200ms poll — force inject if stuck
|
|
154
|
-
constructor(session, opts) {
|
|
155
|
-
this.session = session;
|
|
156
|
-
this.pollIntervalMs = opts?.pollIntervalMs ?? 200;
|
|
157
|
-
this.keystrokeDelayMs = opts?.keystrokeDelayMs ?? 50;
|
|
158
|
-
this.onUndelivered = opts?.onUndelivered ?? defaultUndeliveredWarning;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Delivery callback — drop-in replacement for the raw tmuxDeliver lambda.
|
|
162
|
-
* Pass `bridge.deliver.bind(bridge)` to EventProcessor.run().
|
|
163
|
-
*/
|
|
164
|
-
async deliver(parts, opts) {
|
|
165
|
-
const text = contentPartsToString(parts);
|
|
166
|
-
if (!text.trim()) return;
|
|
167
|
-
this.inject(text, opts?.dedupe === true);
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Detect the current TUI state by reading the screen.
|
|
171
|
-
* Exported for testing — the heuristic logic is in detectStateFromLines().
|
|
172
|
-
*/
|
|
173
|
-
detectState() {
|
|
174
|
-
if (tmuxPaneIsShell(this.session)) return "absent";
|
|
175
|
-
const lines = this.captureScreen();
|
|
176
|
-
return detectStateFromLines(lines);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* State to REPORT to the room, as opposed to the state used to decide
|
|
180
|
-
* whether it is safe to type. Same screen, stricter about claiming a human
|
|
181
|
-
* is needed — see detectReportedStateFromLines.
|
|
182
|
-
*/
|
|
183
|
-
detectReportedState() {
|
|
184
|
-
if (tmuxPaneIsShell(this.session)) return "absent";
|
|
185
|
-
return detectReportedStateFromLines(this.captureScreen());
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Extract claude's live status label from the pane (the "Sautéed for 12s",
|
|
189
|
-
* "Compacting conversation… 23%" lines under the prompt). Returns null when
|
|
190
|
-
* the agent is idle / no status visible.
|
|
191
|
-
*
|
|
192
|
-
* The room TUI calls this through the agent runtime's poll loop to surface
|
|
193
|
-
* "is the agent actually working or stuck?" without making the user attach
|
|
194
|
-
* to each tmux pane.
|
|
195
|
-
*/
|
|
196
|
-
getActivityLabel() {
|
|
197
|
-
return extractActivityLabel(this.captureScreen());
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Try to inject text, choosing strategy based on TUI state.
|
|
201
|
-
* If the state is unsafe, queues the text and starts polling.
|
|
202
|
-
*
|
|
203
|
-
* Text is flattened to a single line before injection to avoid triggering
|
|
204
|
-
* Claude Code's paste detection. When multi-line text arrives via
|
|
205
|
-
* `send-keys -l`, Claude Code detects it as a paste and collapses it into
|
|
206
|
-
* "[Pasted text #1 +N lines]" which may not reliably submit with Enter.
|
|
207
|
-
*/
|
|
208
|
-
inject(text, dedupe) {
|
|
209
|
-
const flat = text.replace(/\n/g, " ");
|
|
210
|
-
const state = this.detectState();
|
|
211
|
-
switch (state) {
|
|
212
|
-
case "idle":
|
|
213
|
-
this.injectIdle(flat);
|
|
214
|
-
break;
|
|
215
|
-
case "typing":
|
|
216
|
-
this.injectWhileTyping(flat);
|
|
217
|
-
break;
|
|
218
|
-
default:
|
|
219
|
-
this.enqueue(flat, dedupe);
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/** Capture the screen via tmux capture-pane. */
|
|
224
|
-
captureScreen() {
|
|
225
|
-
return tmuxCapturePane(this.session);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Inject into an idle prompt: type text + Enter, then check it went.
|
|
229
|
-
*
|
|
230
|
-
* This used to fire a second Enter unconditionally as a "safety net" against
|
|
231
|
-
* Claude Code's paste detection swallowing the first. That is a guess in both
|
|
232
|
-
* directions: it cannot tell whether the first Enter worked, and it cannot
|
|
233
|
-
* tell whether the second one did either. Reading the composer back answers
|
|
234
|
-
* the question instead, and only sends the second Enter when it is needed.
|
|
235
|
-
*/
|
|
236
|
-
injectIdle(text) {
|
|
237
|
-
tmuxInjectText(this.session, text);
|
|
238
|
-
tmuxSendEnter(this.session);
|
|
239
|
-
this.confirmSubmitted(text);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Check the message left the composer, and press Enter once more if it did not.
|
|
243
|
-
*
|
|
244
|
-
* A submitted message moves into the transcript; one still in the composer was
|
|
245
|
-
* never sent. If it is still there after the retry, the room is told rather
|
|
246
|
-
* than left to read the agent's silence as a choice.
|
|
247
|
-
*/
|
|
248
|
-
confirmSubmitted(text) {
|
|
249
|
-
this.sleep(80);
|
|
250
|
-
if (!composerStillHolds(this.captureScreen(), text)) return;
|
|
251
|
-
tmuxSendEnter(this.session);
|
|
252
|
-
this.sleep(120);
|
|
253
|
-
if (!composerStillHolds(this.captureScreen(), text)) return;
|
|
254
|
-
this.onUndelivered(text);
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Inject while the user is typing:
|
|
258
|
-
* 1. Ctrl+U — cut line to kill ring
|
|
259
|
-
* 2. Inject our text + Enter
|
|
260
|
-
* 3. Ctrl+Y — paste the user's text back
|
|
261
|
-
*/
|
|
262
|
-
injectWhileTyping(text) {
|
|
263
|
-
tmuxSendKey(this.session, "C-u");
|
|
264
|
-
this.sleep(this.keystrokeDelayMs);
|
|
265
|
-
tmuxInjectText(this.session, text);
|
|
266
|
-
tmuxSendEnter(this.session);
|
|
267
|
-
this.confirmSubmitted(text);
|
|
268
|
-
this.sleep(this.keystrokeDelayMs);
|
|
269
|
-
tmuxSendKey(this.session, "C-y");
|
|
270
|
-
}
|
|
271
|
-
/** Add to queue and start polling if not already. */
|
|
272
|
-
enqueue(text, dedupe) {
|
|
273
|
-
if (dedupe && this.queue.includes(text)) return;
|
|
274
|
-
this.queue.push(text);
|
|
275
|
-
this.startPolling();
|
|
276
|
-
}
|
|
277
|
-
/** Start the polling timer to drain queued events. */
|
|
278
|
-
startPolling() {
|
|
279
|
-
if (this.pollTimer || this.stopped) return;
|
|
280
|
-
this.pollTimer = setInterval(() => this.drainQueue(), this.pollIntervalMs);
|
|
281
|
-
}
|
|
282
|
-
/** Stop the polling timer. */
|
|
283
|
-
stopPolling() {
|
|
284
|
-
if (this.pollTimer) {
|
|
285
|
-
clearInterval(this.pollTimer);
|
|
286
|
-
this.pollTimer = null;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Try to drain one queued event if the state is safe.
|
|
291
|
-
*
|
|
292
|
-
* Drains one event at a time rather than batching all into one multi-line
|
|
293
|
-
* string — multi-line text triggers Claude Code's paste detection which
|
|
294
|
-
* collapses it into "[Pasted text #1 +N lines]".
|
|
295
|
-
*
|
|
296
|
-
* After injecting one event, the poll continues. The next cycle re-checks
|
|
297
|
-
* state: if Claude is busy (streaming), remaining events wait. If idle,
|
|
298
|
-
* the next event is injected. Events are already flattened in inject().
|
|
299
|
-
*/
|
|
300
|
-
drainQueue() {
|
|
301
|
-
if (this.queue.length === 0) {
|
|
302
|
-
this.stopPolling();
|
|
303
|
-
this.blockedCount = 0;
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const state = this.detectState();
|
|
307
|
-
if (state === "idle" || state === "typing") {
|
|
308
|
-
this.blockedCount = 0;
|
|
309
|
-
const text = this.queue.shift();
|
|
310
|
-
if (state === "idle") {
|
|
311
|
-
this.injectIdle(text);
|
|
312
|
-
} else {
|
|
313
|
-
this.injectWhileTyping(text);
|
|
314
|
-
}
|
|
315
|
-
if (this.queue.length === 0) {
|
|
316
|
-
this.stopPolling();
|
|
317
|
-
}
|
|
318
|
-
} else {
|
|
319
|
-
this.blockedCount++;
|
|
320
|
-
if (this.blockedCount >= _TmuxBridge.BLOCKED_THRESHOLD) {
|
|
321
|
-
this.blockedCount = 0;
|
|
322
|
-
const text = this.queue.shift();
|
|
323
|
-
this.injectIdle(text);
|
|
324
|
-
if (this.queue.length === 0) this.stopPolling();
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/** Cleanup. */
|
|
329
|
-
stop() {
|
|
330
|
-
this.stopped = true;
|
|
331
|
-
this.stopPolling();
|
|
332
|
-
this.queue.length = 0;
|
|
333
|
-
}
|
|
334
|
-
/** Synchronous sleep — only used for tiny keystroke delays. */
|
|
335
|
-
sleep(ms) {
|
|
336
|
-
if (ms <= 0) return;
|
|
337
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
var ACTIVITY_LIVE = /([A-Za-zÀ-ÿ]+…\s*\([^)\n]*\))/;
|
|
341
|
-
var ACTIVITY_COOK = /([A-Za-zÀ-ÿ]+ed\s+for\s+\d+s)/;
|
|
342
|
-
var ACTIVITY_PROG = /((?:Compacting|Thinking|Loading|Connecting)[^()]*?(?:\s+\d+%)?)(?=\s*[(\n]|\s*$)/;
|
|
343
|
-
function extractActivityLabel(lines) {
|
|
344
|
-
if (lines.length === 0) return null;
|
|
345
|
-
let best = null;
|
|
346
|
-
for (const line of lines.slice(-25)) {
|
|
347
|
-
const m = line.match(ACTIVITY_PROG) ?? line.match(ACTIVITY_COOK) ?? line.match(ACTIVITY_LIVE);
|
|
348
|
-
if (m) best = m[1].trim();
|
|
349
|
-
}
|
|
350
|
-
return best;
|
|
351
|
-
}
|
|
352
|
-
function detectStateFromLines(lines, opts) {
|
|
353
|
-
if (lines.length === 0) return "unknown";
|
|
354
|
-
const tail = lines.slice(-30);
|
|
355
|
-
const tailText = tail.join("\n");
|
|
356
|
-
if (opts?.ignorePrompts !== true) {
|
|
357
|
-
for (const pattern of DIALOG_PATTERNS) {
|
|
358
|
-
if (tailText.includes(pattern)) return "dialog";
|
|
359
|
-
}
|
|
360
|
-
for (const pattern of PERMISSION_PATTERNS) {
|
|
361
|
-
if (tailText.includes(pattern)) return "permission";
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const lastFew = tail.slice(-5).join("");
|
|
365
|
-
for (const ch of SPINNER_CHARS) {
|
|
366
|
-
if (lastFew.includes(ch)) return "streaming";
|
|
367
|
-
}
|
|
368
|
-
for (const pattern of BUSY_PATTERNS) {
|
|
369
|
-
if (pattern.test(tailText)) return "streaming";
|
|
370
|
-
}
|
|
371
|
-
const promptChar = /^[❯›](\s|$)/;
|
|
372
|
-
const footerChar = /^[❯›]{2}\s/;
|
|
373
|
-
const separatorLine = /^[─━─\-]{10,}/;
|
|
374
|
-
for (let i = tail.length - 1; i >= 0; i--) {
|
|
375
|
-
const line = tail[i].trimStart();
|
|
376
|
-
if (footerChar.test(line)) {
|
|
377
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
378
|
-
const above = tail[j].trimStart();
|
|
379
|
-
if (promptChar.test(above)) {
|
|
380
|
-
const content = above.replace(/^[❯›]\s*/, "").trim();
|
|
381
|
-
return content.length === 0 ? "idle" : "typing";
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
for (let i = tail.length - 1; i >= 0; i--) {
|
|
388
|
-
const line = tail[i].trimStart();
|
|
389
|
-
if (promptChar.test(line)) {
|
|
390
|
-
const above = i > 0 ? tail[i - 1].trimStart() : "";
|
|
391
|
-
const below = i < tail.length - 1 ? tail[i + 1].trimStart() : "";
|
|
392
|
-
if (separatorLine.test(above) || separatorLine.test(below)) {
|
|
393
|
-
const content = line.replace(/^[❯›]\s*/, "").trim();
|
|
394
|
-
return content.length === 0 ? "idle" : "typing";
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return "unknown";
|
|
399
|
-
}
|
|
400
|
-
function detectReportedStateFromLines(lines) {
|
|
401
|
-
const loose = detectStateFromLines(lines);
|
|
402
|
-
if (loose !== "dialog" && loose !== "permission") return loose;
|
|
403
|
-
if (matchesAny(lines.slice(-30).join("\n"), BLOCKING_PATTERNS)) return loose;
|
|
404
|
-
return detectStateFromLines(lines, { ignorePrompts: true });
|
|
405
|
-
}
|
|
406
|
-
|
|
407
65
|
// src/cli/runtime-setup.ts
|
|
408
66
|
async function setupAgentRuntime(options) {
|
|
409
67
|
const agentName = options.name ?? randomName();
|
|
@@ -1343,7 +1001,7 @@ function queueCodexMessage(threadId, text, run = defaultRunner) {
|
|
|
1343
1001
|
}
|
|
1344
1002
|
|
|
1345
1003
|
// src/cli/codex/tmux-bridge.ts
|
|
1346
|
-
var
|
|
1004
|
+
var SPINNER_CHARS = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
1347
1005
|
var APPROVAL_PATTERNS = [
|
|
1348
1006
|
"Would you like to",
|
|
1349
1007
|
"needs your approval",
|
|
@@ -1390,7 +1048,7 @@ function codexMentionPopupIsOpen(lines) {
|
|
|
1390
1048
|
const tail = lines.slice(-20).join("\n");
|
|
1391
1049
|
return MENTION_POPUP_PATTERNS.some((p) => p.test(tail));
|
|
1392
1050
|
}
|
|
1393
|
-
function
|
|
1051
|
+
function defaultUndeliveredWarning(text) {
|
|
1394
1052
|
const preview = text.replace(/\s+/g, " ").slice(0, 80);
|
|
1395
1053
|
process.stderr.write(`apiary: message NOT delivered to the agent \u2014 still in its composer: ${preview}
|
|
1396
1054
|
`);
|
|
@@ -1412,7 +1070,7 @@ var CodexTmuxBridge = class {
|
|
|
1412
1070
|
this.pasteDelayMs = opts?.pasteDelayMs ?? 150;
|
|
1413
1071
|
this.keystrokeDelayMs = opts?.keystrokeDelayMs ?? 50;
|
|
1414
1072
|
this.resolveThreadId = opts?.resolveThreadId;
|
|
1415
|
-
this.onUndelivered = opts?.onUndelivered ??
|
|
1073
|
+
this.onUndelivered = opts?.onUndelivered ?? defaultUndeliveredWarning;
|
|
1416
1074
|
}
|
|
1417
1075
|
/**
|
|
1418
1076
|
* The Codex session id, resolved once and remembered.
|
|
@@ -1620,7 +1278,7 @@ var CodexTmuxBridge = class {
|
|
|
1620
1278
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1621
1279
|
}
|
|
1622
1280
|
};
|
|
1623
|
-
function
|
|
1281
|
+
function matchesAny(text, patterns) {
|
|
1624
1282
|
const haystack = text.toLowerCase();
|
|
1625
1283
|
for (const pattern of patterns) {
|
|
1626
1284
|
if (typeof pattern === "string") {
|
|
@@ -1635,13 +1293,13 @@ function detectCodexStateFromLines(lines) {
|
|
|
1635
1293
|
if (lines.length === 0) return "unknown";
|
|
1636
1294
|
const tail = lines.slice(-20);
|
|
1637
1295
|
const tailText = tail.join("\n");
|
|
1638
|
-
if (
|
|
1639
|
-
if (
|
|
1296
|
+
if (matchesAny(tailText, BLOCKED_PATTERNS)) return "blocked";
|
|
1297
|
+
if (matchesAny(tailText, APPROVAL_PATTERNS)) return "approval";
|
|
1640
1298
|
for (const pattern of STREAMING_PATTERNS) {
|
|
1641
1299
|
if (pattern.test(tailText)) return "streaming";
|
|
1642
1300
|
}
|
|
1643
1301
|
const lastFew = tail.slice(-5).join("");
|
|
1644
|
-
for (const ch of
|
|
1302
|
+
for (const ch of SPINNER_CHARS) {
|
|
1645
1303
|
if (lastFew.includes(ch)) return "streaming";
|
|
1646
1304
|
}
|
|
1647
1305
|
const nonEmpty = tail.filter((l) => l.trim().length > 0);
|
|
@@ -2155,7 +1813,7 @@ async function main() {
|
|
|
2155
1813
|
const all = rest.includes("--all");
|
|
2156
1814
|
const positional = rest.find((a) => !a.startsWith("--"));
|
|
2157
1815
|
const name = getFlag("name", rest) ?? positional;
|
|
2158
|
-
const { roomStop } = await import("../room-
|
|
1816
|
+
const { roomStop } = await import("../room-XC546NTX.js");
|
|
2159
1817
|
if (all) {
|
|
2160
1818
|
for (const r of listRoomSessions()) {
|
|
2161
1819
|
try {
|
|
@@ -2229,7 +1887,7 @@ async function main() {
|
|
|
2229
1887
|
if (sub === "create") {
|
|
2230
1888
|
const portStr = getFlag("port", roomArgs);
|
|
2231
1889
|
const port = portStr ? parseInt(portStr, 10) : void 0;
|
|
2232
|
-
const { roomCreate } = await import("../room-
|
|
1890
|
+
const { roomCreate } = await import("../room-XC546NTX.js");
|
|
2233
1891
|
await roomCreate({
|
|
2234
1892
|
room: getFlag("room", roomArgs) ?? roomArgs.find((a) => !a.startsWith("--")),
|
|
2235
1893
|
port,
|
|
@@ -2246,12 +1904,12 @@ async function main() {
|
|
|
2246
1904
|
console.error("Usage: apiary room resume <name>");
|
|
2247
1905
|
process.exit(1);
|
|
2248
1906
|
}
|
|
2249
|
-
const { roomResume } = await import("../room-
|
|
1907
|
+
const { roomResume } = await import("../room-XC546NTX.js");
|
|
2250
1908
|
await roomResume(name);
|
|
2251
1909
|
return;
|
|
2252
1910
|
}
|
|
2253
1911
|
if (sub === "list" || sub === "ls") {
|
|
2254
|
-
const { roomList } = await import("../room-
|
|
1912
|
+
const { roomList } = await import("../room-XC546NTX.js");
|
|
2255
1913
|
roomList();
|
|
2256
1914
|
return;
|
|
2257
1915
|
}
|
|
@@ -2261,7 +1919,7 @@ async function main() {
|
|
|
2261
1919
|
console.error("Usage: apiary room stop <name>");
|
|
2262
1920
|
process.exit(1);
|
|
2263
1921
|
}
|
|
2264
|
-
const { roomStop } = await import("../room-
|
|
1922
|
+
const { roomStop } = await import("../room-XC546NTX.js");
|
|
2265
1923
|
await roomStop(name);
|
|
2266
1924
|
return;
|
|
2267
1925
|
}
|
|
@@ -2276,7 +1934,7 @@ async function main() {
|
|
|
2276
1934
|
console.error(`Invalid port: ${portStr}`);
|
|
2277
1935
|
process.exit(1);
|
|
2278
1936
|
}
|
|
2279
|
-
const { spawnDaemonServer, isDaemonResult } = await import("../room-
|
|
1937
|
+
const { spawnDaemonServer, isDaemonResult } = await import("../room-XC546NTX.js");
|
|
2280
1938
|
const daemonRes = await spawnDaemonServer({
|
|
2281
1939
|
room: roomName,
|
|
2282
1940
|
port,
|
|
@@ -2318,11 +1976,11 @@ async function main() {
|
|
|
2318
1976
|
const name = getFlag("name", restArgs) ?? positionalName;
|
|
2319
1977
|
const pendingUrls = [...joinUrls];
|
|
2320
1978
|
if (name) {
|
|
2321
|
-
const { consumeInvite } = await import("../invites-
|
|
1979
|
+
const { consumeInvite } = await import("../invites-ID5SRM3D.js");
|
|
2322
1980
|
const inviteUrl = consumeInvite(name);
|
|
2323
1981
|
if (inviteUrl) pendingUrls.push(inviteUrl);
|
|
2324
1982
|
}
|
|
2325
|
-
const { runMcpServer } = await import("../run-
|
|
1983
|
+
const { runMcpServer } = await import("../run-6JRFWMAQ.js");
|
|
2326
1984
|
await runMcpServer({
|
|
2327
1985
|
joinUrls: pendingUrls.length > 0 ? pendingUrls : void 0,
|
|
2328
1986
|
name,
|