@testchimp/cli 0.1.49 → 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 +80 -28
- 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();
|
|
@@ -584,7 +582,7 @@ function startOpencodeSseRelay(attachUrl, callbacks) {
|
|
|
584
582
|
if (stopped || ac.signal.aborted)
|
|
585
583
|
return;
|
|
586
584
|
if (!connected && attempt === 0) {
|
|
587
|
-
console.error("ChimpHands OpenCode SSE
|
|
585
|
+
console.error("ChimpHands OpenCode SSE not connected yet — using completed-only --format json until SSE connects (retrying)");
|
|
588
586
|
}
|
|
589
587
|
attempt += 1;
|
|
590
588
|
const backoff = Math.min(30_000, 2_000 * attempt);
|
|
@@ -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);
|
|
@@ -1090,29 +1096,58 @@ export async function runChimphands(opts) {
|
|
|
1090
1096
|
const poster = new AgentEventPoster(backend, apiKey, sessionId);
|
|
1091
1097
|
poster.uiAttached = !!(boot.uiAttached ?? boot.ui_attached);
|
|
1092
1098
|
let stopLiveSse = null;
|
|
1099
|
+
/** Do not open localhost OpenCode /event until serve has been (re)started with config. */
|
|
1100
|
+
let opencodeHttpReady = !attachUrl;
|
|
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
|
+
};
|
|
1093
1124
|
const syncLiveSse = (attached) => {
|
|
1094
1125
|
poster.uiAttached = attached;
|
|
1126
|
+
pendingUiAttached = attached;
|
|
1095
1127
|
if (!attachUrl)
|
|
1096
1128
|
return;
|
|
1097
|
-
if (
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
...opts,
|
|
1108
|
-
opencodeSessionId: opts?.opencodeSessionId || opencodeSessionId,
|
|
1109
|
-
});
|
|
1110
|
-
},
|
|
1111
|
-
onWorkingBranch: noteWorkingBranchPlaceholder,
|
|
1112
|
-
});
|
|
1129
|
+
if (!opencodeHttpReady) {
|
|
1130
|
+
if (attached) {
|
|
1131
|
+
console.error("ChimpHands UI attached — deferring OpenCode SSE until serve is ready");
|
|
1132
|
+
}
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (attached) {
|
|
1136
|
+
consecutiveUiDetached = 0;
|
|
1137
|
+
startLiveSseIfNeeded("UI attached — starting OpenCode SSE fanout");
|
|
1138
|
+
return;
|
|
1113
1139
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
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`);
|
|
1116
1151
|
stopLiveSse();
|
|
1117
1152
|
stopLiveSse = null;
|
|
1118
1153
|
}
|
|
@@ -1167,7 +1202,10 @@ export async function runChimphands(opts) {
|
|
|
1167
1202
|
console.error(`ChimpHands OpenCode attach: ${attachUrl}`);
|
|
1168
1203
|
// Serve must load opencode.json (provider + default_agent). Workflow often starts
|
|
1169
1204
|
// serve before this file exists; restart so attach mode can omit --agent safely.
|
|
1205
|
+
// Heartbeat may have already reported ui_attached — wait until after restart to open SSE.
|
|
1170
1206
|
await restartLocalOpencodeServer(attachUrl);
|
|
1207
|
+
opencodeHttpReady = true;
|
|
1208
|
+
syncLiveSse(pendingUiAttached);
|
|
1171
1209
|
}
|
|
1172
1210
|
let opencodeSessionId = bootStr(boot, "opencode_session_id", "opencodeSessionId") || undefined;
|
|
1173
1211
|
const conversationSummary = bootStr(boot, "conversation_summary", "conversationSummary");
|
|
@@ -1307,10 +1345,22 @@ export async function runChimphands(opts) {
|
|
|
1307
1345
|
try {
|
|
1308
1346
|
poster.fireAndForget(ROLE_STATUS, "Agent ready", { status: STATUS_RUNNING });
|
|
1309
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_…).
|
|
1310
1351
|
const pending = boot.pending_user_messages || boot.pendingUserMessages || [];
|
|
1352
|
+
const initialNorm = normalizeUserMessage(prompt);
|
|
1311
1353
|
for (const m of pending) {
|
|
1312
|
-
|
|
1313
|
-
|
|
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 });
|
|
1314
1364
|
}
|
|
1315
1365
|
const waitForNextPrompt = () => new Promise((resolve) => {
|
|
1316
1366
|
let lastPollAt = 0;
|
|
@@ -1440,7 +1490,9 @@ function startRuntimeHeartbeat(backend, apiKey, runtimeId, onUiAttached) {
|
|
|
1440
1490
|
try {
|
|
1441
1491
|
const data = JSON.parse(text);
|
|
1442
1492
|
const attached = !!(data.uiAttached ?? data.ui_attached);
|
|
1443
|
-
|
|
1493
|
+
// Always notify on false so syncLiveSse can accumulate detach hysteresis;
|
|
1494
|
+
// still skip duplicate true→true noise.
|
|
1495
|
+
if (attached !== lastAttached || !attached) {
|
|
1444
1496
|
lastAttached = attached;
|
|
1445
1497
|
onUiAttached?.(attached);
|
|
1446
1498
|
}
|
package/package.json
CHANGED