@armstrongnate/april 0.1.3 → 0.1.4
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/dist/session/herdr.js +90 -20
- package/package.json +1 -1
package/dist/session/herdr.js
CHANGED
|
@@ -35,11 +35,45 @@ async function waitForAgentReady(paneId, attempts = 10, intervalMs = 1000) {
|
|
|
35
35
|
log.warn(`Agent in pane ${paneId} never reported a known status; proceeding anyway`);
|
|
36
36
|
}
|
|
37
37
|
const SUBMIT_SETTLE_MS = 1000;
|
|
38
|
+
const INJECT_ATTEMPTS = 4;
|
|
38
39
|
/** Read a pane's current agent status (undefined if unavailable). */
|
|
39
40
|
async function paneStatus(paneId) {
|
|
40
41
|
const res = await herdrRequest("pane.get", { pane_id: paneId });
|
|
41
42
|
return res.pane?.agent_status;
|
|
42
43
|
}
|
|
44
|
+
/** A status that means the agent left the input box and picked up the prompt. */
|
|
45
|
+
function isAccepted(status) {
|
|
46
|
+
return status !== undefined && status !== "idle" && status !== "unknown";
|
|
47
|
+
}
|
|
48
|
+
const normalizeWhitespace = (text) => text.replace(/\s+/g, " ");
|
|
49
|
+
/**
|
|
50
|
+
* A short, wrap-safe slice of the prompt to look for in the pane's input box.
|
|
51
|
+
* Kept brief (and whitespace-normalized) so terminal wrapping or the `❯ `
|
|
52
|
+
* prefix don't break the substring match against what pane.read returns.
|
|
53
|
+
*/
|
|
54
|
+
function promptSignature(prompt) {
|
|
55
|
+
return normalizeWhitespace(prompt).trim().slice(0, 24);
|
|
56
|
+
}
|
|
57
|
+
/** Visible (on-screen) text of a pane; "" if it can't be read. */
|
|
58
|
+
async function readVisible(paneId) {
|
|
59
|
+
try {
|
|
60
|
+
const res = await herdrRequest("pane.read", {
|
|
61
|
+
pane_id: paneId,
|
|
62
|
+
source: "visible",
|
|
63
|
+
});
|
|
64
|
+
return res.read?.text ?? "";
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// transient; treat as "can't confirm"
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Whether our prompt text currently appears in the pane (i.e. the input box). */
|
|
72
|
+
async function promptIsVisible(paneId, signature) {
|
|
73
|
+
if (!signature)
|
|
74
|
+
return true;
|
|
75
|
+
return normalizeWhitespace(await readVisible(paneId)).includes(signature);
|
|
76
|
+
}
|
|
43
77
|
/**
|
|
44
78
|
* Wait for the agent to leave `idle` — confirmation that the Enter submitted
|
|
45
79
|
* and the agent picked up the prompt. `working`/`blocked`/`done` all mean the
|
|
@@ -48,8 +82,7 @@ async function paneStatus(paneId) {
|
|
|
48
82
|
async function confirmSubmitted(paneId, attempts = 6, intervalMs = 500) {
|
|
49
83
|
for (let i = 0; i < attempts; i++) {
|
|
50
84
|
try {
|
|
51
|
-
|
|
52
|
-
if (status && status !== "idle" && status !== "unknown")
|
|
85
|
+
if (isAccepted(await paneStatus(paneId)))
|
|
53
86
|
return true;
|
|
54
87
|
}
|
|
55
88
|
catch {
|
|
@@ -59,21 +92,53 @@ async function confirmSubmitted(paneId, attempts = 6, intervalMs = 500) {
|
|
|
59
92
|
}
|
|
60
93
|
return false;
|
|
61
94
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Inject the prompt and verify the agent actually picked it up, retrying around
|
|
97
|
+
* the two misfires documented in the header. Returns the agent status when we
|
|
98
|
+
* stop so the caller can log the outcome.
|
|
99
|
+
*
|
|
100
|
+
* The loop is self-correcting: each pass first checks whether the agent is
|
|
101
|
+
* already working (a prior Enter landed even if its confirm timed out — never
|
|
102
|
+
* retype into a running agent), then ensures the text is in the box (typing it
|
|
103
|
+
* only if absent, so retries can't duplicate it), then submits and confirms.
|
|
104
|
+
*/
|
|
105
|
+
async function injectPrompt(paneId, prompt) {
|
|
106
|
+
const signature = promptSignature(prompt);
|
|
107
|
+
for (let attempt = 1; attempt <= INJECT_ATTEMPTS; attempt++) {
|
|
108
|
+
const status = await paneStatus(paneId).catch(() => undefined);
|
|
109
|
+
if (isAccepted(status)) {
|
|
110
|
+
if (attempt > 1)
|
|
111
|
+
log.info(`Agent in pane ${paneId} accepted the prompt (status: ${status})`);
|
|
112
|
+
return status;
|
|
113
|
+
}
|
|
114
|
+
// 1. Ensure our text is in the input box — type it only if it isn't there,
|
|
115
|
+
// so a retry after a failed submit doesn't append a second copy.
|
|
116
|
+
if (!(await promptIsVisible(paneId, signature))) {
|
|
117
|
+
await herdrRequest("pane.send_text", { pane_id: paneId, text: prompt });
|
|
118
|
+
await delay(SUBMIT_SETTLE_MS);
|
|
119
|
+
}
|
|
120
|
+
// 2. Still not there → a Claude startup screen ate the burst. Dismiss it
|
|
121
|
+
// with Enter and loop to retype. (That Enter may instead have submitted
|
|
122
|
+
// real text on a flaky read, so re-check status before retrying.)
|
|
123
|
+
if (!(await promptIsVisible(paneId, signature))) {
|
|
124
|
+
log.warn(`Prompt text not visible in pane ${paneId} input (attempt ${attempt}/${INJECT_ATTEMPTS}); dismissing startup screen and retrying`);
|
|
125
|
+
await herdrRequest("pane.send_keys", { pane_id: paneId, keys: ["Enter"] });
|
|
126
|
+
await delay(SUBMIT_SETTLE_MS);
|
|
127
|
+
const dismissed = await paneStatus(paneId).catch(() => undefined);
|
|
128
|
+
if (isAccepted(dismissed))
|
|
129
|
+
return dismissed;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// 3. Text is in the box — submit it and confirm via agent_status.
|
|
133
|
+
await herdrRequest("pane.send_keys", { pane_id: paneId, keys: ["Enter"] });
|
|
134
|
+
if (await confirmSubmitted(paneId)) {
|
|
135
|
+
const accepted = await paneStatus(paneId).catch(() => undefined);
|
|
136
|
+
log.info(`Prompt submitted to pane ${paneId} on attempt ${attempt} (status: ${accepted ?? "unknown"})`);
|
|
137
|
+
return accepted;
|
|
138
|
+
}
|
|
139
|
+
log.warn(`Agent in pane ${paneId} still idle after Enter (attempt ${attempt}/${INJECT_ATTEMPTS}); will retry`);
|
|
140
|
+
}
|
|
141
|
+
return paneStatus(paneId).catch(() => undefined);
|
|
77
142
|
}
|
|
78
143
|
export const herdrBackend = {
|
|
79
144
|
async listSessions() {
|
|
@@ -128,10 +193,15 @@ export const herdrBackend = {
|
|
|
128
193
|
}
|
|
129
194
|
}
|
|
130
195
|
log.info(`Started agent in herdr workspace "${name}" (pane ${paneId})`);
|
|
131
|
-
// Wait until the agent is up, then inject the prompt.
|
|
196
|
+
// Wait until the agent is up, then inject the prompt and verify it landed.
|
|
132
197
|
await waitForAgentReady(paneId);
|
|
133
|
-
await
|
|
134
|
-
|
|
198
|
+
const status = await injectPrompt(paneId, prompt);
|
|
199
|
+
if (isAccepted(status)) {
|
|
200
|
+
log.info(`Prompt delivered to herdr workspace "${name}" — agent is ${status} (pane ${paneId})`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
log.error(`Prompt may NOT have been accepted in herdr workspace "${name}" — agent status "${status ?? "unavailable"}" after ${INJECT_ATTEMPTS} attempts (pane ${paneId}). The session likely needs manual attention.`);
|
|
204
|
+
}
|
|
135
205
|
},
|
|
136
206
|
async kill(name) {
|
|
137
207
|
const ws = await findByLabel(name);
|
package/package.json
CHANGED