@ordewell/core 0.4.17 → 0.4.19
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/{ITerminalRunner-CbLHKXT5.d.ts → ITerminalRunner-BVU7TfT3.d.ts} +8 -0
- package/dist/{ITerminalRunner-E3-6QNUS.d.mts → ITerminalRunner-C-HlFxuS.d.mts} +8 -0
- package/dist/index.d.mts +37 -3
- package/dist/index.d.ts +37 -3
- package/dist/index.js +112 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -18
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -6281,19 +6281,29 @@ var VerdictEngine = class {
|
|
|
6281
6281
|
for (const l of this.idleListeners) l(taskId, null);
|
|
6282
6282
|
}
|
|
6283
6283
|
}
|
|
6284
|
+
/**
|
|
6285
|
+
* Submit a synchronized resume token to the paused session. An interactive
|
|
6286
|
+
* TUI only accepts the Enter keystroke (`\r`) — a `\n` types the token into
|
|
6287
|
+
* its composer without sending it, leaving the agent paused until a human
|
|
6288
|
+
* presses Enter. A line-oriented piped session has no composer; it reads a
|
|
6289
|
+
* `\n`-terminated line, and the leading newline flushes a partial line.
|
|
6290
|
+
*/
|
|
6291
|
+
resumeToken(session, line) {
|
|
6292
|
+
return session.interactive ? `${line}\r` : `
|
|
6293
|
+
${line}
|
|
6294
|
+
`;
|
|
6295
|
+
}
|
|
6284
6296
|
approveCheckpoint(taskId) {
|
|
6285
6297
|
const session = this.pausedSessions.get(taskId);
|
|
6286
6298
|
if (session) {
|
|
6287
|
-
session.write("
|
|
6299
|
+
session.write(this.resumeToken(session, "ORDEWELL_CONTINUE"));
|
|
6288
6300
|
this.pausedSessions.delete(taskId);
|
|
6289
6301
|
}
|
|
6290
6302
|
}
|
|
6291
6303
|
rejectCheckpoint(taskId, reason) {
|
|
6292
6304
|
const session = this.pausedSessions.get(taskId);
|
|
6293
6305
|
if (session) {
|
|
6294
|
-
session.write(`
|
|
6295
|
-
ORDEWELL_REJECT: ${reason}
|
|
6296
|
-
`);
|
|
6306
|
+
session.write(this.resumeToken(session, `ORDEWELL_REJECT: ${reason}`));
|
|
6297
6307
|
this.pausedSessions.delete(taskId);
|
|
6298
6308
|
}
|
|
6299
6309
|
}
|
|
@@ -9695,6 +9705,8 @@ ${item.text}` : item.text });
|
|
|
9695
9705
|
var SERVER_READY_TIMEOUT_MS = 3e4;
|
|
9696
9706
|
var STDERR_TAIL_CHARS2 = 4e3;
|
|
9697
9707
|
var STREAM_CONNECT_TIMEOUT_MS = 5e3;
|
|
9708
|
+
var RECOVERY_POLL_INTERVAL_MS = 2e3;
|
|
9709
|
+
var RECOVERY_TIMEOUT_MS = 9e5;
|
|
9698
9710
|
var DISABLED_TOOLS = {
|
|
9699
9711
|
question: false,
|
|
9700
9712
|
edit: false,
|
|
@@ -9707,6 +9719,33 @@ function splitModelId(id) {
|
|
|
9707
9719
|
if (slash <= 0 || slash === id.length - 1) return null;
|
|
9708
9720
|
return { providerID: id.slice(0, slash), modelID: id.slice(slash + 1) };
|
|
9709
9721
|
}
|
|
9722
|
+
function describeError(err) {
|
|
9723
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9724
|
+
const lines = [];
|
|
9725
|
+
let current = err;
|
|
9726
|
+
while (current && !visited.has(current)) {
|
|
9727
|
+
visited.add(current);
|
|
9728
|
+
if (!(current instanceof Error)) {
|
|
9729
|
+
lines.push(String(current));
|
|
9730
|
+
break;
|
|
9731
|
+
}
|
|
9732
|
+
const code = current.code;
|
|
9733
|
+
const suffix = typeof code === "string" && !current.message.includes(code) ? ` (${code})` : "";
|
|
9734
|
+
lines.push(`${current.message}${suffix}`);
|
|
9735
|
+
current = current.cause;
|
|
9736
|
+
}
|
|
9737
|
+
return lines.join(": ");
|
|
9738
|
+
}
|
|
9739
|
+
function delay(ms, signal) {
|
|
9740
|
+
return new Promise((resolve4) => {
|
|
9741
|
+
const timer = setTimeout(resolve4, ms);
|
|
9742
|
+
timer.unref?.();
|
|
9743
|
+
signal?.addEventListener("abort", () => {
|
|
9744
|
+
clearTimeout(timer);
|
|
9745
|
+
resolve4();
|
|
9746
|
+
}, { once: true });
|
|
9747
|
+
});
|
|
9748
|
+
}
|
|
9710
9749
|
var OpenCodeAdapter = class {
|
|
9711
9750
|
constructor(deps) {
|
|
9712
9751
|
this.deps = deps;
|
|
@@ -9722,6 +9761,8 @@ var OpenCodeAdapter = class {
|
|
|
9722
9761
|
opts = null;
|
|
9723
9762
|
/** Whether this turn has already emitted reply text — see {@link emitPart}. */
|
|
9724
9763
|
turnHasText = false;
|
|
9764
|
+
/** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
|
|
9765
|
+
lastAssistantId = null;
|
|
9725
9766
|
async start(opts) {
|
|
9726
9767
|
this.opts = opts;
|
|
9727
9768
|
assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
|
|
@@ -9775,6 +9816,8 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
9775
9816
|
const existing = await this.json("GET", `/session/${opts.resumeSessionId}`).catch(() => null);
|
|
9776
9817
|
if (existing?.id) {
|
|
9777
9818
|
this.sessionId = existing.id;
|
|
9819
|
+
const history = await this.json("GET", `/session/${existing.id}/message`).catch(() => null);
|
|
9820
|
+
this.lastAssistantId = this.newestAssistantId(history);
|
|
9778
9821
|
return;
|
|
9779
9822
|
}
|
|
9780
9823
|
}
|
|
@@ -9825,29 +9868,76 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
9825
9868
|
this.dispose();
|
|
9826
9869
|
return;
|
|
9827
9870
|
}
|
|
9828
|
-
|
|
9829
|
-
if (failure) {
|
|
9830
|
-
onEvent({ type: "error", message: failure });
|
|
9831
|
-
return;
|
|
9832
|
-
}
|
|
9833
|
-
const assistantId = reply?.info?.id;
|
|
9834
|
-
for (const part of reply?.parts ?? []) {
|
|
9835
|
-
if (part.type !== "tool" && assistantId && part.messageID !== assistantId) continue;
|
|
9836
|
-
this.emitPart(part, seen, onEvent);
|
|
9837
|
-
}
|
|
9838
|
-
onEvent({ type: "turn_end" });
|
|
9871
|
+
this.settle(reply, seen, onEvent);
|
|
9839
9872
|
} catch (err) {
|
|
9840
9873
|
if (signal?.aborted) {
|
|
9841
9874
|
this.dispose();
|
|
9842
9875
|
return;
|
|
9843
9876
|
}
|
|
9844
|
-
|
|
9877
|
+
const recovered = this.exited ? null : await this.recoverReply(signal, onActivity);
|
|
9878
|
+
if (recovered) {
|
|
9879
|
+
this.settle(recovered, seen, onEvent);
|
|
9880
|
+
return;
|
|
9881
|
+
}
|
|
9882
|
+
onEvent({ type: "error", message: `The OpenCode planner turn failed: ${describeError(err)}` });
|
|
9845
9883
|
} finally {
|
|
9846
9884
|
streamAbort.abort();
|
|
9847
9885
|
await live.catch(() => {
|
|
9848
9886
|
});
|
|
9849
9887
|
}
|
|
9850
9888
|
}
|
|
9889
|
+
/**
|
|
9890
|
+
* Turn one settled assistant message into events. The settled response is
|
|
9891
|
+
* authoritative: it names the assistant message, so its parts are the ones
|
|
9892
|
+
* that make up the reply. Tool parts already seen live are deduplicated by
|
|
9893
|
+
* call id; anything the stream missed (including a stream that never
|
|
9894
|
+
* connected) arrives here.
|
|
9895
|
+
*/
|
|
9896
|
+
settle(reply, seen, onEvent) {
|
|
9897
|
+
const failure = typeof reply?.error === "string" ? reply.error : reply?.error?.message ?? reply?.info?.error?.data?.message;
|
|
9898
|
+
if (failure) {
|
|
9899
|
+
onEvent({ type: "error", message: failure });
|
|
9900
|
+
return;
|
|
9901
|
+
}
|
|
9902
|
+
const assistantId = reply?.info?.id;
|
|
9903
|
+
for (const part of reply?.parts ?? []) {
|
|
9904
|
+
if (part.type !== "tool" && assistantId && part.messageID !== assistantId) continue;
|
|
9905
|
+
this.emitPart(part, seen, onEvent);
|
|
9906
|
+
}
|
|
9907
|
+
if (assistantId) this.lastAssistantId = assistantId;
|
|
9908
|
+
onEvent({ type: "turn_end" });
|
|
9909
|
+
}
|
|
9910
|
+
/**
|
|
9911
|
+
* Poll the session for this turn's assistant message after the POST's socket
|
|
9912
|
+
* died under it. Node's global `fetch` is undici, which aborts a request
|
|
9913
|
+
* whose response headers have not arrived within 300s — and OpenCode sends
|
|
9914
|
+
* none until the turn is done, so any turn past five minutes fails as
|
|
9915
|
+
* `TypeError: fetch failed` while the server is still working. The message
|
|
9916
|
+
* exists server-side either way, so it is waited for and read back.
|
|
9917
|
+
*
|
|
9918
|
+
* A message that exists but has not completed is progress, not an answer:
|
|
9919
|
+
* it refreshes the watchdog and the poll continues.
|
|
9920
|
+
*/
|
|
9921
|
+
async recoverReply(signal, onActivity) {
|
|
9922
|
+
const deadline = Date.now() + RECOVERY_TIMEOUT_MS;
|
|
9923
|
+
for (; ; ) {
|
|
9924
|
+
if (signal?.aborted || this.exited || this.disposed || !this.sessionId) return null;
|
|
9925
|
+
const messages = await this.json("GET", `/session/${this.sessionId}/message`).catch(() => null);
|
|
9926
|
+
const pending = Array.isArray(messages) ? [...messages].reverse().find((m) => m.info?.role === "assistant" && m.info.id && m.info.id !== this.lastAssistantId) : void 0;
|
|
9927
|
+
if (pending?.info?.time?.completed || pending?.info?.error) return pending;
|
|
9928
|
+
if (pending) onActivity?.();
|
|
9929
|
+
if (Date.now() >= deadline) return null;
|
|
9930
|
+
await delay(RECOVERY_POLL_INTERVAL_MS, signal);
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
newestAssistantId(messages) {
|
|
9934
|
+
if (!Array.isArray(messages)) return null;
|
|
9935
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9936
|
+
const info = messages[i]?.info;
|
|
9937
|
+
if (info?.role === "assistant" && info.id) return info.id;
|
|
9938
|
+
}
|
|
9939
|
+
return null;
|
|
9940
|
+
}
|
|
9851
9941
|
/**
|
|
9852
9942
|
* Emit one message part, once. OpenCode reports a tool part repeatedly as it
|
|
9853
9943
|
* moves through pending → running → completed, so parts are keyed by id and
|
|
@@ -12604,11 +12694,13 @@ function defaultHasScriptCmd() {
|
|
|
12604
12694
|
}
|
|
12605
12695
|
}
|
|
12606
12696
|
var HeadlessSession = class extends AbstractTerminalSession {
|
|
12607
|
-
constructor(id, taskId, spawnImpl) {
|
|
12697
|
+
constructor(id, taskId, spawnImpl, interactive = false) {
|
|
12608
12698
|
super(id, taskId);
|
|
12609
12699
|
this.spawnImpl = spawnImpl;
|
|
12700
|
+
this.interactive = interactive;
|
|
12610
12701
|
}
|
|
12611
12702
|
spawnImpl;
|
|
12703
|
+
interactive;
|
|
12612
12704
|
process = null;
|
|
12613
12705
|
outputBuffer = "";
|
|
12614
12706
|
controlStream = null;
|
|
@@ -12690,7 +12782,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
12690
12782
|
this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
|
|
12691
12783
|
}
|
|
12692
12784
|
createSession(id, taskId) {
|
|
12693
|
-
return new HeadlessSession(id, taskId, this.spawnImpl);
|
|
12785
|
+
return new HeadlessSession(id, taskId, this.spawnImpl, this.defaultInteractive);
|
|
12694
12786
|
}
|
|
12695
12787
|
/** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
|
|
12696
12788
|
async prepareLaunch(opts, ptyOptions) {
|
|
@@ -12807,6 +12899,8 @@ var TmuxSession = class extends AbstractTerminalSession {
|
|
|
12807
12899
|
pollIntervalMs;
|
|
12808
12900
|
logPath;
|
|
12809
12901
|
socket;
|
|
12902
|
+
/** A tmux window is a real TTY running the agent's TUI, so it submits on Enter. */
|
|
12903
|
+
interactive = true;
|
|
12810
12904
|
outputBuffer = "";
|
|
12811
12905
|
offset = 0;
|
|
12812
12906
|
timer = null;
|