@testchimp/cli 0.1.50 → 0.1.51
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/chimphands/run.js +66 -27
- package/package.json +1 -1
package/dist/chimphands/run.js
CHANGED
|
@@ -137,10 +137,8 @@ class AgentEventPoster {
|
|
|
137
137
|
if (opts?.pullRequestUrl)
|
|
138
138
|
body.pullRequestUrl = opts.pullRequestUrl;
|
|
139
139
|
const streamRole = isStreamFanoutRole(role);
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
return this.chain;
|
|
143
|
-
}
|
|
140
|
+
// Never drop liveStream tokens: ephemeral when UI is on this replica path,
|
|
141
|
+
// otherwise durable post_agent_event (cross-replica / detached safe).
|
|
144
142
|
this.chain = this.chain.then(async () => {
|
|
145
143
|
if (opts?.throttle || (streamRole && opts?.liveStream)) {
|
|
146
144
|
const now = Date.now();
|
|
@@ -863,7 +861,7 @@ function runOpencode(prompt, model, childEnv, opencodeSessionId, callbacks, atta
|
|
|
863
861
|
callbacks.postEvent(ROLE_STATUS, "Agent is still working… (waiting for OpenCode output)", {
|
|
864
862
|
status: STATUS_RUNNING,
|
|
865
863
|
});
|
|
866
|
-
},
|
|
864
|
+
}, 15_000);
|
|
867
865
|
const noteSessionId = (sessionId) => {
|
|
868
866
|
const id = sessionId?.trim();
|
|
869
867
|
if (!id || id === activeSessionId)
|
|
@@ -887,7 +885,15 @@ function runOpencode(prompt, model, childEnv, opencodeSessionId, callbacks, atta
|
|
|
887
885
|
fatalError = fatal;
|
|
888
886
|
return;
|
|
889
887
|
}
|
|
890
|
-
|
|
888
|
+
let parsed;
|
|
889
|
+
try {
|
|
890
|
+
parsed = JSON.parse(line);
|
|
891
|
+
}
|
|
892
|
+
catch {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
// Newer OpenCode --format json uses bus shape (message.part.updated); normalize first.
|
|
896
|
+
const ev = normalizeStdoutOpencodeEvent(parsed) || parseOpencodeEvent(line);
|
|
891
897
|
if (!ev?.type)
|
|
892
898
|
return;
|
|
893
899
|
noteSessionId(ev.sessionID);
|
|
@@ -1093,6 +1099,28 @@ export async function runChimphands(opts) {
|
|
|
1093
1099
|
/** Do not open localhost OpenCode /event until serve has been (re)started with config. */
|
|
1094
1100
|
let opencodeHttpReady = !attachUrl;
|
|
1095
1101
|
let pendingUiAttached = !!(boot.uiAttached ?? boot.ui_attached);
|
|
1102
|
+
/** Consecutive uiAttached=false heartbeats before stopping OpenCode SSE (multi-replica poison). */
|
|
1103
|
+
let consecutiveUiDetached = 0;
|
|
1104
|
+
const UI_DETACHED_STOP_AFTER = 3;
|
|
1105
|
+
const startLiveSseIfNeeded = (reason) => {
|
|
1106
|
+
if (!attachUrl || !opencodeHttpReady || stopLiveSse)
|
|
1107
|
+
return;
|
|
1108
|
+
console.error(`ChimpHands ${reason}`);
|
|
1109
|
+
stopLiveSse = startOpencodeSseRelay(attachUrl, {
|
|
1110
|
+
getActiveSessionId: () => opencodeSessionId,
|
|
1111
|
+
noteSessionId: (id) => {
|
|
1112
|
+
if (id?.trim())
|
|
1113
|
+
opencodeSessionId = id.trim();
|
|
1114
|
+
},
|
|
1115
|
+
postEvent: (role, content, opts) => {
|
|
1116
|
+
poster.fireAndForget(role, content, {
|
|
1117
|
+
...opts,
|
|
1118
|
+
opencodeSessionId: opts?.opencodeSessionId || opencodeSessionId,
|
|
1119
|
+
});
|
|
1120
|
+
},
|
|
1121
|
+
onWorkingBranch: noteWorkingBranchPlaceholder,
|
|
1122
|
+
});
|
|
1123
|
+
};
|
|
1096
1124
|
const syncLiveSse = (attached) => {
|
|
1097
1125
|
poster.uiAttached = attached;
|
|
1098
1126
|
pendingUiAttached = attached;
|
|
@@ -1104,25 +1132,22 @@ export async function runChimphands(opts) {
|
|
|
1104
1132
|
}
|
|
1105
1133
|
return;
|
|
1106
1134
|
}
|
|
1107
|
-
if (attached
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
noteSessionId: (id) => {
|
|
1112
|
-
if (id?.trim())
|
|
1113
|
-
opencodeSessionId = id.trim();
|
|
1114
|
-
},
|
|
1115
|
-
postEvent: (role, content, opts) => {
|
|
1116
|
-
poster.fireAndForget(role, content, {
|
|
1117
|
-
...opts,
|
|
1118
|
-
opencodeSessionId: opts?.opencodeSessionId || opencodeSessionId,
|
|
1119
|
-
});
|
|
1120
|
-
},
|
|
1121
|
-
onWorkingBranch: noteWorkingBranchPlaceholder,
|
|
1122
|
-
});
|
|
1135
|
+
if (attached) {
|
|
1136
|
+
consecutiveUiDetached = 0;
|
|
1137
|
+
startLiveSseIfNeeded("UI attached — starting OpenCode SSE fanout");
|
|
1138
|
+
return;
|
|
1123
1139
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1140
|
+
// Keep SSE running so live tokens can durable-post even when heartbeat
|
|
1141
|
+
// briefly reports false (UI EventSource on another FS replica).
|
|
1142
|
+
consecutiveUiDetached += 1;
|
|
1143
|
+
if (consecutiveUiDetached < UI_DETACHED_STOP_AFTER) {
|
|
1144
|
+
if (!stopLiveSse) {
|
|
1145
|
+
startLiveSseIfNeeded("starting OpenCode SSE fanout (durable until UI attaches)");
|
|
1146
|
+
}
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (stopLiveSse) {
|
|
1150
|
+
console.error(`ChimpHands UI detached for ${consecutiveUiDetached} heartbeats — stopping OpenCode SSE fanout`);
|
|
1126
1151
|
stopLiveSse();
|
|
1127
1152
|
stopLiveSse = null;
|
|
1128
1153
|
}
|
|
@@ -1320,10 +1345,22 @@ export async function runChimphands(opts) {
|
|
|
1320
1345
|
try {
|
|
1321
1346
|
poster.fireAndForget(ROLE_STATUS, "Agent ready", { status: STATUS_RUNNING });
|
|
1322
1347
|
let prompt = normalizeUserMessage(promptInput || bootStr(boot, "initial_prompt", "initialPrompt"));
|
|
1348
|
+
// Bootstrap sets initialPrompt from the last pending user message AND returns
|
|
1349
|
+
// pendingUserMessages — enqueue only messages that are not the initial prompt
|
|
1350
|
+
// (otherwise the same turn runs twice: session=new then session=ses_…).
|
|
1323
1351
|
const pending = boot.pending_user_messages || boot.pendingUserMessages || [];
|
|
1352
|
+
const initialNorm = normalizeUserMessage(prompt);
|
|
1324
1353
|
for (const m of pending) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1354
|
+
const content = normalizeUserMessage(m?.content || "");
|
|
1355
|
+
if (!content)
|
|
1356
|
+
continue;
|
|
1357
|
+
if (initialNorm && content === initialNorm)
|
|
1358
|
+
continue;
|
|
1359
|
+
const id = ("id" in m && m.id) ||
|
|
1360
|
+
("message_id" in m && m.message_id) ||
|
|
1361
|
+
("messageId" in m && m.messageId) ||
|
|
1362
|
+
undefined;
|
|
1363
|
+
enqueueUserMessage({ id: id || undefined, content });
|
|
1327
1364
|
}
|
|
1328
1365
|
const waitForNextPrompt = () => new Promise((resolve) => {
|
|
1329
1366
|
let lastPollAt = 0;
|
|
@@ -1453,7 +1490,9 @@ function startRuntimeHeartbeat(backend, apiKey, runtimeId, onUiAttached) {
|
|
|
1453
1490
|
try {
|
|
1454
1491
|
const data = JSON.parse(text);
|
|
1455
1492
|
const attached = !!(data.uiAttached ?? data.ui_attached);
|
|
1456
|
-
|
|
1493
|
+
// Always notify on false so syncLiveSse can accumulate detach hysteresis;
|
|
1494
|
+
// still skip duplicate true→true noise.
|
|
1495
|
+
if (attached !== lastAttached || !attached) {
|
|
1457
1496
|
lastAttached = attached;
|
|
1458
1497
|
onUiAttached?.(attached);
|
|
1459
1498
|
}
|
package/package.json
CHANGED