@gorlitzer-labs/apiary 1.13.9 → 1.15.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 +17 -3
- package/dist/agent/index.d.ts +73 -0
- package/dist/agent/index.js +1 -1
- package/dist/{chunk-BCQQJR5E.js → chunk-3UQBSELW.js} +7 -178
- package/dist/chunk-3UQBSELW.js.map +1 -0
- package/dist/{chunk-7JXPJO3R.js → chunk-74XU7HHN.js} +6 -6
- package/dist/{chunk-4A7MREYX.js → chunk-N2LPKMPO.js} +185 -2
- package/dist/chunk-N2LPKMPO.js.map +1 -0
- package/dist/chunk-OEAHTZCA.js +994 -0
- package/dist/chunk-OEAHTZCA.js.map +1 -0
- package/dist/{chunk-MEWZT5KA.js → chunk-W4KWQKPP.js} +2 -2
- package/dist/{chunk-WJD5EKO6.js → chunk-Z4J6TRJY.js} +154 -41
- package/dist/chunk-Z4J6TRJY.js.map +1 -0
- package/dist/cli/index.js +39 -386
- package/dist/cli/index.js.map +1 -1
- package/dist/{invites-GRROUSCE.js → invites-SJ4ZVKVX.js} +4 -4
- package/dist/{join-H62VB4HU.js → join-5UFFMSLS.js} +4 -4
- package/dist/{room-ID4DQDPT.js → room-S6ULVAEH.js} +10 -10
- package/dist/run-5PSUQJF4.js +270 -0
- package/dist/run-5PSUQJF4.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-4A7MREYX.js.map +0 -1
- package/dist/chunk-BCQQJR5E.js.map +0 -1
- package/dist/chunk-WJD5EKO6.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-74XU7HHN.js.map} +0 -0
- /package/dist/{chunk-MEWZT5KA.js.map → chunk-W4KWQKPP.js.map} +0 -0
- /package/dist/{invites-GRROUSCE.js.map → invites-SJ4ZVKVX.js.map} +0 -0
- /package/dist/{join-H62VB4HU.js.map → join-5UFFMSLS.js.map} +0 -0
- /package/dist/{room-ID4DQDPT.js.map → room-S6ULVAEH.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-74XU7HHN.js";
|
|
5
5
|
import {
|
|
6
6
|
deliverInvite,
|
|
7
7
|
sameApiaryServer
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-W4KWQKPP.js";
|
|
9
9
|
import {
|
|
10
10
|
checkForUpdate,
|
|
11
11
|
printUpdateNotice
|
|
@@ -16,38 +16,40 @@ import {
|
|
|
16
16
|
isAgentAlive,
|
|
17
17
|
listAgentSessions,
|
|
18
18
|
listRoomSessions,
|
|
19
|
-
resetTerminal,
|
|
20
19
|
resolveBindAddress,
|
|
21
20
|
saveAgentSession,
|
|
22
|
-
serve
|
|
21
|
+
serve
|
|
22
|
+
} from "../chunk-3UQBSELW.js";
|
|
23
|
+
import "../chunk-PEB6HEFT.js";
|
|
24
|
+
import {
|
|
25
|
+
TmuxBridge,
|
|
26
|
+
buildRuntimeMcpOptions,
|
|
27
|
+
composerStillHolds,
|
|
28
|
+
trackRoomEvent
|
|
29
|
+
} from "../chunk-OEAHTZCA.js";
|
|
30
|
+
import {
|
|
31
|
+
EventProcessor,
|
|
32
|
+
RUNTIME_TOOL_NAMES,
|
|
33
|
+
SseMultiplexer,
|
|
34
|
+
createRuntimeMcpServer,
|
|
35
|
+
parseEngagementMode,
|
|
36
|
+
shouldReportActivity
|
|
37
|
+
} from "../chunk-Z4J6TRJY.js";
|
|
38
|
+
import {
|
|
39
|
+
buildShareUrl,
|
|
40
|
+
resetTerminal,
|
|
23
41
|
tmuxAttach,
|
|
24
42
|
tmuxAvailable,
|
|
25
43
|
tmuxCapturePane,
|
|
26
44
|
tmuxCreateSession,
|
|
27
45
|
tmuxInjectPaste,
|
|
28
|
-
tmuxInjectText,
|
|
29
46
|
tmuxKillSession,
|
|
30
47
|
tmuxPaneIsShell,
|
|
31
48
|
tmuxSendCommand,
|
|
32
49
|
tmuxSendEnter,
|
|
33
50
|
tmuxSendKey,
|
|
34
51
|
tmuxSessionExists
|
|
35
|
-
} from "../chunk-
|
|
36
|
-
import "../chunk-PEB6HEFT.js";
|
|
37
|
-
import {
|
|
38
|
-
buildRuntimeMcpOptions
|
|
39
|
-
} from "../chunk-XLUTCAY6.js";
|
|
40
|
-
import {
|
|
41
|
-
EventProcessor,
|
|
42
|
-
RUNTIME_TOOL_NAMES,
|
|
43
|
-
SseMultiplexer,
|
|
44
|
-
createRuntimeMcpServer,
|
|
45
|
-
parseEngagementMode,
|
|
46
|
-
shouldReportActivity
|
|
47
|
-
} from "../chunk-WJD5EKO6.js";
|
|
48
|
-
import {
|
|
49
|
-
buildShareUrl
|
|
50
|
-
} from "../chunk-4A7MREYX.js";
|
|
52
|
+
} from "../chunk-N2LPKMPO.js";
|
|
51
53
|
import {
|
|
52
54
|
randomName
|
|
53
55
|
} from "../chunk-D7DXZQWT.js";
|
|
@@ -61,349 +63,6 @@ import { writeFileSync as writeFileSync2, mkdtempSync, mkdirSync, rmSync, chmodS
|
|
|
61
63
|
import { join as join4 } from "path";
|
|
62
64
|
import { tmpdir, homedir as homedir3 } from "os";
|
|
63
65
|
|
|
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
66
|
// src/cli/runtime-setup.ts
|
|
408
67
|
async function setupAgentRuntime(options) {
|
|
409
68
|
const agentName = options.name ?? randomName();
|
|
@@ -433,13 +92,7 @@ async function setupAgentRuntime(options) {
|
|
|
433
92
|
if (!result.done) {
|
|
434
93
|
const { roomId, event } = result.value;
|
|
435
94
|
const jr = joinResults.find((j) => j.roomId === roomId);
|
|
436
|
-
if (jr)
|
|
437
|
-
if (event.type === "ParticipantJoined") {
|
|
438
|
-
jr.dataSource.addParticipant(event.participant);
|
|
439
|
-
} else if (event.type === "ParticipantLeft") {
|
|
440
|
-
jr.dataSource.removeParticipant(event.participant_id);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
95
|
+
if (jr) trackRoomEvent(jr, event);
|
|
443
96
|
}
|
|
444
97
|
return result;
|
|
445
98
|
}
|
|
@@ -1343,7 +996,7 @@ function queueCodexMessage(threadId, text, run = defaultRunner) {
|
|
|
1343
996
|
}
|
|
1344
997
|
|
|
1345
998
|
// src/cli/codex/tmux-bridge.ts
|
|
1346
|
-
var
|
|
999
|
+
var SPINNER_CHARS = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
|
|
1347
1000
|
var APPROVAL_PATTERNS = [
|
|
1348
1001
|
"Would you like to",
|
|
1349
1002
|
"needs your approval",
|
|
@@ -1390,7 +1043,7 @@ function codexMentionPopupIsOpen(lines) {
|
|
|
1390
1043
|
const tail = lines.slice(-20).join("\n");
|
|
1391
1044
|
return MENTION_POPUP_PATTERNS.some((p) => p.test(tail));
|
|
1392
1045
|
}
|
|
1393
|
-
function
|
|
1046
|
+
function defaultUndeliveredWarning(text) {
|
|
1394
1047
|
const preview = text.replace(/\s+/g, " ").slice(0, 80);
|
|
1395
1048
|
process.stderr.write(`apiary: message NOT delivered to the agent \u2014 still in its composer: ${preview}
|
|
1396
1049
|
`);
|
|
@@ -1412,7 +1065,7 @@ var CodexTmuxBridge = class {
|
|
|
1412
1065
|
this.pasteDelayMs = opts?.pasteDelayMs ?? 150;
|
|
1413
1066
|
this.keystrokeDelayMs = opts?.keystrokeDelayMs ?? 50;
|
|
1414
1067
|
this.resolveThreadId = opts?.resolveThreadId;
|
|
1415
|
-
this.onUndelivered = opts?.onUndelivered ??
|
|
1068
|
+
this.onUndelivered = opts?.onUndelivered ?? defaultUndeliveredWarning;
|
|
1416
1069
|
}
|
|
1417
1070
|
/**
|
|
1418
1071
|
* The Codex session id, resolved once and remembered.
|
|
@@ -1620,7 +1273,7 @@ var CodexTmuxBridge = class {
|
|
|
1620
1273
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1621
1274
|
}
|
|
1622
1275
|
};
|
|
1623
|
-
function
|
|
1276
|
+
function matchesAny(text, patterns) {
|
|
1624
1277
|
const haystack = text.toLowerCase();
|
|
1625
1278
|
for (const pattern of patterns) {
|
|
1626
1279
|
if (typeof pattern === "string") {
|
|
@@ -1635,13 +1288,13 @@ function detectCodexStateFromLines(lines) {
|
|
|
1635
1288
|
if (lines.length === 0) return "unknown";
|
|
1636
1289
|
const tail = lines.slice(-20);
|
|
1637
1290
|
const tailText = tail.join("\n");
|
|
1638
|
-
if (
|
|
1639
|
-
if (
|
|
1291
|
+
if (matchesAny(tailText, BLOCKED_PATTERNS)) return "blocked";
|
|
1292
|
+
if (matchesAny(tailText, APPROVAL_PATTERNS)) return "approval";
|
|
1640
1293
|
for (const pattern of STREAMING_PATTERNS) {
|
|
1641
1294
|
if (pattern.test(tailText)) return "streaming";
|
|
1642
1295
|
}
|
|
1643
1296
|
const lastFew = tail.slice(-5).join("");
|
|
1644
|
-
for (const ch of
|
|
1297
|
+
for (const ch of SPINNER_CHARS) {
|
|
1645
1298
|
if (lastFew.includes(ch)) return "streaming";
|
|
1646
1299
|
}
|
|
1647
1300
|
const nonEmpty = tail.filter((l) => l.trim().length > 0);
|
|
@@ -2155,7 +1808,7 @@ async function main() {
|
|
|
2155
1808
|
const all = rest.includes("--all");
|
|
2156
1809
|
const positional = rest.find((a) => !a.startsWith("--"));
|
|
2157
1810
|
const name = getFlag("name", rest) ?? positional;
|
|
2158
|
-
const { roomStop } = await import("../room-
|
|
1811
|
+
const { roomStop } = await import("../room-S6ULVAEH.js");
|
|
2159
1812
|
if (all) {
|
|
2160
1813
|
for (const r of listRoomSessions()) {
|
|
2161
1814
|
try {
|
|
@@ -2229,7 +1882,7 @@ async function main() {
|
|
|
2229
1882
|
if (sub === "create") {
|
|
2230
1883
|
const portStr = getFlag("port", roomArgs);
|
|
2231
1884
|
const port = portStr ? parseInt(portStr, 10) : void 0;
|
|
2232
|
-
const { roomCreate } = await import("../room-
|
|
1885
|
+
const { roomCreate } = await import("../room-S6ULVAEH.js");
|
|
2233
1886
|
await roomCreate({
|
|
2234
1887
|
room: getFlag("room", roomArgs) ?? roomArgs.find((a) => !a.startsWith("--")),
|
|
2235
1888
|
port,
|
|
@@ -2246,12 +1899,12 @@ async function main() {
|
|
|
2246
1899
|
console.error("Usage: apiary room resume <name>");
|
|
2247
1900
|
process.exit(1);
|
|
2248
1901
|
}
|
|
2249
|
-
const { roomResume } = await import("../room-
|
|
1902
|
+
const { roomResume } = await import("../room-S6ULVAEH.js");
|
|
2250
1903
|
await roomResume(name);
|
|
2251
1904
|
return;
|
|
2252
1905
|
}
|
|
2253
1906
|
if (sub === "list" || sub === "ls") {
|
|
2254
|
-
const { roomList } = await import("../room-
|
|
1907
|
+
const { roomList } = await import("../room-S6ULVAEH.js");
|
|
2255
1908
|
roomList();
|
|
2256
1909
|
return;
|
|
2257
1910
|
}
|
|
@@ -2261,7 +1914,7 @@ async function main() {
|
|
|
2261
1914
|
console.error("Usage: apiary room stop <name>");
|
|
2262
1915
|
process.exit(1);
|
|
2263
1916
|
}
|
|
2264
|
-
const { roomStop } = await import("../room-
|
|
1917
|
+
const { roomStop } = await import("../room-S6ULVAEH.js");
|
|
2265
1918
|
await roomStop(name);
|
|
2266
1919
|
return;
|
|
2267
1920
|
}
|
|
@@ -2276,7 +1929,7 @@ async function main() {
|
|
|
2276
1929
|
console.error(`Invalid port: ${portStr}`);
|
|
2277
1930
|
process.exit(1);
|
|
2278
1931
|
}
|
|
2279
|
-
const { spawnDaemonServer, isDaemonResult } = await import("../room-
|
|
1932
|
+
const { spawnDaemonServer, isDaemonResult } = await import("../room-S6ULVAEH.js");
|
|
2280
1933
|
const daemonRes = await spawnDaemonServer({
|
|
2281
1934
|
room: roomName,
|
|
2282
1935
|
port,
|
|
@@ -2318,11 +1971,11 @@ async function main() {
|
|
|
2318
1971
|
const name = getFlag("name", restArgs) ?? positionalName;
|
|
2319
1972
|
const pendingUrls = [...joinUrls];
|
|
2320
1973
|
if (name) {
|
|
2321
|
-
const { consumeInvite } = await import("../invites-
|
|
1974
|
+
const { consumeInvite } = await import("../invites-SJ4ZVKVX.js");
|
|
2322
1975
|
const inviteUrl = consumeInvite(name);
|
|
2323
1976
|
if (inviteUrl) pendingUrls.push(inviteUrl);
|
|
2324
1977
|
}
|
|
2325
|
-
const { runMcpServer } = await import("../run-
|
|
1978
|
+
const { runMcpServer } = await import("../run-5PSUQJF4.js");
|
|
2326
1979
|
await runMcpServer({
|
|
2327
1980
|
joinUrls: pendingUrls.length > 0 ? pendingUrls : void 0,
|
|
2328
1981
|
name,
|