@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
|
@@ -458,6 +458,14 @@ interface ITerminalSession {
|
|
|
458
458
|
kill(): void;
|
|
459
459
|
getOutput(): string;
|
|
460
460
|
write(text: string): void;
|
|
461
|
+
/**
|
|
462
|
+
* True when the session runs the agent as a raw-mode TUI (a real PTY for the
|
|
463
|
+
* VS Code terminal, a tmux window). Such a surface submits an input line on
|
|
464
|
+
* the Enter keystroke (`\r`), so a synchronized resume token terminated with
|
|
465
|
+
* `\n` only types the line and never sends it. A line-oriented piped session
|
|
466
|
+
* (`defaultInteractive = false`) leaves this false and accepts `\n`.
|
|
467
|
+
*/
|
|
468
|
+
readonly interactive?: boolean;
|
|
461
469
|
/**
|
|
462
470
|
* Optional transport-level control channel: PTY resize requests for a session
|
|
463
471
|
* whose runner renders a TUI. Absent on transports without a resizable PTY
|
|
@@ -458,6 +458,14 @@ interface ITerminalSession {
|
|
|
458
458
|
kill(): void;
|
|
459
459
|
getOutput(): string;
|
|
460
460
|
write(text: string): void;
|
|
461
|
+
/**
|
|
462
|
+
* True when the session runs the agent as a raw-mode TUI (a real PTY for the
|
|
463
|
+
* VS Code terminal, a tmux window). Such a surface submits an input line on
|
|
464
|
+
* the Enter keystroke (`\r`), so a synchronized resume token terminated with
|
|
465
|
+
* `\n` only types the line and never sends it. A line-oriented piped session
|
|
466
|
+
* (`defaultInteractive = false`) leaves this false and accepts `\n`.
|
|
467
|
+
*/
|
|
468
|
+
readonly interactive?: boolean;
|
|
461
469
|
/**
|
|
462
470
|
* Optional transport-level control channel: PTY resize requests for a session
|
|
463
471
|
* whose runner renders a TUI. Absent on transports without a resizable PTY
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { h as RunnerId, c as PlanStatus, L as LegacyPlanState, a as DiscoveredModel, T as Task, d as ResearchProgress, R as ResearchLogEntry, C as ConversationMessage, n as TaskSnapshot, Q as QueuedMessage, A as ActiveTaskSession, g as ResearchToolType, b as PlanState, v as Verdict } from './Task-4rDGr8Su.mjs';
|
|
2
2
|
export { D as DiscoveredMode, M as Message, P as PlanModificationWarnings, e as ResearchStep, f as ResearchStepOutcome, S as StreamEvent, i as StreamStepEvent, j as StreamThinkingEvent, k as TaskMode, l as TaskModelAssignment, m as TaskOutputSummary, o as TaskStatus, p as TaskType, q as TaskWithParent, r as ThinkingBlock, U as UserPromptEntry, s as UserStep, V as ValidationCheck, t as ValidationContext, u as ValidationResult, w as VerificationCheck, x as addTaskToPlan, y as createEmptyPlan, z as createTask, B as emptyWarnings, E as flattenTasks, F as flattenTasksWithParents, G as migrateLegacyPlan, H as migratePlanState, I as migrateTask, J as removeTaskFromPlan, K as renumberTasks, N as updateTaskInPlan, O as validateModifiedPlan, W as warningsText } from './Task-4rDGr8Su.mjs';
|
|
3
|
-
import { f as IFileSystem, R as ReadFileOpts, T as ToolOutcome, d as GrepOptions, c as GlobOptions, b as FindSymbolOptions, I as IConfig, A as AiProvider, r as ProviderModelLists, i as ITerminalSession, h as ITerminalRunner, w as RunnerRegistry, k as OrchestratorOption, u as RunnerInvocation, g as IPluginStore, v as RunnerPluginManifest, t as ResolveContext } from './ITerminalRunner-
|
|
4
|
-
export { a as AllProviderModels, C as CatalogModel, D as DiscoveryCommand, F as FetchAllProviderModelsOptions, G as GREP_DEFAULT_HEAD_LIMIT, e as GrepOutputMode, M as ModelCatalog, j as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, P as PluginCloneFn, l as PluginEntry, m as PluginFeatures, n as PluginMode, o as PluginModelDiscovery, p as PluginRunnerDef, q as ProviderCredentialSource, s as ProviderModelsResult, S as SEARCH_EXCLUSIONS, x as collectProviderCredentials, y as enabledRunners, z as fetchAllProviderModels, B as isReservedRunnerName, E as knownModelId, H as resolveModelShortcut, J as resolveProvider, K as toOrchestratorOptions } from './ITerminalRunner-
|
|
3
|
+
import { f as IFileSystem, R as ReadFileOpts, T as ToolOutcome, d as GrepOptions, c as GlobOptions, b as FindSymbolOptions, I as IConfig, A as AiProvider, r as ProviderModelLists, i as ITerminalSession, h as ITerminalRunner, w as RunnerRegistry, k as OrchestratorOption, u as RunnerInvocation, g as IPluginStore, v as RunnerPluginManifest, t as ResolveContext } from './ITerminalRunner-C-HlFxuS.mjs';
|
|
4
|
+
export { a as AllProviderModels, C as CatalogModel, D as DiscoveryCommand, F as FetchAllProviderModelsOptions, G as GREP_DEFAULT_HEAD_LIMIT, e as GrepOutputMode, M as ModelCatalog, j as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, P as PluginCloneFn, l as PluginEntry, m as PluginFeatures, n as PluginMode, o as PluginModelDiscovery, p as PluginRunnerDef, q as ProviderCredentialSource, s as ProviderModelsResult, S as SEARCH_EXCLUSIONS, x as collectProviderCredentials, y as enabledRunners, z as fetchAllProviderModels, B as isReservedRunnerName, E as knownModelId, H as resolveModelShortcut, J as resolveProvider, K as toOrchestratorOptions } from './ITerminalRunner-C-HlFxuS.mjs';
|
|
5
5
|
import { I as IApproval, d as ApprovalRequest, a as ApprovalMode } from './ApprovalPolicy-BVhGdECT.mjs';
|
|
6
6
|
export { A as ApprovalKind, b as ApprovalPolicy, c as ApprovalPolicyOptions, e as ApprovalSource, D as DENY_ALL } from './ApprovalPolicy-BVhGdECT.mjs';
|
|
7
7
|
import { R as RunnerModeInfo } from './ModeResolver-mtIWrLXn.mjs';
|
|
@@ -936,10 +936,11 @@ interface HeadlessRunnerDeps {
|
|
|
936
936
|
}
|
|
937
937
|
declare class HeadlessSession extends AbstractTerminalSession {
|
|
938
938
|
private spawnImpl;
|
|
939
|
+
readonly interactive: boolean;
|
|
939
940
|
private process;
|
|
940
941
|
private outputBuffer;
|
|
941
942
|
private controlStream;
|
|
942
|
-
constructor(id: string, taskId: string, spawnImpl: SpawnFn);
|
|
943
|
+
constructor(id: string, taskId: string, spawnImpl: SpawnFn, interactive?: boolean);
|
|
943
944
|
get isStarted(): boolean;
|
|
944
945
|
start(launch: LaunchPlan, cwd: string, resolvedPath: string, env?: Record<string, string>, options?: {
|
|
945
946
|
controlChannel?: boolean;
|
|
@@ -2255,9 +2256,32 @@ declare class OpenCodeAdapter implements AgentAdapter {
|
|
|
2255
2256
|
private opts;
|
|
2256
2257
|
/** Whether this turn has already emitted reply text — see {@link emitPart}. */
|
|
2257
2258
|
private turnHasText;
|
|
2259
|
+
/** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
|
|
2260
|
+
private lastAssistantId;
|
|
2258
2261
|
constructor(deps: AgentProcessDeps);
|
|
2259
2262
|
start(opts: AgentStartOptions): Promise<void>;
|
|
2260
2263
|
send(message: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal, onActivity?: () => void): Promise<void>;
|
|
2264
|
+
/**
|
|
2265
|
+
* Turn one settled assistant message into events. The settled response is
|
|
2266
|
+
* authoritative: it names the assistant message, so its parts are the ones
|
|
2267
|
+
* that make up the reply. Tool parts already seen live are deduplicated by
|
|
2268
|
+
* call id; anything the stream missed (including a stream that never
|
|
2269
|
+
* connected) arrives here.
|
|
2270
|
+
*/
|
|
2271
|
+
private settle;
|
|
2272
|
+
/**
|
|
2273
|
+
* Poll the session for this turn's assistant message after the POST's socket
|
|
2274
|
+
* died under it. Node's global `fetch` is undici, which aborts a request
|
|
2275
|
+
* whose response headers have not arrived within 300s — and OpenCode sends
|
|
2276
|
+
* none until the turn is done, so any turn past five minutes fails as
|
|
2277
|
+
* `TypeError: fetch failed` while the server is still working. The message
|
|
2278
|
+
* exists server-side either way, so it is waited for and read back.
|
|
2279
|
+
*
|
|
2280
|
+
* A message that exists but has not completed is progress, not an answer:
|
|
2281
|
+
* it refreshes the watchdog and the poll continues.
|
|
2282
|
+
*/
|
|
2283
|
+
private recoverReply;
|
|
2284
|
+
private newestAssistantId;
|
|
2261
2285
|
/**
|
|
2262
2286
|
* Emit one message part, once. OpenCode reports a tool part repeatedly as it
|
|
2263
2287
|
* moves through pending → running → completed, so parts are keyed by id and
|
|
@@ -2845,6 +2869,14 @@ declare class VerdictEngine {
|
|
|
2845
2869
|
private touchIdle;
|
|
2846
2870
|
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
2847
2871
|
private clearIdle;
|
|
2872
|
+
/**
|
|
2873
|
+
* Submit a synchronized resume token to the paused session. An interactive
|
|
2874
|
+
* TUI only accepts the Enter keystroke (`\r`) — a `\n` types the token into
|
|
2875
|
+
* its composer without sending it, leaving the agent paused until a human
|
|
2876
|
+
* presses Enter. A line-oriented piped session has no composer; it reads a
|
|
2877
|
+
* `\n`-terminated line, and the leading newline flushes a partial line.
|
|
2878
|
+
*/
|
|
2879
|
+
private resumeToken;
|
|
2848
2880
|
approveCheckpoint(taskId: string): void;
|
|
2849
2881
|
rejectCheckpoint(taskId: string, reason: string): void;
|
|
2850
2882
|
/**
|
|
@@ -3143,6 +3175,8 @@ declare class TmuxSession extends AbstractTerminalSession {
|
|
|
3143
3175
|
private pollIntervalMs;
|
|
3144
3176
|
private logPath;
|
|
3145
3177
|
private socket;
|
|
3178
|
+
/** A tmux window is a real TTY running the agent's TUI, so it submits on Enter. */
|
|
3179
|
+
readonly interactive = true;
|
|
3146
3180
|
private outputBuffer;
|
|
3147
3181
|
private offset;
|
|
3148
3182
|
private timer;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { h as RunnerId, c as PlanStatus, L as LegacyPlanState, a as DiscoveredModel, T as Task, d as ResearchProgress, R as ResearchLogEntry, C as ConversationMessage, n as TaskSnapshot, Q as QueuedMessage, A as ActiveTaskSession, g as ResearchToolType, b as PlanState, v as Verdict } from './Task-4rDGr8Su.js';
|
|
2
2
|
export { D as DiscoveredMode, M as Message, P as PlanModificationWarnings, e as ResearchStep, f as ResearchStepOutcome, S as StreamEvent, i as StreamStepEvent, j as StreamThinkingEvent, k as TaskMode, l as TaskModelAssignment, m as TaskOutputSummary, o as TaskStatus, p as TaskType, q as TaskWithParent, r as ThinkingBlock, U as UserPromptEntry, s as UserStep, V as ValidationCheck, t as ValidationContext, u as ValidationResult, w as VerificationCheck, x as addTaskToPlan, y as createEmptyPlan, z as createTask, B as emptyWarnings, E as flattenTasks, F as flattenTasksWithParents, G as migrateLegacyPlan, H as migratePlanState, I as migrateTask, J as removeTaskFromPlan, K as renumberTasks, N as updateTaskInPlan, O as validateModifiedPlan, W as warningsText } from './Task-4rDGr8Su.js';
|
|
3
|
-
import { f as IFileSystem, R as ReadFileOpts, T as ToolOutcome, d as GrepOptions, c as GlobOptions, b as FindSymbolOptions, I as IConfig, A as AiProvider, r as ProviderModelLists, i as ITerminalSession, h as ITerminalRunner, w as RunnerRegistry, k as OrchestratorOption, u as RunnerInvocation, g as IPluginStore, v as RunnerPluginManifest, t as ResolveContext } from './ITerminalRunner-
|
|
4
|
-
export { a as AllProviderModels, C as CatalogModel, D as DiscoveryCommand, F as FetchAllProviderModelsOptions, G as GREP_DEFAULT_HEAD_LIMIT, e as GrepOutputMode, M as ModelCatalog, j as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, P as PluginCloneFn, l as PluginEntry, m as PluginFeatures, n as PluginMode, o as PluginModelDiscovery, p as PluginRunnerDef, q as ProviderCredentialSource, s as ProviderModelsResult, S as SEARCH_EXCLUSIONS, x as collectProviderCredentials, y as enabledRunners, z as fetchAllProviderModels, B as isReservedRunnerName, E as knownModelId, H as resolveModelShortcut, J as resolveProvider, K as toOrchestratorOptions } from './ITerminalRunner-
|
|
3
|
+
import { f as IFileSystem, R as ReadFileOpts, T as ToolOutcome, d as GrepOptions, c as GlobOptions, b as FindSymbolOptions, I as IConfig, A as AiProvider, r as ProviderModelLists, i as ITerminalSession, h as ITerminalRunner, w as RunnerRegistry, k as OrchestratorOption, u as RunnerInvocation, g as IPluginStore, v as RunnerPluginManifest, t as ResolveContext } from './ITerminalRunner-BVU7TfT3.js';
|
|
4
|
+
export { a as AllProviderModels, C as CatalogModel, D as DiscoveryCommand, F as FetchAllProviderModelsOptions, G as GREP_DEFAULT_HEAD_LIMIT, e as GrepOutputMode, M as ModelCatalog, j as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, P as PluginCloneFn, l as PluginEntry, m as PluginFeatures, n as PluginMode, o as PluginModelDiscovery, p as PluginRunnerDef, q as ProviderCredentialSource, s as ProviderModelsResult, S as SEARCH_EXCLUSIONS, x as collectProviderCredentials, y as enabledRunners, z as fetchAllProviderModels, B as isReservedRunnerName, E as knownModelId, H as resolveModelShortcut, J as resolveProvider, K as toOrchestratorOptions } from './ITerminalRunner-BVU7TfT3.js';
|
|
5
5
|
import { I as IApproval, d as ApprovalRequest, a as ApprovalMode } from './ApprovalPolicy-BVhGdECT.js';
|
|
6
6
|
export { A as ApprovalKind, b as ApprovalPolicy, c as ApprovalPolicyOptions, e as ApprovalSource, D as DENY_ALL } from './ApprovalPolicy-BVhGdECT.js';
|
|
7
7
|
import { R as RunnerModeInfo } from './ModeResolver-Ob-ihQbI.js';
|
|
@@ -936,10 +936,11 @@ interface HeadlessRunnerDeps {
|
|
|
936
936
|
}
|
|
937
937
|
declare class HeadlessSession extends AbstractTerminalSession {
|
|
938
938
|
private spawnImpl;
|
|
939
|
+
readonly interactive: boolean;
|
|
939
940
|
private process;
|
|
940
941
|
private outputBuffer;
|
|
941
942
|
private controlStream;
|
|
942
|
-
constructor(id: string, taskId: string, spawnImpl: SpawnFn);
|
|
943
|
+
constructor(id: string, taskId: string, spawnImpl: SpawnFn, interactive?: boolean);
|
|
943
944
|
get isStarted(): boolean;
|
|
944
945
|
start(launch: LaunchPlan, cwd: string, resolvedPath: string, env?: Record<string, string>, options?: {
|
|
945
946
|
controlChannel?: boolean;
|
|
@@ -2255,9 +2256,32 @@ declare class OpenCodeAdapter implements AgentAdapter {
|
|
|
2255
2256
|
private opts;
|
|
2256
2257
|
/** Whether this turn has already emitted reply text — see {@link emitPart}. */
|
|
2257
2258
|
private turnHasText;
|
|
2259
|
+
/** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
|
|
2260
|
+
private lastAssistantId;
|
|
2258
2261
|
constructor(deps: AgentProcessDeps);
|
|
2259
2262
|
start(opts: AgentStartOptions): Promise<void>;
|
|
2260
2263
|
send(message: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal, onActivity?: () => void): Promise<void>;
|
|
2264
|
+
/**
|
|
2265
|
+
* Turn one settled assistant message into events. The settled response is
|
|
2266
|
+
* authoritative: it names the assistant message, so its parts are the ones
|
|
2267
|
+
* that make up the reply. Tool parts already seen live are deduplicated by
|
|
2268
|
+
* call id; anything the stream missed (including a stream that never
|
|
2269
|
+
* connected) arrives here.
|
|
2270
|
+
*/
|
|
2271
|
+
private settle;
|
|
2272
|
+
/**
|
|
2273
|
+
* Poll the session for this turn's assistant message after the POST's socket
|
|
2274
|
+
* died under it. Node's global `fetch` is undici, which aborts a request
|
|
2275
|
+
* whose response headers have not arrived within 300s — and OpenCode sends
|
|
2276
|
+
* none until the turn is done, so any turn past five minutes fails as
|
|
2277
|
+
* `TypeError: fetch failed` while the server is still working. The message
|
|
2278
|
+
* exists server-side either way, so it is waited for and read back.
|
|
2279
|
+
*
|
|
2280
|
+
* A message that exists but has not completed is progress, not an answer:
|
|
2281
|
+
* it refreshes the watchdog and the poll continues.
|
|
2282
|
+
*/
|
|
2283
|
+
private recoverReply;
|
|
2284
|
+
private newestAssistantId;
|
|
2261
2285
|
/**
|
|
2262
2286
|
* Emit one message part, once. OpenCode reports a tool part repeatedly as it
|
|
2263
2287
|
* moves through pending → running → completed, so parts are keyed by id and
|
|
@@ -2845,6 +2869,14 @@ declare class VerdictEngine {
|
|
|
2845
2869
|
private touchIdle;
|
|
2846
2870
|
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
2847
2871
|
private clearIdle;
|
|
2872
|
+
/**
|
|
2873
|
+
* Submit a synchronized resume token to the paused session. An interactive
|
|
2874
|
+
* TUI only accepts the Enter keystroke (`\r`) — a `\n` types the token into
|
|
2875
|
+
* its composer without sending it, leaving the agent paused until a human
|
|
2876
|
+
* presses Enter. A line-oriented piped session has no composer; it reads a
|
|
2877
|
+
* `\n`-terminated line, and the leading newline flushes a partial line.
|
|
2878
|
+
*/
|
|
2879
|
+
private resumeToken;
|
|
2848
2880
|
approveCheckpoint(taskId: string): void;
|
|
2849
2881
|
rejectCheckpoint(taskId: string, reason: string): void;
|
|
2850
2882
|
/**
|
|
@@ -3143,6 +3175,8 @@ declare class TmuxSession extends AbstractTerminalSession {
|
|
|
3143
3175
|
private pollIntervalMs;
|
|
3144
3176
|
private logPath;
|
|
3145
3177
|
private socket;
|
|
3178
|
+
/** A tmux window is a real TTY running the agent's TUI, so it submits on Enter. */
|
|
3179
|
+
readonly interactive = true;
|
|
3146
3180
|
private outputBuffer;
|
|
3147
3181
|
private offset;
|
|
3148
3182
|
private timer;
|
package/dist/index.js
CHANGED
|
@@ -8114,19 +8114,29 @@ var VerdictEngine = class {
|
|
|
8114
8114
|
for (const l of this.idleListeners) l(taskId, null);
|
|
8115
8115
|
}
|
|
8116
8116
|
}
|
|
8117
|
+
/**
|
|
8118
|
+
* Submit a synchronized resume token to the paused session. An interactive
|
|
8119
|
+
* TUI only accepts the Enter keystroke (`\r`) — a `\n` types the token into
|
|
8120
|
+
* its composer without sending it, leaving the agent paused until a human
|
|
8121
|
+
* presses Enter. A line-oriented piped session has no composer; it reads a
|
|
8122
|
+
* `\n`-terminated line, and the leading newline flushes a partial line.
|
|
8123
|
+
*/
|
|
8124
|
+
resumeToken(session, line) {
|
|
8125
|
+
return session.interactive ? `${line}\r` : `
|
|
8126
|
+
${line}
|
|
8127
|
+
`;
|
|
8128
|
+
}
|
|
8117
8129
|
approveCheckpoint(taskId) {
|
|
8118
8130
|
const session = this.pausedSessions.get(taskId);
|
|
8119
8131
|
if (session) {
|
|
8120
|
-
session.write("
|
|
8132
|
+
session.write(this.resumeToken(session, "ORDEWELL_CONTINUE"));
|
|
8121
8133
|
this.pausedSessions.delete(taskId);
|
|
8122
8134
|
}
|
|
8123
8135
|
}
|
|
8124
8136
|
rejectCheckpoint(taskId, reason) {
|
|
8125
8137
|
const session = this.pausedSessions.get(taskId);
|
|
8126
8138
|
if (session) {
|
|
8127
|
-
session.write(`
|
|
8128
|
-
ORDEWELL_REJECT: ${reason}
|
|
8129
|
-
`);
|
|
8139
|
+
session.write(this.resumeToken(session, `ORDEWELL_REJECT: ${reason}`));
|
|
8130
8140
|
this.pausedSessions.delete(taskId);
|
|
8131
8141
|
}
|
|
8132
8142
|
}
|
|
@@ -11528,6 +11538,8 @@ ${item.text}` : item.text });
|
|
|
11528
11538
|
var SERVER_READY_TIMEOUT_MS = 3e4;
|
|
11529
11539
|
var STDERR_TAIL_CHARS2 = 4e3;
|
|
11530
11540
|
var STREAM_CONNECT_TIMEOUT_MS = 5e3;
|
|
11541
|
+
var RECOVERY_POLL_INTERVAL_MS = 2e3;
|
|
11542
|
+
var RECOVERY_TIMEOUT_MS = 9e5;
|
|
11531
11543
|
var DISABLED_TOOLS = {
|
|
11532
11544
|
question: false,
|
|
11533
11545
|
edit: false,
|
|
@@ -11540,6 +11552,33 @@ function splitModelId(id) {
|
|
|
11540
11552
|
if (slash <= 0 || slash === id.length - 1) return null;
|
|
11541
11553
|
return { providerID: id.slice(0, slash), modelID: id.slice(slash + 1) };
|
|
11542
11554
|
}
|
|
11555
|
+
function describeError(err) {
|
|
11556
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11557
|
+
const lines = [];
|
|
11558
|
+
let current = err;
|
|
11559
|
+
while (current && !visited.has(current)) {
|
|
11560
|
+
visited.add(current);
|
|
11561
|
+
if (!(current instanceof Error)) {
|
|
11562
|
+
lines.push(String(current));
|
|
11563
|
+
break;
|
|
11564
|
+
}
|
|
11565
|
+
const code = current.code;
|
|
11566
|
+
const suffix = typeof code === "string" && !current.message.includes(code) ? ` (${code})` : "";
|
|
11567
|
+
lines.push(`${current.message}${suffix}`);
|
|
11568
|
+
current = current.cause;
|
|
11569
|
+
}
|
|
11570
|
+
return lines.join(": ");
|
|
11571
|
+
}
|
|
11572
|
+
function delay(ms, signal) {
|
|
11573
|
+
return new Promise((resolve4) => {
|
|
11574
|
+
const timer = setTimeout(resolve4, ms);
|
|
11575
|
+
timer.unref?.();
|
|
11576
|
+
signal?.addEventListener("abort", () => {
|
|
11577
|
+
clearTimeout(timer);
|
|
11578
|
+
resolve4();
|
|
11579
|
+
}, { once: true });
|
|
11580
|
+
});
|
|
11581
|
+
}
|
|
11543
11582
|
var OpenCodeAdapter = class {
|
|
11544
11583
|
constructor(deps) {
|
|
11545
11584
|
this.deps = deps;
|
|
@@ -11555,6 +11594,8 @@ var OpenCodeAdapter = class {
|
|
|
11555
11594
|
opts = null;
|
|
11556
11595
|
/** Whether this turn has already emitted reply text — see {@link emitPart}. */
|
|
11557
11596
|
turnHasText = false;
|
|
11597
|
+
/** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
|
|
11598
|
+
lastAssistantId = null;
|
|
11558
11599
|
async start(opts) {
|
|
11559
11600
|
this.opts = opts;
|
|
11560
11601
|
assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
|
|
@@ -11608,6 +11649,8 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
11608
11649
|
const existing = await this.json("GET", `/session/${opts.resumeSessionId}`).catch(() => null);
|
|
11609
11650
|
if (existing?.id) {
|
|
11610
11651
|
this.sessionId = existing.id;
|
|
11652
|
+
const history = await this.json("GET", `/session/${existing.id}/message`).catch(() => null);
|
|
11653
|
+
this.lastAssistantId = this.newestAssistantId(history);
|
|
11611
11654
|
return;
|
|
11612
11655
|
}
|
|
11613
11656
|
}
|
|
@@ -11658,29 +11701,76 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
11658
11701
|
this.dispose();
|
|
11659
11702
|
return;
|
|
11660
11703
|
}
|
|
11661
|
-
|
|
11662
|
-
if (failure) {
|
|
11663
|
-
onEvent({ type: "error", message: failure });
|
|
11664
|
-
return;
|
|
11665
|
-
}
|
|
11666
|
-
const assistantId = reply?.info?.id;
|
|
11667
|
-
for (const part of reply?.parts ?? []) {
|
|
11668
|
-
if (part.type !== "tool" && assistantId && part.messageID !== assistantId) continue;
|
|
11669
|
-
this.emitPart(part, seen, onEvent);
|
|
11670
|
-
}
|
|
11671
|
-
onEvent({ type: "turn_end" });
|
|
11704
|
+
this.settle(reply, seen, onEvent);
|
|
11672
11705
|
} catch (err) {
|
|
11673
11706
|
if (signal?.aborted) {
|
|
11674
11707
|
this.dispose();
|
|
11675
11708
|
return;
|
|
11676
11709
|
}
|
|
11677
|
-
|
|
11710
|
+
const recovered = this.exited ? null : await this.recoverReply(signal, onActivity);
|
|
11711
|
+
if (recovered) {
|
|
11712
|
+
this.settle(recovered, seen, onEvent);
|
|
11713
|
+
return;
|
|
11714
|
+
}
|
|
11715
|
+
onEvent({ type: "error", message: `The OpenCode planner turn failed: ${describeError(err)}` });
|
|
11678
11716
|
} finally {
|
|
11679
11717
|
streamAbort.abort();
|
|
11680
11718
|
await live.catch(() => {
|
|
11681
11719
|
});
|
|
11682
11720
|
}
|
|
11683
11721
|
}
|
|
11722
|
+
/**
|
|
11723
|
+
* Turn one settled assistant message into events. The settled response is
|
|
11724
|
+
* authoritative: it names the assistant message, so its parts are the ones
|
|
11725
|
+
* that make up the reply. Tool parts already seen live are deduplicated by
|
|
11726
|
+
* call id; anything the stream missed (including a stream that never
|
|
11727
|
+
* connected) arrives here.
|
|
11728
|
+
*/
|
|
11729
|
+
settle(reply, seen, onEvent) {
|
|
11730
|
+
const failure = typeof reply?.error === "string" ? reply.error : reply?.error?.message ?? reply?.info?.error?.data?.message;
|
|
11731
|
+
if (failure) {
|
|
11732
|
+
onEvent({ type: "error", message: failure });
|
|
11733
|
+
return;
|
|
11734
|
+
}
|
|
11735
|
+
const assistantId = reply?.info?.id;
|
|
11736
|
+
for (const part of reply?.parts ?? []) {
|
|
11737
|
+
if (part.type !== "tool" && assistantId && part.messageID !== assistantId) continue;
|
|
11738
|
+
this.emitPart(part, seen, onEvent);
|
|
11739
|
+
}
|
|
11740
|
+
if (assistantId) this.lastAssistantId = assistantId;
|
|
11741
|
+
onEvent({ type: "turn_end" });
|
|
11742
|
+
}
|
|
11743
|
+
/**
|
|
11744
|
+
* Poll the session for this turn's assistant message after the POST's socket
|
|
11745
|
+
* died under it. Node's global `fetch` is undici, which aborts a request
|
|
11746
|
+
* whose response headers have not arrived within 300s — and OpenCode sends
|
|
11747
|
+
* none until the turn is done, so any turn past five minutes fails as
|
|
11748
|
+
* `TypeError: fetch failed` while the server is still working. The message
|
|
11749
|
+
* exists server-side either way, so it is waited for and read back.
|
|
11750
|
+
*
|
|
11751
|
+
* A message that exists but has not completed is progress, not an answer:
|
|
11752
|
+
* it refreshes the watchdog and the poll continues.
|
|
11753
|
+
*/
|
|
11754
|
+
async recoverReply(signal, onActivity) {
|
|
11755
|
+
const deadline = Date.now() + RECOVERY_TIMEOUT_MS;
|
|
11756
|
+
for (; ; ) {
|
|
11757
|
+
if (signal?.aborted || this.exited || this.disposed || !this.sessionId) return null;
|
|
11758
|
+
const messages = await this.json("GET", `/session/${this.sessionId}/message`).catch(() => null);
|
|
11759
|
+
const pending = Array.isArray(messages) ? [...messages].reverse().find((m) => m.info?.role === "assistant" && m.info.id && m.info.id !== this.lastAssistantId) : void 0;
|
|
11760
|
+
if (pending?.info?.time?.completed || pending?.info?.error) return pending;
|
|
11761
|
+
if (pending) onActivity?.();
|
|
11762
|
+
if (Date.now() >= deadline) return null;
|
|
11763
|
+
await delay(RECOVERY_POLL_INTERVAL_MS, signal);
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11766
|
+
newestAssistantId(messages) {
|
|
11767
|
+
if (!Array.isArray(messages)) return null;
|
|
11768
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11769
|
+
const info = messages[i]?.info;
|
|
11770
|
+
if (info?.role === "assistant" && info.id) return info.id;
|
|
11771
|
+
}
|
|
11772
|
+
return null;
|
|
11773
|
+
}
|
|
11684
11774
|
/**
|
|
11685
11775
|
* Emit one message part, once. OpenCode reports a tool part repeatedly as it
|
|
11686
11776
|
* moves through pending → running → completed, so parts are keyed by id and
|
|
@@ -14553,11 +14643,13 @@ function defaultHasScriptCmd() {
|
|
|
14553
14643
|
}
|
|
14554
14644
|
}
|
|
14555
14645
|
var HeadlessSession = class extends AbstractTerminalSession {
|
|
14556
|
-
constructor(id, taskId, spawnImpl) {
|
|
14646
|
+
constructor(id, taskId, spawnImpl, interactive = false) {
|
|
14557
14647
|
super(id, taskId);
|
|
14558
14648
|
this.spawnImpl = spawnImpl;
|
|
14649
|
+
this.interactive = interactive;
|
|
14559
14650
|
}
|
|
14560
14651
|
spawnImpl;
|
|
14652
|
+
interactive;
|
|
14561
14653
|
process = null;
|
|
14562
14654
|
outputBuffer = "";
|
|
14563
14655
|
controlStream = null;
|
|
@@ -14639,7 +14731,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
14639
14731
|
this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
|
|
14640
14732
|
}
|
|
14641
14733
|
createSession(id, taskId) {
|
|
14642
|
-
return new HeadlessSession(id, taskId, this.spawnImpl);
|
|
14734
|
+
return new HeadlessSession(id, taskId, this.spawnImpl, this.defaultInteractive);
|
|
14643
14735
|
}
|
|
14644
14736
|
/** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
|
|
14645
14737
|
async prepareLaunch(opts, ptyOptions) {
|
|
@@ -14756,6 +14848,8 @@ var TmuxSession = class extends AbstractTerminalSession {
|
|
|
14756
14848
|
pollIntervalMs;
|
|
14757
14849
|
logPath;
|
|
14758
14850
|
socket;
|
|
14851
|
+
/** A tmux window is a real TTY running the agent's TUI, so it submits on Enter. */
|
|
14852
|
+
interactive = true;
|
|
14759
14853
|
outputBuffer = "";
|
|
14760
14854
|
offset = 0;
|
|
14761
14855
|
timer = null;
|